ledgered 0.4.2.dev8__tar.gz → 0.5.1.dev3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/CHANGELOG.md +11 -0
  2. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/PKG-INFO +2 -1
  3. ledgered-0.5.1.dev3/doc/binary.md +44 -0
  4. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/doc/manifest.md +1 -1
  5. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/pyproject.toml +2 -0
  6. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/src/ledgered/__version__.py +2 -2
  7. ledgered-0.5.1.dev3/src/ledgered/binary.py +78 -0
  8. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/src/ledgered/manifest/app.py +1 -1
  9. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/src/ledgered/manifest/constants.py +1 -1
  10. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/src/ledgered/manifest/manifest.py +1 -1
  11. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/src/ledgered/manifest/tests.py +1 -1
  12. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/src/ledgered/manifest/use_cases.py +1 -1
  13. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/src/ledgered.egg-info/PKG-INFO +2 -1
  14. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/src/ledgered.egg-info/SOURCES.txt +5 -2
  15. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/src/ledgered.egg-info/entry_points.txt +1 -0
  16. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/src/ledgered.egg-info/requires.txt +1 -0
  17. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/tests/_data/ledger_app.toml +2 -2
  18. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/tests/unit/manifest/test_manifest.py +2 -2
  19. ledgered-0.5.1.dev3/tests/unit/test_binary.py +77 -0
  20. ledgered-0.4.2.dev8/tests/unit/manifest/test_types.py → ledgered-0.5.1.dev3/tests/unit/test_serializers.py +1 -1
  21. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/.github/workflows/build_and_tests.yml +0 -0
  22. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/.github/workflows/codeql-analysis.yml +0 -0
  23. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/.github/workflows/fast-checks.yml +0 -0
  24. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/.gitignore +0 -0
  25. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/LICENSE +0 -0
  26. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/README.md +0 -0
  27. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/setup.cfg +0 -0
  28. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/src/ledgered/__init__.py +0 -0
  29. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/src/ledgered/manifest/__init__.py +0 -0
  30. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/src/ledgered/manifest/cli.py +0 -0
  31. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/src/ledgered/manifest/errors.py +0 -0
  32. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/src/ledgered/manifest/utils.py +0 -0
  33. /ledgered-0.4.2.dev8/src/ledgered/manifest/types.py → /ledgered-0.5.1.dev3/src/ledgered/serializers.py +0 -0
  34. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/src/ledgered/utils/__init__.py +0 -0
  35. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/src/ledgered.egg-info/dependency_links.txt +0 -0
  36. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/src/ledgered.egg-info/top_level.txt +0 -0
  37. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/tests/_data/Cargo.toml +0 -0
  38. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/tests/_data/Makefile +0 -0
  39. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/tests/_data/full_correct.toml +0 -0
  40. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/tests/_data/minimal.toml +0 -0
  41. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/tests/_data/one_leaf.toml +0 -0
  42. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/tests/functional/__init__.py +0 -0
  43. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/tests/functional/manifest/__init__.py +0 -0
  44. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/tests/functional/manifest/test_cli.py +0 -0
  45. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/tests/unit/__init__.py +0 -0
  46. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/tests/unit/manifest/__init__.py +0 -0
  47. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/tests/unit/manifest/test_app.py +0 -0
  48. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/tests/unit/manifest/test_tests.py +0 -0
  49. {ledgered-0.4.2.dev8 → ledgered-0.5.1.dev3}/tests/unit/manifest/test_use_cases.py +0 -0
@@ -6,6 +6,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
8
 
9
+ ## [0.6.0] - 2024-03-26
10
+
11
+ ### Added
12
+
13
+ - ledger-binary: Adding an utilitary to parse embedded application ELF file metadatas.
14
+
15
+ ### Changed
16
+
17
+ - Renamed 'Europa' with official product name 'Flex'
18
+
19
+
9
20
  ## [0.5.0] - 2024-03-11
10
21
 
11
22
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ledgered
3
- Version: 0.4.2.dev8
3
+ Version: 0.5.1.dev3
4
4
  Summary: Python tools, utils, libraries, to be used with Ledger cryptodevices
5
5
  Author-email: Ledger <hello@ledger.fr>
6
6
  License: MIT License
@@ -38,6 +38,7 @@ Requires-Python: >=3.7
38
38
  Description-Content-Type: text/markdown
