Skip to content
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

Add codec objects #364

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open

Conversation

andlaus
Copy link
Collaborator

@andlaus andlaus commented Nov 21, 2024

Codecs are any objects that can be en- and decoded into PDUs. The approach so far was to derive all "composite" codecs from BasicStructure and to use a subset of that API for "simple" codable objects like DOPs or parameters. In particular, the previous approach to composite objects was sub-optimal because BASIC-STRUCTURE features subtags that other codable objects like Request and Response do not exhibit (i.e., .byte_size).

Since codecs do not play very nicely with inheritance, the new approach is based on typing protocols (Java would call these "interfaces"): codec.py defines a few runtime checkable typing.Protocol classes which codecs must implement: Codec for the basic functionality, CompositeCodec defines the API for codecs that are composed of a list of parameters like structures, requests, responses or environment datas, and ToplevelCodec defines the "external" API for requests and responses that is supposed to be called by user scripts. Any codable class implementing these APIs can check if it is compliant using isinstance() in the __post_init__() method of the respective class.

Andreas Lauser <[email protected]>, on behalf of MBition GmbH.
Provider Information

@andlaus andlaus requested a review from kayoub5 November 21, 2024 12:20
odxtools/basicstructure.py Outdated Show resolved Hide resolved
@@ -85,7 +85,6 @@ def test_encode_coded_const_infer_order(self) -> None:
admin_data=None,
sdgs=[],
parameters=NamedItemList([param1, param2]),
byte_size=None,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was byte_size=None, removed?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because the spec says that requests and responses do not have such an attribute. (so far, this was inherited from BasicStructure)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

possible to move this change to its own PR? this particular change is too noisy in this PR

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's probably possible, but as I see it, most of the point of this PR is not having to derive Request and Response from BasicStructure anymore...

odxtools/request.py Outdated Show resolved Hide resolved
odxtools/codec.py Outdated Show resolved Hide resolved
def get_static_bit_length(self) -> Optional[int]:
return composite_codec_get_static_bit_length(self)

def print_free_parameters_info(self) -> None:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

personally I think that no object should have print function, that limits the re-usability of the function

Copy link
Collaborator Author

@andlaus andlaus Nov 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess I disagree slightly here: When exploring an ODX database using the REPL (which I frequently do), I find it quite a bit more convenient to be able to do something like

service.print<TAB><RETURN>

than

print(service.free<TAB>)<RETURN>

(that's basically the use case for this method...)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you don't need to use print if the function return a string, REPL will auto print the return value of the function

Copy link
Collaborator Author

@andlaus andlaus Nov 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kind of: IMO the readability of strings with new line characters is quite bad if they are not explicitly printed...

Codecs are any objects that can be en- and decoded into PDUs. The
approach so far was to derive all "composite" codecs from
`BasicStructure` and to use a subset of that API for "simple" codable
objects like DOPs or parameters. In particular, the previous approach
to composite objects was sub-optimal because `BASIC-STRUCTURE`
features subtags that other codable objects like `Request` and
`Response` do not exhibit (i.e., `.byte_size`).

Since codecs do not play very nicely with inheritance, the new
approach is based on typing protocols (Java would call these
"interfaces"): `codec.py` defines a few runtime checkable
`typing.Protocol` classes which codecs must implement: `Codec` for the
basic functionality, `CompositeCodec` defines the API for codecs that
are composed of a list of parameters like structures, requests,
responses or environment datas, and `ToplevelCodec` defines the
"external" API for requests and responses that is supposed to be
called by user scripts. Any codable class implementing these APIs can
check if it is compliant using `isinstance()` in the `__post_init__()`
method of the respective class.

Signed-off-by: Andreas Lauser <[email protected]>
Signed-off-by: Alexander Walz <[email protected]>
Signed-off-by: Gunnar Andersson <[email protected]>
…odec APIs at runtime

as [at]kayoub5 correctly notes, this is already done by `mypy` ahead of time.

Signed-off-by: Andreas Lauser <[email protected]>
this property has been deprecated for more than a year and after this
pull request, request and response objects are no longer structures.

Signed-off-by: Andreas Lauser <[email protected]>
@kayoub5
Copy link
Collaborator

kayoub5 commented Nov 25, 2024

@andlaus you have a merge conflict

the intention of this was to make the code more robust in non-strict
mode, but come think about it, exceptions raised by
`._resolve_snref()` will not be caught even in non-strict mode, so we
can as well simply skip the exception handling entirely. (besides
this, `context.response` is now properly set in
`Response._resolve_snrefs()`.)

thanks to [at]kayoub5 for noticing this...

Signed-off-by: Andreas Lauser <[email protected]>
As [at]kayoub5 rightfully notes, this was not used anywere for
real. (Also, the `Response` class did not even strictly implement it,
because the `.decode()` and `.encode()` methods accept the additional
`coded_request` argument.)

Signed-off-by: Andreas Lauser <[email protected]>
this was an artifact stemming from the development history of this PR:
originally, the `Codec` type protocol mandated this property, but it
turned out that the better approach was to move handling of
statically-sized structures from the generic function
(`composite_codec_get_static_bit_length()`) to the
`get_static_bit_length()` method of structures.

Signed-off-by: Andreas Lauser <[email protected]>
@andlaus
Copy link
Collaborator Author

andlaus commented Nov 25, 2024

@andlaus you have a merge conflict

right, fixed (sorry)

print("The following parameters are required but missing!")
print(" - " + "\n - ".join(missing_parameter_names))
print("The following parameters are required but missing:")
print(" - " + "\n - ".join(sorted(missing_parameter_names)))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why add sorted here?

if parameter is None:
print(f"I don't know the parameter {parameter_sn}")
continue

if isinstance(parameter_value, dict):
# parameter_value refers to a structure (represented as dict of params)
dop = getattr(parameter, "dop", None)
inner_params = getattr(dop, "parameters", None)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not using the interface you created?

"""

@property
def parameters(self) -> List["Parameter"]:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not 100% sure, but I think this is more correct

Suggested change
def parameters(self) -> List["Parameter"]:
def parameters(self) -> NamedItemList[Parameter]:


# Round up to account for padding bits (all structures are
# byte aligned)
return byte_length * 8
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where is the round up code?

request_prefix: bytes = b'') -> bytes:
from .parameters.codedconstparameter import CodedConstParameter
from .parameters.matchingrequestparameter import MatchingRequestParameter
from .parameters.physicalconstantparameter import PhysicalConstantParameter
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is the import here?

for param_value_name in physical_value:
if param_value_name not in param_names:
odxraise(f"Value for unknown parameter '{param_value_name}' specified "
f"for structure {codec.short_name}")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

word "structure" don't fit here anymore

for param in codec.parameters:
if id(param) == id(codec.parameters[-1]):
# The last parameter of the structure is at the end of
# the PDU if the structure itcodec is at the end of the
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

grammatically the sentence makes no sense

if id(param) == id(codec.parameters[-1]):
# The last parameter of the structure is at the end of
# the PDU if the structure itcodec is at the end of the
# PDU. TODO: This assumes that the last parameter
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't start a todo in the middle of a comment

@@ -55,6 +55,10 @@ def _resolve_snrefs(self, context: SnRefContext) -> None: # noqa: B027
"""Recursively resolve any short-name references"""
pass

@property
def byte_size(self) -> Optional[int]:
return None
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

really needed?


@property
@deprecated("use .coding_object") # type: ignore[misc]
def structure(self) -> Union["Request", "Response"]:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

breaking change, merit its own PR


def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
for param in self.parameters:
param._resolve_odxlinks(odxlinks)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about the other properties?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants