├── pytest.ini ├── screenshots ├── fastapi.png ├── mock-result.PNG ├── swagger-ui.PNG └── test-result.PNG ├── .gitignore ├── requirements.txt ├── tests ├── test_base_routes.py ├── test_regions.py ├── utils.py └── test_dates.py ├── api ├── services │ ├── regions.py │ ├── mock.py │ └── dates.py ├── schemas.py ├── database │ └── models.py ├── __init__.py ├── utils.py └── settings.py ├── pyproject.toml ├── LICENSE ├── README.md └── poetry.lock /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | asyncio_mode=auto -------------------------------------------------------------------------------- /screenshots/fastapi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AbduazizZiyodov/ramazon-api/HEAD/screenshots/fastapi.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | env/ 3 | venv/ 4 | __pycache__/ 5 | .env 6 | *.sqlite* 7 | .pytest_cache/ 8 | utils/ 9 | *.json -------------------------------------------------------------------------------- /screenshots/mock-result.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AbduazizZiyodov/ramazon-api/HEAD/screenshots/mock-result.PNG -------------------------------------------------------------------------------- /screenshots/swagger-ui.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AbduazizZiyodov/ramazon-api/HEAD/screenshots/swagger-ui.PNG -------------------------------------------------------------------------------- /screenshots/test-result.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AbduazizZiyodov/ramazon-api/HEAD/screenshots/test-result.PNG -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiosqlite==0.17.0 2 | anyio==3.4.0 3 | asgiref==3.4.1 4 | autopep8==1.6.0 5 | click==8.0.3 6 | colorama==0.4.4 7 | fastapi==0.70.0 8 | h11==0.12.0 9 | idna==3.3 10 | iso8601==0.1.16 11 | pycodestyle==2.8.0 12 | pydantic==1.8.2 13 | pypika-tortoise==0.1.1 14 | pytz==2021.3 15 | sniffio==1.2.0 16 | starlette==0.16.0 17 | toml==0.10.2 18 | tortoise-orm==0.17.8 19 | typing_extensions==4.0.1 20 | uvicorn==0.15.0 21 | rich==10.15.2 22 | sentry-sdk==1.14.0 23 | python-dotenv==0.20.0 -------------------------------------------------------------------------------- /tests/test_base_routes.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from utils import * 3 | 4 | 5 | client = CustomAsyncTestClient() 6 | 7 | 8 | @pytest.mark.anyio 9 | async def test_base_route(): 10 | response = await client.send_request(GET, "") 11 | 12 | assert response.status_code == 200 13 | assert response.text is not None 14 | 15 | 16 | @pytest.mark.anyio 17 | async def test_swagger_ui(): 18 | response = await client.send_request(GET, "swagger") 19 | 20 | assert response.status_code == 200 21 | assert response.text is not None 22 | -------------------------------------------------------------------------------- /api/services/regions.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | from fastapi import APIRouter 3 | 4 | import api.schemas as schemas 5 | from api.database.models import * 6 | 7 | 8 | router = APIRouter( 9 | tags=["Regions"], 10 | prefix='/api' 11 | ) 12 | 13 | 14 | @router.get('/regions', response_model=List[schemas.Region]) 15 | async def regions(): 16 | return await Region.all() 17 | 18 | 19 | @router.get('/regions/{region_id}', response_model=schemas.Region) 20 | async def region_detailed(region_id: int): 21 | return await Region.get(pk=region_id) 22 | -------------------------------------------------------------------------------- /api/schemas.py: -------------------------------------------------------------------------------- 1 | from typing import Dict 2 | from typing import List 3 | 4 | from pydantic import BaseModel 5 | 6 | from datetime import date 7 | from datetime import datetime 8 | 9 | 10 | class Region(BaseModel): 11 | id: int 12 | name: str 13 | 14 | 15 | class Date(BaseModel): 16 | day: date 17 | day_full: str 18 | day_of_ramadan: int 19 | 20 | fajr: datetime 21 | iftar: datetime 22 | 23 | 24 | class Dates(BaseModel): 25 | __root__: Dict[str, List[Date]] 26 | 27 | 28 | class TodayDates(BaseModel): 29 | __root__: Dict[str, Date] 30 | 31 | 32 | class AdditionalInfo(BaseModel): 33 | day: int 34 | month: str 35 | weekday: str 36 | day_of_ramadan: int 37 | 38 | 39 | class Today(BaseModel): 40 | date: date 41 | additional_info: AdditionalInfo 42 | -------------------------------------------------------------------------------- /api/database/models.py: -------------------------------------------------------------------------------- 1 | from tortoise import fields 2 | from tortoise.models import Model 3 | 4 | 5 | class Region(Model): 6 | id = fields.IntField(pk=True) 7 | name = fields.CharField(60, unique=True) 8 | 9 | dates: fields.ManyToManyRelation["Date"] = \ 10 | fields.ManyToManyField( 11 | "models.Date", related_name="regions", 12 | ) 13 | 14 | def __str__(self): 15 | return self.name 16 | 17 | 18 | class Date(Model): 19 | id = fields.IntField(pk=True) 20 | 21 | day = fields.DateField() 22 | day_full = fields.CharField(max_length=50) 23 | day_of_ramadan = fields.IntField() 24 | 25 | fajr = fields.DatetimeField() 26 | iftar = fields.DatetimeField() 27 | 28 | def __str__(self): 29 | return str(self.day) 30 | 31 | 32 | __all__ = ["Region", "Date", ] 33 | -------------------------------------------------------------------------------- /api/__init__.py: -------------------------------------------------------------------------------- 1 | import sentry_sdk 2 | from os import getenv 3 | from fastapi import FastAPI 4 | from dotenv import load_dotenv 5 | 6 | from api import settings 7 | from api.utils import setUp 8 | from api.services.dates import router as dates_router 9 | from api.services.regions import router as regions_router 10 | from api.services.mock import mock_router 11 | 12 | api = FastAPI(**settings.FASTAPI_SETTINGS) 13 | 14 | routers = [dates_router, regions_router] 15 | 16 | if api.debug: 17 | routers.append(mock_router) 18 | 19 | @api.on_event("startup") 20 | async def startup_event(): 21 | load_dotenv(), setUp(api, routers) 22 | sentry_sdk.init( 23 | getenv("SENTRY_DSN"), 24 | traces_sample_rate=1.0 25 | ) 26 | 27 | 28 | @api.get("/", tags=["Base Route"]) 29 | async def base_route(): 30 | return {"message": "Ramadan API is working 🔥"} 31 | -------------------------------------------------------------------------------- /api/services/mock.py: -------------------------------------------------------------------------------- 1 | from rich import print 2 | from fastapi import APIRouter 3 | 4 | from api.utils import generate_days 5 | from api.database.models import Date 6 | from api.database.models import Region 7 | from api.settings import regions_names 8 | 9 | mock_router = APIRouter(tags=["Mock"], prefix="/api") 10 | 11 | 12 | @mock_router.get("/mock") 13 | async def mock_database(): 14 | days = generate_days() 15 | 16 | regions = [ 17 | await Region.create(name=region) 18 | for region in regions_names 19 | ] 20 | 21 | for region in regions: 22 | await region.dates.add( 23 | *[ 24 | await Date.create(day=day) 25 | for day in days.values() 26 | ] 27 | ) 28 | dates = await region.dates 29 | print( 30 | f"[bold green]:fire: Region: [bold red]{region.name} {len(dates)}") 31 | 32 | print( 33 | f"[bold cyan] Success! Total regions: {len(regions)} :tada: ") 34 | 35 | return {"success": True} 36 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "ramazon-api" 3 | version = "2.0.0" 4 | description = "API for Ramadan! Written in FastAPI" 5 | authors = ["Abduaziz "] 6 | license = "MIT" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.8" 10 | fastapi = "0.70.0" 11 | aiosqlite = "0.17.0" 12 | anyio = "3.4.0" 13 | asgiref = "3.4.1" 14 | autopep8 = "1.6.0" 15 | click = "8.0.3" 16 | colorama = "0.4.4" 17 | h11 = "0.12.0" 18 | idna = "3.3" 19 | iso8601 = "0.1.16" 20 | pycodestyle = "2.8.0" 21 | pydantic = "1.8.2" 22 | pypika-tortoise = "0.1.1" 23 | pytz = "2021.3" 24 | sniffio = "1.2.0" 25 | starlette = "0.16.0" 26 | toml = "0.10.2" 27 | tortoise-orm = "0.17.8" 28 | typing-extensions = "4.0.1" 29 | uvicorn = "0.15.0" 30 | rich = "10.15.2" 31 | httpx = "^0.23.0" 32 | pytest = "^7.1.0" 33 | trio = "^0.20.0" 34 | pytest-asyncio = "^0.18.2" 35 | sentry-sdk = "^1.14.0" 36 | python-dotenv = "^0.20.0" 37 | 38 | [tool.poetry.dev-dependencies] 39 | 40 | [build-system] 41 | requires = ["poetry-core>=1.0.0"] 42 | build-backend = "poetry.core.masonry.api" 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Abduaziz 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. 22 | -------------------------------------------------------------------------------- /tests/test_regions.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import pytest_asyncio 3 | 4 | import typing as t 5 | from json import loads 6 | 7 | from utils import * 8 | 9 | client = CustomAsyncTestClient() 10 | 11 | 12 | @pytest_asyncio.fixture 13 | async def random_region_id(): 14 | response = await client.send_request(GET, "api/regions") 15 | pytest.RANDOM_REGION_ID = random_index(loads(response.text)) 16 | 17 | 18 | @pytest.mark.asyncio 19 | async def test_list_regions(): 20 | response = await client.send_request(GET, "api/regions") 21 | body: t.Optional[t.List[dict]] = loads(response.text) 22 | 23 | assert response.status_code == 200 24 | assert "id" in body[random_index(body)].keys() 25 | 26 | 27 | @pytest.mark.asyncio 28 | async def test_region_detailed(random_region_id): 29 | response = await client.send_request( 30 | GET, 31 | f"api/regions/{pytest.RANDOM_REGION_ID}" 32 | ) 33 | 34 | body: t.Optional[dict] = loads(response.text) 35 | 36 | assert response.status_code == 200 37 | 38 | required_keys = ["id", "name"] 39 | 40 | assert all([key in required_keys for key in body.keys()]) is True 41 | -------------------------------------------------------------------------------- /tests/utils.py: -------------------------------------------------------------------------------- 1 | import pytz 2 | import httpx 3 | import typing as t 4 | from random import randint 5 | 6 | from datetime import ( 7 | date, 8 | datetime 9 | ) 10 | 11 | 12 | GET, POST, PUT, PATCH, DELETE = \ 13 | "GET", "POST", "PUT", "PATCH", "DELETE" 14 | 15 | PRODUCTION: bool = False 16 | 17 | 18 | class CustomAsyncTestClient: 19 | API_URL: str = "http://127.0.0.1:8000" if not PRODUCTION else "" 20 | 21 | async def send_request( 22 | self, 23 | method: str, 24 | endpoint: str, 25 | data: t.Optional[dict] = {}, 26 | headers: t.Optional[httpx.Headers] = httpx.Headers({}) 27 | ) -> httpx.Response: 28 | 29 | url: str = f"{self.API_URL}/{endpoint}" 30 | 31 | async with httpx.AsyncClient() as client: 32 | response: httpx.Response = await client\ 33 | .request(method, url, data=data, headers=headers) 34 | 35 | return response 36 | 37 | 38 | def random_index(obj): return randint(1, len(obj)) # obj should be iterable 39 | 40 | 41 | def get_current_time() -> date: 42 | """ 43 | Returns current DateTime according to timezone 44 | """ 45 | if PRODUCTION: 46 | return datetime.now(pytz.timezone("Asia/Tashkent")).date() 47 | return date(2022, 4, 24) 48 | 49 | 50 | __all__ = ["CustomAsyncTestClient", "random_index", 51 | "get_current_time", "GET", "POST", "PUT", "PATCH", "DELETE"] 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # **🕌 Ramazon-API** 2 | 3 | ![FASTAPI_LOGO](/screenshots/fastapi.png) 4 | 5 |

