Skip to content

Commit f564e42

Browse files
Cleanup dependencies and fix #94 (#95)
Upgrade Parsimonious to `v0.10.0+39b3d71ee827ba6a19a7226fddfe6b6e51535794`; fix #92. Compare the bundled version of Parsimonious against its latest release, v0.10.0: erikrose/parsimonious@0.10.0...39b3d71 Remove dependency on `six.py` Fix #94 and add a new error class -- `DataTypeCollisionError`; remove `MultipleDefinitionsUnderSameVersionError`. This does not affect the API compatibility because the specific error classes are private. Bump the minor version number.
1 parent 7f0270c commit f564e42

File tree

13 files changed

+292
-1259
lines changed

13 files changed

+292
-1259
lines changed

docs/index.rst

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -63,26 +63,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
6363
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
6464
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
6565
SOFTWARE.
66-
67-
Six
68-
^^^
69-
70-
Copyright (c) 2010-2020 Benjamin Peterson
71-
72-
Permission is hereby granted, free of charge, to any person obtaining a copy
73-
of this software and associated documentation files (the "Software"), to deal
74-
in the Software without restriction, including without limitation the rights
75-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
76-
copies of the Software, and to permit persons to whom the Software is
77-
furnished to do so, subject to the following conditions:
78-
79-
The above copyright notice and this permission notice shall be included in all
80-
copies or substantial portions of the Software.
81-
82-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
83-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
84-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
85-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
86-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
87-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
88-
SOFTWARE.

docs/pages/installation.rst

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,5 @@ The library is bundled with the following third-party software libraries
2222
(by virtue of being bundled, they are not listed as dependencies and need not be installed by the user):
2323

2424
* `Parsimonious <https://github.com/erikrose/parsimonious>`_ by Erik Rose, MIT license.
25-
* `Six <https://github.com/benjaminp/six>`_ by Benjamin Peterson, MIT license; needed for Parsimonious.
2625

2726
Please refer to the projects' homepages for more information, including the legal information on licensing.

pydsdl/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import sys as _sys
88
from pathlib import Path as _Path
99

10-
__version__ = "1.19.0"
10+
__version__ = "1.20.0"
1111
__version_info__ = tuple(map(int, __version__.split(".")[:3]))
1212
__license__ = "MIT"
1313
__author__ = "OpenCyphal"

pydsdl/_namespace.py

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,13 @@ class RootNamespaceNameCollisionError(_error.InvalidDefinitionError):
2020
"""
2121

2222

23-
class DataTypeNameCollisionError(_error.InvalidDefinitionError):
23+
class DataTypeCollisionError(_error.InvalidDefinitionError):
24+
"""
25+
Raised when there are conflicting data type definitions.
26+
"""
27+
28+
29+
class DataTypeNameCollisionError(DataTypeCollisionError):
2430
"""
2531
Raised when there are conflicting data type names.
2632
"""
@@ -39,16 +45,6 @@ class FixedPortIDCollisionError(_error.InvalidDefinitionError):
3945
"""
4046

4147

