From 14b2eead682beb2d318fc9745e54cf6273bf3916 Mon Sep 17 00:00:00 2001 From: Julien Nicoulaud Date: Thu, 10 Oct 2024 12:24:17 +0200 Subject: [PATCH 01/12] chore: typo fix --- src/erc7730/lint/lint_validate_display_fields.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/erc7730/lint/lint_validate_display_fields.py b/src/erc7730/lint/lint_validate_display_fields.py index c1759c3..8b88fd0 100644 --- a/src/erc7730/lint/lint_validate_display_fields.py +++ b/src/erc7730/lint/lint_validate_display_fields.py @@ -56,14 +56,14 @@ def _validate_eip712_paths(cls, descriptor: ResolvedERC7730Descriptor, out: Outp out.debug( title="Optional Display field missing", message=f"Display field for path `{path}` is missing for message {schema.primaryType}. " - f"If intentionally excluded, please add it to `exclude` list to avoid this " + f"If intentionally excluded, please add it to `excluded` list to avoid this " f"warning.", ) else: out.warning( title="Missing Display field", message=f"Display field for path `{path}` is missing for message {schema.primaryType}. " - f"If intentionally excluded, please add it to `exclude` list to avoid this " + f"If intentionally excluded, please add it to `excluded` list to avoid this " f"warning.", ) for path in format_paths - eip712_paths: @@ -129,13 +129,13 @@ def _validate_abi_paths(cls, descriptor: ResolvedERC7730Descriptor, out: OutputA out.debug( title="Optional Display field missing", message=f"Display field for path `{path}` is missing for selector {function}. If " - f"intentionally excluded, please add it to `exclude` list to avoid this warning.", + f"intentionally excluded, please add it to `excluded` list to avoid this warning.", ) else: out.warning( title="Missing Display field", message=f"Display field for path `{path}` is missing for selector {function}. If " - f"intentionally excluded, please add it to `exclude` list to avoid this warning.", + f"intentionally excluded, please add it to `excluded` list to avoid this warning.", ) for path in format_paths - abi_paths: out.error( From b616e4c584725c099d012920fa3d1fd7dada7bfc Mon Sep 17 00:00:00 2001 From: Julien Nicoulaud Date: Thu, 10 Oct 2024 12:42:50 +0200 Subject: [PATCH 02/12] fix: fix edge case with pydantic errors this shows: ``` checking registry/poap/calldata-PoapBridge.json... ERROR: Validation error: {'type': 'json_invalid', 'loc': (), 'msg': 'Invalid JSON: expected value at line 1 column 1', 'input': 'Missing/Invalid API Key', 'ctx': {'error': 'expected value at line 1 column 1'}} ``` instead of: ``` checking registry/poap/calldata-PoapBridge.json... Error: Invalid JSON: expected value at line 1 column 1 ``` --- src/erc7730/lint/lint.py | 5 ++++- tests/model/test_model_serialization.py | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/erc7730/lint/lint.py b/src/erc7730/lint/lint.py index 540266d..71be886 100644 --- a/src/erc7730/lint/lint.py +++ b/src/erc7730/lint/lint.py @@ -82,7 +82,10 @@ def lint_file(path: Path, linter: ERC7730Linter, out: OutputAdder) -> None: except ValidationError as e: for ex in e.errors(include_url=False, include_context=True, include_input=True): loc = ex["loc"] - adder.error(title=f"{loc} is invalid", message=ex["msg"]) + if loc == (): + adder.error(title="Validation error", message=str(ex)) + else: + adder.error(title=f"{loc} is invalid", message=ex["msg"]) except Exception as e: # TODO unwrap pydantic validation errors here to provide more user-friendly error messages adder.error(title="Failed to parse descriptor", message=str(e)) diff --git a/tests/model/test_model_serialization.py b/tests/model/test_model_serialization.py index 64a409e..fe5f86e 100644 --- a/tests/model/test_model_serialization.py +++ b/tests/model/test_model_serialization.py @@ -20,6 +20,9 @@ def test_schema(input_file: Path) -> None: """Test model serializes to JSON that matches the schema.""" assert_valid_erc_7730(InputERC7730Descriptor.load(input_file)) +def test_poap() -> None: + """Test model serializes to JSON that matches the schema.""" + InputERC7730Descriptor.load(Path("/home/jnicoulaud/work/ledger/backend/cal/python-erc7730/python-erc7730-1/tests/registries/clear-signing-erc7730-registry/registry/poap/calldata-PoapBridge.json")) @pytest.mark.parametrize("input_file", ERC7730_DESCRIPTORS, ids=path_id) def test_round_trip(input_file: Path) -> None: From a2a5f5a55ad149a6ad80ba598a4d1d6bded8f541 Mon Sep 17 00:00:00 2001 From: Julien Nicoulaud Date: Thu, 10 Oct 2024 12:48:00 +0200 Subject: [PATCH 03/12] feat: make `excluded` field work as prefix --- src/erc7730/lint/lint_validate_display_fields.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/erc7730/lint/lint_validate_display_fields.py b/src/erc7730/lint/lint_validate_display_fields.py index 8b88fd0..7ff8180 100644 --- a/src/erc7730/lint/lint_validate_display_fields.py +++ b/src/erc7730/lint/lint_validate_display_fields.py @@ -49,7 +49,13 @@ def _validate_eip712_paths(cls, descriptor: ResolvedERC7730Descriptor, out: Outp excluded = primary_type_format.excluded or [] for path in eip712_paths - format_paths: - if path in excluded: + + allowed = False + for excluded_path in excluded: + if path.startswith(excluded_path): + allowed = True + break + if allowed: continue if any(re.fullmatch(regex, path) for regex in AUTHORIZED_MISSING_DISPLAY_FIELDS_REGEX): @@ -122,7 +128,13 @@ def _validate_abi_paths(cls, descriptor: ResolvedERC7730Descriptor, out: OutputA function = cls._display(selector, keccak) for path in abi_paths - format_paths: - if path in excluded: + + allowed = False + for excluded_path in excluded: + if path.startswith(excluded_path): + allowed = True + break + if allowed: continue if not any(re.fullmatch(regex, path) for regex in AUTHORIZED_MISSING_DISPLAY_FIELDS_REGEX): From 2d286bb3aaa4e048ec3013552989c57e020111d5 Mon Sep 17 00:00:00 2001 From: jnicoulaud-ledger <102984500+jnicoulaud-ledger@users.noreply.github.com> Date: Thu, 10 Oct 2024 12:55:38 +0200 Subject: [PATCH 04/12] doc: add minimal docsite (#64) * doc: add minimal docsite * doc: add minimal docsite --- .github/workflows/ci.yml | 36 ++- docs/conf.py | 80 +++++ docs/index.md | 18 ++ docs/pages/usage_cli.md | 53 ++++ docs/pages/usage_library.md | 120 ++++++++ docs/static/custom.css | 31 ++ docs/static/ledger-icon.png | Bin 0 -> 12554 bytes docs/static/ledger-logo.png | Bin 0 -> 45084 bytes pdm.lock | 584 +++++++++++++++++++++++++++++++++++- pyproject.toml | 12 + 10 files changed, 917 insertions(+), 17 deletions(-) create mode 100644 docs/conf.py create mode 100644 docs/index.md create mode 100644 docs/pages/usage_cli.md create mode 100644 docs/pages/usage_library.md create mode 100644 docs/static/custom.css create mode 100644 docs/static/ledger-icon.png create mode 100644 docs/static/ledger-logo.png diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1ec14a6..991c25a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -89,6 +89,10 @@ jobs: timeout-minutes: 10 uses: ./.github/actions/pdm + - name: Setup Graphviz + timeout-minutes: 10 + uses: ts-graphviz/setup-graphviz@v2 + - name: Install dependencies timeout-minutes: 10 run: pdm install --dev --check --frozen-lockfile @@ -117,7 +121,7 @@ jobs: - name: Publish test reports timeout-minutes: 10 - if: always() + if: ${{ !cancelled() }} uses: pmeier/pytest-results-action@v0.7.1 with: path: tests/.tests.xml @@ -125,3 +129,33 @@ jobs: summary: true display-options: fEX fail-on-empty: false + + - name: Setup docs cache + timeout-minutes: 10 + if: ${{ !cancelled() }} + uses: actions/cache@v4 + with: + path: docs/build + key: docs-cache-${{ hashFiles('docs') }} + + - name: Build docs + timeout-minutes: 10 + if: ${{ !cancelled() }} + run: pdm run docs + + - name: Setup Github Pages + timeout-minutes: 10 + if: ${{ !cancelled() && github.event_name == 'push' && github.ref == 'refs/heads/main' }} + uses: actions/configure-pages@v5 + + - name: Upload docs to Github Pages + timeout-minutes: 10 + if: ${{ !cancelled() && github.event_name == 'push' && github.ref == 'refs/heads/main' }} + uses: actions/upload-pages-artifact@v3 + with: + path: './docs/build' + + - name: Deploy GitHub Pages + timeout-minutes: 10 + if: ${{ !cancelled() && github.event_name == 'push' && github.ref == 'refs/heads/main' }} + uses: actions/deploy-pages@v4 diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..5e88b16 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,80 @@ +import sys +from dataclasses import asdict +from pathlib import Path + +from sphinxawesome_theme import ThemeOptions +from sphinxawesome_theme.postprocess import Icons + +sys.path.insert(0, str(Path(__file__).parent / "src")) + +nitpicky = True +project = "python-erc7730" +copyright = "2024, Ledger" +author = "Ledger" +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.githubpages", + "sphinx.ext.viewcode", + "sphinx.ext.linkcode", + "sphinx.ext.autosummary", + "sphinx.ext.graphviz", + "sphinx.ext.intersphinx", + "sphinx.ext.todo", + "sphinx.ext.autosummary", + "sphinxcontrib.mermaid", + "sphinxcontrib.typer", + "sphinxcontrib.apidoc", + "sphinxcontrib.autodoc_pydantic", + "myst_parser", + "sphinx_github_style", + "sphinx_issues", + "sphinx_design", +] +myst_enable_extensions = [ + "fieldlist", + "linkify", + "substitution", +] +source_suffix = { + ".rst": "restructuredtext", + ".md": "markdown", +} +intersphinx_mapping = { + "python": ("https://docs.python.org/3", None), +} +templates_path = ["templates"] +issues_github_path = "LedgerHQ/python-erc7730" +linkcode_url = "LedgerHQ/python-erc7730" +apidoc_module_dir = "../src" +apidoc_output_dir = "build/reference" +apidoc_separate_modules = True +autosummary_generate = True +autodoc_pydantic_model_show_json = False +autodoc_pydantic_model_show_config_summary = False +autodoc_pydantic_model_validator_members = False +autodoc_pydantic_model_validator_summary = False +autodoc_pydantic_model_field_summary = False +autodoc_pydantic_model_hide_paramlist = True +autodoc_pydantic_model_hide_reused_validator = True +html_theme = "sphinxawesome_theme" +html_static_path = ["static"] +html_css_files = ["custom.css", "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css"] +html_favicon = "static/ledger-icon.png" +html_logo = "static/ledger-icon.png" +html_title = "python-erc7730" +html_show_sphinx = False +html_permalinks_icon = Icons.permalinks_icon +html_context = {"default_mode": "light"} +html_theme_options = asdict( + ThemeOptions( + show_prev_next=False, + show_scrolltop=True, + show_breadcrumbs=True, + breadcrumbs_separator=">", + ) +) +pygments_style = "friendly" +pygments_style_dark = "nord-darker" +todo_include_todos = True +mathjax3_config = {"displayAlign": "left"} +mermaid_version = "11.3.0" diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..9090d5d --- /dev/null +++ b/docs/index.md @@ -0,0 +1,18 @@ +python-erc7730 +============== + +**This library provides tooling for the ERC-7730 standard.** + +See for the standard specification and example descriptors. + +This library implements: + * Reading and writing ERC-7730 descriptor files into an object model + * Validation, available as a command line tool + * Conversion between Ledger specific legacy descriptors and ERC-7730 + + +```{toctree} +:maxdepth: 2 +pages/usage_cli.md +pages/usage_library.md +``` diff --git a/docs/pages/usage_cli.md b/docs/pages/usage_cli.md new file mode 100644 index 0000000..ac5a6be --- /dev/null +++ b/docs/pages/usage_cli.md @@ -0,0 +1,53 @@ +# Command line usage + +## Installation + +The `erc7730` tool is available as a [Python package on PyPI](https://pypi.org/project/erc7730). You can install it +using `pip`: + +```bash +pip install --user erc7730 +``` + +## Validation + +You can validate your setup by running the `erc7730` command: + +```{typer} erc7730.main.app +:preferred: svg +:theme: dark +:width: 100 +:prog: cal +``` + +## Commands + +### `erc7730 lint` + +The `lint` command runs validations on descriptors and outputs warnings and errors to the console: +```shell +$ erc7730 lint registry +checking registry/lido/calldata-stETH.json... + +checking registry/lido/calldata-wstETH.json... + +checking registry/makerdao/eip712-permit-DAI.json... +DEBUG: Optional Display field missing: Display field for path `nonce` is missing for message Permit. If intentionally excluded, please add +it to `exclude` list to avoid this warning. +WARNING: Missing Display field: Display field for path `owner` is missing for message Permit. If intentionally excluded, please add it to +`exclude` list to avoid this warning. +``` + +It can be called with single files or directories, in which case all descriptors will be checked. + +### `erc7730 convert` + +The `convert` command converts descriptors between the ERC-7730 format and the legacy formats used by Ledger, for +instance: +```shell +$ erc7730 convert eip712-to-erc7730 ledger-asset-dapps/ethereum/1inch/eip712.json erc7730-eip712-1inch.json +generated erc7730-eip712-1inch.0x119c71d3bbac22029622cbaec24854d3d32d2828.json ✅ +generated erc7730-eip712-1inch.0x111111125421ca6dc452d289314280a0f8842a65.json ✅ +``` + +Please run `erc7730 convert --help` for more information on the available options. diff --git a/docs/pages/usage_library.md b/docs/pages/usage_library.md new file mode 100644 index 0000000..680fccf --- /dev/null +++ b/docs/pages/usage_library.md @@ -0,0 +1,120 @@ +# Library usage + +## Installation + +The `erc7730` library is available as a [Python package on PyPI](https://pypi.org/project/erc7730). You can install it +using `pip`: + +```bash +pip install --user erc7730 +``` + +## Overview + +A typical usage of the `erc7730` library is to load descriptors and compile them to a wallet manufacturer-specific +format: + +```{mermaid} +--- +title: Node +--- +flowchart TD + input_json@{ shape: doc, label: "ERC-7730 descriptor file" } + input[input ERC-7730 descriptor] + resolved[resolved ERC-7730 descriptor] + vendor[wallet specific ERC-7730 descriptor] + input_json -- load/validate --> input + input -- resolve/validate --> resolved + resolved -- convert --> vendor +``` + +## Packages + +### `erc7730.model` + +The `erc7730.model` package implements an object model mapping for ERC-7730 descriptors using +[pydantic](https://docs.pydantic.dev), allowing to easily read/write/transform descriptors. + +#### input and resolved forms + +Descriptors can be manipulated in 2 forms: + - *"Input" form*: the descriptor document as defined in the ERC-7730 specification, after `include` tags have been + resolved. It is possible to save back the descriptor back to the original descriptor document. + - *"Resolved" form*: the descriptor after pre-processing: + - URLs have been fetched + - Contract addresses have been normalized to lowercase + - References have been inlined + - Constants have been inlined + - Field definitions have been inlined + - Selectors have been converted to 4 bytes form + This form is the most adapted to be used by tools and applications. + +```{eval-rst} +.. autosummary:: + :nosignatures: + + erc7730.model.input.descriptor.InputERC7730Descriptor + erc7730.model.resolved.descriptor.ResolvedERC7730Descriptor +``` + +#### input data model + +```{eval-rst} +.. autopydantic_model:: erc7730.model.input.descriptor.InputERC7730Descriptor + :noindex: + :model-show-config-summary: False + :model-show-field-summary: False + :model-erdantic-figure: True + :model-erdantic-figure-collapsed: False +``` + +#### resolved data model + +```{eval-rst} +.. autopydantic_model:: erc7730.model.resolved.descriptor.ResolvedERC7730Descriptor + :noindex: + :model-show-config-summary: False + :model-show-field-summary: False + :model-erdantic-figure: True + :model-erdantic-figure-collapsed: False +``` + +### `erc7730.lint` + +The `erc7730.lint` package implements the `erc7730 lint` command. The main interface is `ERC7730Linter`: + +```{eval-rst} +.. autoclass:: erc7730.lint.ERC7730Linter + :members: +``` + +The package contains several linter implementations: + +```{eval-rst} +.. autosummary:: + :nosignatures: + + erc7730.lint.lint_validate_abi.ValidateABILinter + erc7730.lint.lint_validate_display_fields.ValidateDisplayFieldsLinter + erc7730.lint.lint_transaction_type_classifier.ClassifyTransactionTypeLinter +``` + +### `erc7730.convert` + +The `erc7730.convert` package implements the `erc7730 convert` command. The main interface is `ERC7730Converter`: + +```{eval-rst} +.. autoclass:: erc7730.convert.ERC7730Converter + :members: +``` + +The package contains several converter implementations: + +```{eval-rst} +.. autosummary:: + :nosignatures: + + erc7730.convert.convert_erc7730_input_to_resolved.ERC7730InputToResolved + erc7730.convert.convert_eip712_to_erc7730.EIP712toERC7730Converter + erc7730.convert.convert_erc7730_to_eip712.ERC7730toEIP712Converter +``` diff --git a/docs/static/custom.css b/docs/static/custom.css new file mode 100644 index 0000000..bb87f39 --- /dev/null +++ b/docs/static/custom.css @@ -0,0 +1,31 @@ +@media (min-width: 1200px) { + .container { + max-width:1200px + } +} + +@media (min-width: 1400px) { + .container { + max-width:1400px + } +} + +@media (min-width: 1600px) { + .container { + max-width:1600px + } +} + +@media (min-width: 1800px) { + .container { + max-width:1800px + } +} + +#content ol, #content ul:not(.search) { + margin-top: 0.2rem; +} + +#content ol p, #content ol>li, #content ul:not(.search) p, #content ul:not(.search)>li { + margin-top: 0.2rem; +} diff --git a/docs/static/ledger-icon.png b/docs/static/ledger-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ef35fc7730549aec3a5ba31bc026e81eaa22be7d GIT binary patch literal 12554 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.0.0; python_version < \"3.9\"", ] @@ -38,6 +49,65 @@ files = [ {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, ] +[[package]] +name = "autodoc-pydantic" +version = "2.2.0" +requires_python = "<4.0.0,>=3.8.1" +summary = "Seamlessly integrate pydantic models in your Sphinx documentation." +groups = ["dev"] +dependencies = [ + "Sphinx>=4.0", + "importlib-metadata>1; python_version <= \"3.8\"", + "pydantic-settings<3.0.0,>=2.0", + "pydantic<3.0.0,>=2.0", +] +files = [ + {file = "autodoc_pydantic-2.2.0-py3-none-any.whl", hash = "sha256:8c6a36fbf6ed2700ea9c6d21ea76ad541b621fbdf16b5a80ee04673548af4d95"}, +] + +[[package]] +name = "autodoc-pydantic" +version = "2.2.0" +extras = ["erdantic"] +requires_python = "<4.0.0,>=3.8.1" +summary = "Seamlessly integrate pydantic models in your Sphinx documentation." +groups = ["dev"] +dependencies = [ + "autodoc-pydantic==2.2.0", + "erdantic<2.0", +] +files = [ + {file = "autodoc_pydantic-2.2.0-py3-none-any.whl", hash = "sha256:8c6a36fbf6ed2700ea9c6d21ea76ad541b621fbdf16b5a80ee04673548af4d95"}, +] + +[[package]] +name = "babel" +version = "2.16.0" +requires_python = ">=3.8" +summary = "Internationalization utilities" +groups = ["dev"] +dependencies = [ + "pytz>=2015.7; python_version < \"3.9\"", +] +files = [ + {file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"}, + {file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"}, +] + +[[package]] +name = "beautifulsoup4" +version = "4.12.3" +requires_python = ">=3.6.0" +summary = "Screen-scraping library" +groups = ["dev"] +dependencies = [ + "soupsieve>1.2", +] +files = [ + {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, + {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, +] + [[package]] name = "cattrs" version = "24.1.2" @@ -59,7 +129,7 @@ name = "certifi" version = "2024.8.30" requires_python = ">=3.6" summary = "Python package for providing Mozilla's CA Bundle." -groups = ["default"] +groups = ["default", "dev"] files = [ {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, @@ -81,7 +151,7 @@ name = "charset-normalizer" version = "3.3.2" requires_python = ">=3.7.0" summary = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -groups = ["default"] +groups = ["default", "dev"] files = [ {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, @@ -107,7 +177,7 @@ name = "click" version = "8.1.7" requires_python = ">=3.7" summary = "Composable command line interface toolkit" -groups = ["default"] +groups = ["default", "dev"] dependencies = [ "colorama; platform_system == \"Windows\"", "importlib-metadata; python_version < \"3.8\"", @@ -211,6 +281,17 @@ files = [ {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, ] +[[package]] +name = "docutils" +version = "0.21.2" +requires_python = ">=3.9" +summary = "Docutils -- Python Documentation Utilities" +groups = ["dev"] +files = [ + {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, + {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, +] + [[package]] name = "eip712-clearsign" version = "2.1.0" @@ -225,6 +306,26 @@ files = [ {file = "eip712_clearsign-2.1.0.tar.gz", hash = "sha256:68b3636fe5dbc1363a67fb1d5816c6b3a4bc123273df1f2e2ff2b83c866ef034"}, ] +[[package]] +name = "erdantic" +version = "1.0.5" +requires_python = ">=3.8" +summary = "Entity relationship diagrams for Python data model classes like Pydantic." +groups = ["dev"] +dependencies = [ + "pydantic-core", + "pydantic>=2", + "pygraphviz", + "sortedcontainers-pydantic", + "typenames>=1.3", + "typer", + "typing-extensions>4; python_version < \"3.12\"", +] +files = [ + {file = "erdantic-1.0.5-py3-none-any.whl", hash = "sha256:f9ae0016d5e6116e14cf11b75c6a1ead467d7a13adbea9632df102199e22499a"}, + {file = "erdantic-1.0.5.tar.gz", hash = "sha256:e35cc1babb37b8dc62fc220ce61a167afaebd11e391c6bd53882918a3b01644b"}, +] + [[package]] name = "eth-hash" version = "0.7.0" @@ -347,12 +448,23 @@ name = "idna" version = "3.10" requires_python = ">=3.6" summary = "Internationalized Domain Names in Applications (IDNA)" -groups = ["default"] +groups = ["default", "dev"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, ] +[[package]] +name = "imagesize" +version = "1.4.1" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +summary = "Getting image size from png/jpeg/jpeg2000/gif file" +groups = ["dev"] +files = [ + {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, + {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, +] + [[package]] name = "iniconfig" version = "2.0.0" @@ -364,6 +476,20 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "jinja2" +version = "3.1.4" +requires_python = ">=3.7" +summary = "A very fast and expressive template engine." +groups = ["dev"] +dependencies = [ + "MarkupSafe>=2.0", +] +files = [ + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, +] + [[package]] name = "jsonschema" version = "4.23.0" @@ -409,6 +535,20 @@ files = [ {file = "lark-1.2.2.tar.gz", hash = "sha256:ca807d0162cd16cef15a8feecb862d7319e7a09bdb13aef927968e45040fed80"}, ] +[[package]] +name = "linkify-it-py" +version = "2.0.3" +requires_python = ">=3.7" +summary = "Links recognition library with FULL unicode support." +groups = ["dev"] +dependencies = [ + "uc-micro-py", +] +files = [ + {file = "linkify-it-py-2.0.3.tar.gz", hash = "sha256:68cda27e162e9215c17d786649d1da0021a451bdc436ef9e0fa0ba5234b9b048"}, + {file = "linkify_it_py-2.0.3-py3-none-any.whl", hash = "sha256:6bcbc417b0ac14323382aef5c5192c0075bf8a9d6b41820a2b66371eac6b6d79"}, +] + [[package]] name = "lsprotocol" version = "2023.0.1" @@ -429,7 +569,7 @@ name = "markdown-it-py" version = "3.0.0" requires_python = ">=3.8" summary = "Python port of markdown-it. Markdown parsing, done right!" -groups = ["default"] +groups = ["default", "dev"] dependencies = [ "mdurl~=0.1", ] @@ -438,17 +578,86 @@ files = [ {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, ] +[[package]] +name = "markupsafe" +version = "3.0.1" +requires_python = ">=3.9" +summary = "Safely add untrusted strings to HTML/XML markup." +groups = ["dev"] +files = [ + {file = "MarkupSafe-3.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8ae369e84466aa70f3154ee23c1451fda10a8ee1b63923ce76667e3077f2b0c4"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40f1e10d51c92859765522cbd79c5c8989f40f0419614bcdc5015e7b6bf97fc5"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a4cb365cb49b750bdb60b846b0c0bc49ed62e59a76635095a179d440540c346"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee3941769bd2522fe39222206f6dd97ae83c442a94c90f2b7a25d847d40f4729"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62fada2c942702ef8952754abfc1a9f7658a4d5460fabe95ac7ec2cbe0d02abc"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c2d64fdba74ad16138300815cfdc6ab2f4647e23ced81f59e940d7d4a1469d9"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fb532dd9900381d2e8f48172ddc5a59db4c445a11b9fab40b3b786da40d3b56b"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0f84af7e813784feb4d5e4ff7db633aba6c8ca64a833f61d8e4eade234ef0c38"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-win32.whl", hash = "sha256:cbf445eb5628981a80f54087f9acdbf84f9b7d862756110d172993b9a5ae81aa"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:a10860e00ded1dd0a65b83e717af28845bb7bd16d8ace40fe5531491de76b79f"}, + {file = "markupsafe-3.0.1.tar.gz", hash = "sha256:3e683ee4f5d0fa2dde4db77ed8dd8a876686e3fc417655c2ece9a90576905344"}, +] + +[[package]] +name = "mdit-py-plugins" +version = "0.4.2" +requires_python = ">=3.8" +summary = "Collection of plugins for markdown-it-py" +groups = ["dev"] +dependencies = [ + "markdown-it-py<4.0.0,>=1.0.0", +] +files = [ + {file = "mdit_py_plugins-0.4.2-py3-none-any.whl", hash = "sha256:0c673c3f889399a33b95e88d2f0d111b4447bdfea7f237dab2d488f459835636"}, + {file = "mdit_py_plugins-0.4.2.tar.gz", hash = "sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5"}, +] + [[package]] name = "mdurl" version = "0.1.2" requires_python = ">=3.7" summary = "Markdown URL utilities" -groups = ["default"] +groups = ["default", "dev"] files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] +[[package]] +name = "myst-parser" +version = "4.0.0" +requires_python = ">=3.10" +summary = "An extended [CommonMark](https://spec.commonmark.org/) compliant parser," +groups = ["dev"] +dependencies = [ + "docutils<0.22,>=0.19", + "jinja2", + "markdown-it-py~=3.0", + "mdit-py-plugins>=0.4.1,~=0.4", + "pyyaml", + "sphinx<9,>=7", +] +files = [ + {file = "myst_parser-4.0.0-py3-none-any.whl", hash = "sha256:b9317997552424448c6096c2558872fdb6f81d3ecb3a40ce84a7518798f3f28d"}, + {file = "myst_parser-4.0.0.tar.gz", hash = "sha256:851c9dfb44e36e56d15d05e72f02b80da21a9e0d07cba96baf5e2d476bb91531"}, +] + +[[package]] +name = "myst-parser" +version = "4.0.0" +extras = ["linkify"] +requires_python = ">=3.10" +summary = "An extended [CommonMark](https://spec.commonmark.org/) compliant parser," +groups = ["dev"] +dependencies = [ + "linkify-it-py~=2.0", + "myst-parser==4.0.0", +] +files = [ + {file = "myst_parser-4.0.0-py3-none-any.whl", hash = "sha256:b9317997552424448c6096c2558872fdb6f81d3ecb3a40ce84a7518798f3f28d"}, + {file = "myst_parser-4.0.0.tar.gz", hash = "sha256:851c9dfb44e36e56d15d05e72f02b80da21a9e0d07cba96baf5e2d476bb91531"}, +] + [[package]] name = "networkx" version = "3.3" @@ -482,6 +691,17 @@ files = [ {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] +[[package]] +name = "pbr" +version = "6.1.0" +requires_python = ">=2.6" +summary = "Python Build Reasonableness" +groups = ["dev"] +files = [ + {file = "pbr-6.1.0-py2.py3-none-any.whl", hash = "sha256:a776ae228892d8013649c0aeccbb3d5f99ee15e005a4cbb7e61d55a067b28a2a"}, + {file = "pbr-6.1.0.tar.gz", hash = "sha256:788183e382e3d1d7707db08978239965e8b9e4e5ed42669bf4758186734d5f24"}, +] + [[package]] name = "platformdirs" version = "4.3.6" @@ -601,7 +821,7 @@ name = "pydantic" version = "2.9.2" requires_python = ">=3.8" summary = "Data validation using Python type hints" -groups = ["default"] +groups = ["default", "dev"] dependencies = [ "annotated-types>=0.6.0", "pydantic-core==2.23.4", @@ -618,7 +838,7 @@ name = "pydantic-core" version = "2.23.4" requires_python = ">=3.8" summary = "Core functionality for Pydantic validation and serialization" -groups = ["default"] +groups = ["default", "dev"] dependencies = [ "typing-extensions!=4.7.0,>=4.6.0", ] @@ -638,6 +858,21 @@ files = [ {file = "pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863"}, ] +[[package]] +name = "pydantic-settings" +version = "2.5.2" +requires_python = ">=3.8" +summary = "Settings management using Pydantic" +groups = ["dev"] +dependencies = [ + "pydantic>=2.7.0", + "python-dotenv>=0.21.0", +] +files = [ + {file = "pydantic_settings-2.5.2-py3-none-any.whl", hash = "sha256:2c912e55fd5794a59bf8c832b9de832dcfdf4778d79ff79b708744eed499a907"}, + {file = "pydantic_settings-2.5.2.tar.gz", hash = "sha256:f90b139682bee4d2065273d5185d71d37ea46cfe57e1b5ae184fc6a0b2484ca0"}, +] + [[package]] name = "pygls" version = "1.3.1" @@ -658,12 +893,22 @@ name = "pygments" version = "2.18.0" requires_python = ">=3.8" summary = "Pygments is a syntax highlighting package written in Python." -groups = ["default"] +groups = ["default", "dev"] files = [ {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, ] +[[package]] +name = "pygraphviz" +version = "1.14" +requires_python = ">=3.10" +summary = "Python interface to Graphviz" +groups = ["dev"] +files = [ + {file = "pygraphviz-1.14.tar.gz", hash = "sha256:c10df02377f4e39b00ae17c862f4ee7e5767317f1c6b2dfd04cea6acc7fc2bea"}, +] + [[package]] name = "pytest" version = "8.3.3" @@ -830,6 +1075,17 @@ files = [ {file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"}, ] +[[package]] +name = "python-dotenv" +version = "1.0.1" +requires_python = ">=3.8" +summary = "Read key-value pairs from a .env file and set them as environment variables" +groups = ["dev"] +files = [ + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, +] + [[package]] name = "pyyaml" version = "6.0.2" @@ -869,7 +1125,7 @@ name = "requests" version = "2.32.3" requires_python = ">=3.8" summary = "Python HTTP for Humans." -groups = ["default"] +groups = ["default", "dev"] dependencies = [ "certifi>=2017.4.17", "charset-normalizer<4,>=2", @@ -886,7 +1142,7 @@ name = "rich" version = "13.9.2" requires_python = ">=3.8.0" summary = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -groups = ["default"] +groups = ["default", "dev"] dependencies = [ "markdown-it-py>=2.2.0", "pygments<3.0.0,>=2.13.0", @@ -981,12 +1237,254 @@ name = "shellingham" version = "1.5.4" requires_python = ">=3.7" summary = "Tool to Detect Surrounding Shell" -groups = ["default"] +groups = ["default", "dev"] files = [ {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, ] +[[package]] +name = "snowballstemmer" +version = "2.2.0" +summary = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +groups = ["dev"] +files = [ + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, +] + +[[package]] +name = "sortedcontainers" +version = "2.4.0" +summary = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" +groups = ["dev"] +files = [ + {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, + {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, +] + +[[package]] +name = "sortedcontainers-pydantic" +version = "1.0.0" +requires_python = ">=3.8" +summary = "Pydantic support for the sortedcontainers package." +groups = ["dev"] +dependencies = [ + "pydantic-core", + "pydantic>=2", + "sortedcontainers", +] +files = [ + {file = "sortedcontainers_pydantic-1.0.0-py3-none-any.whl", hash = "sha256:07e92e9b85dbf9248e0a5b59e0311a095e6fb27e0dc461b1209ead5a550b60b2"}, + {file = "sortedcontainers_pydantic-1.0.0.tar.gz", hash = "sha256:eb0e4aeb5197d690165f0a7b1a55c490eabe4b21375c3a91726373b2551e25ee"}, +] + +[[package]] +name = "soupsieve" +version = "2.6" +requires_python = ">=3.8" +summary = "A modern CSS selector implementation for Beautiful Soup." +groups = ["dev"] +files = [ + {file = "soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9"}, + {file = "soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb"}, +] + +[[package]] +name = "sphinx" +version = "8.0.2" +requires_python = ">=3.10" +summary = "Python documentation generator" +groups = ["dev"] +dependencies = [ + "Jinja2>=3.1", + "Pygments>=2.17", + "alabaster>=0.7.14", + "babel>=2.13", + "colorama>=0.4.6; sys_platform == \"win32\"", + "docutils<0.22,>=0.20", + "imagesize>=1.3", + "packaging>=23.0", + "requests>=2.30.0", + "snowballstemmer>=2.2", + "sphinxcontrib-applehelp", + "sphinxcontrib-devhelp", + "sphinxcontrib-htmlhelp>=2.0.0", + "sphinxcontrib-jsmath", + "sphinxcontrib-qthelp", + "sphinxcontrib-serializinghtml>=1.1.9", + "tomli>=2; python_version < \"3.11\"", +] +files = [ + {file = "sphinx-8.0.2-py3-none-any.whl", hash = "sha256:56173572ae6c1b9a38911786e206a110c9749116745873feae4f9ce88e59391d"}, + {file = "sphinx-8.0.2.tar.gz", hash = "sha256:0cce1ddcc4fd3532cf1dd283bc7d886758362c5c1de6598696579ce96d8ffa5b"}, +] + +[[package]] +name = "sphinx-design" +version = "0.6.1" +requires_python = ">=3.9" +summary = "A sphinx extension for designing beautiful, view size responsive web components." +groups = ["dev"] +dependencies = [ + "sphinx<9,>=6", +] +files = [ + {file = "sphinx_design-0.6.1-py3-none-any.whl", hash = "sha256:b11f37db1a802a183d61b159d9a202314d4d2fe29c163437001324fe2f19549c"}, + {file = "sphinx_design-0.6.1.tar.gz", hash = "sha256:b44eea3719386d04d765c1a8257caca2b3e6f8421d7b3a5e742c0fd45f84e632"}, +] + +[[package]] +name = "sphinx-github-style" +version = "1.2.2" +summary = "GitHub source code links and syntax highlighting for Sphinx documentation" +groups = ["dev"] +dependencies = [ + "sphinx>=1.8", +] +files = [ + {file = "sphinx-github-style-1.2.2.tar.gz", hash = "sha256:aad6231fcc0a4f59df82531eb3a82dda8a461a94d9e304895c34ac1158d7347c"}, + {file = "sphinx_github_style-1.2.2-py3-none-any.whl", hash = "sha256:8f6fd1a2c1a5878428a010050549c8353354a1ca9650f6bb01049c649935677d"}, +] + +[[package]] +name = "sphinx-issues" +version = "4.1.0" +requires_python = ">=3.8" +summary = "A Sphinx extension for linking to your project's issue tracker" +groups = ["dev"] +dependencies = [ + "sphinx", +] +files = [ + {file = "sphinx_issues-4.1.0-py3-none-any.whl", hash = "sha256:d779dddff441175c9fddb7a4018eca38f9f6cdb1e0a2fe31d93b4f89587c7ba1"}, + {file = "sphinx_issues-4.1.0.tar.gz", hash = "sha256:a67f7ef31d164b420b2f21b6c9b020baeb4a014afd4045f5be314c984e8ee520"}, +] + +[[package]] +name = "sphinxawesome-theme" +version = "5.3.1" +requires_python = "<4.0,>=3.8" +summary = "An awesome theme for the Sphinx documentation generator" +groups = ["dev"] +dependencies = [ + "beautifulsoup4<5.0.0,>=4.9.1", + "sphinx<7.2; python_version >= \"3.8\" and python_version < \"3.9\"", + "sphinx<7.5,>=7.2; python_version >= \"3.9\" and python_version < \"3.10\"", + "sphinx<9,>=8; python_version >= \"3.10\" and python_version < \"3.13\"", +] +files = [ + {file = "sphinxawesome_theme-5.3.1-py3-none-any.whl", hash = "sha256:d34f276f9785138cf9bb60d3a7294e5bdb93118989e8965ed0d9fee691d7702a"}, + {file = "sphinxawesome_theme-5.3.1.tar.gz", hash = "sha256:91d4df71c55f7e6a4a617efe61f2267339ff63c56f39bec8b7b192b504f118c4"}, +] + +[[package]] +name = "sphinxcontrib-apidoc" +version = "0.5.0" +requires_python = ">=3.8" +summary = "A Sphinx extension for running 'sphinx-apidoc' on each build" +groups = ["dev"] +dependencies = [ + "Sphinx>=5.0.0", + "pbr", +] +files = [ + {file = "sphinxcontrib-apidoc-0.5.0.tar.gz", hash = "sha256:65efcd92212a5f823715fb95ee098b458a6bb09a5ee617d9ed3dead97177cd55"}, + {file = "sphinxcontrib_apidoc-0.5.0-py3-none-any.whl", hash = "sha256:c671d644d6dc468be91b813dcddf74d87893bff74fe8f1b8b01b69408f0fb776"}, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +requires_python = ">=3.9" +summary = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" +groups = ["dev"] +files = [ + {file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"}, + {file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"}, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +requires_python = ">=3.9" +summary = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" +groups = ["dev"] +files = [ + {file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"}, + {file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"}, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +requires_python = ">=3.9" +summary = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +groups = ["dev"] +files = [ + {file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"}, + {file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"}, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +requires_python = ">=3.5" +summary = "A sphinx extension which renders display math in HTML via JavaScript" +groups = ["dev"] +files = [ + {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, + {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, +] + +[[package]] +name = "sphinxcontrib-mermaid" +version = "0.9.2" +requires_python = ">=3.7" +summary = "Mermaid diagrams in yours Sphinx powered docs" +groups = ["dev"] +files = [ + {file = "sphinxcontrib-mermaid-0.9.2.tar.gz", hash = "sha256:252ef13dd23164b28f16d8b0205cf184b9d8e2b714a302274d9f59eb708e77af"}, + {file = "sphinxcontrib_mermaid-0.9.2-py3-none-any.whl", hash = "sha256:6795a72037ca55e65663d2a2c1a043d636dc3d30d418e56dd6087d1459d98a5d"}, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +requires_python = ">=3.9" +summary = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" +groups = ["dev"] +files = [ + {file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"}, + {file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"}, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +requires_python = ">=3.9" +summary = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" +groups = ["dev"] +files = [ + {file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"}, + {file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"}, +] + +[[package]] +name = "sphinxcontrib-typer" +version = "0.5.0" +requires_python = "<4.0,>=3.9" +summary = "Auto generate docs for typer commands." +groups = ["dev"] +dependencies = [ + "sphinx>=5.0.0", + "typer-slim[standard]<1.0.0,>=0.12.0", +] +files = [ + {file = "sphinxcontrib_typer-0.5.0-py3-none-any.whl", hash = "sha256:780ca6b5f85105e5f8a1d2168fd490a6411c209e2a8e2ec84d387f0d6fba0ce1"}, + {file = "sphinxcontrib_typer-0.5.0.tar.gz", hash = "sha256:cc8937020bd804deb3f5abd5628c0e39cc2859e1a076307c1397b42594c4d33b"}, +] + [[package]] name = "termcolor" version = "2.4.0" @@ -1010,12 +1508,23 @@ files = [ {file = "toolz-0.12.1.tar.gz", hash = "sha256:ecca342664893f177a13dac0e6b41cbd8ac25a358e5f215316d43e2100224f4d"}, ] +[[package]] +name = "typenames" +version = "1.3.0" +requires_python = ">=3.8" +summary = "String representations of type annotations." +groups = ["dev"] +files = [ + {file = "typenames-1.3.0-py3-none-any.whl", hash = "sha256:666dfd7baebe3675dbdf950f19de08d5b15e5f9e0a71fe91fb8135ea68fe0889"}, + {file = "typenames-1.3.0.tar.gz", hash = "sha256:205a1954512e28b6558e761c134e74d243003755676fe8cce49250b8fc192798"}, +] + [[package]] name = "typer" version = "0.12.5" requires_python = ">=3.7" summary = "Typer, build great CLIs. Easy to code. Based on Python type hints." -groups = ["default"] +groups = ["default", "dev"] dependencies = [ "click>=8.0.0", "rich>=10.11.0", @@ -1027,6 +1536,38 @@ files = [ {file = "typer-0.12.5.tar.gz", hash = "sha256:f592f089bedcc8ec1b974125d64851029c3b1af145f04aca64d69410f0c9b722"}, ] +[[package]] +name = "typer-slim" +version = "0.12.5" +requires_python = ">=3.7" +summary = "Typer, build great CLIs. Easy to code. Based on Python type hints." +groups = ["dev"] +dependencies = [ + "click>=8.0.0", + "typing-extensions>=3.7.4.3", +] +files = [ + {file = "typer_slim-0.12.5-py3-none-any.whl", hash = "sha256:9a994f721b828783dbf144e17461b1c720bb4598e0d5eff7c1b3f08ee58cb062"}, + {file = "typer_slim-0.12.5.tar.gz", hash = "sha256:c8e3fcf93cc7dd584036df8755d2e2363f85f8a4dd028c7911eed3f00cf0ebb1"}, +] + +[[package]] +name = "typer-slim" +version = "0.12.5" +extras = ["standard"] +requires_python = ">=3.7" +summary = "Typer, build great CLIs. Easy to code. Based on Python type hints." +groups = ["dev"] +dependencies = [ + "rich>=10.11.0", + "shellingham>=1.3.0", + "typer-slim==0.12.5", +] +files = [ + {file = "typer_slim-0.12.5-py3-none-any.whl", hash = "sha256:9a994f721b828783dbf144e17461b1c720bb4598e0d5eff7c1b3f08ee58cb062"}, + {file = "typer_slim-0.12.5.tar.gz", hash = "sha256:c8e3fcf93cc7dd584036df8755d2e2363f85f8a4dd028c7911eed3f00cf0ebb1"}, +] + [[package]] name = "typing-extensions" version = "4.12.2" @@ -1038,12 +1579,23 @@ files = [ {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] +[[package]] +name = "uc-micro-py" +version = "1.0.3" +requires_python = ">=3.7" +summary = "Micro subset of unicode data files for linkify-it-py projects." +groups = ["dev"] +files = [ + {file = "uc-micro-py-1.0.3.tar.gz", hash = "sha256:d321b92cff673ec58027c04015fcaa8bb1e005478643ff4a500882eaab88c48a"}, + {file = "uc_micro_py-1.0.3-py3-none-any.whl", hash = "sha256:db1dffff340817673d7b466ec86114a9dc0e9d4d9b5ba229d9d60e5c12600cd5"}, +] + [[package]] name = "urllib3" version = "2.2.3" requires_python = ">=3.8" summary = "HTTP library with thread-safe connection pooling, file post, and more." -groups = ["default"] +groups = ["default", "dev"] files = [ {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, diff --git a/pyproject.toml b/pyproject.toml index 6ebd8b6..4f01655 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,6 +48,8 @@ lint.help = "Run all linters/formatters (automatically run by pre-commit)" lint.shell = "pre-commit run --all-files" test.help = "Run unit/integration tests suite" test.shell = "pytest tests" +docs.help = "Build documentation (output is at docs/build/index.html)" +docs.shell = "sphinx-build docs docs/build" all.help = "Run lint+test" all.shell = { composite = ["lint", "test"] } exe.help = "Package the application into a standalone executable" @@ -70,6 +72,16 @@ dev = [ "pytest-unordered>=0.6.1", "pytest-raises>=0.11", "pytest-datadir-ng>=1.1.1", + "sphinx>=7.3.7", + "sphinxcontrib-apidoc>=0.5.0", + "sphinxcontrib-typer>=0.5.0", + "sphinxcontrib-mermaid>=0.9.2", + "sphinxawesome-theme>=5.2.0", + "sphinx-github-style>=1.2.2", + "sphinx-design>=0.6.1", + "sphinx-issues>=4.1.0", + "autodoc-pydantic[erdantic]>=2.2.0", + "myst-parser[linkify]>=4.0.0", "ruff>=0.6.3", "ruff-lsp>=0.0.55", "prettydiff[terminal]>=0.1.0", From 9a12c2f5aeff929123529905d08e9389bcfef0e0 Mon Sep 17 00:00:00 2001 From: Julien Nicoulaud Date: Thu, 10 Oct 2024 12:59:10 +0200 Subject: [PATCH 05/12] ci: setup API keys --- .github/workflows/ci.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 991c25a..ccdd067 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -78,6 +78,14 @@ jobs: dependencies add_comment: true + - name: Get API keys + timeout-minutes: 10 + shell: bash + run: | + cat << EOF | jq -r 'to_entries[] | select(.key|endswith("_API_KEY")) | "\(.key)=\(.value)"' >> "$GITHUB_ENV" + ${{ toJSON(secrets) }} + EOF + - name: Setup pre-commit cache timeout-minutes: 10 uses: actions/cache@v4 From ff04db2a0e205e6bf5718f8238879458e7971ee1 Mon Sep 17 00:00:00 2001 From: Julien Nicoulaud Date: Thu, 10 Oct 2024 17:18:35 +0200 Subject: [PATCH 06/12] linter --- src/erc7730/lint/lint_validate_display_fields.py | 2 -- tests/model/test_model_serialization.py | 3 --- 2 files changed, 5 deletions(-) diff --git a/src/erc7730/lint/lint_validate_display_fields.py b/src/erc7730/lint/lint_validate_display_fields.py index 7ff8180..d5101bf 100644 --- a/src/erc7730/lint/lint_validate_display_fields.py +++ b/src/erc7730/lint/lint_validate_display_fields.py @@ -49,7 +49,6 @@ def _validate_eip712_paths(cls, descriptor: ResolvedERC7730Descriptor, out: Outp excluded = primary_type_format.excluded or [] for path in eip712_paths - format_paths: - allowed = False for excluded_path in excluded: if path.startswith(excluded_path): @@ -128,7 +127,6 @@ def _validate_abi_paths(cls, descriptor: ResolvedERC7730Descriptor, out: OutputA function = cls._display(selector, keccak) for path in abi_paths - format_paths: - allowed = False for excluded_path in excluded: if path.startswith(excluded_path): diff --git a/tests/model/test_model_serialization.py b/tests/model/test_model_serialization.py index fe5f86e..64a409e 100644 --- a/tests/model/test_model_serialization.py +++ b/tests/model/test_model_serialization.py @@ -20,9 +20,6 @@ def test_schema(input_file: Path) -> None: """Test model serializes to JSON that matches the schema.""" assert_valid_erc_7730(InputERC7730Descriptor.load(input_file)) -def test_poap() -> None: - """Test model serializes to JSON that matches the schema.""" - InputERC7730Descriptor.load(Path("/home/jnicoulaud/work/ledger/backend/cal/python-erc7730/python-erc7730-1/tests/registries/clear-signing-erc7730-registry/registry/poap/calldata-PoapBridge.json")) @pytest.mark.parametrize("input_file", ERC7730_DESCRIPTORS, ids=path_id) def test_round_trip(input_file: Path) -> None: From 5667639643a8caa4fd00c27c33687375aeef31e4 Mon Sep 17 00:00:00 2001 From: jnicoulaud-ledger <102984500+jnicoulaud-ledger@users.noreply.github.com> Date: Thu, 10 Oct 2024 17:59:09 +0200 Subject: [PATCH 07/12] chore: open source library (#57) * chore: add Apache 2.0 license file https://www.apache.org/licenses/LICENSE-2.0.txt * ci: add publish to PyPI step * update project metadata * format * fix syntax * remove zipapp distribution --- .github/workflows/release.yml | 29 +---- LICENSE | 202 ++++++++++++++++++++++++++++++++++ pyproject.toml | 4 +- 3 files changed, 209 insertions(+), 26 deletions(-) create mode 100644 LICENSE diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 010c06d..260c92e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -40,34 +40,18 @@ jobs: timeout-minutes: 10 run: pdm build - - name: Build standalone application - timeout-minutes: 10 - run: pdm exe - - name: Generate library build attestations timeout-minutes: 10 uses: LedgerHQ/actions-security/actions/attest@actions/attest-1 with: subject-path: dist/* - - name: Generate application build attestations - timeout-minutes: 10 - uses: LedgerHQ/actions-security/actions/attest@actions/attest-1 - with: - subject-path: erc7730 - - name: Sign library artifacts timeout-minutes: 10 uses: LedgerHQ/actions-security/actions/sign-blob@actions/sign-blob-1 with: path: dist - - name: Sign application artifacts - timeout-minutes: 10 - uses: LedgerHQ/actions-security/actions/sign-blob@actions/sign-blob-1 - with: - path: erc7730 - - name: Upload library artifacts to Ledger Artifactory repository timeout-minutes: 10 env: @@ -76,12 +60,9 @@ jobs: PDM_PUBLISH_PASSWORD: ${{ steps.jfrog-login.outputs.oidc-token }} run: pdm publish --no-build - - name: Attach standalone application to Github release - uses: svenstaro/upload-release-action@v2 + - name: Upload library artifacts to PyPI timeout-minutes: 10 - with: - repo_token: ${{ secrets.GITHUB_TOKEN }} - tag: ${{ github.ref }} - file: erc7730 - asset_name: erc7730 - overwrite: true + env: + PDM_PUBLISH_USERNAME: __token__ + PDM_PUBLISH_PASSWORD: ${{ secrets.PYPI_FULL_ACCESS_TOKEN }} + run: pdm publish --no-build diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/pyproject.toml b/pyproject.toml index 4f01655..09de911 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ name = "erc7730" description = "ERC-7730 descriptors validation and utilities." authors = [{ name = "Ledger" }] maintainers = [{ name = "Ledger" }] -license = { text = "LicenseRef-Proprietary" } +license = "Apache-2.0" readme = "README.md" dynamic = ["version"] requires-python = ">= 3.12, <3.13" @@ -11,7 +11,7 @@ classifiers = [ "Development Status :: 1 - Planning Copy", "Intended Audience :: Developers", "Topic :: Software Development :: Build Tools", - "License :: Proprietary", + "License :: OSI Approved :: Apache Software License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.12", From ec893b9d2c8ec6a389ef9b529c1cc23eb9c1892d Mon Sep 17 00:00:00 2001 From: Julien Nicoulaud Date: Thu, 10 Oct 2024 18:04:21 +0200 Subject: [PATCH 08/12] ci: fir workflow missing dependencies --- .github/workflows/release.yml | 4 ++++ .github/workflows/update.yml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 260c92e..e07833b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,6 +24,10 @@ jobs: timeout-minutes: 10 uses: actions/checkout@v4 + - name: Setup Graphviz + timeout-minutes: 10 + uses: ts-graphviz/setup-graphviz@v2 + - name: Setup python / PDM timeout-minutes: 10 uses: ./.github/actions/pdm diff --git a/.github/workflows/update.yml b/.github/workflows/update.yml index f25e8ab..3167b4b 100644 --- a/.github/workflows/update.yml +++ b/.github/workflows/update.yml @@ -24,6 +24,10 @@ jobs: timeout-minutes: 10 uses: actions/checkout@v4 + - name: Setup Graphviz + timeout-minutes: 10 + uses: ts-graphviz/setup-graphviz@v2 + - name: Setup python / PDM timeout-minutes: 10 uses: ./.github/actions/pdm From bbd729bdf9d24a0f9c42a7bdc1c52d5f73d418aa Mon Sep 17 00:00:00 2001 From: Julien Nicoulaud Date: Thu, 10 Oct 2024 18:19:43 +0200 Subject: [PATCH 09/12] ci: try using PYPI_PUSH_TOKEN --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e07833b..a5010ab 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -68,5 +68,5 @@ jobs: timeout-minutes: 10 env: PDM_PUBLISH_USERNAME: __token__ - PDM_PUBLISH_PASSWORD: ${{ secrets.PYPI_FULL_ACCESS_TOKEN }} + PDM_PUBLISH_PASSWORD: ${{ secrets.PYPI_PUSH_TOKEN }} run: pdm publish --no-build From db24a02d2cf562fd812c778cdeec10e45e607d01 Mon Sep 17 00:00:00 2001 From: Julien Nicoulaud Date: Thu, 10 Oct 2024 18:31:46 +0200 Subject: [PATCH 10/12] ci: try without username --- .github/workflows/release.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a5010ab..ca5725f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -67,6 +67,5 @@ jobs: - name: Upload library artifacts to PyPI timeout-minutes: 10 env: - PDM_PUBLISH_USERNAME: __token__ - PDM_PUBLISH_PASSWORD: ${{ secrets.PYPI_PUSH_TOKEN }} + PDM_PUBLISH_PASSWORD: ${{ secrets.PYPI_FULL_ACCESS_TOKEN }} run: pdm publish --no-build From 5e84f713e9077969fccb88d0700c1b622011378c Mon Sep 17 00:00:00 2001 From: Julien Nicoulaud Date: Thu, 10 Oct 2024 18:42:57 +0200 Subject: [PATCH 11/12] ci: try another token --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ca5725f..ddcb140 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -67,5 +67,5 @@ jobs: - name: Upload library artifacts to PyPI timeout-minutes: 10 env: - PDM_PUBLISH_PASSWORD: ${{ secrets.PYPI_FULL_ACCESS_TOKEN }} + PDM_PUBLISH_PASSWORD: ${{ secrets.PYPI_PUBLIC_API_TOKEN }} run: pdm publish --no-build From 55875ca3f89845042d042f4f9d0cbcac4ee5cd60 Mon Sep 17 00:00:00 2001 From: Julien Nicoulaud Date: Thu, 10 Oct 2024 19:07:10 +0200 Subject: [PATCH 12/12] doc: link site in README --- README.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/README.md b/README.md index 7b3b4e3..e2cf3f7 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,4 @@ python-erc7730 **This library provides tooling for the ERC-7730 standard.** -See for the standard specification and example descriptors. - -This library implements: - * Reading and writing ERC-7730 descriptor files into an object model - * Validation, available as a command line tool - * Conversion between Ledger specific legacy descriptors and ERC-7730 +See documentation at .