├── .flake8
├── .github
└── workflows
│ ├── docs.yml
│ └── tests.yml
├── .gitignore
├── .vscode
└── settings.json
├── CHANGELOG.md
├── LICENSE
├── README.md
├── docs
├── defining_new_types.md
├── img
│ └── bowl-mix.svg
├── index.md
└── types.md
├── mkdocs.yml
├── mypy.ini
├── noxfile.py
├── overrides
└── main.html
├── poetry.lock
├── poetry.toml
├── pyproject.toml
├── pytest.ini
├── src
└── borsh_construct
│ ├── __init__.py
│ ├── core.py
│ ├── enum.py
│ └── py.typed
└── tests
├── __init__.py
├── conftest.py
├── test_core.py
└── test_hypothesis.py
/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | ignore =
3 | D100,
4 | D104,
5 | D107,
6 | I001,
7 | I003,
8 | I004,
9 | I005,
10 | N814,
11 | Q000,
12 | WPS110,
13 | WPS111,
14 | WPS115,
15 | WPS201,
16 | WPS202,
17 | WPS221,
18 | WPS235,
19 | WPS226,
20 | WPS300,
21 | WPS301,
22 | WPS306,
23 | WPS305,
24 | WPS326,
25 | WPS410,
26 | WPS412,
27 | WPS430,
28 | WPS432,
29 | WPS441,
30 | W503,
31 | DAR201,
32 | DAR401
33 | exclude = .git,__pycache__,docs/source/conf.py,old,build,dist
34 | max-line-length = 88
35 | per-file-ignores=
36 | src/borsh_construct/core.py:F401
37 | tests/test_core.py:S101,DAR101
38 | tests/test_hypothesis.py:S101,DAR101,B008,WPS404
--------------------------------------------------------------------------------
/.github/workflows/docs.yml:
--------------------------------------------------------------------------------
1 | name: Docs
2 | on:
3 | push:
4 | branches:
5 | - master
6 |
7 | jobs:
8 | build:
9 | name: Deploy docs
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Checkout main
13 | uses: actions/checkout@v2
14 |
15 | - name: Deploy docs
16 | uses: mhausenblas/mkdocs-deploy-gh-pages@master
17 | env:
18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
19 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: Tests
2 |
3 | on:
4 | push:
5 | branches: [master]
6 | pull_request:
7 | branches: [master]
8 |
9 | jobs:
10 | test:
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - uses: actions/checkout@v2
15 |
16 | - name: Set up Python
17 | uses: actions/setup-python@v1
18 | with:
19 | python-version: 3.9
20 |
21 | - name: Run image
22 | uses: abatilo/actions-poetry@v2.0.0
23 | with:
24 | poetry-version: 1.1.7
25 |
26 | - name: Install nox-poetry
27 | run: pip install nox-poetry
28 |
29 | - name: Run tests
30 | run: nox
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | share/python-wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 | MANIFEST
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .nox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | *.py,cover
50 | .hypothesis/
51 | .pytest_cache/
52 | cover/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | .pybuilder/
76 | target/
77 |
78 | # Jupyter Notebook
79 | .ipynb_checkpoints
80 |
81 | # IPython
82 | profile_default/
83 | ipython_config.py
84 |
85 | # pyenv
86 | # For a library or package, you might want to ignore these files since the code is
87 | # intended to run in multiple environments; otherwise, check them in:
88 | # .python-version
89 |
90 | # pipenv
91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
94 | # install all needed dependencies.
95 | #Pipfile.lock
96 |
97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
98 | __pypackages__/
99 |
100 | # Celery stuff
101 | celerybeat-schedule
102 | celerybeat.pid
103 |
104 | # SageMath parsed files
105 | *.sage.py
106 |
107 | # Environments
108 | .env
109 | .venv
110 | env/
111 | venv/
112 | ENV/
113 | env.bak/
114 | venv.bak/
115 |
116 | # Spyder project settings
117 | .spyderproject
118 | .spyproject
119 |
120 | # Rope project settings
121 | .ropeproject
122 |
123 | # mkdocs documentation
124 | /site
125 |
126 | # mypy
127 | .mypy_cache/
128 | .dmypy.json
129 | dmypy.json
130 |
131 | # Pyre type checker
132 | .pyre/
133 |
134 | # pytype static type analyzer
135 | .pytype/
136 |
137 | # Cython debug symbols
138 | cython_debug/
139 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "python.linting.flake8Enabled": true,
3 | "python.linting.enabled": true,
4 | "python.formatting.provider": "black",
5 | "python.linting.mypyEnabled": true,
6 | }
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [0.1.0] - 2021-10-01
4 |
5 | Initial release
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2021 Kevin Heavey
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
14 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
15 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
16 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
19 | OR OTHER DEALINGS IN THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # borsh-construct
2 |
3 | [](https://github.com/near/borsh-construct-py/actions?workflow=Tests)
4 | [](https://near.github.io/borsh-construct-py/)
5 |
6 | `borsh-construct` is an implementation of the [Borsh](https://borsh.io/) binary serialization format for Python projects.
7 |
8 | Borsh stands for Binary Object Representation Serializer for Hashing. It is meant to be used in security-critical projects as it prioritizes consistency, safety, speed, and comes with a strict specification.
9 |
10 | Read the [Documentation](https://near.github.io/borsh-construct-py/).
11 | ## Installation
12 |
13 | ```sh
14 | pip install borsh-construct
15 |
16 | ```
17 |
18 |
19 | ### Development Setup
20 |
21 | 1. Install [poetry](https://python-poetry.org/docs/#installation)
22 | 2. Install dev dependencies:
23 | ```sh
24 | poetry install
25 |
26 | ```
27 | 3. Install [nox-poetry](https://github.com/cjolowicz/nox-poetry) (note: do not use Poetry to install this, see [here](https://medium.com/@cjolowicz/nox-is-a-part-of-your-global-developer-environment-like-poetry-pre-commit-pyenv-or-pipx-1cdeba9198bd))
28 | 4. Activate the poetry shell:
29 | ```sh
30 | poetry shell
31 |
32 | ```
33 |
34 | ### Quick Tests
35 | ```sh
36 | pytest
37 |
38 | ```
39 |
40 | ### Full Tests
41 | ```sh
42 | nox
43 |
44 | ```
45 |
--------------------------------------------------------------------------------
/docs/defining_new_types.md:
--------------------------------------------------------------------------------
1 | # Defining New Types
2 |
3 | You can build new schemas on top of `borsh-construct` using [`construct.Adapter`](https://construct.readthedocs.io/en/latest/adapters.html#adapting).
4 |
5 | For example, here we implement (de)serialization for Python's `Fraction` class:
6 |
7 | ```python
8 | from typing import Tuple
9 | from fractions import Fraction
10 | from construct import Adapter
11 | from borsh_construct import I32, TupleStruct
12 |
13 |
14 | class Frac(Adapter):
15 | def __init__(self, int_type) -> None:
16 | super().__init__(TupleStruct(int_type, int_type)) # type: ignore
17 |
18 | def _encode(self, obj: Fraction, context, path) -> Tuple[int, int]:
19 | return obj.numerator, obj.denominator
20 |
21 | def _decode(self, obj: Tuple[int, int], context, path) -> Fraction:
22 | numerator, denominator = obj
23 | return Fraction(numerator, denominator)
24 |
25 | frac = Frac(I32)
26 | to_serialize = Fraction(10, 3)
27 | assert frac.parse(frac.build(to_serialize)) == to_serialize
28 |
29 | ```
30 |
--------------------------------------------------------------------------------
/docs/img/bowl-mix.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | `borsh-construct` is an implementation of the [Borsh](https://borsh.io/) binary serialization format for Python projects.
4 |
5 | Borsh stands for Binary Object Representation Serializer for Hashing. It is meant to be used in security-critical projects as it prioritizes consistency, safety, speed, and comes with a strict specification.
6 |
7 | `borsh-construct` is built on top of the very powerful [`construct`](https://construct.readthedocs.io/en/latest/) library, so it is strongly recommended that you have a look at the basics of `construct` before using `borsh-construct`.
8 |
9 | ## Who is this for?
10 |
11 | This library was built with the NEAR and Solana blockchains in mind: the only obvious reason to use `borsh-construct` currently is to write client code for those blockchains, as they typically expect Borsh serialization.
12 |
13 | You may find other reasons to use `borsh-construct`, but it is worth noting that the Borsh spec is written from a Rust perspective, so if you're not interacting with a Rust project then Borsh may not make sense.
14 |
15 | ## Installation
16 |
17 | `pip install borsh-construct`
18 |
19 | Alternatively, using conda/mamba:
20 |
21 | `mamba install borsh-construct -c conda-forge`
22 |
23 | ## Usage
24 |
25 | Since `borsh-construct` is built with `construct`, it serializes objects using `.build` and deserializes them using `.parse`. For example:
26 |
27 | ```python
28 | >>> from borsh_construct import U8, String, CStruct
29 | >>> animal = CStruct(
30 | ... "name" / String,
31 | ... "legs" / U8
32 | ... )
33 | >>> animal.build({"name": "Ferris", "legs": 6})
34 | b'\x06\x00\x00\x00Ferris\x06'
35 | >>> animal.parse(b'\x06\x00\x00\x00Ferris\x06')
36 | Container(name=u'Ferris', legs=6)
37 |
38 | ```
39 |
--------------------------------------------------------------------------------
/docs/types.md:
--------------------------------------------------------------------------------
1 | This is an outline of all the types supported by `borsh-construct`. Since Borsh is Rust-centric, some Rust snippets are included to make it clear what the equivalent Rust type is.
2 |
3 | ## Numeric types
4 |
5 | All numeric types mentioned in the Borsh spec are supported:
6 |
7 | - Unsigned integers: U8, U16, U32, U64, U128
8 | - Signed integers: I8, I16, I32, I64, I128
9 | - Floats: F32, F64
10 | - Bool (this is not explicitly part of the spec, but `borsh-rs` implements Bool as a `u8` with value 0 or 1)
11 |
12 | Example:
13 |
14 | ```python
15 | >>> from borsh_construct import U32
16 | >>> U32.build(42)
17 | b'*\x00\x00\x00'
18 | >>> U32.parse(b'*\x00\x00\x00')
19 | 42
20 |
21 | ```
22 |
23 |
24 | !!! note
25 |
26 | Most of the numeric types come directly from the `construct` library, and are just aliased so that they match the Borsh spec. For example, `borsh_construct.U8` is really `construct.Int8ul`.
27 |
28 | ## Fixed sized arrays
29 |
30 | `construct` gives us a nice `[]` syntax to represent fixed sized arrays. For example, an array of 3 u8 integers:
31 |
32 | ```python
33 | >>> from borsh_construct import U8
34 | >>> U8[3].build([1, 2, 3])
35 | b'\x01\x02\x03'
36 | >>> U8[3].parse(b'\x01\x02\x03')
37 | ListContainer([1, 2, 3])
38 |
39 | ```
40 |
41 | This is what that fixed size array looks like in Rust:
42 |
43 | ```rust
44 | let arr = [1u8, 2, 3];
45 |
46 | ```
47 |
48 | ## Dynamic sized arrays
49 |
50 | Dynamic arrays are implemented using the `Vec` function:
51 |
52 | ```python
53 | >>> from borsh_construct import Vec, U8
54 | >>> Vec(U8).build([1, 2, 3])
55 | b'\x03\x00\x00\x00\x01\x02\x03'
56 | >>> Vec(U8).parse(b'\x03\x00\x00\x00\x01\x02\x03')
57 | ListContainer([1, 2, 3])
58 |
59 | ```
60 |
61 | In Rust we could build that vector like this:
62 |
63 | ```rust
64 | let v = vec![1u8, 2, 3];
65 |
66 | ```
67 |
68 | ## C-like structs
69 |
70 | This is analogous to a Rust struct with named fields:
71 |
72 | ```python
73 | >>> from borsh_construct import CStruct, String, U8
74 | >>> person = CStruct(
75 | ... "name" / String,
76 | ... "age" / U8
77 | ... )
78 | >>> person.build({"name": "Alice", "age": 50})
79 | b'\x05\x00\x00\x00Alice2'
80 | >>> person.parse(b'\x05\x00\x00\x00Alice2')
81 | Container(name=u'Alice', age=50)
82 |
83 | ```
84 | Rust type:
85 | ```rust
86 | struct Person {
87 | name: String,
88 | age: u8,
89 | }
90 |
91 | ```
92 |
93 | ## Tuple structs
94 | ```python
95 | >>> from borsh_construct import TupleStruct, I32, F32
96 | >>> pair = TupleStruct(I32, F32)
97 | >>> pair.build([3, 0.5])
98 | b'\x03\x00\x00\x00\x00\x00\x00?'
99 | >>> pair.parse(b'\x03\x00\x00\x00\x00\x00\x00?')
100 | ListContainer([3, 0.5])
101 |
102 | ```
103 | Rust type:
104 | ```rust
105 | struct Pair(i32, f32);
106 |
107 | ```
108 |
109 |
110 | ## Enum
111 |
112 | Rust's `enum` is the trickiest part of `borsh-construct` because it's rather different from Python's `enum.Enum`. Under the hood, `borsh-construct` uses the [`sumtypes`](https://sumtypes.readthedocs.io/en/latest/) library to represent Rust enums in Python.
113 |
114 | Notice below how our `message` object has a `.enum` attribute: this is the Python imitation of Rust's enum type.
115 |
116 | Defining an enum:
117 |
118 | ```python
119 | >>> from borsh_construct import Enum, I32, CStruct, TupleStruct, String
120 | >>> message = Enum(
121 | ... "Quit",
122 | ... "Move" / CStruct("x" / I32, "y" / I32),
123 | ... "Write" / TupleStruct(String),
124 | ... "ChangeColor" / TupleStruct(I32, I32, I32),
125 | ... enum_name="Message",
126 | ... )
127 | >>> message.build(message.enum.Quit())
128 | b'\x00'
129 | >>> message.parse(b'\x00')
130 | Message.Quit()
131 | >>> message.build(message.enum.Move(x=1, y=3))
132 | b'\x01\x01\x00\x00\x00\x03\x00\x00\x00'
133 | >>> message.parse(b'\x01\x01\x00\x00\x00\x03\x00\x00\x00')
134 | Message.Move(x=1, y=3)
135 | >>> message.build(message.enum.Write(("hello",)))
136 | b'\x02\x05\x00\x00\x00hello'
137 | >>> message.parse(b'\x02\x05\x00\x00\x00hello')
138 | Message.Write(tuple_data=ListContainer(['hello']))
139 | >>> message.build(message.enum.ChangeColor((1, 2, 3)))
140 | b'\x03\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00'
141 | >>> message.parse(b'\x03\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00')
142 | Message.ChangeColor(tuple_data=ListContainer([1, 2, 3]))
143 |
144 | ```
145 |
146 | Notice also how each variant of the enum is a subclass of the enum itself:
147 |
148 | ```python
149 | >>> assert isinstance(message.enum.Quit(), message.enum)
150 |
151 | ```
152 |
153 | Rust type:
154 | ```rust
155 | enum Message {
156 | Quit,
157 | Move { x: i32, y: i32 },
158 | Write(String),
159 | ChangeColor(i32, i32, i32),
160 | }
161 | ```
162 |
163 | ## HashMap
164 |
165 | You can think of HashMap as a Python dictionary as long as the keys and values have a well-defined type.
166 |
167 | ```python
168 | >>> from borsh_construct import HashMap, String, U32
169 | >>> scores = HashMap(String, U32)
170 | >>> scores.build({"Blue": 10, "Yellow": 50})
171 | b'\x02\x00\x00\x00\x04\x00\x00\x00Blue\n\x00\x00\x00\x06\x00\x00\x00Yellow2\x00\x00\x00'
172 | >>> scores.parse(b'\x02\x00\x00\x00\x04\x00\x00\x00Blue\n\x00\x00\x00\x06\x00\x00\x00Yellow2\x00\x00\x00')
173 | {'Blue': 10, 'Yellow': 50}
174 |
175 | ```
176 | Rust type:
177 | ```rust
178 | fn main() {
179 | use std::collections::HashMap;
180 |
181 | let mut scores = HashMap::new();
182 |
183 | scores.insert(String::from("Blue"), 10);
184 | scores.insert(String::from("Yellow"), 50);
185 | }
186 | ```
187 |
188 | ## HashSet
189 |
190 | The HashSet is similar to a Python `set` with a well-defined type.
191 |
192 | ```python
193 | >>> from borsh_construct import HashSet, I32
194 | >>> a = HashSet(I32)
195 | >>> a.build({1, 2, 3})
196 | b'\x03\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00'
197 | >>> a.parse(b'\x03\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00')
198 | {1, 2, 3}
199 |
200 | ```
201 | Rust type:
202 | ```rust
203 | use std::collections::HashSet;
204 |
205 | fn main() {
206 | let mut a: HashSet = vec![1i32, 2, 3].into_iter().collect();
207 | }
208 |
209 | ```
210 |
211 | ## Option
212 |
213 | Rust programmers will notice that our Option type is not implemented like a Rust enum, because it's not worth the complexity.
214 |
215 | ```python
216 | >>> from borsh_construct import Option, U8
217 | >>> optional_num = Option(U8)
218 | >>> optional_num.build(None)
219 | b'\x00'
220 | >>> optional_num.parse(b'\x00') is None
221 | True
222 | >>> optional_num.build(3)
223 | b'\x01\x03'
224 | >>> optional_num.parse(b'\x01\x03')
225 | 3
226 |
227 | ```
228 | Rust type:
229 | ```rust
230 | Option
231 | ```
232 |
233 | ## Bytes
234 |
235 | The Borsh spec doesn't specifically mention serializing raw bytes, but it's worth including anyway:
236 |
237 | ```python
238 | >>> from borsh_construct import Bytes
239 | >>> Bytes.build(bytes([1, 2, 3]))
240 | b'\x03\x00\x00\x00\x01\x02\x03'
241 | >>> Bytes.parse(b'\x03\x00\x00\x00\x01\x02\x03')
242 | b'\x01\x02\x03'
243 |
244 | ```
245 | Rust type:
246 | ```rust
247 | vec![1u8, 2, 3]
248 |
249 | ```
250 |
251 | ## String
252 |
253 | Python:
254 |
255 | ```python
256 | >>> from borsh_construct import String
257 | >>> String.build("🚀🚀🚀")
258 | b'\x0c\x00\x00\x00\xf0\x9f\x9a\x80\xf0\x9f\x9a\x80\xf0\x9f\x9a\x80'
259 | >>> String.parse(b'\x0c\x00\x00\x00\xf0\x9f\x9a\x80\xf0\x9f\x9a\x80\xf0\x9f\x9a\x80')
260 | '🚀🚀🚀'
261 |
262 | ```
263 |
264 | Rust type:
265 |
266 | ```rust
267 | String::from("🚀🚀🚀")
268 |
269 | ```
270 |
271 |
272 |
--------------------------------------------------------------------------------
/mkdocs.yml:
--------------------------------------------------------------------------------
1 | site_name: borsh-construct
2 | theme:
3 | name: material
4 | icon:
5 | logo: material/bowl-mix
6 | favicon: img/bowl-mix.svg
7 | palette:
8 | - scheme: default
9 | primary: lime
10 | toggle:
11 | icon: material/toggle-switch-off-outline
12 | name: Switch to dark mode
13 | - scheme: slate
14 | toggle:
15 | icon: material/toggle-switch
16 | name: Switch to light mode
17 | custom_dir: overrides
18 | markdown_extensions:
19 | - pymdownx.highlight
20 | - pymdownx.superfences
21 | - admonition
22 | repo_url: https://github.com/near/borsh-construct-py
23 | repo_name: near/borsh-construct-py
24 | site_url: https://near.github.io/borsh-construct-py/
25 | nav:
26 | - index.md
27 | - types.md
28 | - defining_new_types.md
29 |
--------------------------------------------------------------------------------
/mypy.ini:
--------------------------------------------------------------------------------
1 | [mypy]
2 |
3 | [mypy-pytest]
4 | ignore_missing_imports = True
5 |
6 | [mypy-sumtypes]
7 | ignore_missing_imports = True
8 |
9 | [mypy-nox_poetry]
10 | ignore_missing_imports = True
11 |
--------------------------------------------------------------------------------
/noxfile.py:
--------------------------------------------------------------------------------
1 | from nox_poetry import session # type: ignore
2 |
3 |
4 | @session(python=["3.9", "3.8.3"])
5 | def tests(session): # noqa: D103,WPS442
6 | session.run_always("poetry", "install", external=True)
7 | session.install(".")
8 | session.run("pytest", external=True)
9 |
--------------------------------------------------------------------------------
/overrides/main.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block extrahead %}
4 |
5 | {% endblock %}
--------------------------------------------------------------------------------
/poetry.lock:
--------------------------------------------------------------------------------
1 | [[package]]
2 | name = "astor"
3 | version = "0.8.1"
4 | description = "Read/rewrite/write Python ASTs"
5 | category = "dev"
6 | optional = false
7 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
8 |
9 | [[package]]
10 | name = "atomicwrites"
11 | version = "1.4.0"
12 | description = "Atomic file writes."
13 | category = "dev"
14 | optional = false
15 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
16 |
17 | [[package]]
18 | name = "attrs"
19 | version = "21.2.0"
20 | description = "Classes Without Boilerplate"
21 | category = "main"
22 | optional = false
23 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
24 |
25 | [package.extras]
26 | dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"]
27 | docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
28 | tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"]
29 | tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"]
30 |
31 | [[package]]
32 | name = "bandit"
33 | version = "1.7.0"
34 | description = "Security oriented static analyser for python code."
35 | category = "dev"
36 | optional = false
37 | python-versions = ">=3.5"
38 |
39 | [package.dependencies]
40 | colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""}
41 | GitPython = ">=1.0.1"
42 | PyYAML = ">=5.3.1"
43 | six = ">=1.10.0"
44 | stevedore = ">=1.20.0"
45 |
46 | [[package]]
47 | name = "black"
48 | version = "21.9b0"
49 | description = "The uncompromising code formatter."
50 | category = "dev"
51 | optional = false
52 | python-versions = ">=3.6.2"
53 |
54 | [package.dependencies]
55 | click = ">=7.1.2"
56 | mypy-extensions = ">=0.4.3"
57 | pathspec = ">=0.9.0,<1"
58 | platformdirs = ">=2"
59 | regex = ">=2020.1.8"
60 | tomli = ">=0.2.6,<2.0.0"
61 | typing-extensions = [
62 | {version = ">=3.10.0.0", markers = "python_version < \"3.10\""},
63 | {version = "!=3.10.0.1", markers = "python_version >= \"3.10\""},
64 | ]
65 |
66 | [package.extras]
67 | colorama = ["colorama (>=0.4.3)"]
68 | d = ["aiohttp (>=3.6.0)", "aiohttp-cors (>=0.4.0)"]
69 | jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
70 | python2 = ["typed-ast (>=1.4.2)"]
71 | uvloop = ["uvloop (>=0.15.2)"]
72 |
73 | [[package]]
74 | name = "click"
75 | version = "8.0.3"
76 | description = "Composable command line interface toolkit"
77 | category = "dev"
78 | optional = false
79 | python-versions = ">=3.6"
80 |
81 | [package.dependencies]
82 | colorama = {version = "*", markers = "platform_system == \"Windows\""}
83 |
84 | [[package]]
85 | name = "colorama"
86 | version = "0.4.4"
87 | description = "Cross-platform colored terminal text."
88 | category = "dev"
89 | optional = false
90 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
91 |
92 | [[package]]
93 | name = "construct"
94 | version = "2.10.67"
95 | description = "A powerful declarative symmetric parser/builder for binary data"
96 | category = "main"
97 | optional = false
98 | python-versions = ">=3.6"
99 |
100 | [package.extras]
101 | extras = ["arrow", "cloudpickle", "enum34", "lz4", "numpy", "ruamel.yaml"]
102 |
103 | [[package]]
104 | name = "construct-typing"
105 | version = "0.5.1"
106 | description = "Extension for the python package 'construct' that adds typing features"
107 | category = "main"
108 | optional = false
109 | python-versions = ">=3.7"
110 |
111 | [package.dependencies]
112 | construct = "2.10.67"
113 |
114 | [[package]]
115 | name = "coverage"
116 | version = "5.5"
117 | description = "Code coverage measurement for Python"
118 | category = "dev"
119 | optional = false
120 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
121 |
122 | [package.dependencies]
123 | toml = {version = "*", optional = true, markers = "extra == \"toml\""}
124 |
125 | [package.extras]
126 | toml = ["toml"]
127 |
128 | [[package]]
129 | name = "darglint"
130 | version = "1.8.0"
131 | description = "A utility for ensuring Google-style docstrings stay up to date with the source code."
132 | category = "dev"
133 | optional = false
134 | python-versions = ">=3.6,<4.0"
135 |
136 | [[package]]
137 | name = "docutils"
138 | version = "0.17.1"
139 | description = "Docutils -- Python Documentation Utilities"
140 | category = "dev"
141 | optional = false
142 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
143 |
144 | [[package]]
145 | name = "eradicate"
146 | version = "2.0.0"
147 | description = "Removes commented-out code."
148 | category = "dev"
149 | optional = false
150 | python-versions = "*"
151 |
152 | [[package]]
153 | name = "filelock"
154 | version = "3.3.1"
155 | description = "A platform independent file lock."
156 | category = "dev"
157 | optional = false
158 | python-versions = ">=3.6"
159 |
160 | [package.extras]
161 | docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"]
162 | testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"]
163 |
164 | [[package]]
165 | name = "flake8"
166 | version = "3.9.2"
167 | description = "the modular source code checker: pep8 pyflakes and co"
168 | category = "dev"
169 | optional = false
170 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
171 |
172 | [package.dependencies]
173 | mccabe = ">=0.6.0,<0.7.0"
174 | pycodestyle = ">=2.7.0,<2.8.0"
175 | pyflakes = ">=2.3.0,<2.4.0"
176 |
177 | [[package]]
178 | name = "flake8-bandit"
179 | version = "2.1.2"
180 | description = "Automated security testing with bandit and flake8."
181 | category = "dev"
182 | optional = false
183 | python-versions = "*"
184 |
185 | [package.dependencies]
186 | bandit = "*"
187 | flake8 = "*"
188 | flake8-polyfill = "*"
189 | pycodestyle = "*"
190 |
191 | [[package]]
192 | name = "flake8-broken-line"
193 | version = "0.3.0"
194 | description = "Flake8 plugin to forbid backslashes for line breaks"
195 | category = "dev"
196 | optional = false
197 | python-versions = ">=3.6,<4.0"
198 |
199 | [package.dependencies]
200 | flake8 = ">=3.5,<4.0"
201 |
202 | [[package]]
203 | name = "flake8-bugbear"
204 | version = "21.9.2"
205 | description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle."
206 | category = "dev"
207 | optional = false
208 | python-versions = ">=3.6"
209 |
210 | [package.dependencies]
211 | attrs = ">=19.2.0"
212 | flake8 = ">=3.0.0"
213 |
214 | [package.extras]
215 | dev = ["coverage", "black", "hypothesis", "hypothesmith"]
216 |
217 | [[package]]
218 | name = "flake8-commas"
219 | version = "2.1.0"
220 | description = "Flake8 lint for trailing commas."
221 | category = "dev"
222 | optional = false
223 | python-versions = "*"
224 |
225 | [package.dependencies]
226 | flake8 = ">=2"
227 |
228 | [[package]]
229 | name = "flake8-comprehensions"
230 | version = "3.7.0"
231 | description = "A flake8 plugin to help you write better list/set/dict comprehensions."
232 | category = "dev"
233 | optional = false
234 | python-versions = ">=3.6"
235 |
236 | [package.dependencies]
237 | flake8 = ">=3.0,<3.2.0 || >3.2.0,<5"
238 |
239 | [[package]]
240 | name = "flake8-debugger"
241 | version = "4.0.0"
242 | description = "ipdb/pdb statement checker plugin for flake8"
243 | category = "dev"
244 | optional = false
245 | python-versions = ">=3.6"
246 |
247 | [package.dependencies]
248 | flake8 = ">=3.0"
249 | pycodestyle = "*"
250 | six = "*"
251 |
252 | [[package]]
253 | name = "flake8-docstrings"
254 | version = "1.6.0"
255 | description = "Extension for flake8 which uses pydocstyle to check docstrings"
256 | category = "dev"
257 | optional = false
258 | python-versions = "*"
259 |
260 | [package.dependencies]
261 | flake8 = ">=3"
262 | pydocstyle = ">=2.1"
263 |
264 | [[package]]
265 | name = "flake8-eradicate"
266 | version = "1.1.0"
267 | description = "Flake8 plugin to find commented out code"
268 | category = "dev"
269 | optional = false
270 | python-versions = ">=3.6,<4.0"
271 |
272 | [package.dependencies]
273 | attrs = "*"
274 | eradicate = ">=2.0,<3.0"
275 | flake8 = ">=3.5,<4.0"
276 |
277 | [[package]]
278 | name = "flake8-isort"
279 | version = "4.1.1"
280 | description = "flake8 plugin that integrates isort ."
281 | category = "dev"
282 | optional = false
283 | python-versions = "*"
284 |
285 | [package.dependencies]
286 | flake8 = ">=3.2.1,<5"
287 | isort = ">=4.3.5,<6"
288 | testfixtures = ">=6.8.0,<7"
289 |
290 | [package.extras]
291 | test = ["pytest-cov"]
292 |
293 | [[package]]
294 | name = "flake8-polyfill"
295 | version = "1.0.2"
296 | description = "Polyfill package for Flake8 plugins"
297 | category = "dev"
298 | optional = false
299 | python-versions = "*"
300 |
301 | [package.dependencies]
302 | flake8 = "*"
303 |
304 | [[package]]
305 | name = "flake8-quotes"
306 | version = "3.3.0"
307 | description = "Flake8 lint for quotes."
308 | category = "dev"
309 | optional = false
310 | python-versions = "*"
311 |
312 | [package.dependencies]
313 | flake8 = "*"
314 |
315 | [[package]]
316 | name = "flake8-rst-docstrings"
317 | version = "0.2.3"
318 | description = "Python docstring reStructuredText (RST) validator"
319 | category = "dev"
320 | optional = false
321 | python-versions = ">=3.3"
322 |
323 | [package.dependencies]
324 | flake8 = ">=3.0.0"
325 | pygments = "*"
326 | restructuredtext-lint = "*"
327 |
328 | [[package]]
329 | name = "flake8-string-format"
330 | version = "0.3.0"
331 | description = "string format checker, plugin for flake8"
332 | category = "dev"
333 | optional = false
334 | python-versions = "*"
335 |
336 | [package.dependencies]
337 | flake8 = "*"
338 |
339 | [[package]]
340 | name = "ghp-import"
341 | version = "2.0.2"
342 | description = "Copy your docs directly to the gh-pages branch."
343 | category = "dev"
344 | optional = false
345 | python-versions = "*"
346 |
347 | [package.dependencies]
348 | python-dateutil = ">=2.8.1"
349 |
350 | [package.extras]
351 | dev = ["twine", "markdown", "flake8", "wheel"]
352 |
353 | [[package]]
354 | name = "gitdb"
355 | version = "4.0.7"
356 | description = "Git Object Database"
357 | category = "dev"
358 | optional = false
359 | python-versions = ">=3.4"
360 |
361 | [package.dependencies]
362 | smmap = ">=3.0.1,<5"
363 |
364 | [[package]]
365 | name = "gitpython"
366 | version = "3.1.24"
367 | description = "GitPython is a python library used to interact with Git repositories"
368 | category = "dev"
369 | optional = false
370 | python-versions = ">=3.7"
371 |
372 | [package.dependencies]
373 | gitdb = ">=4.0.1,<5"
374 | typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.10\""}
375 |
376 | [[package]]
377 | name = "hypothesis"
378 | version = "6.23.2"
379 | description = "A library for property-based testing"
380 | category = "dev"
381 | optional = false
382 | python-versions = ">=3.6"
383 |
384 | [package.dependencies]
385 | attrs = ">=19.2.0"
386 | sortedcontainers = ">=2.1.0,<3.0.0"
387 |
388 | [package.extras]
389 | all = ["black (>=19.10b0)", "click (>=7.0)", "django (>=2.2)", "dpcontracts (>=0.4)", "lark-parser (>=0.6.5)", "libcst (>=0.3.16)", "numpy (>=1.9.0)", "pandas (>=0.25)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "importlib-resources (>=3.3.0)", "importlib-metadata (>=3.6)", "backports.zoneinfo (>=0.2.1)", "tzdata (>=2020.4)"]
390 | cli = ["click (>=7.0)", "black (>=19.10b0)", "rich (>=9.0.0)"]
391 | codemods = ["libcst (>=0.3.16)"]
392 | dateutil = ["python-dateutil (>=1.4)"]
393 | django = ["pytz (>=2014.1)", "django (>=2.2)"]
394 | dpcontracts = ["dpcontracts (>=0.4)"]
395 | ghostwriter = ["black (>=19.10b0)"]
396 | lark = ["lark-parser (>=0.6.5)"]
397 | numpy = ["numpy (>=1.9.0)"]
398 | pandas = ["pandas (>=0.25)"]
399 | pytest = ["pytest (>=4.6)"]
400 | pytz = ["pytz (>=2014.1)"]
401 | redis = ["redis (>=3.0.0)"]
402 | zoneinfo = ["importlib-resources (>=3.3.0)", "backports.zoneinfo (>=0.2.1)", "tzdata (>=2020.4)"]
403 |
404 | [[package]]
405 | name = "importlib-metadata"
406 | version = "4.8.1"
407 | description = "Read metadata from Python packages"
408 | category = "dev"
409 | optional = false
410 | python-versions = ">=3.6"
411 |
412 | [package.dependencies]
413 | zipp = ">=0.5"
414 |
415 | [package.extras]
416 | docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
417 | perf = ["ipython"]
418 | testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"]
419 |
420 | [[package]]
421 | name = "isort"
422 | version = "5.9.3"
423 | description = "A Python utility / library to sort Python imports."
424 | category = "dev"
425 | optional = false
426 | python-versions = ">=3.6.1,<4.0"
427 |
428 | [package.extras]
429 | pipfile_deprecated_finder = ["pipreqs", "requirementslib"]
430 | requirements_deprecated_finder = ["pipreqs", "pip-api"]
431 | colors = ["colorama (>=0.4.3,<0.5.0)"]
432 | plugins = ["setuptools"]
433 |
434 | [[package]]
435 | name = "jinja2"
436 | version = "3.0.2"
437 | description = "A very fast and expressive template engine."
438 | category = "dev"
439 | optional = false
440 | python-versions = ">=3.6"
441 |
442 | [package.dependencies]
443 | MarkupSafe = ">=2.0"
444 |
445 | [package.extras]
446 | i18n = ["Babel (>=2.7)"]
447 |
448 | [[package]]
449 | name = "markdown"
450 | version = "3.3.4"
451 | description = "Python implementation of Markdown."
452 | category = "dev"
453 | optional = false
454 | python-versions = ">=3.6"
455 |
456 | [package.extras]
457 | testing = ["coverage", "pyyaml"]
458 |
459 | [[package]]
460 | name = "markupsafe"
461 | version = "2.0.1"
462 | description = "Safely add untrusted strings to HTML/XML markup."
463 | category = "dev"
464 | optional = false
465 | python-versions = ">=3.6"
466 |
467 | [[package]]
468 | name = "mccabe"
469 | version = "0.6.1"
470 | description = "McCabe checker, plugin for flake8"
471 | category = "dev"
472 | optional = false
473 | python-versions = "*"
474 |
475 | [[package]]
476 | name = "mergedeep"
477 | version = "1.3.4"
478 | description = "A deep merge function for 🐍."
479 | category = "dev"
480 | optional = false
481 | python-versions = ">=3.6"
482 |
483 | [[package]]
484 | name = "mkdocs"
485 | version = "1.2.3"
486 | description = "Project documentation with Markdown."
487 | category = "dev"
488 | optional = false
489 | python-versions = ">=3.6"
490 |
491 | [package.dependencies]
492 | click = ">=3.3"
493 | ghp-import = ">=1.0"
494 | importlib-metadata = ">=3.10"
495 | Jinja2 = ">=2.10.1"
496 | Markdown = ">=3.2.1"
497 | mergedeep = ">=1.3.4"
498 | packaging = ">=20.5"
499 | PyYAML = ">=3.10"
500 | pyyaml-env-tag = ">=0.1"
501 | watchdog = ">=2.0"
502 |
503 | [package.extras]
504 | i18n = ["babel (>=2.9.0)"]
505 |
506 | [[package]]
507 | name = "mkdocs-material"
508 | version = "7.3.3"
509 | description = "A Material Design theme for MkDocs"
510 | category = "dev"
511 | optional = false
512 | python-versions = "*"
513 |
514 | [package.dependencies]
515 | jinja2 = ">=2.11.1"
516 | markdown = ">=3.2"
517 | mkdocs = ">=1.2.2"
518 | mkdocs-material-extensions = ">=1.0"
519 | pygments = ">=2.4"
520 | pymdown-extensions = ">=9.0"
521 |
522 | [[package]]
523 | name = "mkdocs-material-extensions"
524 | version = "1.0.3"
525 | description = "Extension pack for Python Markdown."
526 | category = "dev"
527 | optional = false
528 | python-versions = ">=3.6"
529 |
530 | [[package]]
531 | name = "more-itertools"
532 | version = "8.10.0"
533 | description = "More routines for operating on iterables, beyond itertools"
534 | category = "dev"
535 | optional = false
536 | python-versions = ">=3.5"
537 |
538 | [[package]]
539 | name = "mypy"
540 | version = "0.910"
541 | description = "Optional static typing for Python"
542 | category = "dev"
543 | optional = false
544 | python-versions = ">=3.5"
545 |
546 | [package.dependencies]
547 | mypy-extensions = ">=0.4.3,<0.5.0"
548 | toml = "*"
549 | typing-extensions = ">=3.7.4"
550 |
551 | [package.extras]
552 | dmypy = ["psutil (>=4.0)"]
553 | python2 = ["typed-ast (>=1.4.0,<1.5.0)"]
554 |
555 | [[package]]
556 | name = "mypy-extensions"
557 | version = "0.4.3"
558 | description = "Experimental type system extensions for programs checked with the mypy typechecker."
559 | category = "dev"
560 | optional = false
561 | python-versions = "*"
562 |
563 | [[package]]
564 | name = "packaging"
565 | version = "21.0"
566 | description = "Core utilities for Python packages"
567 | category = "dev"
568 | optional = false
569 | python-versions = ">=3.6"
570 |
571 | [package.dependencies]
572 | pyparsing = ">=2.0.2"
573 |
574 | [[package]]
575 | name = "pathspec"
576 | version = "0.9.0"
577 | description = "Utility library for gitignore style pattern matching of file paths."
578 | category = "dev"
579 | optional = false
580 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
581 |
582 | [[package]]
583 | name = "pbr"
584 | version = "5.6.0"
585 | description = "Python Build Reasonableness"
586 | category = "dev"
587 | optional = false
588 | python-versions = ">=2.6"
589 |
590 | [[package]]
591 | name = "pep8-naming"
592 | version = "0.11.1"
593 | description = "Check PEP-8 naming conventions, plugin for flake8"
594 | category = "dev"
595 | optional = false
596 | python-versions = "*"
597 |
598 | [package.dependencies]
599 | flake8-polyfill = ">=1.0.2,<2"
600 |
601 | [[package]]
602 | name = "platformdirs"
603 | version = "2.4.0"
604 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
605 | category = "dev"
606 | optional = false
607 | python-versions = ">=3.6"
608 |
609 | [package.extras]
610 | docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"]
611 | test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"]
612 |
613 | [[package]]
614 | name = "pluggy"
615 | version = "0.13.1"
616 | description = "plugin and hook calling mechanisms for python"
617 | category = "dev"
618 | optional = false
619 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
620 |
621 | [package.extras]
622 | dev = ["pre-commit", "tox"]
623 |
624 | [[package]]
625 | name = "py"
626 | version = "1.10.0"
627 | description = "library with cross-python path, ini-parsing, io, code, log facilities"
628 | category = "dev"
629 | optional = false
630 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
631 |
632 | [[package]]
633 | name = "pycodestyle"
634 | version = "2.7.0"
635 | description = "Python style guide checker"
636 | category = "dev"
637 | optional = false
638 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
639 |
640 | [[package]]
641 | name = "pydocstyle"
642 | version = "6.1.1"
643 | description = "Python docstring style checker"
644 | category = "dev"
645 | optional = false
646 | python-versions = ">=3.6"
647 |
648 | [package.dependencies]
649 | snowballstemmer = "*"
650 |
651 | [package.extras]
652 | toml = ["toml"]
653 |
654 | [[package]]
655 | name = "pyflakes"
656 | version = "2.3.1"
657 | description = "passive checker of Python programs"
658 | category = "dev"
659 | optional = false
660 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
661 |
662 | [[package]]
663 | name = "pygments"
664 | version = "2.10.0"
665 | description = "Pygments is a syntax highlighting package written in Python."
666 | category = "dev"
667 | optional = false
668 | python-versions = ">=3.5"
669 |
670 | [[package]]
671 | name = "pymdown-extensions"
672 | version = "9.0"
673 | description = "Extension pack for Python Markdown."
674 | category = "dev"
675 | optional = false
676 | python-versions = ">=3.6"
677 |
678 | [package.dependencies]
679 | Markdown = ">=3.2"
680 |
681 | [[package]]
682 | name = "pyparsing"
683 | version = "2.4.7"
684 | description = "Python parsing module"
685 | category = "dev"
686 | optional = false
687 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
688 |
689 | [[package]]
690 | name = "pytest"
691 | version = "5.4.3"
692 | description = "pytest: simple powerful testing with Python"
693 | category = "dev"
694 | optional = false
695 | python-versions = ">=3.5"
696 |
697 | [package.dependencies]
698 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
699 | attrs = ">=17.4.0"
700 | colorama = {version = "*", markers = "sys_platform == \"win32\""}
701 | more-itertools = ">=4.0.0"
702 | packaging = "*"
703 | pluggy = ">=0.12,<1.0"
704 | py = ">=1.5.0"
705 | wcwidth = "*"
706 |
707 | [package.extras]
708 | checkqa-mypy = ["mypy (==v0.761)"]
709 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
710 |
711 | [[package]]
712 | name = "pytest-cov"
713 | version = "2.12.1"
714 | description = "Pytest plugin for measuring coverage."
715 | category = "dev"
716 | optional = false
717 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
718 |
719 | [package.dependencies]
720 | coverage = ">=5.2.1"
721 | pytest = ">=4.6"
722 | toml = "*"
723 |
724 | [package.extras]
725 | testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"]
726 |
727 | [[package]]
728 | name = "pytest-flake8"
729 | version = "1.0.7"
730 | description = "pytest plugin to check FLAKE8 requirements"
731 | category = "dev"
732 | optional = false
733 | python-versions = "*"
734 |
735 | [package.dependencies]
736 | flake8 = ">=3.5"
737 | pytest = ">=3.5"
738 |
739 | [[package]]
740 | name = "pytest-mypy"
741 | version = "0.8.1"
742 | description = "Mypy static type checker plugin for Pytest"
743 | category = "dev"
744 | optional = false
745 | python-versions = ">=3.5"
746 |
747 | [package.dependencies]
748 | attrs = ">=19.0"
749 | filelock = ">=3.0"
750 | mypy = [
751 | {version = ">=0.700", markers = "python_version >= \"3.8\" and python_version < \"3.9\""},
752 | {version = ">=0.780", markers = "python_version >= \"3.9\""},
753 | ]
754 | pytest = ">=3.5"
755 |
756 | [[package]]
757 | name = "python-dateutil"
758 | version = "2.8.2"
759 | description = "Extensions to the standard Python datetime module"
760 | category = "dev"
761 | optional = false
762 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
763 |
764 | [package.dependencies]
765 | six = ">=1.5"
766 |
767 | [[package]]
768 | name = "pyyaml"
769 | version = "6.0"
770 | description = "YAML parser and emitter for Python"
771 | category = "dev"
772 | optional = false
773 | python-versions = ">=3.6"
774 |
775 | [[package]]
776 | name = "pyyaml-env-tag"
777 | version = "0.1"
778 | description = "A custom YAML tag for referencing environment variables in YAML files. "
779 | category = "dev"
780 | optional = false
781 | python-versions = ">=3.6"
782 |
783 | [package.dependencies]
784 | pyyaml = "*"
785 |
786 | [[package]]
787 | name = "regex"
788 | version = "2021.10.8"
789 | description = "Alternative regular expression module, to replace re."
790 | category = "dev"
791 | optional = false
792 | python-versions = "*"
793 |
794 | [[package]]
795 | name = "restructuredtext-lint"
796 | version = "1.3.2"
797 | description = "reStructuredText linter"
798 | category = "dev"
799 | optional = false
800 | python-versions = "*"
801 |
802 | [package.dependencies]
803 | docutils = ">=0.11,<1.0"
804 |
805 | [[package]]
806 | name = "six"
807 | version = "1.16.0"
808 | description = "Python 2 and 3 compatibility utilities"
809 | category = "dev"
810 | optional = false
811 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
812 |
813 | [[package]]
814 | name = "smmap"
815 | version = "4.0.0"
816 | description = "A pure Python implementation of a sliding window memory map manager"
817 | category = "dev"
818 | optional = false
819 | python-versions = ">=3.5"
820 |
821 | [[package]]
822 | name = "snowballstemmer"
823 | version = "2.1.0"
824 | description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms."
825 | category = "dev"
826 | optional = false
827 | python-versions = "*"
828 |
829 | [[package]]
830 | name = "sortedcontainers"
831 | version = "2.4.0"
832 | description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set"
833 | category = "dev"
834 | optional = false
835 | python-versions = "*"
836 |
837 | [[package]]
838 | name = "stevedore"
839 | version = "3.4.0"
840 | description = "Manage dynamic plugins for Python applications"
841 | category = "dev"
842 | optional = false
843 | python-versions = ">=3.6"
844 |
845 | [package.dependencies]
846 | pbr = ">=2.0.0,<2.1.0 || >2.1.0"
847 |
848 | [[package]]
849 | name = "sumtypes"
850 | version = "0.1a5"
851 | description = "Algebraic types for Python (notably providing Sum Types, aka Tagged Unions)"
852 | category = "main"
853 | optional = false
854 | python-versions = "*"
855 |
856 | [package.dependencies]
857 | attrs = "*"
858 |
859 | [[package]]
860 | name = "testfixtures"
861 | version = "6.18.3"
862 | description = "A collection of helpers and mock objects for unit tests and doc tests."
863 | category = "dev"
864 | optional = false
865 | python-versions = "*"
866 |
867 | [package.extras]
868 | build = ["setuptools-git", "wheel", "twine"]
869 | docs = ["sphinx", "zope.component", "sybil", "twisted", "mock", "django (<2)", "django"]
870 | test = ["pytest (>=3.6)", "pytest-cov", "pytest-django", "zope.component", "sybil", "twisted", "mock", "django (<2)", "django"]
871 |
872 | [[package]]
873 | name = "toml"
874 | version = "0.10.2"
875 | description = "Python Library for Tom's Obvious, Minimal Language"
876 | category = "dev"
877 | optional = false
878 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
879 |
880 | [[package]]
881 | name = "tomli"
882 | version = "1.2.1"
883 | description = "A lil' TOML parser"
884 | category = "dev"
885 | optional = false
886 | python-versions = ">=3.6"
887 |
888 | [[package]]
889 | name = "typing-extensions"
890 | version = "3.10.0.2"
891 | description = "Backported and Experimental Type Hints for Python 3.5+"
892 | category = "dev"
893 | optional = false
894 | python-versions = "*"
895 |
896 | [[package]]
897 | name = "watchdog"
898 | version = "2.1.6"
899 | description = "Filesystem events monitoring"
900 | category = "dev"
901 | optional = false
902 | python-versions = ">=3.6"
903 |
904 | [package.extras]
905 | watchmedo = ["PyYAML (>=3.10)"]
906 |
907 | [[package]]
908 | name = "wcwidth"
909 | version = "0.2.5"
910 | description = "Measures the displayed width of unicode strings in a terminal"
911 | category = "dev"
912 | optional = false
913 | python-versions = "*"
914 |
915 | [[package]]
916 | name = "wemake-python-styleguide"
917 | version = "0.15.3"
918 | description = "The strictest and most opinionated python linter ever"
919 | category = "dev"
920 | optional = false
921 | python-versions = ">=3.6,<4.0"
922 |
923 | [package.dependencies]
924 | astor = ">=0.8,<0.9"
925 | attrs = "*"
926 | darglint = ">=1.2,<2.0"
927 | flake8 = ">=3.7,<4.0"
928 | flake8-bandit = ">=2.1,<3.0"
929 | flake8-broken-line = ">=0.3,<0.4"
930 | flake8-bugbear = ">=20.1,<22.0"
931 | flake8-commas = ">=2.0,<3.0"
932 | flake8-comprehensions = ">=3.1,<4.0"
933 | flake8-debugger = ">=4.0,<5.0"
934 | flake8-docstrings = ">=1.3,<2.0"
935 | flake8-eradicate = ">=1.0,<2.0"
936 | flake8-isort = ">=4.0,<5.0"
937 | flake8-quotes = ">=3.0,<4.0"
938 | flake8-rst-docstrings = ">=0.2.3,<0.3.0"
939 | flake8-string-format = ">=0.3,<0.4"
940 | pep8-naming = ">=0.11,<0.12"
941 | pygments = ">=2.4,<3.0"
942 | typing_extensions = ">=3.6,<4.0"
943 |
944 | [[package]]
945 | name = "zipp"
946 | version = "3.6.0"
947 | description = "Backport of pathlib-compatible object wrapper for zip files"
948 | category = "dev"
949 | optional = false
950 | python-versions = ">=3.6"
951 |
952 | [package.extras]
953 | docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
954 | testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"]
955 |
956 | [metadata]
957 | lock-version = "1.1"
958 | python-versions = "^3.8.3"
959 | content-hash = "5cb0921d7dfcb86807942426261936acd4e3f4e0e37a471d7197194090bbd684"
960 |
961 | [metadata.files]
962 | astor = [
963 | {file = "astor-0.8.1-py2.py3-none-any.whl", hash = "sha256:070a54e890cefb5b3739d19f30f5a5ec840ffc9c50ffa7d23cc9fc1a38ebbfc5"},
964 | {file = "astor-0.8.1.tar.gz", hash = "sha256:6a6effda93f4e1ce9f618779b2dd1d9d84f1e32812c23a29b3fff6fd7f63fa5e"},
965 | ]
966 | atomicwrites = [
967 | {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"},
968 | {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
969 | ]
970 | attrs = [
971 | {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"},
972 | {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"},
973 | ]
974 | bandit = [
975 | {file = "bandit-1.7.0-py3-none-any.whl", hash = "sha256:216be4d044209fa06cf2a3e51b319769a51be8318140659719aa7a115c35ed07"},
976 | {file = "bandit-1.7.0.tar.gz", hash = "sha256:8a4c7415254d75df8ff3c3b15cfe9042ecee628a1e40b44c15a98890fbfc2608"},
977 | ]
978 | black = [
979 | {file = "black-21.9b0-py3-none-any.whl", hash = "sha256:380f1b5da05e5a1429225676655dddb96f5ae8c75bdf91e53d798871b902a115"},
980 | {file = "black-21.9b0.tar.gz", hash = "sha256:7de4cfc7eb6b710de325712d40125689101d21d25283eed7e9998722cf10eb91"},
981 | ]
982 | click = [
983 | {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"},
984 | {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"},
985 | ]
986 | colorama = [
987 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
988 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
989 | ]
990 | construct = [
991 | {file = "construct-2.10.67.tar.gz", hash = "sha256:730235fedf4f2fee5cfadda1d14b83ef1bf23790fb1cc579073e10f70a050883"},
992 | ]
993 | construct-typing = [
994 | {file = "construct-typing-0.5.1.tar.gz", hash = "sha256:ed0b2bbbce1d4b31d184886320cec0fee6748f0ae329ceb307d08263a2205266"},
995 | {file = "construct_typing-0.5.1-py3-none-any.whl", hash = "sha256:c6d6af9a84cb00116d1a205f1b53cf0261748e504a41cc1da269d95dbccaeb44"},
996 | ]
997 | coverage = [
998 | {file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"},
999 | {file = "coverage-5.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b"},
1000 | {file = "coverage-5.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669"},
1001 | {file = "coverage-5.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90"},
1002 | {file = "coverage-5.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c"},
1003 | {file = "coverage-5.5-cp27-cp27m-win32.whl", hash = "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a"},
1004 | {file = "coverage-5.5-cp27-cp27m-win_amd64.whl", hash = "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82"},
1005 | {file = "coverage-5.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905"},
1006 | {file = "coverage-5.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083"},
1007 | {file = "coverage-5.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5"},
1008 | {file = "coverage-5.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81"},
1009 | {file = "coverage-5.5-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6"},
1010 | {file = "coverage-5.5-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0"},
1011 | {file = "coverage-5.5-cp310-cp310-win_amd64.whl", hash = "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae"},
1012 | {file = "coverage-5.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb"},
1013 | {file = "coverage-5.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160"},
1014 | {file = "coverage-5.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"},
1015 | {file = "coverage-5.5-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701"},
1016 | {file = "coverage-5.5-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793"},
1017 | {file = "coverage-5.5-cp35-cp35m-win32.whl", hash = "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e"},
1018 | {file = "coverage-5.5-cp35-cp35m-win_amd64.whl", hash = "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3"},
1019 | {file = "coverage-5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066"},
1020 | {file = "coverage-5.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a"},
1021 | {file = "coverage-5.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465"},
1022 | {file = "coverage-5.5-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb"},
1023 | {file = "coverage-5.5-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821"},
1024 | {file = "coverage-5.5-cp36-cp36m-win32.whl", hash = "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45"},
1025 | {file = "coverage-5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184"},
1026 | {file = "coverage-5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a"},
1027 | {file = "coverage-5.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53"},
1028 | {file = "coverage-5.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d"},
1029 | {file = "coverage-5.5-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638"},
1030 | {file = "coverage-5.5-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3"},
1031 | {file = "coverage-5.5-cp37-cp37m-win32.whl", hash = "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a"},
1032 | {file = "coverage-5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a"},
1033 | {file = "coverage-5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6"},
1034 | {file = "coverage-5.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2"},
1035 | {file = "coverage-5.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759"},
1036 | {file = "coverage-5.5-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873"},
1037 | {file = "coverage-5.5-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a"},
1038 | {file = "coverage-5.5-cp38-cp38-win32.whl", hash = "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6"},
1039 | {file = "coverage-5.5-cp38-cp38-win_amd64.whl", hash = "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502"},
1040 | {file = "coverage-5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b"},
1041 | {file = "coverage-5.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529"},
1042 | {file = "coverage-5.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b"},
1043 | {file = "coverage-5.5-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff"},
1044 | {file = "coverage-5.5-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b"},
1045 | {file = "coverage-5.5-cp39-cp39-win32.whl", hash = "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6"},
1046 | {file = "coverage-5.5-cp39-cp39-win_amd64.whl", hash = "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03"},
1047 | {file = "coverage-5.5-pp36-none-any.whl", hash = "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079"},
1048 | {file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"},
1049 | {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"},
1050 | ]
1051 | darglint = [
1052 | {file = "darglint-1.8.0-py3-none-any.whl", hash = "sha256:ac6797bcc918cd8d8f14c168a4a364f54e1aeb4ced59db58e7e4c6dfec2fe15c"},
1053 | {file = "darglint-1.8.0.tar.gz", hash = "sha256:aa605ef47817a6d14797d32b390466edab621768ea4ca5cc0f3c54f6d8dcaec8"},
1054 | ]
1055 | docutils = [
1056 | {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"},
1057 | {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"},
1058 | ]
1059 | eradicate = [
1060 | {file = "eradicate-2.0.0.tar.gz", hash = "sha256:27434596f2c5314cc9b31410c93d8f7e8885747399773cd088d3adea647a60c8"},
1061 | ]
1062 | filelock = [
1063 | {file = "filelock-3.3.1-py3-none-any.whl", hash = "sha256:2b5eb3589e7fdda14599e7eb1a50e09b4cc14f34ed98b8ba56d33bfaafcbef2f"},
1064 | {file = "filelock-3.3.1.tar.gz", hash = "sha256:34a9f35f95c441e7b38209775d6e0337f9a3759f3565f6c5798f19618527c76f"},
1065 | ]
1066 | flake8 = [
1067 | {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"},
1068 | {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"},
1069 | ]
1070 | flake8-bandit = [
1071 | {file = "flake8_bandit-2.1.2.tar.gz", hash = "sha256:687fc8da2e4a239b206af2e54a90093572a60d0954f3054e23690739b0b0de3b"},
1072 | ]
1073 | flake8-broken-line = [
1074 | {file = "flake8-broken-line-0.3.0.tar.gz", hash = "sha256:f74e052833324a9e5f0055032f7ccc54b23faabafe5a26241c2f977e70b10b50"},
1075 | {file = "flake8_broken_line-0.3.0-py3-none-any.whl", hash = "sha256:611f79c7f27118e7e5d3dc098ef7681c40aeadf23783700c5dbee840d2baf3af"},
1076 | ]
1077 | flake8-bugbear = [
1078 | {file = "flake8-bugbear-21.9.2.tar.gz", hash = "sha256:db9a09893a6c649a197f5350755100bb1dd84f110e60cf532fdfa07e41808ab2"},
1079 | {file = "flake8_bugbear-21.9.2-py36.py37.py38-none-any.whl", hash = "sha256:4f7eaa6f05b7d7ea4cbbde93f7bcdc5438e79320fa1ec420d860c181af38b769"},
1080 | ]
1081 | flake8-commas = [
1082 | {file = "flake8-commas-2.1.0.tar.gz", hash = "sha256:940441ab8ee544df564ae3b3f49f20462d75d5c7cac2463e0b27436e2050f263"},
1083 | {file = "flake8_commas-2.1.0-py2.py3-none-any.whl", hash = "sha256:ebb96c31e01d0ef1d0685a21f3f0e2f8153a0381430e748bf0bbbb5d5b453d54"},
1084 | ]
1085 | flake8-comprehensions = [
1086 | {file = "flake8-comprehensions-3.7.0.tar.gz", hash = "sha256:6b3218b2dde8ac5959c6476cde8f41a79e823c22feb656be2710cd2a3232cef9"},
1087 | {file = "flake8_comprehensions-3.7.0-py3-none-any.whl", hash = "sha256:a5d7aea6315bbbd6fbcb2b4e80bff6a54d1600155e26236e555d0c6fe1d62522"},
1088 | ]
1089 | flake8-debugger = [
1090 | {file = "flake8-debugger-4.0.0.tar.gz", hash = "sha256:e43dc777f7db1481db473210101ec2df2bd39a45b149d7218a618e954177eda6"},
1091 | {file = "flake8_debugger-4.0.0-py3-none-any.whl", hash = "sha256:82e64faa72e18d1bdd0000407502ebb8ecffa7bc027c62b9d4110ce27c091032"},
1092 | ]
1093 | flake8-docstrings = [
1094 | {file = "flake8-docstrings-1.6.0.tar.gz", hash = "sha256:9fe7c6a306064af8e62a055c2f61e9eb1da55f84bb39caef2b84ce53708ac34b"},
1095 | {file = "flake8_docstrings-1.6.0-py2.py3-none-any.whl", hash = "sha256:99cac583d6c7e32dd28bbfbef120a7c0d1b6dde4adb5a9fd441c4227a6534bde"},
1096 | ]
1097 | flake8-eradicate = [
1098 | {file = "flake8-eradicate-1.1.0.tar.gz", hash = "sha256:f5917d6dbca352efcd10c15fdab9c55c48f0f26f6a8d47898b25d39101f170a8"},
1099 | {file = "flake8_eradicate-1.1.0-py3-none-any.whl", hash = "sha256:d8e39b684a37c257a53cda817d86e2d96c9ba3450ddc292742623a5dfee04d9e"},
1100 | ]
1101 | flake8-isort = [
1102 | {file = "flake8-isort-4.1.1.tar.gz", hash = "sha256:d814304ab70e6e58859bc5c3e221e2e6e71c958e7005239202fee19c24f82717"},
1103 | {file = "flake8_isort-4.1.1-py3-none-any.whl", hash = "sha256:c4e8b6dcb7be9b71a02e6e5d4196cefcef0f3447be51e82730fb336fff164949"},
1104 | ]
1105 | flake8-polyfill = [
1106 | {file = "flake8-polyfill-1.0.2.tar.gz", hash = "sha256:e44b087597f6da52ec6393a709e7108b2905317d0c0b744cdca6208e670d8eda"},
1107 | {file = "flake8_polyfill-1.0.2-py2.py3-none-any.whl", hash = "sha256:12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9"},
1108 | ]
1109 | flake8-quotes = [
1110 | {file = "flake8-quotes-3.3.0.tar.gz", hash = "sha256:f1dd87830ed77ff2ce47fc0ee0fd87ae20e8f045355354ffbf4dcaa18d528217"},
1111 | ]
1112 | flake8-rst-docstrings = [
1113 | {file = "flake8-rst-docstrings-0.2.3.tar.gz", hash = "sha256:3045794e1c8467fba33aaea5c246b8369efc9c44ef8b0b20199bb6df7a4bd47b"},
1114 | {file = "flake8_rst_docstrings-0.2.3-py3-none-any.whl", hash = "sha256:565bbb391d7e4d0042924102221e9857ad72929cdd305b26501736ec22c1451a"},
1115 | ]
1116 | flake8-string-format = [
1117 | {file = "flake8-string-format-0.3.0.tar.gz", hash = "sha256:65f3da786a1461ef77fca3780b314edb2853c377f2e35069723348c8917deaa2"},
1118 | {file = "flake8_string_format-0.3.0-py2.py3-none-any.whl", hash = "sha256:812ff431f10576a74c89be4e85b8e075a705be39bc40c4b4278b5b13e2afa9af"},
1119 | ]
1120 | ghp-import = [
1121 | {file = "ghp-import-2.0.2.tar.gz", hash = "sha256:947b3771f11be850c852c64b561c600fdddf794bab363060854c1ee7ad05e071"},
1122 | {file = "ghp_import-2.0.2-py3-none-any.whl", hash = "sha256:5f8962b30b20652cdffa9c5a9812f7de6bcb56ec475acac579807719bf242c46"},
1123 | ]
1124 | gitdb = [
1125 | {file = "gitdb-4.0.7-py3-none-any.whl", hash = "sha256:6c4cc71933456991da20917998acbe6cf4fb41eeaab7d6d67fbc05ecd4c865b0"},
1126 | {file = "gitdb-4.0.7.tar.gz", hash = "sha256:96bf5c08b157a666fec41129e6d327235284cca4c81e92109260f353ba138005"},
1127 | ]
1128 | gitpython = [
1129 | {file = "GitPython-3.1.24-py3-none-any.whl", hash = "sha256:dc0a7f2f697657acc8d7f89033e8b1ea94dd90356b2983bca89dc8d2ab3cc647"},
1130 | {file = "GitPython-3.1.24.tar.gz", hash = "sha256:df83fdf5e684fef7c6ee2c02fc68a5ceb7e7e759d08b694088d0cacb4eba59e5"},
1131 | ]
1132 | hypothesis = [
1133 | {file = "hypothesis-6.23.2-py3-none-any.whl", hash = "sha256:ffe81bf1e3122edfcdbf21b31a0b8db3759fac6b87cdc2f9ae32cd360311ccf4"},
1134 | {file = "hypothesis-6.23.2.tar.gz", hash = "sha256:b71b257916c91484716a10220ed2b9a0cf82acc3ed8ef421bb2aa0a671761053"},
1135 | ]
1136 | importlib-metadata = [
1137 | {file = "importlib_metadata-4.8.1-py3-none-any.whl", hash = "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15"},
1138 | {file = "importlib_metadata-4.8.1.tar.gz", hash = "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"},
1139 | ]
1140 | isort = [
1141 | {file = "isort-5.9.3-py3-none-any.whl", hash = "sha256:e17d6e2b81095c9db0a03a8025a957f334d6ea30b26f9ec70805411e5c7c81f2"},
1142 | {file = "isort-5.9.3.tar.gz", hash = "sha256:9c2ea1e62d871267b78307fe511c0838ba0da28698c5732d54e2790bf3ba9899"},
1143 | ]
1144 | jinja2 = [
1145 | {file = "Jinja2-3.0.2-py3-none-any.whl", hash = "sha256:8569982d3f0889eed11dd620c706d39b60c36d6d25843961f33f77fb6bc6b20c"},
1146 | {file = "Jinja2-3.0.2.tar.gz", hash = "sha256:827a0e32839ab1600d4eb1c4c33ec5a8edfbc5cb42dafa13b81f182f97784b45"},
1147 | ]
1148 | markdown = [
1149 | {file = "Markdown-3.3.4-py3-none-any.whl", hash = "sha256:96c3ba1261de2f7547b46a00ea8463832c921d3f9d6aba3f255a6f71386db20c"},
1150 | {file = "Markdown-3.3.4.tar.gz", hash = "sha256:31b5b491868dcc87d6c24b7e3d19a0d730d59d3e46f4eea6430a321bed387a49"},
1151 | ]
1152 | markupsafe = [
1153 | {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"},
1154 | {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"},
1155 | {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"},
1156 | {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"},
1157 | {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"},
1158 | {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"},
1159 | {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"},
1160 | {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"},
1161 | {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"},
1162 | {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"},
1163 | {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"},
1164 | {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"},
1165 | {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"},
1166 | {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"},
1167 | {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"},
1168 | {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"},
1169 | {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"},
1170 | {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"},
1171 | {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"},
1172 | {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"},
1173 | {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"},
1174 | {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"},
1175 | {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"},
1176 | {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"},
1177 | {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"},
1178 | {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"},
1179 | {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"},
1180 | {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"},
1181 | {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"},
1182 | {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"},
1183 | {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"},
1184 | {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"},
1185 | {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"},
1186 | {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"},
1187 | {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"},
1188 | {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"},
1189 | {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"},
1190 | {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"},
1191 | {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"},
1192 | {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"},
1193 | {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"},
1194 | {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"},
1195 | {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"},
1196 | {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"},
1197 | {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"},
1198 | {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"},
1199 | {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"},
1200 | {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"},
1201 | {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"},
1202 | {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"},
1203 | {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"},
1204 | {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"},
1205 | {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"},
1206 | {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"},
1207 | ]
1208 | mccabe = [
1209 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
1210 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
1211 | ]
1212 | mergedeep = [
1213 | {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"},
1214 | {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"},
1215 | ]
1216 | mkdocs = [
1217 | {file = "mkdocs-1.2.3-py3-none-any.whl", hash = "sha256:a1fa8c2d0c1305d7fc2b9d9f607c71778572a8b110fb26642aa00296c9e6d072"},
1218 | {file = "mkdocs-1.2.3.tar.gz", hash = "sha256:89f5a094764381cda656af4298727c9f53dc3e602983087e1fe96ea1df24f4c1"},
1219 | ]
1220 | mkdocs-material = [
1221 | {file = "mkdocs-material-7.3.3.tar.gz", hash = "sha256:3444b681f47e62c0ec7166bfb6f12360a26c751224cd6d3b3816f7310827073f"},
1222 | {file = "mkdocs_material-7.3.3-py2.py3-none-any.whl", hash = "sha256:56bcc4e43356b97caa07706b0f446a3d9c39eb9aa1ad64131686d555270fc65e"},
1223 | ]
1224 | mkdocs-material-extensions = [
1225 | {file = "mkdocs-material-extensions-1.0.3.tar.gz", hash = "sha256:bfd24dfdef7b41c312ede42648f9eb83476ea168ec163b613f9abd12bbfddba2"},
1226 | {file = "mkdocs_material_extensions-1.0.3-py3-none-any.whl", hash = "sha256:a82b70e533ce060b2a5d9eb2bc2e1be201cf61f901f93704b4acf6e3d5983a44"},
1227 | ]
1228 | more-itertools = [
1229 | {file = "more-itertools-8.10.0.tar.gz", hash = "sha256:1debcabeb1df793814859d64a81ad7cb10504c24349368ccf214c664c474f41f"},
1230 | {file = "more_itertools-8.10.0-py3-none-any.whl", hash = "sha256:56ddac45541718ba332db05f464bebfb0768110111affd27f66e0051f276fa43"},
1231 | ]
1232 | mypy = [
1233 | {file = "mypy-0.910-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457"},
1234 | {file = "mypy-0.910-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b94e4b785e304a04ea0828759172a15add27088520dc7e49ceade7834275bedb"},
1235 | {file = "mypy-0.910-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:088cd9c7904b4ad80bec811053272986611b84221835e079be5bcad029e79dd9"},
1236 | {file = "mypy-0.910-cp35-cp35m-win_amd64.whl", hash = "sha256:adaeee09bfde366d2c13fe6093a7df5df83c9a2ba98638c7d76b010694db760e"},
1237 | {file = "mypy-0.910-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ecd2c3fe726758037234c93df7e98deb257fd15c24c9180dacf1ef829da5f921"},
1238 | {file = "mypy-0.910-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d9dd839eb0dc1bbe866a288ba3c1afc33a202015d2ad83b31e875b5905a079b6"},
1239 | {file = "mypy-0.910-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:3e382b29f8e0ccf19a2df2b29a167591245df90c0b5a2542249873b5c1d78212"},
1240 | {file = "mypy-0.910-cp36-cp36m-win_amd64.whl", hash = "sha256:53fd2eb27a8ee2892614370896956af2ff61254c275aaee4c230ae771cadd885"},
1241 | {file = "mypy-0.910-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b6fb13123aeef4a3abbcfd7e71773ff3ff1526a7d3dc538f3929a49b42be03f0"},
1242 | {file = "mypy-0.910-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e4dab234478e3bd3ce83bac4193b2ecd9cf94e720ddd95ce69840273bf44f6de"},
1243 | {file = "mypy-0.910-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:7df1ead20c81371ccd6091fa3e2878559b5c4d4caadaf1a484cf88d93ca06703"},
1244 | {file = "mypy-0.910-cp37-cp37m-win_amd64.whl", hash = "sha256:0aadfb2d3935988ec3815952e44058a3100499f5be5b28c34ac9d79f002a4a9a"},
1245 | {file = "mypy-0.910-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ec4e0cd079db280b6bdabdc807047ff3e199f334050db5cbb91ba3e959a67504"},
1246 | {file = "mypy-0.910-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:119bed3832d961f3a880787bf621634ba042cb8dc850a7429f643508eeac97b9"},
1247 | {file = "mypy-0.910-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:866c41f28cee548475f146aa4d39a51cf3b6a84246969f3759cb3e9c742fc072"},
1248 | {file = "mypy-0.910-cp38-cp38-win_amd64.whl", hash = "sha256:ceb6e0a6e27fb364fb3853389607cf7eb3a126ad335790fa1e14ed02fba50811"},
1249 | {file = "mypy-0.910-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a85e280d4d217150ce8cb1a6dddffd14e753a4e0c3cf90baabb32cefa41b59e"},
1250 | {file = "mypy-0.910-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:42c266ced41b65ed40a282c575705325fa7991af370036d3f134518336636f5b"},
1251 | {file = "mypy-0.910-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:3c4b8ca36877fc75339253721f69603a9c7fdb5d4d5a95a1a1b899d8b86a4de2"},
1252 | {file = "mypy-0.910-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:c0df2d30ed496a08de5daed2a9ea807d07c21ae0ab23acf541ab88c24b26ab97"},
1253 | {file = "mypy-0.910-cp39-cp39-win_amd64.whl", hash = "sha256:c6c2602dffb74867498f86e6129fd52a2770c48b7cd3ece77ada4fa38f94eba8"},
1254 | {file = "mypy-0.910-py3-none-any.whl", hash = "sha256:ef565033fa5a958e62796867b1df10c40263ea9ded87164d67572834e57a174d"},
1255 | {file = "mypy-0.910.tar.gz", hash = "sha256:704098302473cb31a218f1775a873b376b30b4c18229421e9e9dc8916fd16150"},
1256 | ]
1257 | mypy-extensions = [
1258 | {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
1259 | {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
1260 | ]
1261 | packaging = [
1262 | {file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"},
1263 | {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"},
1264 | ]
1265 | pathspec = [
1266 | {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"},
1267 | {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
1268 | ]
1269 | pbr = [
1270 | {file = "pbr-5.6.0-py2.py3-none-any.whl", hash = "sha256:c68c661ac5cc81058ac94247278eeda6d2e6aecb3e227b0387c30d277e7ef8d4"},
1271 | {file = "pbr-5.6.0.tar.gz", hash = "sha256:42df03e7797b796625b1029c0400279c7c34fd7df24a7d7818a1abb5b38710dd"},
1272 | ]
1273 | pep8-naming = [
1274 | {file = "pep8-naming-0.11.1.tar.gz", hash = "sha256:a1dd47dd243adfe8a83616e27cf03164960b507530f155db94e10b36a6cd6724"},
1275 | {file = "pep8_naming-0.11.1-py2.py3-none-any.whl", hash = "sha256:f43bfe3eea7e0d73e8b5d07d6407ab47f2476ccaeff6937c84275cd30b016738"},
1276 | ]
1277 | platformdirs = [
1278 | {file = "platformdirs-2.4.0-py3-none-any.whl", hash = "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"},
1279 | {file = "platformdirs-2.4.0.tar.gz", hash = "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2"},
1280 | ]
1281 | pluggy = [
1282 | {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
1283 | {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
1284 | ]
1285 | py = [
1286 | {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"},
1287 | {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"},
1288 | ]
1289 | pycodestyle = [
1290 | {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"},
1291 | {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"},
1292 | ]
1293 | pydocstyle = [
1294 | {file = "pydocstyle-6.1.1-py3-none-any.whl", hash = "sha256:6987826d6775056839940041beef5c08cc7e3d71d63149b48e36727f70144dc4"},
1295 | {file = "pydocstyle-6.1.1.tar.gz", hash = "sha256:1d41b7c459ba0ee6c345f2eb9ae827cab14a7533a88c5c6f7e94923f72df92dc"},
1296 | ]
1297 | pyflakes = [
1298 | {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"},
1299 | {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"},
1300 | ]
1301 | pygments = [
1302 | {file = "Pygments-2.10.0-py3-none-any.whl", hash = "sha256:b8e67fe6af78f492b3c4b3e2970c0624cbf08beb1e493b2c99b9fa1b67a20380"},
1303 | {file = "Pygments-2.10.0.tar.gz", hash = "sha256:f398865f7eb6874156579fdf36bc840a03cab64d1cde9e93d68f46a425ec52c6"},
1304 | ]
1305 | pymdown-extensions = [
1306 | {file = "pymdown-extensions-9.0.tar.gz", hash = "sha256:01e4bec7f4b16beaba0087a74496401cf11afd69e3a11fe95cb593e5c698ef40"},
1307 | {file = "pymdown_extensions-9.0-py3-none-any.whl", hash = "sha256:430cc2fbb30cef2df70edac0b4f62614a6a4d2b06462e32da4ca96098b7c1dfb"},
1308 | ]
1309 | pyparsing = [
1310 | {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
1311 | {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
1312 | ]
1313 | pytest = [
1314 | {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"},
1315 | {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"},
1316 | ]
1317 | pytest-cov = [
1318 | {file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"},
1319 | {file = "pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a"},
1320 | ]
1321 | pytest-flake8 = [
1322 | {file = "pytest-flake8-1.0.7.tar.gz", hash = "sha256:f0259761a903563f33d6f099914afef339c085085e643bee8343eb323b32dd6b"},
1323 | {file = "pytest_flake8-1.0.7-py2.py3-none-any.whl", hash = "sha256:c28cf23e7d359753c896745fd4ba859495d02e16c84bac36caa8b1eec58f5bc1"},
1324 | ]
1325 | pytest-mypy = [
1326 | {file = "pytest-mypy-0.8.1.tar.gz", hash = "sha256:1fa55723a4bf1d054fcba1c3bd694215a2a65cc95ab10164f5808afd893f3b11"},
1327 | {file = "pytest_mypy-0.8.1-py3-none-any.whl", hash = "sha256:6e68e8eb7ceeb7d1c83a1590912f784879f037b51adfb9c17b95c6b2fc57466b"},
1328 | ]
1329 | python-dateutil = [
1330 | {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
1331 | {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
1332 | ]
1333 | pyyaml = [
1334 | {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
1335 | {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
1336 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"},
1337 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"},
1338 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"},
1339 | {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"},
1340 | {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"},
1341 | {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"},
1342 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"},
1343 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"},
1344 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"},
1345 | {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"},
1346 | {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"},
1347 | {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"},
1348 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"},
1349 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"},
1350 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"},
1351 | {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"},
1352 | {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"},
1353 | {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"},
1354 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"},
1355 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"},
1356 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"},
1357 | {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"},
1358 | {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"},
1359 | {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"},
1360 | {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"},
1361 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"},
1362 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"},
1363 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"},
1364 | {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"},
1365 | {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
1366 | {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
1367 | ]
1368 | pyyaml-env-tag = [
1369 | {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"},
1370 | {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"},
1371 | ]
1372 | regex = [
1373 | {file = "regex-2021.10.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:094a905e87a4171508c2a0e10217795f83c636ccc05ddf86e7272c26e14056ae"},
1374 | {file = "regex-2021.10.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:981c786293a3115bc14c103086ae54e5ee50ca57f4c02ce7cf1b60318d1e8072"},
1375 | {file = "regex-2021.10.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b0f2f874c6a157c91708ac352470cb3bef8e8814f5325e3c5c7a0533064c6a24"},
1376 | {file = "regex-2021.10.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51feefd58ac38eb91a21921b047da8644155e5678e9066af7bcb30ee0dca7361"},
1377 | {file = "regex-2021.10.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea8de658d7db5987b11097445f2b1f134400e2232cb40e614e5f7b6f5428710e"},
1378 | {file = "regex-2021.10.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1ce02f420a7ec3b2480fe6746d756530f69769292eca363218c2291d0b116a01"},
1379 | {file = "regex-2021.10.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:39079ebf54156be6e6902f5c70c078f453350616cfe7bfd2dd15bdb3eac20ccc"},
1380 | {file = "regex-2021.10.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ff24897f6b2001c38a805d53b6ae72267025878d35ea225aa24675fbff2dba7f"},
1381 | {file = "regex-2021.10.8-cp310-cp310-win32.whl", hash = "sha256:c6569ba7b948c3d61d27f04e2b08ebee24fec9ff8e9ea154d8d1e975b175bfa7"},
1382 | {file = "regex-2021.10.8-cp310-cp310-win_amd64.whl", hash = "sha256:45cb0f7ff782ef51bc79e227a87e4e8f24bc68192f8de4f18aae60b1d60bc152"},
1383 | {file = "regex-2021.10.8-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fab3ab8aedfb443abb36729410403f0fe7f60ad860c19a979d47fb3eb98ef820"},
1384 | {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74e55f8d66f1b41d44bc44c891bcf2c7fad252f8f323ee86fba99d71fd1ad5e3"},
1385 | {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d52c5e089edbdb6083391faffbe70329b804652a53c2fdca3533e99ab0580d9"},
1386 | {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1abbd95cbe9e2467cac65c77b6abd9223df717c7ae91a628502de67c73bf6838"},
1387 | {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9b5c215f3870aa9b011c00daeb7be7e1ae4ecd628e9beb6d7e6107e07d81287"},
1388 | {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f540f153c4f5617bc4ba6433534f8916d96366a08797cbbe4132c37b70403e92"},
1389 | {file = "regex-2021.10.8-cp36-cp36m-win32.whl", hash = "sha256:1f51926db492440e66c89cd2be042f2396cf91e5b05383acd7372b8cb7da373f"},
1390 | {file = "regex-2021.10.8-cp36-cp36m-win_amd64.whl", hash = "sha256:5f55c4804797ef7381518e683249310f7f9646da271b71cb6b3552416c7894ee"},
1391 | {file = "regex-2021.10.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb2baff66b7d2267e07ef71e17d01283b55b3cc51a81b54cc385e721ae172ba4"},
1392 | {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e527ab1c4c7cf2643d93406c04e1d289a9d12966529381ce8163c4d2abe4faf"},
1393 | {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c98b013273e9da5790ff6002ab326e3f81072b4616fd95f06c8fa733d2745f"},
1394 | {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:55ef044899706c10bc0aa052f2fc2e58551e2510694d6aae13f37c50f3f6ff61"},
1395 | {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa0ab3530a279a3b7f50f852f1bab41bc304f098350b03e30a3876b7dd89840e"},
1396 | {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a37305eb3199d8f0d8125ec2fb143ba94ff6d6d92554c4b8d4a8435795a6eccd"},
1397 | {file = "regex-2021.10.8-cp37-cp37m-win32.whl", hash = "sha256:2efd47704bbb016136fe34dfb74c805b1ef5c7313aef3ce6dcb5ff844299f432"},
1398 | {file = "regex-2021.10.8-cp37-cp37m-win_amd64.whl", hash = "sha256:924079d5590979c0e961681507eb1773a142553564ccae18d36f1de7324e71ca"},
1399 | {file = "regex-2021.10.8-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:19b8f6d23b2dc93e8e1e7e288d3010e58fafed323474cf7f27ab9451635136d9"},
1400 | {file = "regex-2021.10.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b09d3904bf312d11308d9a2867427479d277365b1617e48ad09696fa7dfcdf59"},
1401 | {file = "regex-2021.10.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:951be934dc25d8779d92b530e922de44dda3c82a509cdb5d619f3a0b1491fafa"},
1402 | {file = "regex-2021.10.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f125fce0a0ae4fd5c3388d369d7a7d78f185f904c90dd235f7ecf8fe13fa741"},
1403 | {file = "regex-2021.10.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f199419a81c1016e0560c39773c12f0bd924c37715bffc64b97140d2c314354"},
1404 | {file = "regex-2021.10.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:09e1031e2059abd91177c302da392a7b6859ceda038be9e015b522a182c89e4f"},
1405 | {file = "regex-2021.10.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c070d5895ac6aeb665bd3cd79f673775caf8d33a0b569e98ac434617ecea57d"},
1406 | {file = "regex-2021.10.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:176796cb7f82a7098b0c436d6daac82f57b9101bb17b8e8119c36eecf06a60a3"},
1407 | {file = "regex-2021.10.8-cp38-cp38-win32.whl", hash = "sha256:5e5796d2f36d3c48875514c5cd9e4325a1ca172fc6c78b469faa8ddd3d770593"},
1408 | {file = "regex-2021.10.8-cp38-cp38-win_amd64.whl", hash = "sha256:e4204708fa116dd03436a337e8e84261bc8051d058221ec63535c9403a1582a1"},
1409 | {file = "regex-2021.10.8-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6dcf53d35850ce938b4f044a43b33015ebde292840cef3af2c8eb4c860730fff"},
1410 | {file = "regex-2021.10.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b8b6ee6555b6fbae578f1468b3f685cdfe7940a65675611365a7ea1f8d724991"},
1411 | {file = "regex-2021.10.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e2ec1c106d3f754444abf63b31e5c4f9b5d272272a491fa4320475aba9e8157c"},
1412 | {file = "regex-2021.10.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:973499dac63625a5ef9dfa4c791aa33a502ddb7615d992bdc89cf2cc2285daa3"},
1413 | {file = "regex-2021.10.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88dc3c1acd3f0ecfde5f95c32fcb9beda709dbdf5012acdcf66acbc4794468eb"},
1414 | {file = "regex-2021.10.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4786dae85c1f0624ac77cb3813ed99267c9adb72e59fdc7297e1cf4d6036d493"},
1415 | {file = "regex-2021.10.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe6ce4f3d3c48f9f402da1ceb571548133d3322003ce01b20d960a82251695d2"},
1416 | {file = "regex-2021.10.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9e3e2cea8f1993f476a6833ef157f5d9e8c75a59a8d8b0395a9a6887a097243b"},
1417 | {file = "regex-2021.10.8-cp39-cp39-win32.whl", hash = "sha256:82cfb97a36b1a53de32b642482c6c46b6ce80803854445e19bc49993655ebf3b"},
1418 | {file = "regex-2021.10.8-cp39-cp39-win_amd64.whl", hash = "sha256:b04e512eb628ea82ed86eb31c0f7fc6842b46bf2601b66b1356a7008327f7700"},
1419 | {file = "regex-2021.10.8.tar.gz", hash = "sha256:26895d7c9bbda5c52b3635ce5991caa90fbb1ddfac9c9ff1c7ce505e2282fb2a"},
1420 | ]
1421 | restructuredtext-lint = [
1422 | {file = "restructuredtext_lint-1.3.2.tar.gz", hash = "sha256:d3b10a1fe2ecac537e51ae6d151b223b78de9fafdd50e5eb6b08c243df173c80"},
1423 | ]
1424 | six = [
1425 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
1426 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
1427 | ]
1428 | smmap = [
1429 | {file = "smmap-4.0.0-py2.py3-none-any.whl", hash = "sha256:a9a7479e4c572e2e775c404dcd3080c8dc49f39918c2cf74913d30c4c478e3c2"},
1430 | {file = "smmap-4.0.0.tar.gz", hash = "sha256:7e65386bd122d45405ddf795637b7f7d2b532e7e401d46bbe3fb49b9986d5182"},
1431 | ]
1432 | snowballstemmer = [
1433 | {file = "snowballstemmer-2.1.0-py2.py3-none-any.whl", hash = "sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2"},
1434 | {file = "snowballstemmer-2.1.0.tar.gz", hash = "sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914"},
1435 | ]
1436 | sortedcontainers = [
1437 | {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"},
1438 | {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"},
1439 | ]
1440 | stevedore = [
1441 | {file = "stevedore-3.4.0-py3-none-any.whl", hash = "sha256:920ce6259f0b2498aaa4545989536a27e4e4607b8318802d7ddc3a533d3d069e"},
1442 | {file = "stevedore-3.4.0.tar.gz", hash = "sha256:59b58edb7f57b11897f150475e7bc0c39c5381f0b8e3fa9f5c20ce6c89ec4aa1"},
1443 | ]
1444 | sumtypes = [
1445 | {file = "sumtypes-0.1a5-py2.py3-none-any.whl", hash = "sha256:148b45ba70b8b82c043e1a9346009b32cf13e03a1083919edf4fd76cb952a8af"},
1446 | {file = "sumtypes-0.1a5.tar.gz", hash = "sha256:bb57fa40a341fc9204ba7f03f181a5d3c0ab765f4ee517a6ba96d8311f4713e7"},
1447 | ]
1448 | testfixtures = [
1449 | {file = "testfixtures-6.18.3-py2.py3-none-any.whl", hash = "sha256:6ddb7f56a123e1a9339f130a200359092bd0a6455e31838d6c477e8729bb7763"},
1450 | {file = "testfixtures-6.18.3.tar.gz", hash = "sha256:2600100ae96ffd082334b378e355550fef8b4a529a6fa4c34f47130905c7426d"},
1451 | ]
1452 | toml = [
1453 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
1454 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
1455 | ]
1456 | tomli = [
1457 | {file = "tomli-1.2.1-py3-none-any.whl", hash = "sha256:8dd0e9524d6f386271a36b41dbf6c57d8e32fd96fd22b6584679dc569d20899f"},
1458 | {file = "tomli-1.2.1.tar.gz", hash = "sha256:a5b75cb6f3968abb47af1b40c1819dc519ea82bcc065776a866e8d74c5ca9442"},
1459 | ]
1460 | typing-extensions = [
1461 | {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"},
1462 | {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"},
1463 | {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"},
1464 | ]
1465 | watchdog = [
1466 | {file = "watchdog-2.1.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9693f35162dc6208d10b10ddf0458cc09ad70c30ba689d9206e02cd836ce28a3"},
1467 | {file = "watchdog-2.1.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:aba5c812f8ee8a3ff3be51887ca2d55fb8e268439ed44110d3846e4229eb0e8b"},
1468 | {file = "watchdog-2.1.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4ae38bf8ba6f39d5b83f78661273216e7db5b00f08be7592062cb1fc8b8ba542"},
1469 | {file = "watchdog-2.1.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ad6f1796e37db2223d2a3f302f586f74c72c630b48a9872c1e7ae8e92e0ab669"},
1470 | {file = "watchdog-2.1.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:922a69fa533cb0c793b483becaaa0845f655151e7256ec73630a1b2e9ebcb660"},
1471 | {file = "watchdog-2.1.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b2fcf9402fde2672545b139694284dc3b665fd1be660d73eca6805197ef776a3"},
1472 | {file = "watchdog-2.1.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3386b367e950a11b0568062b70cc026c6f645428a698d33d39e013aaeda4cc04"},
1473 | {file = "watchdog-2.1.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f1c00aa35f504197561060ca4c21d3cc079ba29cf6dd2fe61024c70160c990b"},
1474 | {file = "watchdog-2.1.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b52b88021b9541a60531142b0a451baca08d28b74a723d0c99b13c8c8d48d604"},
1475 | {file = "watchdog-2.1.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8047da932432aa32c515ec1447ea79ce578d0559362ca3605f8e9568f844e3c6"},
1476 | {file = "watchdog-2.1.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e92c2d33858c8f560671b448205a268096e17870dcf60a9bb3ac7bfbafb7f5f9"},
1477 | {file = "watchdog-2.1.6-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b7d336912853d7b77f9b2c24eeed6a5065d0a0cc0d3b6a5a45ad6d1d05fb8cd8"},
1478 | {file = "watchdog-2.1.6-py3-none-manylinux2014_aarch64.whl", hash = "sha256:cca7741c0fcc765568350cb139e92b7f9f3c9a08c4f32591d18ab0a6ac9e71b6"},
1479 | {file = "watchdog-2.1.6-py3-none-manylinux2014_armv7l.whl", hash = "sha256:25fb5240b195d17de949588628fdf93032ebf163524ef08933db0ea1f99bd685"},
1480 | {file = "watchdog-2.1.6-py3-none-manylinux2014_i686.whl", hash = "sha256:be9be735f827820a06340dff2ddea1fb7234561fa5e6300a62fe7f54d40546a0"},
1481 | {file = "watchdog-2.1.6-py3-none-manylinux2014_ppc64.whl", hash = "sha256:d0d19fb2441947b58fbf91336638c2b9f4cc98e05e1045404d7a4cb7cddc7a65"},
1482 | {file = "watchdog-2.1.6-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:3becdb380d8916c873ad512f1701f8a92ce79ec6978ffde92919fd18d41da7fb"},
1483 | {file = "watchdog-2.1.6-py3-none-manylinux2014_s390x.whl", hash = "sha256:ae67501c95606072aafa865b6ed47343ac6484472a2f95490ba151f6347acfc2"},
1484 | {file = "watchdog-2.1.6-py3-none-manylinux2014_x86_64.whl", hash = "sha256:e0f30db709c939cabf64a6dc5babb276e6d823fd84464ab916f9b9ba5623ca15"},
1485 | {file = "watchdog-2.1.6-py3-none-win32.whl", hash = "sha256:e02794ac791662a5eafc6ffeaf9bcc149035a0e48eb0a9d40a8feb4622605a3d"},
1486 | {file = "watchdog-2.1.6-py3-none-win_amd64.whl", hash = "sha256:bd9ba4f332cf57b2c1f698be0728c020399ef3040577cde2939f2e045b39c1e5"},
1487 | {file = "watchdog-2.1.6-py3-none-win_ia64.whl", hash = "sha256:a0f1c7edf116a12f7245be06120b1852275f9506a7d90227648b250755a03923"},
1488 | {file = "watchdog-2.1.6.tar.gz", hash = "sha256:a36e75df6c767cbf46f61a91c70b3ba71811dfa0aca4a324d9407a06a8b7a2e7"},
1489 | ]
1490 | wcwidth = [
1491 | {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
1492 | {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"},
1493 | ]
1494 | wemake-python-styleguide = [
1495 | {file = "wemake-python-styleguide-0.15.3.tar.gz", hash = "sha256:8b89aedabae67b7b915908ed06c178b702068137c0d8afe1fb59cdc829cd2143"},
1496 | {file = "wemake_python_styleguide-0.15.3-py3-none-any.whl", hash = "sha256:a382f6c9ec87d56daa08a11e47cab019c99b384f1393b32564ebc74c6da80441"},
1497 | ]
1498 | zipp = [
1499 | {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"},
1500 | {file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"},
1501 | ]
1502 |
--------------------------------------------------------------------------------
/poetry.toml:
--------------------------------------------------------------------------------
1 | [virtualenvs]
2 | in-project = true
3 |
4 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "borsh-construct"
3 | version = "0.1.0"
4 | description = "Python implementation of Borsh serialization, built on the Construct library."
5 | authors = ["kevinheavey "]
6 | license = "MIT"
7 | homepage = "https://github.com/near/borsh-construct-py"
8 | repository = "https://github.com/near/borsh-construct-py"
9 | readme = "README.md"
10 |
11 |
12 | [tool.poetry.dependencies]
13 | python = "^3.8.3"
14 | construct-typing = "^0.5.1"
15 | sumtypes = "^0.1a5"
16 |
17 | [tool.poetry.dev-dependencies]
18 | pytest = "^5.2"
19 | wemake-python-styleguide = "^0.15.3"
20 | mypy = "^0.910"
21 | black = "^21.7b0"
22 | pydocstyle = "^6.1.1"
23 | isort = "^5.9.3"
24 | coverage = {extras = ["toml"], version = "^5.5"}
25 | pytest-cov = "^2.12.1"
26 | mkdocs = "^1.2.2"
27 | mkdocs-material = "^7.2.6"
28 | flake8 = "^3.9.2"
29 | pytest-flake8 = "^1.0.7"
30 | pytest-mypy = "^0.8.1"
31 | hypothesis = "^6.23.0"
32 |
33 | [build-system]
34 | requires = ["poetry-core>=1.0.0"]
35 | build-backend = "poetry.core.masonry.api"
36 |
37 | [tool.coverage.paths]
38 | source = ["src", "*/site-packages"]
39 |
40 | [tool.coverage.run]
41 | branch = true
42 | source = ["borsh_construct"]
43 |
44 | [tool.coverage.report]
45 | show_missing = true
46 | fail_under = 100
47 |
--------------------------------------------------------------------------------
/pytest.ini:
--------------------------------------------------------------------------------
1 | [pytest]
2 | addopts = --cov --doctest-glob="*.md" --flake8 --mypy
3 |
--------------------------------------------------------------------------------
/src/borsh_construct/__init__.py:
--------------------------------------------------------------------------------
1 | from importlib.metadata import version, PackageNotFoundError
2 | from construct import Flag as Bool
3 | from construct import Int8sl as I8
4 | from construct import Int16sl as I16
5 | from construct import Int16ul as U16
6 | from construct import Int32sl as I32
7 | from construct import Int64sl as I64
8 | from construct import Int64ul as U64
9 |
10 | from .core import (
11 | F32,
12 | F64,
13 | I128,
14 | U8,
15 | U32,
16 | U128,
17 | Vec,
18 | CStruct,
19 | TupleStruct,
20 | Bytes,
21 | String,
22 | Option,
23 | HashMap,
24 | HashSet,
25 | )
26 | from .enum import Enum
27 |
28 | try:
29 | __version__ = version(__name__)
30 | except PackageNotFoundError: # pragma: no cover
31 | __version__ = "unknown"
32 |
33 | __all__ = [
34 | "I8",
35 | "I16",
36 | "I32",
37 | "U8",
38 | "I64",
39 | "I128",
40 | "U16",
41 | "U32",
42 | "U64",
43 | "U128",
44 | "F32",
45 | "F64",
46 | "Bool",
47 | "Vec",
48 | "CStruct",
49 | "TupleStruct",
50 | "Bytes",
51 | "String",
52 | "Enum",
53 | "Option",
54 | "HashMap",
55 | "HashSet",
56 | ]
57 |
--------------------------------------------------------------------------------
/src/borsh_construct/core.py:
--------------------------------------------------------------------------------
1 | from typing import Any, Optional, List, Tuple
2 | from math import isnan
3 | from construct import Adapter, Array, BytesInteger, Construct
4 | from construct import singleton # type: ignore
5 | from construct import FormatField, FormatFieldError, GreedyBytes, IfThenElse
6 | from construct import Int8ul as U8
7 | from construct import Int32ul as U32
8 | from construct import Pass, Prefixed, PrefixedArray
9 | from construct import Sequence
10 | from construct import Struct
11 |
12 | TUPLE_DATA = "tuple_data"
13 |
14 | NAMED_TUPLE_FIELD_ERROR = ValueError("TupleStruct cannot have named fields")
15 | UNNAMED_SUBCON_ERROR = ValueError("CStruct fields and enum variants must be named")
16 | NON_STR_NAME_ERROR = ValueError("Names must be strings.")
17 | TUPLE_DATA_NAME_ERROR = ValueError(
18 | f"The name {TUPLE_DATA} is reserved. If you encountered this "
19 | "error it's either a wild coincidence or you're "
20 | "doing it wrong." # noqa: C812
21 | )
22 | UNDERSCORE_NAME_ERROR = ValueError("names cannot start with an underscore.")
23 | U128 = BytesInteger(16, signed=False, swapped=True)
24 | I128 = BytesInteger(16, signed=True, swapped=True)
25 |
26 |
27 | class TupleStruct(Sequence):
28 | """Python implementation of Rust tuple struct."""
29 |
30 | def __init__(self, *subcons) -> None:
31 | super().__init__(*subcons) # type: ignore
32 | for subcon in self.subcons:
33 | if subcon.name is not None:
34 | raise NAMED_TUPLE_FIELD_ERROR
35 |
36 |
37 | class CStruct(Struct):
38 | """Python implementation of Rust C-like struct."""
39 |
40 | def __init__(self, *subcons) -> None:
41 | super().__init__(*subcons)
42 | for subcon in subcons:
43 | check_subcon_name(subcon.name)
44 |
45 |
46 | def _check_name_not_null(name: Optional[str]) -> None:
47 | if name is None:
48 | raise UNNAMED_SUBCON_ERROR
49 |
50 |
51 | def check_subcon_name(name: Optional[str]) -> None:
52 | """Check that CStructs and Enums have valid names.""" # noqa: DAR101
53 | _check_name_not_null(name)
54 | if not isinstance(name, str):
55 | raise NON_STR_NAME_ERROR
56 | if name == TUPLE_DATA:
57 | raise TUPLE_DATA_NAME_ERROR
58 | if name[0] == "_":
59 | raise UNDERSCORE_NAME_ERROR
60 |
61 |
62 | class FormatFieldNoNan(FormatField):
63 | """Adapted form of `construct.FormatField` that forbids nan."""
64 |
65 | def _parse(self, stream, context, path):
66 | result = super()._parse(stream, context, path)
67 | if isnan(result):
68 | raise FormatFieldError("Borsh does not support nan.")
69 | return result
70 |
71 | def _build(self, obj, stream, context, path):
72 | if isnan(obj):
73 | raise FormatFieldError("Borsh does not support nan.")
74 | return super()._build(obj, stream, context, path)
75 |
76 |
77 | @singleton
78 | def F32() -> FormatFieldNoNan: # noqa: N802
79 | """Little endian, 32-bit IEEE floating point number."""
80 | return FormatFieldNoNan("<", "f")
81 |
82 |
83 | @singleton
84 | def F64() -> FormatFieldNoNan: # noqa: N802
85 | """Little endian, 64-bit IEEE floating point number."""
86 | return FormatFieldNoNan("<", "d")
87 |
88 |
89 | def Vec(subcon: Construct) -> Array: # noqa: N802
90 | """Dynamic sized array.
91 |
92 | Args:
93 | subcon (Construct): the type of the array members.
94 |
95 | Returns:
96 | Array: a Construct PrefixedArray.
97 | """
98 | return PrefixedArray(U32, subcon)
99 |
100 |
101 | Bytes = Prefixed(U32, GreedyBytes)
102 |
103 |
104 | class _String(Adapter):
105 | def __init__(self) -> None:
106 | super().__init__(Bytes) # type: ignore
107 |
108 | def _decode(self, obj: bytes, context, path) -> str:
109 | return obj.decode("utf8")
110 |
111 | def _encode(self, obj: str, context, path) -> bytes:
112 | return bytes(obj, "utf8")
113 |
114 |
115 | String = _String()
116 |
117 |
118 | class Option(Adapter):
119 | """Borsh implementation for Rust's Option type."""
120 |
121 | _discriminator_key = "discriminator"
122 | _value_key = "value"
123 |
124 | def __init__(self, subcon: Construct) -> None:
125 | option_struct = CStruct(
126 | self._discriminator_key / U8,
127 | self._value_key
128 | / IfThenElse(lambda this: this[self._discriminator_key] == 0, Pass, subcon),
129 | )
130 | super().__init__(option_struct) # type: ignore
131 |
132 | def _decode(self, obj, context, path) -> Any:
133 | return obj[self._value_key]
134 |
135 | def _encode(self, obj, context, path) -> dict:
136 | discriminator = 0 if obj is None else 1
137 | return {self._discriminator_key: discriminator, self._value_key: obj}
138 |
139 |
140 | class HashMap(Adapter):
141 | """Borsh implementation for Rust HashMap."""
142 |
143 | def __init__(self, key_subcon: Construct, value_subcon: Construct) -> None:
144 | super().__init__(
145 | PrefixedArray(U32, TupleStruct(key_subcon, value_subcon)),
146 | ) # type: ignore
147 |
148 | def _decode(self, obj: List[Tuple[Any, Any]], context, path) -> dict:
149 | return dict(obj)
150 |
151 | def _encode(self, obj, context, path) -> List[Tuple]:
152 | return sorted(obj.items())
153 |
154 |
155 | class HashSet(Adapter):
156 | """Python implementation of Rust HashSet."""
157 |
158 | def __init__(self, subcon: Construct) -> None:
159 | super().__init__(PrefixedArray(U32, subcon)) # type: ignore
160 |
161 | def _decode(self, obj, context, path) -> set:
162 | return set(obj)
163 |
164 | def _encode(self, obj, context, path) -> list:
165 | return sorted(obj)
166 |
--------------------------------------------------------------------------------
/src/borsh_construct/enum.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 | from typing import List, Tuple, Union, cast, Any, Dict
3 | from sumtypes import sumtype, constructor
4 | from construct import Pass, Renamed, Adapter, Switch, Container, Construct
5 | import attr
6 |
7 | from .core import CStruct, TupleStruct, U8, TUPLE_DATA, check_subcon_name
8 |
9 |
10 | def _rust_enum(klass):
11 | indexed = sumtype(klass)
12 | for idx, cname in enumerate(indexed._sumtype_constructor_names): # noqa: WPS437
13 | constructr = getattr(indexed, cname)
14 | constructr.index = idx
15 |
16 | # __getitem__ magic method cannot be classmethod
17 | @classmethod
18 | def getitem(cls, _index: int): # noqa: WPS614
19 | return getattr(cls, cls._sumtype_constructor_names[_index])
20 |
21 | indexed.getitem = getitem
22 |
23 | return indexed
24 |
25 |
26 | def _tuple_struct():
27 | return constructor(**{TUPLE_DATA: attr.ib(type=tuple)})
28 |
29 |
30 | def _unit_struct():
31 | return constructor()
32 |
33 |
34 | def _clike_struct(*fields: str):
35 | return constructor(*fields)
36 |
37 |
38 | def _handle_cstruct_variant(underlying_variant, variant_name) -> Tuple[str, Any]:
39 | subcon_names: List[str] = []
40 | for s in underlying_variant.subcons:
41 | name = s.name
42 | subcon_names.append(cast(str, name))
43 | return variant_name, _clike_struct(*subcon_names)
44 |
45 |
46 | def _handle_struct_variant(variant) -> Tuple[str, Any]:
47 | variant_name = variant.name
48 | check_subcon_name(variant_name)
49 | underlying_variant = variant.subcon if isinstance(variant, Renamed) else variant
50 | if isinstance(underlying_variant, TupleStruct):
51 | return variant_name, _tuple_struct()
52 | elif isinstance(underlying_variant, CStruct):
53 | return _handle_cstruct_variant(underlying_variant, variant_name)
54 | variant_type = type(underlying_variant)
55 | raise ValueError(f"Unrecognized variant type: {variant_type}")
56 |
57 |
58 | def _make_cls_dict(*variants) -> dict: # noqa: WPS210
59 | result = {"__doc__": "Python representation of Rust's Enum type."}
60 | seen_variant_names = set()
61 | for variant in variants:
62 | if isinstance(variant, str):
63 | variant_name = variant
64 | result[variant] = _unit_struct()
65 | else:
66 | variant_name = variant.name
67 | key, val = _handle_struct_variant(variant)
68 | result[key] = val
69 | if variant_name in seen_variant_names:
70 | raise ValueError("Enum variant names must be unique.")
71 | seen_variant_names.add(variant_name)
72 | return result
73 |
74 |
75 | def _make_enum(*variants, name: str):
76 | klass = type(name, (object,), _make_cls_dict(*variants))
77 | return _rust_enum(klass)
78 |
79 |
80 | class Enum(Adapter):
81 | """Borsh representation of Rust's enum type."""
82 |
83 | _index_key = "index"
84 | _value_key = "value"
85 |
86 | def __init__(self, *variants: Union[str, Construct], enum_name: str) -> None:
87 | """Init enum.
88 |
89 | Note: unlike other types, you must use the `enum_name` keyword argument
90 | to give your Enum a name when instantiating it.
91 | """ # noqa: DAR101
92 | switch_cases = {}
93 | for idx, var in enumerate(variants):
94 | if isinstance(var, str):
95 | parser = Pass
96 | else:
97 | parser = var
98 | switch_cases[idx] = parser
99 | enum_struct = CStruct(
100 | self._index_key / U8,
101 | self._value_key / Switch(lambda this: this.index, switch_cases),
102 | )
103 | super().__init__(enum_struct) # type: ignore
104 | self.variants = variants
105 | self.enum_name = enum_name
106 | self.enum = _make_enum(*variants, name=enum_name)
107 |
108 | def _decode(self, obj: Any, context, path) -> Any:
109 | index = obj.index
110 | enum_variant = self.enum.getitem(index)
111 | val = obj.value
112 | if val is None:
113 | return enum_variant()
114 | if isinstance(val, Container):
115 | return enum_variant(**{k: v for k, v in val.items() if k != "_io"})
116 | return enum_variant(val)
117 |
118 | def _encode(self, obj: Any, context, path) -> Dict[str, Any]:
119 | index = obj.index
120 | as_dict = attr.asdict(obj)
121 | if as_dict:
122 | try:
123 | to_build = as_dict[TUPLE_DATA]
124 | except KeyError:
125 | to_build = as_dict
126 | else:
127 | to_build = None
128 | return {self._index_key: index, self._value_key: to_build}
129 |
--------------------------------------------------------------------------------
/src/borsh_construct/py.typed:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/near/borsh-construct-py/e49dee71716ec217e8c9966aaa621c61669f7c15/src/borsh_construct/py.typed
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 | """Unit tests."""
2 |
--------------------------------------------------------------------------------
/tests/conftest.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 |
4 | def pytest_configure(config):
5 | """Flake8 is very verbose by default. Silence it.""" # noqa: DAR101
6 | logging.getLogger("flake8").setLevel(logging.ERROR)
7 |
--------------------------------------------------------------------------------
/tests/test_core.py:
--------------------------------------------------------------------------------
1 | """Core tests."""
2 | from typing import Any
3 |
4 | import pytest
5 | from borsh_construct import (
6 | F32,
7 | F64,
8 | I8,
9 | I16,
10 | I32,
11 | I64,
12 | I128,
13 | U8,
14 | U16,
15 | U32,
16 | U64,
17 | U128,
18 | Bool,
19 | Vec,
20 | CStruct,
21 | TupleStruct,
22 | Enum,
23 | String,
24 | Option,
25 | HashMap,
26 | HashSet,
27 | Bytes,
28 | )
29 | from borsh_construct.core import (
30 | NAMED_TUPLE_FIELD_ERROR,
31 | TUPLE_DATA,
32 | UNNAMED_SUBCON_ERROR,
33 | NON_STR_NAME_ERROR,
34 | UNDERSCORE_NAME_ERROR,
35 | TUPLE_DATA_NAME_ERROR,
36 | )
37 | from construct import Construct, Float32l, Float64l, FormatField, FormatFieldError
38 |
39 | ENUM = Enum(
40 | "Unit",
41 | "TupleVariant" / TupleStruct(U128, String, I64, Option(U16)),
42 | "CStructVariant"
43 | / CStruct("u128_field" / U128, "string_field" / String, "vec_field" / Vec(U16)),
44 | enum_name="Placeholder",
45 | )
46 |
47 | TYPE_INPUT_EXPECTED = (
48 | (Bool, True, [1]),
49 | (Bool, False, [0]),
50 | (U8, 255, [255]),
51 | (I8, -128, [128]),
52 | (U16, 65535, [255, 255]),
53 | (I16, -32768, [0, 128]),
54 | (U32, 4294967295, [255, 255, 255, 255]),
55 | (I32, -2147483648, [0, 0, 0, 128]),
56 | (U64, 18446744073709551615, [255, 255, 255, 255, 255, 255, 255, 255]),
57 | (I64, -9223372036854775808, [0, 0, 0, 0, 0, 0, 0, 128]),
58 | (
59 | U128,
60 | 340282366920938463463374607431768211455,
61 | [
62 | 255,
63 | 255,
64 | 255,
65 | 255,
66 | 255,
67 | 255,
68 | 255,
69 | 255,
70 | 255,
71 | 255,
72 | 255,
73 | 255,
74 | 255,
75 | 255,
76 | 255,
77 | 255,
78 | ],
79 | ),
80 | (
81 | I128,
82 | -170141183460469231731687303715884105728,
83 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128],
84 | ),
85 | (F32, 0.5, [0, 0, 0, 63]),
86 | (F64, -0.5, [0, 0, 0, 0, 0, 0, 224, 191]),
87 | (I16[3], [1, 2, 3], [1, 0, 2, 0, 3, 0]),
88 | (Vec(I16), [1, 1], [2, 0, 0, 0, 1, 0, 1, 0]),
89 | (
90 | TupleStruct(U128, String, I64, Option(U16)),
91 | [123, "hello", 1400, 13],
92 | [
93 | 123,
94 | 0,
95 | 0,
96 | 0,
97 | 0,
98 | 0,
99 | 0,
100 | 0,
101 | 0,
102 | 0,
103 | 0,
104 | 0,
105 | 0,
106 | 0,
107 | 0,
108 | 0,
109 | 5,
110 | 0,
111 | 0,
112 | 0,
113 | 104,
114 | 101,
115 | 108,
116 | 108,
117 | 111,
118 | 120,
119 | 5,
120 | 0,
121 | 0,
122 | 0,
123 | 0,
124 | 0,
125 | 0,
126 | 1,
127 | 13,
128 | 0,
129 | ],
130 | ),
131 | (
132 | CStruct("u128_field" / U128, "string_field" / String, "vec_field" / Vec(U16)),
133 | {"u128_field": 1033, "string_field": "hello", "vec_field": [1, 2, 3]},
134 | [
135 | 9,
136 | 4,
137 | 0,
138 | 0,
139 | 0,
140 | 0,
141 | 0,
142 | 0,
143 | 0,
144 | 0,
145 | 0,
146 | 0,
147 | 0,
148 | 0,
149 | 0,
150 | 0,
151 | 5,
152 | 0,
153 | 0,
154 | 0,
155 | 104,
156 | 101,
157 | 108,
158 | 108,
159 | 111,
160 | 3,
161 | 0,
162 | 0,
163 | 0,
164 | 1,
165 | 0,
166 | 2,
167 | 0,
168 | 3,
169 | 0,
170 | ],
171 | ),
172 | (ENUM, ENUM.enum.Unit(), [0]),
173 | (
174 | ENUM,
175 | ENUM.enum.TupleVariant([10, "hello", 13, 12]),
176 | [
177 | 1,
178 | 10,
179 | 0,
180 | 0,
181 | 0,
182 | 0,
183 | 0,
184 | 0,
185 | 0,
186 | 0,
187 | 0,
188 | 0,
189 | 0,
190 | 0,
191 | 0,
192 | 0,
193 | 0,
194 | 5,
195 | 0,
196 | 0,
197 | 0,
198 | 104,
199 | 101,
200 | 108,
201 | 108,
202 | 111,
203 | 13,
204 | 0,
205 | 0,
206 | 0,
207 | 0,
208 | 0,
209 | 0,
210 | 0,
211 | 1,
212 | 12,
213 | 0,
214 | ],
215 | ),
216 | (
217 | ENUM,
218 | ENUM.enum.CStructVariant(
219 | u128_field=15,
220 | string_field="hi",
221 | vec_field=[3, 2, 1],
222 | ),
223 | [
224 | 2,
225 | 15,
226 | 0,
227 | 0,
228 | 0,
229 | 0,
230 | 0,
231 | 0,
232 | 0,
233 | 0,
234 | 0,
235 | 0,
236 | 0,
237 | 0,
238 | 0,
239 | 0,
240 | 0,
241 | 2,
242 | 0,
243 | 0,
244 | 0,
245 | 104,
246 | 105,
247 | 3,
248 | 0,
249 | 0,
250 | 0,
251 | 3,
252 | 0,
253 | 2,
254 | 0,
255 | 1,
256 | 0,
257 | ],
258 | ),
259 | (
260 | HashMap(U8, ENUM),
261 | {2: ENUM.enum.Unit(), 1: ENUM.enum.TupleVariant([11, "hello", 123, None])},
262 | [
263 | 2,
264 | 0,
265 | 0,
266 | 0,
267 | 1,
268 | 1,
269 | 11,
270 | 0,
271 | 0,
272 | 0,
273 | 0,
274 | 0,
275 | 0,
276 | 0,
277 | 0,
278 | 0,
279 | 0,
280 | 0,
281 | 0,
282 | 0,
283 | 0,
284 | 0,
285 | 5,
286 | 0,
287 | 0,
288 | 0,
289 | 104,
290 | 101,
291 | 108,
292 | 108,
293 | 111,
294 | 123,
295 | 0,
296 | 0,
297 | 0,
298 | 0,
299 | 0,
300 | 0,
301 | 0,
302 | 0,
303 | 2,
304 | 0,
305 | ],
306 | ),
307 | (HashSet(U8), {1, 2, 3}, [3, 0, 0, 0, 1, 2, 3]),
308 | (Bytes, b"\x01\x02\x03", [3, 0, 0, 0, 1, 2, 3]),
309 | (
310 | String,
311 | "🚀🚀🚀",
312 | [12, 0, 0, 0, 240, 159, 154, 128, 240, 159, 154, 128, 240, 159, 154, 128],
313 | ),
314 | )
315 |
316 |
317 | @pytest.mark.parametrize("obj_type,obj_input,expected", TYPE_INPUT_EXPECTED)
318 | def test_serde(obj_type: Construct, obj_input: Any, expected: Any) -> None:
319 | """Tests that inputs are serialized and deserialized as expected."""
320 | serialized = obj_type.build(obj_input)
321 | assert list(serialized) == expected
322 | deserialized = obj_type.parse(serialized)
323 | assert deserialized == obj_input
324 |
325 |
326 | @pytest.mark.parametrize(
327 | "nonan_type,construct_type",
328 | [(F32, Float32l), (F64, Float64l)],
329 | )
330 | def test_nan_floats(nonan_type: FormatField, construct_type: FormatField) -> None:
331 | """Check that error is raised if you try to build or parse nan floats."""
332 | nan = float("nan") # noqa: WPS456
333 | with pytest.raises(FormatFieldError):
334 | nonan_type.build(nan)
335 | nan_serialized = construct_type.build(nan)
336 | with pytest.raises(FormatFieldError):
337 | nonan_type.parse(nan_serialized)
338 |
339 |
340 | def test_named_tuple_struct_field_raises() -> None:
341 | """Check that error is raised if TupleStruct field is named."""
342 | with pytest.raises(ValueError) as exc:
343 | TupleStruct("foo" / U8)
344 | assert exc.value == NAMED_TUPLE_FIELD_ERROR
345 |
346 |
347 | def test_unnamed_subcon_raises() -> None:
348 | """Check that error is raised when enum variant or CStruct field is unnamed."""
349 | with pytest.raises(ValueError) as excinfo:
350 | Enum("foo", TupleStruct(U8), enum_name="placeholder")
351 | assert str(excinfo.value) == str(UNNAMED_SUBCON_ERROR)
352 |
353 |
354 | def test_non_str_name_raises() -> None:
355 | """Check that error is raised when subcon name is not a string."""
356 | with pytest.raises(ValueError) as excinfo:
357 | CStruct(1 / U8) # type: ignore
358 | assert str(excinfo.value) == str(NON_STR_NAME_ERROR)
359 |
360 |
361 | def test_tuple_data_name_raises() -> None:
362 | """Check that error is raised when subcon name is not a string."""
363 | with pytest.raises(ValueError) as excinfo:
364 | CStruct(TUPLE_DATA / U8)
365 | assert str(excinfo.value) == str(TUPLE_DATA_NAME_ERROR)
366 |
367 |
368 | def test_underscore_name_raises() -> None:
369 | """Check that error is raised when subcon name starts with underscore."""
370 | with pytest.raises(ValueError) as excinfo:
371 | CStruct("_foo" / U8)
372 | assert str(excinfo.value) == str(UNDERSCORE_NAME_ERROR)
373 |
374 |
375 | def test_unrecognized_variant_type_raises() -> None:
376 | """Check that error is raised if variant type is not valid."""
377 | with pytest.raises(ValueError) as excinfo:
378 | Enum("foo" / U8, enum_name="placeholder")
379 | assert "Unrecognized" in str(excinfo.value)
380 |
381 |
382 | def test_duplicate_variant_name_raises() -> None:
383 | """Check error raised if two variants in same Enum have same name."""
384 | with pytest.raises(ValueError) as excinfo:
385 | Enum("foo", "foo", enum_name="placeholder")
386 | assert "must be unique" in str(excinfo.value)
387 |
--------------------------------------------------------------------------------
/tests/test_hypothesis.py:
--------------------------------------------------------------------------------
1 | from hypothesis import given
2 | import hypothesis.strategies as st
3 |
4 | from borsh_construct import (
5 | U8,
6 | I8,
7 | U16,
8 | I16,
9 | U32,
10 | I32,
11 | U64,
12 | I64,
13 | U128,
14 | I128,
15 | F32,
16 | F64,
17 | String,
18 | Bytes,
19 | HashMap,
20 | HashSet,
21 | Vec,
22 | Option,
23 | )
24 |
25 |
26 | borsh_simple_types = (
27 | U8,
28 | I8,
29 | U16,
30 | I16,
31 | U32,
32 | I32,
33 | U64,
34 | I64,
35 | U128,
36 | I128,
37 | F32,
38 | F64,
39 | Bytes,
40 | String,
41 | )
42 | borsh_compound_types = (
43 | HashMap,
44 | HashSet,
45 | Vec,
46 | Option,
47 | )
48 |
49 |
50 | @given(st.text())
51 | def test_string(s):
52 | """Test string encoding/decoding."""
53 | assert String.parse(String.build(s)) == s
54 |
55 |
56 | u8_ints = st.integers(0, 255)
57 | i8_ints = st.integers(-128, 127)
58 | u16_ints = st.integers(0, 65535)
59 | i16_ints = st.integers(-32768, 32767)
60 | u32_ints = st.integers(0, 4294967295)
61 | i32_ints = st.integers(-2147483648, 2147483647)
62 | u64_ints = st.integers(0, 18446744073709551615)
63 | i64_ints = st.integers(-9223372036854775808, 9223372036854775807)
64 | u128_ints = st.integers(0, 340282366920938463463374607431768211455)
65 | i128_ints = st.integers(
66 | -170141183460469231731687303715884105728,
67 | 170141183460469231731687303715884105727,
68 | )
69 | f32_floats = st.floats(width=32, allow_nan=False)
70 | f64_floats = st.floats(width=64, allow_nan=False)
71 | numeric_strategies = (
72 | u8_ints,
73 | i8_ints,
74 | u16_ints,
75 | i16_ints,
76 | u32_ints,
77 | i32_ints,
78 | u64_ints,
79 | i64_ints,
80 | u128_ints,
81 | i128_ints,
82 | )
83 |
84 | type_map = {
85 | U8: u8_ints,
86 | I8: i8_ints,
87 | U16: u16_ints,
88 | I16: i16_ints,
89 | U32: u32_ints,
90 | I32: i32_ints,
91 | U64: u64_ints,
92 | I64: i64_ints,
93 | U128: u128_ints,
94 | I128: i128_ints,
95 | F32: f32_floats,
96 | F64: f64_floats,
97 | Bytes: st.binary(),
98 | String: st.text(),
99 | }
100 |
101 |
102 | @given(u8_ints)
103 | def test_u8(s):
104 | """Test U8 encoding/decoding."""
105 | assert U8.parse(U8.build(s)) == s
106 |
107 |
108 | @given(i8_ints)
109 | def test_i8(s):
110 | """Test I8 encoding/decoding."""
111 | assert I8.parse(I8.build(s)) == s
112 |
113 |
114 | @given(u16_ints)
115 | def test_u16(s):
116 | """Test I16 encoding/decoding."""
117 | assert U16.parse(U16.build(s)) == s
118 |
119 |
120 | @given(i16_ints)
121 | def test_i16(s):
122 | """Test I16 encoding/decoding."""
123 | assert I16.parse(I16.build(s)) == s
124 |
125 |
126 | @given(u32_ints)
127 | def test_u32(s):
128 | """Test U32 encoding/decoding."""
129 | assert U32.parse(U32.build(s)) == s
130 |
131 |
132 | @given(i32_ints)
133 | def test_i32(s):
134 | """Test I32 encoding/decoding."""
135 | assert I32.parse(I32.build(s)) == s
136 |
137 |
138 | @given(u64_ints)
139 | def test_u64(s):
140 | """Test U64 encoding/decoding."""
141 | assert U64.parse(U64.build(s)) == s
142 |
143 |
144 | @given(i64_ints)
145 | def test_i64(s):
146 | """Test I64 encoding/decoding."""
147 | assert I64.parse(I64.build(s)) == s
148 |
149 |
150 | @given(u128_ints)
151 | def test_u128(s):
152 | """Test U128 encoding/decoding."""
153 | assert U128.parse(U128.build(s)) == s
154 |
155 |
156 | @given(i128_ints)
157 | def test_i128(s):
158 | """Test I128 encoding/decoding."""
159 | assert I128.parse(I128.build(s)) == s
160 |
161 |
162 | @given(f32_floats)
163 | def test_f32(s):
164 | """Test F32 encoding/decoding."""
165 | assert F32.parse(F32.build(s)) == s
166 |
167 |
168 | @given(f64_floats)
169 | def test_f64(s):
170 | """Test F64 encoding/decoding."""
171 | assert F64.parse(F64.build(s)) == s
172 |
173 |
174 | @st.composite
175 | def element_and_borsh_type(draw, elements=st.sampled_from(borsh_simple_types)):
176 | """Return a simple borsh type and a simple example of data fitting that type."""
177 | borsh_type = draw(elements)
178 | data_strategy = type_map[borsh_type]
179 | data = draw(data_strategy)
180 | return data, borsh_type
181 |
182 |
183 | @st.composite
184 | def list_data_and_borsh_type(
185 | draw,
186 | elements=st.sampled_from(borsh_simple_types),
187 | min_length=0,
188 | unique=False,
189 | ):
190 | """Return a list whose elements fit a particular borsh type."""
191 | borsh_type = draw(elements)
192 | data_strategy = type_map[borsh_type]
193 | data = draw(st.lists(data_strategy, min_size=min_length, unique=unique))
194 | return data, borsh_type, len(data)
195 |
196 |
197 | @given(list_data_and_borsh_type()) # type: ignore
198 | def test_vec(data_borsh_type):
199 | """Test Vec encoding."""
200 | data, borsh_type, _ = data_borsh_type
201 | vec_type = Vec(borsh_type)
202 | assert vec_type.parse(vec_type.build(data)) == data
203 |
204 |
205 | @given(list_data_and_borsh_type(min_length=1)) # type: ignore
206 | def test_array(data_borsh_type):
207 | """Test Array encoding."""
208 | data, borsh_type, length = data_borsh_type
209 | array_type = borsh_type[length]
210 | assert array_type.parse(array_type.build(data)) == data
211 |
212 |
213 | @given(element_and_borsh_type()) # type: ignore
214 | def test_option(data_borsh_type):
215 | """Test Option encoding."""
216 | data, borsh_type = data_borsh_type
217 | option_type = Option(borsh_type)
218 | assert option_type.parse(option_type.build(data)) == data
219 |
220 |
221 | @given(list_data_and_borsh_type()) # type: ignore
222 | def test_hashset(data_borsh_type):
223 | """Test HashSet encoding."""
224 | data, borsh_type, _ = data_borsh_type
225 | data = set(data)
226 | set_type = HashSet(borsh_type)
227 | assert set_type.parse(set_type.build(data)) == data
228 |
--------------------------------------------------------------------------------