├── .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 | 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 | 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 | 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 | ![alt text](FastAPI.png) 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 | ![alt text](sample-FastAPI.png) 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 | --------------------------------------------------------------------------------