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

WIP: Dataclass #31

Merged
merged 18 commits into from
Mar 10, 2020
Merged

WIP: Dataclass #31

merged 18 commits into from
Mar 10, 2020

Conversation

HeMan
Copy link
Owner

@HeMan HeMan commented Mar 4, 2020

Would solve #23
Needs:

  • Documentation
  • Code cleanup
    • Move datatypes to own file
    • Short types mapping struct types "names" (what to do with boolean?)
    • Remove the need for explicit inheritance from BinmapDataclass

@HeMan
Copy link
Owner Author

HeMan commented Mar 4, 2020

@philippeitis are you interrested in reviewing this?

@philippeitis
Copy link
Contributor

I don't mind

@philippeitis
Copy link
Contributor

Is there any reason you don't implement the data class like so:

    def __init_subclass__(cls, byteorder: str = ">"):
        """
        Subclass initiator.
        :param str byteorder: byteorder for binary data
        """
        cls._byteorder = byteorder
        dataclasses.dataclass(cls)
        type_hints = get_type_hints(cls)

        cls._formatstring = ""

        for field_ in dataclasses.fields(cls):
            _base, _type = datatypemapping[type_hints[field_.name]]
            if "constant" in field_.metadata:
                _base = ConstField
            elif "enum" in field_.metadata:
                _base = EnumField
            setattr(cls, field_.name, _base(name=field_.name))
            if type_hints[field_.name] is pad:
                _type = field_.default * _type
            if (
                type_hints[field_.name] is string
                or type_hints[field_.name] is pascalstring
                or type_hints[field_.name] is str
            ):
                _type = str(field_.metadata["length"]) + _type
            cls._formatstring += _type

Having to do @binmap.binmapdataclass every time you instantiate a class makes the code quite repetitive.

@HeMan
Copy link
Owner Author

HeMan commented Mar 5, 2020

Is there any reason you don't implement the data class like so:

    def __init_subclass__(cls, byteorder: str = ">"):
        """
        Subclass initiator.
        :param str byteorder: byteorder for binary data
        """
        cls._byteorder = byteorder
        dataclasses.dataclass(cls)
        type_hints = get_type_hints(cls)

        cls._formatstring = ""

        for field_ in dataclasses.fields(cls):
            _base, _type = datatypemapping[type_hints[field_.name]]
            if "constant" in field_.metadata:
                _base = ConstField
            elif "enum" in field_.metadata:
                _base = EnumField
            setattr(cls, field_.name, _base(name=field_.name))
            if type_hints[field_.name] is pad:
                _type = field_.default * _type
            if (
                type_hints[field_.name] is string
                or type_hints[field_.name] is pascalstring
                or type_hints[field_.name] is str
            ):
                _type = str(field_.metadata["length"]) + _type
            cls._formatstring += _type

Having to do @binmap.binmapdataclass every time you instantiate a class makes the code quite repetitive.

Yeah, it's either that way or removing the need to inherit from BinmapDataclass I think. Having the decorator could have a value since it clearly shows in the code that it is a dataclass.
Also, it will only be needed when defining the class, not when instantion, right?

@HeMan HeMan marked this pull request as ready for review March 5, 2020 10:08
Missed empty line
@philippeitis
Copy link
Contributor

I don't think it matters if the code is a dataclass or not, since the code still behaves the same way. This is really just an implementation detail which the user should not have to think about - I'd stick with extending the class, since it's more informative about the methods and behaviours you'll inherit, as opposed to a decorator. And yes, it's only needed when declaring the class - my suggestion of moving the logic to the init_subclass works just fine, since we're directly modifying the class object.

if value in obj._enums[self.name]:
obj.__dict__[f"_{self.name}"] = value
datafieldsmap = {f.name: f for f in dataclasses.fields(obj)}
if type(value) is str:
Copy link
Contributor

Choose a reason for hiding this comment

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

if isinstance(value, str):

This allows subtypes which may be relevant here.

obj.__dict__[f"_{self.name}"] = value
datafieldsmap = {f.name: f for f in dataclasses.fields(obj)}
if type(value) is str:
datafieldsmap[self.name].metadata["enum"][value]
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this a dictionary access? I'd leave a comment explaining this if its supposed to be here.

if self.name in obj.__dict__:
raise AttributeError(f"{self.name} is a constant")
else:
obj.__dict__.update({self.name: value})
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not set these values with obj.__dict__[self.name] = value?

obj.__dict__.update({self.name: value})


char = NewType("char", int)
Copy link
Contributor

Choose a reason for hiding this comment

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

Typically, you want to put constants at the top of the file, with variable names in all caps to help identify them - eg: CHAR = NewType("char", int)

Copy link
Owner Author

Choose a reason for hiding this comment

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

Hmm, you're right that constants usually are all caps, but since these are types I think it looks better and more consistent with other types to have them lower case. Also, already moved them to their own file in 41cffd4 and 1d2d109

setattr(cls, field_.name, _base(name=field_.name))
if type_hints[field_.name] is pad:
_type = field_.default * _type
if (
Copy link
Contributor

Choose a reason for hiding this comment

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

I would do type_hints[field_.name] in (string, pascalstring, str) here.

class BinmapDataclass(ABC):
_byteorder = ""
_formatstring = ""
__binarydata: dataclasses.InitVar[bytes] = b""
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we have __binarydata and _binarydata?

Copy link
Owner Author

Choose a reason for hiding this comment

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

I changed that in later (already pushed) changes.

datafields = [
field for field in self._datafields if not field.startswith("_pad")
f.name for f in dataclasses.fields(self) if not (type_hints[f.name] is pad)
Copy link
Contributor

Choose a reason for hiding this comment

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

type_hints[f.name] is not pad

if _binarydata != b"":
self._unpacker(_binarydata)
# Kludgy hack to keep order
for f in dataclasses.fields(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we store our fields like so self._fields = dataclasses.fields(self)?

def __post_init__(self, _binarydata: bytes):
if _binarydata != b"":
self._unpacker(_binarydata)
# Kludgy hack to keep order
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we maintain order through an internal class variable to allow the user to set the order themselves (for instance, _order)? This would also help avoid the self.__dict__.update(...) strangeness.

else:
self._binarydata = b""
if "constant" in f.metadata:
self.__dict__.update({f.name: f.default})
Copy link
Contributor

Choose a reason for hiding this comment

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

Again, this should be commented, but preferably, we should not be relying on this to maintain order.

@philippeitis
Copy link
Contributor

Some of my comments may be off by a line or two - turns out the comments weren't submitted when I thought they were - hopefully they should still be close enough.

No need for decorator now.
@HeMan
Copy link
Owner Author

HeMan commented Mar 9, 2020

Addressed some of the comments and reworked some initializations.

@HeMan
Copy link
Owner Author

HeMan commented Mar 10, 2020

Merging it now, it could be easier to work with after that.

@HeMan HeMan merged commit fe96c69 into master Mar 10, 2020
This was referenced Mar 11, 2020
@HeMan HeMan deleted the dataclass branch March 12, 2020 20:31
@HeMan HeMan restored the dataclass branch October 16, 2024 15:32
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.

3 participants