6 | Assalamu alaikum 👋
7 | Ramadan Mubarak. Wishing you a blessed and Happy Ramadan 😀 8 |

9 | 10 | ## **🧰 Setup** 11 | 12 | > **Required:** python +3.6 13 | 14 | Clone this repository: 15 | 16 | ```bash 17 | # git 18 | $ git clone https://github.com/AbduazizZiyodov/ramazon-api.git 19 | ``` 20 | 21 | ```bash 22 | # github-cli: 23 | $ gh repo clone AbduazizZiyodov/ramazon-api 24 | ``` 25 | 26 | Go to the project directory. Create virtual enviroment and activate it: 27 | 28 | ```bash 29 | $ cd ramazon-api/ 30 | $ python -m venv env && source env/bin/activate 31 | 32 | # env/Scripts/activate - windows 33 | ``` 34 | 35 | Install all required packages using `pip` from `requirements.txt` file. 36 | 37 | ```bash 38 | $ pip install -r requirements.txt 39 | ``` 40 | 41 | > Or, you can use `poetry` (if you have). 42 | 43 | ``` 44 | $ poetry install 45 | ``` 46 | 47 | # **🚀Running Server** 48 | 49 | To run fastapi application you will have to use `uvicorn` or `gunicorn`. 50 | 51 | ### **Uvicorn** 🦄 52 | 53 | > `--reload` - reloading for development server. 54 | 55 | ```bash 56 | $ uvicorn main:api --reload 57 | ``` 58 | 59 | ### **Gunicorn** (🟢)🦄 60 | 61 | ```bash 62 | $ gunicorn main:api --worker-class uvicorn.workers.UvicornWorker --reload 63 | ``` 64 | 65 | ![SWAGGER_UI](/screenshots/swagger-ui.PNG) 66 | 67 | - `http://{{ host }}/swagger` - _Swagger UI_ 68 | 69 |

70 | 71 | ## **✨Mocking Database** 72 | 73 | After running server, you should send `GET` request to `/simulate` endpoint from anywhere(swagger UI, curl, postman ...). The results of mocking will be logged on your terminal. 74 | 75 | ![MOCK_RESULT](/screenshots/mock-result.PNG) 76 | 77 | ## **🧪 Running Tests** 78 | 79 | Running with `poetry`: 80 | 81 | ```bash 82 | $ poetry run pytest 83 | ``` 84 | 85 | or 86 | 87 | ```bash 88 | $ pytest 89 | ``` 90 | 91 | ![TEST](/screenshots/test-result.PNG) 92 | 93 |

94 | 🐍 Abduaziz Ziyodov 95 |

