Skip to content
This repository has been archived by the owner on Dec 8, 2024. It is now read-only.

Commit

Permalink
Merge pull request #54 from JR-1991/id-field-control
Browse files Browse the repository at this point in the history
Allow `id` field to be disabled
  • Loading branch information
JR-1991 authored Mar 4, 2024
2 parents cc2d1e2 + 1acafb2 commit 3c37e2f
Show file tree
Hide file tree
Showing 9 changed files with 68 additions and 27 deletions.
16 changes: 7 additions & 9 deletions sdRDM/base/datamodel.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@
from sdRDM.generator.utils import extract_modules
from sdRDM.tools.utils import YAMLDumper
from sdRDM.tools.gitutils import (
ObjectNode,
build_library_from_git_specs,
_import_library,
)
Expand Down Expand Up @@ -623,23 +622,21 @@ def _is_hdf5(path: str):

@classmethod
def from_markdown(cls, path: str) -> ImportedModules:
"""Fetches a Markdown specification from a git repository and builds the library accordingly.
This function will clone the repository into a temporary directory and
builds the correpsonding API and loads it into the memory. After that
the cloned repository is deleted and the root object(s) detected.
"""Converts a markdown file into a in-memory Python API.
Args:
url (str): Link to the git repository. Use the URL ending with ".git".
commit (Optional[str], optional): Hash of the commit to fetch from. Defaults to None.
path (str): Path to the markdown file.
"""

with tempfile.TemporaryDirectory() as tmpdirname:
# Generate API to parse the file
lib_name = f"sdRDM-Library-{str(random.randint(0,30))}"
api_loc = os.path.join(tmpdirname, lib_name)
generate_python_api(
path=path, dirpath=tmpdirname, libname=lib_name, use_formatter=False
path=path,
dirpath=tmpdirname,
libname=lib_name,
use_formatter=False,
)

lib = _import_library(api_loc, lib_name)
Expand All @@ -665,6 +662,7 @@ def from_git(
url (str): Link to the git repository. Use the URL ending with ".git".
commit (Optional[str], optional): Hash of the commit to fetch from. Defaults to None.
tag (Optional[str], optional): Tag of the release or branch to fetch from. Defaults to None.
only_classes (bool, optional): If True, only the classes will be returned. Defaults to False.
"""

if not validators.url(url):
Expand Down
10 changes: 8 additions & 2 deletions sdRDM/generator/classrender.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ def render_object(
repo: Optional[str] = None,
commit: Optional[str] = None,
small_types: Dict = {},
add_id_field: bool = True,
) -> str:
"""Renders a class of type object coming from a parsed Markdown model"""

Expand All @@ -33,6 +34,7 @@ def render_object(
repo=repo,
commit=commit,
namespaces=namespaces,
add_id_field=add_id_field,
)
for subtype in small_types.values()
if subtype["origin"] == object["name"]
Expand All @@ -49,6 +51,7 @@ def render_object(
repo=repo,
commit=commit,
namespaces=namespaces,
add_id_field=add_id_field,
)

