Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions flatdata-generator/flatdata/generator/engine.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
'''
Copyright (c) 2017 HERE Europe B.V.
Copyright (c) 2025 HERE Europe B.V.
See the LICENSE file in the root of this project for license details.
'''

Expand Down Expand Up @@ -61,14 +61,15 @@ def render(self, generator_name):
output_content = generator.render(self.tree)
return output_content

def render_python_module(self, module_name=None, archive_name=None):
def render_python_module(self, module_name=None, archive_name=None, root_namespace=None):
"""
Render python module.
:param module_name: Module name to use. If none, root namespace name is used.
:param archive_name: Archive name to lookup,
if specified, archive type is returned along with the model
:param root_namespace: Root namespace to pick in case of multiple top level namespaces.
"""
root_namespace = self._find_root_namespace(self.tree)
root_namespace = self._find_root_namespace(self.tree, root_namespace)
module_code = self.render("py")
module = types.ModuleType(module_name if module_name is not None else root_namespace.name)
#pylint: disable=exec-used
Expand All @@ -89,14 +90,20 @@ def _create_generator(cls, name):
return generator_type()

@staticmethod
def _find_root_namespace(tree):
def _find_root_namespace(tree, root_namespace=None):
root_children = tree.root.children
root_namespaces = [
child for child in root_children
if isinstance(child, Namespace) and "builtin" not in child.name
]
if not root_namespaces:
raise RuntimeError("No root namespace found.")
elif root_namespace:
for namespace in root_namespaces:
if namespace.name == root_namespace:
return namespace
raise RuntimeError("Invalid root namespace provided. Could not find root namespace in archive.")
elif len(root_namespaces) > 1:
raise RuntimeError("Ambiguous root namespace. Could not find root archive.")

return root_namespaces[0]
12 changes: 8 additions & 4 deletions flatdata-py/flatdata/lib/inspector.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
'''
Copyright (c) 2017 HERE Europe B.V.
Copyright (c) 2025 HERE Europe B.V.
See the LICENSE file in the root of this project for license details.
'''

Expand Down Expand Up @@ -29,7 +29,7 @@
"""


def open_archive(path, archive=None, module_name=None):
def open_archive(path, archive=None, module_name=None, root_namespace=None):
"""
Opens archive at a given path.
Archive schema is read and python bindings are generated on the fly.
Expand All @@ -38,6 +38,7 @@ def open_archive(path, archive=None, module_name=None):
:param archive: Archive name to open (in case multiple archives reside in one directory)
if None, will be implied. If cannot be implied, RuntimeError is raised.
:param module_name: Module name to create. If None, will match the highest-level namespace.
:param root_namespace: Root namespace to pick in case of multiple top level namespaces.
:return: tuple archive, module
"""
if not os.path.exists(path):
Expand Down Expand Up @@ -73,7 +74,8 @@ def open_archive(path, archive=None, module_name=None):
try:
module, archive_type = \
Engine(schema.read().decode()).render_python_module(module_name=module_name,
archive_name=archive_name)
archive_name=archive_name,
root_namespace=root_namespace)
except FlatdataSyntaxError as err:
raise RuntimeError("Error reading schema: %s " % err)

Expand All @@ -87,12 +89,14 @@ def main():
help="Path to archive")
parser.add_argument("-a", "--archive", type=str, dest="archive", required=False, default=None,
help="Name of the archive")
parser.add_argument("-n", "--namespace", type=str, dest="namespace", required=False, default=None,
help="Root namespace to pick in case of multiple top level namespaces")
parser.add_argument("--non-interactive", type=str, dest="non_interactive", required=False,
default=None,
help="Python code to execute in non-interactive mode")
args = parser.parse_args()

archive, _ = open_archive(args.path, args.archive)
archive, _ = open_archive(args.path, args.archive, None, args.namespace)

pd.set_option('display.max_rows', 30)
pd.set_option('expand_frame_repr', False)
Expand Down
17 changes: 17 additions & 0 deletions flatdata-py/tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,23 @@
)


MULTI_NAMESPACE_TEST_SCHEMA = """
namespace backward_compatibility {
struct SignedStruct {
a : i16 : 5;
b : u32 : 32;
c : i32 : 7;
d : u32 : 32;
}
archive Archive {
resource: SignedStruct;
}
}
namespace second {
}
"""



VECTOR_TEST_SCHEMA = """
namespace backward_compatibility {
Expand Down
14 changes: 14 additions & 0 deletions flatdata-py/tests/test_backward_compatibility.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,20 @@ def test_instance_reading():
check_signed_struct(archive.resource[0])


def test_multi_namespace_instance_reading():
root_namespace = "backward_compatibility"
module = Engine(MULTI_NAMESPACE_TEST_SCHEMA).render_python_module(None, None, root_namespace)
valid_data = {
"Archive.archive": ARCHIVE_SIGNATURE_PAYLOAD,
"Archive.archive.schema": module.backward_compatibility_Archive.schema().encode(),
"resource": RESOURCE_PAYLOAD,
"resource.schema": module.backward_compatibility_Archive.resource_schema('resource').encode()
}
archive = module.backward_compatibility_Archive(DictResourceStorage(valid_data))
check_signed_struct(archive.resource)
check_signed_struct(archive.resource[0])


def test_vector_reading():
module = Engine(VECTOR_TEST_SCHEMA).render_python_module()
valid_data = {
Expand Down