96 | -------------------------------------------------------------------------------- /api/services/dates.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | from datetime import date 3 | 4 | from fastapi import APIRouter 5 | from fastapi.exceptions import HTTPException 6 | 7 | import api.schemas as schemas 8 | from api.database.models import * 9 | 10 | from api.utils import generate_days 11 | from api.utils import get_current_time 12 | 13 | router = APIRouter( 14 | tags=["Dates"], 15 | prefix="/api" 16 | ) 17 | 18 | days = generate_days() 19 | 20 | 21 | @router.get("/today",response_model=schemas.Today) 22 | async def get_today(): 23 | return get_current_time(full_format=True) 24 | 25 | 26 | @router.get( 27 | "/dates", 28 | response_model=List[schemas.Dates] 29 | ) 30 | async def get_all_dates(): 31 | return [ 32 | {region.name: await region.dates} 33 | for region in await Region.all() 34 | ] 35 | 36 | 37 | @router.get( 38 | "/regions/{region_id}/dates", 39 | response_model=List[schemas.Date] 40 | ) 41 | async def get_dates_by_region(region_id: int): 42 | region: Region = await Region.get(pk=region_id) 43 | 44 | return await region.dates 45 | 46 | 47 | @router.get( 48 | "/dates/today", 49 | response_model=List[schemas.TodayDates] 50 | ) 51 | async def get_dates_today(): 52 | current_day: date = get_current_time() 53 | 54 | return [ 55 | { 56 | region.name: await region.dates.filter(day=current_day).first() 57 | } 58 | for region in await Region.all() 59 | ] 60 | 61 | 62 | @router.get( 63 | "/regions/{region_id}/dates/today", 64 | response_model=schemas.Date 65 | ) 66 | async def get_today_dates_by_region(region_id: int): 67 | current_day: date = get_current_time() 68 | region: Region = await Region.get(pk=region_id) 69 | 70 | return await region.dates.filter(day=current_day).first() 71 | 72 | 73 | @router.get( 74 | "/regions/{region_id}/day/{day}", 75 | response_model=schemas.Date 76 | ) 77 | async def get_specific_date(region_id: int, day: int): 78 | day_of_ramadan: int = days.get(day) 79 | 80 | region: Region = await Region.get(pk=region_id) 81 | _date: Date = await region.dates.filter(day=day_of_ramadan).first() 82 | 83 | if _date is None: 84 | raise HTTPException(404) 85 | 86 | return _date 87 | -------------------------------------------------------------------------------- /api/utils.py: -------------------------------------------------------------------------------- 1 | import pytz 2 | from rich import print 3 | from fastapi import FastAPI 4 | from datetime import date 5 | from datetime import datetime 6 | from datetime import timedelta 7 | from fastapi.middleware.cors import CORSMiddleware 8 | from tortoise.contrib.fastapi import register_tortoise 9 | 10 | from api import settings 11 | from api.database.models import * 12 | 13 | 14 | def generate_days() -> dict: 15 | """ 16 | Generates days for Ramadan. 17 | e.g First day of ramadan = 01.12.2099. 18 | 19 | >>> days = {...} 20 | >>> day = 1 # it means first day of ramadan 21 | >>> days[1] 22 | datetime(...) # it should return date for this day 23 | """ 24 | days: dict = {1: settings.START_OF_RAMADAN, } 25 | 26 | for day in range(2, settings.MONTH+1): 27 | days[day] = days[day-1] + timedelta(1) 28 | 29 | return days 30 | 31 | 32 | def get_current_time(full_format: bool = False) -> date: 33 | """ 34 | Returns current Datetime according to timezone 35 | """ 36 | def format_month_weekday(date: date): 37 | 38 | day_of_ramadan = [ 39 | key 40 | for key, value in generate_days().items() 41 | if value == date 42 | ][0] 43 | return { 44 | "date": date, 45 | "additional_info": { 46 | "month": settings.MONTHS.get(date.month), 47 | "day": date.day, 48 | "weekday": settings.DAYS.get(date.weekday()), 49 | "day_of_ramadan": day_of_ramadan, 50 | } 51 | } 52 | 53 | if settings.FASTAPI_SETTINGS["debug"]: 54 | now = date(2022, 4, 24) 55 | return format_month_weekday(now) if full_format else now 56 | 57 | else: 58 | now = datetime.now(pytz.timezone(settings.TIMEZONE)).date() 59 | 60 | return format_month_weekday(now) if full_format else now 61 | 62 | 63 | def setUp(app: FastAPI, routers: list): 64 | app.add_middleware( 65 | CORSMiddleware, 66 | allow_credentials=True, 67 | allow_origins=["*"], 68 | allow_methods=["*"], 69 | allow_headers=["*"], 70 | ) 71 | 72 | for router in routers: 73 | app.include_router(router) 74 | 75 | register_tortoise(app, **settings.DATABASE_SETTINGS) 76 | 77 | 78 | def print_region(region, dates): 79 | print( 80 | f"[bold green]:fire: Region: [bold red]{region.name} {len(dates)}") 81 | 82 | 83 | def print_total(regions): 84 | print( 85 | f"[bold cyan] Success! Total regions: {len(regions)} :tada: ") 86 | 87 | 88 | def to_datetime(_date: date, data: dict) -> datetime: 89 | def helper(time: str): 90 | return datetime( 91 | year=_date.year, 92 | month=_date.month, 93 | day=_date.day, 94 | hour=int(time[:2]), 95 | minute=int(time[3:]) 96 | ) 97 | 98 | return dict( 99 | fajr=helper(data["saharlik"]), 100 | iftar=helper(data["iftorlik"]) 101 | ) 102 | -------------------------------------------------------------------------------- /api/settings.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | import pytz 3 | from datetime import ( 4 | date, 5 | datetime, 6 | timedelta 7 | ) 8 | 9 | TITLE: str = "RamadanAPI" 10 | 11 | DESCRIPTION: str = """ 12 | **API** for Ramadan Calendar(2021). 13 |
Assalamu alaikum👋 Ramadan Mubarak. 14 | Wishing you a blessed and Happy Ramadan! 15 |
Data about times in this month is extremely important. 16 |
That's why I transferred this information to the **API** interface. 17 | This **API** will benefit everyone! 18 |
**Author: Abduaziz Ziyodov** 19 | """ 20 | 21 | 22 | DATABASE_SETTINGS = { 23 | "db_url": "sqlite://api/database/database.sqlite", 24 | "modules": { 25 | 'models': ['api.database.models'] 26 | }, 27 | "generate_schemas": True, 28 | "add_exception_handlers": True, 29 | } 30 | 31 | FASTAPI_SETTINGS = { 32 | "title": TITLE, 33 | "openapi_tags": [ 34 | { 35 | "name": "Regions", 36 | "description": "Operations with regions" 37 | }, 38 | { 39 | "name": "Dates", 40 | "description": "Operations with dates" 41 | } 42 | ], 43 | "version": "3.0.0", 44 | "description": DESCRIPTION, 45 | "redoc_url": None, 46 | "docs_url": '/swagger', 47 | "debug": True 48 | } 49 | 50 | TIMEZONE = "Asia/Tashkent" 51 | 52 | regions_names: List[str] = [ 53 | # Toshkent 54 | 'Toshkent', 'Angren', 'Piskent', 'Bekobod', 'Parkent', "G'azalkent", 'Olmaliq', 55 | "Boka", "Yangiyo'l", 'Nurafshon', 56 | # Buxoro 57 | 'Buxoro', 'Gazli', "G'ijduvon", "Qorako'l", 'Jondor', 58 | # Fargona 59 | "Farg'ona", "Marg'ilon", "Qo'qon", "Quva", 'Rishton', "Bog'dod", "Oltiariq", 60 | # Sirdaryo 61 | 'Guliston', 'Sardoba', 'Sirdaryo', 'Boyovut', 'Paxtaobod', 62 | # Jizzax 63 | 'Jizzax', 'Zomin', 'Forish', "G'allaorol", "Do'stlik", 64 | # Navoiy 65 | "Navoiy", 'Zarafshon', 'Konimex', 'Nurota', 'Uchquduq', 66 | # Namangan 67 | 'Namangan', 'Chortoq', 'Chust', 'Pop', "Uchqo'rg'on", 68 | # Qoraqalpogiston 69 | 'Nukus', "Mo'ynoq", "Taxtako'pir", "To'rtkol", "Qo'ng'irot", 70 | # Samarqand 71 | 'Samarqand', 'Ishtixon', 'Mirbozor', "Kattaqo'rg'on", 'Urgut', 72 | # Surxondaryo 73 | 'Termiz', 'Boysun', 'Denov', 'Sherobod', "Sho'rchi", 74 | # Qashqadaryo 75 | 'Qarshi', 'Dehqonobod', 'Muborak', 'Shahrisabz', "G'uzor", 76 | # Andijon 77 | 'Andijon', 'Xonobod', 'Shahrixon', "Xo'jaobod", 'Asaka', 'Marhamat', "Paytug'", 78 | # Xorazm 79 | 'Urganch', 'Hazorasp', 'Xonqa', 'Yangibozor', 'Shovot', 'Xiva' 80 | ] 81 | 82 | DAYS = { 83 | 0: "Dushanba", 84 | 1: "Seshanba", 85 | 2: "Chorshanba", 86 | 3: "Payshanba", 87 | 4: "Juma", 88 | 5: "Shanba", 89 | 6: "Yakshanba", 90 | } 91 | 92 | MONTHS = { 93 | 3: "Mart", 94 | 4: "Aprel", 95 | 5: "May", 96 | } 97 | 98 | 99 | 100 | MONTH: int = 30 # days 101 | 102 | START_OF_RAMADAN: date = date(2022, 4, 2) 103 | END_OF_RAMADAN: date = START_OF_RAMADAN + timedelta(MONTH) 104 | -------------------------------------------------------------------------------- /tests/test_dates.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import pytest_asyncio 3 | 4 | import typing as t 5 | from json import loads 6 | 7 | from utils import * 8 | 9 | client = CustomAsyncTestClient() 10 | 11 | REQUIRED_FIELDS = ["day", "fajr", "iftar", "day_of_ramadan", "day_full"] 12 | 13 | 14 | @pytest_asyncio.fixture 15 | async def random_region_id(): 16 | response = await client.send_request(GET, "api/regions") 17 | pytest.RANDOM_REGION_ID = random_index(loads(response.text)) 18 | 19 | 20 | @pytest_asyncio.fixture 21 | async def regions_count(): 22 | response = await client.send_request(GET, "api/regions") 23 | pytest.REGIONS_COUNT = len(loads(response.text)) 24 | 25 | 26 | @pytest.mark.asyncio 27 | async def test_dates(): 28 | response = await client.send_request(GET, "api/dates") 29 | body: t.List[dict] = loads(response.text) 30 | dates_of_region: dict = body[random_index(body)-1] 31 | dates_of_region: t.List[dict] = [ 32 | key 33 | for key in dates_of_region.values() 34 | ][0] 35 | 36 | assert response.status_code == 200 37 | assert all( 38 | [ 39 | key in REQUIRED_FIELDS 40 | for data in dates_of_region 41 | for key in data.keys() 42 | ] 43 | ) is True 44 | 45 | 46 | @pytest.mark.asyncio 47 | async def test_region_dates(random_region_id): 48 | response = await client.send_request(GET, f"api/regions/{pytest.RANDOM_REGION_ID}/dates") 49 | body: t.List[dict] = loads(response.text) 50 | 51 | assert response.status_code == 200 52 | assert all( 53 | [ 54 | key in REQUIRED_FIELDS 55 | for data in body 56 | for key in data.keys() 57 | ] 58 | ) is True 59 | 60 | 61 | @pytest.mark.asyncio 62 | async def test_today_dates(regions_count): 63 | response = await client.send_request(GET, "api/dates/today") 64 | body: t.List[dict] = loads(response.text) 65 | 66 | assert response.status_code == 200 67 | assert len(body) == pytest.REGIONS_COUNT 68 | 69 | random_region_date: dict = body[random_index(body)-1] 70 | date = [data for data in random_region_date.values()][0] 71 | 72 | assert all( 73 | [ 74 | key in REQUIRED_FIELDS 75 | for key in date.keys() 76 | ] 77 | ) 78 | assert date["day"] == str(get_current_time()) 79 | 80 | 81 | @pytest.mark.asyncio 82 | async def test_today_dates_for_spec_region(random_region_id): 83 | response = await client.send_request( 84 | GET, 85 | f"api/regions/{pytest.RANDOM_REGION_ID}/dates/today" 86 | ) 87 | 88 | body: t.List[dict] = loads(response.text) 89 | 90 | assert response.status_code == 200 91 | 92 | assert all( 93 | [ 94 | key in REQUIRED_FIELDS 95 | for key in body.keys() 96 | ] 97 | ) 98 | assert body["day"] == str(get_current_time()) 99 | 100 | DAYS_IN_MONTH: int = 30 101 | 102 | 103 | @pytest.mark.asyncio 104 | async def test_spec_day_dates_for_spec_regions(regions_count): 105 | for region_id in range(1, pytest.REGIONS_COUNT+1): 106 | for day in range(1, DAYS_IN_MONTH+1): 107 | response = await client.send_request( 108 | GET, 109 | f"api/regions/{region_id}/day/{day}" 110 | ) 111 | 112 | body: t.List[dict] = loads(response.text) 113 | 114 | assert response.status_code == 200 115 | assert all( 116 | [ 117 | key in REQUIRED_FIELDS 118 | for key in body.keys() 119 | ] 120 | ) 121 | 122 | 123 | @pytest.mark.asyncio 124 | async def test_today_info(): 125 | response = await client.send_request( 126 | GET, 127 | "api/today" 128 | ) 129 | 130 | body: t.List[dict] = loads(response.text) 131 | 132 | assert all([ 133 | field in ["month", "weekday", "day", "day_of_ramadan"] 134 | for field in body["additional_info"].keys() 135 | ] + ["date" in body.keys()]) 136 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "aiosqlite" 5 | version = "0.17.0" 6 | description = "asyncio bridge to the standard sqlite3 module" 7 | optional = false 8 | python-versions = ">=3.6" 9 | files = [ 10 | {file = "aiosqlite-0.17.0-py3-none-any.whl", hash = "sha256:6c49dc6d3405929b1d08eeccc72306d3677503cc5e5e43771efc1e00232e8231"}, 11 | {file = "aiosqlite-0.17.0.tar.gz", hash = "sha256:f0e6acc24bc4864149267ac82fb46dfb3be4455f99fe21df82609cc6e6baee51"}, 12 | ] 13 | 14 | [package.dependencies] 15 | typing_extensions = ">=3.7.2" 16 | 17 | [[package]] 18 | name = "anyio" 19 | version = "3.4.0" 20 | description = "High level compatibility layer for multiple asynchronous event loop implementations" 21 | optional = false 22 | python-versions = ">=3.6.2" 23 | files = [ 24 | {file = "anyio-3.4.0-py3-none-any.whl", hash = "sha256:2855a9423524abcdd652d942f8932fda1735210f77a6b392eafd9ff34d3fe020"}, 25 | {file = "anyio-3.4.0.tar.gz", hash = "sha256:24adc69309fb5779bc1e06158e143e0b6d2c56b302a3ac3de3083c705a6ed39d"}, 26 | ] 27 | 28 | [package.dependencies] 29 | idna = ">=2.8" 30 | sniffio = ">=1.1" 31 | 32 | [package.extras] 33 | doc = ["sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] 34 | test = ["contextlib2", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "pytest (>=6.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "uvloop (>=0.15)"] 35 | trio = ["trio (>=0.16)"] 36 | 37 | [[package]] 38 | name = "asgiref" 39 | version = "3.4.1" 40 | description = "ASGI specs, helper code, and adapters" 41 | optional = false 42 | python-versions = ">=3.6" 43 | files = [ 44 | {file = "asgiref-3.4.1-py3-none-any.whl", hash = "sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214"}, 45 | {file = "asgiref-3.4.1.tar.gz", hash = "sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9"}, 46 | ] 47 | 48 | [package.extras] 49 | tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] 50 | 51 | [[package]] 52 | name = "async-generator" 53 | version = "1.10" 54 | description = "Async generators and context managers for Python 3.5+" 55 | optional = false 56 | python-versions = ">=3.5" 57 | files = [ 58 | {file = "async_generator-1.10-py3-none-any.whl", hash = "sha256:01c7bf666359b4967d2cda0000cc2e4af16a0ae098cbffcb8472fb9e8ad6585b"}, 59 | {file = "async_generator-1.10.tar.gz", hash = "sha256:6ebb3d106c12920aaae42ccb6f787ef5eefdcdd166ea3d628fa8476abe712144"}, 60 | ] 61 | 62 | [[package]] 63 | name = "atomicwrites" 64 | version = "1.4.0" 65 | description = "Atomic file writes." 66 | optional = false 67 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 68 | files = [ 69 | {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, 70 | {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, 71 | ] 72 | 73 | [[package]] 74 | name = "attrs" 75 | version = "21.4.0" 76 | description = "Classes Without Boilerplate" 77 | optional = false 78 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 79 | files = [ 80 | {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, 81 | {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, 82 | ] 83 | 84 | [package.extras] 85 | dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six", "sphinx", "sphinx-notfound-page", "zope.interface"] 86 | docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] 87 | tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six", "zope.interface"] 88 | tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six"] 89 | 90 | [[package]] 91 | name = "autopep8" 92 | version = "1.6.0" 93 | description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" 94 | optional = false 95 | python-versions = "*" 96 | files = [ 97 | {file = "autopep8-1.6.0-py2.py3-none-any.whl", hash = "sha256:ed77137193bbac52d029a52c59bec1b0629b5a186c495f1eb21b126ac466083f"}, 98 | {file = "autopep8-1.6.0.tar.gz", hash = "sha256:44f0932855039d2c15c4510d6df665e4730f2b8582704fa48f9c55bd3e17d979"}, 99 | ] 100 | 101 | [package.dependencies] 102 | pycodestyle = ">=2.8.0" 103 | toml = "*" 104 | 105 | [[package]] 106 | name = "certifi" 107 | version = "2023.7.22" 108 | description = "Python package for providing Mozilla's CA Bundle." 109 | optional = false 110 | python-versions = ">=3.6" 111 | files = [ 112 | {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, 113 | {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, 114 | ] 115 | 116 | [[package]] 117 | name = "cffi" 118 | version = "1.15.0" 119 | description = "Foreign Function Interface for Python calling C code." 120 | optional = false 121 | python-versions = "*" 122 | files = [ 123 | {file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"}, 124 | {file = "cffi-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0"}, 125 | {file = "cffi-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14"}, 126 | {file = "cffi-1.15.0-cp27-cp27m-win32.whl", hash = "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474"}, 127 | {file = "cffi-1.15.0-cp27-cp27m-win_amd64.whl", hash = "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6"}, 128 | {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27"}, 129 | {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023"}, 130 | {file = "cffi-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2"}, 131 | {file = "cffi-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e"}, 132 | {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7"}, 133 | {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3"}, 134 | {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c"}, 135 | {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962"}, 136 | {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382"}, 137 | {file = "cffi-1.15.0-cp310-cp310-win32.whl", hash = "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55"}, 138 | {file = "cffi-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0"}, 139 | {file = "cffi-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e"}, 140 | {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39"}, 141 | {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc"}, 142 | {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032"}, 143 | {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8"}, 144 | {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605"}, 145 | {file = "cffi-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e"}, 146 | {file = "cffi-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc"}, 147 | {file = "cffi-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636"}, 148 | {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4"}, 149 | {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997"}, 150 | {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b"}, 151 | {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2"}, 152 | {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7"}, 153 | {file = "cffi-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66"}, 154 | {file = "cffi-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029"}, 155 | {file = "cffi-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880"}, 156 | {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20"}, 157 | {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024"}, 158 | {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e"}, 159 | {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728"}, 160 | {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6"}, 161 | {file = "cffi-1.15.0-cp38-cp38-win32.whl", hash = "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c"}, 162 | {file = "cffi-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443"}, 163 | {file = "cffi-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a"}, 164 | {file = "cffi-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37"}, 165 | {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a"}, 166 | {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e"}, 167 | {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796"}, 168 | {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df"}, 169 | {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8"}, 170 | {file = "cffi-1.15.0-cp39-cp39-win32.whl", hash = "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a"}, 171 | {file = "cffi-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139"}, 172 | {file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"}, 173 | ] 174 | 175 | [package.dependencies] 176 | pycparser = "*" 177 | 178 | [[package]] 179 | name = "click" 180 | version = "8.0.3" 181 | description = "Composable command line interface toolkit" 182 | optional = false 183 | python-versions = ">=3.6" 184 | files = [ 185 | {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, 186 | {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"}, 187 | ] 188 | 189 | [package.dependencies] 190 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 191 | 192 | [[package]] 193 | name = "colorama" 194 | version = "0.4.4" 195 | description = "Cross-platform colored terminal text." 196 | optional = false 197 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 198 | files = [ 199 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, 200 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, 201 | ] 202 | 203 | [[package]] 204 | name = "commonmark" 205 | version = "0.9.1" 206 | description = "Python parser for the CommonMark Markdown spec" 207 | optional = false 208 | python-versions = "*" 209 | files = [ 210 | {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"}, 211 | {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, 212 | ] 213 | 214 | [package.extras] 215 | test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] 216 | 217 | [[package]] 218 | name = "fastapi" 219 | version = "0.70.0" 220 | description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" 221 | optional = false 222 | python-versions = ">=3.6.1" 223 | files = [ 224 | {file = "fastapi-0.70.0-py3-none-any.whl", hash = "sha256:a36d5f2fad931aa3575c07a3472c784e81f3e664e3bb5c8b9c88d0ec1104f59c"}, 225 | {file = "fastapi-0.70.0.tar.gz", hash = "sha256:66da43cfe5185ea1df99552acffd201f1832c6b364e0f4136c0a99f933466ced"}, 226 | ] 227 | 228 | [package.dependencies] 229 | pydantic = ">=1.6.2,<1.7 || >1.7,<1.7.1 || >1.7.1,<1.7.2 || >1.7.2,<1.7.3 || >1.7.3,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0" 230 | starlette = "0.16.0" 231 | 232 | [package.extras] 233 | all = ["email_validator (>=1.1.1,<2.0.0)", "itsdangerous (>=1.1.0,<3.0.0)", "jinja2 (>=2.11.2,<4.0.0)", "orjson (>=3.2.1,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "pyyaml (>=5.3.1,<6.0.0)", "requests (>=2.24.0,<3.0.0)", "ujson (>=4.0.1,<5.0.0)", "uvicorn[standard] (>=0.12.0,<0.16.0)"] 234 | dev = ["autoflake (>=1.4.0,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "python-jose[cryptography] (>=3.3.0,<4.0.0)", "uvicorn[standard] (>=0.12.0,<0.16.0)"] 235 | doc = ["mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "mkdocs-material (>=7.1.9,<8.0.0)", "pyyaml (>=5.3.1,<6.0.0)", "typer-cli (>=0.0.12,<0.0.13)"] 236 | test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==21.9b0)", "databases[sqlite] (>=0.3.2,<0.6.0)", "email_validator (>=1.1.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "flask (>=1.1.2,<3.0.0)", "httpx (>=0.14.0,<0.19.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "orjson (>=3.2.1,<4.0.0)", "peewee (>=3.13.3,<4.0.0)", "pytest (>=6.2.4,<7.0.0)", "pytest-cov (>=2.12.0,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "requests (>=2.24.0,<3.0.0)", "sqlalchemy (>=1.3.18,<1.5.0)", "types-dataclasses (==0.1.7)", "types-orjson (==3.6.0)", "types-ujson (==0.1.1)", "ujson (>=4.0.1,<5.0.0)"] 237 | 238 | [[package]] 239 | name = "h11" 240 | version = "0.12.0" 241 | description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" 242 | optional = false 243 | python-versions = ">=3.6" 244 | files = [ 245 | {file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"}, 246 | {file = "h11-0.12.0.tar.gz", hash = "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"}, 247 | ] 248 | 249 | [[package]] 250 | name = "httpcore" 251 | version = "0.15.0" 252 | description = "A minimal low-level HTTP client." 253 | optional = false 254 | python-versions = ">=3.7" 255 | files = [ 256 | {file = "httpcore-0.15.0-py3-none-any.whl", hash = "sha256:1105b8b73c025f23ff7c36468e4432226cbb959176eab66864b8e31c4ee27fa6"}, 257 | {file = "httpcore-0.15.0.tar.gz", hash = "sha256:18b68ab86a3ccf3e7dc0f43598eaddcf472b602aba29f9aa6ab85fe2ada3980b"}, 258 | ] 259 | 260 | [package.dependencies] 261 | anyio = "==3.*" 262 | certifi = "*" 263 | h11 = ">=0.11,<0.13" 264 | sniffio = "==1.*" 265 | 266 | [package.extras] 267 | http2 = ["h2 (>=3,<5)"] 268 | socks = ["socksio (==1.*)"] 269 | 270 | [[package]] 271 | name = "httpx" 272 | version = "0.23.0" 273 | description = "The next generation HTTP client." 274 | optional = false 275 | python-versions = ">=3.7" 276 | files = [ 277 | {file = "httpx-0.23.0-py3-none-any.whl", hash = "sha256:42974f577483e1e932c3cdc3cd2303e883cbfba17fe228b0f63589764d7b9c4b"}, 278 | {file = "httpx-0.23.0.tar.gz", hash = "sha256:f28eac771ec9eb4866d3fb4ab65abd42d38c424739e80c08d8d20570de60b0ef"}, 279 | ] 280 | 281 | [package.dependencies] 282 | certifi = "*" 283 | httpcore = ">=0.15.0,<0.16.0" 284 | rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]} 285 | sniffio = "*" 286 | 287 | [package.extras] 288 | brotli = ["brotli", "brotlicffi"] 289 | cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<13)"] 290 | http2 = ["h2 (>=3,<5)"] 291 | socks = ["socksio (==1.*)"] 292 | 293 | [[package]] 294 | name = "idna" 295 | version = "3.3" 296 | description = "Internationalized Domain Names in Applications (IDNA)" 297 | optional = false 298 | python-versions = ">=3.5" 299 | files = [ 300 | {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, 301 | {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, 302 | ] 303 | 304 | [[package]] 305 | name = "iniconfig" 306 | version = "1.1.1" 307 | description = "iniconfig: brain-dead simple config-ini parsing" 308 | optional = false 309 | python-versions = "*" 310 | files = [ 311 | {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, 312 | {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, 313 | ] 314 | 315 | [[package]] 316 | name = "iso8601" 317 | version = "0.1.16" 318 | description = "Simple module to parse ISO 8601 dates" 319 | optional = false 320 | python-versions = "*" 321 | files = [ 322 | {file = "iso8601-0.1.16-py2.py3-none-any.whl", hash = "sha256:906714829fedbc89955d52806c903f2332e3948ed94e31e85037f9e0226b8376"}, 323 | {file = "iso8601-0.1.16.tar.gz", hash = "sha256:36532f77cc800594e8f16641edae7f1baf7932f05d8e508545b95fc53c6dc85b"}, 324 | ] 325 | 326 | [[package]] 327 | name = "outcome" 328 | version = "1.1.0" 329 | description = "Capture the outcome of Python function calls." 330 | optional = false 331 | python-versions = ">=3.6" 332 | files = [ 333 | {file = "outcome-1.1.0-py2.py3-none-any.whl", hash = "sha256:c7dd9375cfd3c12db9801d080a3b63d4b0a261aa996c4c13152380587288d958"}, 334 | {file = "outcome-1.1.0.tar.gz", hash = "sha256:e862f01d4e626e63e8f92c38d1f8d5546d3f9cce989263c521b2e7990d186967"}, 335 | ] 336 | 337 | [package.dependencies] 338 | attrs = ">=19.2.0" 339 | 340 | [[package]] 341 | name = "packaging" 342 | version = "21.3" 343 | description = "Core utilities for Python packages" 344 | optional = false 345 | python-versions = ">=3.6" 346 | files = [ 347 | {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, 348 | {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, 349 | ] 350 | 351 | [package.dependencies] 352 | pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" 353 | 354 | [[package]] 355 | name = "pluggy" 356 | version = "1.0.0" 357 | description = "plugin and hook calling mechanisms for python" 358 | optional = false 359 | python-versions = ">=3.6" 360 | files = [ 361 | {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, 362 | {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, 363 | ] 364 | 365 | [package.extras] 366 | dev = ["pre-commit", "tox"] 367 | testing = ["pytest", "pytest-benchmark"] 368 | 369 | [[package]] 370 | name = "py" 371 | version = "1.11.0" 372 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 373 | optional = false 374 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 375 | files = [ 376 | {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, 377 | {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, 378 | ] 379 | 380 | [[package]] 381 | name = "pycodestyle" 382 | version = "2.8.0" 383 | description = "Python style guide checker" 384 | optional = false 385 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 386 | files = [ 387 | {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, 388 | {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, 389 | ] 390 | 391 | [[package]] 392 | name = "pycparser" 393 | version = "2.21" 394 | description = "C parser in Python" 395 | optional = false 396 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 397 | files = [ 398 | {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, 399 | {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, 400 | ] 401 | 402 | [[package]] 403 | name = "pydantic" 404 | version = "1.8.2" 405 | description = "Data validation and settings management using python 3.6 type hinting" 406 | optional = false 407 | python-versions = ">=3.6.1" 408 | files = [ 409 | {file = "pydantic-1.8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739"}, 410 | {file = "pydantic-1.8.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4"}, 411 | {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:589eb6cd6361e8ac341db97602eb7f354551482368a37f4fd086c0733548308e"}, 412 | {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:10e5622224245941efc193ad1d159887872776df7a8fd592ed746aa25d071840"}, 413 | {file = "pydantic-1.8.2-cp36-cp36m-win_amd64.whl", hash = "sha256:99a9fc39470010c45c161a1dc584997f1feb13f689ecf645f59bb4ba623e586b"}, 414 | {file = "pydantic-1.8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a83db7205f60c6a86f2c44a61791d993dff4b73135df1973ecd9eed5ea0bda20"}, 415 | {file = "pydantic-1.8.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:41b542c0b3c42dc17da70554bc6f38cbc30d7066d2c2815a94499b5684582ecb"}, 416 | {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:ea5cb40a3b23b3265f6325727ddfc45141b08ed665458be8c6285e7b85bd73a1"}, 417 | {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:18b5ea242dd3e62dbf89b2b0ec9ba6c7b5abaf6af85b95a97b00279f65845a23"}, 418 | {file = "pydantic-1.8.2-cp37-cp37m-win_amd64.whl", hash = "sha256:234a6c19f1c14e25e362cb05c68afb7f183eb931dd3cd4605eafff055ebbf287"}, 419 | {file = "pydantic-1.8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:021ea0e4133e8c824775a0cfe098677acf6fa5a3cbf9206a376eed3fc09302cd"}, 420 | {file = "pydantic-1.8.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e710876437bc07bd414ff453ac8ec63d219e7690128d925c6e82889d674bb505"}, 421 | {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:ac8eed4ca3bd3aadc58a13c2aa93cd8a884bcf21cb019f8cfecaae3b6ce3746e"}, 422 | {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:4a03cbbe743e9c7247ceae6f0d8898f7a64bb65800a45cbdc52d65e370570820"}, 423 | {file = "pydantic-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:8621559dcf5afacf0069ed194278f35c255dc1a1385c28b32dd6c110fd6531b3"}, 424 | {file = "pydantic-1.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8b223557f9510cf0bfd8b01316bf6dd281cf41826607eada99662f5e4963f316"}, 425 | {file = "pydantic-1.8.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:244ad78eeb388a43b0c927e74d3af78008e944074b7d0f4f696ddd5b2af43c62"}, 426 | {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:05ef5246a7ffd2ce12a619cbb29f3307b7c4509307b1b49f456657b43529dc6f"}, 427 | {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:54cd5121383f4a461ff7644c7ca20c0419d58052db70d8791eacbbe31528916b"}, 428 | {file = "pydantic-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:4be75bebf676a5f0f87937c6ddb061fa39cbea067240d98e298508c1bda6f3f3"}, 429 | {file = "pydantic-1.8.2-py3-none-any.whl", hash = "sha256:fec866a0b59f372b7e776f2d7308511784dace622e0992a0b59ea3ccee0ae833"}, 430 | {file = "pydantic-1.8.2.tar.gz", hash = "sha256:26464e57ccaafe72b7ad156fdaa4e9b9ef051f69e175dbbb463283000c05ab7b"}, 431 | ] 432 | 433 | [package.dependencies] 434 | typing-extensions = ">=3.7.4.3" 435 | 436 | [package.extras] 437 | dotenv = ["python-dotenv (>=0.10.4)"] 438 | email = ["email-validator (>=1.0.3)"] 439 | 440 | [[package]] 441 | name = "pygments" 442 | version = "2.15.0" 443 | description = "Pygments is a syntax highlighting package written in Python." 444 | optional = false 445 | python-versions = ">=3.7" 446 | files = [ 447 | {file = "Pygments-2.15.0-py3-none-any.whl", hash = "sha256:77a3299119af881904cd5ecd1ac6a66214b6e9bed1f2db16993b54adede64094"}, 448 | {file = "Pygments-2.15.0.tar.gz", hash = "sha256:f7e36cffc4c517fbc252861b9a6e4644ca0e5abadf9a113c72d1358ad09b9500"}, 449 | ] 450 | 451 | [package.extras] 452 | plugins = ["importlib-metadata"] 453 | 454 | [[package]] 455 | name = "pyparsing" 456 | version = "3.0.7" 457 | description = "Python parsing module" 458 | optional = false 459 | python-versions = ">=3.6" 460 | files = [ 461 | {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, 462 | {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, 463 | ] 464 | 465 | [package.extras] 466 | diagrams = ["jinja2", "railroad-diagrams"] 467 | 468 | [[package]] 469 | name = "pypika-tortoise" 470 | version = "0.1.1" 471 | description = "Forked from pypika and streamline just for tortoise-orm" 472 | optional = false 473 | python-versions = ">=3.7,<4.0" 474 | files = [ 475 | {file = "pypika-tortoise-0.1.1.tar.gz", hash = "sha256:6831d0a56e5e0ecefac3307dd9bdb3e5073fdac5d617401601d3a6713e059f3c"}, 476 | {file = "pypika_tortoise-0.1.1-py3-none-any.whl", hash = "sha256:860020094e01058ea80602c90d4a843d0a42cffefcf4f3cb1a7f2c18b880c638"}, 477 | ] 478 | 479 | [[package]] 480 | name = "pytest" 481 | version = "7.1.0" 482 | description = "pytest: simple powerful testing with Python" 483 | optional = false 484 | python-versions = ">=3.7" 485 | files = [ 486 | {file = "pytest-7.1.0-py3-none-any.whl", hash = "sha256:b555252a95bbb2a37a97b5ac2eb050c436f7989993565f5e0c9128fcaacadd0e"}, 487 | {file = "pytest-7.1.0.tar.gz", hash = "sha256:f1089d218cfcc63a212c42896f1b7fbf096874d045e1988186861a1a87d27b47"}, 488 | ] 489 | 490 | [package.dependencies] 491 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} 492 | attrs = ">=19.2.0" 493 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 494 | iniconfig = "*" 495 | packaging = "*" 496 | pluggy = ">=0.12,<2.0" 497 | py = ">=1.8.2" 498 | tomli = ">=1.0.0" 499 | 500 | [package.extras] 501 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] 502 | 503 | [[package]] 504 | name = "pytest-asyncio" 505 | version = "0.18.2" 506 | description = "Pytest support for asyncio" 507 | optional = false 508 | python-versions = ">=3.7" 509 | files = [ 510 | {file = "pytest-asyncio-0.18.2.tar.gz", hash = "sha256:fc8e4190f33fee7797cc7f1829f46a82c213f088af5d1bb5d4e454fe87e6cdc2"}, 511 | {file = "pytest_asyncio-0.18.2-py3-none-any.whl", hash = "sha256:20db0bdd3d7581b2e11f5858a5d9541f2db9cd8c5853786f94ad273d466c8c6d"}, 512 | ] 513 | 514 | [package.dependencies] 515 | pytest = ">=6.1.0" 516 | 517 | [package.extras] 518 | testing = ["coverage (==6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (==0.931)"] 519 | 520 | [[package]] 521 | name = "python-dotenv" 522 | version = "0.20.0" 523 | description = "Read key-value pairs from a .env file and set them as environment variables" 524 | optional = false 525 | python-versions = ">=3.5" 526 | files = [ 527 | {file = "python-dotenv-0.20.0.tar.gz", hash = "sha256:b7e3b04a59693c42c36f9ab1cc2acc46fa5df8c78e178fc33a8d4cd05c8d498f"}, 528 | {file = "python_dotenv-0.20.0-py3-none-any.whl", hash = "sha256:d92a187be61fe482e4fd675b6d52200e7be63a12b724abbf931a40ce4fa92938"}, 529 | ] 530 | 531 | [package.extras] 532 | cli = ["click (>=5.0)"] 533 | 534 | [[package]] 535 | name = "pytz" 536 | version = "2021.3" 537 | description = "World timezone definitions, modern and historical" 538 | optional = false 539 | python-versions = "*" 540 | files = [ 541 | {file = "pytz-2021.3-py2.py3-none-any.whl", hash = "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c"}, 542 | {file = "pytz-2021.3.tar.gz", hash = "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"}, 543 | ] 544 | 545 | [[package]] 546 | name = "rfc3986" 547 | version = "1.5.0" 548 | description = "Validating URI References per RFC 3986" 549 | optional = false 550 | python-versions = "*" 551 | files = [ 552 | {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, 553 | {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, 554 | ] 555 | 556 | [package.dependencies] 557 | idna = {version = "*", optional = true, markers = "extra == \"idna2008\""} 558 | 559 | [package.extras] 560 | idna2008 = ["idna"] 561 | 562 | [[package]] 563 | name = "rich" 564 | version = "10.15.2" 565 | description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" 566 | optional = false 567 | python-versions = ">=3.6.2,<4.0.0" 568 | files = [ 569 | {file = "rich-10.15.2-py3-none-any.whl", hash = "sha256:43b2c6ad51f46f6c94992aee546f1c177719f4e05aff8f5ea4d2efae3ebdac89"}, 570 | {file = "rich-10.15.2.tar.gz", hash = "sha256:1dded089b79dd042b3ab5cd63439a338e16652001f0c16e73acdcf4997ad772d"}, 571 | ] 572 | 573 | [package.dependencies] 574 | colorama = ">=0.4.0,<0.5.0" 575 | commonmark = ">=0.9.0,<0.10.0" 576 | pygments = ">=2.6.0,<3.0.0" 577 | 578 | [package.extras] 579 | jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"] 580 | 581 | [[package]] 582 | name = "sentry-sdk" 583 | version = "1.14.0" 584 | description = "Python client for Sentry (https://sentry.io)" 585 | optional = false 586 | python-versions = "*" 587 | files = [ 588 | {file = "sentry-sdk-1.14.0.tar.gz", hash = "sha256:273fe05adf052b40fd19f6d4b9a5556316807246bd817e5e3482930730726bb0"}, 589 | {file = "sentry_sdk-1.14.0-py2.py3-none-any.whl", hash = "sha256:72c00322217d813cf493fe76590b23a757e063ff62fec59299f4af7201dd4448"}, 590 | ] 591 | 592 | [package.dependencies] 593 | certifi = "*" 594 | urllib3 = {version = ">=1.26.11", markers = "python_version >= \"3.6\""} 595 | 596 | [package.extras] 597 | aiohttp = ["aiohttp (>=3.5)"] 598 | beam = ["apache-beam (>=2.12)"] 599 | bottle = ["bottle (>=0.12.13)"] 600 | celery = ["celery (>=3)"] 601 | chalice = ["chalice (>=1.16.0)"] 602 | django = ["django (>=1.8)"] 603 | falcon = ["falcon (>=1.4)"] 604 | fastapi = ["fastapi (>=0.79.0)"] 605 | flask = ["blinker (>=1.1)", "flask (>=0.11)"] 606 | httpx = ["httpx (>=0.16.0)"] 607 | opentelemetry = ["opentelemetry-distro (>=0.35b0)"] 608 | pure-eval = ["asttokens", "executing", "pure-eval"] 609 | pymongo = ["pymongo (>=3.1)"] 610 | pyspark = ["pyspark (>=2.4.4)"] 611 | quart = ["blinker (>=1.1)", "quart (>=0.16.1)"] 612 | rq = ["rq (>=0.6)"] 613 | sanic = ["sanic (>=0.8)"] 614 | sqlalchemy = ["sqlalchemy (>=1.2)"] 615 | starlette = ["starlette (>=0.19.1)"] 616 | starlite = ["starlite (>=1.48)"] 617 | tornado = ["tornado (>=5)"] 618 | 619 | [[package]] 620 | name = "sniffio" 621 | version = "1.2.0" 622 | description = "Sniff out which async library your code is running under" 623 | optional = false 624 | python-versions = ">=3.5" 625 | files = [ 626 | {file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"}, 627 | {file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"}, 628 | ] 629 | 630 | [[package]] 631 | name = "sortedcontainers" 632 | version = "2.4.0" 633 | description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" 634 | optional = false 635 | python-versions = "*" 636 | files = [ 637 | {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, 638 | {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, 639 | ] 640 | 641 | [[package]] 642 | name = "starlette" 643 | version = "0.16.0" 644 | description = "The little ASGI library that shines." 645 | optional = false 646 | python-versions = ">=3.6" 647 | files = [ 648 | {file = "starlette-0.16.0-py3-none-any.whl", hash = "sha256:38eb24bf705a2c317e15868e384c1b8a12ca396e5a3c3a003db7e667c43f939f"}, 649 | {file = "starlette-0.16.0.tar.gz", hash = "sha256:e1904b5d0007aee24bdd3c43994be9b3b729f4f58e740200de1d623f8c3a8870"}, 650 | ] 651 | 652 | [package.dependencies] 653 | anyio = ">=3.0.0,<4" 654 | 655 | [package.extras] 656 | full = ["graphene", "itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests"] 657 | 658 | [[package]] 659 | name = "toml" 660 | version = "0.10.2" 661 | description = "Python Library for Tom's Obvious, Minimal Language" 662 | optional = false 663 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 664 | files = [ 665 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 666 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 667 | ] 668 | 669 | [[package]] 670 | name = "tomli" 671 | version = "2.0.1" 672 | description = "A lil' TOML parser" 673 | optional = false 674 | python-versions = ">=3.7" 675 | files = [ 676 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 677 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 678 | ] 679 | 680 | [[package]] 681 | name = "tortoise-orm" 682 | version = "0.17.8" 683 | description = "Easy async ORM for python, built with relations in mind" 684 | optional = false 685 | python-versions = ">=3.7,<4.0" 686 | files = [ 687 | {file = "tortoise-orm-0.17.8.tar.gz", hash = "sha256:1f5020e9964d32a4d6ed685d466b5d7285de328a63ee92ee988c1e4baf8fefbf"}, 688 | {file = "tortoise_orm-0.17.8-py3-none-any.whl", hash = "sha256:f18c41bb83be4748a6ca259ed7309ca954b35f5790971824bbc79a11d2b1ef3b"}, 689 | ] 690 | 691 | [package.dependencies] 692 | aiosqlite = ">=0.16.0,<0.18.0" 693 | iso8601 = ">=0.1.13,<0.2.0" 694 | pypika-tortoise = ">=0.1.1,<0.2.0" 695 | pytz = "*" 696 | 697 | [package.extras] 698 | accel = ["ciso8601 (>=2.1.2,<3.0.0)", "python-rapidjson", "uvloop (>=0.14.0,<0.15.0)"] 699 | aiomysql = ["aiomysql"] 700 | asyncmy = ["asyncmy"] 701 | asyncpg = ["asyncpg"] 702 | 703 | [[package]] 704 | name = "trio" 705 | version = "0.20.0" 706 | description = "A friendly Python library for async concurrency and I/O" 707 | optional = false 708 | python-versions = ">=3.7" 709 | files = [ 710 | {file = "trio-0.20.0-py3-none-any.whl", hash = "sha256:fb2d48e4eab0dfb786a472cd514aaadc71e3445b203bc300bad93daa75d77c1a"}, 711 | {file = "trio-0.20.0.tar.gz", hash = "sha256:670a52d3115d0e879e1ac838a4eb999af32f858163e3a704fe4839de2a676070"}, 712 | ] 713 | 714 | [package.dependencies] 715 | async-generator = ">=1.9" 716 | attrs = ">=19.2.0" 717 | cffi = {version = ">=1.14", markers = "os_name == \"nt\" and implementation_name != \"pypy\""} 718 | idna = "*" 719 | outcome = "*" 720 | sniffio = "*" 721 | sortedcontainers = "*" 722 | 723 | [[package]] 724 | name = "typing-extensions" 725 | version = "4.0.1" 726 | description = "Backported and Experimental Type Hints for Python 3.6+" 727 | optional = false 728 | python-versions = ">=3.6" 729 | files = [ 730 | {file = "typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"}, 731 | {file = "typing_extensions-4.0.1.tar.gz", hash = "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e"}, 732 | ] 733 | 734 | [[package]] 735 | name = "urllib3" 736 | version = "1.26.18" 737 | description = "HTTP library with thread-safe connection pooling, file post, and more." 738 | optional = false 739 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" 740 | files = [ 741 | {file = "urllib3-1.26.18-py2.py3-none-any.whl", hash = "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07"}, 742 | {file = "urllib3-1.26.18.tar.gz", hash = "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0"}, 743 | ] 744 | 745 | [package.extras] 746 | brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] 747 | secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] 748 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] 749 | 750 | [[package]] 751 | name = "uvicorn" 752 | version = "0.15.0" 753 | description = "The lightning-fast ASGI server." 754 | optional = false 755 | python-versions = "*" 756 | files = [ 757 | {file = "uvicorn-0.15.0-py3-none-any.whl", hash = "sha256:17f898c64c71a2640514d4089da2689e5db1ce5d4086c2d53699bf99513421c1"}, 758 | {file = "uvicorn-0.15.0.tar.gz", hash = "sha256:d9a3c0dd1ca86728d3e235182683b4cf94cd53a867c288eaeca80ee781b2caff"}, 759 | ] 760 | 761 | [package.dependencies] 762 | asgiref = ">=3.4.0" 763 | click = ">=7.0" 764 | h11 = ">=0.8" 765 | 766 | [package.extras] 767 | standard = ["PyYAML (>=5.1)", "colorama (>=0.4)", "httptools (==0.2.*)", "python-dotenv (>=0.13)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchgod (>=0.6)", "websockets (>=9.1)"] 768 | 769 | [metadata] 770 | lock-version = "2.0" 771 | python-versions = "^3.8" 772 | content-hash = "4e0bcafa6431f2316feed913267a8d9da0e97c5cc23dcf5645c738b6d8d6f29b" 773 | --------------------------------------------------------------------------------