From 23526cb2215d3f6554ab4227c5931211153ff4e4 Mon Sep 17 00:00:00 2001 From: Adam Novak Date: Fri, 17 Apr 2020 11:14:35 -0700 Subject: Read schema from package resource --- bh20simplewebuploader/main.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bh20simplewebuploader/main.py b/bh20simplewebuploader/main.py index f5324a5..13c3fef 100644 --- a/bh20simplewebuploader/main.py +++ b/bh20simplewebuploader/main.py @@ -7,7 +7,7 @@ import sys import re import string import yaml -import urllib.request +import pkg_resources from flask import Flask, request, redirect, send_file, send_from_directory, render_template import os.path @@ -133,8 +133,8 @@ def generate_form(schema): return list(walk_fields(root_name)) -# At startup, we need to load the current metadata schema so we can make a form for it -METADATA_SCHEMA = yaml.safe_load(urllib.request.urlopen('https://raw.githubusercontent.com/arvados/bh20-seq-resource/master/bh20sequploader/bh20seq-schema.yml')) +# At startup, we need to load the metadata schema from the uploader module, so we can make a form for it +METADATA_SCHEMA = yaml.safe_load(pkg_resources.resource_stream("bh20sequploader", "bh20seq-schema.yml")) FORM_ITEMS = generate_form(METADATA_SCHEMA) @app.route('/') -- cgit v1.2.3 From e823bf1b464fcb8f52a91dcaebaf5b82cafc06dc Mon Sep 17 00:00:00 2001 From: Adam Novak Date: Fri, 17 Apr 2020 11:27:26 -0700 Subject: Don't use @id as a URL if no URL is found --- bh20simplewebuploader/main.py | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/bh20simplewebuploader/main.py b/bh20simplewebuploader/main.py index 13c3fef..0b1616b 100644 --- a/bh20simplewebuploader/main.py +++ b/bh20simplewebuploader/main.py @@ -49,6 +49,17 @@ def name_to_label(field_name): return string.capwords(field_name.replace('_', ' ')) +def is_url(string): + """ + Return True if the given string looks like a URL, and False otherwise. + + Used for finding type URLs in the schema. + + Right now only supports http(s) URLs because that's all we have in our schema. + """ + + return string.startswith('http') + def generate_form(schema): """ Linearize the schema and send a bunch of dicts. @@ -93,13 +104,28 @@ def generate_form(schema): ref_url = None if not isinstance(field_type, str): # If the type isn't a string + # See if it has a more info/what goes here URL predicate = field_type.get('jsonldPredicate', {}) - if not isinstance(predicate, str): - ref_url = predicate.get('_id', None) + # Predicate may be a URL, a dict with a URL in _id, maybe a + # dict with a URL in _type, or a dict with _id and _type but no + # URLs anywhere. Some of these may not technically be allowed + # by the format, but if they occur, we might as well try to + # handle them. + if isinstance(predicate, str): + if is_url(predicate): + ref_url = predicate else: - ref_url = predicate # not sure this is correct - # Grab out its type field + # Assume it's a dict. Look at the fields we know about. + for field in ['_id', 'type']: + field_value = predicate.get(field, None) + if isinstance(field_value, str) and is_url(field_value) and ref_url is None: + # Take the first URL-looking thing we find + ref_url = field_value + break + + + # Now overwrite the field type with the actual type string field_type = field_type.get('type', '') # Decide if the field is optional (type ends in ?) -- cgit v1.2.3 From ea55ccc246a032e9c32e0899785da068be2e8dc5 Mon Sep 17 00:00:00 2001 From: Adam Novak Date: Fri, 17 Apr 2020 11:35:49 -0700 Subject: Call URLs IRIs because that's more general --- bh20simplewebuploader/main.py | 20 ++++++++++---------- bh20simplewebuploader/templates/form.html | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/bh20simplewebuploader/main.py b/bh20simplewebuploader/main.py index 0b1616b..253f0e5 100644 --- a/bh20simplewebuploader/main.py +++ b/bh20simplewebuploader/main.py @@ -49,11 +49,11 @@ def name_to_label(field_name): return string.capwords(field_name.replace('_', ' ')) -def is_url(string): +def is_iri(string): """ - Return True if the given string looks like a URL, and False otherwise. + Return True if the given string looks like an IRI, and False otherwise. - Used for finding type URLs in the schema. + Used for finding type IRIs in the schema. Right now only supports http(s) URLs because that's all we have in our schema. """ @@ -101,7 +101,7 @@ def generate_form(schema): for field_name, field_type in by_name.get(type_name, {}).get('fields', {}).items(): # For each field - ref_url = None + ref_iri = None if not isinstance(field_type, str): # If the type isn't a string @@ -113,15 +113,15 @@ def generate_form(schema): # by the format, but if they occur, we might as well try to # handle them. if isinstance(predicate, str): - if is_url(predicate): - ref_url = predicate + if is_iri(predicate): + ref_iri = predicate else: # Assume it's a dict. Look at the fields we know about. for field in ['_id', 'type']: field_value = predicate.get(field, None) - if isinstance(field_value, str) and is_url(field_value) and ref_url is None: + if isinstance(field_value, str) and is_iri(field_value) and ref_iri is None: # Take the first URL-looking thing we find - ref_url = field_value + ref_iri = field_value break @@ -146,8 +146,8 @@ def generate_form(schema): record['id'] = '.'.join(parent_keys + [field_name]) record['label'] = name_to_label(field_name) record['required'] = not optional and not subtree_optional - if ref_url: - record['ref_url'] = ref_url + if ref_iri: + record['ref_iri'] = ref_iri if field_type == 'string': record['type'] = 'text' # HTML input type elif field_type == 'int': diff --git a/bh20simplewebuploader/templates/form.html b/bh20simplewebuploader/templates/form.html index df66e8c..9cfb60f 100644 --- a/bh20simplewebuploader/templates/form.html +++ b/bh20simplewebuploader/templates/form.html @@ -224,8 +224,8 @@ -- cgit v1.2.3 From 6c09aa8e672acd8c0deb593150ab3d0e5893d2ae Mon Sep 17 00:00:00 2001 From: Adam Novak Date: Fri, 17 Apr 2020 11:36:28 -0700 Subject: Correct maximum file size message --- bh20simplewebuploader/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bh20simplewebuploader/main.py b/bh20simplewebuploader/main.py index 253f0e5..c7f63ea 100644 --- a/bh20simplewebuploader/main.py +++ b/bh20simplewebuploader/main.py @@ -25,7 +25,7 @@ app.config['MAX_CONTENT_LENGTH'] = 50 * 1024 * 1024 @app.errorhandler(413) def handle_large_file(e): return (render_template('error.html', - error_message="One of your files is too large. The maximum file size is 1 megabyte."), 413) + error_message="One of your files is too large. The maximum file size is 50 megabytes."), 413) def type_to_heading(type_name): -- cgit v1.2.3 From 3a10517123c261d5ce26f174b97764fcaad06e93 Mon Sep 17 00:00:00 2001 From: Adam Novak Date: Fri, 17 Apr 2020 12:02:51 -0700 Subject: Create option dropdowns from an options file --- bh20sequploader/bh20seq-options.yml | 16 +++++++++++++ bh20simplewebuploader/main.py | 37 ++++++++++++++++++++++++++----- bh20simplewebuploader/templates/form.html | 10 ++++++++- setup.py | 2 +- 4 files changed, 57 insertions(+), 8 deletions(-) create mode 100644 bh20sequploader/bh20seq-options.yml diff --git a/bh20sequploader/bh20seq-options.yml b/bh20sequploader/bh20seq-options.yml new file mode 100644 index 0000000..02e911f --- /dev/null +++ b/bh20sequploader/bh20seq-options.yml @@ -0,0 +1,16 @@ +# Contains suggested human-readable field values and their corresponding IRIs. +# Keyed on the field names in the types in the schema. Relies on field names +# being unique or at least using the same options in different containing +# types. + +host_age_unit: + year: http://purl.obolibrary.org/obo/UO_0000036 + month: http://purl.obolibrary.org/obo/UO_0000035 + week: http://purl.obolibrary.org/obo/UO_0000035 + day: http://purl.obolibrary.org/obo/UO_0000034 + hour: http://purl.obolibrary.org/obo/UO_0000032 + +host_sex: + Male: http://purl.obolibrary.org/obo/NCIT_C20197 + Female: http://purl.obolibrary.org/obo/NCIT_C27993 + unknown: http://purl.obolibrary.org/obo/NCIT_C17998 diff --git a/bh20simplewebuploader/main.py b/bh20simplewebuploader/main.py index c7f63ea..c8fdc3f 100644 --- a/bh20simplewebuploader/main.py +++ b/bh20simplewebuploader/main.py @@ -60,14 +60,25 @@ def is_iri(string): return string.startswith('http') -def generate_form(schema): +def generate_form(schema, options): """ - Linearize the schema and send a bunch of dicts. + Linearize the schema into a list of dicts. + Each dict either has a 'heading' (in which case we put a heading for a form section in the template) or an 'id', 'label', 'type', and 'required' - (in which case we make a form field in the template). + (in which case we make a form field in the template). Non-heading dicts + with type 'select' will have an 'options' field, with a list of (name, + value) tuples, and represent a form dropdown element. + + Takes the deserialized metadata schema YAML, and also a deserialized YAML + of option values. The option values are keyed on (unscoped) field name in + the schema, and each is a dict of human readable option -> corresponding + IRI. """ + print(schema) + print(options) + # Get the list of form components, one of which is the root components = schema.get('$graph', []) @@ -141,14 +152,27 @@ def generate_form(schema): for item in walk_fields(field_type, parent_keys + [field_name], subtree_optional or optional): yield item else: - # We know how to make a string input + # This is a leaf field. We need an input for it. record = {} record['id'] = '.'.join(parent_keys + [field_name]) record['label'] = name_to_label(field_name) record['required'] = not optional and not subtree_optional if ref_iri: record['ref_iri'] = ref_iri - if field_type == 'string': + + print(field_name) + + if field_name in options: + print("Has options: {}".format(options[field_name])) + # The field will be a 'select' type no matter what its real + # data type is. + record['type'] = 'select' # Not a real HTML input type. It's its own tag. + # We have a set of values to present + record['options'] = [] + for name, value in options[field_name].items(): + # Make a tuple for each one + record['options'].append((name, value)) + elif field_type == 'string': record['type'] = 'text' # HTML input type elif field_type == 'int': record['type'] = 'number' @@ -161,7 +185,8 @@ def generate_form(schema): # At startup, we need to load the metadata schema from the uploader module, so we can make a form for it METADATA_SCHEMA = yaml.safe_load(pkg_resources.resource_stream("bh20sequploader", "bh20seq-schema.yml")) -FORM_ITEMS = generate_form(METADATA_SCHEMA) +METADATA_OPTION_DEFINITIONS = yaml.safe_load(pkg_resources.resource_stream("bh20sequploader", "bh20seq-options.yml")) +FORM_ITEMS = generate_form(METADATA_SCHEMA, METADATA_OPTION_DEFINITIONS) @app.route('/') def send_form(): diff --git a/bh20simplewebuploader/templates/form.html b/bh20simplewebuploader/templates/form.html index 9cfb60f..012b9c5 100644 --- a/bh20simplewebuploader/templates/form.html +++ b/bh20simplewebuploader/templates/form.html @@ -211,7 +211,6 @@
- {{ record }} {% for record in fields %} {% if 'heading' in record %} @@ -228,8 +227,17 @@ ? {% endif %} + {% if record['type'] == 'select' %} + + {% else %} {% endif %} + {% endif %} {% if loop.index == loop.length %}
{% endif %} diff --git a/setup.py b/setup.py index 18e858e..0e91274 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,7 @@ setup( author_email="peter.amstutz@curii.com", license="Apache 2.0", packages=["bh20sequploader", "bh20seqanalyzer", "bh20simplewebuploader"], - package_data={"bh20sequploader": ["bh20seq-schema.yml", "validation/formats"], + package_data={"bh20sequploader": ["bh20seq-schema.yml", "bh20seq-options.yml", "validation/formats"], }, install_requires=install_requires, extras_require={ -- cgit v1.2.3 From 8a65eaff3147cf9908159c0a2a38f86a4043fe3e Mon Sep 17 00:00:00 2001 From: Adam Novak Date: Fri, 17 Apr 2020 12:20:28 -0700 Subject: Remove extraneous prints --- bh20simplewebuploader/main.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/bh20simplewebuploader/main.py b/bh20simplewebuploader/main.py index c8fdc3f..0db7d84 100644 --- a/bh20simplewebuploader/main.py +++ b/bh20simplewebuploader/main.py @@ -76,9 +76,6 @@ def generate_form(schema, options): IRI. """ - print(schema) - print(options) - # Get the list of form components, one of which is the root components = schema.get('$graph', []) @@ -160,10 +157,7 @@ def generate_form(schema, options): if ref_iri: record['ref_iri'] = ref_iri - print(field_name) - if field_name in options: - print("Has options: {}".format(options[field_name])) # The field will be a 'select' type no matter what its real # data type is. record['type'] = 'select' # Not a real HTML input type. It's its own tag. -- cgit v1.2.3 From c536f92264e094d4a59c1fdcb6a3aea64c0035b2 Mon Sep 17 00:00:00 2001 From: Adam Novak Date: Fri, 17 Apr 2020 12:18:53 -0700 Subject: Spruce up the options file and add missing values --- bh20sequploader/bh20seq-options.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/bh20sequploader/bh20seq-options.yml b/bh20sequploader/bh20seq-options.yml index 02e911f..d05be5a 100644 --- a/bh20sequploader/bh20seq-options.yml +++ b/bh20sequploader/bh20seq-options.yml @@ -4,13 +4,14 @@ # types. host_age_unit: - year: http://purl.obolibrary.org/obo/UO_0000036 - month: http://purl.obolibrary.org/obo/UO_0000035 - week: http://purl.obolibrary.org/obo/UO_0000035 - day: http://purl.obolibrary.org/obo/UO_0000034 - hour: http://purl.obolibrary.org/obo/UO_0000032 + Years: http://purl.obolibrary.org/obo/UO_0000036 + Months: http://purl.obolibrary.org/obo/UO_0000035 + Weeks: http://purl.obolibrary.org/obo/UO_0000034 + Days: http://purl.obolibrary.org/obo/UO_0000033 + Hours: http://purl.obolibrary.org/obo/UO_0000032 host_sex: Male: http://purl.obolibrary.org/obo/NCIT_C20197 Female: http://purl.obolibrary.org/obo/NCIT_C27993 - unknown: http://purl.obolibrary.org/obo/NCIT_C17998 + Intersex: http://purl.obolibrary.org/obo/NCIT_C45908 + Unknown: http://purl.obolibrary.org/obo/NCIT_C17998 -- cgit v1.2.3 From 88e6bb6e82f606c91fca6b3edd410cb28146f569 Mon Sep 17 00:00:00 2001 From: Adam Novak Date: Fri, 17 Apr 2020 12:19:33 -0700 Subject: Copy NCIT IRIs back to schema doc --- bh20sequploader/bh20seq-schema.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bh20sequploader/bh20seq-schema.yml b/bh20sequploader/bh20seq-schema.yml index d8641a6..0520e36 100644 --- a/bh20sequploader/bh20seq-schema.yml +++ b/bh20sequploader/bh20seq-schema.yml @@ -30,7 +30,7 @@ $graph: # jsonldPredicate: # _id: http://purl.obolibrary.org/obo/NOMEN_0000037 host_sex: - doc: Sex of the host as define in NCIT, IRI expected (http://purl.obolibrary.org/obo/C20197 (Male), http://purl.obolibrary.org/obo/NCIT_C27993 (Female) or unkown (http://purl.obolibrary.org/obo/NCIT_C17998)) + doc: Sex of the host as defined in NCIT, IRI expected (http://purl.obolibrary.org/obo/NCIT_C20197 (Male), http://purl.obolibrary.org/obo/NCIT_C27993 (Female), http://purl.obolibrary.org/obo/NCIT_C45908 (Intersex), or http://purl.obolibrary.org/obo/NCIT_C17998 (Unknown)) type: string jsonldPredicate: _id: http://purl.obolibrary.org/obo/PATO_0000047 -- cgit v1.2.3 From b2d3b101b7a44932632036146b7c271acfd67593 Mon Sep 17 00:00:00 2001 From: Adam Novak Date: Fri, 17 Apr 2020 13:07:18 -0700 Subject: Pull 'doc' from the schema through to the frontend --- bh20simplewebuploader/main.py | 9 ++++++++- bh20simplewebuploader/templates/form.html | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/bh20simplewebuploader/main.py b/bh20simplewebuploader/main.py index 0db7d84..8c5c18c 100644 --- a/bh20simplewebuploader/main.py +++ b/bh20simplewebuploader/main.py @@ -68,7 +68,8 @@ def generate_form(schema, options): form section in the template) or an 'id', 'label', 'type', and 'required' (in which case we make a form field in the template). Non-heading dicts with type 'select' will have an 'options' field, with a list of (name, - value) tuples, and represent a form dropdown element. + value) tuples, and represent a form dropdown element. Non-heading dicts may + have a human-readable 'docstring' field describing them. Takes the deserialized metadata schema YAML, and also a deserialized YAML of option values. The option values are keyed on (unscoped) field name in @@ -110,8 +111,12 @@ def generate_form(schema, options): # For each field ref_iri = None + docstring = None if not isinstance(field_type, str): # If the type isn't a string + + # It may have documentation + docstring = field_type.get('doc', None) # See if it has a more info/what goes here URL predicate = field_type.get('jsonldPredicate', {}) @@ -156,6 +161,8 @@ def generate_form(schema, options): record['required'] = not optional and not subtree_optional if ref_iri: record['ref_iri'] = ref_iri + if docstring: + record['docstring'] = docstring if field_name in options: # The field will be a 'select' type no matter what its real diff --git a/bh20simplewebuploader/templates/form.html b/bh20simplewebuploader/templates/form.html index 012b9c5..166f51c 100644 --- a/bh20simplewebuploader/templates/form.html +++ b/bh20simplewebuploader/templates/form.html @@ -224,7 +224,7 @@ {{ record['label'] }} {{ "*" if record['required'] else "" }} {% if 'ref_iri' in record %} - ? + ? {% endif %} {% if record['type'] == 'select' %} -- cgit v1.2.3 From 3932c2a342c93c9488e73d399f24329b2b0072ea Mon Sep 17 00:00:00 2001 From: Adam Novak Date: Fri, 17 Apr 2020 13:09:05 -0700 Subject: Tooltip the whole label --- bh20simplewebuploader/templates/form.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bh20simplewebuploader/templates/form.html b/bh20simplewebuploader/templates/form.html index 166f51c..0e98fd7 100644 --- a/bh20simplewebuploader/templates/form.html +++ b/bh20simplewebuploader/templates/form.html @@ -220,11 +220,11 @@

{{ record['heading'] }}

{% else %} -