methods_part = render_add_methods(
Expand Down Expand Up @@ -88,6 +91,7 @@ def render_class(
inherits: List[Dict],
objects: List[Dict],
namespaces: Dict,
add_id_field: bool,
repo: Optional[str] = None,
commit: Optional[str] = None,
) -> str:
Expand All @@ -105,7 +109,6 @@ def render_class(
if filtered and len(filtered) == 1:
inherit = filtered[0]["parent"]


return template.render(
name=name,
inherit=inherit,
Expand All @@ -121,6 +124,7 @@ def render_class(
repo=repo,
commit=commit,
namespaces=namespaces,
add_id_field=add_id_field,
)


Expand Down Expand Up @@ -153,7 +157,9 @@ def render_attribute(
attribute["default_factory"] = "ListPlus"
elif not is_multiple and is_all_optional:
attribute["default_factory"] = f"{attribute['type'][0]}"
del attribute["default"]

if "default" in attribute:
del attribute["default"]

if has_reference:
reference_types = get_reference_type(attribute["reference"], objects)
Expand Down
3 changes: 3 additions & 0 deletions sdRDM/generator/codegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ def generate_api_from_parser(
repo=url,
commit=commit,
namespaces=parser.namespaces,
add_id_field=parser.add_id_field,
)

# Write init files
Expand Down Expand Up @@ -112,6 +113,7 @@ def write_classes(
namespaces: Dict,
repo: Optional[str] = None,
commit: Optional[str] = None,
add_id_field: bool = True,
) -> None:
"""Renders classes that were parsed from a markdown model and creates a library."""

Expand All @@ -132,6 +134,7 @@ def write_classes(
commit=commit,
small_types=small_types,
namespaces=namespaces,
add_id_field=add_id_field,
)
path = os.path.join(libpath, "core", f"{object['name'].lower()}.py")
save_rendered_to_file(rendered, path, use_formatter)
Expand Down
3 changes: 3 additions & 0 deletions sdRDM/generator/templates/class_template.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,20 @@ class {{name}}({% if inherit is not none %}
{% endfor %}
},
{% endif %}
search_mode="unordered",
):


{% if docstring is not none %}"""{{ docstring }}"""{% endif %}

{% if add_id_field %}
id: Optional[str] = attr(
name="id",
description="Unique identifier of the given object.",
default_factory=lambda: str(uuid4()),
xml="@id"
)
{% endif %}

{% for attribute in attributes %}
{{ attribute }}
Expand Down
22 changes: 7 additions & 15 deletions sdRDM/markdown/markdownparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class MarkdownParser(BaseModel):
compositions: List = []
external_objects: Dict = {}
namespaces: Dict = {}
add_id_field: bool = True

@classmethod
def parse(cls, handle: IO):
Expand All @@ -28,8 +29,13 @@ def parse(cls, handle: IO):
parser = cls()

content = parser.clean_html_tags(handle.readlines())

# Extract frontmatter and markdown
metadata = frontmatter.load(StringIO(content))
parser.namespaces = metadata.get("xmlns", {}) # type: ignore
parser.add_id_field = metadata.get("id-field", True) # type: ignore

doc = MarkdownIt().parse(parser._remove_header(content))
parser.namespaces = parser._extract_namespaces(content)
modules, enumerations = parser.get_objects_and_enumerations(doc)

for module, model in modules.items():
Expand Down Expand Up @@ -67,20 +73,6 @@ def add_model(self, parser: "MarkdownParser"):
self.compositions += parser.compositions
self.external_objects.update(parser.external_objects)

@staticmethod
def _extract_namespaces(content: str):
"""Extracts all namespaces from the model.
Args:
content (str): The content of the model.
Returns:
dict: A dictionary containing the extracted namespaces.
"""

doc = frontmatter.load(StringIO(content))
return doc.get("xmlns", {})

@staticmethod
def _remove_header(text: str):
"""
Expand Down
4 changes: 3 additions & 1 deletion sdRDM/markdown/objectutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,9 @@ def process_type_option(
if dtype.endswith("[]"):
dtype = dtype.rstrip("[]")
object_stack[-1]["attributes"][-1]["multiple"] = "True"
del object_stack[-1]["attributes"][-1]["default"]

if "default" in object_stack[-1]["attributes"][-1]:
del object_stack[-1]["attributes"][-1]["default"]

if not dtype:
continue
Expand Down
6 changes: 6 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ def model_all_expected():
return json.load(open("tests/fixtures/static/model_all_expected.json"))


@pytest.fixture
def model_no_id():
"""Loads the data model that has ID field disabled"""
return DataModel.from_markdown("tests/fixtures/static/model_minimal_no_id.md")


@pytest.fixture
def model_all_dataset(model_all):
# Create a dataset with the given library
Expand Down
14 changes: 14 additions & 0 deletions tests/end2end/test_creation.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,17 @@ def test_ns_map(model_all):
assert (
model_all.Root.__xml_nsmap__ == expected
), "Namespace map does not match expected values"


@pytest.mark.e2e
def test_no_id_field(model_no_id):
"""
Test that the 'id' field is not present in the model's fields.
Args:
model_no_id (object): The model object without the 'id' field.
Returns:
None
"""
assert "id" not in model_no_id.Object.model_fields.keys()
17 changes: 17 additions & 0 deletions tests/fixtures/static/model_minimal_no_id.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
id-field: false
---

# Module

Some description

## Objects

Other description

### Object

- attribute
- Type: int
- Description: Primitive attribute

0 comments on commit 3c37e2f

Please sign in to comment.