42-
class MultipleDefinitionsUnderSameVersionError(_error.InvalidDefinitionError):
43-
"""
44-
For example::
45-
46-
Type.1.0.dsdl
47-
2800.Type.1.0.dsdl
48-
2801.Type.1.0.dsdl
49-
"""
50-
51-
5248
class VersionsOfDifferentKindError(_error.InvalidDefinitionError):
5349
"""
5450
Definitions that share the same name but are of different kinds.
@@ -166,7 +162,7 @@ def read_namespace(
166162
lookup_dsdl_definitions += _construct_dsdl_definitions_from_namespace(ld)
167163

168164
# Check for collisions against the lookup definitions also.
169-
_ensure_no_name_collisions(target_dsdl_definitions, lookup_dsdl_definitions)
165+
_ensure_no_collisions(target_dsdl_definitions, lookup_dsdl_definitions)
170166

171167
_logger.debug("Lookup DSDL definitions are listed below:")
172168
for x in lookup_dsdl_definitions:
@@ -245,7 +241,7 @@ def handler(line_number: int, text: str) -> None:
245241
return types
246242

247243

248-
def _ensure_no_name_collisions(
244+
def _ensure_no_collisions(
249245
target_definitions: List[_dsdl_definition.DSDLDefinition],
250246
lookup_definitions: List[_dsdl_definition.DSDLDefinition],
251247
) -> None:
@@ -275,6 +271,12 @@ def _ensure_no_name_collisions(
275271
raise DataTypeNameCollisionError(
276272
"This type conflicts with the namespace of %s" % lu.file_path, path=tg.file_path
277273
)
274+
if (
275+
tg_full_name_period == lu_full_name_period
276+
and tg.version == lu.version
277+
and not tg.file_path.samefile(lu.file_path)
278+
): # https://github.com/OpenCyphal/pydsdl/issues/94
279+
raise DataTypeCollisionError("This type is redefined in %s" % lu.file_path, path=tg.file_path)
278280

279281

280282
def _ensure_no_fixed_port_id_collisions(types: List[_serializable.CompositeType]) -> None:
@@ -320,14 +322,9 @@ def _ensure_minor_version_compatibility_pairwise(
320322
a: _serializable.CompositeType, b: _serializable.CompositeType
321323
) -> None:
322324
assert a is not b
323-
assert a.version.major == b.version.major
324325
assert a.full_name == b.full_name
325-
326-
# Version collision
327-
if a.version.minor == b.version.minor:
328-
raise MultipleDefinitionsUnderSameVersionError(
329-
"This definition shares its version number with %s" % b.source_file_path, path=a.source_file_path
330-
)
326+
assert a.version.major == b.version.major
327+
assert a.version.minor != b.version.minor # This is the whole point of this function.
331328

332329
# Must be of the same kind: both messages or both services
333330
if isinstance(a, _serializable.ServiceType) != isinstance(b, _serializable.ServiceType):

pydsdl/_test.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1262,7 +1262,7 @@ def _unittest_parse_namespace_versioning(wrkspc: Workspace) -> None:
12621262
),
12631263
)
12641264

1265-
with raises(_namespace.MultipleDefinitionsUnderSameVersionError):
1265+
with raises(_namespace.DataTypeCollisionError):
12661266
_namespace.read_namespace((wrkspc.directory / "ns"), [])
12671267

12681268
wrkspc.drop("ns/Spartans.30.2.dsdl")
@@ -1604,6 +1604,27 @@ def _unittest_parse_namespace_versioning(wrkspc: Workspace) -> None:
16041604
wrkspc.drop("ns/Consistency*")
16051605

16061606

1607+
def _unittest_issue94(wrkspc: Workspace) -> None:
1608+
from pytest import raises
1609+
1610+
wrkspc.new("outer_a/ns/Foo.1.0.dsdl", "@sealed")
1611+
wrkspc.new("outer_b/ns/Foo.1.0.dsdl", "@sealed") # Conflict!
1612+
wrkspc.new("outer_a/ns/Bar.1.0.dsdl", "Foo.1.0 fo\n@sealed") # Which Foo.1.0?
1613+
1614+
with raises(_namespace.DataTypeCollisionError):
1615+
_namespace.read_namespace(
1616+
wrkspc.directory / "outer_a" / "ns",
1617+
[wrkspc.directory / "outer_b" / "ns"],
1618+
)
1619+
1620+
wrkspc.drop("outer_b/ns/Foo.1.0.dsdl") # Clear the conflict.
1621+
defs = _namespace.read_namespace(
1622+
wrkspc.directory / "outer_a" / "ns",
1623+
[wrkspc.directory / "outer_b" / "ns"],
1624+
)
1625+
assert len(defs) == 2
1626+
1627+
16071628
def _unittest_parse_namespace_faults() -> None:
16081629
from pytest import raises
16091630

pydsdl/third_party/parsimonious/exceptions.py

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
from six import text_type, python_2_unicode_compatible
1+
from textwrap import dedent
22

33
from parsimonious.utils import StrAndRepr
44

55

6-
@python_2_unicode_compatible
7-
class ParseError(StrAndRepr, Exception):
6+
class ParsimoniousError(Exception):
7+
"""A base exception class to allow library users to catch any Parsimonious error."""
8+
pass
9+
10+
11+
class ParseError(StrAndRepr, ParsimoniousError):
812
"""A call to ``Expression.parse()`` or ``match()`` didn't match."""
913