39
39
  License-File: LICENSE
40
40
  Requires-Dist: toml
41
+ Requires-Dist: pyelftools
41
42
  Provides-Extra: dev
42
43
  Requires-Dist: pytest; extra == "dev"
43
44
  Requires-Dist: pytest-cov; extra == "dev"
@@ -0,0 +1,44 @@
1
+ # Binary parser
2
+
3
+ Ledger embedded application ELF file contains metadata injected during the compilation. These
4
+ information are used by tools like [Speculos](https://speculos.ledger.com) to ease the manipulation
5
+ of the binaries (for instance: no need to explicit the device type, the SDK version, etc.).
6
+
7
+ For easier integration in Python code, `Ledgered` integrates a small binary parser to extract these
8
+ data. This code is the `ledgered.binary` library, and `Ledgered` also provides a CLI entrypoint
9
+ (`ledger-binary`).
10
+
11
+ ## `ledger-binary` utilitary
12
+
13
+ ### Example
14
+
15
+ Let's suppose we're compiling of the [Boilerplate application](https://github.com/LedgerHQ/app-boilerplate)
16
+ for Stax:
17
+
18
+ ```bash
19
+ bash-5.1# make -j BOLOS_SDK=$STAX_SDK
20
+ ```
21
+
22
+ The resulting ELF file is stored at (relative path) `./build/stax/bin/app.elf`. Let's use
23
+ `ledger-binary` to inspect this file:
24
+
25
+ ```bash
26
+ $ ledger-binary build/stax/bin/app.elf
27
+ api_level 15
28
+ app_name Boilerplate
29
+ app_version 2.1.0
30
+ sdk_graphics bagl
31
+ sdk_hash a23bad84cbf39a5071644d2191b177191c089b23
32
+ sdk_name ledger-secure-sdk
33
+ sdk_version v15.1.0
34
+ target stax
35
+ target_id 0x33200004
36
+ target_name TARGET_STAX
37
+ ```
38
+
39
+ It is also possible to ask for a JSON-like output:
40
+
41
+ ```bash
42
+ $ ledger-binary build/stax/bin/app.elf -j
43
+ {'api_level': '15', 'app_name': 'Boilerplate', 'app_version': '2.1.0', 'sdk_graphics': 'bagl', 'sdk_hash': 'a23bad84cbf39a5071644d2191b177191c089b23', 'sdk_name': 'ledger-secure-sdk', 'sdk_version': 'v15.1.0', 'target': 'stax', 'target_id': '0x33200004', 'target_name': 'TARGET_STAX'}
44
+ ```
@@ -6,7 +6,7 @@ This manifest contains application metadata such as build directory, compatible
6
6
  and is used by several tools to know how to build or test the application.
7
7
 
8
8
  The `ledgered.utils.manifest` library is used to parse and manipulate application manifests
9
- in Python code. `Ledgered` also provides a cli entrypoint (`ledger-manifest`) to parse, extract
9
+ in Python code. `Ledgered` also provides a CLI entrypoint (`ledger-manifest`) to parse, extract
10
10
  and check information from manifests.
11
11
 
12
12
  ## Manifest content
@@ -28,6 +28,7 @@ dynamic = [ "version" ]
28
28
  requires-python = ">=3.7"
29
29
  dependencies = [
30
30
  "toml",
31
+ "pyelftools",
31
32
  ]
32
33
 
33
34
  [project.optional-dependencies]
@@ -41,6 +42,7 @@ Home = "https://github.com/LedgerHQ/ledgered"
41
42
 
42
43
  [project.scripts]
43
44
  ledger-manifest = "ledgered.manifest.cli:main"
45
+ ledger-binary = "ledgered.binary:main"
44
46
 
45
47
  [tool.setuptools_scm]
46
48
  write_to = "src/ledgered/__version__.py"
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.4.2.dev8'
16
- __version_tuple__ = version_tuple = (0, 4, 2, 'dev8')
15
+ __version__ = version = '0.5.1.dev3'
16
+ __version_tuple__ = version_tuple = (0, 5, 1, 'dev3')
@@ -0,0 +1,78 @@
1
+ import logging
2
+ from argparse import ArgumentParser
3
+ from dataclasses import asdict, dataclass
4
+ from elftools.elf.elffile import ELFFile
5
+ from pathlib import Path
6
+ from typing import Optional, Union
7
+
8
+ from ledgered.serializers import Jsonable
9
+
10
+ LEDGER_PREFIX = "ledger."
11
+ DEFAULT_GRAPHICS = "bagl"
12
+
13
+
14
+ @dataclass
15
+ class Sections(Jsonable):
16
+ api_level: Optional[str] = None
17
+ app_name: Optional[str] = None
18
+ app_version: Optional[str] = None
19
+ sdk_graphics: str = DEFAULT_GRAPHICS
20
+ sdk_hash: Optional[str] = None
21
+ sdk_name: Optional[str] = None
22
+ sdk_version: Optional[str] = None
23
+ target: Optional[str] = None
24
+ target_id: Optional[str] = None
25
+ target_name: Optional[str] = None
26
+
27
+ def __str__(self) -> str:
28
+ return "\n".join(f"{key} {value}" for key, value in sorted(asdict(self).items()))
29
+
30
+
31
+ class LedgerBinaryApp:
32
+
33
+ def __init__(self, binary_path: Union[str, Path]):
34
+ if isinstance(binary_path, str):
35
+ binary_path = Path(binary_path)
36
+ self._path = binary_path = binary_path.resolve()
37
+ logging.info("Parsing binary '%s'", self._path)
38
+ with self._path.open("rb") as filee:
39
+ sections = {
40
+ s.name.replace(LEDGER_PREFIX, ""): s.data().decode()
41
+ for s in ELFFile(filee).iter_sections() if LEDGER_PREFIX in s.name
42
+ }
43
+ self._sections = Sections(**sections)
44
+
45
+ @property
46
+ def sections(self) -> Sections:
47
+ return self._sections
48
+
49
+
50
+ def set_parser() -> ArgumentParser:
51
+ parser = ArgumentParser(prog="ledger-binary",
52
+ description="Utilitary to parse Ledger embedded application ELF file "
53
+ "and output metadatas")
54
+ parser.add_argument('-v', '--verbose', action='count', default=0)
55
+ parser.add_argument("binary", type=Path, help="The ledger embedded application ELF file")
56
+ parser.add_argument("-j",
57
+ "--json",
58
+ required=False,
59
+ action="store_true",
60
+ help="outputs as JSON rather than text")
61
+ return parser
62
+
63
+
64
+ def main() -> None:
65
+ args = set_parser().parse_args()
66
+ assert args.binary.is_file(), f"'{args.binary.resolve()}' does not appear to be a file."
67
+
68
+ # verbosity
69
+ if args.verbose == 1:
70
+ logging.root.setLevel(logging.INFO)
71
+ elif args.verbose > 1:
72
+ logging.root.setLevel(logging.DEBUG)
73
+
74
+ app = LedgerBinaryApp(args.binary)
75
+ if args.json:
76
+ print(app.sections.json)
77
+ else:
78
+ print(app.sections)
@@ -2,8 +2,8 @@ from dataclasses import dataclass
2
2
  from pathlib import Path
3
3
  from typing import Iterable, Union
4
4
 
5
+ from ledgered.serializers import Jsonable, JsonSet
5
6
  from .constants import EXISTING_DEVICES
6
- from .types import Jsonable, JsonSet
7
7
 
8
8
 
9
9
  @dataclass
@@ -1,3 +1,3 @@
1
- EXISTING_DEVICES = ["nanos", "nanox", "nanos+", "stax", "europa"]
1
+ EXISTING_DEVICES = ["nanos", "nanox", "nanos+", "stax", "flex"]
2
2
  MANIFEST_FILE_NAME = "ledger_app.toml"
3
3
  DEFAULT_USE_CASE = "default"
@@ -4,10 +4,10 @@ from dataclasses import dataclass
4
4
  from pathlib import Path
5
5
  from typing import Dict, IO, Optional, Union
6
6
 
7
+ from ledgered.serializers import Jsonable
7
8
  from .app import AppConfig
8
9
  from .constants import MANIFEST_FILE_NAME
9
10
  from .tests import TestsConfig
10
- from .types import Jsonable
11
11
  from .use_cases import UseCasesConfig
12
12
 
13
13
 
@@ -3,9 +3,9 @@ from pathlib import Path
3
3
  from typing import Dict, List, Optional, Union
4
4
  from urllib.parse import urlparse
5
5
 
6
+ from ledgered.serializers import Jsonable, JsonDict, JsonSet
6
7
  from .constants import DEFAULT_USE_CASE
7
8
  from .errors import MissingField
8
- from .types import Jsonable, JsonDict, JsonSet
9
9
  from .utils import getLogger
10
10
 
11
11
  APPLICATION_DIRECTORY_KEY = "application_directory"
@@ -1,8 +1,8 @@
1
1
  from dataclasses import dataclass
2
2
  from typing import Dict, Optional
3
3
 
4
+ from ledgered.serializers import Jsonable, JsonDict
4
5
  from .constants import DEFAULT_USE_CASE
5
- from .types import Jsonable, JsonDict
6
6
 
7
7
 
8
8
  @dataclass
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ledgered
3
- Version: 0.4.2.dev8
3
+ Version: 0.5.1.dev3
4
4
  Summary: Python tools, utils, libraries, to be used with Ledger cryptodevices
5
5
  Author-email: Ledger <hello@ledger.fr>
6
6
  License: MIT License
@@ -38,6 +38,7 @@ Requires-Python: >=3.7
38
38
  Description-Content-Type: text/markdown
39
39
  License-File: LICENSE
40
40
  Requires-Dist: toml
41
+ Requires-Dist: pyelftools
41
42
  Provides-Extra: dev
42
43
  Requires-Dist: pytest; extra == "dev"
43
44
  Requires-Dist: pytest-cov; extra == "dev"
@@ -6,9 +6,12 @@ pyproject.toml
6
6
  .github/workflows/build_and_tests.yml
7
7
  .github/workflows/codeql-analysis.yml
8
8
  .github/workflows/fast-checks.yml
9
+ doc/binary.md
9
10
  doc/manifest.md
10
11
  src/ledgered/__init__.py
11
12
  src/ledgered/__version__.py
13
+ src/ledgered/binary.py
14
+ src/ledgered/serializers.py
12
15
  src/ledgered.egg-info/PKG-INFO
13
16
  src/ledgered.egg-info/SOURCES.txt
14
17
  src/ledgered.egg-info/dependency_links.txt
@@ -22,7 +25,6 @@ src/ledgered/manifest/constants.py
22
25
  src/ledgered/manifest/errors.py
23
26
  src/ledgered/manifest/manifest.py
24
27
  src/ledgered/manifest/tests.py
25
- src/ledgered/manifest/types.py
26
28
  src/ledgered/manifest/use_cases.py
27
29
  src/ledgered/manifest/utils.py
28
30
  src/ledgered/utils/__init__.py
@@ -36,9 +38,10 @@ tests/functional/__init__.py
36
38
  tests/functional/manifest/__init__.py
37
39
  tests/functional/manifest/test_cli.py
38
40
  tests/unit/__init__.py
41
+ tests/unit/test_binary.py
42
+ tests/unit/test_serializers.py
39
43
  tests/unit/manifest/__init__.py
40
44
  tests/unit/manifest/test_app.py
41
45
  tests/unit/manifest/test_manifest.py
42
46
  tests/unit/manifest/test_tests.py
43
- tests/unit/manifest/test_types.py
44
47
  tests/unit/manifest/test_use_cases.py
@@ -1,2 +1,3 @@
1
1
  [console_scripts]
2
+ ledger-binary = ledgered.binary:main
2
3
  ledger-manifest = ledgered.manifest.cli:main
@@ -1,4 +1,5 @@
1
1
  toml
2
+ pyelftools
2
3
 
3
4
  [dev]
4
5
  pytest
@@ -1,8 +1,8 @@
1
1
  [app]
2
2
  sdk = "Rust"
3
3
  build_directory = ""
4
- devices = ["nanos", "Stax", "EUROPA"]
4
+ devices = ["nanos", "Stax", "FLEX"]
5
5
 
6
6
  [tests]
7
7
  unit_directory = "unit"
8
- pytest_directory = "pytest"
8
+ pytest_directory = "pytest"
@@ -10,7 +10,7 @@ class TestManifest(TestCase):
10
10
 
11
11
  def check_ledger_app_toml(self, manifest: Manifest) -> None:
12
12
  self.assertEqual(manifest.app.sdk, "rust")
13
- self.assertEqual(manifest.app.devices, {"nanos", "stax", "europa"})
13
+ self.assertEqual(manifest.app.devices, {"nanos", "stax", "flex"})
14
14
  self.assertEqual(manifest.app.build_directory, Path(""))
15
15
  self.assertTrue(manifest.app.is_rust)
16
16
  self.assertFalse(manifest.app.is_c)
@@ -19,7 +19,7 @@ class TestManifest(TestCase):
19
19
  self.assertEqual(manifest.tests.pytest_directory, Path("pytest"))
20
20
 
21
21
  def test___init__ok(self):
22
- app = {"sdk": "rust", "devices": ["NANOS", "stAX", "europa"], "build_directory": ""}
22
+ app = {"sdk": "rust", "devices": ["NANOS", "stAX", "flex"], "build_directory": ""}
23
23
  tests = {"unit_directory": "unit", "pytest_directory": "pytest"}
24
24
  self.check_ledger_app_toml(Manifest(app, tests))
25
25
 
@@ -0,0 +1,77 @@
1
+ from dataclasses import dataclass
2
+ from unittest import TestCase
3
+ from unittest.mock import MagicMock, patch
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ from ledgered import binary as B
8
+
9
+
10
+ class TestSections(TestCase):
11
+
12
+ def setUp(self):
13
+ self.inputs = {
14
+ "api_level": "api_level",
15
+ "app_name": "app_name",
16
+ "app_version": "app_version",
17
+ "sdk_graphics": "sdk_graphics",
18
+ "sdk_hash": "sdk_hash",
19
+ "sdk_name": "sdk_name",
20
+ "sdk_version": "sdk_version",
21
+ "target": "target",
22
+ "target_id": "target_id",
23
+ "target_name": "target_name"
24
+ }
25
+
26
+ def test___init__empty(self):
27
+ sections = B.Sections()
28
+ self.assertIsNone(sections.api_level)
29
+ self.assertIsNone(sections.app_name)
30
+ self.assertIsNone(sections.app_version)
31
+ self.assertEqual(sections.sdk_graphics, B.DEFAULT_GRAPHICS)
32
+ self.assertIsNone(sections.sdk_hash)
33
+ self.assertIsNone(sections.sdk_name)
34
+ self.assertIsNone(sections.sdk_version)
35
+ self.assertIsNone(sections.target)
36
+ self.assertIsNone(sections.target_id)
37
+ self.assertIsNone(sections.target_name)
38
+
39
+ def test___str__(self):
40
+ sections = B.Sections(**self.inputs)
41
+ self.assertEqual("\n".join(f"{k} {v}"
42
+ for k,v in sorted(self.inputs.items())),
43
+ str(sections))
44
+
45
+ def test_json(self):
46
+ sections = B.Sections(**self.inputs)
47
+ self.assertDictEqual(self.inputs, sections.json)
48
+
49
+
50
+ @dataclass
51
+ class Section:
52
+ name: str
53
+ _data: Any
54
+ def data(self) -> Any:
55
+ return self._data
56
+
57
+
58
+ class TestLedgerBinaryApp(TestCase):
59
+
60
+ def test___init__(self):
61
+ path = Path("/dev/urandom")
62
+ api_level, sdk_hash = "something", "some hash"
63
+ expected = B.Sections(api_level=api_level, sdk_hash=sdk_hash)
64
+ with patch("ledgered.binary.ELFFile") as elfmock:
65
+ elfmock().iter_sections.return_value = [
66
+ Section("unused", 1),
67
+ Section("ledger.api_level", api_level.encode()),
68
+ Section("ledger.sdk_hash", sdk_hash.encode()),
69
+ Section("still not used", b"some data")
70
+ ]
71
+ bin = B.LedgerBinaryApp(path)
72
+ self.assertEqual(bin.sections, expected)
73
+
74
+ def test___init__from_str(self):
75
+ path = "/dev/urandom"
76
+ with patch("ledgered.binary.ELFFile") as elfmock:
77
+ B.LedgerBinaryApp(path)
@@ -1,7 +1,7 @@
1
1
  from dataclasses import dataclass
2
2
  from unittest import TestCase
3
3
 
4
- from ledgered.manifest.types import Jsonable, JsonList, JsonSet, JsonDict
4
+ from ledgered.serializers import Jsonable, JsonList, JsonSet, JsonDict
5
5
 
6
6
 
7
7
  @dataclass
File without changes
File without changes
File without changes
File without changes