├── .gitignore
├── LICENSE
├── README.md
├── article.md
└── demo
├── poetry.lock
├── pydantic-2021.ipynb
├── pydantic.ipynb
├── pydantic_more_examples.ipynb
└── pyproject.toml
/.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 | pip-wheel-metadata/
24 | share/python-wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | MANIFEST
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .nox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *.cover
50 | *.py,cover
51 | .hypothesis/
52 | .pytest_cache/
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 | target/
76 |
77 | # Jupyter Notebook
78 | .ipynb_checkpoints
79 |
80 | # IPython
81 | profile_default/
82 | ipython_config.py
83 |
84 | # pyenv
85 | .python-version
86 |
87 | # pipenv
88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
91 | # install all needed dependencies.
92 | #Pipfile.lock
93 |
94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
95 | __pypackages__/
96 |
97 | # Celery stuff
98 | celerybeat-schedule
99 | celerybeat.pid
100 |
101 | # SageMath parsed files
102 | *.sage.py
103 |
104 | # Environments
105 | .env
106 | .venv
107 | env/
108 | venv/
109 | ENV/
110 | env.bak/
111 | venv.bak/
112 |
113 | # Spyder project settings
114 | .spyderproject
115 | .spyproject
116 |
117 | # Rope project settings
118 | .ropeproject
119 |
120 | # mkdocs documentation
121 | /site
122 |
123 | # mypy
124 | .mypy_cache/
125 | .dmypy.json
126 | dmypy.json
127 |
128 | # Pyre type checker
129 | .pyre/
130 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2020, Hultnér Technologies
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | 1. Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | 2. Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | 3. Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Hultnér Technologies AB | @ahultner | Blog | Slides | Nordisk Python Community | GitHub
6 |
7 |
8 |
9 | # ⠠⠵ Introduction to pydantic
10 | **Introduction to the Python Pydantic library.**
11 | Take your data classes to the next level! Showcasing run time type checking, data serialization and deserialization, custom validators and of course data class integration.
12 | Make it easy to go from standard data classes to pydantic models.
13 |
14 | ## ⠠⠵ Quick reference
15 |
16 | ```python
17 | # Minimal usage example
18 | from pydantic import BaseModel, validator, root_validator
19 | from enum import Enum
20 |
21 | class Topping(str, Enum):
22 | mozzarella = 'mozzarella'
23 | tomato_sauce = 'tomato sauce'
24 | prosciutto = 'prosciutto'
25 | basil = 'basil'
26 | rucola = 'rucola'
27 |
28 |
29 | class Pizza(BaseModel):
30 | style: str
31 | toppings: Tuple[Topping, ...]
32 |
33 |
34 | class BakedPizza(Pizza):
35 | # For simplicity in the example we use int for temperature
36 | oven_temperature: int
37 |
38 | # A validator looking at a single property
39 | @validator('style')
40 | def check_style(cls, style):
41 | house_styles = ("Napoli", "Roman", "Italian")
42 | if style not in house_styles:
43 | raise ValueError(f"We only cook the following styles: {house_styles}, given: {style}")
44 | return style
45 |
46 | # Root validators check the entire model
47 | @root_validator
48 | def check_temp(cls, values):
49 | style, temp = values.get("style"), values.get("oven_temperature")
50 |
51 | if style != "Napoli":
52 | # We don't have any special rules yet for the other styles
53 | return values
54 |
55 | if 350 <= temp <= 400:
56 | # Target temperature 350 - 400°C, ideally around 375°C
57 | return values
58 |
59 | raise ValueError(f"Napoli pizzas require a oven_temperature in the range of 350 - 400°C, given: {temp}°C")
60 |
61 |
62 | ```
63 |
64 | ## Talks
65 | ### ⠠⠵ Python Pizza, 2020 April 25 (Remote)
66 | **Give your data classes super powers with pydantic**, _25 April 2020, 14:12 UTC_
67 | The speaker schedule for the next Python Pizza conference is up, and I'm one of the speakers. The conference will be held remotely, [tickets](https://ti.to/acpyss/remote-python-pizza-2020-1) are €30 and all the proceeds will go to [Doctors Without Borders](
68 | https://www.msf.org/)!
69 |
70 |
71 | If you or your company can't afford the ticket there's also a [financial aid program](https://docs.google.com/forms/d/e/1FAIpQLSeEzqiE9bTCiM2dOQ9Numku2xJHPJKbRj9cqMGqxSD3KVlxOA/viewform).
72 |
73 |
74 |
75 | [](https://www.youtube.com/watch?v=LzNBfPVtrPk)
76 |
77 | - [Slides](http://slides.com/hultner/python-pizza-2020/#/)
78 | - [Conference site](https://remote.python.pizza)
79 | - [Jupyter Lab Notebook](demo/pydantic.ipynb)
80 | - [Announcement video](https://www.youtube.com/watch?v=LzNBfPVtrPk)
81 |
82 | ## ⠠⠵ FAQ, pydantic
83 | **Is the pydantic type-checker strict?**
84 | No, pydantic currently favours parsing and will coerce the type if possible. A [strict-mode](https://github.com/samuelcolvin/pydantic/issues/1098) is being worked on.
85 |
86 | **Can pydantics runtime type-checker be used on functions?**
87 | [Yes](https://pydantic-docs.helpmanual.io/usage/validation_decorator/), through the @validate_arguments decorator. But the feature is at the time of writing still in beta (2020-04-25).
88 |
89 | **Do settings managment support `.env`?**
90 | [Yes](https://pydantic-docs.helpmanual.io/usage/settings/) it does!
91 |
92 | **I have a question not covered here, where can I ask it?**
93 | I'm [@ahultner on twitter](https://twitter.com/ahultner), otherwise you can also email me (address in slides).
94 |
95 | ## ⠠⠵ Links
96 | - [Pydantic docs](https://pydantic-docs.helpmanual.io)
97 | - [Pydantic Github](https://github.com/samuelcolvin/pydantic/)
98 | - [FastAPI](https://fastapi.tiangolo.com)
99 | - [Dataclasses](https://docs.python.org/3/library/dataclasses.html)
100 | - [NamedTuple](https://docs.python.org/3/library/typing.html#typing.NamedTuple)
101 |
--------------------------------------------------------------------------------
/article.md:
--------------------------------------------------------------------------------
1 |
2 | .
3 | ..:
4 | Hultnér
5 | Technologies
6 |
7 | @ahultner | https://hultner.se/
8 |
9 |
10 | # Give your dataclasses super powers with pydantic
11 |
12 | ## Index
13 | - Quick refresher on python data classes
14 | - Pydantic introduction
15 | - Prior art
16 | - Minimal example from dataclass
17 | - Runtime type-checking
18 | - JSON (de)serialisation
19 | - JSONSchema
20 | - Validators
21 | - [Custom model validators](https://pydantic-docs.helpmanual.io/usage/validators/)
22 | - [Validation decorator for functions](https://pydantic-docs.helpmanual.io/usage/validation_decorator/), via `@validate_arguments`. Still in beta, API may change.
23 | - FastAPI framework
24 | - OpenAPI Specifications
25 | - Autogenerated tests
26 | - Cool features worth mentioning
27 | - Future
28 | - Conclusion
29 |
30 |
31 | Let's start with a quick `@dataclass`-refresher.
32 |
33 | Well use pizza-based examples in the spirit of [python.pizza](https://remote.python.pizza) 🐍🍕
34 |
35 |
36 | ```python
37 | from dataclasses import dataclass
38 | from typing import Tuple
39 | ```
40 |
41 |
42 | ```python
43 | @dataclass
44 | class Pizza:
45 | style: str
46 | toppings: Tuple[str, ...]
47 |
48 | ```
49 |
50 |
51 | ```python
52 | Pizza(1, ("cheese", "ham"))
53 | ```
54 |
55 |
56 |
57 |
58 | Pizza(style=1, toppings=('cheese', 'ham'))
59 |
60 |
61 |
62 | Now we may want to constrain the toppings to ones we actually offer.
63 | Our pizzeria doesn't offer pineapple 🚫🍍 as a valid topping, hate it 😡 or love it 💕
64 |
65 |
66 | ```python
67 | from enum import Enum
68 | ```
69 |
70 |
71 | ```python
72 | class Topping(str, Enum):
73 | mozzarella = 'mozzarella'
74 | tomato_sauce = 'tomato sauce'
75 | prosciutto = 'prosciutto'
76 | basil = 'basil'
77 | rucola = 'rucola'
78 |
79 |
80 | @dataclass
81 | class Pizza:
82 | style: str
83 | toppings: Tuple[Topping, ...]
84 | ```
85 |
86 | Let's see what happens if we try to create a pizza with pineapple 🍍 topping.
87 |
88 |
89 | ```python
90 | Pizza(2, ("pineapple", 24))
91 | ```
92 |
93 |
94 |
95 |
96 | Pizza(style=2, toppings=('pineapple', 24))
97 |
98 |
99 |
100 | With dataclasses the types aren't enforced, this can ofcourse be implemented but in this case we'll lean on the shoulders of a giant, pydantic 🧹🐍🧐
101 |
102 |
103 | ```python
104 | from pydantic.dataclasses import dataclass
105 |
106 |
107 | @dataclass
108 | class Pizza:
109 | style: str
110 | toppings: Tuple[Topping, ...]
111 | ```
112 |
113 | As you can see the only thing changed in this example is that we import the dataclass decorator from pydantic.
114 |
115 |
116 | ```python
117 | from pydantic import ValidationError
118 | try:
119 | Pizza(2, ("pineapple", 24))
120 | except ValidationError as err:
121 | print(err)
122 | ```
123 |
124 | 2 validation errors for Pizza
125 | toppings -> 0
126 | value is not a valid enumeration member; permitted: 'mozzarella', 'tomato sauce', 'prosciutto', 'basil', 'rucola' (type=type_error.enum; enum_values=[, , , , ])
127 | toppings -> 1
128 | value is not a valid enumeration member; permitted: 'mozzarella', 'tomato sauce', 'prosciutto', 'basil', 'rucola' (type=type_error.enum; enum_values=[, , , , ])
129 |
130 |
131 | And with that simple chage we can see that our new instance of an invalid pizza actually raises errors 🚫🚨
132 |
133 | Additionally these errors are very readable!
134 |
135 | So let's try to create a valid pizza 🍕✅
136 |
137 |
138 | ```python
139 | Pizza("Napoli", (Topping.tomato_sauce, Topping.prosciutto, Topping.mozzarella, Topping.basil))
140 | ```
141 |
142 |
143 |
144 |
145 | Pizza(style='Napoli', toppings=(, , , ))
146 |
147 |
148 |
149 | So what about JSON? 🧑💻
150 | The dataclass dropin replacement decorator from pydantic is great for compability but by using `pydantic.BaseModel` we can get even more out of pydantic. One of those things is (de)serialisation, pydantic have native support JSON encoding and decoding.
151 |
152 |
153 | ```python
154 | from pydantic import BaseModel
155 |
156 |
157 |
158 | class Pizza(BaseModel):
159 | style: str
160 | toppings: Tuple[Topping, ...]
161 | ```
162 |
163 | *Disclaimer: Pydantic is primarly a parsing library and does validation as a means to an end, so make sure it makes sense for you.*
164 |
165 | When using the BaseModel the default behaviour requires to specify the init arguments using their keywords like below
166 |
167 |
168 | ```python
169 | Pizza(style="Napoli", toppings=(Topping.tomato_sauce, Topping.prosciutto, Topping.mozzarella, Topping.basil))
170 | ```
171 |
172 |
173 |
174 |
175 | Pizza(style='Napoli', toppings=(, , , ))
176 |
177 |
178 |
179 | We can now easily encode this object as `JSON`, there's also [built-in support](https://pydantic-docs.helpmanual.io/usage/exporting_models/) for dict, pickle, immutable `copy()`. Pydantic will also (de)serialise subclasses.
180 |
181 |
182 | ```python
183 | _.json()
184 | ```
185 |
186 |
187 |
188 |
189 | '{"style": "Napoli", "toppings": ["tomato sauce", "prosciutto", "mozzarella", "basil"]}'
190 |
191 |
192 |
193 | And we can also reconstruct our original object using the `parse_raw`-method.
194 |
195 |
196 | ```python
197 | Pizza.parse_raw('{"style": "Napoli", "toppings": ["tomato sauce", "prosciutto", "mozzarella", "basil"]}')
198 | ```
199 |
200 |
201 |
202 |
203 | Pizza(style='Napoli', toppings=(, , , ))
204 |
205 |
206 |
207 | Errors raises a validation error, these can also be represented as JSON.
208 |
209 |
210 | ```python
211 | try:
212 | Pizza(style="Napoli", toppings=(2,))
213 | except ValidationError as err:
214 | print(err.json())
215 | ```
216 |
217 | [
218 | {
219 | "loc": [
220 | "toppings",
221 | 0
222 | ],
223 | "msg": "value is not a valid enumeration member; permitted: 'mozzarella', 'tomato sauce', 'prosciutto', 'basil', 'rucola'",
224 | "type": "type_error.enum",
225 | "ctx": {
226 | "enum_values": [
227 | "mozzarella",
228 | "tomato sauce",
229 | "prosciutto",
230 | "basil",
231 | "rucola"
232 | ]
233 | }
234 | }
235 | ]
236 |
237 |
238 | We can also export a JSONSchema directly from our model, this is very useful for instance if we want to use your model to feed a Swagger/OpenAPI-spec. 📜✅
239 |
240 | ⚠ *Caution: Pydantic uses the latest draft 7 of JSONSchema, this will be used in the comming OpenAPI 3.1 spec but the current 3.0.x spec uses draft 4. I spoke with Samuel Colvin, the creator of pydantic about this and his recommendation is to write a `schema_extra`function to use the older JSONSchema version if you want strict compability. The FastAPI framework doesn't do this and is slightly incompatible with the current OpenAPI-spec*
241 |
242 |
243 | ```python
244 | Pizza.schema()
245 | ```
246 |
247 |
248 |
249 |
250 | {'title': 'Pizza',
251 | 'type': 'object',
252 | 'properties': {'style': {'title': 'Style', 'type': 'string'},
253 | 'toppings': {'title': 'Toppings',
254 | 'type': 'array',
255 | 'items': {'enum': ['mozzarella',
256 | 'tomato sauce',
257 | 'prosciutto',
258 | 'basil',
259 | 'rucola'],
260 | 'type': 'string'}}},
261 | 'required': ['style', 'toppings']}
262 |
263 |
264 |
265 | That was the basics using the built-in validators, but what if you want to implement your own business rules in a custom validator, we're going to look at this next.
266 |
267 | We now want to add a new property for `oven_temperature`, but in our case we also want to ensure that we are close to the ideal of roughly 375°C for Neapolitan pizzas, which is our imaginary restaurants house style.
268 |
269 |
270 | ```python
271 | from pydantic import validator, root_validator
272 | class BakedPizza(Pizza):
273 | # For simplicity in the example we use int for temperature
274 | oven_temperature: int
275 |
276 | # A validator looking at a single property
277 | @validator('style')
278 | def check_style(cls, style):
279 | house_styles = ("Napoli", "Roman", "Italian")
280 | if style not in house_styles:
281 | raise ValueError(f"We only cook the following styles: {house_styles}, given: {style}")
282 | return style
283 |
284 | # Root validators check the entire model
285 | @root_validator
286 | def check_temp(cls, values):
287 | style, temp = values.get("style"), values.get("oven_temperature")
288 |
289 | if style != "Napoli":
290 | # We don't have any special rules yet for the other styles
291 | return values
292 |
293 | if 350 <= temp <= 400:
294 | # Target temperature 350 - 400°C, ideally around 375°C
295 | return values
296 |
297 | raise ValueError(f"Napoli pizzas require a oven_temperature in the range of 350 - 400°C, given: {temp}°C")
298 |
299 | ```
300 |
301 | Now let's see if we create some invalid pizzas ⚠️🚨
302 |
303 |
304 | ```python
305 | try:
306 | BakedPizza(style="Panpizza", toppings=["tomato sauce"], oven_temperature=250 )
307 | except ValidationError as err:
308 | print(err)
309 | ```
310 |
311 | 1 validation error for BakedPizza
312 | style
313 | We only cook the following styles: ('Napoli', 'Roman', 'Italian'), given: Panpizza (type=value_error)
314 |
315 |
316 |
317 | ```python
318 | try:
319 | BakedPizza(style="Napoli", toppings=["tomato sauce"], oven_temperature=300 )
320 | except ValidationError as err:
321 | print(err)
322 | ```
323 |
324 | 1 validation error for BakedPizza
325 | __root__
326 | Napoli pizzas require a oven_temperature in the range of 350 - 400°C, given: 300°C (type=value_error)
327 |
328 |
329 | Now let's create a pizza 🍕 allowed by our rules! ✨
330 |
331 |
332 | ```python
333 | BakedPizza(style="Napoli", toppings=["tomato sauce"], oven_temperature=350)
334 | ```
335 |
336 |
337 |
338 |
339 | BakedPizza(style='Napoli', toppings=(,), oven_temperature=350)
340 |
341 |
342 |
343 | Gosh these runtime type checkers are rather useful, but what about **functions**?
344 |
345 | Pydantic got you covered with `@validate_arguments`. *Still in beta, API may change, release 2020-04-18 in version 1.5*
346 |
347 |
348 | ```python
349 | from pydantic import validate_arguments
350 |
351 | # Validator on function
352 | # Ensure that we use a valid pizza when making orders
353 | @validate_arguments
354 | def make_order(pizza: Pizza):
355 | ...
356 |
357 | ```
358 |
359 |
360 | ```python
361 | try:
362 | make_order({
363 | "style":"Napoli",
364 | "toppings":("tomato sauce", "mozzarella", "prosciutto", "pineapple")
365 | })
366 | except ValidationError as err:
367 | print(err)
368 | ```
369 |
370 | 1 validation error for MakeOrder
371 | pizza -> toppings -> 3
372 | value is not a valid enumeration member; permitted: 'mozzarella', 'tomato sauce', 'prosciutto', 'basil', 'rucola' (type=type_error.enum; enum_values=[, , , , ])
373 |
374 |
375 | ## FastAPI
376 | FastAPI is a lean microframework similar to Flask which utilizes pydantic models heavily, it will also automatically generate OpenAPI-specifications from your application based on your models.
377 |
378 | This gives you framework agnostic models while still being able to leverage tight integration with a modern and easy to use framework. If you're going to start a new API-project i highly recommend trying FastAPI.
379 |
380 |
381 | ```python
382 | from fastapi import FastAPI
383 | from pydantic import BaseModel
384 |
385 | app = FastAPI()
386 |
387 | def make_order(pizza: Pizza):
388 | # Business logic for making an order
389 | pass
390 |
391 | def dispatch_order(pizza: BakedPizza):
392 | # Hand over pizza to delivery company
393 | pass
394 |
395 | # Deliver a baked pizza
396 | @app.post("/delivery/pizza")
397 | async def deliver_pizza_order(pizza: BakedPizza):
398 | dispatch = dispatch_order(pizza)
399 | return dispatch
400 |
401 | @app.post("/order/pizza")
402 | async def order_pizza(pizza: Pizza):
403 | order = make_order(pizza)
404 | return order
405 |
406 | ```
407 |
408 | This is everything we need to create a small API around our models.
409 |
410 | ---
411 |
412 | That's it, a quick introduction to pydantic!
413 |
414 | But this is just the tip of the iceberg 🗻 and I want to give you a hint about what more can be done.
415 | I'm not going to go into detail in any of this but feel free to ask me about it in the chat, on Twitter/LinkedIn or via email. 💬📨
416 |
417 | ## Cool features worth mentioning
418 |
419 | - Post **1.0**, reached this milestone about a year ago
420 | - Support for [standard library types](https://pydantic-docs.helpmanual.io/usage/types/#pydantic-types)
421 | - Offer useful extra types for every day use
422 | - Email
423 | - HttpUrl (and more, stricturl for custom validation)
424 | - PostgresDsn
425 | - IPvAnyAddress (as well as IPv4Address and IPv6Address from ipaddress)
426 | - PositiveInt
427 | - PaymentCardNumber, PaymentCardBrand.[amex, mastercard, visa, other], checks luhn, str of digits and BIN-based lenght.
428 | - [Constrained types](https://pydantic-docs.helpmanual.io/usage/types/#constrained-types) (e.g. conlist, conint, etc.)
429 | - and more…
430 | - Supports [custom datatypes](https://pydantic-docs.helpmanual.io/usage/types/#custom-data-types)
431 | - [Settings management](https://pydantic-docs.helpmanual.io/usage/settings/)
432 | - Typed configuration management
433 | - Automatically reads from environment variables
434 | - Dotenv (`.env`) support via defacto standard [python-dotenv](https://pypi.org/project/python-dotenv/).
435 | - ORM-mode
436 | - Recursive models
437 | - Works with mypy out of the box, [mypy plugin](https://pydantic-docs.helpmanual.io/mypy_plugin/) further improves experience.
438 | - [Postponed annotations, self-referencing models](https://pydantic-docs.helpmanual.io/usage/postponed_annotations/), [PEP-563](https://www.python.org/dev/peps/pep-0563/)-style.
439 | - python-devtools intergration
440 | - PyCharm plugin
441 | - [Fast](https://pydantic-docs.helpmanual.io/benchmarks/) compared to popular alternatives!
442 | But always make your own benchmarks for your own usecase if performance is important for you.
443 |
444 | ## Future
445 | - A strict mode is being worked on, in the future this will enable us to choose between Strict and Coercion on a model level instead of relying on the Strict* types.
446 | - The project is very active and a lot of improvements are constantly being made to the library.
447 |
448 |
449 | ## Conclusion
450 | Pure python syntax
451 | Better validation
452 | Very useful JSON-tools for API's
453 | Easy to migrate from dataclasses
454 | Lots of useful features
455 | Try it out!
456 |
457 | ## Want to hear more from me?
458 | I'm making a course on property based testing in python using Hypothesis.
459 | [Sign up here](https://forms.gle/yRWapypPXdPFSLME7)
460 |
--------------------------------------------------------------------------------
/demo/poetry.lock:
--------------------------------------------------------------------------------
1 | [[package]]
2 | name = "anyio"
3 | version = "2.2.0"
4 | description = "High level compatibility layer for multiple asynchronous event loop implementations"
5 | category = "main"
6 | optional = false
7 | python-versions = ">=3.6.2"
8 |
9 | [package.dependencies]
10 | idna = ">=2.8"
11 | sniffio = ">=1.1"
12 |
13 | [package.extras]
14 | curio = ["curio (>=1.4)"]
15 | doc = ["sphinx-rtd-theme", "sphinx-autodoc-typehints (>=1.2.0)"]
16 | test = ["coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "pytest (>=6.0)", "trustme", "uvloop (<0.15)", "uvloop (>=0.15)"]
17 | trio = ["trio (>=0.16)"]
18 |
19 | [[package]]
20 | name = "appnope"
21 | version = "0.1.2"
22 | description = "Disable App Nap on macOS >= 10.9"
23 | category = "main"
24 | optional = false
25 | python-versions = "*"
26 |
27 | [[package]]
28 | name = "argon2-cffi"
29 | version = "20.1.0"
30 | description = "The secure Argon2 password hashing algorithm."
31 | category = "main"
32 | optional = false
33 | python-versions = "*"
34 |
35 | [package.dependencies]
36 | cffi = ">=1.0.0"
37 | six = "*"
38 |
39 | [package.extras]
40 | dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pytest", "sphinx", "wheel", "pre-commit"]
41 | docs = ["sphinx"]
42 | tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pytest"]
43 |
44 | [[package]]
45 | name = "async-generator"
46 | version = "1.10"
47 | description = "Async generators and context managers for Python 3.5+"
48 | category = "main"
49 | optional = false
50 | python-versions = ">=3.5"
51 |
52 | [[package]]
53 | name = "attrs"
54 | version = "20.3.0"
55 | description = "Classes Without Boilerplate"
56 | category = "main"
57 | optional = false
58 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
59 |
60 | [package.extras]
61 | dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"]
62 | docs = ["furo", "sphinx", "zope.interface"]
63 | tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
64 | tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"]
65 |
66 | [[package]]
67 | name = "babel"
68 | version = "2.9.0"
69 | description = "Internationalization utilities"
70 | category = "main"
71 | optional = false
72 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
73 |
74 | [package.dependencies]
75 | pytz = ">=2015.7"
76 |
77 | [[package]]
78 | name = "backcall"
79 | version = "0.2.0"
80 | description = "Specifications for callback functions passed in to an API"
81 | category = "main"
82 | optional = false
83 | python-versions = "*"
84 |
85 | [[package]]
86 | name = "bleach"
87 | version = "3.3.0"
88 | description = "An easy safelist-based HTML-sanitizing tool."
89 | category = "main"
90 | optional = false
91 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
92 |
93 | [package.dependencies]
94 | packaging = "*"
95 | six = ">=1.9.0"
96 | webencodings = "*"
97 |
98 | [[package]]
99 | name = "blessings"
100 | version = "1.7"
101 | description = "A thin, practical wrapper around terminal coloring, styling, and positioning"
102 | category = "main"
103 | optional = false
104 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
105 |
106 | [package.dependencies]
107 | six = "*"
108 |
109 | [[package]]
110 | name = "bpython"
111 | version = "0.21"
112 | description = "Fancy Interface to the Python Interpreter"
113 | category = "main"
114 | optional = false
115 | python-versions = ">=3.6"
116 |
117 | [package.dependencies]
118 | curtsies = ">=0.3.5"
119 | cwcwidth = "*"
120 | greenlet = "*"
121 | pygments = "*"
122 | pyxdg = "*"
123 | requests = "*"
124 |
125 | [package.extras]
126 | jedi = ["jedi (>=0.16)"]
127 | urwid = ["urwid"]
128 | watch = ["watchdog"]
129 |
130 | [[package]]
131 | name = "certifi"
132 | version = "2020.12.5"
133 | description = "Python package for providing Mozilla's CA Bundle."
134 | category = "main"
135 | optional = false
136 | python-versions = "*"
137 |
138 | [[package]]
139 | name = "cffi"
140 | version = "1.14.5"
141 | description = "Foreign Function Interface for Python calling C code."
142 | category = "main"
143 | optional = false
144 | python-versions = "*"
145 |
146 | [package.dependencies]
147 | pycparser = "*"
148 |
149 | [[package]]
150 | name = "chardet"
151 | version = "4.0.0"
152 | description = "Universal encoding detector for Python 2 and 3"
153 | category = "main"
154 | optional = false
155 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
156 |
157 | [[package]]
158 | name = "click"
159 | version = "7.1.2"
160 | description = "Composable command line interface toolkit"
161 | category = "main"
162 | optional = false
163 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
164 |
165 | [[package]]
166 | name = "colorama"
167 | version = "0.4.4"
168 | description = "Cross-platform colored terminal text."
169 | category = "main"
170 | optional = false
171 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
172 |
173 | [[package]]
174 | name = "curtsies"
175 | version = "0.3.5"
176 | description = "Curses-like terminal wrapper, with colored strings!"
177 | category = "main"
178 | optional = false
179 | python-versions = ">=3.6"
180 |
181 | [package.dependencies]
182 | blessings = ">=1.5"
183 | cwcwidth = "*"
184 |
185 | [[package]]
186 | name = "cwcwidth"
187 | version = "0.1.4"
188 | description = "Python bindings for wc(s)width"
189 | category = "main"
190 | optional = false
191 | python-versions = "*"
192 |
193 | [[package]]
194 | name = "decorator"
195 | version = "4.4.2"
196 | description = "Decorators for Humans"
197 | category = "main"
198 | optional = false
199 | python-versions = ">=2.6, !=3.0.*, !=3.1.*"
200 |
201 | [[package]]
202 | name = "defusedxml"
203 | version = "0.7.1"
204 | description = "XML bomb protection for Python stdlib modules"
205 | category = "main"
206 | optional = false
207 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
208 |
209 | [[package]]
210 | name = "entrypoints"
211 | version = "0.3"
212 | description = "Discover and load entry points from installed packages."
213 | category = "main"
214 | optional = false
215 | python-versions = ">=2.7"
216 |
217 | [[package]]
218 | name = "fastapi"
219 | version = "0.63.0"
220 | description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
221 | category = "main"
222 | optional = false
223 | python-versions = ">=3.6"
224 |
225 | [package.dependencies]
226 | pydantic = ">=1.0.0,<2.0.0"
227 | starlette = "0.13.6"
228 |
229 | [package.extras]
230 | all = ["requests (>=2.24.0,<3.0.0)", "aiofiles (>=0.5.0,<0.6.0)", "jinja2 (>=2.11.2,<3.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "itsdangerous (>=1.1.0,<2.0.0)", "pyyaml (>=5.3.1,<6.0.0)", "graphene (>=2.1.8,<3.0.0)", "ujson (>=3.0.0,<4.0.0)", "orjson (>=3.2.1,<4.0.0)", "email_validator (>=1.1.1,<2.0.0)", "uvicorn[standard] (>=0.12.0,<0.14.0)", "async_exit_stack (>=1.0.1,<2.0.0)", "async_generator (>=1.10,<2.0.0)"]
231 | dev = ["python-jose[cryptography] (>=3.1.0,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "uvicorn[standard] (>=0.12.0,<0.14.0)", "graphene (>=2.1.8,<3.0.0)"]
232 | doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=6.1.4,<7.0.0)", "markdown-include (>=0.5.1,<0.6.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.2.0)", "typer-cli (>=0.0.9,<0.0.10)", "pyyaml (>=5.3.1,<6.0.0)"]
233 | test = ["pytest (==5.4.3)", "pytest-cov (==2.10.0)", "pytest-asyncio (>=0.14.0,<0.15.0)", "mypy (==0.790)", "flake8 (>=3.8.3,<4.0.0)", "black (==20.8b1)", "isort (>=5.0.6,<6.0.0)", "requests (>=2.24.0,<3.0.0)", "httpx (>=0.14.0,<0.15.0)", "email_validator (>=1.1.1,<2.0.0)", "sqlalchemy (>=1.3.18,<2.0.0)", "peewee (>=3.13.3,<4.0.0)", "databases[sqlite] (>=0.3.2,<0.4.0)", "orjson (>=3.2.1,<4.0.0)", "async_exit_stack (>=1.0.1,<2.0.0)", "async_generator (>=1.10,<2.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "aiofiles (>=0.5.0,<0.6.0)", "flask (>=1.1.2,<2.0.0)"]
234 |
235 | [[package]]
236 | name = "greenlet"
237 | version = "1.0.0"
238 | description = "Lightweight in-process concurrent programming"
239 | category = "main"
240 | optional = false
241 | python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*"
242 |
243 | [package.extras]
244 | docs = ["sphinx"]
245 |
246 | [[package]]
247 | name = "h11"
248 | version = "0.12.0"
249 | description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
250 | category = "main"
251 | optional = false
252 | python-versions = ">=3.6"
253 |
254 | [[package]]
255 | name = "idna"
256 | version = "2.10"
257 | description = "Internationalized Domain Names in Applications (IDNA)"
258 | category = "main"
259 | optional = false
260 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
261 |
262 | [[package]]
263 | name = "ipykernel"
264 | version = "5.5.0"
265 | description = "IPython Kernel for Jupyter"
266 | category = "main"
267 | optional = false
268 | python-versions = ">=3.5"
269 |
270 | [package.dependencies]
271 | appnope = {version = "*", markers = "platform_system == \"Darwin\""}
272 | ipython = ">=5.0.0"
273 | jupyter-client = "*"
274 | tornado = ">=4.2"
275 | traitlets = ">=4.1.0"
276 |
277 | [package.extras]
278 | test = ["pytest (!=5.3.4)", "pytest-cov", "flaky", "nose", "jedi (<=0.17.2)"]
279 |
280 | [[package]]
281 | name = "ipython"
282 | version = "7.21.0"
283 | description = "IPython: Productive Interactive Computing"
284 | category = "main"
285 | optional = false
286 | python-versions = ">=3.7"
287 |
288 | [package.dependencies]
289 | appnope = {version = "*", markers = "sys_platform == \"darwin\""}
290 | backcall = "*"
291 | colorama = {version = "*", markers = "sys_platform == \"win32\""}
292 | decorator = "*"
293 | jedi = ">=0.16"
294 | pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""}
295 | pickleshare = "*"
296 | prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0"
297 | pygments = "*"
298 | traitlets = ">=4.2"
299 |
300 | [package.extras]
301 | all = ["Sphinx (>=1.3)", "ipykernel", "ipyparallel", "ipywidgets", "nbconvert", "nbformat", "nose (>=0.10.1)", "notebook", "numpy (>=1.14)", "pygments", "qtconsole", "requests", "testpath"]
302 | doc = ["Sphinx (>=1.3)"]
303 | kernel = ["ipykernel"]
304 | nbconvert = ["nbconvert"]
305 | nbformat = ["nbformat"]
306 | notebook = ["notebook", "ipywidgets"]
307 | parallel = ["ipyparallel"]
308 | qtconsole = ["qtconsole"]
309 | test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.14)"]
310 |
311 | [[package]]
312 | name = "ipython-genutils"
313 | version = "0.2.0"
314 | description = "Vestigial utilities from IPython"
315 | category = "main"
316 | optional = false
317 | python-versions = "*"
318 |
319 | [[package]]
320 | name = "jedi"
321 | version = "0.18.0"
322 | description = "An autocompletion tool for Python that can be used for text editors."
323 | category = "main"
324 | optional = false
325 | python-versions = ">=3.6"
326 |
327 | [package.dependencies]
328 | parso = ">=0.8.0,<0.9.0"
329 |
330 | [package.extras]
331 | qa = ["flake8 (==3.8.3)", "mypy (==0.782)"]
332 | testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<6.0.0)"]
333 |
334 | [[package]]
335 | name = "jinja2"
336 | version = "2.11.3"
337 | description = "A very fast and expressive template engine."
338 | category = "main"
339 | optional = false
340 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
341 |
342 | [package.dependencies]
343 | MarkupSafe = ">=0.23"
344 |
345 | [package.extras]
346 | i18n = ["Babel (>=0.8)"]
347 |
348 | [[package]]
349 | name = "json5"
350 | version = "0.9.5"
351 | description = "A Python implementation of the JSON5 data format."
352 | category = "main"
353 | optional = false
354 | python-versions = "*"
355 |
356 | [package.extras]
357 | dev = ["hypothesis"]
358 |
359 | [[package]]
360 | name = "jsonschema"
361 | version = "3.2.0"
362 | description = "An implementation of JSON Schema validation for Python"
363 | category = "main"
364 | optional = false
365 | python-versions = "*"
366 |
367 | [package.dependencies]
368 | attrs = ">=17.4.0"
369 | pyrsistent = ">=0.14.0"
370 | six = ">=1.11.0"
371 |
372 | [package.extras]
373 | format = ["idna", "jsonpointer (>1.13)", "rfc3987", "strict-rfc3339", "webcolors"]
374 | format_nongpl = ["idna", "jsonpointer (>1.13)", "webcolors", "rfc3986-validator (>0.1.0)", "rfc3339-validator"]
375 |
376 | [[package]]
377 | name = "jupyter-client"
378 | version = "6.1.12"
379 | description = "Jupyter protocol implementation and client libraries"
380 | category = "main"
381 | optional = false
382 | python-versions = ">=3.5"
383 |
384 | [package.dependencies]
385 | jupyter-core = ">=4.6.0"
386 | python-dateutil = ">=2.1"
387 | pyzmq = ">=13"
388 | tornado = ">=4.1"
389 | traitlets = "*"
390 |
391 | [package.extras]
392 | doc = ["sphinx (>=1.3.6)", "sphinx-rtd-theme", "sphinxcontrib-github-alt"]
393 | test = ["async-generator", "ipykernel", "ipython", "mock", "pytest-asyncio", "pytest-timeout", "pytest", "jedi (<0.18)"]
394 |
395 | [[package]]
396 | name = "jupyter-core"
397 | version = "4.7.1"
398 | description = "Jupyter core package. A base package on which Jupyter projects rely."
399 | category = "main"
400 | optional = false
401 | python-versions = ">=3.6"
402 |
403 | [package.dependencies]
404 | pywin32 = {version = ">=1.0", markers = "sys_platform == \"win32\""}
405 | traitlets = "*"
406 |
407 | [[package]]
408 | name = "jupyter-packaging"
409 | version = "0.7.12"
410 | description = "Jupyter Packaging Utilities"
411 | category = "main"
412 | optional = false
413 | python-versions = ">=3.6"
414 |
415 | [package.dependencies]
416 | packaging = "*"
417 |
418 | [package.extras]
419 | test = ["pytest"]
420 |
421 | [[package]]
422 | name = "jupyter-server"
423 | version = "1.5.1"
424 | description = "The backend—i.e. core services, APIs, and REST endpoints—to Jupyter web applications."
425 | category = "main"
426 | optional = false
427 | python-versions = ">=3.6"
428 |
429 | [package.dependencies]
430 | anyio = ">=2.0.2"
431 | argon2-cffi = "*"
432 | ipython-genutils = "*"
433 | jinja2 = "*"
434 | jupyter-client = ">=6.1.1"
435 | jupyter-core = ">=4.4.0"
436 | nbconvert = "*"
437 | nbformat = "*"
438 | prometheus-client = "*"
439 | pywin32 = {version = ">=1.0", markers = "sys_platform == \"win32\""}
440 | pyzmq = ">=17"
441 | Send2Trash = "*"
442 | terminado = ">=0.8.3"
443 | tornado = ">=6.1.0"
444 | traitlets = ">=4.2.1"
445 |
446 | [package.extras]
447 | test = ["coverage", "requests", "pytest", "pytest-cov", "pytest-tornasync", "pytest-console-scripts", "ipykernel"]
448 |
449 | [[package]]
450 | name = "jupyterlab"
451 | version = "3.0.12"
452 | description = "The JupyterLab server extension."
453 | category = "main"
454 | optional = false
455 | python-versions = ">=3.6"
456 |
457 | [package.dependencies]
458 | ipython = "*"
459 | jinja2 = ">=2.10"
460 | jupyter-core = "*"
461 | jupyter-packaging = ">=0.7.3,<0.8.0"
462 | jupyter-server = ">=1.4,<2.0"
463 | jupyterlab-server = ">=2.3,<3.0"
464 | nbclassic = ">=0.2,<1.0"
465 | packaging = "*"
466 | tornado = ">=6.1.0"
467 |
468 | [package.extras]
469 | docs = ["jsx-lexer", "recommonmark", "sphinx", "sphinx-rtd-theme", "sphinx-copybutton"]
470 | test = ["pytest (>=6.0)", "pytest-cov", "pytest-console-scripts", "pytest-check-links", "jupyterlab-server[test] (>=2.0,<3.0)", "requests", "wheel", "virtualenv", "nose-exclude"]
471 |
472 | [[package]]
473 | name = "jupyterlab-pygments"
474 | version = "0.1.2"
475 | description = "Pygments theme using JupyterLab CSS variables"
476 | category = "main"
477 | optional = false
478 | python-versions = "*"
479 |
480 | [package.dependencies]
481 | pygments = ">=2.4.1,<3"
482 |
483 | [[package]]
484 | name = "jupyterlab-server"
485 | version = "2.3.0"
486 | description = "JupyterLab Server"
487 | category = "main"
488 | optional = false
489 | python-versions = ">=3.6"
490 |
491 | [package.dependencies]
492 | babel = "*"
493 | jinja2 = ">=2.10"
494 | json5 = "*"
495 | jsonschema = ">=3.0.1"
496 | jupyter-server = ">=1.4,<2.0"
497 | packaging = "*"
498 | requests = "*"
499 |
500 | [package.extras]
501 | test = ["codecov", "ipykernel", "pytest (>=5.3.2)", "pytest-cov", "jupyter-server", "pytest-console-scripts", "strict-rfc3339", "wheel"]
502 |
503 | [[package]]
504 | name = "markupsafe"
505 | version = "1.1.1"
506 | description = "Safely add untrusted strings to HTML/XML markup."
507 | category = "main"
508 | optional = false
509 | python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
510 |
511 | [[package]]
512 | name = "mistune"
513 | version = "0.8.4"
514 | description = "The fastest markdown parser in pure Python"
515 | category = "main"
516 | optional = false
517 | python-versions = "*"
518 |
519 | [[package]]
520 | name = "mypy"
521 | version = "0.812"
522 | description = "Optional static typing for Python"
523 | category = "main"
524 | optional = false
525 | python-versions = ">=3.5"
526 |
527 | [package.dependencies]
528 | mypy-extensions = ">=0.4.3,<0.5.0"
529 | typed-ast = ">=1.4.0,<1.5.0"
530 | typing-extensions = ">=3.7.4"
531 |
532 | [package.extras]
533 | dmypy = ["psutil (>=4.0)"]
534 |
535 | [[package]]
536 | name = "mypy-extensions"
537 | version = "0.4.3"
538 | description = "Experimental type system extensions for programs checked with the mypy typechecker."
539 | category = "main"
540 | optional = false
541 | python-versions = "*"
542 |
543 | [[package]]
544 | name = "nbclassic"
545 | version = "0.2.6"
546 | description = "Jupyter Notebook as a Jupyter Server Extension."
547 | category = "main"
548 | optional = false
549 | python-versions = ">=3.6"
550 |
551 | [package.dependencies]
552 | jupyter-server = ">=1.1,<2.0"
553 | notebook = "<7"
554 |
555 | [package.extras]
556 | test = ["pytest", "pytest-tornasync", "pytest-console-scripts"]
557 |
558 | [[package]]
559 | name = "nbclient"
560 | version = "0.5.3"
561 | description = "A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor."
562 | category = "main"
563 | optional = false
564 | python-versions = ">=3.6.1"
565 |
566 | [package.dependencies]
567 | async-generator = "*"
568 | jupyter-client = ">=6.1.5"
569 | nbformat = ">=5.0"
570 | nest-asyncio = "*"
571 | traitlets = ">=4.2"
572 |
573 | [package.extras]
574 | dev = ["codecov", "coverage", "ipython", "ipykernel", "ipywidgets", "pytest (>=4.1)", "pytest-cov (>=2.6.1)", "check-manifest", "flake8", "mypy", "tox", "bumpversion", "xmltodict", "pip (>=18.1)", "wheel (>=0.31.0)", "setuptools (>=38.6.0)", "twine (>=1.11.0)", "black"]
575 | sphinx = ["Sphinx (>=1.7)", "sphinx-book-theme", "mock", "moto", "myst-parser"]
576 | test = ["codecov", "coverage", "ipython", "ipykernel", "ipywidgets", "pytest (>=4.1)", "pytest-cov (>=2.6.1)", "check-manifest", "flake8", "mypy", "tox", "bumpversion", "xmltodict", "pip (>=18.1)", "wheel (>=0.31.0)", "setuptools (>=38.6.0)", "twine (>=1.11.0)", "black"]
577 |
578 | [[package]]
579 | name = "nbconvert"
580 | version = "6.0.7"
581 | description = "Converting Jupyter Notebooks"
582 | category = "main"
583 | optional = false
584 | python-versions = ">=3.6"
585 |
586 | [package.dependencies]
587 | bleach = "*"
588 | defusedxml = "*"
589 | entrypoints = ">=0.2.2"
590 | jinja2 = ">=2.4"
591 | jupyter-core = "*"
592 | jupyterlab-pygments = "*"
593 | mistune = ">=0.8.1,<2"
594 | nbclient = ">=0.5.0,<0.6.0"
595 | nbformat = ">=4.4"
596 | pandocfilters = ">=1.4.1"
597 | pygments = ">=2.4.1"
598 | testpath = "*"
599 | traitlets = ">=4.2"
600 |
601 | [package.extras]
602 | all = ["pytest", "pytest-cov", "pytest-dependency", "ipykernel", "ipywidgets (>=7)", "pyppeteer (==0.2.2)", "tornado (>=4.0)", "sphinx (>=1.5.1)", "sphinx-rtd-theme", "nbsphinx (>=0.2.12)", "ipython"]
603 | docs = ["sphinx (>=1.5.1)", "sphinx-rtd-theme", "nbsphinx (>=0.2.12)", "ipython"]
604 | serve = ["tornado (>=4.0)"]
605 | test = ["pytest", "pytest-cov", "pytest-dependency", "ipykernel", "ipywidgets (>=7)", "pyppeteer (==0.2.2)"]
606 | webpdf = ["pyppeteer (==0.2.2)"]
607 |
608 | [[package]]
609 | name = "nbformat"
610 | version = "5.1.2"
611 | description = "The Jupyter Notebook format"
612 | category = "main"
613 | optional = false
614 | python-versions = ">=3.5"
615 |
616 | [package.dependencies]
617 | ipython-genutils = "*"
618 | jsonschema = ">=2.4,<2.5.0 || >2.5.0"
619 | jupyter-core = "*"
620 | traitlets = ">=4.1"
621 |
622 | [package.extras]
623 | fast = ["fastjsonschema"]
624 | test = ["check-manifest", "fastjsonschema", "testpath", "pytest", "pytest-cov"]
625 |
626 | [[package]]
627 | name = "nest-asyncio"
628 | version = "1.5.1"
629 | description = "Patch asyncio to allow nested event loops"
630 | category = "main"
631 | optional = false
632 | python-versions = ">=3.5"
633 |
634 | [[package]]
635 | name = "notebook"
636 | version = "6.3.0"
637 | description = "A web-based notebook environment for interactive computing"
638 | category = "main"
639 | optional = false
640 | python-versions = ">=3.6"
641 |
642 | [package.dependencies]
643 | argon2-cffi = "*"
644 | ipykernel = "*"
645 | ipython-genutils = "*"
646 | jinja2 = "*"
647 | jupyter-client = ">=5.3.4"
648 | jupyter-core = ">=4.6.1"
649 | nbconvert = "*"
650 | nbformat = "*"
651 | prometheus-client = "*"
652 | pyzmq = ">=17"
653 | Send2Trash = ">=1.5.0"
654 | terminado = ">=0.8.3"
655 | tornado = ">=6.1"
656 | traitlets = ">=4.2.1"
657 |
658 | [package.extras]
659 | docs = ["sphinx", "nbsphinx", "sphinxcontrib-github-alt", "sphinx-rtd-theme"]
660 | json-logging = ["json-logging"]
661 | test = ["pytest", "coverage", "requests", "nbval", "selenium", "pytest-cov", "requests-unixsocket"]
662 |
663 | [[package]]
664 | name = "packaging"
665 | version = "20.9"
666 | description = "Core utilities for Python packages"
667 | category = "main"
668 | optional = false
669 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
670 |
671 | [package.dependencies]
672 | pyparsing = ">=2.0.2"
673 |
674 | [[package]]
675 | name = "pandocfilters"
676 | version = "1.4.3"
677 | description = "Utilities for writing pandoc filters in python"
678 | category = "main"
679 | optional = false
680 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
681 |
682 | [[package]]
683 | name = "parso"
684 | version = "0.8.1"
685 | description = "A Python Parser"
686 | category = "main"
687 | optional = false
688 | python-versions = ">=3.6"
689 |
690 | [package.extras]
691 | qa = ["flake8 (==3.8.3)", "mypy (==0.782)"]
692 | testing = ["docopt", "pytest (<6.0.0)"]
693 |
694 | [[package]]
695 | name = "pexpect"
696 | version = "4.8.0"
697 | description = "Pexpect allows easy control of interactive console applications."
698 | category = "main"
699 | optional = false
700 | python-versions = "*"
701 |
702 | [package.dependencies]
703 | ptyprocess = ">=0.5"
704 |
705 | [[package]]
706 | name = "pickleshare"
707 | version = "0.7.5"
708 | description = "Tiny 'shelve'-like database with concurrency support"
709 | category = "main"
710 | optional = false
711 | python-versions = "*"
712 |
713 | [[package]]
714 | name = "prometheus-client"
715 | version = "0.9.0"
716 | description = "Python client for the Prometheus monitoring system."
717 | category = "main"
718 | optional = false
719 | python-versions = "*"
720 |
721 | [package.extras]
722 | twisted = ["twisted"]
723 |
724 | [[package]]
725 | name = "prompt-toolkit"
726 | version = "3.0.18"
727 | description = "Library for building powerful interactive command lines in Python"
728 | category = "main"
729 | optional = false
730 | python-versions = ">=3.6.1"
731 |
732 | [package.dependencies]
733 | wcwidth = "*"
734 |
735 | [[package]]
736 | name = "ptyprocess"
737 | version = "0.7.0"
738 | description = "Run a subprocess in a pseudo terminal"
739 | category = "main"
740 | optional = false
741 | python-versions = "*"
742 |
743 | [[package]]
744 | name = "py"
745 | version = "1.10.0"
746 | description = "library with cross-python path, ini-parsing, io, code, log facilities"
747 | category = "main"
748 | optional = false
749 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
750 |
751 | [[package]]
752 | name = "pycparser"
753 | version = "2.20"
754 | description = "C parser in Python"
755 | category = "main"
756 | optional = false
757 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
758 |
759 | [[package]]
760 | name = "pydantic"
761 | version = "1.8.1"
762 | description = "Data validation and settings management using python 3.6 type hinting"
763 | category = "main"
764 | optional = false
765 | python-versions = ">=3.6.1"
766 |
767 | [package.dependencies]
768 | typing-extensions = ">=3.7.4.3"
769 |
770 | [package.extras]
771 | dotenv = ["python-dotenv (>=0.10.4)"]
772 | email = ["email-validator (>=1.0.3)"]
773 |
774 | [[package]]
775 | name = "pygments"
776 | version = "2.8.1"
777 | description = "Pygments is a syntax highlighting package written in Python."
778 | category = "main"
779 | optional = false
780 | python-versions = ">=3.5"
781 |
782 | [[package]]
783 | name = "pyparsing"
784 | version = "2.4.7"
785 | description = "Python parsing module"
786 | category = "main"
787 | optional = false
788 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
789 |
790 | [[package]]
791 | name = "pyrsistent"
792 | version = "0.17.3"
793 | description = "Persistent/Functional/Immutable data structures"
794 | category = "main"
795 | optional = false
796 | python-versions = ">=3.5"
797 |
798 | [[package]]
799 | name = "python-dateutil"
800 | version = "2.8.1"
801 | description = "Extensions to the standard Python datetime module"
802 | category = "main"
803 | optional = false
804 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
805 |
806 | [package.dependencies]
807 | six = ">=1.5"
808 |
809 | [[package]]
810 | name = "python-devtools"
811 | version = "2"
812 | description = "Dev tools for python"
813 | category = "main"
814 | optional = false
815 | python-versions = ">=3.5"
816 |
817 | [[package]]
818 | name = "pytz"
819 | version = "2021.1"
820 | description = "World timezone definitions, modern and historical"
821 | category = "main"
822 | optional = false
823 | python-versions = "*"
824 |
825 | [[package]]
826 | name = "pywin32"
827 | version = "300"
828 | description = "Python for Window Extensions"
829 | category = "main"
830 | optional = false
831 | python-versions = "*"
832 |
833 | [[package]]
834 | name = "pywinpty"
835 | version = "0.5.7"
836 | description = "Python bindings for the winpty library"
837 | category = "main"
838 | optional = false
839 | python-versions = "*"
840 |
841 | [[package]]
842 | name = "pyxdg"
843 | version = "0.27"
844 | description = "PyXDG contains implementations of freedesktop.org standards in python."
845 | category = "main"
846 | optional = false
847 | python-versions = "*"
848 |
849 | [[package]]
850 | name = "pyzmq"
851 | version = "22.0.3"
852 | description = "Python bindings for 0MQ"
853 | category = "main"
854 | optional = false
855 | python-versions = ">=3.6"
856 |
857 | [package.dependencies]
858 | cffi = {version = "*", markers = "implementation_name == \"pypy\""}
859 | py = {version = "*", markers = "implementation_name == \"pypy\""}
860 |
861 | [[package]]
862 | name = "requests"
863 | version = "2.25.1"
864 | description = "Python HTTP for Humans."
865 | category = "main"
866 | optional = false
867 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
868 |
869 | [package.dependencies]
870 | certifi = ">=2017.4.17"
871 | chardet = ">=3.0.2,<5"
872 | idna = ">=2.5,<3"
873 | urllib3 = ">=1.21.1,<1.27"
874 |
875 | [package.extras]
876 | security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"]
877 | socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
878 |
879 | [[package]]
880 | name = "send2trash"
881 | version = "1.5.0"
882 | description = "Send file to trash natively under Mac OS X, Windows and Linux."
883 | category = "main"
884 | optional = false
885 | python-versions = "*"
886 |
887 | [[package]]
888 | name = "six"
889 | version = "1.15.0"
890 | description = "Python 2 and 3 compatibility utilities"
891 | category = "main"
892 | optional = false
893 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
894 |
895 | [[package]]
896 | name = "sniffio"
897 | version = "1.2.0"
898 | description = "Sniff out which async library your code is running under"
899 | category = "main"
900 | optional = false
901 | python-versions = ">=3.5"
902 |
903 | [[package]]
904 | name = "starlette"
905 | version = "0.13.6"
906 | description = "The little ASGI library that shines."
907 | category = "main"
908 | optional = false
909 | python-versions = ">=3.6"
910 |
911 | [package.extras]
912 | full = ["aiofiles", "graphene", "itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests", "ujson"]
913 |
914 | [[package]]
915 | name = "terminado"
916 | version = "0.9.3"
917 | description = "Tornado websocket backend for the Xterm.js Javascript terminal emulator library."
918 | category = "main"
919 | optional = false
920 | python-versions = ">=3.6"
921 |
922 | [package.dependencies]
923 | ptyprocess = {version = "*", markers = "os_name != \"nt\""}
924 | pywinpty = {version = ">=0.5", markers = "os_name == \"nt\""}
925 | tornado = ">=4"
926 |
927 | [[package]]
928 | name = "testpath"
929 | version = "0.4.4"
930 | description = "Test utilities for code working with files and commands"
931 | category = "main"
932 | optional = false
933 | python-versions = "*"
934 |
935 | [package.extras]
936 | test = ["pathlib2"]
937 |
938 | [[package]]
939 | name = "tornado"
940 | version = "6.1"
941 | description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed."
942 | category = "main"
943 | optional = false
944 | python-versions = ">= 3.5"
945 |
946 | [[package]]
947 | name = "traitlets"
948 | version = "5.0.5"
949 | description = "Traitlets Python configuration system"
950 | category = "main"
951 | optional = false
952 | python-versions = ">=3.7"
953 |
954 | [package.dependencies]
955 | ipython-genutils = "*"
956 |
957 | [package.extras]
958 | test = ["pytest"]
959 |
960 | [[package]]
961 | name = "typed-ast"
962 | version = "1.4.2"
963 | description = "a fork of Python 2 and 3 ast modules with type comment support"
964 | category = "main"
965 | optional = false
966 | python-versions = "*"
967 |
968 | [[package]]
969 | name = "typing-extensions"
970 | version = "3.7.4.3"
971 | description = "Backported and Experimental Type Hints for Python 3.5+"
972 | category = "main"
973 | optional = false
974 | python-versions = "*"
975 |
976 | [[package]]
977 | name = "urllib3"
978 | version = "1.26.4"
979 | description = "HTTP library with thread-safe connection pooling, file post, and more."
980 | category = "main"
981 | optional = false
982 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
983 |
984 | [package.extras]
985 | secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
986 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
987 | brotli = ["brotlipy (>=0.6.0)"]
988 |
989 | [[package]]
990 | name = "uvicorn"
991 | version = "0.13.4"
992 | description = "The lightning-fast ASGI server."
993 | category = "main"
994 | optional = false
995 | python-versions = "*"
996 |
997 | [package.dependencies]
998 | click = ">=7.0.0,<8.0.0"
999 | h11 = ">=0.8"
1000 |
1001 | [package.extras]
1002 | standard = ["websockets (>=8.0.0,<9.0.0)", "watchgod (>=0.6)", "python-dotenv (>=0.13)", "PyYAML (>=5.1)", "httptools (>=0.1.0,<0.2.0)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "colorama (>=0.4)"]
1003 |
1004 | [[package]]
1005 | name = "wcwidth"
1006 | version = "0.2.5"
1007 | description = "Measures the displayed width of unicode strings in a terminal"
1008 | category = "main"
1009 | optional = false
1010 | python-versions = "*"
1011 |
1012 | [[package]]
1013 | name = "webencodings"
1014 | version = "0.5.1"
1015 | description = "Character encoding aliases for legacy web content"
1016 | category = "main"
1017 | optional = false
1018 | python-versions = "*"
1019 |
1020 | [metadata]
1021 | lock-version = "1.1"
1022 | python-versions = "^3.8"
1023 | content-hash = "211dd9321b7d9971756c4396fd2c122da09eb0835ee45a66ad3247175296219f"
1024 |
1025 | [metadata.files]
1026 | anyio = [
1027 | {file = "anyio-2.2.0-py3-none-any.whl", hash = "sha256:aa3da546ed17f097ca876c78024dea380a3b7fa80759abfdda59f12176a3dac8"},
1028 | {file = "anyio-2.2.0.tar.gz", hash = "sha256:4a41c5b3a65ed92e469d51b6fba3779301850ea2e352afcf9e36c46f21ee14a9"},
1029 | ]
1030 | appnope = [
1031 | {file = "appnope-0.1.2-py2.py3-none-any.whl", hash = "sha256:93aa393e9d6c54c5cd570ccadd8edad61ea0c4b9ea7a01409020c9aa019eb442"},
1032 | {file = "appnope-0.1.2.tar.gz", hash = "sha256:dd83cd4b5b460958838f6eb3000c660b1f9caf2a5b1de4264e941512f603258a"},
1033 | ]
1034 | argon2-cffi = [
1035 | {file = "argon2-cffi-20.1.0.tar.gz", hash = "sha256:d8029b2d3e4b4cea770e9e5a0104dd8fa185c1724a0f01528ae4826a6d25f97d"},
1036 | {file = "argon2_cffi-20.1.0-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:6ea92c980586931a816d61e4faf6c192b4abce89aa767ff6581e6ddc985ed003"},
1037 | {file = "argon2_cffi-20.1.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:05a8ac07c7026542377e38389638a8a1e9b78f1cd8439cd7493b39f08dd75fbf"},
1038 | {file = "argon2_cffi-20.1.0-cp27-cp27m-win32.whl", hash = "sha256:0bf066bc049332489bb2d75f69216416329d9dc65deee127152caeb16e5ce7d5"},
1039 | {file = "argon2_cffi-20.1.0-cp27-cp27m-win_amd64.whl", hash = "sha256:57358570592c46c420300ec94f2ff3b32cbccd10d38bdc12dc6979c4a8484fbc"},
1040 | {file = "argon2_cffi-20.1.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:7d455c802727710e9dfa69b74ccaab04568386ca17b0ad36350b622cd34606fe"},
1041 | {file = "argon2_cffi-20.1.0-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:b160416adc0f012fb1f12588a5e6954889510f82f698e23ed4f4fa57f12a0647"},
1042 | {file = "argon2_cffi-20.1.0-cp35-cp35m-win32.whl", hash = "sha256:9bee3212ba4f560af397b6d7146848c32a800652301843df06b9e8f68f0f7361"},
1043 | {file = "argon2_cffi-20.1.0-cp35-cp35m-win_amd64.whl", hash = "sha256:392c3c2ef91d12da510cfb6f9bae52512a4552573a9e27600bdb800e05905d2b"},
1044 | {file = "argon2_cffi-20.1.0-cp36-cp36m-win32.whl", hash = "sha256:ba7209b608945b889457f949cc04c8e762bed4fe3fec88ae9a6b7765ae82e496"},
1045 | {file = "argon2_cffi-20.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:da7f0445b71db6d3a72462e04f36544b0de871289b0bc8a7cc87c0f5ec7079fa"},
1046 | {file = "argon2_cffi-20.1.0-cp37-abi3-macosx_10_6_intel.whl", hash = "sha256:cc0e028b209a5483b6846053d5fd7165f460a1f14774d79e632e75e7ae64b82b"},
1047 | {file = "argon2_cffi-20.1.0-cp37-cp37m-win32.whl", hash = "sha256:18dee20e25e4be86680b178b35ccfc5d495ebd5792cd00781548d50880fee5c5"},
1048 | {file = "argon2_cffi-20.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:6678bb047373f52bcff02db8afab0d2a77d83bde61cfecea7c5c62e2335cb203"},
1049 | {file = "argon2_cffi-20.1.0-cp38-cp38-win32.whl", hash = "sha256:77e909cc756ef81d6abb60524d259d959bab384832f0c651ed7dcb6e5ccdbb78"},
1050 | {file = "argon2_cffi-20.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:9dfd5197852530294ecb5795c97a823839258dfd5eb9420233c7cfedec2058f2"},
1051 | {file = "argon2_cffi-20.1.0-cp39-cp39-win32.whl", hash = "sha256:e2db6e85c057c16d0bd3b4d2b04f270a7467c147381e8fd73cbbe5bc719832be"},
1052 | {file = "argon2_cffi-20.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:8a84934bd818e14a17943de8099d41160da4a336bcc699bb4c394bbb9b94bd32"},
1053 | ]
1054 | async-generator = [
1055 | {file = "async_generator-1.10-py3-none-any.whl", hash = "sha256:01c7bf666359b4967d2cda0000cc2e4af16a0ae098cbffcb8472fb9e8ad6585b"},
1056 | {file = "async_generator-1.10.tar.gz", hash = "sha256:6ebb3d106c12920aaae42ccb6f787ef5eefdcdd166ea3d628fa8476abe712144"},
1057 | ]
1058 | attrs = [
1059 | {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"},
1060 | {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"},
1061 | ]
1062 | babel = [
1063 | {file = "Babel-2.9.0-py2.py3-none-any.whl", hash = "sha256:9d35c22fcc79893c3ecc85ac4a56cde1ecf3f19c540bba0922308a6c06ca6fa5"},
1064 | {file = "Babel-2.9.0.tar.gz", hash = "sha256:da031ab54472314f210b0adcff1588ee5d1d1d0ba4dbd07b94dba82bde791e05"},
1065 | ]
1066 | backcall = [
1067 | {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"},
1068 | {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"},
1069 | ]
1070 | bleach = [
1071 | {file = "bleach-3.3.0-py2.py3-none-any.whl", hash = "sha256:6123ddc1052673e52bab52cdc955bcb57a015264a1c57d37bea2f6b817af0125"},
1072 | {file = "bleach-3.3.0.tar.gz", hash = "sha256:98b3170739e5e83dd9dc19633f074727ad848cbedb6026708c8ac2d3b697a433"},
1073 | ]
1074 | blessings = [
1075 | {file = "blessings-1.7-py2-none-any.whl", hash = "sha256:caad5211e7ba5afe04367cdd4cfc68fa886e2e08f6f35e76b7387d2109ccea6e"},
1076 | {file = "blessings-1.7-py3-none-any.whl", hash = "sha256:b1fdd7e7a675295630f9ae71527a8ebc10bfefa236b3d6aa4932ee4462c17ba3"},
1077 | {file = "blessings-1.7.tar.gz", hash = "sha256:98e5854d805f50a5b58ac2333411b0482516a8210f23f43308baeb58d77c157d"},
1078 | ]
1079 | bpython = [
1080 | {file = "bpython-0.21-py3-none-any.whl", hash = "sha256:64a2032052c629f0fc2d215cdcf3cbdc005d9001a4e8c11b2126e80899be77fb"},
1081 | {file = "bpython-0.21.tar.gz", hash = "sha256:88aa9b89974f6a7726499a2608fa7ded216d84c69e78114ab2ef996a45709487"},
1082 | ]
1083 | certifi = [
1084 | {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"},
1085 | {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"},
1086 | ]
1087 | cffi = [
1088 | {file = "cffi-1.14.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991"},
1089 | {file = "cffi-1.14.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1"},
1090 | {file = "cffi-1.14.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa"},
1091 | {file = "cffi-1.14.5-cp27-cp27m-win32.whl", hash = "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3"},
1092 | {file = "cffi-1.14.5-cp27-cp27m-win_amd64.whl", hash = "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5"},
1093 | {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482"},
1094 | {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6"},
1095 | {file = "cffi-1.14.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045"},
1096 | {file = "cffi-1.14.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa"},
1097 | {file = "cffi-1.14.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406"},
1098 | {file = "cffi-1.14.5-cp35-cp35m-win32.whl", hash = "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369"},
1099 | {file = "cffi-1.14.5-cp35-cp35m-win_amd64.whl", hash = "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315"},
1100 | {file = "cffi-1.14.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892"},
1101 | {file = "cffi-1.14.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058"},
1102 | {file = "cffi-1.14.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5"},
1103 | {file = "cffi-1.14.5-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132"},
1104 | {file = "cffi-1.14.5-cp36-cp36m-win32.whl", hash = "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53"},
1105 | {file = "cffi-1.14.5-cp36-cp36m-win_amd64.whl", hash = "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813"},
1106 | {file = "cffi-1.14.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73"},
1107 | {file = "cffi-1.14.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06"},
1108 | {file = "cffi-1.14.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1"},
1109 | {file = "cffi-1.14.5-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49"},
1110 | {file = "cffi-1.14.5-cp37-cp37m-win32.whl", hash = "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62"},
1111 | {file = "cffi-1.14.5-cp37-cp37m-win_amd64.whl", hash = "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4"},
1112 | {file = "cffi-1.14.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053"},
1113 | {file = "cffi-1.14.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0"},
1114 | {file = "cffi-1.14.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e"},
1115 | {file = "cffi-1.14.5-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827"},
1116 | {file = "cffi-1.14.5-cp38-cp38-win32.whl", hash = "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e"},
1117 | {file = "cffi-1.14.5-cp38-cp38-win_amd64.whl", hash = "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396"},
1118 | {file = "cffi-1.14.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea"},
1119 | {file = "cffi-1.14.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322"},
1120 | {file = "cffi-1.14.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c"},
1121 | {file = "cffi-1.14.5-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee"},
1122 | {file = "cffi-1.14.5-cp39-cp39-win32.whl", hash = "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396"},
1123 | {file = "cffi-1.14.5-cp39-cp39-win_amd64.whl", hash = "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d"},
1124 | {file = "cffi-1.14.5.tar.gz", hash = "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"},
1125 | ]
1126 | chardet = [
1127 | {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"},
1128 | {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"},
1129 | ]
1130 | click = [
1131 | {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"},
1132 | {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"},
1133 | ]
1134 | colorama = [
1135 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
1136 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
1137 | ]
1138 | curtsies = [
1139 | {file = "curtsies-0.3.5.tar.gz", hash = "sha256:a587ff3335667a32be7afed163f60a1c82c5d9c848d8297534a06fd29de20dbd"},
1140 | ]
1141 | cwcwidth = [
1142 | {file = "cwcwidth-0.1.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0614e892110401284fec5850ee45846d5ff163654574d3df040f86f02ec05399"},
1143 | {file = "cwcwidth-0.1.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:ffb278e25d3ff9789dca99dcb666469a390ff226b181f846cc8736f1554ff085"},
1144 | {file = "cwcwidth-0.1.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:77281cd94e6d582f3459e1535305cb3ad0afd3fbed0bacbe2e84b7e5cb3e9123"},
1145 | {file = "cwcwidth-0.1.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:3a93491f4cbe5fc821bae02ebcccfa5b9206f441fa3ef618dc6f62fdccde0f07"},
1146 | {file = "cwcwidth-0.1.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:ede2a05f88e3ddc4be22591fd5c5491e8a94f6e7fd3c93a3a06164f4ce8690d0"},
1147 | {file = "cwcwidth-0.1.4-cp36-cp36m-win32.whl", hash = "sha256:d76c3b5078355e78ca3aa0fd06939a9793f5a9f9bf4522738fff90fb58b47429"},
1148 | {file = "cwcwidth-0.1.4-cp36-cp36m-win_amd64.whl", hash = "sha256:d5a487c6981bf157b67f83514a754df5e6713a9090df71558a2625788c4a448a"},
1149 | {file = "cwcwidth-0.1.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d8f8464656b48549d2a8ac776eed5c6f10906ee2dcc3767ef8228cc582857f6d"},
1150 | {file = "cwcwidth-0.1.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:a85539ec3b879177eb1715bda5bd2bb9753d84569f8717684f07016efb92a5c7"},
1151 | {file = "cwcwidth-0.1.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:53ec58c6478af6062e979a89fc11ec6ca1e4254e93f3305ac62da28809745185"},
1152 | {file = "cwcwidth-0.1.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:3bec2366e89de99c6ca8dcd1c92156d60efdbb47dc3a9cdb86d7064773c05d65"},
1153 | {file = "cwcwidth-0.1.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:5a7da558423d32064bb8cabe461824543c6072ecdf2d0c2adf521dc63b3f0073"},
1154 | {file = "cwcwidth-0.1.4-cp37-cp37m-win32.whl", hash = "sha256:ec9d57742804a975a75dc633ee3a0bb5bffe67dc897def6a3d84717805584dbd"},
1155 | {file = "cwcwidth-0.1.4-cp37-cp37m-win_amd64.whl", hash = "sha256:9faa4adcdb0c74fb8350da8eee6f893dde5b9a0f817ee0b83607b8e0e4d12929"},
1156 | {file = "cwcwidth-0.1.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b9d0188488c55d947f71d48f47e7f8e4355d75a86afcc8932a500cd84e32e278"},
1157 | {file = "cwcwidth-0.1.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:73d66da4f1807cc673cf924c9fd83f9f61465af13693f5ef2b5b4b9c32faa0c7"},
1158 | {file = "cwcwidth-0.1.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:ef08bc8af421e5991ff6c2e67124add008e73ed7fd4fb8767f44c07b789fe114"},
1159 | {file = "cwcwidth-0.1.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:3011f108504e3ad6472f53df0b7a12b9a978e6e0e41bd841a768a6a5f678bc0e"},
1160 | {file = "cwcwidth-0.1.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:55969b269a11f317c29b206e74cae02267af92a3a9a2fb86860a84f64366705a"},
1161 | {file = "cwcwidth-0.1.4-cp38-cp38-win32.whl", hash = "sha256:51481cb731c6d9c46a5d751bafff03ea3072f856c590fe8d4a27a1d404bb20be"},
1162 | {file = "cwcwidth-0.1.4-cp38-cp38-win_amd64.whl", hash = "sha256:146069bc61cb5db11d3c037b057454d78ef2254932f4f4871ae355e0923ce8e7"},
1163 | {file = "cwcwidth-0.1.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2fc0d1c4214f76ba7fec60aac6e1467588d865a0005ce9063c5471c57751f895"},
1164 | {file = "cwcwidth-0.1.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:b1d75b2c9edc19a579dd5d92e93dc7087b6430a250928a06527aa6ebd627b06c"},
1165 | {file = "cwcwidth-0.1.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:63190cb5b87a568ed89cfae3be203935a14dea0c10b116160a15031273771b44"},
1166 | {file = "cwcwidth-0.1.4-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:99fb16a3b0258ee2fa952e7dab80b839b990aebdb96b98b648211a99e8a0c906"},
1167 | {file = "cwcwidth-0.1.4-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:01b630049fdd8fc37f0e929d24012fee7855d8aa3f304c8a0c26caf2415c7d85"},
1168 | {file = "cwcwidth-0.1.4-cp39-cp39-win32.whl", hash = "sha256:0e05498c57629bf6c8445b17b2e5a9ec26c0f97080cb7ae2602e14a5db67209b"},
1169 | {file = "cwcwidth-0.1.4-cp39-cp39-win_amd64.whl", hash = "sha256:7779cb2ccc04694f95134d3f660216f32be5de82101dcbd8f1c8f81ff748ae41"},
1170 | {file = "cwcwidth-0.1.4.tar.gz", hash = "sha256:482a937891a6918667436e0a7041aab576c26e4bcbcdddd178ef79362fbcf9ab"},
1171 | ]
1172 | decorator = [
1173 | {file = "decorator-4.4.2-py2.py3-none-any.whl", hash = "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760"},
1174 | {file = "decorator-4.4.2.tar.gz", hash = "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"},
1175 | ]
1176 | defusedxml = [
1177 | {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"},
1178 | {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"},
1179 | ]
1180 | entrypoints = [
1181 | {file = "entrypoints-0.3-py2.py3-none-any.whl", hash = "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19"},
1182 | {file = "entrypoints-0.3.tar.gz", hash = "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"},
1183 | ]
1184 | fastapi = [
1185 | {file = "fastapi-0.63.0-py3-none-any.whl", hash = "sha256:98d8ea9591d8512fdadf255d2a8fa56515cdd8624dca4af369da73727409508e"},
1186 | {file = "fastapi-0.63.0.tar.gz", hash = "sha256:63c4592f5ef3edf30afa9a44fa7c6b7ccb20e0d3f68cd9eba07b44d552058dcb"},
1187 | ]
1188 | greenlet = [
1189 | {file = "greenlet-1.0.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:1d1d4473ecb1c1d31ce8fd8d91e4da1b1f64d425c1dc965edc4ed2a63cfa67b2"},
1190 | {file = "greenlet-1.0.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:cfd06e0f0cc8db2a854137bd79154b61ecd940dce96fad0cba23fe31de0b793c"},
1191 | {file = "greenlet-1.0.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:eb333b90036358a0e2c57373f72e7648d7207b76ef0bd00a4f7daad1f79f5203"},
1192 | {file = "greenlet-1.0.0-cp27-cp27m-win32.whl", hash = "sha256:1a1ada42a1fd2607d232ae11a7b3195735edaa49ea787a6d9e6a53afaf6f3476"},
1193 | {file = "greenlet-1.0.0-cp27-cp27m-win_amd64.whl", hash = "sha256:f6f65bf54215e4ebf6b01e4bb94c49180a589573df643735107056f7a910275b"},
1194 | {file = "greenlet-1.0.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:f59eded163d9752fd49978e0bab7a1ff21b1b8d25c05f0995d140cc08ac83379"},
1195 | {file = "greenlet-1.0.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:875d4c60a6299f55df1c3bb870ebe6dcb7db28c165ab9ea6cdc5d5af36bb33ce"},
1196 | {file = "greenlet-1.0.0-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:1bb80c71de788b36cefb0c3bb6bfab306ba75073dbde2829c858dc3ad70f867c"},
1197 | {file = "greenlet-1.0.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b5f1b333015d53d4b381745f5de842f19fe59728b65f0fbb662dafbe2018c3a5"},
1198 | {file = "greenlet-1.0.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:5352c15c1d91d22902582e891f27728d8dac3bd5e0ee565b6a9f575355e6d92f"},
1199 | {file = "greenlet-1.0.0-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:2c65320774a8cd5fdb6e117c13afa91c4707548282464a18cf80243cf976b3e6"},
1200 | {file = "greenlet-1.0.0-cp35-cp35m-manylinux2014_ppc64le.whl", hash = "sha256:111cfd92d78f2af0bc7317452bd93a477128af6327332ebf3c2be7df99566683"},
1201 | {file = "greenlet-1.0.0-cp35-cp35m-win32.whl", hash = "sha256:cdb90267650c1edb54459cdb51dab865f6c6594c3a47ebd441bc493360c7af70"},
1202 | {file = "greenlet-1.0.0-cp35-cp35m-win_amd64.whl", hash = "sha256:eac8803c9ad1817ce3d8d15d1bb82c2da3feda6bee1153eec5c58fa6e5d3f770"},
1203 | {file = "greenlet-1.0.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:c93d1a71c3fe222308939b2e516c07f35a849c5047f0197442a4d6fbcb4128ee"},
1204 | {file = "greenlet-1.0.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:122c63ba795fdba4fc19c744df6277d9cfd913ed53d1a286f12189a0265316dd"},
1205 | {file = "greenlet-1.0.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:c5b22b31c947ad8b6964d4ed66776bcae986f73669ba50620162ba7c832a6b6a"},
1206 | {file = "greenlet-1.0.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:4365eccd68e72564c776418c53ce3c5af402bc526fe0653722bc89efd85bf12d"},
1207 | {file = "greenlet-1.0.0-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:da7d09ad0f24270b20f77d56934e196e982af0d0a2446120cb772be4e060e1a2"},
1208 | {file = "greenlet-1.0.0-cp36-cp36m-win32.whl", hash = "sha256:647ba1df86d025f5a34043451d7c4a9f05f240bee06277a524daad11f997d1e7"},
1209 | {file = "greenlet-1.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:e6e9fdaf6c90d02b95e6b0709aeb1aba5affbbb9ccaea5502f8638e4323206be"},
1210 | {file = "greenlet-1.0.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:62afad6e5fd70f34d773ffcbb7c22657e1d46d7fd7c95a43361de979f0a45aef"},
1211 | {file = "greenlet-1.0.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d3789c1c394944084b5e57c192889985a9f23bd985f6d15728c745d380318128"},
1212 | {file = "greenlet-1.0.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:f5e2d36c86c7b03c94b8459c3bd2c9fe2c7dab4b258b8885617d44a22e453fb7"},
1213 | {file = "greenlet-1.0.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:292e801fcb3a0b3a12d8c603c7cf340659ea27fd73c98683e75800d9fd8f704c"},
1214 | {file = "greenlet-1.0.0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:f3dc68272990849132d6698f7dc6df2ab62a88b0d36e54702a8fd16c0490e44f"},
1215 | {file = "greenlet-1.0.0-cp37-cp37m-win32.whl", hash = "sha256:7cd5a237f241f2764324396e06298b5dee0df580cf06ef4ada0ff9bff851286c"},
1216 | {file = "greenlet-1.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:0ddd77586553e3daf439aa88b6642c5f252f7ef79a39271c25b1d4bf1b7cbb85"},
1217 | {file = "greenlet-1.0.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:90b6a25841488cf2cb1c8623a53e6879573010a669455046df5f029d93db51b7"},
1218 | {file = "greenlet-1.0.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:ed1d1351f05e795a527abc04a0d82e9aecd3bdf9f46662c36ff47b0b00ecaf06"},
1219 | {file = "greenlet-1.0.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:94620ed996a7632723a424bccb84b07e7b861ab7bb06a5aeb041c111dd723d36"},
1220 | {file = "greenlet-1.0.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:f97d83049715fd9dec7911860ecf0e17b48d8725de01e45de07d8ac0bd5bc378"},
1221 | {file = "greenlet-1.0.0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:0a77691f0080c9da8dfc81e23f4e3cffa5accf0f5b56478951016d7cfead9196"},
1222 | {file = "greenlet-1.0.0-cp38-cp38-win32.whl", hash = "sha256:e1128e022d8dce375362e063754e129750323b67454cac5600008aad9f54139e"},
1223 | {file = "greenlet-1.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:5d4030b04061fdf4cbc446008e238e44936d77a04b2b32f804688ad64197953c"},
1224 | {file = "greenlet-1.0.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:f8450d5ef759dbe59f84f2c9f77491bb3d3c44bc1a573746daf086e70b14c243"},
1225 | {file = "greenlet-1.0.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:df8053867c831b2643b2c489fe1d62049a98566b1646b194cc815f13e27b90df"},
1226 | {file = "greenlet-1.0.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:df3e83323268594fa9755480a442cabfe8d82b21aba815a71acf1bb6c1776218"},
1227 | {file = "greenlet-1.0.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:181300f826625b7fd1182205b830642926f52bd8cdb08b34574c9d5b2b1813f7"},
1228 | {file = "greenlet-1.0.0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:58ca0f078d1c135ecf1879d50711f925ee238fe773dfe44e206d7d126f5bc664"},
1229 | {file = "greenlet-1.0.0-cp39-cp39-win32.whl", hash = "sha256:5f297cb343114b33a13755032ecf7109b07b9a0020e841d1c3cedff6602cc139"},
1230 | {file = "greenlet-1.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:5d69bbd9547d3bc49f8a545db7a0bd69f407badd2ff0f6e1a163680b5841d2b0"},
1231 | {file = "greenlet-1.0.0.tar.gz", hash = "sha256:719e169c79255816cdcf6dccd9ed2d089a72a9f6c42273aae12d55e8d35bdcf8"},
1232 | ]
1233 | h11 = [
1234 | {file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"},
1235 | {file = "h11-0.12.0.tar.gz", hash = "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"},
1236 | ]
1237 | idna = [
1238 | {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"},
1239 | {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"},
1240 | ]
1241 | ipykernel = [
1242 | {file = "ipykernel-5.5.0-py3-none-any.whl", hash = "sha256:efd07253b54d84d26e0878d268c8c3a41582a18750da633c2febfd2ece0d467d"},
1243 | {file = "ipykernel-5.5.0.tar.gz", hash = "sha256:98321abefdf0505fb3dc7601f60fc4087364d394bd8fad53107eb1adee9ff475"},
1244 | ]
1245 | ipython = [
1246 | {file = "ipython-7.21.0-py3-none-any.whl", hash = "sha256:34207ffb2f653bced2bc8e3756c1db86e7d93e44ed049daae9814fed66d408ec"},
1247 | {file = "ipython-7.21.0.tar.gz", hash = "sha256:04323f72d5b85b606330b6d7e2dc8d2683ad46c3905e955aa96ecc7a99388e70"},
1248 | ]
1249 | ipython-genutils = [
1250 | {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"},
1251 | {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"},
1252 | ]
1253 | jedi = [
1254 | {file = "jedi-0.18.0-py2.py3-none-any.whl", hash = "sha256:18456d83f65f400ab0c2d3319e48520420ef43b23a086fdc05dff34132f0fb93"},
1255 | {file = "jedi-0.18.0.tar.gz", hash = "sha256:92550a404bad8afed881a137ec9a461fed49eca661414be45059329614ed0707"},
1256 | ]
1257 | jinja2 = [
1258 | {file = "Jinja2-2.11.3-py2.py3-none-any.whl", hash = "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419"},
1259 | {file = "Jinja2-2.11.3.tar.gz", hash = "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"},
1260 | ]
1261 | json5 = [
1262 | {file = "json5-0.9.5-py2.py3-none-any.whl", hash = "sha256:af1a1b9a2850c7f62c23fde18be4749b3599fd302f494eebf957e2ada6b9e42c"},
1263 | {file = "json5-0.9.5.tar.gz", hash = "sha256:703cfee540790576b56a92e1c6aaa6c4b0d98971dc358ead83812aa4d06bdb96"},
1264 | ]
1265 | jsonschema = [
1266 | {file = "jsonschema-3.2.0-py2.py3-none-any.whl", hash = "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163"},
1267 | {file = "jsonschema-3.2.0.tar.gz", hash = "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a"},
1268 | ]
1269 | jupyter-client = [
1270 | {file = "jupyter_client-6.1.12-py3-none-any.whl", hash = "sha256:e053a2c44b6fa597feebe2b3ecb5eea3e03d1d91cc94351a52931ee1426aecfc"},
1271 | {file = "jupyter_client-6.1.12.tar.gz", hash = "sha256:c4bca1d0846186ca8be97f4d2fa6d2bae889cce4892a167ffa1ba6bd1f73e782"},
1272 | ]
1273 | jupyter-core = [
1274 | {file = "jupyter_core-4.7.1-py3-none-any.whl", hash = "sha256:8c6c0cac5c1b563622ad49321d5ec47017bd18b94facb381c6973a0486395f8e"},
1275 | {file = "jupyter_core-4.7.1.tar.gz", hash = "sha256:79025cb3225efcd36847d0840f3fc672c0abd7afd0de83ba8a1d3837619122b4"},
1276 | ]
1277 | jupyter-packaging = [
1278 | {file = "jupyter-packaging-0.7.12.tar.gz", hash = "sha256:b140325771881a7df7b7f2d14997b619063fe75ae756b9025852e4346000bbb8"},
1279 | {file = "jupyter_packaging-0.7.12-py2.py3-none-any.whl", hash = "sha256:e36efa5edd52b302f0b784ff2a4d1f2cd50f7058af331151315e98b73f947b8d"},
1280 | ]
1281 | jupyter-server = [
1282 | {file = "jupyter_server-1.5.1-py3-none-any.whl", hash = "sha256:951b944f25dcf7e34106415859630273f59a472fdb24e866f63ef431b232d3a0"},
1283 | {file = "jupyter_server-1.5.1.tar.gz", hash = "sha256:db646c991c17ad3dff12e419d36e2b91025912dfd21d13f8ddd524531e6d63bf"},
1284 | ]
1285 | jupyterlab = [
1286 | {file = "jupyterlab-3.0.12-py3-none-any.whl", hash = "sha256:dbbbf6329c18422e7e22e95494933e7ea28b0fad09965cd60431b1b857f80ca2"},
1287 | {file = "jupyterlab-3.0.12.tar.gz", hash = "sha256:929c60d7fb4aa704084c02d8ededc209b8b378e0b3adab46158b7fa6acc24230"},
1288 | ]
1289 | jupyterlab-pygments = [
1290 | {file = "jupyterlab_pygments-0.1.2-py2.py3-none-any.whl", hash = "sha256:abfb880fd1561987efaefcb2d2ac75145d2a5d0139b1876d5be806e32f630008"},
1291 | {file = "jupyterlab_pygments-0.1.2.tar.gz", hash = "sha256:cfcda0873626150932f438eccf0f8bf22bfa92345b814890ab360d666b254146"},
1292 | ]
1293 | jupyterlab-server = [
1294 | {file = "jupyterlab_server-2.3.0-py3-none-any.whl", hash = "sha256:653cb811739e59f2f6998f659b4635a90c110a128e0245ce27dc3fe02c46543a"},
1295 | {file = "jupyterlab_server-2.3.0.tar.gz", hash = "sha256:e7a0245aa3de23a1803de2eff401e4ca4594538d9f59806134f30419a6d8b6a3"},
1296 | ]
1297 | markupsafe = [
1298 | {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"},
1299 | {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"},
1300 | {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"},
1301 | {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"},
1302 | {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"},
1303 | {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"},
1304 | {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"},
1305 | {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"},
1306 | {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"},
1307 | {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"},
1308 | {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"},
1309 | {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"},
1310 | {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"},
1311 | {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"},
1312 | {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"},
1313 | {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"},
1314 | {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"},
1315 | {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"},
1316 | {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"},
1317 | {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"},
1318 | {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"},
1319 | {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"},
1320 | {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"},
1321 | {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"},
1322 | {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"},
1323 | {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"},
1324 | {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"},
1325 | {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"},
1326 | {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"},
1327 | {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"},
1328 | {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"},
1329 | {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"},
1330 | {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"},
1331 | ]
1332 | mistune = [
1333 | {file = "mistune-0.8.4-py2.py3-none-any.whl", hash = "sha256:88a1051873018da288eee8538d476dffe1262495144b33ecb586c4ab266bb8d4"},
1334 | {file = "mistune-0.8.4.tar.gz", hash = "sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e"},
1335 | ]
1336 | mypy = [
1337 | {file = "mypy-0.812-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a26f8ec704e5a7423c8824d425086705e381b4f1dfdef6e3a1edab7ba174ec49"},
1338 | {file = "mypy-0.812-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:28fb5479c494b1bab244620685e2eb3c3f988d71fd5d64cc753195e8ed53df7c"},
1339 | {file = "mypy-0.812-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:9743c91088d396c1a5a3c9978354b61b0382b4e3c440ce83cf77994a43e8c521"},
1340 | {file = "mypy-0.812-cp35-cp35m-win_amd64.whl", hash = "sha256:d7da2e1d5f558c37d6e8c1246f1aec1e7349e4913d8fb3cb289a35de573fe2eb"},
1341 | {file = "mypy-0.812-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:4eec37370483331d13514c3f55f446fc5248d6373e7029a29ecb7b7494851e7a"},
1342 | {file = "mypy-0.812-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d65cc1df038ef55a99e617431f0553cd77763869eebdf9042403e16089fe746c"},
1343 | {file = "mypy-0.812-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:61a3d5b97955422964be6b3baf05ff2ce7f26f52c85dd88db11d5e03e146a3a6"},
1344 | {file = "mypy-0.812-cp36-cp36m-win_amd64.whl", hash = "sha256:25adde9b862f8f9aac9d2d11971f226bd4c8fbaa89fb76bdadb267ef22d10064"},
1345 | {file = "mypy-0.812-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:552a815579aa1e995f39fd05dde6cd378e191b063f031f2acfe73ce9fb7f9e56"},
1346 | {file = "mypy-0.812-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:499c798053cdebcaa916eef8cd733e5584b5909f789de856b482cd7d069bdad8"},
1347 | {file = "mypy-0.812-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:5873888fff1c7cf5b71efbe80e0e73153fe9212fafdf8e44adfe4c20ec9f82d7"},
1348 | {file = "mypy-0.812-cp37-cp37m-win_amd64.whl", hash = "sha256:9f94aac67a2045ec719ffe6111df543bac7874cee01f41928f6969756e030564"},
1349 | {file = "mypy-0.812-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d23e0ea196702d918b60c8288561e722bf437d82cb7ef2edcd98cfa38905d506"},
1350 | {file = "mypy-0.812-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:674e822aa665b9fd75130c6c5f5ed9564a38c6cea6a6432ce47eafb68ee578c5"},
1351 | {file = "mypy-0.812-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:abf7e0c3cf117c44d9285cc6128856106183938c68fd4944763003decdcfeb66"},
1352 | {file = "mypy-0.812-cp38-cp38-win_amd64.whl", hash = "sha256:0d0a87c0e7e3a9becdfbe936c981d32e5ee0ccda3e0f07e1ef2c3d1a817cf73e"},
1353 | {file = "mypy-0.812-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7ce3175801d0ae5fdfa79b4f0cfed08807af4d075b402b7e294e6aa72af9aa2a"},
1354 | {file = "mypy-0.812-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:b09669bcda124e83708f34a94606e01b614fa71931d356c1f1a5297ba11f110a"},
1355 | {file = "mypy-0.812-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:33f159443db0829d16f0a8d83d94df3109bb6dd801975fe86bacb9bf71628e97"},
1356 | {file = "mypy-0.812-cp39-cp39-win_amd64.whl", hash = "sha256:3f2aca7f68580dc2508289c729bd49ee929a436208d2b2b6aab15745a70a57df"},
1357 | {file = "mypy-0.812-py3-none-any.whl", hash = "sha256:2f9b3407c58347a452fc0736861593e105139b905cca7d097e413453a1d650b4"},
1358 | {file = "mypy-0.812.tar.gz", hash = "sha256:cd07039aa5df222037005b08fbbfd69b3ab0b0bd7a07d7906de75ae52c4e3119"},
1359 | ]
1360 | mypy-extensions = [
1361 | {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
1362 | {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
1363 | ]
1364 | nbclassic = [
1365 | {file = "nbclassic-0.2.6-py3-none-any.whl", hash = "sha256:0248333262d6f90c2fbe05aacb4f008f1d71b5250a9f737488e0a03cfa1c6ed5"},
1366 | {file = "nbclassic-0.2.6.tar.gz", hash = "sha256:b649436ff85dc731ba8115deef089e5abbe827d7a6dccbad42c15b8d427104e8"},
1367 | ]
1368 | nbclient = [
1369 | {file = "nbclient-0.5.3-py3-none-any.whl", hash = "sha256:e79437364a2376892b3f46bedbf9b444e5396cfb1bc366a472c37b48e9551500"},
1370 | {file = "nbclient-0.5.3.tar.gz", hash = "sha256:db17271330c68c8c88d46d72349e24c147bb6f34ec82d8481a8f025c4d26589c"},
1371 | ]
1372 | nbconvert = [
1373 | {file = "nbconvert-6.0.7-py3-none-any.whl", hash = "sha256:39e9f977920b203baea0be67eea59f7b37a761caa542abe80f5897ce3cf6311d"},
1374 | {file = "nbconvert-6.0.7.tar.gz", hash = "sha256:cbbc13a86dfbd4d1b5dee106539de0795b4db156c894c2c5dc382062bbc29002"},
1375 | ]
1376 | nbformat = [
1377 | {file = "nbformat-5.1.2-py3-none-any.whl", hash = "sha256:3949fdc8f5fa0b1afca16fb307546e78494fa7a7bceff880df8168eafda0e7ac"},
1378 | {file = "nbformat-5.1.2.tar.gz", hash = "sha256:1d223e64a18bfa7cdf2db2e9ba8a818312fc2a0701d2e910b58df66809385a56"},
1379 | ]
1380 | nest-asyncio = [
1381 | {file = "nest_asyncio-1.5.1-py3-none-any.whl", hash = "sha256:76d6e972265063fe92a90b9cc4fb82616e07d586b346ed9d2c89a4187acea39c"},
1382 | {file = "nest_asyncio-1.5.1.tar.gz", hash = "sha256:afc5a1c515210a23c461932765691ad39e8eba6551c055ac8d5546e69250d0aa"},
1383 | ]
1384 | notebook = [
1385 | {file = "notebook-6.3.0-py3-none-any.whl", hash = "sha256:cb271af1e8134e3d6fc6d458bdc79c40cbfc84c1eb036a493f216d58f0880e92"},
1386 | {file = "notebook-6.3.0.tar.gz", hash = "sha256:cbc9398d6c81473e9cdb891d2cae9c0d3718fca289dda6d26df5cb660fcadc7d"},
1387 | ]
1388 | packaging = [
1389 | {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"},
1390 | {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"},
1391 | ]
1392 | pandocfilters = [
1393 | {file = "pandocfilters-1.4.3.tar.gz", hash = "sha256:bc63fbb50534b4b1f8ebe1860889289e8af94a23bff7445259592df25a3906eb"},
1394 | ]
1395 | parso = [
1396 | {file = "parso-0.8.1-py2.py3-none-any.whl", hash = "sha256:15b00182f472319383252c18d5913b69269590616c947747bc50bf4ac768f410"},
1397 | {file = "parso-0.8.1.tar.gz", hash = "sha256:8519430ad07087d4c997fda3a7918f7cfa27cb58972a8c89c2a0295a1c940e9e"},
1398 | ]
1399 | pexpect = [
1400 | {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"},
1401 | {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"},
1402 | ]
1403 | pickleshare = [
1404 | {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"},
1405 | {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"},
1406 | ]
1407 | prometheus-client = [
1408 | {file = "prometheus_client-0.9.0-py2.py3-none-any.whl", hash = "sha256:b08c34c328e1bf5961f0b4352668e6c8f145b4a087e09b7296ef62cbe4693d35"},
1409 | {file = "prometheus_client-0.9.0.tar.gz", hash = "sha256:9da7b32f02439d8c04f7777021c304ed51d9ec180604700c1ba72a4d44dceb03"},
1410 | ]
1411 | prompt-toolkit = [
1412 | {file = "prompt_toolkit-3.0.18-py3-none-any.whl", hash = "sha256:bf00f22079f5fadc949f42ae8ff7f05702826a97059ffcc6281036ad40ac6f04"},
1413 | {file = "prompt_toolkit-3.0.18.tar.gz", hash = "sha256:e1b4f11b9336a28fa11810bc623c357420f69dfdb6d2dac41ca2c21a55c033bc"},
1414 | ]
1415 | ptyprocess = [
1416 | {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"},
1417 | {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"},
1418 | ]
1419 | py = [
1420 | {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"},
1421 | {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"},
1422 | ]
1423 | pycparser = [
1424 | {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"},
1425 | {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"},
1426 | ]
1427 | pydantic = [
1428 | {file = "pydantic-1.8.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0c40162796fc8d0aa744875b60e4dc36834db9f2a25dbf9ba9664b1915a23850"},
1429 | {file = "pydantic-1.8.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:fff29fe54ec419338c522b908154a2efabeee4f483e48990f87e189661f31ce3"},
1430 | {file = "pydantic-1.8.1-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:fbfb608febde1afd4743c6822c19060a8dbdd3eb30f98e36061ba4973308059e"},
1431 | {file = "pydantic-1.8.1-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:eb8ccf12295113ce0de38f80b25f736d62f0a8d87c6b88aca645f168f9c78771"},
1432 | {file = "pydantic-1.8.1-cp36-cp36m-win_amd64.whl", hash = "sha256:20d42f1be7c7acc352b3d09b0cf505a9fab9deb93125061b376fbe1f06a5459f"},
1433 | {file = "pydantic-1.8.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dde4ca368e82791de97c2ec019681ffb437728090c0ff0c3852708cf923e0c7d"},
1434 | {file = "pydantic-1.8.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:3bbd023c981cbe26e6e21c8d2ce78485f85c2e77f7bab5ec15b7d2a1f491918f"},
1435 | {file = "pydantic-1.8.1-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:830ef1a148012b640186bf4d9789a206c56071ff38f2460a32ae67ca21880eb8"},
1436 | {file = "pydantic-1.8.1-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:fb77f7a7e111db1832ae3f8f44203691e15b1fa7e5a1cb9691d4e2659aee41c4"},
1437 | {file = "pydantic-1.8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:3bcb9d7e1f9849a6bdbd027aabb3a06414abd6068cb3b21c49427956cce5038a"},
1438 | {file = "pydantic-1.8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2287ebff0018eec3cc69b1d09d4b7cebf277726fa1bd96b45806283c1d808683"},
1439 | {file = "pydantic-1.8.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:4bbc47cf7925c86a345d03b07086696ed916c7663cb76aa409edaa54546e53e2"},
1440 | {file = "pydantic-1.8.1-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:6388ef4ef1435364c8cc9a8192238aed030595e873d8462447ccef2e17387125"},
1441 | {file = "pydantic-1.8.1-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:dd4888b300769ecec194ca8f2699415f5f7760365ddbe243d4fd6581485fa5f0"},
1442 | {file = "pydantic-1.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:8fbb677e4e89c8ab3d450df7b1d9caed23f254072e8597c33279460eeae59b99"},
1443 | {file = "pydantic-1.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2f2736d9a996b976cfdfe52455ad27462308c9d3d0ae21a2aa8b4cd1a78f47b9"},
1444 | {file = "pydantic-1.8.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:3114d74329873af0a0e8004627f5389f3bb27f956b965ddd3e355fe984a1789c"},
1445 | {file = "pydantic-1.8.1-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:258576f2d997ee4573469633592e8b99aa13bda182fcc28e875f866016c8e07e"},
1446 | {file = "pydantic-1.8.1-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:c17a0b35c854049e67c68b48d55e026c84f35593c66d69b278b8b49e2484346f"},
1447 | {file = "pydantic-1.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:e8bc082afef97c5fd3903d05c6f7bb3a6af9fc18631b4cc9fedeb4720efb0c58"},
1448 | {file = "pydantic-1.8.1-py3-none-any.whl", hash = "sha256:e3f8790c47ac42549dc8b045a67b0ca371c7f66e73040d0197ce6172b385e520"},
1449 | {file = "pydantic-1.8.1.tar.gz", hash = "sha256:26cf3cb2e68ec6c0cfcb6293e69fb3450c5fd1ace87f46b64f678b0d29eac4c3"},
1450 | ]
1451 | pygments = [
1452 | {file = "Pygments-2.8.1-py3-none-any.whl", hash = "sha256:534ef71d539ae97d4c3a4cf7d6f110f214b0e687e92f9cb9d2a3b0d3101289c8"},
1453 | {file = "Pygments-2.8.1.tar.gz", hash = "sha256:2656e1a6edcdabf4275f9a3640db59fd5de107d88e8663c5d4e9a0fa62f77f94"},
1454 | ]
1455 | pyparsing = [
1456 | {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
1457 | {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
1458 | ]
1459 | pyrsistent = [
1460 | {file = "pyrsistent-0.17.3.tar.gz", hash = "sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e"},
1461 | ]
1462 | python-dateutil = [
1463 | {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"},
1464 | {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"},
1465 | ]
1466 | python-devtools = [
1467 | {file = "python-devtools-2.tar.gz", hash = "sha256:2bb8e37305818dc80b5a23335aef54c602e5b6fd2572af38600d1ca1bcae89a1"},
1468 | ]
1469 | pytz = [
1470 | {file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"},
1471 | {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"},
1472 | ]
1473 | pywin32 = [
1474 | {file = "pywin32-300-cp35-cp35m-win32.whl", hash = "sha256:1c204a81daed2089e55d11eefa4826c05e604d27fe2be40b6bf8db7b6a39da63"},
1475 | {file = "pywin32-300-cp35-cp35m-win_amd64.whl", hash = "sha256:350c5644775736351b77ba68da09a39c760d75d2467ecec37bd3c36a94fbed64"},
1476 | {file = "pywin32-300-cp36-cp36m-win32.whl", hash = "sha256:a3b4c48c852d4107e8a8ec980b76c94ce596ea66d60f7a697582ea9dce7e0db7"},
1477 | {file = "pywin32-300-cp36-cp36m-win_amd64.whl", hash = "sha256:27a30b887afbf05a9cbb05e3ffd43104a9b71ce292f64a635389dbad0ed1cd85"},
1478 | {file = "pywin32-300-cp37-cp37m-win32.whl", hash = "sha256:d7e8c7efc221f10d6400c19c32a031add1c4a58733298c09216f57b4fde110dc"},
1479 | {file = "pywin32-300-cp37-cp37m-win_amd64.whl", hash = "sha256:8151e4d7a19262d6694162d6da85d99a16f8b908949797fd99c83a0bfaf5807d"},
1480 | {file = "pywin32-300-cp38-cp38-win32.whl", hash = "sha256:fbb3b1b0fbd0b4fc2a3d1d81fe0783e30062c1abed1d17c32b7879d55858cfae"},
1481 | {file = "pywin32-300-cp38-cp38-win_amd64.whl", hash = "sha256:60a8fa361091b2eea27f15718f8eb7f9297e8d51b54dbc4f55f3d238093d5190"},
1482 | {file = "pywin32-300-cp39-cp39-win32.whl", hash = "sha256:638b68eea5cfc8def537e43e9554747f8dee786b090e47ead94bfdafdb0f2f50"},
1483 | {file = "pywin32-300-cp39-cp39-win_amd64.whl", hash = "sha256:b1609ce9bd5c411b81f941b246d683d6508992093203d4eb7f278f4ed1085c3f"},
1484 | ]
1485 | pywinpty = [
1486 | {file = "pywinpty-0.5.7-cp27-cp27m-win32.whl", hash = "sha256:b358cb552c0f6baf790de375fab96524a0498c9df83489b8c23f7f08795e966b"},
1487 | {file = "pywinpty-0.5.7-cp27-cp27m-win_amd64.whl", hash = "sha256:1e525a4de05e72016a7af27836d512db67d06a015aeaf2fa0180f8e6a039b3c2"},
1488 | {file = "pywinpty-0.5.7-cp35-cp35m-win32.whl", hash = "sha256:2740eeeb59297593a0d3f762269b01d0285c1b829d6827445fcd348fb47f7e70"},
1489 | {file = "pywinpty-0.5.7-cp35-cp35m-win_amd64.whl", hash = "sha256:33df97f79843b2b8b8bc5c7aaf54adec08cc1bae94ee99dfb1a93c7a67704d95"},
1490 | {file = "pywinpty-0.5.7-cp36-cp36m-win32.whl", hash = "sha256:e854211df55d107f0edfda8a80b39dfc87015bef52a8fe6594eb379240d81df2"},
1491 | {file = "pywinpty-0.5.7-cp36-cp36m-win_amd64.whl", hash = "sha256:dbd838de92de1d4ebf0dce9d4d5e4fc38d0b7b1de837947a18b57a882f219139"},
1492 | {file = "pywinpty-0.5.7-cp37-cp37m-win32.whl", hash = "sha256:5fb2c6c6819491b216f78acc2c521b9df21e0f53b9a399d58a5c151a3c4e2a2d"},
1493 | {file = "pywinpty-0.5.7-cp37-cp37m-win_amd64.whl", hash = "sha256:dd22c8efacf600730abe4a46c1388355ce0d4ab75dc79b15d23a7bd87bf05b48"},
1494 | {file = "pywinpty-0.5.7-cp38-cp38-win_amd64.whl", hash = "sha256:8fc5019ff3efb4f13708bd3b5ad327589c1a554cb516d792527361525a7cb78c"},
1495 | {file = "pywinpty-0.5.7.tar.gz", hash = "sha256:2d7e9c881638a72ffdca3f5417dd1563b60f603e1b43e5895674c2a1b01f95a0"},
1496 | ]
1497 | pyxdg = [
1498 | {file = "pyxdg-0.27-py2.py3-none-any.whl", hash = "sha256:2d6701ab7c74bbab8caa6a95e0a0a129b1643cf6c298bf7c569adec06d0709a0"},
1499 | {file = "pyxdg-0.27.tar.gz", hash = "sha256:80bd93aae5ed82435f20462ea0208fb198d8eec262e831ee06ce9ddb6b91c5a5"},
1500 | ]
1501 | pyzmq = [
1502 | {file = "pyzmq-22.0.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c0cde362075ee8f3d2b0353b283e203c2200243b5a15d5c5c03b78112a17e7d4"},
1503 | {file = "pyzmq-22.0.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:ff1ea14075bbddd6f29bf6beb8a46d0db779bcec6b9820909584081ec119f8fd"},
1504 | {file = "pyzmq-22.0.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:26380487eae4034d6c2a3fb8d0f2dff6dd0d9dd711894e8d25aa2d1938950a33"},
1505 | {file = "pyzmq-22.0.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:3e29f9cf85a40d521d048b55c63f59d6c772ac1c4bf51cdfc23b62a62e377c33"},
1506 | {file = "pyzmq-22.0.3-cp36-cp36m-win32.whl", hash = "sha256:4f34a173f813b38b83f058e267e30465ed64b22cd0cf6bad21148d3fa718f9bb"},
1507 | {file = "pyzmq-22.0.3-cp36-cp36m-win_amd64.whl", hash = "sha256:30df70f81fe210506aa354d7fd486a39b87d9f7f24c3d3f4f698ec5d96b8c084"},
1508 | {file = "pyzmq-22.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7026f0353977431fc884abd4ac28268894bd1a780ba84bb266d470b0ec26d2ed"},
1509 | {file = "pyzmq-22.0.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6d4163704201fff0f3ab0cd5d7a0ea1514ecfffd3926d62ec7e740a04d2012c7"},
1510 | {file = "pyzmq-22.0.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:763c175294d861869f18eb42901d500eda7d3fa4565f160b3b2fd2678ea0ebab"},
1511 | {file = "pyzmq-22.0.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:61e4bb6cd60caf1abcd796c3f48395e22c5b486eeca6f3a8797975c57d94b03e"},
1512 | {file = "pyzmq-22.0.3-cp37-cp37m-win32.whl", hash = "sha256:b25e5d339550a850f7e919fe8cb4c8eabe4c917613db48dab3df19bfb9a28969"},
1513 | {file = "pyzmq-22.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:3ef50d74469b03725d781a2a03c57537d86847ccde587130fe35caafea8f75c6"},
1514 | {file = "pyzmq-22.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:60e63577b85055e4cc43892fecd877b86695ee3ef12d5d10a3c5d6e77a7cc1a3"},
1515 | {file = "pyzmq-22.0.3-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:f5831eff6b125992ec65d973f5151c48003b6754030094723ac4c6e80a97c8c4"},
1516 | {file = "pyzmq-22.0.3-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:9221783dacb419604d5345d0e097bddef4459a9a95322de6c306bf1d9896559f"},
1517 | {file = "pyzmq-22.0.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:b62ea18c0458a65ccd5be90f276f7a5a3f26a6dea0066d948ce2fa896051420f"},
1518 | {file = "pyzmq-22.0.3-cp38-cp38-win32.whl", hash = "sha256:81e7df0da456206201e226491aa1fc449da85328bf33bbeec2c03bb3a9f18324"},
1519 | {file = "pyzmq-22.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:f52070871a0fd90a99130babf21f8af192304ec1e995bec2a9533efc21ea4452"},
1520 | {file = "pyzmq-22.0.3-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:c5e29fe4678f97ce429f076a2a049a3d0b2660ada8f2c621e5dc9939426056dd"},
1521 | {file = "pyzmq-22.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d18ddc6741b51f3985978f2fda57ddcdae359662d7a6b395bc8ff2292fca14bd"},
1522 | {file = "pyzmq-22.0.3-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4231943514812dfb74f44eadcf85e8dd8cf302b4d0bce450ce1357cac88dbfdc"},
1523 | {file = "pyzmq-22.0.3-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:23a74de4b43c05c3044aeba0d1f3970def8f916151a712a3ac1e5cd9c0bc2902"},
1524 | {file = "pyzmq-22.0.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:532af3e6dddea62d9c49062ece5add998c9823c2419da943cf95589f56737de0"},
1525 | {file = "pyzmq-22.0.3-cp39-cp39-win32.whl", hash = "sha256:33acd2b9790818b9d00526135acf12790649d8d34b2b04d64558b469c9d86820"},
1526 | {file = "pyzmq-22.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:a558c5bc89d56d7253187dccc4e81b5bb0eac5ae9511eb4951910a1245d04622"},
1527 | {file = "pyzmq-22.0.3-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:581787c62eaa0e0db6c5413cedc393ebbadac6ddfd22e1cf9a60da23c4f1a4b2"},
1528 | {file = "pyzmq-22.0.3-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:38e3dca75d81bec4f2defa14b0a65b74545812bb519a8e89c8df96bbf4639356"},
1529 | {file = "pyzmq-22.0.3-pp36-pypy36_pp73-win32.whl", hash = "sha256:2f971431aaebe0a8b54ac018e041c2f0b949a43745444e4dadcc80d0f0ef8457"},
1530 | {file = "pyzmq-22.0.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:da7d4d4c778c86b60949d17531e60c54ed3726878de8a7f8a6d6e7f8cc8c3205"},
1531 | {file = "pyzmq-22.0.3-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:13465c1ff969cab328bc92f7015ce3843f6e35f8871ad79d236e4fbc85dbe4cb"},
1532 | {file = "pyzmq-22.0.3-pp37-pypy37_pp73-win32.whl", hash = "sha256:279cc9b51db48bec2db146f38e336049ac5a59e5f12fb3a8ad864e238c1c62e3"},
1533 | {file = "pyzmq-22.0.3.tar.gz", hash = "sha256:f7f63ce127980d40f3e6a5fdb87abf17ce1a7c2bd8bf2c7560e1bbce8ab1f92d"},
1534 | ]
1535 | requests = [
1536 | {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"},
1537 | {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"},
1538 | ]
1539 | send2trash = [
1540 | {file = "Send2Trash-1.5.0-py3-none-any.whl", hash = "sha256:f1691922577b6fa12821234aeb57599d887c4900b9ca537948d2dac34aea888b"},
1541 | {file = "Send2Trash-1.5.0.tar.gz", hash = "sha256:60001cc07d707fe247c94f74ca6ac0d3255aabcb930529690897ca2a39db28b2"},
1542 | ]
1543 | six = [
1544 | {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"},
1545 | {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"},
1546 | ]
1547 | sniffio = [
1548 | {file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"},
1549 | {file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"},
1550 | ]
1551 | starlette = [
1552 | {file = "starlette-0.13.6-py3-none-any.whl", hash = "sha256:bd2ffe5e37fb75d014728511f8e68ebf2c80b0fa3d04ca1479f4dc752ae31ac9"},
1553 | {file = "starlette-0.13.6.tar.gz", hash = "sha256:ebe8ee08d9be96a3c9f31b2cb2a24dbdf845247b745664bd8a3f9bd0c977fdbc"},
1554 | ]
1555 | terminado = [
1556 | {file = "terminado-0.9.3-py3-none-any.whl", hash = "sha256:430e876ec9d4d93a4fd8a49e82dcfae0c25f846540d0c5ca774b397533e237e8"},
1557 | {file = "terminado-0.9.3.tar.gz", hash = "sha256:261c0b7825fecf629666e1820b484a5380f7e54d6b8bd889fa482e99dcf9bde4"},
1558 | ]
1559 | testpath = [
1560 | {file = "testpath-0.4.4-py2.py3-none-any.whl", hash = "sha256:bfcf9411ef4bf3db7579063e0546938b1edda3d69f4e1fb8756991f5951f85d4"},
1561 | {file = "testpath-0.4.4.tar.gz", hash = "sha256:60e0a3261c149755f4399a1fff7d37523179a70fdc3abdf78de9fc2604aeec7e"},
1562 | ]
1563 | tornado = [
1564 | {file = "tornado-6.1-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:d371e811d6b156d82aa5f9a4e08b58debf97c302a35714f6f45e35139c332e32"},
1565 | {file = "tornado-6.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:0d321a39c36e5f2c4ff12b4ed58d41390460f798422c4504e09eb5678e09998c"},
1566 | {file = "tornado-6.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9de9e5188a782be6b1ce866e8a51bc76a0fbaa0e16613823fc38e4fc2556ad05"},
1567 | {file = "tornado-6.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:61b32d06ae8a036a6607805e6720ef00a3c98207038444ba7fd3d169cd998910"},
1568 | {file = "tornado-6.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:3e63498f680547ed24d2c71e6497f24bca791aca2fe116dbc2bd0ac7f191691b"},
1569 | {file = "tornado-6.1-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:6c77c9937962577a6a76917845d06af6ab9197702a42e1346d8ae2e76b5e3675"},
1570 | {file = "tornado-6.1-cp35-cp35m-win32.whl", hash = "sha256:6286efab1ed6e74b7028327365cf7346b1d777d63ab30e21a0f4d5b275fc17d5"},
1571 | {file = "tornado-6.1-cp35-cp35m-win_amd64.whl", hash = "sha256:fa2ba70284fa42c2a5ecb35e322e68823288a4251f9ba9cc77be04ae15eada68"},
1572 | {file = "tornado-6.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0a00ff4561e2929a2c37ce706cb8233b7907e0cdc22eab98888aca5dd3775feb"},
1573 | {file = "tornado-6.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:748290bf9112b581c525e6e6d3820621ff020ed95af6f17fedef416b27ed564c"},
1574 | {file = "tornado-6.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:e385b637ac3acaae8022e7e47dfa7b83d3620e432e3ecb9a3f7f58f150e50921"},
1575 | {file = "tornado-6.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:25ad220258349a12ae87ede08a7b04aca51237721f63b1808d39bdb4b2164558"},
1576 | {file = "tornado-6.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:65d98939f1a2e74b58839f8c4dab3b6b3c1ce84972ae712be02845e65391ac7c"},
1577 | {file = "tornado-6.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:e519d64089b0876c7b467274468709dadf11e41d65f63bba207e04217f47c085"},
1578 | {file = "tornado-6.1-cp36-cp36m-win32.whl", hash = "sha256:b87936fd2c317b6ee08a5741ea06b9d11a6074ef4cc42e031bc6403f82a32575"},
1579 | {file = "tornado-6.1-cp36-cp36m-win_amd64.whl", hash = "sha256:cc0ee35043162abbf717b7df924597ade8e5395e7b66d18270116f8745ceb795"},
1580 | {file = "tornado-6.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7250a3fa399f08ec9cb3f7b1b987955d17e044f1ade821b32e5f435130250d7f"},
1581 | {file = "tornado-6.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ed3ad863b1b40cd1d4bd21e7498329ccaece75db5a5bf58cd3c9f130843e7102"},
1582 | {file = "tornado-6.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:dcef026f608f678c118779cd6591c8af6e9b4155c44e0d1bc0c87c036fb8c8c4"},
1583 | {file = "tornado-6.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:70dec29e8ac485dbf57481baee40781c63e381bebea080991893cd297742b8fd"},
1584 | {file = "tornado-6.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:d3f7594930c423fd9f5d1a76bee85a2c36fd8b4b16921cae7e965f22575e9c01"},
1585 | {file = "tornado-6.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:3447475585bae2e77ecb832fc0300c3695516a47d46cefa0528181a34c5b9d3d"},
1586 | {file = "tornado-6.1-cp37-cp37m-win32.whl", hash = "sha256:e7229e60ac41a1202444497ddde70a48d33909e484f96eb0da9baf8dc68541df"},
1587 | {file = "tornado-6.1-cp37-cp37m-win_amd64.whl", hash = "sha256:cb5ec8eead331e3bb4ce8066cf06d2dfef1bfb1b2a73082dfe8a161301b76e37"},
1588 | {file = "tornado-6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:20241b3cb4f425e971cb0a8e4ffc9b0a861530ae3c52f2b0434e6c1b57e9fd95"},
1589 | {file = "tornado-6.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:c77da1263aa361938476f04c4b6c8916001b90b2c2fdd92d8d535e1af48fba5a"},
1590 | {file = "tornado-6.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:fba85b6cd9c39be262fcd23865652920832b61583de2a2ca907dbd8e8a8c81e5"},
1591 | {file = "tornado-6.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:1e8225a1070cd8eec59a996c43229fe8f95689cb16e552d130b9793cb570a288"},
1592 | {file = "tornado-6.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d14d30e7f46a0476efb0deb5b61343b1526f73ebb5ed84f23dc794bdb88f9d9f"},
1593 | {file = "tornado-6.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8f959b26f2634a091bb42241c3ed8d3cedb506e7c27b8dd5c7b9f745318ddbb6"},
1594 | {file = "tornado-6.1-cp38-cp38-win32.whl", hash = "sha256:34ca2dac9e4d7afb0bed4677512e36a52f09caa6fded70b4e3e1c89dbd92c326"},
1595 | {file = "tornado-6.1-cp38-cp38-win_amd64.whl", hash = "sha256:6196a5c39286cc37c024cd78834fb9345e464525d8991c21e908cc046d1cc02c"},
1596 | {file = "tornado-6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f0ba29bafd8e7e22920567ce0d232c26d4d47c8b5cf4ed7b562b5db39fa199c5"},
1597 | {file = "tornado-6.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:33892118b165401f291070100d6d09359ca74addda679b60390b09f8ef325ffe"},
1598 | {file = "tornado-6.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7da13da6f985aab7f6f28debab00c67ff9cbacd588e8477034c0652ac141feea"},
1599 | {file = "tornado-6.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:e0791ac58d91ac58f694d8d2957884df8e4e2f6687cdf367ef7eb7497f79eaa2"},
1600 | {file = "tornado-6.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:66324e4e1beede9ac79e60f88de548da58b1f8ab4b2f1354d8375774f997e6c0"},
1601 | {file = "tornado-6.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:a48900ecea1cbb71b8c71c620dee15b62f85f7c14189bdeee54966fbd9a0c5bd"},
1602 | {file = "tornado-6.1-cp39-cp39-win32.whl", hash = "sha256:d3d20ea5782ba63ed13bc2b8c291a053c8d807a8fa927d941bd718468f7b950c"},
1603 | {file = "tornado-6.1-cp39-cp39-win_amd64.whl", hash = "sha256:548430be2740e327b3fe0201abe471f314741efcb0067ec4f2d7dcfb4825f3e4"},
1604 | {file = "tornado-6.1.tar.gz", hash = "sha256:33c6e81d7bd55b468d2e793517c909b139960b6c790a60b7991b9b6b76fb9791"},
1605 | ]
1606 | traitlets = [
1607 | {file = "traitlets-5.0.5-py3-none-any.whl", hash = "sha256:69ff3f9d5351f31a7ad80443c2674b7099df13cc41fc5fa6e2f6d3b0330b0426"},
1608 | {file = "traitlets-5.0.5.tar.gz", hash = "sha256:178f4ce988f69189f7e523337a3e11d91c786ded9360174a3d9ca83e79bc5396"},
1609 | ]
1610 | typed-ast = [
1611 | {file = "typed_ast-1.4.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:7703620125e4fb79b64aa52427ec192822e9f45d37d4b6625ab37ef403e1df70"},
1612 | {file = "typed_ast-1.4.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c9aadc4924d4b5799112837b226160428524a9a45f830e0d0f184b19e4090487"},
1613 | {file = "typed_ast-1.4.2-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:9ec45db0c766f196ae629e509f059ff05fc3148f9ffd28f3cfe75d4afb485412"},
1614 | {file = "typed_ast-1.4.2-cp35-cp35m-win32.whl", hash = "sha256:85f95aa97a35bdb2f2f7d10ec5bbdac0aeb9dafdaf88e17492da0504de2e6400"},
1615 | {file = "typed_ast-1.4.2-cp35-cp35m-win_amd64.whl", hash = "sha256:9044ef2df88d7f33692ae3f18d3be63dec69c4fb1b5a4a9ac950f9b4ba571606"},
1616 | {file = "typed_ast-1.4.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c1c876fd795b36126f773db9cbb393f19808edd2637e00fd6caba0e25f2c7b64"},
1617 | {file = "typed_ast-1.4.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5dcfc2e264bd8a1db8b11a892bd1647154ce03eeba94b461effe68790d8b8e07"},
1618 | {file = "typed_ast-1.4.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8db0e856712f79c45956da0c9a40ca4246abc3485ae0d7ecc86a20f5e4c09abc"},
1619 | {file = "typed_ast-1.4.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:d003156bb6a59cda9050e983441b7fa2487f7800d76bdc065566b7d728b4581a"},
1620 | {file = "typed_ast-1.4.2-cp36-cp36m-win32.whl", hash = "sha256:4c790331247081ea7c632a76d5b2a265e6d325ecd3179d06e9cf8d46d90dd151"},
1621 | {file = "typed_ast-1.4.2-cp36-cp36m-win_amd64.whl", hash = "sha256:d175297e9533d8d37437abc14e8a83cbc68af93cc9c1c59c2c292ec59a0697a3"},
1622 | {file = "typed_ast-1.4.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf54cfa843f297991b7388c281cb3855d911137223c6b6d2dd82a47ae5125a41"},
1623 | {file = "typed_ast-1.4.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:b4fcdcfa302538f70929eb7b392f536a237cbe2ed9cba88e3bf5027b39f5f77f"},
1624 | {file = "typed_ast-1.4.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:987f15737aba2ab5f3928c617ccf1ce412e2e321c77ab16ca5a293e7bbffd581"},
1625 | {file = "typed_ast-1.4.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:37f48d46d733d57cc70fd5f30572d11ab8ed92da6e6b28e024e4a3edfb456e37"},
1626 | {file = "typed_ast-1.4.2-cp37-cp37m-win32.whl", hash = "sha256:36d829b31ab67d6fcb30e185ec996e1f72b892255a745d3a82138c97d21ed1cd"},
1627 | {file = "typed_ast-1.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8368f83e93c7156ccd40e49a783a6a6850ca25b556c0fa0240ed0f659d2fe496"},
1628 | {file = "typed_ast-1.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:963c80b583b0661918718b095e02303d8078950b26cc00b5e5ea9ababe0de1fc"},
1629 | {file = "typed_ast-1.4.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e683e409e5c45d5c9082dc1daf13f6374300806240719f95dc783d1fc942af10"},
1630 | {file = "typed_ast-1.4.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:84aa6223d71012c68d577c83f4e7db50d11d6b1399a9c779046d75e24bed74ea"},
1631 | {file = "typed_ast-1.4.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:a38878a223bdd37c9709d07cd357bb79f4c760b29210e14ad0fb395294583787"},
1632 | {file = "typed_ast-1.4.2-cp38-cp38-win32.whl", hash = "sha256:a2c927c49f2029291fbabd673d51a2180038f8cd5a5b2f290f78c4516be48be2"},
1633 | {file = "typed_ast-1.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:c0c74e5579af4b977c8b932f40a5464764b2f86681327410aa028a22d2f54937"},
1634 | {file = "typed_ast-1.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:07d49388d5bf7e863f7fa2f124b1b1d89d8aa0e2f7812faff0a5658c01c59aa1"},
1635 | {file = "typed_ast-1.4.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:240296b27397e4e37874abb1df2a608a92df85cf3e2a04d0d4d61055c8305ba6"},
1636 | {file = "typed_ast-1.4.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:d746a437cdbca200622385305aedd9aef68e8a645e385cc483bdc5e488f07166"},
1637 | {file = "typed_ast-1.4.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:14bf1522cdee369e8f5581238edac09150c765ec1cb33615855889cf33dcb92d"},
1638 | {file = "typed_ast-1.4.2-cp39-cp39-win32.whl", hash = "sha256:cc7b98bf58167b7f2db91a4327da24fb93368838eb84a44c472283778fc2446b"},
1639 | {file = "typed_ast-1.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:7147e2a76c75f0f64c4319886e7639e490fee87c9d25cb1d4faef1d8cf83a440"},
1640 | {file = "typed_ast-1.4.2.tar.gz", hash = "sha256:9fc0b3cb5d1720e7141d103cf4819aea239f7d136acf9ee4a69b047b7986175a"},
1641 | ]
1642 | typing-extensions = [
1643 | {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"},
1644 | {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"},
1645 | {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"},
1646 | ]
1647 | urllib3 = [
1648 | {file = "urllib3-1.26.4-py2.py3-none-any.whl", hash = "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df"},
1649 | {file = "urllib3-1.26.4.tar.gz", hash = "sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937"},
1650 | ]
1651 | uvicorn = [
1652 | {file = "uvicorn-0.13.4-py3-none-any.whl", hash = "sha256:7587f7b08bd1efd2b9bad809a3d333e972f1d11af8a5e52a9371ee3a5de71524"},
1653 | {file = "uvicorn-0.13.4.tar.gz", hash = "sha256:3292251b3c7978e8e4a7868f4baf7f7f7bb7e40c759ecc125c37e99cdea34202"},
1654 | ]
1655 | wcwidth = [
1656 | {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
1657 | {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"},
1658 | ]
1659 | webencodings = [
1660 | {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"},
1661 | {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"},
1662 | ]
1663 |
--------------------------------------------------------------------------------
/demo/pydantic-2021.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": 1,
6 | "metadata": {},
7 | "outputs": [
8 | {
9 | "name": "stdout",
10 | "output_type": "stream",
11 | "text": [
12 | " .\n",
13 | " ..:\n",
14 | " Hultnér\n",
15 | " Technologies\n",
16 | "\n",
17 | "@ahultner | https://hultner.se/\n"
18 | ]
19 | }
20 | ],
21 | "source": [
22 | "def s(x): return \" \"*(10+x)\n",
23 | "print(s(5)+\".\\n\"+s(4)+\"..:\\n\"+s(2)+\"Hultnér\\n\"+s(0)+\"Technologies\\n\\n@ahultner | https://hultner.se/\")"
24 | ]
25 | },
26 | {
27 | "cell_type": "markdown",
28 | "metadata": {},
29 | "source": [
30 | "# Intro to pydantic\n",
31 | "### Run-Time Type Checking For Your Dataclasses\n",
32 | "\n",
33 | "## Index\n",
34 | "- Quick refresher on python data classes\n",
35 | "- Pydantic introduction\n",
36 | " - Prior art\n",
37 | " - Minimal example from dataclass\n",
38 | " - Runtime type-checking\n",
39 | " - JSON (de)serialisation\n",
40 | " - JSONSchema\n",
41 | " - Validators\n",
42 | " - [Custom model validators](https://pydantic-docs.helpmanual.io/usage/validators/)\n",
43 | " - [Validation decorator for functions](https://pydantic-docs.helpmanual.io/usage/validation_decorator/), via `@validate_arguments`. Still in beta, API may change.\n",
44 | " - FastAPI framework\n",
45 | " - OpenAPI Specifications\n",
46 | " - Autogenerated tests\n",
47 | "- Cool features worth mentioning\n",
48 | "- Future\n",
49 | "- Conclusion\n",
50 | " "
51 | ]
52 | },
53 | {
54 | "cell_type": "markdown",
55 | "metadata": {},
56 | "source": [
57 | "Let's start with a quick `@dataclass`-refresher.\n",
58 | "\n",
59 | "I love waffles, don't everyone?\n",
60 | "For our examples we'll use our imaginary café, \"The Waffle Bistro\" 🧇 🌟"
61 | ]
62 | },
63 | {
64 | "cell_type": "code",
65 | "execution_count": 164,
66 | "metadata": {},
67 | "outputs": [],
68 | "source": [
69 | "from dataclasses import dataclass\n",
70 | "from typing import Tuple"
71 | ]
72 | },
73 | {
74 | "cell_type": "code",
75 | "execution_count": 165,
76 | "metadata": {},
77 | "outputs": [],
78 | "source": [
79 | "@dataclass\n",
80 | "class Waffle:\n",
81 | " style: str\n",
82 | " toppings: Tuple[str, ...]\n",
83 | " "
84 | ]
85 | },
86 | {
87 | "cell_type": "code",
88 | "execution_count": 166,
89 | "metadata": {},
90 | "outputs": [
91 | {
92 | "data": {
93 | "text/plain": [
94 | "Waffle(style='Swedish', toppings=('chocolate sauce', 'ham'))"
95 | ]
96 | },
97 | "execution_count": 166,
98 | "metadata": {},
99 | "output_type": "execute_result"
100 | }
101 | ],
102 | "source": [
103 | "Waffle(\"Swedish\", (\"chocolate sauce\", \"ham\"))"
104 | ]
105 | },
106 | {
107 | "cell_type": "markdown",
108 | "metadata": {},
109 | "source": [
110 | "Now we may want to constrain the toppings to ones we actually offer\n",
111 | "🥛 🍓 🟠 🍫\n",
112 | "\n",
113 | "We offer a couple of cream based toppings, and a couple of _\"dessert sauces\"_"
114 | ]
115 | },
116 | {
117 | "cell_type": "code",
118 | "execution_count": 167,
119 | "metadata": {},
120 | "outputs": [],
121 | "source": [
122 | "from typing import Union\n",
123 | "from enum import Enum"
124 | ]
125 | },
126 | {
127 | "cell_type": "code",
128 | "execution_count": 168,
129 | "metadata": {},
130 | "outputs": [],
131 | "source": [
132 | "class Cream(str, Enum):\n",
133 | " whipped_cream = \"whipped cream\"\n",
134 | " ice_cream = \"icecream\"\n",
135 | "\n",
136 | "class DessertSauce(str, Enum):\n",
137 | " cloudberry_jam = \"cloudberry jam\"\n",
138 | " raspberry_jam = \"raspberry jam\"\n",
139 | " choclate_sauce = \"chocolate sauce\"\n",
140 | "\n",
141 | "Topping = Union[DessertSauce, Cream]\n",
142 | "\n",
143 | "class WaffleStyle(str, Enum):\n",
144 | " swedish = \"Swedish\"\n",
145 | " belgian = \"Belgian\"\n",
146 | "\n",
147 | "\n",
148 | "@dataclass\n",
149 | "class Waffle:\n",
150 | " style: WaffleStyle\n",
151 | " toppings: Tuple[Topping, ...]"
152 | ]
153 | },
154 | {
155 | "cell_type": "markdown",
156 | "metadata": {},
157 | "source": [
158 | "Let's see what happens if we try to create a waffle with ham topping."
159 | ]
160 | },
161 | {
162 | "cell_type": "code",
163 | "execution_count": 100,
164 | "metadata": {},
165 | "outputs": [
166 | {
167 | "data": {
168 | "text/plain": [
169 | "Waffle(style='Swedish', toppings=('chocolate sauce', 'ham'))"
170 | ]
171 | },
172 | "execution_count": 100,
173 | "metadata": {},
174 | "output_type": "execute_result"
175 | }
176 | ],
177 | "source": [
178 | "Waffle(\"Swedish\", (\"chocolate sauce\", \"ham\"))"
179 | ]
180 | },
181 | {
182 | "cell_type": "markdown",
183 | "metadata": {},
184 | "source": [
185 | "With dataclasses the types aren't enforced, this can ofcourse be implemented but in this case we'll lean on the shoulders of a giant, pydantic 🧹🐍🧐"
186 | ]
187 | },
188 | {
189 | "cell_type": "code",
190 | "execution_count": 101,
191 | "metadata": {},
192 | "outputs": [],
193 | "source": [
194 | "from pydantic.dataclasses import dataclass\n",
195 | "\n",
196 | "@dataclass\n",
197 | "class Waffle:\n",
198 | " style: WaffleStyle\n",
199 | " toppings: Tuple[Topping, ...]"
200 | ]
201 | },
202 | {
203 | "cell_type": "markdown",
204 | "metadata": {},
205 | "source": [
206 | "As you can see the only thing changed in this example is that we import the dataclass decorator from pydantic."
207 | ]
208 | },
209 | {
210 | "cell_type": "code",
211 | "execution_count": 102,
212 | "metadata": {},
213 | "outputs": [
214 | {
215 | "name": "stdout",
216 | "output_type": "stream",
217 | "text": [
218 | "2 validation errors for Waffle\n",
219 | "toppings -> 1\n",
220 | " value is not a valid enumeration member; permitted: 'cloudberry jam', 'raspberry jam', 'chocolate sauce' (type=type_error.enum; enum_values=[, , ])\n",
221 | "toppings -> 1\n",
222 | " value is not a valid enumeration member; permitted: 'whipped cream', 'icecream' (type=type_error.enum; enum_values=[, ])\n"
223 | ]
224 | }
225 | ],
226 | "source": [
227 | "from pydantic import ValidationError\n",
228 | "try:\n",
229 | " Waffle(\"Swedish\", (\"chocolate sauce\", \"ham\"))\n",
230 | "except ValidationError as err:\n",
231 | " print(err)"
232 | ]
233 | },
234 | {
235 | "cell_type": "markdown",
236 | "metadata": {},
237 | "source": [
238 | "With that simple change we can see that our new instance of an unsupported waffle actually raises errors 🚫🚨\n",
239 | "\n",
240 | "These errors are very readable!"
241 | ]
242 | },
243 | {
244 | "cell_type": "markdown",
245 | "metadata": {},
246 | "source": [
247 | "So let's try to create a valid waffle ✅"
248 | ]
249 | },
250 | {
251 | "cell_type": "code",
252 | "execution_count": 103,
253 | "metadata": {},
254 | "outputs": [
255 | {
256 | "data": {
257 | "text/plain": [
258 | "Waffle(style=, toppings=(, ))"
259 | ]
260 | },
261 | "execution_count": 103,
262 | "metadata": {},
263 | "output_type": "execute_result"
264 | }
265 | ],
266 | "source": [
267 | "Waffle(\"Swedish\", (Cream.whipped_cream, \"cloudberry jam\"))"
268 | ]
269 | },
270 | {
271 | "cell_type": "markdown",
272 | "metadata": {},
273 | "source": [
274 | "See how the cloudberry jam was automatically parsed as a Dessert Sauce. This is a feature of pydantic, more strictness can be achieved with the strict types and a fully [strict mode](https://github.com/samuelcolvin/pydantic/issues/1098) is being worked on."
275 | ]
276 | },
277 | {
278 | "cell_type": "markdown",
279 | "metadata": {},
280 | "source": [
281 | "So what about JSON? 🧑💻 \n",
282 | "The dataclass dropin replacement decorator from pydantic is great for compability but by using `pydantic.BaseModel` we can get even more out of pydantic. One of those things is (de)serialisation, pydantic have native support JSON encoding and decoding."
283 | ]
284 | },
285 | {
286 | "cell_type": "code",
287 | "execution_count": 169,
288 | "metadata": {},
289 | "outputs": [],
290 | "source": [
291 | "from pydantic import BaseModel\n",
292 | "\n",
293 | "class Waffle(BaseModel):\n",
294 | " style: WaffleStyle\n",
295 | " toppings: Tuple[Topping, ...]"
296 | ]
297 | },
298 | {
299 | "cell_type": "markdown",
300 | "metadata": {},
301 | "source": [
302 | "*Disclaimer: Pydantic is primarly a parsing library and does validation as a means to an end, so make sure it makes sense for you.*"
303 | ]
304 | },
305 | {
306 | "cell_type": "markdown",
307 | "metadata": {},
308 | "source": [
309 | "When using the BaseModel the default behaviour requires to specify the init arguments using their keywords like below"
310 | ]
311 | },
312 | {
313 | "cell_type": "code",
314 | "execution_count": 105,
315 | "metadata": {},
316 | "outputs": [
317 | {
318 | "data": {
319 | "text/plain": [
320 | "Waffle(style=, toppings=(, ))"
321 | ]
322 | },
323 | "execution_count": 105,
324 | "metadata": {},
325 | "output_type": "execute_result"
326 | }
327 | ],
328 | "source": [
329 | "Waffle(style=\"Swedish\", toppings=(Cream.whipped_cream, \"cloudberry jam\"))"
330 | ]
331 | },
332 | {
333 | "cell_type": "markdown",
334 | "metadata": {},
335 | "source": [
336 | "We can now easily encode this object as `JSON`, there's also [built-in support](https://pydantic-docs.helpmanual.io/usage/exporting_models/) for dict, pickle, immutable `copy()`. Pydantic will also (de)serialise subclasses."
337 | ]
338 | },
339 | {
340 | "cell_type": "code",
341 | "execution_count": 106,
342 | "metadata": {},
343 | "outputs": [
344 | {
345 | "data": {
346 | "text/plain": [
347 | "'{\"style\": \"Swedish\", \"toppings\": [\"whipped cream\", \"cloudberry jam\"]}'"
348 | ]
349 | },
350 | "execution_count": 106,
351 | "metadata": {},
352 | "output_type": "execute_result"
353 | }
354 | ],
355 | "source": [
356 | "_.json()"
357 | ]
358 | },
359 | {
360 | "cell_type": "markdown",
361 | "metadata": {},
362 | "source": [
363 | "And we can also reconstruct our original object using the `parse_raw`-method."
364 | ]
365 | },
366 | {
367 | "cell_type": "code",
368 | "execution_count": 107,
369 | "metadata": {},
370 | "outputs": [
371 | {
372 | "data": {
373 | "text/plain": [
374 | "Waffle(style=, toppings=(, ))"
375 | ]
376 | },
377 | "execution_count": 107,
378 | "metadata": {},
379 | "output_type": "execute_result"
380 | }
381 | ],
382 | "source": [
383 | "Waffle.parse_raw('{\"style\": \"Swedish\", \"toppings\": [\"whipped cream\", \"cloudberry jam\"]}')"
384 | ]
385 | },
386 | {
387 | "cell_type": "markdown",
388 | "metadata": {},
389 | "source": [
390 | "Errors raises a validation error, these can also be represented as JSON."
391 | ]
392 | },
393 | {
394 | "cell_type": "code",
395 | "execution_count": 124,
396 | "metadata": {},
397 | "outputs": [
398 | {
399 | "name": "stdout",
400 | "output_type": "stream",
401 | "text": [
402 | "[\n",
403 | " {\n",
404 | " \"loc\": [\n",
405 | " \"style\"\n",
406 | " ],\n",
407 | " \"msg\": \"value is not a valid enumeration member; permitted: 'Swedish', 'Belgian'\",\n",
408 | " \"type\": \"type_error.enum\",\n",
409 | " \"ctx\": {\n",
410 | " \"enum_values\": [\n",
411 | " \"Swedish\",\n",
412 | " \"Belgian\"\n",
413 | " ]\n",
414 | " }\n",
415 | " }\n",
416 | "]\n"
417 | ]
418 | }
419 | ],
420 | "source": [
421 | "try:\n",
422 | " Waffle(style=42, toppings=(Cream.whipped_cream, \"cloudberry jam\"))\n",
423 | "except ValidationError as err:\n",
424 | " print(err.json())"
425 | ]
426 | },
427 | {
428 | "cell_type": "markdown",
429 | "metadata": {},
430 | "source": [
431 | "We can also export a JSONSchema directly from our model, this is very useful for instance if we want to use your model to feed a Swagger/OpenAPI-spec. 📜✅\n",
432 | "\n",
433 | "⚠ *Caution: Pydantic uses draft 7 of JSONSchema, this is used in the just released OpenAPI 3.1 spec. \n",
434 | "The still common 3.0.x spec uses draft 4. \n",
435 | "I spoke with Samuel Colvin, the creator of pydantic about this and his recommendation is to write a `schema_extra`function to use the older JSONSchema version if you want strict compability. The FastAPI framework doesn't do this and is slightly incompatible with the older OpenAPI-spec*"
436 | ]
437 | },
438 | {
439 | "cell_type": "code",
440 | "execution_count": 125,
441 | "metadata": {},
442 | "outputs": [
443 | {
444 | "data": {
445 | "text/plain": [
446 | "{'title': 'Waffle',\n",
447 | " 'type': 'object',\n",
448 | " 'properties': {'style': {'$ref': '#/definitions/WaffleStyle'},\n",
449 | " 'toppings': {'title': 'Toppings',\n",
450 | " 'type': 'array',\n",
451 | " 'items': {'anyOf': [{'$ref': '#/definitions/DessertSauce'},\n",
452 | " {'$ref': '#/definitions/Cream'}]}}},\n",
453 | " 'required': ['style', 'toppings'],\n",
454 | " 'definitions': {'WaffleStyle': {'title': 'WaffleStyle',\n",
455 | " 'description': 'An enumeration.',\n",
456 | " 'enum': ['Swedish', 'Belgian'],\n",
457 | " 'type': 'string'},\n",
458 | " 'DessertSauce': {'title': 'DessertSauce',\n",
459 | " 'description': 'An enumeration.',\n",
460 | " 'enum': ['cloudberry jam', 'raspberry jam', 'chocolate sauce'],\n",
461 | " 'type': 'string'},\n",
462 | " 'Cream': {'title': 'Cream',\n",
463 | " 'description': 'An enumeration.',\n",
464 | " 'enum': ['whipped cream', 'icecream'],\n",
465 | " 'type': 'string'}}}"
466 | ]
467 | },
468 | "execution_count": 125,
469 | "metadata": {},
470 | "output_type": "execute_result"
471 | }
472 | ],
473 | "source": [
474 | "Waffle.schema()"
475 | ]
476 | },
477 | {
478 | "cell_type": "markdown",
479 | "metadata": {},
480 | "source": [
481 | "That was the basics using the built-in validators, but what if you want to implement your own business rules in a custom validator, we're going to look at this next.\n",
482 | "\n",
483 | "We now want to add some custom busniess logic specific for \"The Waffle Bistro\".\n",
484 | "In this case we want to only allow:\n",
485 | "- Either icecream or whipped cream 🍦 ⊕🥛\n",
486 | "- Jam for Swedish waffles 🇸🇪 🟠 🔴\n",
487 | "- Choclate for Belgian waffles 🇧🇪 🍫"
488 | ]
489 | },
490 | {
491 | "cell_type": "code",
492 | "execution_count": 172,
493 | "metadata": {},
494 | "outputs": [],
495 | "source": [
496 | "from pydantic import validator, root_validator\n",
497 | "\n",
498 | "swedish_toppings = (\n",
499 | " DessertSauce.raspberry_jam, DessertSauce.cloudberry_jam,\n",
500 | ")\n",
501 | "\n",
502 | "belgian_toppings = (DessertSauce.choclate_sauce,)\n",
503 | "\n",
504 | "class WaffleOrder(Waffle):\n",
505 | "\n",
506 | " \n",
507 | " # Root validators check the entire model\n",
508 | " @root_validator(pre=False)\n",
509 | " def check_style_topping(cls, values):\n",
510 | " style, toppings = values.get(\"style\"), values.get(\"toppings\")\n",
511 | " # Check swedish style\n",
512 | " if (\n",
513 | " style == WaffleStyle.swedish and \n",
514 | " all(t in swedish_toppings for t in toppings if type(t) is DessertSauce)\n",
515 | " ):\n",
516 | " return values\n",
517 | " \n",
518 | " # Check belgian style\n",
519 | " if (\n",
520 | " style == WaffleStyle.belgian and \n",
521 | " all(t in belgian_toppings for t in toppings if type(t) is DessertSauce)\n",
522 | " ):\n",
523 | " return values\n",
524 | " \n",
525 | " # Doesn't match any of our allowed styles\n",
526 | " raise ValueError(f\"The Waffle Bistro doesn't sell this waffle.\")\n",
527 | " \n",
528 | " \n",
529 | " # A validator looking at a single property\n",
530 | " @validator('toppings')\n",
531 | " def check_cream(cls, toppings):\n",
532 | " creams = [t for t in toppings if type(t) is Cream]\n",
533 | " if len(creams) > 1:\n",
534 | " raise ValueError(f\"We only allow for one cream topping, given: {creams}\")\n",
535 | " return toppings\n"
536 | ]
537 | },
538 | {
539 | "cell_type": "markdown",
540 | "metadata": {},
541 | "source": [
542 | "Now let's see if we create some invalid waffles 🧇 ⚠️🚨"
543 | ]
544 | },
545 | {
546 | "cell_type": "code",
547 | "execution_count": 173,
548 | "metadata": {},
549 | "outputs": [
550 | {
551 | "name": "stdout",
552 | "output_type": "stream",
553 | "text": [
554 | "2 validation errors for WaffleOrder\n",
555 | "toppings\n",
556 | " We only allow for one cream topping, given: [, ] (type=value_error)\n",
557 | "__root__\n",
558 | " 'NoneType' object is not iterable (type=type_error)\n"
559 | ]
560 | }
561 | ],
562 | "source": [
563 | "try: \n",
564 | " WaffleOrder(style=\"Swedish\", toppings=[\"icecream\", \"whipped cream\", \"cloudberry jam\"])\n",
565 | "except ValidationError as err:\n",
566 | " print(err)"
567 | ]
568 | },
569 | {
570 | "cell_type": "code",
571 | "execution_count": 174,
572 | "metadata": {},
573 | "outputs": [
574 | {
575 | "name": "stdout",
576 | "output_type": "stream",
577 | "text": [
578 | "1 validation error for WaffleOrder\n",
579 | "__root__\n",
580 | " The Waffle Bistro doesn't sell this waffle. (type=value_error)\n"
581 | ]
582 | }
583 | ],
584 | "source": [
585 | "try: \n",
586 | " WaffleOrder(style=\"Swedish\", toppings=[\"icecream\", \"cloudberry jam\", \"chocolate sauce\"])\n",
587 | "except ValidationError as err:\n",
588 | " print(err)"
589 | ]
590 | },
591 | {
592 | "cell_type": "markdown",
593 | "metadata": {},
594 | "source": [
595 | "Now let's create a waffle 🧇 allowed by our rules! ✨"
596 | ]
597 | },
598 | {
599 | "cell_type": "code",
600 | "execution_count": 156,
601 | "metadata": {},
602 | "outputs": [
603 | {
604 | "data": {
605 | "text/plain": [
606 | "WaffleOrder(style=, toppings=(, ))"
607 | ]
608 | },
609 | "execution_count": 156,
610 | "metadata": {},
611 | "output_type": "execute_result"
612 | }
613 | ],
614 | "source": [
615 | "WaffleOrder(style=\"Belgian\", toppings=[\"whipped cream\", \"chocolate sauce\"])"
616 | ]
617 | },
618 | {
619 | "cell_type": "markdown",
620 | "metadata": {},
621 | "source": [
622 | "Gosh these runtime type checkers are rather useful, but what about **functions**? \n",
623 | "\n",
624 | "Pydantic got you covered with `@validate_arguments`. *Still in beta, API may change, release 2020-04-18 in version 1.5*"
625 | ]
626 | },
627 | {
628 | "cell_type": "code",
629 | "execution_count": 175,
630 | "metadata": {},
631 | "outputs": [],
632 | "source": [
633 | "from pydantic import validate_arguments\n",
634 | "\n",
635 | "# Validator on function\n",
636 | "# Ensure valid waffles when making orders\n",
637 | "@validate_arguments\n",
638 | "def make_order(waffle: WaffleOrder):\n",
639 | " ...\n",
640 | " "
641 | ]
642 | },
643 | {
644 | "cell_type": "code",
645 | "execution_count": 161,
646 | "metadata": {},
647 | "outputs": [
648 | {
649 | "name": "stdout",
650 | "output_type": "stream",
651 | "text": [
652 | "2 validation errors for MakeOrder\n",
653 | "waffle -> style\n",
654 | " value is not a valid enumeration member; permitted: 'Swedish', 'Belgian' (type=type_error.enum; enum_values=[, ])\n",
655 | "waffle -> __root__\n",
656 | " The Waffle Bistro doesn't sell this waffle. (type=value_error)\n"
657 | ]
658 | }
659 | ],
660 | "source": [
661 | "try:\n",
662 | " make_order({\n",
663 | " \"style\":\"Breakfast\",\n",
664 | " \"toppings\":(\"whipped cream\", \"raspberry jam\")\n",
665 | " })\n",
666 | "except ValidationError as err:\n",
667 | " print(err)"
668 | ]
669 | },
670 | {
671 | "cell_type": "code",
672 | "execution_count": null,
673 | "metadata": {},
674 | "outputs": [],
675 | "source": []
676 | },
677 | {
678 | "cell_type": "markdown",
679 | "metadata": {},
680 | "source": [
681 | "## FastAPI\n",
682 | "FastAPI is a lean microframework similar to Flask which utilizes pydantic models heavily, it will also automatically generate OpenAPI-specifications from your application based on your models.\n",
683 | "\n",
684 | "This gives you framework agnostic models while still being able to leverage tight integration with a modern and easy to use framework. If you're going to start a new API-project i highly recommend trying FastAPI."
685 | ]
686 | },
687 | {
688 | "cell_type": "code",
689 | "execution_count": 176,
690 | "metadata": {},
691 | "outputs": [],
692 | "source": [
693 | "from fastapi import FastAPI\n",
694 | "from pydantic import BaseModel\n",
695 | "\n",
696 | "app = FastAPI()\n",
697 | "\n",
698 | "def make_order(waffle: WaffleOrder):\n",
699 | " # Business logic for making an order\n",
700 | " pass\n",
701 | "\n",
702 | "def dispatch_order(waffle: WaffleOrder):\n",
703 | " # Hand over waffle customer\n",
704 | " pass\n",
705 | "\n",
706 | "# Deliver a waffle\n",
707 | "@app.post(\"/delivery/waffle\")\n",
708 | "async def deliver_waffle_order(waffle: WaffleOrder):\n",
709 | " dispatch = dispatch_order(waffle)\n",
710 | " return dispatch\n",
711 | "\n",
712 | "@app.post(\"/order/waffle\")\n",
713 | "async def order_waffle(waffle: WaffleOrder):\n",
714 | " order = make_order(waffle)\n",
715 | " return order\n"
716 | ]
717 | },
718 | {
719 | "cell_type": "markdown",
720 | "metadata": {},
721 | "source": [
722 | "This is everything we need to create a small API around our models."
723 | ]
724 | },
725 | {
726 | "cell_type": "markdown",
727 | "metadata": {},
728 | "source": [
729 | "---\n",
730 | "\n",
731 | "That's it, a quick introduction to pydantic! \n",
732 | "\n",
733 | "But this is just the tip of the iceberg 🗻 and I want to give you a hint about what more can be done. \n",
734 | "I'm not going to go into detail in any of this but feel free to ask me about it in the chat, on Twitter/LinkedIn or via email. 💬📨"
735 | ]
736 | },
737 | {
738 | "cell_type": "markdown",
739 | "metadata": {},
740 | "source": [
741 | "## Cool features worth mentioning\n",
742 | "\n",
743 | "- Post **1.0**, reached this milestone about a year ago\n",
744 | "- Support for [standard library types](https://pydantic-docs.helpmanual.io/usage/types/#pydantic-types)\n",
745 | "- Offer useful extra types for every day use\n",
746 | " - Email\n",
747 | " - HttpUrl (and more, stricturl for custom validation)\n",
748 | " - PostgresDsn\n",
749 | " - IPvAnyAddress (as well as IPv4Address and IPv6Address from ipaddress)\n",
750 | " - PositiveInt\n",
751 | " - PaymentCardNumber, PaymentCardBrand.[amex, mastercard, visa, other], checks luhn, str of digits and BIN-based lenght.\n",
752 | " - [Constrained types](https://pydantic-docs.helpmanual.io/usage/types/#constrained-types) (e.g. conlist, conint, etc.)\n",
753 | " - and more…\n",
754 | "- Supports [custom datatypes](https://pydantic-docs.helpmanual.io/usage/types/#custom-data-types)\n",
755 | "- [Settings management](https://pydantic-docs.helpmanual.io/usage/settings/)\n",
756 | " - Typed configuration management\n",
757 | " - Automatically reads from environment variables\n",
758 | " - Dotenv (`.env`) support via defacto standard [python-dotenv](https://pypi.org/project/python-dotenv/).\n",
759 | "- ORM-mode\n",
760 | "- Recursive models\n",
761 | "- Works with mypy out of the box, [mypy plugin](https://pydantic-docs.helpmanual.io/mypy_plugin/) further improves experience.\n",
762 | "- [Postponed annotations, self-referencing models](https://pydantic-docs.helpmanual.io/usage/postponed_annotations/), [PEP-563](https://www.python.org/dev/peps/pep-0563/)-style.\n",
763 | "- python-devtools intergration\n",
764 | "- PyCharm plugin\n",
765 | "- [Fast](https://pydantic-docs.helpmanual.io/benchmarks/) compared to popular alternatives! \n",
766 | " But always make your own benchmarks for your own usecase if performance is important for you.\n",
767 | "\n",
768 | "## Future\n",
769 | "- A strict mode is being worked on, in the future this will enable us to choose between Strict and Coercion on a model level instead of relying on the Strict* types.\n",
770 | "- The project is very active and a lot of improvements are constantly being made to the library.\n",
771 | " "
772 | ]
773 | },
774 | {
775 | "cell_type": "markdown",
776 | "metadata": {},
777 | "source": [
778 | "## Conclusion\n",
779 | "Pure python syntax\n",
780 | "Better validation\n",
781 | "Very useful JSON-tools for API's\n",
782 | "Easy to migrate from dataclasses\n",
783 | "Lots of useful features\n",
784 | "Try it out!"
785 | ]
786 | },
787 | {
788 | "cell_type": "markdown",
789 | "metadata": {},
790 | "source": [
791 | "## Want to hear more from me? \n",
792 | "I'm making a course on property based testing in python using Hypothesis. \n",
793 | "[Sign up here](https://forms.gle/yRWapypPXdPFSLME7)"
794 | ]
795 | },
796 | {
797 | "cell_type": "code",
798 | "execution_count": null,
799 | "metadata": {},
800 | "outputs": [],
801 | "source": []
802 | }
803 | ],
804 | "metadata": {
805 | "kernelspec": {
806 | "display_name": "Python 3",
807 | "language": "python",
808 | "name": "python3"
809 | },
810 | "language_info": {
811 | "codemirror_mode": {
812 | "name": "ipython",
813 | "version": 3
814 | },
815 | "file_extension": ".py",
816 | "mimetype": "text/x-python",
817 | "name": "python",
818 | "nbconvert_exporter": "python",
819 | "pygments_lexer": "ipython3",
820 | "version": "3.9.1"
821 | }
822 | },
823 | "nbformat": 4,
824 | "nbformat_minor": 4
825 | }
826 |
--------------------------------------------------------------------------------
/demo/pydantic.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": 1,
6 | "metadata": {},
7 | "outputs": [
8 | {
9 | "name": "stdout",
10 | "output_type": "stream",
11 | "text": [
12 | " .\n",
13 | " ..:\n",
14 | " Hultnér\n",
15 | " Technologies\n",
16 | "\n",
17 | "@ahultner | https://hultner.se/\n"
18 | ]
19 | }
20 | ],
21 | "source": [
22 | "def s(x): return \" \"*(10+x)\n",
23 | "print(s(5)+\".\\n\"+s(4)+\"..:\\n\"+s(2)+\"Hultnér\\n\"+s(0)+\"Technologies\\n\\n@ahultner | https://hultner.se/\")"
24 | ]
25 | },
26 | {
27 | "cell_type": "markdown",
28 | "metadata": {},
29 | "source": [
30 | "# Give your dataclasses super powers with pydantic\n",
31 | "\n",
32 | "## Index\n",
33 | "- Quick refresher on python data classes\n",
34 | "- Pydantic introduction\n",
35 | " - Prior art\n",
36 | " - Minimal example from dataclass\n",
37 | " - Runtime type-checking\n",
38 | " - JSON (de)serialisation\n",
39 | " - JSONSchema\n",
40 | " - Validators\n",
41 | " - [Custom model validators](https://pydantic-docs.helpmanual.io/usage/validators/)\n",
42 | " - [Validation decorator for functions](https://pydantic-docs.helpmanual.io/usage/validation_decorator/), via `@validate_arguments`. Still in beta, API may change.\n",
43 | " - FastAPI framework\n",
44 | " - OpenAPI Specifications\n",
45 | " - Autogenerated tests\n",
46 | "- Cool features worth mentioning\n",
47 | "- Future\n",
48 | "- Conclusion\n",
49 | " "
50 | ]
51 | },
52 | {
53 | "cell_type": "markdown",
54 | "metadata": {},
55 | "source": [
56 | "Let's start with a quick `@dataclass`-refresher.\n",
57 | "\n",
58 | "Well use pizza-based examples in the spirit of [python.pizza](https://remote.python.pizza) 🐍🍕"
59 | ]
60 | },
61 | {
62 | "cell_type": "code",
63 | "execution_count": 2,
64 | "metadata": {},
65 | "outputs": [],
66 | "source": [
67 | "from dataclasses import dataclass\n",
68 | "from typing import Tuple"
69 | ]
70 | },
71 | {
72 | "cell_type": "code",
73 | "execution_count": 3,
74 | "metadata": {},
75 | "outputs": [],
76 | "source": [
77 | "@dataclass\n",
78 | "class Pizza:\n",
79 | " style: str\n",
80 | " toppings: Tuple[str, ...]\n",
81 | " "
82 | ]
83 | },
84 | {
85 | "cell_type": "code",
86 | "execution_count": 4,
87 | "metadata": {},
88 | "outputs": [
89 | {
90 | "data": {
91 | "text/plain": [
92 | "Pizza(style=1, toppings=('cheese', 'ham'))"
93 | ]
94 | },
95 | "execution_count": 4,
96 | "metadata": {},
97 | "output_type": "execute_result"
98 | }
99 | ],
100 | "source": [
101 | "Pizza(1, (\"cheese\", \"ham\"))"
102 | ]
103 | },
104 | {
105 | "cell_type": "markdown",
106 | "metadata": {},
107 | "source": [
108 | "Now we may want to constrain the toppings to ones we actually offer.\n",
109 | "Our pizzeria doesn't offer pineapple 🚫🍍 as a valid topping, hate it 😡 or love it 💕"
110 | ]
111 | },
112 | {
113 | "cell_type": "code",
114 | "execution_count": 5,
115 | "metadata": {},
116 | "outputs": [],
117 | "source": [
118 | "from enum import Enum"
119 | ]
120 | },
121 | {
122 | "cell_type": "code",
123 | "execution_count": 6,
124 | "metadata": {},
125 | "outputs": [],
126 | "source": [
127 | "class Topping(str, Enum):\n",
128 | " mozzarella = 'mozzarella'\n",
129 | " tomato_sauce = 'tomato sauce'\n",
130 | " prosciutto = 'prosciutto'\n",
131 | " basil = 'basil'\n",
132 | " rucola = 'rucola'\n",
133 | " \n",
134 | "\n",
135 | "@dataclass\n",
136 | "class Pizza:\n",
137 | " style: str\n",
138 | " toppings: Tuple[Topping, ...]"
139 | ]
140 | },
141 | {
142 | "cell_type": "markdown",
143 | "metadata": {},
144 | "source": [
145 | "Let's see what happens if we try to create a pizza with pineapple 🍍 topping."
146 | ]
147 | },
148 | {
149 | "cell_type": "code",
150 | "execution_count": 7,
151 | "metadata": {},
152 | "outputs": [
153 | {
154 | "data": {
155 | "text/plain": [
156 | "Pizza(style=2, toppings=('pineapple', 24))"
157 | ]
158 | },
159 | "execution_count": 7,
160 | "metadata": {},
161 | "output_type": "execute_result"
162 | }
163 | ],
164 | "source": [
165 | "Pizza(2, (\"pineapple\", 24))"
166 | ]
167 | },
168 | {
169 | "cell_type": "markdown",
170 | "metadata": {},
171 | "source": [
172 | "With dataclasses the types aren't enforced, this can ofcourse be implemented but in this case we'll lean on the shoulders of a giant, pydantic 🧹🐍🧐"
173 | ]
174 | },
175 | {
176 | "cell_type": "code",
177 | "execution_count": 8,
178 | "metadata": {},
179 | "outputs": [],
180 | "source": [
181 | "from pydantic.dataclasses import dataclass\n",
182 | "\n",
183 | " \n",
184 | "@dataclass\n",
185 | "class Pizza:\n",
186 | " style: str\n",
187 | " toppings: Tuple[Topping, ...]"
188 | ]
189 | },
190 | {
191 | "cell_type": "markdown",
192 | "metadata": {},
193 | "source": [
194 | "As you can see the only thing changed in this example is that we import the dataclass decorator from pydantic."
195 | ]
196 | },
197 | {
198 | "cell_type": "code",
199 | "execution_count": 9,
200 | "metadata": {},
201 | "outputs": [
202 | {
203 | "name": "stdout",
204 | "output_type": "stream",
205 | "text": [
206 | "2 validation errors for Pizza\n",
207 | "toppings -> 0\n",
208 | " value is not a valid enumeration member; permitted: 'mozzarella', 'tomato sauce', 'prosciutto', 'basil', 'rucola' (type=type_error.enum; enum_values=[, , , , ])\n",
209 | "toppings -> 1\n",
210 | " value is not a valid enumeration member; permitted: 'mozzarella', 'tomato sauce', 'prosciutto', 'basil', 'rucola' (type=type_error.enum; enum_values=[, , , , ])\n"
211 | ]
212 | }
213 | ],
214 | "source": [
215 | "from pydantic import ValidationError\n",
216 | "try:\n",
217 | " Pizza(2, (\"pineapple\", 24))\n",
218 | "except ValidationError as err:\n",
219 | " print(err)"
220 | ]
221 | },
222 | {
223 | "cell_type": "markdown",
224 | "metadata": {},
225 | "source": [
226 | "And with that simple chage we can see that our new instance of an invalid pizza actually raises errors 🚫🚨\n",
227 | "\n",
228 | "Additionally these errors are very readable!"
229 | ]
230 | },
231 | {
232 | "cell_type": "markdown",
233 | "metadata": {},
234 | "source": [
235 | "So let's try to create a valid pizza 🍕✅"
236 | ]
237 | },
238 | {
239 | "cell_type": "code",
240 | "execution_count": 10,
241 | "metadata": {},
242 | "outputs": [
243 | {
244 | "data": {
245 | "text/plain": [
246 | "Pizza(style='Napoli', toppings=(, , , ))"
247 | ]
248 | },
249 | "execution_count": 10,
250 | "metadata": {},
251 | "output_type": "execute_result"
252 | }
253 | ],
254 | "source": [
255 | "Pizza(\"Napoli\", (Topping.tomato_sauce, Topping.prosciutto, Topping.mozzarella, Topping.basil))"
256 | ]
257 | },
258 | {
259 | "cell_type": "markdown",
260 | "metadata": {},
261 | "source": [
262 | "So what about JSON? 🧑💻 \n",
263 | "The dataclass dropin replacement decorator from pydantic is great for compability but by using `pydantic.BaseModel` we can get even more out of pydantic. One of those things is (de)serialisation, pydantic have native support JSON encoding and decoding."
264 | ]
265 | },
266 | {
267 | "cell_type": "code",
268 | "execution_count": 11,
269 | "metadata": {},
270 | "outputs": [],
271 | "source": [
272 | "from pydantic import BaseModel\n",
273 | "\n",
274 | "\n",
275 | "\n",
276 | "class Pizza(BaseModel):\n",
277 | " style: str\n",
278 | " toppings: Tuple[Topping, ...]"
279 | ]
280 | },
281 | {
282 | "cell_type": "markdown",
283 | "metadata": {},
284 | "source": [
285 | "*Disclaimer: Pydantic is primarly a parsing library and does validation as a means to an end, so make sure it makes sense for you.*"
286 | ]
287 | },
288 | {
289 | "cell_type": "markdown",
290 | "metadata": {},
291 | "source": [
292 | "When using the BaseModel the default behaviour requires to specify the init arguments using their keywords like below"
293 | ]
294 | },
295 | {
296 | "cell_type": "code",
297 | "execution_count": 12,
298 | "metadata": {},
299 | "outputs": [
300 | {
301 | "data": {
302 | "text/plain": [
303 | "Pizza(style='Napoli', toppings=(, , , ))"
304 | ]
305 | },
306 | "execution_count": 12,
307 | "metadata": {},
308 | "output_type": "execute_result"
309 | }
310 | ],
311 | "source": [
312 | "Pizza(style=\"Napoli\", toppings=(Topping.tomato_sauce, Topping.prosciutto, Topping.mozzarella, Topping.basil))"
313 | ]
314 | },
315 | {
316 | "cell_type": "markdown",
317 | "metadata": {},
318 | "source": [
319 | "We can now easily encode this object as `JSON`, there's also [built-in support](https://pydantic-docs.helpmanual.io/usage/exporting_models/) for dict, pickle, immutable `copy()`. Pydantic will also (de)serialise subclasses."
320 | ]
321 | },
322 | {
323 | "cell_type": "code",
324 | "execution_count": 13,
325 | "metadata": {},
326 | "outputs": [
327 | {
328 | "data": {
329 | "text/plain": [
330 | "'{\"style\": \"Napoli\", \"toppings\": [\"tomato sauce\", \"prosciutto\", \"mozzarella\", \"basil\"]}'"
331 | ]
332 | },
333 | "execution_count": 13,
334 | "metadata": {},
335 | "output_type": "execute_result"
336 | }
337 | ],
338 | "source": [
339 | "_.json()"
340 | ]
341 | },
342 | {
343 | "cell_type": "markdown",
344 | "metadata": {},
345 | "source": [
346 | "And we can also reconstruct our original object using the `parse_raw`-method."
347 | ]
348 | },
349 | {
350 | "cell_type": "code",
351 | "execution_count": 14,
352 | "metadata": {},
353 | "outputs": [
354 | {
355 | "data": {
356 | "text/plain": [
357 | "Pizza(style='Napoli', toppings=(, , , ))"
358 | ]
359 | },
360 | "execution_count": 14,
361 | "metadata": {},
362 | "output_type": "execute_result"
363 | }
364 | ],
365 | "source": [
366 | "Pizza.parse_raw('{\"style\": \"Napoli\", \"toppings\": [\"tomato sauce\", \"prosciutto\", \"mozzarella\", \"basil\"]}')"
367 | ]
368 | },
369 | {
370 | "cell_type": "markdown",
371 | "metadata": {},
372 | "source": [
373 | "Errors raises a validation error, these can also be represented as JSON."
374 | ]
375 | },
376 | {
377 | "cell_type": "code",
378 | "execution_count": 15,
379 | "metadata": {},
380 | "outputs": [
381 | {
382 | "name": "stdout",
383 | "output_type": "stream",
384 | "text": [
385 | "[\n",
386 | " {\n",
387 | " \"loc\": [\n",
388 | " \"toppings\",\n",
389 | " 0\n",
390 | " ],\n",
391 | " \"msg\": \"value is not a valid enumeration member; permitted: 'mozzarella', 'tomato sauce', 'prosciutto', 'basil', 'rucola'\",\n",
392 | " \"type\": \"type_error.enum\",\n",
393 | " \"ctx\": {\n",
394 | " \"enum_values\": [\n",
395 | " \"mozzarella\",\n",
396 | " \"tomato sauce\",\n",
397 | " \"prosciutto\",\n",
398 | " \"basil\",\n",
399 | " \"rucola\"\n",
400 | " ]\n",
401 | " }\n",
402 | " }\n",
403 | "]\n"
404 | ]
405 | }
406 | ],
407 | "source": [
408 | "try:\n",
409 | " Pizza(style=\"Napoli\", toppings=(2,))\n",
410 | "except ValidationError as err:\n",
411 | " print(err.json())"
412 | ]
413 | },
414 | {
415 | "cell_type": "markdown",
416 | "metadata": {},
417 | "source": [
418 | "We can also export a JSONSchema directly from our model, this is very useful for instance if we want to use your model to feed a Swagger/OpenAPI-spec. 📜✅\n",
419 | "\n",
420 | "⚠ *Caution: Pydantic uses the latest draft 7 of JSONSchema, this will be used in the comming OpenAPI 3.1 spec but the current 3.0.x spec uses draft 4. I spoke with Samuel Colvin, the creator of pydantic about this and his recommendation is to write a `schema_extra`function to use the older JSONSchema version if you want strict compability. The FastAPI framework doesn't do this and is slightly incompatible with the current OpenAPI-spec*"
421 | ]
422 | },
423 | {
424 | "cell_type": "code",
425 | "execution_count": 16,
426 | "metadata": {},
427 | "outputs": [
428 | {
429 | "data": {
430 | "text/plain": [
431 | "{'title': 'Pizza',\n",
432 | " 'type': 'object',\n",
433 | " 'properties': {'style': {'title': 'Style', 'type': 'string'},\n",
434 | " 'toppings': {'type': 'array', 'items': {'$ref': '#/definitions/Topping'}}},\n",
435 | " 'required': ['style', 'toppings'],\n",
436 | " 'definitions': {'Topping': {'title': 'Topping',\n",
437 | " 'description': 'An enumeration.',\n",
438 | " 'enum': ['mozzarella', 'tomato sauce', 'prosciutto', 'basil', 'rucola'],\n",
439 | " 'type': 'string'}}}"
440 | ]
441 | },
442 | "execution_count": 16,
443 | "metadata": {},
444 | "output_type": "execute_result"
445 | }
446 | ],
447 | "source": [
448 | "Pizza.schema()"
449 | ]
450 | },
451 | {
452 | "cell_type": "markdown",
453 | "metadata": {},
454 | "source": [
455 | "That was the basics using the built-in validators, but what if you want to implement your own business rules in a custom validator, we're going to look at this next.\n",
456 | "\n",
457 | "We now want to add a new property for `oven_temperature`, but in our case we also want to ensure that we are close to the ideal of roughly 375°C for Neapolitan pizzas, which is our imaginary restaurants house style."
458 | ]
459 | },
460 | {
461 | "cell_type": "code",
462 | "execution_count": 17,
463 | "metadata": {},
464 | "outputs": [],
465 | "source": [
466 | "from pydantic import validator, root_validator\n",
467 | "class BakedPizza(Pizza):\n",
468 | " # For simplicity in the example we use int for temperature\n",
469 | " oven_temperature: int\n",
470 | " \n",
471 | " # A validator looking at a single property\n",
472 | " @validator('style')\n",
473 | " def check_style(cls, style):\n",
474 | " house_styles = (\"Napoli\", \"Roman\", \"Italian\")\n",
475 | " if style not in house_styles:\n",
476 | " raise ValueError(f\"We only cook the following styles: {house_styles}, given: {style}\")\n",
477 | " return style\n",
478 | " \n",
479 | " # Root validators check the entire model\n",
480 | " @root_validator\n",
481 | " def check_temp(cls, values):\n",
482 | " style, temp = values.get(\"style\"), values.get(\"oven_temperature\")\n",
483 | " \n",
484 | " if style != \"Napoli\":\n",
485 | " # We don't have any special rules yet for the other styles\n",
486 | " return values\n",
487 | "\n",
488 | " if 350 <= temp <= 400: \n",
489 | " # Target temperature 350 - 400°C, ideally around 375°C\n",
490 | " return values\n",
491 | "\n",
492 | " raise ValueError(f\"Napoli pizzas require a oven_temperature in the range of 350 - 400°C, given: {temp}°C\")\n"
493 | ]
494 | },
495 | {
496 | "cell_type": "markdown",
497 | "metadata": {},
498 | "source": [
499 | "Now let's see if we create some invalid pizzas ⚠️🚨"
500 | ]
501 | },
502 | {
503 | "cell_type": "code",
504 | "execution_count": 18,
505 | "metadata": {},
506 | "outputs": [
507 | {
508 | "name": "stdout",
509 | "output_type": "stream",
510 | "text": [
511 | "1 validation error for BakedPizza\n",
512 | "style\n",
513 | " We only cook the following styles: ('Napoli', 'Roman', 'Italian'), given: Panpizza (type=value_error)\n"
514 | ]
515 | }
516 | ],
517 | "source": [
518 | "try: \n",
519 | " BakedPizza(style=\"Panpizza\", toppings=[\"tomato sauce\"], oven_temperature=250 )\n",
520 | "except ValidationError as err:\n",
521 | " print(err)"
522 | ]
523 | },
524 | {
525 | "cell_type": "code",
526 | "execution_count": 19,
527 | "metadata": {},
528 | "outputs": [
529 | {
530 | "name": "stdout",
531 | "output_type": "stream",
532 | "text": [
533 | "1 validation error for BakedPizza\n",
534 | "__root__\n",
535 | " Napoli pizzas require a oven_temperature in the range of 350 - 400°C, given: 300°C (type=value_error)\n"
536 | ]
537 | }
538 | ],
539 | "source": [
540 | "try: \n",
541 | " BakedPizza(style=\"Napoli\", toppings=[\"tomato sauce\"], oven_temperature=300 )\n",
542 | "except ValidationError as err:\n",
543 | " print(err)"
544 | ]
545 | },
546 | {
547 | "cell_type": "markdown",
548 | "metadata": {},
549 | "source": [
550 | "Now let's create a pizza 🍕 allowed by our rules! ✨"
551 | ]
552 | },
553 | {
554 | "cell_type": "code",
555 | "execution_count": 20,
556 | "metadata": {},
557 | "outputs": [
558 | {
559 | "data": {
560 | "text/plain": [
561 | "BakedPizza(style='Napoli', toppings=(,), oven_temperature=350)"
562 | ]
563 | },
564 | "execution_count": 20,
565 | "metadata": {},
566 | "output_type": "execute_result"
567 | }
568 | ],
569 | "source": [
570 | "BakedPizza(style=\"Napoli\", toppings=[\"tomato sauce\"], oven_temperature=350)"
571 | ]
572 | },
573 | {
574 | "cell_type": "markdown",
575 | "metadata": {},
576 | "source": [
577 | "Gosh these runtime type checkers are rather useful, but what about **functions**? \n",
578 | "\n",
579 | "Pydantic got you covered with `@validate_arguments`. *Still in beta, API may change, release 2020-04-18 in version 1.5*"
580 | ]
581 | },
582 | {
583 | "cell_type": "code",
584 | "execution_count": 21,
585 | "metadata": {},
586 | "outputs": [],
587 | "source": [
588 | "from pydantic import validate_arguments\n",
589 | "\n",
590 | "# Validator on function\n",
591 | "# Ensure that we use a valid pizza when making orders\n",
592 | "@validate_arguments\n",
593 | "def make_order(pizza: Pizza):\n",
594 | " ...\n",
595 | " "
596 | ]
597 | },
598 | {
599 | "cell_type": "code",
600 | "execution_count": 22,
601 | "metadata": {},
602 | "outputs": [
603 | {
604 | "name": "stdout",
605 | "output_type": "stream",
606 | "text": [
607 | "1 validation error for MakeOrder\n",
608 | "pizza -> toppings -> 3\n",
609 | " value is not a valid enumeration member; permitted: 'mozzarella', 'tomato sauce', 'prosciutto', 'basil', 'rucola' (type=type_error.enum; enum_values=[, , , , ])\n"
610 | ]
611 | }
612 | ],
613 | "source": [
614 | "try:\n",
615 | " make_order({\n",
616 | " \"style\":\"Napoli\",\n",
617 | " \"toppings\":(\"tomato sauce\", \"mozzarella\", \"prosciutto\", \"pineapple\")\n",
618 | " })\n",
619 | "except ValidationError as err:\n",
620 | " print(err)"
621 | ]
622 | },
623 | {
624 | "cell_type": "markdown",
625 | "metadata": {},
626 | "source": [
627 | "## FastAPI\n",
628 | "FastAPI is a lean microframework similar to Flask which utilizes pydantic models heavily, it will also automatically generate OpenAPI-specifications from your application based on your models.\n",
629 | "\n",
630 | "This gives you framework agnostic models while still being able to leverage tight integration with a modern and easy to use framework. If you're going to start a new API-project i highly recommend trying FastAPI."
631 | ]
632 | },
633 | {
634 | "cell_type": "code",
635 | "execution_count": 23,
636 | "metadata": {},
637 | "outputs": [],
638 | "source": [
639 | "from fastapi import FastAPI\n",
640 | "from pydantic import BaseModel\n",
641 | "\n",
642 | "app = FastAPI()\n",
643 | "\n",
644 | "def make_order(pizza: Pizza):\n",
645 | " # Business logic for making an order\n",
646 | " pass\n",
647 | "\n",
648 | "def dispatch_order(pizza: BakedPizza):\n",
649 | " # Hand over pizza to delivery company\n",
650 | " pass\n",
651 | "\n",
652 | "# Deliver a baked pizza\n",
653 | "@app.post(\"/delivery/pizza\")\n",
654 | "async def deliver_pizza_order(pizza: BakedPizza):\n",
655 | " dispatch = dispatch_order(pizza)\n",
656 | " return dispatch\n",
657 | "\n",
658 | "@app.post(\"/order/pizza\")\n",
659 | "async def order_pizza(pizza: Pizza):\n",
660 | " order = make_order(pizza)\n",
661 | " return order\n"
662 | ]
663 | },
664 | {
665 | "cell_type": "markdown",
666 | "metadata": {},
667 | "source": [
668 | "This is everything we need to create a small API around our models."
669 | ]
670 | },
671 | {
672 | "cell_type": "markdown",
673 | "metadata": {},
674 | "source": [
675 | "---\n",
676 | "\n",
677 | "That's it, a quick introduction to pydantic! \n",
678 | "\n",
679 | "But this is just the tip of the iceberg 🗻 and I want to give you a hint about what more can be done. \n",
680 | "I'm not going to go into detail in any of this but feel free to ask me about it in the chat, on Twitter/LinkedIn or via email. 💬📨"
681 | ]
682 | },
683 | {
684 | "cell_type": "markdown",
685 | "metadata": {},
686 | "source": [
687 | "## Cool features worth mentioning\n",
688 | "\n",
689 | "- Post **1.0**, reached this milestone about a year ago\n",
690 | "- Support for [standard library types](https://pydantic-docs.helpmanual.io/usage/types/#pydantic-types)\n",
691 | "- Offer useful extra types for every day use\n",
692 | " - Email\n",
693 | " - HttpUrl (and more, stricturl for custom validation)\n",
694 | " - PostgresDsn\n",
695 | " - IPvAnyAddress (as well as IPv4Address and IPv6Address from ipaddress)\n",
696 | " - PositiveInt\n",
697 | " - PaymentCardNumber, PaymentCardBrand.[amex, mastercard, visa, other], checks luhn, str of digits and BIN-based lenght.\n",
698 | " - [Constrained types](https://pydantic-docs.helpmanual.io/usage/types/#constrained-types) (e.g. conlist, conint, etc.)\n",
699 | " - and more…\n",
700 | "- Supports [custom datatypes](https://pydantic-docs.helpmanual.io/usage/types/#custom-data-types)\n",
701 | "- [Settings management](https://pydantic-docs.helpmanual.io/usage/settings/)\n",
702 | " - Typed configuration management\n",
703 | " - Automatically reads from environment variables\n",
704 | " - Dotenv (`.env`) support via defacto standard [python-dotenv](https://pypi.org/project/python-dotenv/).\n",
705 | "- ORM-mode\n",
706 | "- Recursive models\n",
707 | "- Works with mypy out of the box, [mypy plugin](https://pydantic-docs.helpmanual.io/mypy_plugin/) further improves experience.\n",
708 | "- [Postponed annotations, self-referencing models](https://pydantic-docs.helpmanual.io/usage/postponed_annotations/), [PEP-563](https://www.python.org/dev/peps/pep-0563/)-style.\n",
709 | "- python-devtools intergration\n",
710 | "- PyCharm plugin\n",
711 | "- [Fast](https://pydantic-docs.helpmanual.io/benchmarks/) compared to popular alternatives! \n",
712 | " But always make your own benchmarks for your own usecase if performance is important for you.\n",
713 | "\n",
714 | "## Future\n",
715 | "- A strict mode is being worked on, in the future this will enable us to choose between Strict and Coercion on a model level instead of relying on the Strict* types.\n",
716 | "- The project is very active and a lot of improvements are constantly being made to the library.\n",
717 | " "
718 | ]
719 | },
720 | {
721 | "cell_type": "markdown",
722 | "metadata": {},
723 | "source": [
724 | "## Conclusion\n",
725 | "Pure python syntax\n",
726 | "Better validation\n",
727 | "Very useful JSON-tools for API's\n",
728 | "Easy to migrate from dataclasses\n",
729 | "Lots of useful features\n",
730 | "Try it out!"
731 | ]
732 | },
733 | {
734 | "cell_type": "markdown",
735 | "metadata": {},
736 | "source": [
737 | "## Want to hear more from me? \n",
738 | "I'm making a course on property based testing in python using Hypothesis. \n",
739 | "[Sign up here](https://forms.gle/yRWapypPXdPFSLME7)"
740 | ]
741 | },
742 | {
743 | "cell_type": "code",
744 | "execution_count": null,
745 | "metadata": {},
746 | "outputs": [],
747 | "source": []
748 | }
749 | ],
750 | "metadata": {
751 | "kernelspec": {
752 | "display_name": "Python 3",
753 | "language": "python",
754 | "name": "python3"
755 | },
756 | "language_info": {
757 | "codemirror_mode": {
758 | "name": "ipython",
759 | "version": 3
760 | },
761 | "file_extension": ".py",
762 | "mimetype": "text/x-python",
763 | "name": "python",
764 | "nbconvert_exporter": "python",
765 | "pygments_lexer": "ipython3",
766 | "version": "3.9.1"
767 | }
768 | },
769 | "nbformat": 4,
770 | "nbformat_minor": 4
771 | }
772 |
--------------------------------------------------------------------------------
/demo/pydantic_more_examples.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": 6,
6 | "metadata": {},
7 | "outputs": [
8 | {
9 | "name": "stdout",
10 | "output_type": "stream",
11 | "text": [
12 | "\"Foo\" object has no field \"patched_attribute\"\n"
13 | ]
14 | }
15 | ],
16 | "source": [
17 | "from pydantic import BaseModel\n",
18 | "from pydantic.dataclasses import dataclass\n",
19 | "\n",
20 | "# Based on questions from Gergo Jedlicska\n",
21 | "class Foo(BaseModel):\n",
22 | " bar: str\n",
23 | "\n",
24 | "\n",
25 | "@dataclass\n",
26 | "class DataFoo():\n",
27 | " bar: str\n",
28 | "\n",
29 | "\n",
30 | "# pydantic dataclass is just a thin wrapper around builtin dataclass,\n",
31 | "# so this is completely fine\n",
32 | "data_foo = DataFoo(bar=\"bar\")\n",
33 | "\n",
34 | "data_foo.patched_attribute = 123\n",
35 | "\n",
36 | "# but with the pydantic base model, the object behaves like a class with __slot__ implementation\n",
37 | "try:\n",
38 | " foo = Foo(bar=\"bar\")\n",
39 | "\n",
40 | " # this is fine, I want the class to stay mutable\n",
41 | " foo.bar = \"a higher bar\"\n",
42 | "\n",
43 | " # this is not ok. It patches the attribute at runtime\n",
44 | " foo.patched_attribute = 123\n",
45 | "except ValueError as err:\n",
46 | " print(err)"
47 | ]
48 | },
49 | {
50 | "cell_type": "code",
51 | "execution_count": null,
52 | "metadata": {},
53 | "outputs": [],
54 | "source": []
55 | }
56 | ],
57 | "metadata": {
58 | "kernelspec": {
59 | "display_name": "Python 3",
60 | "language": "python",
61 | "name": "python3"
62 | },
63 | "language_info": {
64 | "codemirror_mode": {
65 | "name": "ipython",
66 | "version": 3
67 | },
68 | "file_extension": ".py",
69 | "mimetype": "text/x-python",
70 | "name": "python",
71 | "nbconvert_exporter": "python",
72 | "pygments_lexer": "ipython3",
73 | "version": "3.8.0"
74 | }
75 | },
76 | "nbformat": 4,
77 | "nbformat_minor": 4
78 | }
79 |
--------------------------------------------------------------------------------
/demo/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "pydantic-intro-demo"
3 | version = "0.1.0"
4 | description = "Introduction to pydantic, code demo"
5 | authors = ["Alexander Hultnér "]
6 | license = "BSD-3-Clause"
7 |
8 | [tool.poetry.dependencies]
9 | python = "^3.8"
10 | pydantic = "^1.8.1"
11 | jupyterlab = "^3.0.12"
12 | bpython = "^0.21"
13 | fastapi = "^0.63.0"
14 | uvicorn = "^0.13.4"
15 | mypy = "^0.812"
16 | python-devtools = "^2"
17 |
18 | [tool.poetry.dev-dependencies]
19 |
20 | [build-system]
21 | requires = ["poetry>=0.12"]
22 | build-backend = "poetry.masonry.api"
23 |
--------------------------------------------------------------------------------