Skip to content

Commit

Permalink
Fix for #82 PyDSDL allows same namespace name for lookup dir and root… (
Browse files Browse the repository at this point in the history
#88)

* Fix for #82 PyDSDL allows same namespace name for lookup dir and root namespace

Problem: Pydsdl does not allow using same root namespace names.

Solution: We made the check for root namespace name collisions optional.
Tests: Unit test, Manual test

* formatting using black

* fixing unit test for case-insensitive volumes

Co-authored-by: Mehmet Kaya <[email protected]>
  • Loading branch information
thirtytwobits and Mehmet Kaya authored Jan 25, 2023
1 parent d575685 commit fb01759
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 24 deletions.
31 changes: 26 additions & 5 deletions pydsdl/_namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ def read_namespace(
lookup_directories: Union[None, Path, str, Iterable[Union[Path, str]]] = None,
print_output_handler: Optional[PrintOutputHandler] = None,
allow_unregulated_fixed_port_id: bool = False,
allow_root_namespace_name_collision: bool = True,
) -> List[_serializable.CompositeType]:
"""
This function is the main entry point of the library.
Expand All @@ -109,6 +110,10 @@ def read_namespace(
This is a dangerous feature that must not be used unless you understand the risks.
Please read https://opencyphal.org/guide.
:param allow_root_namespace_name_collision: Allow using the source root namespace name in the look up dirs or
the same root namespace name multiple times in the lookup dirs. This will enable defining a namespace
partially and let other entities define new messages or new sub-namespaces in the same root namespace.
:return: A list of :class:`pydsdl.CompositeType` sorted lexicographically by full data type name,
then by major version (newest version first), then by minor version (newest version first).
The ordering guarantee allows the caller to always find the newest version simply by picking
Expand Down Expand Up @@ -145,7 +150,9 @@ def read_namespace(

# Check the namespaces.
_ensure_no_nested_root_namespaces(lookup_directories_path_list)
_ensure_no_namespace_name_collisions(lookup_directories_path_list)

if not allow_root_namespace_name_collision:
_ensure_no_namespace_name_collisions(lookup_directories_path_list)

# Construct DSDL definitions from the target and the lookup dirs.
target_dsdl_definitions = _construct_dsdl_definitions_from_namespace(root_namespace_directory)
Expand Down Expand Up @@ -247,20 +254,34 @@ def _ensure_no_name_collisions(
lookup_definitions: List[_dsdl_definition.DSDLDefinition],
) -> None:
for tg in target_definitions:
tg_full_namespace_period = tg.full_namespace.lower() + "."
tg_full_name_period = tg.full_name.lower() + "."
for lu in lookup_definitions:
lu_full_namespace_period = lu.full_namespace.lower() + "."
lu_full_name_period = lu.full_name.lower() + "."
"""
This is to allow the following messages to coexist happily:
zubax/noncolliding/iceberg/Ice.0.1.dsdl
zubax/noncolliding/Iceb.0.1.dsdl
The following is still not allowed:
zubax/colliding/iceberg/Ice.0.1.dsdl
zubax/colliding/Iceberg.0.1.dsdl
"""
if tg.full_name != lu.full_name and tg.full_name.lower() == lu.full_name.lower():
raise DataTypeNameCollisionError(
"Full name of this definition differs from %s only by letter case, "
"which is not permitted" % lu.file_path,
path=tg.file_path,
)

if tg.full_namespace.lower().startswith(lu.full_name.lower()): # pragma: no cover
if (tg_full_namespace_period).startswith(lu_full_name_period):
raise DataTypeNameCollisionError(
"The namespace of this type conflicts with %s" % lu.file_path, path=tg.file_path
)

if lu.full_namespace.lower().startswith(tg.full_name.lower()):
if (lu_full_namespace_period).startswith(tg_full_name_period):
raise DataTypeNameCollisionError(
"This type conflicts with the namespace of %s" % lu.file_path, path=tg.file_path
)
Expand Down
66 changes: 47 additions & 19 deletions pydsdl/_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
from . import _serializable
from . import _namespace


# Type annotation disabled here because MyPy is misbehaving, reporting these nonsensical error messages:
# pydsdl/_test.py:18: error: Missing type parameters for generic type
# pydsdl/_test.py: note: In function "_in_n_out":
Expand Down Expand Up @@ -1125,6 +1124,36 @@ def _define(rel_path: str, text: str) -> None:
except _namespace.FixedPortIDCollisionError: # pragma: no cover
pass # We're running on a platform where paths are not case-sensitive.

# Test namespece can intersect with type name
os.unlink(Path(directory.name, "zubax/COLLIDING/300.Iceberg.30.0.dsdl"))
try:
os.unlink(Path(directory.name, "zubax/colliding/300.Iceberg.30.0.dsdl"))
except FileNotFoundError:
pass # We're running on a platform where paths are not case-sensitive.
_define(
"zubax/noncolliding/iceberg/Ice.1.0.dsdl",
dedent(
"""
@extent 1024
---
@extent 1024
"""
),
)
_define(
"zubax/noncolliding/Iceb.1.0.dsdl",
dedent(
"""
@extent 1024
---
@extent 1024
"""
),
)
parsed = _namespace.read_namespace(Path(directory.name, "zubax"), Path(directory.name, "zubax"))
assert "zubax.noncolliding.iceberg.Ice" in [x.full_name for x in parsed]
assert "zubax.noncolliding.Iceb" in [x.full_name for x in parsed]


def _unittest_parse_namespace_versioning() -> None:
from pytest import raises
Expand Down Expand Up @@ -1587,25 +1616,24 @@ def _undefine_glob(rel_path_glob: str) -> None:


def _unittest_parse_namespace_faults() -> None:
try:
_namespace.read_namespace("/foo/bar/baz", ["/bat/wot", "/foo/bar/baz/bad"])
except _namespace.NestedRootNamespaceError as ex:
print(ex)
else: # pragma: no cover
assert False
from pytest import raises

try:
_namespace.read_namespace("/foo/bar/baz", ["/foo/bar/zoo", "/foo/bar/doo/roo/BAZ"]) # Notice the letter case
except _namespace.RootNamespaceNameCollisionError as ex:
print(ex)
else: # pragma: no cover
assert False
try:
_namespace.read_namespace("/foo/bar/baz", ["/foo/bar/zoo", "/foo/bar/doo/roo/zoo", "/foo/bar/doo/roo/baz"])
except _namespace.RootNamespaceNameCollisionError as ex:
print(ex)
else: # pragma: no cover
assert False
with raises(_namespace.NestedRootNamespaceError):
_namespace.read_namespace(
"/foo/bar/baz", ["/bat/wot", "/foo/bar/baz/bad"], allow_root_namespace_name_collision=False
)

with raises(_namespace.RootNamespaceNameCollisionError):
_namespace.read_namespace(
"/foo/bar/baz", ["/foo/bar/zoo", "/foo/bar/doo/roo/BAZ"], allow_root_namespace_name_collision=False
) # Notice the letter case

with raises(_namespace.RootNamespaceNameCollisionError):
_namespace.read_namespace(
"/foo/bar/baz",
["/foo/bar/zoo", "/foo/bar/doo/roo/zoo", "/foo/bar/doo/roo/baz"],
allow_root_namespace_name_collision=False,
)


@_in_n_out
Expand Down

0 comments on commit fb01759

Please sign in to comment.