├── .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 | Hultnér Technologies 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 | [![Speaker at Python Pizza 2020, Alexander Hultnér talks about pydantic](https://i.ytimg.com/vi/LzNBfPVtrPk/maxresdefault.jpg)](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 | --------------------------------------------------------------------------------