-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor code #34
Merged
Merged
Refactor code #34
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
a09e1c7
package initialization added
mahdanoura 1924c2e
WoT toolchain CLI updated with click library
mahdanoura 025e4fa
wotis package created
mahdanoura 5a86944
CLI interface changed to click and refractoring
mahdanoura 2f49283
CLI interface changed to click and refractoring
mahdanoura af886e4
configs added to initiation
mahdanoura b71f78e
gitattributes added
mahdanoura f3658bc
json-ld context postprocessing refractored
mahdanoura f307f2c
package update
mahdanoura File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
*.html linguist-detectable=false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,26 +1,36 @@ | ||
[project] | ||
name = "thing_description_schema" | ||
name = "wotis" | ||
version = "0.1.0" | ||
description = "Thing Description Information Model as a LinkML schema" | ||
description = "A CLI for Web of Things Integrated Schemas (WOTIS)" | ||
authors = [ | ||
{ name = "Mahda Noura", email = "mahdanoura@gmail.com" } | ||
{ name = "Mahda Noura", email = "mahda.noura@siemens.com" } | ||
] | ||
license = "MIT" | ||
readme = "README.md" | ||
include = ["README.md", "src/thing_description_schema/schema", "project"] | ||
|
||
requires-python = ">=3.12" | ||
include = ["README.md", "resources/schemas/*"] | ||
requires-python = ">=3.11" | ||
dependencies = [ | ||
"linkml-runtime>=1.8.2", | ||
"linkml>=1.8.2", | ||
"mkdocs-mermaid2-plugin>=1.1.1", | ||
"mkdocs-material>=9.5.32", | ||
"schemasheets>=0.3.1", | ||
"schemasheets>=0.3.1" | ||
] | ||
|
||
[project.optional-dependencies] | ||
cli = [ | ||
"click>=8.1.7" | ||
] | ||
|
||
[build-system] | ||
requires = ["hatchling"] | ||
build-backend = "hatchling.build" | ||
|
||
[tool.hatch.build] | ||
only-include = ["main.py"] | ||
only-include = ["src/wotis/cli.py"] | ||
|
||
[tool.hatch.metadata] | ||
root = "src" | ||
|
||
[project.scripts] | ||
wotis = "src.wotis.cli:main" |
Empty file.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
from pathlib import Path | ||
|
||
RESOURCES_PATH = Path('resources') | ||
GENS_PATH = RESOURCES_PATH / 'gens' | ||
SCHEMA_PATH = RESOURCES_PATH / 'schemas' | ||
YAML_SCHEMA_PATH = SCHEMA_PATH / 'thing_description.yaml' | ||
DOCDIR = GENS_PATH / 'docs' / 'ontology' | ||
|
||
GENERATORS = ['jsonschema', 'shacl', 'jsonldcontext', 'linkml'] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
"""Command line interface for WoTIS.""" | ||
import click | ||
import logging | ||
from pathlib import Path | ||
import subprocess | ||
|
||
import yaml | ||
from linkml.generators.jsonschemagen import JsonSchemaGenerator | ||
from linkml.generators.shaclgen import ShaclGenerator | ||
from linkml.generators.owlgen import OwlSchemaGenerator | ||
from linkml.generators.jsonldcontextgen import ContextGenerator | ||
from linkml.generators.docgen import DocGenerator | ||
from linkml.generators.linkmlgen import LinkmlGenerator | ||
from linkml_runtime.utils.schemaview import SchemaView | ||
|
||
from src.wotis import YAML_SCHEMA_PATH, GENS_PATH, GENERATORS, DOCDIR | ||
from src.wotis.post_processors.jsonld_context_postprocessor import post_process_jsonld_context | ||
|
||
|
||
input_option = click.option('-i', '--input_schema', | ||
type=str, | ||
show_default=True, | ||
help="Path to the input schema specified as LinkML yaml.", | ||
default=YAML_SCHEMA_PATH) | ||
docs_option = click.option('-d', | ||
'--generate_docs', | ||
type=bool, | ||
is_flag=True, | ||
default=False, | ||
show_default=True, | ||
help="Boolean for local documentation generation.") | ||
serve_docs_option = click.option('-s', | ||
'--serve_docs', | ||
type=bool, | ||
is_flag=True, | ||
default=False, | ||
show_default=True, | ||
help="Boolean for serving the generated documentation.") | ||
|
||
|
||
def serve_documentation(): | ||
subprocess.run(['mkdocs', 'serve'], check=True) | ||
|
||
|
||
def generate_documentation(): | ||
DOCDIR.mkdir(parents=True, exist_ok=True) | ||
doc_generator = DocGenerator(YAML_SCHEMA_PATH, mergeimports=False) | ||
doc_generator.serialize(directory=str(DOCDIR)) | ||
|
||
|
||
# Main generation function | ||
def run_generator(schema_view, generator, output_dir): | ||
if generator == 'jsonschema': | ||
logging.info(f"Proceeding with LinkML to JSON Schema convertion") | ||
json_schema_generator = JsonSchemaGenerator(schema_view.schema, mergeimports=True) | ||
(output_dir / 'jsonschema.json').write_text(json_schema_generator.serialize()) | ||
elif generator == 'shacl': | ||
logging.info(f"Proceeding with LinkML to SHACL convertion") | ||
shacl_generator = ShaclGenerator(schema_view.schema, mergeimports=False, closed=True, suffix='Shape') | ||
(output_dir / 'shapes.shacl.ttl').write_text(shacl_generator.serialize()) | ||
elif generator == 'owl': | ||
logging.info(f"Proceeding with LinkML to OWL convertion") | ||
owl_generator = OwlSchemaGenerator(schema_view.schema, ) | ||
(output_dir / 'ontology.owl.ttl').write_text(owl_generator.serialize()) | ||
elif generator == 'jsonldcontext': | ||
logging.info(f"Proceeding with LinkML to JSON-LD Context convertion") | ||
context_generator = ContextGenerator(schema_view.schema, mergeimports=True) | ||
(output_dir / 'context.jsonld').write_text(post_process_jsonld_context(schema_view, | ||
context_generator.serialize())) | ||
elif generator == 'linkml': | ||
linkml_generator = LinkmlGenerator(schema_view.schema, mergeimports=True, format='yaml', output='linkml.yaml') | ||
(output_dir / 'linkml.yaml').write_text(linkml_generator.serialize()) | ||
else: | ||
print(f"Unknown generator: {generator}") | ||
|
||
|
||
@click.group() | ||
@click.option("-v", "--verbose", count=True) | ||
@click.option("-q", "--quiet") | ||
def main(verbose: int, quiet: bool): | ||
"""CLI for WOTIS (Web of Things Integrated Schemas) toolchain. | ||
|
||
:param verbose: Verbosity while running. | ||
:param quiet: Boolean to be quiet or verbose. | ||
""" | ||
logger = logging.getLogger() | ||
if verbose >= 2: | ||
logger.setLevel(level=logging.DEBUG) | ||
elif verbose == 1: | ||
logger.setLevel(level=logging.INFO) | ||
else: | ||
logger.setLevel(level=logging.WARNING) | ||
if quiet: | ||
logger.setLevel(level=logging.ERROR) | ||
logger.info(f"Logger {logger.name} set to level {logger.level}") | ||
|
||
|
||
@main.command() | ||
@input_option | ||
@docs_option | ||
@serve_docs_option | ||
def generate_wot_resources(input_schema: str, generate_docs: bool, serve_docs: bool): | ||
""" | ||
Generating WoT resources (RDF, JSON-LD Context, SHACL Shapes, and JSON Schema) from manually constructed | ||
LinkML-based schemas. | ||
""" | ||
if input_schema and not Path(input_schema).exists(): | ||
raise FileNotFoundError(f"Cannot find input LinkML schema file {input_schema}.") | ||
elif not input_schema and not YAML_SCHEMA_PATH.exists(): | ||
raise FileNotFoundError(f"Cannot find the default LinkML schema file {YAML_SCHEMA_PATH}.") | ||
else: | ||
try: | ||
linkml_schema_view = SchemaView(input_schema, merge_imports=True) | ||
logging.info(f"Input schema {input_schema} loaded successfully!") | ||
for generator in GENERATORS: | ||
output_dir = GENS_PATH / generator | ||
output_dir.mkdir(parents=True, exist_ok=True) | ||
logging.info(f"Proceeding with WoT resource generation") | ||
run_generator(linkml_schema_view, generator, output_dir) | ||
except yaml.YAMLError as e: | ||
logging.info(f"LinkML schema validation failed: {e}") | ||
if generate_docs: | ||
logging.info(f"Generating documentation locally as markdown files...") | ||
generate_documentation() | ||
if serve_docs: | ||
logging.info(f"Serving documentation...") | ||
serve_documentation() | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
File renamed without changes.
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import json | ||
import logging | ||
|
||
from linkml_runtime.linkml_model.meta import AnonymousSlotExpression | ||
from linkml_runtime.utils.schemaview import SchemaView | ||
|
||
XSD_NS = "http://www.w3.org/2001/XMLSchema#" | ||
|
||
|
||
def process_langstring_conditions(slot, context_entry): | ||
""" | ||
Handles 'langString' and 'exactly_one_of' language conditions for a slot. | ||
""" | ||
is_langstring = slot.range == 'langString' | ||
is_exactly_one_language = ( | ||
slot.range is None and | ||
any( | ||
expr.range == 'langString' | ||
for expr in slot.exactly_one_of if isinstance(expr, AnonymousSlotExpression) | ||
) | ||
) | ||
if is_langstring or (is_exactly_one_language and isinstance(context_entry, dict)): | ||
context_entry['@container'] = '@language' | ||
context_entry.pop('@type', None) | ||
return context_entry | ||
|
||
|
||
def process_multivalued_slots(slot, context_entry): | ||
""" | ||
Handles multivalued slots and applies the '@set' container. | ||
""" | ||
if slot.multivalued and isinstance(context_entry, dict) and '@container' not in context_entry: | ||
context_entry['@container'] = '@set' | ||
context_entry.pop('@type', None) | ||
return context_entry | ||
|
||
|
||
def process_inlined_slot(slot, context_entry): | ||
""" | ||
Processes slots that are both inlined, multi-valued and have a instantiates fields. | ||
""" | ||
if slot.inlined and slot.multivalued and not slot.inlined_as_list and isinstance(context_entry, dict): | ||
context_entry['@container'] = '@index' | ||
context_entry['@type'] = '@id' | ||
if slot.instantiates: | ||
context_entry['@index'] = slot.instantiates | ||
return context_entry | ||
|
||
|
||
def process_exactly_one_of(slot, context_entry): | ||
""" | ||
Handles the 'exactly_one_of' condition for a slot, determining its type. | ||
""" | ||
if hasattr(slot, 'exactly_one_of') and slot.exactly_one_of: | ||
ranges = [opt['range'] for opt in slot.exactly_one_of if 'range' in opt] | ||
if len(set(ranges)) == 1: | ||
context_entry["@type"] = f"{XSD_NS}:{ranges[0]}" | ||
else: | ||
logging.warning(f"Warning: Slot {slot.name} has different ranges") | ||
return context_entry | ||
|
||
|
||
def post_process_jsonld_context(schema_view: SchemaView, serialized_schema: str) -> str: | ||
""" | ||
Post-processes a JSON-LD context generated from the default LinkML generators. | ||
""" | ||
logging.info(f"Proceeding with JSON-LD Context postprocessor") | ||
serialized_schema_json = json.loads(serialized_schema) | ||
generated_context = serialized_schema_json.get('@context', {}) | ||
default_range = schema_view.schema.default_range if schema_view.schema.default_range else "string" | ||
for slot in schema_view.all_slots().values(): | ||
slot_name = slot.name | ||
context_entry = generated_context.get(slot_name, {}) | ||
# Process language conditions | ||
context_entry = process_langstring_conditions(slot, context_entry) | ||
# Add @language for LinkML in_language slot | ||
if slot.in_language and isinstance(context_entry, dict): | ||
context_entry['@language'] = slot.in_language | ||
# Handle slots that are dict objects | ||
context_entry = process_inlined_slot(slot, context_entry) | ||
# Handle the @id for slots with exactly_one_of field | ||
context_entry = process_exactly_one_of(slot, context_entry) | ||
# Handles default range type | ||
if slot.range == default_range: | ||
context_entry["@type"] = f"{XSD_NS}:{default_range}" | ||
generated_context[slot_name] = context_entry | ||
|
||
# Intentionally handle multivalued slots separately after other processing | ||
for slot in schema_view.all_slots().values(): | ||
context_entry = generated_context.get(slot.name, {}) | ||
context_entry = process_multivalued_slots(slot, context_entry) | ||
generated_context[slot.name] = context_entry | ||
return json.dumps(serialized_schema_json, indent=3) |
Empty file.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.