Skip to content

Commit ba0da09

Browse files
authored
Modernize python code for construct and add types (#609)
* construct: Fix doctests Python 3 is working on `bytes`. Use raw-strings to allow backslash escaped bytes. Signed-off-by: Philipp Hahn <[email protected]> * construct: Fix String documentation `paddir=both` -> `center`; see [PaddedStringAdapter](elftools/construct/adapters.py#L186). Signed-off-by: Philipp Hahn <[email protected]> * construct: Fix Switch documentation Signed-off-by: Philipp Hahn <[email protected]> * construct: Fix BitStreamWriter bytes Since Python 3 streams work on `bytes`, not (Unicode) `str`ings. Signed-off-by: Philipp Hahn <[email protected]> * construct: Implement IO for BitStreams `BitStreamReader` and `BitStreamWriter` are supposed to be used instead of `IO[bytes]`, but actually implement a different signature: that would force all functions currently accepting `IO[bytes]` to be changed to `IO[bytes] | BitStreamReader` respective `IO[bytes] | BitStreamWriter`. Instead let both inherit from `io.RawIOBase` to inherit the complete API and implement that interface by returning the correct values and types. See <https://docs.python.org/3/library/io.html#io.RawIOBase>. Signed-off-by: Philipp Hahn <[email protected]>^ * construct: Use named arguments instead of kwargs Explicitly name the named parameters in the function signature instead of using `**kwargs`, which is a pain to type annotate. This also allows us to get rid of the manual check for extra unknown arguments, as the compiler will do that for us. Signed-off-by: Philipp Hahn <[email protected]> * construct: Initialize variables out of loop Type checkers complain about `pos` being undefined inside the exception handling code. Actually `ConstructError` will only be raised by `parse()` inside the loop, so both `c` and `pos` will be assigned at least once, but all type-checkers fail to detect this. Initialize both `c` and `pos` before the loop to silence them. Signed-off-by: Philipp Hahn <[email protected]> * construct: Export used symbols `pyright` is very picky about `Final` symbols being imported multiple times, even when they are the same. Explicitly name all symbole to be exported. Signed-off-by: Philipp Hahn <[email protected]> * construct: typing: Add / fix PEP-484 hints - [x] `pyright` - [x] `mypy` Signed-off-by: Philipp Hahn <[email protected]> * construct: Assert correct type Signed-off-by: Philipp Hahn <[email protected]> * construct: Assert PrefixedArray.length_field.name Assert name of `PrefixedArray.length_field.name` is `not None`. Signed-off-by: Philipp Hahn <[email protected]> * construct: Fix ExprAdapter encoder/decoder `ExprAdapter` expects two *functions* as encode/decoder and overwrites its *methods*: Are those two functions called with `self` as the 1st argument? Make it clear by implementing 2 methods and calling those two functions as functions. Signed-off-by: Philipp Hahn <[email protected]> * construct: Return str from StringAdapter Directly return `str` after `decode()` as `obj` is declared as `bytes`. Signed-off-by: Philipp Hahn <[email protected]> * construct: Return None for Peek `Peek._parse()` must `return None` explicitly for `mypy`. Signed-off-by: Philipp Hahn <[email protected]> * construct: typing: Hint static attribute name We know that `name` is a `str`, so tell it `mypy` too. Signed-off-by: Philipp Hahn <[email protected]> * construct: typing: singletons `NoDefault`, `Pass` and `Terminator` are all singletons, which are used in various dictionaries. For type hinting the *type* is also required in addition to the singleton *instance*. Prefix the class with an underscore. Hint instances with `Final[Any]` to prevent re-assignment and to exempt them from type checking: Otherwise all code de-referencing something from `dict[str, … | _Pass]` has to explicitly check for `_Pass` all time, which currently it does not. Actually that is a bug, which needs fixing at a later day. Signed-off-by: Philipp Hahn <[email protected]> * construct: Dispose LazyContainer Delete instance variables instead of assigning `None`, which then requires checks for `not None`. Signed-off-by: Philipp Hahn <[email protected]> * construct: typing ignore: LazyContainer.__eq__ Code explicitly checks for `AttributeError`, but `mypy` is picky here. Signed-off-by: Philipp Hahn <[email protected]> * construct: typing: Container `Container` is a static typing nightmare as it acts as a `dict[str, Any]`. In addition to that instances are also assigned additional variables using arbitrary types. Basically `Any` goes in, `Any` comes out with is not useful for typing. As we know the types used by `pyelftools`, we can give `mypy` et.al. some hints on those use cases, but have to put theme here on `Container` as that is the base for everything. Yikes. Signed-off-by: Philipp Hahn <[email protected]> --------- Signed-off-by: Philipp Hahn <[email protected]> Signed-off-by: Philipp Hahn <[email protected]>^
1 parent 1ebeb75 commit ba0da09

File tree

9 files changed

+687
-484
lines changed

9 files changed

+687
-484
lines changed

elftools/construct/__init__.py

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,24 @@
1212
http://construct.wikispaces.com (including online tutorial)
1313
1414
Typical usage:
15-
>>> from construct import *
15+
>>> from ..construct import *
1616
1717
Hands-on example:
18-
>>> from construct import *
18+
>>> from ..construct import *
1919
>>> s = Struct("foo",
2020
... UBInt8("a"),
2121
... UBInt16("b"),
2222
... )
23-
>>> s.parse("\\x01\\x02\\x03")
24-
Container(a = 1, b = 515)
25-
>>> print(s.parse("\\x01\\x02\\x03"))
26-
Container:
27-
a = 1
28-
b = 515
29-
>>> s.build(Container(a = 1, b = 0x0203))
30-
"\\x01\\x02\\x03"
23+
>>> s.parse(b"\\x01\\x02\\x03")
24+
Container({'a': 1, 'b': 515})
25+
>>> print(s.parse(b"\\x01\\x02\\x03"))
26+
Container({'a': 1, 'b': 515})
27+
>>> s.build(Container(a=1, b=0x0203))
28+
b'\\x01\\x02\\x03'
3129
"""
30+
from __future__ import annotations
3231

32+
from .lib.container import *
3333
from .core import *
3434
from .adapters import *
3535
from .macros import *
@@ -39,9 +39,9 @@
3939
#===============================================================================
4040
# Metadata
4141
#===============================================================================
42-
__author__ = "tomer filiba (tomerfiliba [at] gmail.com)"
43-
__maintainer__ = "Corbin Simpson <[email protected]>"
44-
__version__ = "2.06"
42+
__author__: str = "tomer filiba (tomerfiliba [at] gmail.com)"
43+
__maintainer__: str = "Corbin Simpson <[email protected]>"
44+
__version__: str = "2.06"
4545

4646
#===============================================================================
4747
# Shorthand expressions
@@ -59,10 +59,20 @@
5959
#===============================================================================
6060
import functools
6161
import warnings
62+
from typing import TYPE_CHECKING, TypeVar
6263

63-
def deprecated(f):
64+
if TYPE_CHECKING:
65+
from collections.abc import Callable
66+
67+
from typing_extensions import ParamSpec
68+
69+
_P = ParamSpec('_P')
70+
_T = TypeVar('_T')
71+
72+
73+
def deprecated(f: Callable[_P, _T]) -> Callable[_P, _T]:
6474
@functools.wraps(f)
65-
def wrapper(*args, **kwargs):
75+
def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _T:
6676
warnings.warn(
6777
"This name is deprecated, use %s instead" % f.__name__,
6878
DeprecationWarning, stacklevel=2)

0 commit comments

Comments
 (0)