├── .idea
├── .gitignore
├── misc.xml
├── vcs.xml
├── inspectionProfiles
│ ├── profiles_settings.xml
│ └── Project_Default.xml
├── modules.xml
└── python-sample-fastapi-application.iml
├── data.db
├── FastAPI.png
├── sample-FastAPI.png
├── Pipfile
├── db.py
├── sql_app
├── schemas.py
├── models.py
└── repositories.py
├── universities.py
├── LICENSE.txt
├── README.md
├── main.py
└── Pipfile.lock
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/data.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sumanentc/python-sample-FastAPI-application/HEAD/data.db
--------------------------------------------------------------------------------
/FastAPI.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sumanentc/python-sample-FastAPI-application/HEAD/FastAPI.png
--------------------------------------------------------------------------------
/sample-FastAPI.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sumanentc/python-sample-FastAPI-application/HEAD/sample-FastAPI.png
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Pipfile:
--------------------------------------------------------------------------------
1 | [[source]]
2 | url = "https://pypi.org/simple"
3 | verify_ssl = true
4 | name = "pypi"
5 |
6 | [packages]
7 | fastapi = "==0.68.1"
8 | uvicorn = "==0.15.0"
9 | sqlalchemy = "==1.4.23"
10 | httpx = "==0.21.1"
11 |
12 | [dev-packages]
13 |
14 | [requires]
15 | python_version = "3.9.2"
16 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/python-sample-fastapi-application.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/db.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import create_engine
2 | from sqlalchemy.ext.declarative import declarative_base
3 | from sqlalchemy.orm import sessionmaker
4 |
5 | SQLALCHEMY_DATABASE_URL = "sqlite:///./data.db"
6 |
7 |
8 | engine = create_engine(
9 | SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False},echo=True
10 | )
11 | SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
12 |
13 | Base = declarative_base()
14 |
15 |
16 | # Dependency
17 | def get_db():
18 | db = SessionLocal()
19 | try:
20 | yield db
21 | finally:
22 | db.close()
23 |
24 |
--------------------------------------------------------------------------------
/sql_app/schemas.py:
--------------------------------------------------------------------------------
1 | from typing import List, Optional
2 |
3 | from pydantic import BaseModel
4 |
5 |
6 | class ItemBase(BaseModel):
7 | name: str
8 | price: float
9 | description: Optional[str] = None
10 | store_id: int
11 |
12 |
13 | class ItemCreate(ItemBase):
14 | pass
15 |
16 |
17 | class Item(ItemBase):
18 | id: int
19 |
20 | class Config:
21 | orm_mode = True
22 |
23 |
24 | class StoreBase(BaseModel):
25 | name: str
26 |
27 |
28 | class StoreCreate(StoreBase):
29 | pass
30 |
31 |
32 | class Store(StoreBase):
33 | id: int
34 | items: List[Item] = []
35 |
36 | class Config:
37 | orm_mode = True
38 |
39 |
40 | class University(BaseModel):
41 | country: Optional[str] = None
42 | web_pages: List[str] = []
43 | name: Optional[str] = None
44 | alpha_two_code: Optional[str] = None
45 | domains: List[str] = []
46 |
--------------------------------------------------------------------------------
/sql_app/models.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import Column, ForeignKey, Integer, String, Float
2 | from sqlalchemy.orm import relationship
3 |
4 | from db import Base
5 |
6 | class Item(Base):
7 | __tablename__ = "items"
8 |
9 | id = Column(Integer, primary_key=True,index=True)
10 | name = Column(String(80), nullable=False, unique=True,index=True)
11 | price = Column(Float(precision=2), nullable=False)
12 | description = Column(String(200))
13 | store_id = Column(Integer,ForeignKey('stores.id'),nullable=False)
14 | def __repr__(self):
15 | return 'ItemModel(name=%s, price=%s,store_id=%s)' % (self.name, self.price,self.store_id)
16 |
17 | class Store(Base):
18 | __tablename__ = "stores"
19 | id = Column(Integer, primary_key=True,index=True)
20 | name = Column(String(80), nullable=False, unique=True)
21 | items = relationship("Item",primaryjoin="Store.id == Item.store_id",cascade="all, delete-orphan")
22 |
23 | def __repr__(self):
24 | return 'Store(name=%s)' % self.name
--------------------------------------------------------------------------------
/universities.py:
--------------------------------------------------------------------------------
1 | import httpx
2 | import json
3 | from sql_app.schemas import University
4 |
5 | url = 'http://universities.hipolabs.com/search'
6 |
7 |
8 | def get_all_universities_for_country(country: str) -> dict:
9 | params = {'country': country}
10 | client = httpx.Client()
11 | response = client.get(url, params=params)
12 | response_json = json.loads(response.text)
13 | universities = []
14 | for university in response_json:
15 | university_obj = University.parse_obj(university)
16 | universities.append(university_obj)
17 | return {country: universities}
18 |
19 |
20 | async def get_all_universities_for_country_async(country: str, data: dict) -> None:
21 | params = {'country': country}
22 | async with httpx.AsyncClient() as client:
23 | response = await client.get(url, params=params)
24 | response_json = json.loads(response.text)
25 | universities = []
26 | for university in response_json:
27 | university_obj = University.parse_obj(university)
28 | universities.append(university_obj)
29 | data[country] = universities
30 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Suman Das
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/sql_app/repositories.py:
--------------------------------------------------------------------------------
1 |
2 | from sqlalchemy.orm import Session
3 |
4 | from . import models, schemas
5 |
6 |
7 | class ItemRepo:
8 |
9 | async def create(db: Session, item: schemas.ItemCreate):
10 | db_item = models.Item(name=item.name,price=item.price,description=item.description,store_id=item.store_id)
11 | db.add(db_item)
12 | db.commit()
13 | db.refresh(db_item)
14 | return db_item
15 |
16 | def fetch_by_id(db: Session,_id):
17 | return db.query(models.Item).filter(models.Item.id == _id).first()
18 |
19 | def fetch_by_name(db: Session,name):
20 | return db.query(models.Item).filter(models.Item.name == name).first()
21 |
22 | def fetch_all(db: Session, skip: int = 0, limit: int = 100):
23 | return db.query(models.Item).offset(skip).limit(limit).all()
24 |
25 | async def delete(db: Session,item_id):
26 | db_item= db.query(models.Item).filter_by(id=item_id).first()
27 | db.delete(db_item)
28 | db.commit()
29 |
30 |
31 | async def update(db: Session,item_data):
32 | updated_item = db.merge(item_data)
33 | db.commit()
34 | return updated_item
35 |
36 |
37 |
38 | class StoreRepo:
39 |
40 | async def create(db: Session, store: schemas.StoreCreate):
41 | db_store = models.Store(name=store.name)
42 | db.add(db_store)
43 | db.commit()
44 | db.refresh(db_store)
45 | return db_store
46 |
47 | def fetch_by_id(db: Session,_id:int):
48 | return db.query(models.Store).filter(models.Store.id == _id).first()
49 |
50 | def fetch_by_name(db: Session,name:str):
51 | return db.query(models.Store).filter(models.Store.name == name).first()
52 |
53 | def fetch_all(db: Session, skip: int = 0, limit: int = 100):
54 | return db.query(models.Store).offset(skip).limit(limit).all()
55 |
56 | async def delete(db: Session,_id:int):
57 | db_store= db.query(models.Store).filter_by(id=_id).first()
58 | db.delete(db_store)
59 | db.commit()
60 |
61 | async def update(db: Session,store_data):
62 | db.merge(store_data)
63 | db.commit()
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Sample Python REST API application using [FastAPI](https://fastapi.tiangolo.com/) ,[Uvicorn](https://www.uvicorn.org/#introduction) and [SQLAlchemy](https://www.sqlalchemy.org/)
2 |
3 | 
4 |
5 | ### FastAPI
6 |
7 | FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints.
8 |
9 | The key features are:
10 |
11 | **Fast**: Very high performance, on par with NodeJS and Go (thanks to [Starlette](https://www.starlette.io/) and [Pydantic](https://pydantic-docs.helpmanual.io/)). One of the fastest Python frameworks available.
12 |
13 | **Fast to code**: It allows for significant increases in development speed.
14 |
15 | **Easy**: Designed to be easy to use and learn. Less time reading docs.
16 | **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs.
17 | **Robust**: Get production-ready code. With automatic interactive documentation.
18 | **Standards-based**: It’s based on the open standards for APIs, [OpenAPI](https://github.com/OAI/OpenAPI-Specification) and [JSON Schema](https://json-schema.org/).
19 |
20 | ### Uvicorn
21 |
22 | Uvicorn is a lightning-fast ASGI server implementation, using [uvloop](https://github.com/MagicStack/uvloop) and [httptools](https://github.com/MagicStack/httptools).
23 |
24 | Until recently Python has lacked a minimal low-level server/application interface for asyncio frameworks. The ASGI specification fills this gap, and means we're now able to start building a common set of tooling usable across all asyncio frameworks.
25 |
26 | ASGI should help enable an ecosystem of Python web frameworks that are highly competitive against Node and Go in terms of achieving high throughput in IO-bound contexts. It also provides support for HTTP/2 and WebSockets, which cannot be handled by WSGI.
27 |
28 | Uvicorn currently supports HTTP/1.1 and WebSockets. Support for HTTP/2 is planned.
29 |
30 | ### SQLAlchemy
31 |
32 | SQLAlchemy is the Python SQL toolkit and Object Relational Mapper that gives application developers the full power and flexibility of SQL.
33 |
34 | It provides a full suite of well known enterprise-level persistence patterns, designed for efficient and high-performing database access, adapted into a simple and Pythonic domain language.
35 |
36 | ### Pydantic
37 |
38 | Data validation and settings management using python type annotations.
39 |
40 | pydantic enforces type hints at runtime, and provides user-friendly errors when data is invalid.
41 |
42 | ## Setting up the VirtualEnv and install dependencies
43 |
44 | Go inside the project folder and execute the below commands. We will use [Pipenv](https://pypi.org/project/pipenv/) to setup the VirtualEnv.
45 |
46 | ```
47 | pipenv shell
48 | pipenv install
49 |
50 | ```
51 |
52 | Dependencies will be installed from the Pipfile. Python version 3.9.2 is used for this project.
53 |
54 | ## Run the Application
55 |
56 | ```
57 | python main.py
58 |
59 | ```
60 |
61 | This will start the application on port 9000
62 |
63 | ## Test the application
64 |
65 | FastAPI also automatically generated fully interactive API documentation that we can use to interact with our API.
66 | We can visit http://127.0.0.1:9000/docs in our browser to see the interactive API documentation provided by [Swagger UI](https://github.com/swagger-api/swagger-ui):
67 |
68 | 
69 |
70 | The server will start at .
71 |
72 |
73 | Please check my write-up for further details
74 |
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
1 | from typing import List, Optional
2 |
3 | import uvicorn
4 | from fastapi import Depends, FastAPI, HTTPException
5 | from fastapi.encoders import jsonable_encoder
6 | from fastapi.responses import JSONResponse
7 | from sqlalchemy.orm import Session
8 | import universities
9 | import time
10 | import asyncio
11 |
12 | import sql_app.models as models
13 | import sql_app.schemas as schemas
14 | from db import get_db, engine
15 | from sql_app.repositories import ItemRepo, StoreRepo
16 |
17 | app = FastAPI(title="Sample FastAPI Application",
18 | description="Sample FastAPI Application with Swagger and Sqlalchemy",
19 | version="1.0.0", )
20 |
21 | models.Base.metadata.create_all(bind=engine)
22 |
23 |
24 | @app.exception_handler(Exception)
25 | def validation_exception_handler(request, err):
26 | base_error_message = f"Failed to execute: {request.method}: {request.url}"
27 | return JSONResponse(status_code=400, content={"message": f"{base_error_message}. Detail: {err}"})
28 |
29 |
30 | @app.middleware("http")
31 | async def add_process_time_header(request, call_next):
32 | print('inside middleware!')
33 | start_time = time.time()
34 | response = await call_next(request)
35 | process_time = time.time() - start_time
36 | response.headers["X-Process-Time"] = str(f'{process_time:0.4f} sec')
37 | return response
38 |
39 |
40 | @app.post('/items', tags=["Item"], response_model=schemas.Item, status_code=201)
41 | async def create_item(item_request: schemas.ItemCreate, db: Session = Depends(get_db)):
42 | """
43 | Create an Item and store it in the database
44 | """
45 |
46 | db_item = ItemRepo.fetch_by_name(db, name=item_request.name)
47 | if db_item:
48 | raise HTTPException(status_code=400, detail="Item already exists!")
49 |
50 | return await ItemRepo.create(db=db, item=item_request)
51 |
52 |
53 | @app.get('/items', tags=["Item"], response_model=List[schemas.Item])
54 | def get_all_items(name: Optional[str] = None, db: Session = Depends(get_db)):
55 | """
56 | Get all the Items stored in database
57 | """
58 | if name:
59 | items = []
60 | db_item = ItemRepo.fetch_by_name(db, name)
61 | items.append(db_item)
62 | return items
63 | else:
64 | return ItemRepo.fetch_all(db)
65 |
66 |
67 | @app.get('/items/{item_id}', tags=["Item"], response_model=schemas.Item)
68 | def get_item(item_id: int, db: Session = Depends(get_db)):
69 | """
70 | Get the Item with the given ID provided by User stored in database
71 | """
72 | db_item = ItemRepo.fetch_by_id(db, item_id)
73 | if db_item is None:
74 | raise HTTPException(status_code=404, detail="Item not found with the given ID")
75 | return db_item
76 |
77 |
78 | @app.delete('/items/{item_id}', tags=["Item"])
79 | async def delete_item(item_id: int, db: Session = Depends(get_db)):
80 | """
81 | Delete the Item with the given ID provided by User stored in database
82 | """
83 | db_item = ItemRepo.fetch_by_id(db, item_id)
84 | if db_item is None:
85 | raise HTTPException(status_code=404, detail="Item not found with the given ID")
86 | await ItemRepo.delete(db, item_id)
87 | return "Item deleted successfully!"
88 |
89 |
90 | @app.put('/items/{item_id}', tags=["Item"], response_model=schemas.Item)
91 | async def update_item(item_id: int, item_request: schemas.Item, db: Session = Depends(get_db)):
92 | """
93 | Update an Item stored in the database
94 | """
95 | db_item = ItemRepo.fetch_by_id(db, item_id)
96 | if db_item:
97 | update_item_encoded = jsonable_encoder(item_request)
98 | db_item.name = update_item_encoded['name']
99 | db_item.price = update_item_encoded['price']
100 | db_item.description = update_item_encoded['description']
101 | db_item.store_id = update_item_encoded['store_id']
102 | return await ItemRepo.update(db=db, item_data=db_item)
103 | else:
104 | raise HTTPException(status_code=400, detail="Item not found with the given ID")
105 |
106 |
107 | @app.post('/stores', tags=["Store"], response_model=schemas.Store, status_code=201)
108 | async def create_store(store_request: schemas.StoreCreate, db: Session = Depends(get_db)):
109 | """
110 | Create a Store and save it in the database
111 | """
112 | db_store = StoreRepo.fetch_by_name(db, name=store_request.name)
113 | print(db_store)
114 | if db_store:
115 | raise HTTPException(status_code=400, detail="Store already exists!")
116 |
117 | return await StoreRepo.create(db=db, store=store_request)
118 |
119 |
120 | @app.get('/stores', tags=["Store"], response_model=List[schemas.Store])
121 | def get_all_stores(name: Optional[str] = None, db: Session = Depends(get_db)):
122 | """
123 | Get all the Stores stored in database
124 | """
125 | if name:
126 | stores = []
127 | db_store = StoreRepo.fetch_by_name(db, name)
128 | print(db_store)
129 | stores.append(db_store)
130 | return stores
131 | else:
132 | return StoreRepo.fetch_all(db)
133 |
134 |
135 | @app.get('/stores/{store_id}', tags=["Store"], response_model=schemas.Store)
136 | def get_store(store_id: int, db: Session = Depends(get_db)):
137 | """
138 | Get the Store with the given ID provided by User stored in database
139 | """
140 | db_store = StoreRepo.fetch_by_id(db, store_id)
141 | if db_store is None:
142 | raise HTTPException(status_code=404, detail="Store not found with the given ID")
143 | return db_store
144 |
145 |
146 | @app.delete('/stores/{store_id}', tags=["Store"])
147 | async def delete_store(store_id: int, db: Session = Depends(get_db)):
148 | """
149 | Delete the Item with the given ID provided by User stored in database
150 | """
151 | db_store = StoreRepo.fetch_by_id(db, store_id)
152 | if db_store is None:
153 | raise HTTPException(status_code=404, detail="Store not found with the given ID")
154 | await StoreRepo.delete(db, store_id)
155 | return "Store deleted successfully!"
156 |
157 |
158 | @app.get("/universities/", tags=["University"])
159 | def get_universities() -> dict:
160 | """
161 | Return the List of universities for some random countries in sync way
162 | """
163 | data: dict = {}
164 | data.update(universities.get_all_universities_for_country("turkey"))
165 | data.update(universities.get_all_universities_for_country("india"))
166 | data.update(universities.get_all_universities_for_country("australia"))
167 | return data
168 |
169 |
170 | @app.get("/universities/async", tags=["University"])
171 | async def get_universities_async() -> dict:
172 | """
173 | Return the List of universities for some random countries in async way
174 | """
175 | data: dict = {}
176 | await asyncio.gather(universities.get_all_universities_for_country_async("turkey", data),
177 | universities.get_all_universities_for_country_async("india", data),
178 | universities.get_all_universities_for_country_async("australia", data))
179 | return data
180 |
181 |
182 | if __name__ == "__main__":
183 | uvicorn.run("main:app", port=9000, reload=True)
184 |
--------------------------------------------------------------------------------
/Pipfile.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_meta": {
3 | "hash": {
4 | "sha256": "5a406903273e75ed10083f4719142d3da6997640d315a1b9129cfa0bfb41e53f"
5 | },
6 | "pipfile-spec": 6,
7 | "requires": {
8 | "python_version": "3.9.2"
9 | },
10 | "sources": [
11 | {
12 | "name": "pypi",
13 | "url": "https://pypi.org/simple",
14 | "verify_ssl": true
15 | }
16 | ]
17 | },
18 | "default": {
19 | "anyio": {
20 | "hashes": [
21 | "sha256:24adc69309fb5779bc1e06158e143e0b6d2c56b302a3ac3de3083c705a6ed39d",
22 | "sha256:2855a9423524abcdd652d942f8932fda1735210f77a6b392eafd9ff34d3fe020"
23 | ],
24 | "markers": "python_full_version >= '3.6.2'",
25 | "version": "==3.4.0"
26 | },
27 | "asgiref": {
28 | "hashes": [
29 | "sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9",
30 | "sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214"
31 | ],
32 | "markers": "python_version >= '3.6'",
33 | "version": "==3.4.1"
34 | },
35 | "certifi": {
36 | "hashes": [
37 | "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872",
38 | "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"
39 | ],
40 | "version": "==2021.10.8"
41 | },
42 | "charset-normalizer": {
43 | "hashes": [
44 | "sha256:876d180e9d7432c5d1dfd4c5d26b72f099d503e8fcc0feb7532c9289be60fcbd",
45 | "sha256:cb957888737fc0bbcd78e3df769addb41fd1ff8cf950dc9e7ad7793f1bf44455"
46 | ],
47 | "markers": "python_version >= '3.5'",
48 | "version": "==2.0.10"
49 | },
50 | "click": {
51 | "hashes": [
52 | "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3",
53 | "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"
54 | ],
55 | "markers": "python_version >= '3.6'",
56 | "version": "==8.0.3"
57 | },
58 | "fastapi": {
59 | "hashes": [
60 | "sha256:644bb815bae326575c4b2842469fb83053a4b974b82fa792ff9283d17fbbd99d",
61 | "sha256:94d2820906c36b9b8303796fb7271337ec89c74223229e3cfcf056b5a7d59e23"
62 | ],
63 | "index": "pypi",
64 | "version": "==0.68.1"
65 | },
66 | "greenlet": {
67 | "hashes": [
68 | "sha256:0051c6f1f27cb756ffc0ffbac7d2cd48cb0362ac1736871399a739b2885134d3",
69 | "sha256:00e44c8afdbe5467e4f7b5851be223be68adb4272f44696ee71fe46b7036a711",
70 | "sha256:013d61294b6cd8fe3242932c1c5e36e5d1db2c8afb58606c5a67efce62c1f5fd",
71 | "sha256:049fe7579230e44daef03a259faa24511d10ebfa44f69411d99e6a184fe68073",
72 | "sha256:14d4f3cd4e8b524ae9b8aa567858beed70c392fdec26dbdb0a8a418392e71708",
73 | "sha256:166eac03e48784a6a6e0e5f041cfebb1ab400b394db188c48b3a84737f505b67",
74 | "sha256:17ff94e7a83aa8671a25bf5b59326ec26da379ace2ebc4411d690d80a7fbcf23",
75 | "sha256:1e12bdc622676ce47ae9abbf455c189e442afdde8818d9da983085df6312e7a1",
76 | "sha256:21915eb821a6b3d9d8eefdaf57d6c345b970ad722f856cd71739493ce003ad08",
77 | "sha256:288c6a76705dc54fba69fbcb59904ae4ad768b4c768839b8ca5fdadec6dd8cfd",
78 | "sha256:2bde6792f313f4e918caabc46532aa64aa27a0db05d75b20edfc5c6f46479de2",
79 | "sha256:32ca72bbc673adbcfecb935bb3fb1b74e663d10a4b241aaa2f5a75fe1d1f90aa",
80 | "sha256:356b3576ad078c89a6107caa9c50cc14e98e3a6c4874a37c3e0273e4baf33de8",
81 | "sha256:40b951f601af999a8bf2ce8c71e8aaa4e8c6f78ff8afae7b808aae2dc50d4c40",
82 | "sha256:572e1787d1460da79590bf44304abbc0a2da944ea64ec549188fa84d89bba7ab",
83 | "sha256:58df5c2a0e293bf665a51f8a100d3e9956febfbf1d9aaf8c0677cf70218910c6",
84 | "sha256:64e6175c2e53195278d7388c454e0b30997573f3f4bd63697f88d855f7a6a1fc",
85 | "sha256:7227b47e73dedaa513cdebb98469705ef0d66eb5a1250144468e9c3097d6b59b",
86 | "sha256:7418b6bfc7fe3331541b84bb2141c9baf1ec7132a7ecd9f375912eca810e714e",
87 | "sha256:7cbd7574ce8e138bda9df4efc6bf2ab8572c9aff640d8ecfece1b006b68da963",
88 | "sha256:7ff61ff178250f9bb3cd89752df0f1dd0e27316a8bd1465351652b1b4a4cdfd3",
89 | "sha256:833e1551925ed51e6b44c800e71e77dacd7e49181fdc9ac9a0bf3714d515785d",
90 | "sha256:8639cadfda96737427330a094476d4c7a56ac03de7265622fcf4cfe57c8ae18d",
91 | "sha256:8c5d5b35f789a030ebb95bff352f1d27a93d81069f2adb3182d99882e095cefe",
92 | "sha256:8c790abda465726cfb8bb08bd4ca9a5d0a7bd77c7ac1ca1b839ad823b948ea28",
93 | "sha256:8d2f1fb53a421b410751887eb4ff21386d119ef9cde3797bf5e7ed49fb51a3b3",
94 | "sha256:903bbd302a2378f984aef528f76d4c9b1748f318fe1294961c072bdc7f2ffa3e",
95 | "sha256:93f81b134a165cc17123626ab8da2e30c0455441d4ab5576eed73a64c025b25c",
96 | "sha256:95e69877983ea39b7303570fa6760f81a3eec23d0e3ab2021b7144b94d06202d",
97 | "sha256:9633b3034d3d901f0a46b7939f8c4d64427dfba6bbc5a36b1a67364cf148a1b0",
98 | "sha256:97e5306482182170ade15c4b0d8386ded995a07d7cc2ca8f27958d34d6736497",
99 | "sha256:9f3cba480d3deb69f6ee2c1825060177a22c7826431458c697df88e6aeb3caee",
100 | "sha256:aa5b467f15e78b82257319aebc78dd2915e4c1436c3c0d1ad6f53e47ba6e2713",
101 | "sha256:abb7a75ed8b968f3061327c433a0fbd17b729947b400747c334a9c29a9af6c58",
102 | "sha256:aec52725173bd3a7b56fe91bc56eccb26fbdff1386ef123abb63c84c5b43b63a",
103 | "sha256:b11548073a2213d950c3f671aa88e6f83cda6e2fb97a8b6317b1b5b33d850e06",
104 | "sha256:b1692f7d6bc45e3200844be0dba153612103db241691088626a33ff1f24a0d88",
105 | "sha256:b336501a05e13b616ef81ce329c0e09ac5ed8c732d9ba7e3e983fcc1a9e86965",
106 | "sha256:b8c008de9d0daba7b6666aa5bbfdc23dcd78cafc33997c9b7741ff6353bafb7f",
107 | "sha256:b92e29e58bef6d9cfd340c72b04d74c4b4e9f70c9fa7c78b674d1fec18896dc4",
108 | "sha256:be5f425ff1f5f4b3c1e33ad64ab994eed12fc284a6ea71c5243fd564502ecbe5",
109 | "sha256:dd0b1e9e891f69e7675ba5c92e28b90eaa045f6ab134ffe70b52e948aa175b3c",
110 | "sha256:e30f5ea4ae2346e62cedde8794a56858a67b878dd79f7df76a0767e356b1744a",
111 | "sha256:e6a36bb9474218c7a5b27ae476035497a6990e21d04c279884eb10d9b290f1b1",
112 | "sha256:e859fcb4cbe93504ea18008d1df98dee4f7766db66c435e4882ab35cf70cac43",
113 | "sha256:eb6ea6da4c787111adf40f697b4e58732ee0942b5d3bd8f435277643329ba627",
114 | "sha256:ec8c433b3ab0419100bd45b47c9c8551248a5aee30ca5e9d399a0b57ac04651b",
115 | "sha256:eff9d20417ff9dcb0d25e2defc2574d10b491bf2e693b4e491914738b7908168",
116 | "sha256:f0214eb2a23b85528310dad848ad2ac58e735612929c8072f6093f3585fd342d",
117 | "sha256:f276df9830dba7a333544bd41070e8175762a7ac20350786b322b714b0e654f5",
118 | "sha256:f3acda1924472472ddd60c29e5b9db0cec629fbe3c5c5accb74d6d6d14773478",
119 | "sha256:f70a9e237bb792c7cc7e44c531fd48f5897961701cdaa06cf22fc14965c496cf",
120 | "sha256:f9d29ca8a77117315101425ec7ec2a47a22ccf59f5593378fc4077ac5b754fce",
121 | "sha256:fa877ca7f6b48054f847b61d6fa7bed5cebb663ebc55e018fda12db09dcc664c",
122 | "sha256:fdcec0b8399108577ec290f55551d926d9a1fa6cad45882093a7a07ac5ec147b"
123 | ],
124 | "markers": "python_version >= '3' and platform_machine in 'x86_64 X86_64 aarch64 AARCH64 ppc64le PPC64LE amd64 AMD64 win32 WIN32'",
125 | "version": "==1.1.2"
126 | },
127 | "h11": {
128 | "hashes": [
129 | "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6",
130 | "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"
131 | ],
132 | "markers": "python_version >= '3.6'",
133 | "version": "==0.12.0"
134 | },
135 | "httpcore": {
136 | "hashes": [
137 | "sha256:9410fe352bea732311f2b2bee0555c8cc5e62b9a73b9d3272fe125a2aa6eb28e",
138 | "sha256:d4305811f604d3c2e22869147392f134796976ff946c96a8cfba87f4e0171d83"
139 | ],
140 | "markers": "python_version >= '3.6'",
141 | "version": "==0.14.4"
142 | },
143 | "httpx": {
144 | "hashes": [
145 | "sha256:02af20df486b78892a614a7ccd4e4e86a5409ec4981ab0e422c579a887acad83",
146 | "sha256:208e5ef2ad4d105213463cfd541898ed9d11851b346473539a8425e644bb7c66"
147 | ],
148 | "index": "pypi",
149 | "version": "==0.21.1"
150 | },
151 | "idna": {
152 | "hashes": [
153 | "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff",
154 | "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"
155 | ],
156 | "markers": "python_version >= '3.5'",
157 | "version": "==3.3"
158 | },
159 | "pydantic": {
160 | "hashes": [
161 | "sha256:085ca1de245782e9b46cefcf99deecc67d418737a1fd3f6a4f511344b613a5b3",
162 | "sha256:086254884d10d3ba16da0588604ffdc5aab3f7f09557b998373e885c690dd398",
163 | "sha256:0b6037175234850ffd094ca77bf60fb54b08b5b22bc85865331dd3bda7a02fa1",
164 | "sha256:0fe476769acaa7fcddd17cadd172b156b53546ec3614a4d880e5d29ea5fbce65",
165 | "sha256:1d5278bd9f0eee04a44c712982343103bba63507480bfd2fc2790fa70cd64cf4",
166 | "sha256:2cc6a4cb8a118ffec2ca5fcb47afbacb4f16d0ab8b7350ddea5e8ef7bcc53a16",
167 | "sha256:2ee7e3209db1e468341ef41fe263eb655f67f5c5a76c924044314e139a1103a2",
168 | "sha256:3011b975c973819883842c5ab925a4e4298dffccf7782c55ec3580ed17dc464c",
169 | "sha256:3c3b035103bd4e2e4a28da9da7ef2fa47b00ee4a9cf4f1a735214c1bcd05e0f6",
170 | "sha256:4c68c3bc88dbda2a6805e9a142ce84782d3930f8fdd9655430d8576315ad97ce",
171 | "sha256:574936363cd4b9eed8acdd6b80d0143162f2eb654d96cb3a8ee91d3e64bf4cf9",
172 | "sha256:5a79330f8571faf71bf93667d3ee054609816f10a259a109a0738dac983b23c3",
173 | "sha256:5e48ef4a8b8c066c4a31409d91d7ca372a774d0212da2787c0d32f8045b1e034",
174 | "sha256:6c5b77947b9e85a54848343928b597b4f74fc364b70926b3c4441ff52620640c",
175 | "sha256:742645059757a56ecd886faf4ed2441b9c0cd406079c2b4bee51bcc3fbcd510a",
176 | "sha256:7bdfdadb5994b44bd5579cfa7c9b0e1b0e540c952d56f627eb227851cda9db77",
177 | "sha256:815ddebb2792efd4bba5488bc8fde09c29e8ca3227d27cf1c6990fc830fd292b",
178 | "sha256:8b5ac0f1c83d31b324e57a273da59197c83d1bb18171e512908fe5dc7278a1d6",
179 | "sha256:96f240bce182ca7fe045c76bcebfa0b0534a1bf402ed05914a6f1dadff91877f",
180 | "sha256:a733965f1a2b4090a5238d40d983dcd78f3ecea221c7af1497b845a9709c1721",
181 | "sha256:ab624700dc145aa809e6f3ec93fb8e7d0f99d9023b713f6a953637429b437d37",
182 | "sha256:b2571db88c636d862b35090ccf92bf24004393f85c8870a37f42d9f23d13e032",
183 | "sha256:bbbc94d0c94dd80b3340fc4f04fd4d701f4b038ebad72c39693c794fd3bc2d9d",
184 | "sha256:c0727bda6e38144d464daec31dff936a82917f431d9c39c39c60a26567eae3ed",
185 | "sha256:c556695b699f648c58373b542534308922c46a1cda06ea47bc9ca45ef5b39ae6",
186 | "sha256:c86229333cabaaa8c51cf971496f10318c4734cf7b641f08af0a6fbf17ca3054",
187 | "sha256:c8d7da6f1c1049eefb718d43d99ad73100c958a5367d30b9321b092771e96c25",
188 | "sha256:c8e9dcf1ac499679aceedac7e7ca6d8641f0193c591a2d090282aaf8e9445a46",
189 | "sha256:cb23bcc093697cdea2708baae4f9ba0e972960a835af22560f6ae4e7e47d33f5",
190 | "sha256:d1e4c28f30e767fd07f2ddc6f74f41f034d1dd6bc526cd59e63a82fe8bb9ef4c",
191 | "sha256:d9c9bdb3af48e242838f9f6e6127de9be7063aad17b32215ccc36a09c5cf1070",
192 | "sha256:dee5ef83a76ac31ab0c78c10bd7d5437bfdb6358c95b91f1ba7ff7b76f9996a1",
193 | "sha256:e0896200b6a40197405af18828da49f067c2fa1f821491bc8f5bde241ef3f7d7",
194 | "sha256:f5a64b64ddf4c99fe201ac2724daada8595ada0d102ab96d019c1555c2d6441d",
195 | "sha256:f947352c3434e8b937e3aa8f96f47bdfe6d92779e44bb3f41e4c213ba6a32145"
196 | ],
197 | "markers": "python_full_version >= '3.6.1'",
198 | "version": "==1.9.0"
199 | },
200 | "rfc3986": {
201 | "extras": [
202 | "idna2008"
203 | ],
204 | "hashes": [
205 | "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835",
206 | "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"
207 | ],
208 | "version": "==1.5.0"
209 | },
210 | "sniffio": {
211 | "hashes": [
212 | "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663",
213 | "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"
214 | ],
215 | "markers": "python_version >= '3.5'",
216 | "version": "==1.2.0"
217 | },
218 | "sqlalchemy": {
219 | "hashes": [
220 | "sha256:059c5f41e8630f51741a234e6ba2a034228c11b3b54a15478e61d8b55fa8bd9d",
221 | "sha256:07b9099a95dd2b2620498544300eda590741ac54915c6b20809b6de7e3c58090",
222 | "sha256:0aa312f9906ecebe133d7f44168c3cae4c76f27a25192fa7682f3fad505543c9",
223 | "sha256:0aa746d1173587743960ff17b89b540e313aacfe6c1e9c81aa48393182c36d4f",
224 | "sha256:1c15191f2430a30082f540ec6f331214746fc974cfdf136d7a1471d1c61d68ff",
225 | "sha256:25e9b2e5ca088879ce3740d9ccd4d58cb9061d49566a0b5e12166f403d6f4da0",
226 | "sha256:2bca9a6e30ee425cc321d988a152a5fe1be519648e7541ac45c36cd4f569421f",
227 | "sha256:355024cf061ed04271900414eb4a22671520241d2216ddb691bdd8a992172389",
228 | "sha256:370f4688ce47f0dc1e677a020a4d46252a31a2818fd67f5c256417faefc938af",
229 | "sha256:37f2bd1b8e32c5999280f846701712347fc0ee7370e016ede2283c71712e127a",
230 | "sha256:3a0d3b3d51c83a66f5b72c57e1aad061406e4c390bd42cf1fda94effe82fac81",
231 | "sha256:43fc207be06e50158e4dae4cc4f27ce80afbdbfa7c490b3b22feb64f6d9775a0",
232 | "sha256:448612570aa1437a5d1b94ada161805778fe80aba5b9a08a403e8ae4e071ded6",
233 | "sha256:4803a481d4c14ce6ad53dc35458c57821863e9a079695c27603d38355e61fb7f",
234 | "sha256:512f52a8872e8d63d898e4e158eda17e2ee40b8d2496b3b409422e71016db0bd",
235 | "sha256:6a8dbf3d46e889d864a57ee880c4ad3a928db5aa95e3d359cbe0da2f122e50c4",
236 | "sha256:76ff246881f528089bf19385131b966197bb494653990396d2ce138e2a447583",
237 | "sha256:82c03325111eab88d64e0ff48b6fe15c75d23787429fa1d84c0995872e702787",
238 | "sha256:967307ea52985985224a79342527c36ec2d1daa257a39748dd90e001a4be4d90",
239 | "sha256:9b128a78581faea7a5ee626ad4471353eee051e4e94616dfeff4742b6e5ba262",
240 | "sha256:a8395c4db3e1450eef2b68069abf500cc48af4b442a0d98b5d3c9535fe40cde8",
241 | "sha256:ae07895b55c7d58a7dd47438f437ac219c0f09d24c2e7d69fdebc1ea75350f00",
242 | "sha256:bd41f8063a9cd11b76d6d7d6af8139ab3c087f5dbbe5a50c02cb8ece7da34d67",
243 | "sha256:be185b3daf651c6c0639987a916bf41e97b60e68f860f27c9cb6574385f5cbb4",
244 | "sha256:cd0e85dd2067159848c7672acd517f0c38b7b98867a347411ea01b432003f8d9",
245 | "sha256:cd68c5f9d13ffc8f4d6802cceee786678c5b1c668c97bc07b9f4a60883f36cd1",
246 | "sha256:cec1a4c6ddf5f82191301a25504f0e675eccd86635f0d5e4c69e0661691931c5",
247 | "sha256:d9667260125688c71ccf9af321c37e9fb71c2693575af8210f763bfbbee847c7",
248 | "sha256:e0ce4a2e48fe0a9ea3a5160411a4c5135da5255ed9ac9c15f15f2bcf58c34194",
249 | "sha256:e9d4f4552aa5e0d1417fc64a2ce1cdf56a30bab346ba6b0dd5e838eb56db4d29"
250 | ],
251 | "index": "pypi",
252 | "version": "==1.4.23"
253 | },
254 | "starlette": {
255 | "hashes": [
256 | "sha256:3c8e48e52736b3161e34c9f0e8153b4f32ec5d8995a3ee1d59410d92f75162ed",
257 | "sha256:7d49f4a27f8742262ef1470608c59ddbc66baf37c148e938c7038e6bc7a998aa"
258 | ],
259 | "markers": "python_version >= '3.6'",
260 | "version": "==0.14.2"
261 | },
262 | "typing-extensions": {
263 | "hashes": [
264 | "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e",
265 | "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"
266 | ],
267 | "markers": "python_version >= '3.6'",
268 | "version": "==4.0.1"
269 | },
270 | "uvicorn": {
271 | "hashes": [
272 | "sha256:17f898c64c71a2640514d4089da2689e5db1ce5d4086c2d53699bf99513421c1",
273 | "sha256:d9a3c0dd1ca86728d3e235182683b4cf94cd53a867c288eaeca80ee781b2caff"
274 | ],
275 | "index": "pypi",
276 | "version": "==0.15.0"
277 | }
278 | },
279 | "develop": {}
280 | }
281 |
--------------------------------------------------------------------------------