1014
def __init__(self, text, pos=-1, expr=None):
@@ -16,9 +20,9 @@ def __init__(self, text, pos=-1, expr=None):
1620
self.expr = expr
1721

1822
def __str__(self):
19-
rule_name = ((u"'%s'" % self.expr.name) if self.expr.name else
20-
text_type(self.expr))
21-
return u"Rule %s didn't match at '%s' (line %s, column %s)." % (
23+
rule_name = (("'%s'" % self.expr.name) if self.expr.name else
24+
str(self.expr))
25+
return "Rule %s didn't match at '%s' (line %s, column %s)." % (
2226
rule_name,
2327
self.text[self.pos:self.pos + 20],
2428
self.line(),
@@ -32,31 +36,47 @@ def line(self):
3236
match."""
3337
# This is a method rather than a property in case we ever wanted to
3438
# pass in which line endings we want to use.
35-
return self.text.count('\n', 0, self.pos) + 1
39+
if isinstance(self.text, list): # TokenGrammar
40+
return None
41+
else:
42+
return self.text.count('\n', 0, self.pos) + 1
3643

3744
def column(self):
3845
"""Return the 1-based column where the expression ceased to match."""
3946
# We choose 1-based because that's what Python does with SyntaxErrors.
4047
try:
4148
return self.pos - self.text.rindex('\n', 0, self.pos)
42-
except ValueError:
49+
except (ValueError, AttributeError):
4350
return self.pos + 1
4451

4552

46-
@python_2_unicode_compatible
53+
class LeftRecursionError(ParseError):
54+
def __str__(self):
55+
rule_name = self.expr.name if self.expr.name else str(self.expr)
56+
window = self.text[self.pos:self.pos + 20]
57+
return dedent(f"""
58+
Left recursion in rule {rule_name!r} at {window!r} (line {self.line()}, column {self.column()}).
59+
60+
Parsimonious is a packrat parser, so it can't handle left recursion.
61+
See https://en.wikipedia.org/wiki/Parsing_expression_grammar#Indirect_left_recursion
62+
for how to rewrite your grammar into a rule that does not use left-recursion.
63+
"""
64+
).strip()
65+
66+
4767
class IncompleteParseError(ParseError):
4868
"""A call to ``parse()`` matched a whole Expression but did not consume the
4969
entire text."""
5070

5171
def __str__(self):
52-
return u"Rule '%s' matched in its entirety, but it didn't consume all the text. The non-matching portion of the text begins with '%s' (line %s, column %s)." % (
72+
return "Rule '%s' matched in its entirety, but it didn't consume all the text. The non-matching portion of the text begins with '%s' (line %s, column %s)." % (
5373
self.expr.name,
5474
self.text[self.pos:self.pos + 20],
5575
self.line(),
5676
self.column())
5777

5878

59-
class VisitationError(Exception):
79+
class VisitationError(ParsimoniousError):
6080
"""Something went wrong while traversing a parse tree.
6181
6282
This exception exists to augment an underlying exception with information
@@ -76,7 +96,7 @@ def __init__(self, exc, exc_class, node):
7696
7797
"""
7898
self.original_class = exc_class
79-
super(VisitationError, self).__init__(
99+
super().__init__(
80100
'%s: %s\n\n'
81101
'Parse tree:\n'
82102
'%s' %
@@ -85,7 +105,7 @@ def __init__(self, exc, exc_class, node):
85105
node.prettily(error=node)))
86106

87107

88-
class BadGrammar(StrAndRepr, Exception):
108+
class BadGrammar(StrAndRepr, ParsimoniousError):
89109
"""Something was wrong with the definition of a grammar.
90110
91111
Note that a ParseError might be raised instead if the error is in the
@@ -94,7 +114,6 @@ class BadGrammar(StrAndRepr, Exception):
94114
"""
95115

96116

97-
@python_2_unicode_compatible
98117
class UndefinedLabel(BadGrammar):
99118
"""A rule referenced in a grammar was never defined.
100119
@@ -106,4 +125,4 @@ def __init__(self, label):
106125
self.label = label
107126

108127
def __str__(self):
109-
return u'The label "%s" was never defined.' % self.label
128+
return 'The label "%s" was never defined.' % self.label

0 commit comments

Comments
 (0)