├── .github └── workflows │ └── python-package.yml ├── LICENSE ├── README.rst ├── dist ├── flask-fastx-0.1.0.tar.gz └── flask_fastx-0.1.0-py3-none-any.whl ├── examples ├── __init__.py ├── api_model_example.py ├── data.py ├── param_location_parsing.py └── quick_start.py ├── flask_fastx ├── __init__.py ├── autowire_decorator.py ├── model_api.py ├── param_funcations.py ├── params.py ├── parser_api.py └── path_param.py ├── poetry.lock ├── pyproject.toml ├── requirements.txt ├── tests ├── __init__.py ├── test_flask_fastx.py ├── test_model.py ├── test_parser.py └── utils.py └── verify.sh /.github/workflows/python-package.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: CI Tests 5 | 6 | on: 7 | push: 8 | branches: 9 | - main 10 | pull_request: 11 | branches: 12 | - main 13 | 14 | jobs: 15 | build: 16 | 17 | runs-on: ${{ matrix.os }} 18 | strategy: 19 | matrix: 20 | os: [ubuntu-latest, macos-latest, windows-latest] 21 | include: 22 | - os: ubuntu-latest 23 | path: ~/.cache/pip 24 | - os: macos-latest 25 | path: ~/Library/Caches/pip 26 | - os: windows-latest 27 | path: ~\AppData\Local\pip\Cache 28 | 29 | steps: 30 | - uses: actions/checkout@v2 31 | - name: Set up Python 3.7 32 | uses: actions/setup-python@v2 33 | with: 34 | python-version: 3.7 35 | - name: Cache pip 36 | uses: actions/cache@v2 37 | with: 38 | path: ${{ matrix.path }} 39 | key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }} 40 | restore-keys: | 41 | ${{ runner.os }}-pip- 42 | ${{ runner.os }}- 43 | - name: Setup Poetry 44 | uses: snok/install-poetry@v1 45 | with: 46 | virtualenvs-create: false 47 | - name: Install dependencies 48 | run: | 49 | python -m pip install --upgrade pip 50 | python -m pip install flake8 pytest 51 | pip install -r requirements.txt 52 | - name: Lint with flake8 53 | run: | 54 | # stop the build if there are Python syntax errors or undefined names 55 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 56 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 57 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 58 | - name: Verify with Pylint & Mypy 59 | run: sh ./verify.sh 60 | - name: Run unit tests 61 | run: coverage run -m unittest discover tests 62 | - name: Generate coverage report 63 | run: | 64 | coverage report 65 | coverage xml 66 | - name: Upload coverage to Codecov 67 | if: matrix.os == 'ubuntu-latest' 68 | uses: codecov/codecov-action@v2 69 | with: 70 | token: ${{ secrets.CODECOV_TOKEN }} 71 | fail_ci_if_error: true 72 | files: ./coverage.xml 73 | flags: unittests 74 | 75 | 76 | publish: 77 | needs: build 78 | runs-on: ubuntu-latest 79 | steps: 80 | - uses: actions/checkout@v2 81 | - name: Set up Python 3.7 82 | uses: actions/setup-python@v2 83 | with: 84 | python-version: 3.7 85 | - name: Cache pip 86 | uses: actions/cache@v2 87 | with: 88 | path: ~/.cache/pip 89 | key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }} 90 | restore-keys: | 91 | ${{ runner.os }}-pip- 92 | ${{ runner.os }}- 93 | - name: Setup Poetry 94 | uses: snok/install-poetry@v1 95 | - name: Install dependencies 96 | run: | 97 | python -m pip install --upgrade pip 98 | python -m pip install flake8 pytest 99 | poetry install 100 | - name: Publishing to PyPi 101 | env: 102 | PYPI_USER: ${{ secrets.PYPI_USERNAME }} 103 | PYPI_PASS: ${{ secrets.PYPI_PASSWORD }} 104 | run: | 105 | poetry build 106 | poetry publish -u $PYPI_USER -p $PYPI_PASS 107 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Tactful.ai 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 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ============== 2 | Flask-Fastx 3 | ============== 4 | 5 | .. image:: https://codecov.io/gh/tactful-ai/flask-faster-api/branch/main/graph/badge.svg?token=FZJANN69LH 6 | :target: https://codecov.io/gh/tactful-ai/flask-faster-api 7 | :alt: Code Coverage 8 | 9 | .. image:: https://github.com/tactful-ai/flask-faster-api/actions/workflows/python-package.yml/badge.svg 10 | :target: https://github.com/tactful-ai/flask-faster-api/actions/workflows/python-package.yml 11 | :alt: CI Tests 12 | .. image:: https://img.shields.io/github/license/tactful-ai/flask-faster-api 13 | :alt: License 14 | .. image:: https://img.shields.io/github/stars/tactful-ai/flask-faster-api?style=social :alt: GitHub Repo stars 15 | 16 | .. image:: https://img.shields.io/pypi/pyversions/flask-fastx 17 | :target: https://pypi.org/project/flask-fastx 18 | :alt: Supported Python versions 19 | 20 | 21 | Flask-Fastx is a Fast API style support for Flask. It Gives you MyPy types with the flexibility of flask. 22 | 23 | 24 | 25 | Compatibility 26 | ============= 27 | 28 | Flask-Fastx requires Python 3.7+. 29 | 30 | 31 | 32 | Installation 33 | ============ 34 | 35 | You can install Flask-Fastx with pip: 36 | 37 | .. code-block:: console 38 | 39 | $ pip install flask-fastx 40 | 41 | 42 | Quick start 43 | =========== 44 | 45 | With Flask-Fastx and the autowire decorator feature, parsing path parameters and creating api models is done using only one decorator! 46 | 47 | Before Using Flask-Fastx 48 | ================================ 49 | 50 | .. image:: https://user-images.githubusercontent.com/63073172/130047931-d0944c25-05a5-4e25-b920-568262498deb.png 51 | 52 | 53 | After Using Flask-Fastx 54 | ================================ 55 | 56 | .. image:: https://user-images.githubusercontent.com/63073172/130047935-1ca90e0d-b8ae-4ca8-9564-f11b894f0664.png 57 | 58 | 59 | 60 | 61 | Contributors 62 | ============ 63 | 64 | Flask-Fastx is brought to you by @mfouad, @seifashraf1, @ahmedihabb2, @nadaabdelmaboud, @omargamal253 65 | 66 | 67 | 68 | 69 | Contribution 70 | ============ 71 | Want to contribute? That's awesome! (Details Soon) 72 | -------------------------------------------------------------------------------- /dist/flask-fastx-0.1.0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tactful-ai/flask-faster-api/60a3bf6fe5ff240bdc2795951923246e2f287f6d/dist/flask-fastx-0.1.0.tar.gz -------------------------------------------------------------------------------- /dist/flask_fastx-0.1.0-py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tactful-ai/flask-faster-api/60a3bf6fe5ff240bdc2795951923246e2f287f6d/dist/flask_fastx-0.1.0-py3-none-any.whl -------------------------------------------------------------------------------- /examples/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tactful-ai/flask-faster-api/60a3bf6fe5ff240bdc2795951923246e2f287f6d/examples/__init__.py -------------------------------------------------------------------------------- /examples/api_model_example.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Testing the library with returing model example 3 | ''' 4 | 5 | from flask import Flask, request 6 | from flask_restx import Api, Resource 7 | from flask_fastx import autowire, register_api 8 | from .data import courses 9 | 10 | app = Flask(__name__) 11 | api = Api(app) 12 | 13 | register_api(api) 14 | 15 | course_Model = dict({ 16 | 'id': int, 17 | 'title': str, 18 | 'duration': int 19 | }) 20 | 21 | 22 | @api.route('/') 23 | class Course(Resource): 24 | @autowire 25 | def get(self, id) -> course_Model: 26 | for course in courses: 27 | if course['id'] == id: 28 | return course 29 | 30 | 31 | if __name__ == '__main__': 32 | app.run(debug=True) 33 | -------------------------------------------------------------------------------- /examples/data.py: -------------------------------------------------------------------------------- 1 | courses = [ 2 | {'id': 0, 3 | 'title': 'CS106', 4 | 'duration': '30', 5 | 'teachers': ['Seif', 'Ahmed', 'Hammad'], 6 | 'studentsCount': 0, 7 | }, 8 | {'id': 1, 9 | 'title': 'CS110', 10 | 'duration': '45', 11 | 'teachers': ['Mohamed', 'Ahmed', 'Hammad'], 12 | 'studentsCount': 0, 13 | }, 14 | {'id': 2, 15 | 'title': 'CS321', 16 | 'duration': '60', 17 | 'teachers': ['Seif', 'Ashraf', 'Hammad'], 18 | 'studentsCount': 0, 19 | } 20 | ] 21 | -------------------------------------------------------------------------------- /examples/param_location_parsing.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tactful-ai/flask-faster-api/60a3bf6fe5ff240bdc2795951923246e2f287f6d/examples/param_location_parsing.py -------------------------------------------------------------------------------- /examples/quick_start.py: -------------------------------------------------------------------------------- 1 | from typing import List, Literal 2 | from flask import Flask, request, Blueprint 3 | from flask_restx import Api, Resource 4 | from werkzeug.utils import secure_filename 5 | 6 | from flask_fastx import autowire, register_api 7 | from flask_fastx.param_funcations import File, Header 8 | from flask_fastx.params import Query, Path, Body 9 | from pydantic import BaseModel 10 | from werkzeug.datastructures import FileStorage 11 | 12 | 13 | app = Flask(__name__) 14 | api = Api(app, version="1.0", title="Courses API", description="A simple API") 15 | courses_ns = api.namespace("courses", description="Courses operations") 16 | 17 | register_api(api) 18 | 19 | 20 | class Course(BaseModel): 21 | ''' 22 | id : the course unique identifier 23 | title : the course title 24 | duration : the course duration 25 | teachers : the enrolled teachers of the course 26 | studentsCount : the number of students enrolled in the course 27 | ''' 28 | id: int 29 | title: str 30 | duration: str 31 | teachers: List[str] 32 | studentsCount: int 33 | 34 | 35 | file_model = dict({ 36 | 'course_file': str, 37 | 'key': str, 38 | }) 39 | 40 | 41 | courses: List[Course] = [] 42 | 43 | 44 | @courses_ns.route('/') 45 | class CourseListApi(Resource): 46 | @autowire 47 | def get(self) -> List[dict]: 48 | dict_courses = [] 49 | for course in courses: 50 | dict_courses.append(dict(course)) 51 | return dict_courses 52 | 53 | @autowire 54 | def post(self, title: str = Body("CMP"), duration: int = Body(3)) -> Course: 55 | course = Course(id=len(courses)+1, title=title, 56 | duration=duration, teachers=[], studentsCount=0) 57 | courses.append(course) 58 | return course 59 | 60 | @autowire 61 | def put(self, course_file: FileStorage = File(None), key: str = Header(None)) -> file_model: 62 | filename = secure_filename(course_file.filename) 63 | file_ = { 64 | 'course_file': filename, 65 | 'key': key 66 | } 67 | return file_ 68 | 69 | 70 | @courses_ns.route('/') 71 | class CourseApi(Resource): 72 | @autowire 73 | def get(self, id: int) -> Course: 74 | course = [course for course in courses if course.id == id][0] 75 | return course 76 | 77 | @autowire 78 | def put(self, id: int, title: Literal["Java", "C#", "Kotlin"] = Query("Java"), teachers: List[str] = Query([])) -> Course: 79 | course = [course for course in courses if course.id == id][0] 80 | if title is not None: 81 | course.title = title 82 | if teachers is not None: 83 | course.teachers = teachers 84 | return course 85 | 86 | @autowire 87 | def delete(self, id: int) -> str: 88 | course = [course for course in courses if course.id == id][0] 89 | courses.remove(course) 90 | return "course is removed succisfully", 200 91 | 92 | 93 | if __name__ == '__main__': 94 | app.run(debug=True) 95 | -------------------------------------------------------------------------------- /flask_fastx/__init__.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Init file 3 | ''' 4 | 5 | __version__ = '0.1.0' 6 | 7 | from .autowire_decorator import autowire, register_api 8 | -------------------------------------------------------------------------------- /flask_fastx/autowire_decorator.py: -------------------------------------------------------------------------------- 1 | """The Autowire decorator is used to 2 | replace @api.marshal_with decorator and 3 | @api.expect decorator through supporting MyPy types.""" 4 | import inspect 5 | from functools import wraps 6 | from flask_restx import Api # type: ignore 7 | from flask_fastx.model_api import create_model 8 | from flask_fastx.parser_api import get_parser 9 | 10 | # pylint: disable=C0207 11 | 12 | class ApiDecorator(): 13 | """The API Decorator""" 14 | 15 | def __init__(self) -> None: 16 | """Initialize the API decorator""" 17 | self.api = Api() 18 | 19 | def set_api(self, api: Api) -> None: 20 | """Set the API""" 21 | self.api = api 22 | 23 | def get_api(self) -> Api: 24 | """Get the API""" 25 | return self.api 26 | 27 | 28 | API = ApiDecorator() 29 | 30 | 31 | def register_api(api_main): 32 | """Register The Main App Api""" 33 | API.set_api(api_main) 34 | 35 | 36 | def get_params_description(doc): 37 | """Get the parameters description from the docstring""" 38 | params_description = {} 39 | if doc is not None: 40 | doc = doc.split('\n') 41 | for line in doc: 42 | if ':' in line: 43 | line = line.split(':') 44 | line[0] = line[0].strip() 45 | params_description[line[0]] = line[1] 46 | return params_description 47 | 48 | 49 | def autowire(func): 50 | """The Autowire Decorator that wraps the function""" 51 | api = API.get_api() 52 | model_name = func.__qualname__.split('.')[0] 53 | params_return = func.__annotations__ 54 | params_return = params_return.get('return') 55 | isprimitive = False 56 | params_return_des = {} 57 | if params_return is None: 58 | api_model = api.model(model_name, {}) 59 | else: 60 | if check_class_dict(params_return): 61 | params_return = {'data': params_return} 62 | isprimitive = True 63 | if hasattr(params_return, '__annotations__'): 64 | params_return_des = get_params_description( 65 | params_return.__doc__) 66 | params_return = params_return.__annotations__ 67 | api_model = create_model( 68 | params_return, params_return_des) 69 | api_model = api.model(str(func.__name__)+model_name, api_model) 70 | signature = inspect.signature(func) 71 | parameters = dict(signature.parameters) 72 | parameters.pop('self') 73 | 74 | parser = get_parser(parameters) 75 | 76 | @wraps(func) 77 | @api.expect(parser) 78 | @api.marshal_with(api_model) 79 | def wrapper(*args, **kwargs): 80 | args_parser = parser.parse_args() 81 | if isprimitive: 82 | return {'data': func(*args, **args_parser, **kwargs)} 83 | return func(*args, **args_parser, **kwargs) 84 | return wrapper 85 | 86 | 87 | def check_class_dict(param_type): 88 | """Check the class dict""" 89 | dict_type = str(param_type).replace("<", "") 90 | dict_type = dict_type.replace(">", "") 91 | dict_type = dict_type.split(" ")[-1] 92 | check_dect = dict_type != "dict" and not isinstance(param_type, dict) 93 | if not hasattr(param_type, '__annotations__') and check_dect: 94 | return True 95 | return False 96 | -------------------------------------------------------------------------------- /flask_fastx/model_api.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This files contains the implementation of the model generator function 3 | it takes return type as input and returns a model as an output 4 | ''' 5 | # pylint: disable=C0207 6 | 7 | from flask_restx import fields # type: ignore 8 | 9 | # dict to map each return type to its field data type 10 | python2restplus = { 11 | 'str': fields.String, 12 | 'datetime': fields.DateTime, 13 | 'time': fields.String, 14 | 'bool': fields.Boolean, 15 | 'int': fields.Integer, 16 | 'float': fields.Float, 17 | 'relation': fields.Nested, 18 | 'typing.List': fields.List, 19 | 'dict': fields.Raw, 20 | } 21 | 22 | 23 | def create_model(types, descriptions): 24 | ''' 25 | the model generator function which takes return type as input and returns a model as an output 26 | ''' 27 | modeltemp = {} 28 | for type_ in types: 29 | ans = str(types[type_]) 30 | 31 | if ans.find('typing') != -1: 32 | key = ans.split("[")[0] 33 | fields_param = ans.split("[")[1].replace("]", "") 34 | type_ = str(type_) 35 | if descriptions.get(type_) is None: 36 | descriptions[type_] = "" 37 | if python2restplus.get(fields_param) is None: 38 | fields_param = 'dict' 39 | modeltemp[type_] = python2restplus[key]( 40 | python2restplus[fields_param], description=descriptions[type_]) 41 | else: 42 | k = str(type_) 43 | ans = ans.split(" ")[1] 44 | ans = ans.replace("'", "") 45 | ans = ans.replace(">", "") 46 | if descriptions.get(k) is None: 47 | descriptions[k] = "" 48 | modeltemp[k] = python2restplus[ans](description=descriptions[k]) 49 | return modeltemp 50 | -------------------------------------------------------------------------------- /flask_fastx/param_funcations.py: -------------------------------------------------------------------------------- 1 | """ 2 | define parameters functions 3 | """ 4 | # pylint: disable=E0611 5 | 6 | from typing import Any, Dict, Optional 7 | from pydantic.fields import Undefined # type: ignore 8 | from flask_fastx import params 9 | 10 | # pylint: disable=invalid-name 11 | def Path( # noqa: N802 12 | default: Any, 13 | *, 14 | alias: Optional[str] = None, 15 | title: Optional[str] = None, 16 | description: Optional[str] = None, 17 | min_length: Optional[int] = None, 18 | max_length: Optional[int] = None, 19 | regex: Optional[str] = None, 20 | example: Any = Undefined, 21 | examples: Optional[Dict[str, Any]] = None, 22 | deprecated: Optional[bool] = None, 23 | **extra: Any, 24 | ) -> Any: 25 | """ define path function """ 26 | return params.Path( 27 | default=default, 28 | alias=alias, 29 | title=title, 30 | description=description, 31 | min_length=min_length, 32 | max_length=max_length, 33 | regex=regex, 34 | example=example, 35 | examples=examples, 36 | deprecated=deprecated, 37 | **extra, 38 | ) 39 | 40 | 41 | def Query( # noqa: N802 42 | default: Any, 43 | *, 44 | alias: Optional[str] = None, 45 | title: Optional[str] = None, 46 | description: Optional[str] = None, 47 | min_length: Optional[int] = None, 48 | max_length: Optional[int] = None, 49 | regex: Optional[str] = None, 50 | example: Any = Undefined, 51 | examples: Optional[Dict[str, Any]] = None, 52 | deprecated: Optional[bool] = None, 53 | **extra: Any, 54 | ) -> Any: 55 | """ define Query function """ 56 | return params.Query( 57 | default, 58 | alias=alias, 59 | title=title, 60 | description=description, 61 | min_length=min_length, 62 | max_length=max_length, 63 | regex=regex, 64 | example=example, 65 | examples=examples, 66 | deprecated=deprecated, 67 | **extra, 68 | ) 69 | 70 | 71 | def Header( # noqa: N802 72 | default: Any, 73 | *, 74 | alias: Optional[str] = None, 75 | convert_underscores: bool = True, 76 | title: Optional[str] = None, 77 | description: Optional[str] = None, 78 | min_length: Optional[int] = None, 79 | max_length: Optional[int] = None, 80 | regex: Optional[str] = None, 81 | example: Any = Undefined, 82 | examples: Optional[Dict[str, Any]] = None, 83 | deprecated: Optional[bool] = None, 84 | **extra: Any, 85 | ) -> Any: 86 | """ define header function """ 87 | return params.Header( 88 | default, 89 | alias=alias, 90 | convert_underscores=convert_underscores, 91 | title=title, 92 | description=description, 93 | min_length=min_length, 94 | max_length=max_length, 95 | regex=regex, 96 | example=example, 97 | examples=examples, 98 | deprecated=deprecated, 99 | **extra, 100 | ) 101 | 102 | 103 | def Cookie( # noqa: N802 104 | default: Any, 105 | *, 106 | alias: Optional[str] = None, 107 | title: Optional[str] = None, 108 | description: Optional[str] = None, 109 | min_length: Optional[int] = None, 110 | max_length: Optional[int] = None, 111 | regex: Optional[str] = None, 112 | example: Any = Undefined, 113 | examples: Optional[Dict[str, Any]] = None, 114 | deprecated: Optional[bool] = None, 115 | **extra: Any, 116 | ) -> Any: 117 | """ define cookie function """ 118 | return params.Cookie( 119 | default, 120 | alias=alias, 121 | title=title, 122 | description=description, 123 | min_length=min_length, 124 | max_length=max_length, 125 | regex=regex, 126 | example=example, 127 | examples=examples, 128 | deprecated=deprecated, 129 | **extra, 130 | ) 131 | 132 | 133 | def Body( # noqa: N802 134 | default: Any, 135 | *, 136 | embed: bool = False, 137 | media_type: str = "application/json", 138 | alias: Optional[str] = None, 139 | title: Optional[str] = None, 140 | description: Optional[str] = None, 141 | min_length: Optional[int] = None, 142 | max_length: Optional[int] = None, 143 | regex: Optional[str] = None, 144 | example: Any = Undefined, 145 | examples: Optional[Dict[str, Any]] = None, 146 | **extra: Any, 147 | ) -> Any: 148 | """ define body function """ 149 | return params.Body( 150 | default, 151 | embed=embed, 152 | media_type=media_type, 153 | alias=alias, 154 | title=title, 155 | description=description, 156 | min_length=min_length, 157 | max_length=max_length, 158 | regex=regex, 159 | example=example, 160 | examples=examples, 161 | **extra, 162 | ) 163 | 164 | 165 | def Form( # noqa: N802 166 | default: Any, 167 | *, 168 | media_type: str = "application/x-www-form-urlencoded", 169 | alias: Optional[str] = None, 170 | title: Optional[str] = None, 171 | description: Optional[str] = None, 172 | min_length: Optional[int] = None, 173 | max_length: Optional[int] = None, 174 | regex: Optional[str] = None, 175 | example: Any = Undefined, 176 | examples: Optional[Dict[str, Any]] = None, 177 | **extra: Any, 178 | ) -> Any: 179 | """ define form function """ 180 | return params.Form( 181 | default, 182 | media_type=media_type, 183 | alias=alias, 184 | title=title, 185 | description=description, 186 | min_length=min_length, 187 | max_length=max_length, 188 | regex=regex, 189 | example=example, 190 | examples=examples, 191 | **extra, 192 | ) 193 | 194 | 195 | def File( # noqa: N802 196 | default: Any, 197 | *, 198 | media_type: str = "multipart/form-data", 199 | alias: Optional[str] = None, 200 | title: Optional[str] = None, 201 | description: Optional[str] = None, 202 | min_length: Optional[int] = None, 203 | max_length: Optional[int] = None, 204 | regex: Optional[str] = None, 205 | example: Any = Undefined, 206 | examples: Optional[Dict[str, Any]] = None, 207 | **extra: Any, 208 | ) -> Any: 209 | """ define file function """ 210 | return params.File( 211 | default, 212 | media_type=media_type, 213 | alias=alias, 214 | title=title, 215 | description=description, 216 | min_length=min_length, 217 | max_length=max_length, 218 | regex=regex, 219 | example=example, 220 | examples=examples, 221 | **extra, 222 | ) 223 | -------------------------------------------------------------------------------- /flask_fastx/params.py: -------------------------------------------------------------------------------- 1 | """ 2 | define parameters location classes 3 | """ 4 | 5 | # pylint: disable=E0611 6 | # pylint: disable=R0903 7 | 8 | from enum import Enum 9 | from typing import Any, Dict, Optional 10 | 11 | from pydantic.fields import FieldInfo, Undefined # type: ignore 12 | 13 | 14 | class ParamTypes(Enum): 15 | """ 16 | define ParamTypes 17 | """ 18 | QUERY = "args" 19 | HEADER = "headers" 20 | PATH = "path" 21 | COOKIE = "cookies" 22 | BODY = 'json' 23 | FORM = 'form' 24 | FILE = 'files' 25 | 26 | 27 | 28 | 29 | 30 | class Param(FieldInfo): 31 | """ 32 | define Param class 33 | """ 34 | in_: ParamTypes 35 | 36 | def __init__( 37 | self, 38 | default: Any, 39 | *, 40 | alias: Optional[str] = None, 41 | title: Optional[str] = None, 42 | description: Optional[str] = None, 43 | min_length: Optional[int] = None, 44 | max_length: Optional[int] = None, 45 | regex: Optional[str] = None, 46 | example: Any = Undefined, 47 | examples: Optional[Dict[str, Any]] = None, 48 | deprecated: Optional[bool] = None, 49 | **extra: Any, 50 | ): 51 | self.deprecated = deprecated 52 | self.example = example 53 | self.examples = examples 54 | super().__init__( 55 | default, 56 | alias=alias, 57 | title=title, 58 | description=description, 59 | min_length=min_length, 60 | max_length=max_length, 61 | regex=regex, 62 | **extra, 63 | ) 64 | 65 | def __repr__(self) -> str: 66 | return f"{self.__class__.__name__}({self.default})" 67 | 68 | 69 | class Path(Param): 70 | """ 71 | define Path class 72 | """ 73 | in_ = ParamTypes.PATH 74 | 75 | def __init__( 76 | self, 77 | default: Any, 78 | *, 79 | alias: Optional[str] = None, 80 | title: Optional[str] = None, 81 | description: Optional[str] = None, 82 | min_length: Optional[int] = None, 83 | max_length: Optional[int] = None, 84 | regex: Optional[str] = None, 85 | example: Any = Undefined, 86 | examples: Optional[Dict[str, Any]] = None, 87 | deprecated: Optional[bool] = None, 88 | **extra: Any, 89 | ): 90 | self.in_ = self.in_ 91 | super().__init__( 92 | ..., 93 | alias=alias, 94 | title=title, 95 | description=description, 96 | min_length=min_length, 97 | max_length=max_length, 98 | regex=regex, 99 | deprecated=deprecated, 100 | example=example, 101 | examples=examples, 102 | **extra, 103 | ) 104 | 105 | 106 | class Query(Param): 107 | """ 108 | define Query class 109 | """ 110 | in_ = ParamTypes.QUERY 111 | 112 | def __init__( 113 | self, 114 | default: Any, 115 | *, 116 | alias: Optional[str] = None, 117 | title: Optional[str] = None, 118 | description: Optional[str] = None, 119 | min_length: Optional[int] = None, 120 | max_length: Optional[int] = None, 121 | regex: Optional[str] = None, 122 | example: Any = Undefined, 123 | examples: Optional[Dict[str, Any]] = None, 124 | deprecated: Optional[bool] = None, 125 | **extra: Any, 126 | ): 127 | self.in_ = self.in_ 128 | super().__init__( 129 | default, 130 | alias=alias, 131 | title=title, 132 | description=description, 133 | min_length=min_length, 134 | max_length=max_length, 135 | regex=regex, 136 | deprecated=deprecated, 137 | example=example, 138 | examples=examples, 139 | **extra, 140 | ) 141 | 142 | 143 | class Header(Param): 144 | """ 145 | define Header class 146 | """ 147 | in_ = ParamTypes.HEADER 148 | 149 | def __init__( 150 | self, 151 | default: Any, 152 | *, 153 | alias: Optional[str] = None, 154 | convert_underscores: bool = True, 155 | title: Optional[str] = None, 156 | description: Optional[str] = None, 157 | min_length: Optional[int] = None, 158 | max_length: Optional[int] = None, 159 | regex: Optional[str] = None, 160 | example: Any = Undefined, 161 | examples: Optional[Dict[str, Any]] = None, 162 | deprecated: Optional[bool] = None, 163 | **extra: Any, 164 | ): 165 | self.convert_underscores = convert_underscores 166 | self.in_ = self.in_ 167 | super().__init__( 168 | default, 169 | alias=alias, 170 | title=title, 171 | description=description, 172 | min_length=min_length, 173 | max_length=max_length, 174 | regex=regex, 175 | deprecated=deprecated, 176 | example=example, 177 | examples=examples, 178 | **extra, 179 | ) 180 | 181 | 182 | class Cookie(Param): 183 | """ 184 | define Cookie class 185 | """ 186 | in_ = ParamTypes.COOKIE 187 | 188 | def __init__( 189 | self, 190 | default: Any, 191 | *, 192 | alias: Optional[str] = None, 193 | title: Optional[str] = None, 194 | description: Optional[str] = None, 195 | min_length: Optional[int] = None, 196 | max_length: Optional[int] = None, 197 | regex: Optional[str] = None, 198 | example: Any = Undefined, 199 | examples: Optional[Dict[str, Any]] = None, 200 | deprecated: Optional[bool] = None, 201 | **extra: Any, 202 | ): 203 | self.in_ = self.in_ 204 | super().__init__( 205 | default, 206 | alias=alias, 207 | title=title, 208 | description=description, 209 | min_length=min_length, 210 | max_length=max_length, 211 | regex=regex, 212 | deprecated=deprecated, 213 | example=example, 214 | examples=examples, 215 | **extra, 216 | ) 217 | 218 | 219 | class Body(Param): 220 | """ 221 | define Body class 222 | """ 223 | in_ = ParamTypes.BODY 224 | 225 | def __init__( 226 | self, 227 | default: Any, 228 | *, 229 | embed: bool = False, 230 | media_type: str = "application/json", 231 | alias: Optional[str] = None, 232 | title: Optional[str] = None, 233 | description: Optional[str] = None, 234 | min_length: Optional[int] = None, 235 | max_length: Optional[int] = None, 236 | regex: Optional[str] = None, 237 | example: Any = Undefined, 238 | examples: Optional[Dict[str, Any]] = None, 239 | **extra: Any, 240 | ): 241 | self.embed = embed 242 | self.media_type = media_type 243 | self.example = example 244 | self.examples = examples 245 | super().__init__( 246 | default, 247 | alias=alias, 248 | title=title, 249 | description=description, 250 | min_length=min_length, 251 | max_length=max_length, 252 | regex=regex, 253 | **extra, 254 | ) 255 | 256 | def __repr__(self) -> str: 257 | return f"{self.__class__.__name__}({self.default})" 258 | 259 | 260 | class Form(Param): 261 | """ 262 | define Form class 263 | """ 264 | in_ = ParamTypes.FORM 265 | 266 | def __init__( 267 | self, 268 | default: Any, 269 | *, 270 | media_type: str = "application/x-www-form-urlencoded", 271 | alias: Optional[str] = None, 272 | title: Optional[str] = None, 273 | description: Optional[str] = None, 274 | min_length: Optional[int] = None, 275 | max_length: Optional[int] = None, 276 | regex: Optional[str] = None, 277 | example: Any = Undefined, 278 | examples: Optional[Dict[str, Any]] = None, 279 | **extra: Any, 280 | ): 281 | super().__init__( 282 | default, 283 | embed=True, 284 | media_type=media_type, 285 | alias=alias, 286 | title=title, 287 | description=description, 288 | min_length=min_length, 289 | max_length=max_length, 290 | regex=regex, 291 | example=example, 292 | examples=examples, 293 | **extra, 294 | ) 295 | 296 | 297 | class File(Param): 298 | """ 299 | define File class 300 | """ 301 | in_ = ParamTypes.FILE 302 | 303 | def __init__( 304 | self, 305 | default: Any, 306 | *, 307 | media_type: str = "multipart/form-data", 308 | alias: Optional[str] = None, 309 | title: Optional[str] = None, 310 | description: Optional[str] = None, 311 | min_length: Optional[int] = None, 312 | max_length: Optional[int] = None, 313 | regex: Optional[str] = None, 314 | example: Any = Undefined, 315 | examples: Optional[Dict[str, Any]] = None, 316 | **extra: Any, 317 | ): 318 | super().__init__( 319 | default, 320 | media_type=media_type, 321 | alias=alias, 322 | title=title, 323 | description=description, 324 | min_length=min_length, 325 | max_length=max_length, 326 | regex=regex, 327 | example=example, 328 | examples=examples, 329 | **extra, 330 | ) 331 | -------------------------------------------------------------------------------- /flask_fastx/parser_api.py: -------------------------------------------------------------------------------- 1 | """ This module enable to add parameters to be parsed """ 2 | 3 | import ast 4 | from flask_restx import reqparse # type: ignore 5 | from flask_fastx.params import Param 6 | 7 | LocationToRestX = { 8 | 'Query': 'args', 9 | 'Body': 'json', 10 | 'Form': 'form', 11 | 'Header': 'headers', 12 | 'Cookie': 'cookies', 13 | 'File': 'files', 14 | 'Path': 'path', 15 | } 16 | 17 | 18 | def get_parser(parameters) -> reqparse: 19 | """ 20 | Parse all parameters which taken from the provided auto-wire decorator and 21 | return the results as a parser. 22 | """ 23 | parser = reqparse.RequestParser() 24 | for param in parameters.values(): 25 | location: str = get_param_location(param) 26 | param_type = get_param_type(param) 27 | default = None 28 | if location == 'path': 29 | continue 30 | param_default: Param 31 | param_default = param.default 32 | if param_default.default: 33 | default = param_default.default 34 | if str(param_type).find('typing.List') != -1: 35 | list_type = get_list_type(param_type) 36 | parser.add_argument(str(param.name), type=list_type, action='split', 37 | location=location, default=default) 38 | 39 | elif str(param_type).find('typing.Literal') != -1: 40 | res = get_literal_tuple(str(param_type)) 41 | if res and len(res) > 0: 42 | parser.add_argument(str(param.name), type=type(res[0]), choices=res, 43 | location=location, default=default) 44 | else: 45 | parser.add_argument(str(param.name), type=param_type, 46 | location=location, default=default) 47 | 48 | return parser 49 | 50 | 51 | def get_param_type(param): 52 | """ return parameter type """ 53 | return param.annotation 54 | 55 | 56 | def get_literal_tuple(param_type): 57 | """ 58 | get literal option from annotation string and return result in tuple. 59 | """ 60 | s_index: int = str(param_type).index('[') 61 | e_index: int = str(param_type).index(']') 62 | str_list = str(param_type)[s_index + 1:e_index] 63 | 64 | res = ast.literal_eval(str_list) 65 | if res: 66 | return tuple(res) 67 | 68 | return None 69 | 70 | 71 | def get_param_location(param) -> str: 72 | """ 73 | take a param and return his location in rest_x . 74 | """ 75 | for location, value in LocationToRestX.items(): 76 | if str(param).find(location) != -1: 77 | return value 78 | 79 | return LocationToRestX['Path'] 80 | 81 | 82 | def get_list_type(param_type): 83 | """ 84 | return type of given list 85 | """ 86 | if str(param_type).find('[str]') != -1: 87 | return str 88 | 89 | if str(param_type).find('[int]') != -1: 90 | return int 91 | 92 | if str(param_type).find('[float]') != -1: 93 | return float 94 | 95 | if str(param_type).find('[bool]') != -1: 96 | return bool 97 | 98 | return str 99 | -------------------------------------------------------------------------------- /flask_fastx/path_param.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This module is for extracting path params from a given route 3 | ''' 4 | 5 | from typing import List 6 | import re 7 | 8 | 9 | def extract_path_params(path): 10 | ''' 11 | Extracting path params from string by regex and append them to list 12 | ''' 13 | params_types_list: List[str] = [] # Store path params with their type 14 | params_list: List[str] = [] # Store path params without type 15 | # Searching for params between angle brackets 16 | params = re.findall(r'\<(.*?)\>', path) # returns a list with all matches 17 | if len(params) > 0: 18 | params_types_list.extend(params) 19 | # removing param type 20 | for param in params_types_list: 21 | param = param[param.find(':')+1:] 22 | params_list.append(param) 23 | return params_list 24 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "aniso8601" 3 | version = "9.0.1" 4 | description = "A library for parsing ISO 8601 strings." 5 | category = "main" 6 | optional = false 7 | python-versions = "*" 8 | 9 | [package.extras] 10 | dev = ["black", "coverage", "isort", "pre-commit", "pyenchant", "pylint"] 11 | 12 | [[package]] 13 | name = "astroid" 14 | version = "2.6.6" 15 | description = "An abstract syntax tree for Python with inference support." 16 | category = "dev" 17 | optional = false 18 | python-versions = "~=3.6" 19 | 20 | [package.dependencies] 21 | lazy-object-proxy = ">=1.4.0" 22 | typed-ast = {version = ">=1.4.0,<1.5", markers = "implementation_name == \"cpython\" and python_version < \"3.8\""} 23 | typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} 24 | wrapt = ">=1.11,<1.13" 25 | 26 | [[package]] 27 | name = "atomicwrites" 28 | version = "1.4.0" 29 | description = "Atomic file writes." 30 | category = "dev" 31 | optional = false 32 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 33 | 34 | [[package]] 35 | name = "attrs" 36 | version = "21.2.0" 37 | description = "Classes Without Boilerplate" 38 | category = "main" 39 | optional = false 40 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 41 | 42 | [package.extras] 43 | dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] 44 | docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] 45 | tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] 46 | tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] 47 | 48 | [[package]] 49 | name = "click" 50 | version = "8.0.1" 51 | description = "Composable command line interface toolkit" 52 | category = "main" 53 | optional = false 54 | python-versions = ">=3.6" 55 | 56 | [package.dependencies] 57 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 58 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} 59 | 60 | [[package]] 61 | name = "colorama" 62 | version = "0.4.4" 63 | description = "Cross-platform colored terminal text." 64 | category = "main" 65 | optional = false 66 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 67 | 68 | [[package]] 69 | name = "coverage" 70 | version = "5.5" 71 | description = "Code coverage measurement for Python" 72 | category = "dev" 73 | optional = false 74 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" 75 | 76 | [package.extras] 77 | toml = ["toml"] 78 | 79 | [[package]] 80 | name = "flask" 81 | version = "2.0.1" 82 | description = "A simple framework for building complex web applications." 83 | category = "main" 84 | optional = false 85 | python-versions = ">=3.6" 86 | 87 | [package.dependencies] 88 | click = ">=7.1.2" 89 | itsdangerous = ">=2.0" 90 | Jinja2 = ">=3.0" 91 | Werkzeug = ">=2.0" 92 | 93 | [package.extras] 94 | async = ["asgiref (>=3.2)"] 95 | dotenv = ["python-dotenv"] 96 | 97 | [[package]] 98 | name = "flask-restx" 99 | version = "0.5.0" 100 | description = "Fully featured framework for fast, easy and documented API development with Flask" 101 | category = "main" 102 | optional = false 103 | python-versions = "*" 104 | 105 | [package.dependencies] 106 | aniso8601 = {version = ">=0.82", markers = "python_version >= \"3.5\""} 107 | Flask = ">=0.8,<2.0.0 || >2.0.0" 108 | jsonschema = "*" 109 | pytz = "*" 110 | six = ">=1.3.0" 111 | werkzeug = "!=2.0.0" 112 | 113 | [package.extras] 114 | dev = ["blinker", "Faker (==2.0.0)", "mock (==3.0.5)", "pytest-benchmark (==3.2.2)", "pytest-cov (==2.7.1)", "pytest-flask (==0.15.1)", "pytest-mock (==1.10.4)", "pytest-profiling (==1.7.0)", "tzlocal", "invoke (==1.3.0)", "readme-renderer (==24.0)", "twine (==1.15.0)", "tox", "pytest (==4.6.5)", "pytest (==5.4.1)", "ossaudit", "black"] 115 | doc = ["alabaster (==0.7.12)", "Sphinx (==2.1.2)", "sphinx-issues (==1.2.0)"] 116 | test = ["blinker", "Faker (==2.0.0)", "mock (==3.0.5)", "pytest-benchmark (==3.2.2)", "pytest-cov (==2.7.1)", "pytest-flask (==0.15.1)", "pytest-mock (==1.10.4)", "pytest-profiling (==1.7.0)", "tzlocal", "invoke (==1.3.0)", "readme-renderer (==24.0)", "twine (==1.15.0)", "pytest (==4.6.5)", "pytest (==5.4.1)", "ossaudit"] 117 | 118 | [[package]] 119 | name = "importlib-metadata" 120 | version = "4.6.4" 121 | description = "Read metadata from Python packages" 122 | category = "main" 123 | optional = false 124 | python-versions = ">=3.6" 125 | 126 | [package.dependencies] 127 | typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} 128 | zipp = ">=0.5" 129 | 130 | [package.extras] 131 | docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] 132 | perf = ["ipython"] 133 | testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] 134 | 135 | [[package]] 136 | name = "isort" 137 | version = "5.9.3" 138 | description = "A Python utility / library to sort Python imports." 139 | category = "dev" 140 | optional = false 141 | python-versions = ">=3.6.1,<4.0" 142 | 143 | [package.extras] 144 | pipfile_deprecated_finder = ["pipreqs", "requirementslib"] 145 | requirements_deprecated_finder = ["pipreqs", "pip-api"] 146 | colors = ["colorama (>=0.4.3,<0.5.0)"] 147 | plugins = ["setuptools"] 148 | 149 | [[package]] 150 | name = "itsdangerous" 151 | version = "2.0.1" 152 | description = "Safely pass data to untrusted environments and back." 153 | category = "main" 154 | optional = false 155 | python-versions = ">=3.6" 156 | 157 | [[package]] 158 | name = "jinja2" 159 | version = "3.0.1" 160 | description = "A very fast and expressive template engine." 161 | category = "main" 162 | optional = false 163 | python-versions = ">=3.6" 164 | 165 | [package.dependencies] 166 | MarkupSafe = ">=2.0" 167 | 168 | [package.extras] 169 | i18n = ["Babel (>=2.7)"] 170 | 171 | [[package]] 172 | name = "jsonschema" 173 | version = "3.2.0" 174 | description = "An implementation of JSON Schema validation for Python" 175 | category = "main" 176 | optional = false 177 | python-versions = "*" 178 | 179 | [package.dependencies] 180 | attrs = ">=17.4.0" 181 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} 182 | pyrsistent = ">=0.14.0" 183 | six = ">=1.11.0" 184 | 185 | [package.extras] 186 | format = ["idna", "jsonpointer (>1.13)", "rfc3987", "strict-rfc3339", "webcolors"] 187 | format_nongpl = ["idna", "jsonpointer (>1.13)", "webcolors", "rfc3986-validator (>0.1.0)", "rfc3339-validator"] 188 | 189 | [[package]] 190 | name = "lazy-object-proxy" 191 | version = "1.6.0" 192 | description = "A fast and thorough lazy object proxy." 193 | category = "dev" 194 | optional = false 195 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" 196 | 197 | [[package]] 198 | name = "markupsafe" 199 | version = "2.0.1" 200 | description = "Safely add untrusted strings to HTML/XML markup." 201 | category = "main" 202 | optional = false 203 | python-versions = ">=3.6" 204 | 205 | [[package]] 206 | name = "mccabe" 207 | version = "0.6.1" 208 | description = "McCabe checker, plugin for flake8" 209 | category = "dev" 210 | optional = false 211 | python-versions = "*" 212 | 213 | [[package]] 214 | name = "more-itertools" 215 | version = "8.8.0" 216 | description = "More routines for operating on iterables, beyond itertools" 217 | category = "dev" 218 | optional = false 219 | python-versions = ">=3.5" 220 | 221 | [[package]] 222 | name = "mypy" 223 | version = "0.910" 224 | description = "Optional static typing for Python" 225 | category = "dev" 226 | optional = false 227 | python-versions = ">=3.5" 228 | 229 | [package.dependencies] 230 | mypy-extensions = ">=0.4.3,<0.5.0" 231 | toml = "*" 232 | typed-ast = {version = ">=1.4.0,<1.5.0", markers = "python_version < \"3.8\""} 233 | typing-extensions = ">=3.7.4" 234 | 235 | [package.extras] 236 | dmypy = ["psutil (>=4.0)"] 237 | python2 = ["typed-ast (>=1.4.0,<1.5.0)"] 238 | 239 | [[package]] 240 | name = "mypy-extensions" 241 | version = "0.4.3" 242 | description = "Experimental type system extensions for programs checked with the mypy typechecker." 243 | category = "dev" 244 | optional = false 245 | python-versions = "*" 246 | 247 | [[package]] 248 | name = "packaging" 249 | version = "21.0" 250 | description = "Core utilities for Python packages" 251 | category = "dev" 252 | optional = false 253 | python-versions = ">=3.6" 254 | 255 | [package.dependencies] 256 | pyparsing = ">=2.0.2" 257 | 258 | [[package]] 259 | name = "pluggy" 260 | version = "0.13.1" 261 | description = "plugin and hook calling mechanisms for python" 262 | category = "dev" 263 | optional = false 264 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 265 | 266 | [package.dependencies] 267 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 268 | 269 | [package.extras] 270 | dev = ["pre-commit", "tox"] 271 | 272 | [[package]] 273 | name = "py" 274 | version = "1.10.0" 275 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 276 | category = "dev" 277 | optional = false 278 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 279 | 280 | [[package]] 281 | name = "pydantic" 282 | version = "1.8.2" 283 | description = "Data validation and settings management using python 3.6 type hinting" 284 | category = "main" 285 | optional = false 286 | python-versions = ">=3.6.1" 287 | 288 | [package.dependencies] 289 | typing-extensions = ">=3.7.4.3" 290 | 291 | [package.extras] 292 | dotenv = ["python-dotenv (>=0.10.4)"] 293 | email = ["email-validator (>=1.0.3)"] 294 | 295 | [[package]] 296 | name = "pylint" 297 | version = "2.9.6" 298 | description = "python code static checker" 299 | category = "dev" 300 | optional = false 301 | python-versions = "~=3.6" 302 | 303 | [package.dependencies] 304 | astroid = ">=2.6.5,<2.7" 305 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 306 | isort = ">=4.2.5,<6" 307 | mccabe = ">=0.6,<0.7" 308 | toml = ">=0.7.1" 309 | 310 | [[package]] 311 | name = "pyparsing" 312 | version = "2.4.7" 313 | description = "Python parsing module" 314 | category = "dev" 315 | optional = false 316 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 317 | 318 | [[package]] 319 | name = "pyrsistent" 320 | version = "0.18.0" 321 | description = "Persistent/Functional/Immutable data structures" 322 | category = "main" 323 | optional = false 324 | python-versions = ">=3.6" 325 | 326 | [[package]] 327 | name = "pytest" 328 | version = "5.4.3" 329 | description = "pytest: simple powerful testing with Python" 330 | category = "dev" 331 | optional = false 332 | python-versions = ">=3.5" 333 | 334 | [package.dependencies] 335 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} 336 | attrs = ">=17.4.0" 337 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 338 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 339 | more-itertools = ">=4.0.0" 340 | packaging = "*" 341 | pluggy = ">=0.12,<1.0" 342 | py = ">=1.5.0" 343 | wcwidth = "*" 344 | 345 | [package.extras] 346 | checkqa-mypy = ["mypy (==v0.761)"] 347 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] 348 | 349 | [[package]] 350 | name = "pytz" 351 | version = "2021.1" 352 | description = "World timezone definitions, modern and historical" 353 | category = "main" 354 | optional = false 355 | python-versions = "*" 356 | 357 | [[package]] 358 | name = "six" 359 | version = "1.16.0" 360 | description = "Python 2 and 3 compatibility utilities" 361 | category = "main" 362 | optional = false 363 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 364 | 365 | [[package]] 366 | name = "toml" 367 | version = "0.10.2" 368 | description = "Python Library for Tom's Obvious, Minimal Language" 369 | category = "dev" 370 | optional = false 371 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 372 | 373 | [[package]] 374 | name = "typed-ast" 375 | version = "1.4.3" 376 | description = "a fork of Python 2 and 3 ast modules with type comment support" 377 | category = "dev" 378 | optional = false 379 | python-versions = "*" 380 | 381 | [[package]] 382 | name = "typing-extensions" 383 | version = "3.10.0.0" 384 | description = "Backported and Experimental Type Hints for Python 3.5+" 385 | category = "main" 386 | optional = false 387 | python-versions = "*" 388 | 389 | [[package]] 390 | name = "wcwidth" 391 | version = "0.2.5" 392 | description = "Measures the displayed width of unicode strings in a terminal" 393 | category = "dev" 394 | optional = false 395 | python-versions = "*" 396 | 397 | [[package]] 398 | name = "werkzeug" 399 | version = "2.0.1" 400 | description = "The comprehensive WSGI web application library." 401 | category = "main" 402 | optional = false 403 | python-versions = ">=3.6" 404 | 405 | [package.extras] 406 | watchdog = ["watchdog"] 407 | 408 | [[package]] 409 | name = "wrapt" 410 | version = "1.12.1" 411 | description = "Module for decorators, wrappers and monkey patching." 412 | category = "dev" 413 | optional = false 414 | python-versions = "*" 415 | 416 | [[package]] 417 | name = "zipp" 418 | version = "3.5.0" 419 | description = "Backport of pathlib-compatible object wrapper for zip files" 420 | category = "main" 421 | optional = false 422 | python-versions = ">=3.6" 423 | 424 | [package.extras] 425 | docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] 426 | testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] 427 | 428 | [metadata] 429 | lock-version = "1.1" 430 | python-versions = "^3.7" 431 | content-hash = "f0117bb6d617c0b57aaa17f9b379e6ea7269743c3995fbb570cd5beff15842d7" 432 | 433 | [metadata.files] 434 | aniso8601 = [ 435 | {file = "aniso8601-9.0.1-py2.py3-none-any.whl", hash = "sha256:1d2b7ef82963909e93c4f24ce48d4de9e66009a21bf1c1e1c85bdd0812fe412f"}, 436 | {file = "aniso8601-9.0.1.tar.gz", hash = "sha256:72e3117667eedf66951bb2d93f4296a56b94b078a8a95905a052611fb3f1b973"}, 437 | ] 438 | astroid = [ 439 | {file = "astroid-2.6.6-py3-none-any.whl", hash = "sha256:ab7f36e8a78b8e54a62028ba6beef7561db4cdb6f2a5009ecc44a6f42b5697ef"}, 440 | {file = "astroid-2.6.6.tar.gz", hash = "sha256:3975a0bd5373bdce166e60c851cfcbaf21ee96de80ec518c1f4cb3e94c3fb334"}, 441 | ] 442 | atomicwrites = [ 443 | {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, 444 | {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, 445 | ] 446 | attrs = [ 447 | {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, 448 | {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, 449 | ] 450 | click = [ 451 | {file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"}, 452 | {file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"}, 453 | ] 454 | colorama = [ 455 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, 456 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, 457 | ] 458 | coverage = [ 459 | {file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"}, 460 | {file = "coverage-5.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b"}, 461 | {file = "coverage-5.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669"}, 462 | {file = "coverage-5.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90"}, 463 | {file = "coverage-5.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c"}, 464 | {file = "coverage-5.5-cp27-cp27m-win32.whl", hash = "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a"}, 465 | {file = "coverage-5.5-cp27-cp27m-win_amd64.whl", hash = "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82"}, 466 | {file = "coverage-5.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905"}, 467 | {file = "coverage-5.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083"}, 468 | {file = "coverage-5.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5"}, 469 | {file = "coverage-5.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81"}, 470 | {file = "coverage-5.5-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6"}, 471 | {file = "coverage-5.5-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0"}, 472 | {file = "coverage-5.5-cp310-cp310-win_amd64.whl", hash = "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae"}, 473 | {file = "coverage-5.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb"}, 474 | {file = "coverage-5.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160"}, 475 | {file = "coverage-5.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"}, 476 | {file = "coverage-5.5-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701"}, 477 | {file = "coverage-5.5-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793"}, 478 | {file = "coverage-5.5-cp35-cp35m-win32.whl", hash = "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e"}, 479 | {file = "coverage-5.5-cp35-cp35m-win_amd64.whl", hash = "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3"}, 480 | {file = "coverage-5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066"}, 481 | {file = "coverage-5.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a"}, 482 | {file = "coverage-5.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465"}, 483 | {file = "coverage-5.5-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb"}, 484 | {file = "coverage-5.5-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821"}, 485 | {file = "coverage-5.5-cp36-cp36m-win32.whl", hash = "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45"}, 486 | {file = "coverage-5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184"}, 487 | {file = "coverage-5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a"}, 488 | {file = "coverage-5.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53"}, 489 | {file = "coverage-5.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d"}, 490 | {file = "coverage-5.5-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638"}, 491 | {file = "coverage-5.5-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3"}, 492 | {file = "coverage-5.5-cp37-cp37m-win32.whl", hash = "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a"}, 493 | {file = "coverage-5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a"}, 494 | {file = "coverage-5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6"}, 495 | {file = "coverage-5.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2"}, 496 | {file = "coverage-5.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759"}, 497 | {file = "coverage-5.5-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873"}, 498 | {file = "coverage-5.5-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a"}, 499 | {file = "coverage-5.5-cp38-cp38-win32.whl", hash = "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6"}, 500 | {file = "coverage-5.5-cp38-cp38-win_amd64.whl", hash = "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502"}, 501 | {file = "coverage-5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b"}, 502 | {file = "coverage-5.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529"}, 503 | {file = "coverage-5.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b"}, 504 | {file = "coverage-5.5-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff"}, 505 | {file = "coverage-5.5-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b"}, 506 | {file = "coverage-5.5-cp39-cp39-win32.whl", hash = "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6"}, 507 | {file = "coverage-5.5-cp39-cp39-win_amd64.whl", hash = "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03"}, 508 | {file = "coverage-5.5-pp36-none-any.whl", hash = "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079"}, 509 | {file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"}, 510 | {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, 511 | ] 512 | flask = [ 513 | {file = "Flask-2.0.1-py3-none-any.whl", hash = "sha256:a6209ca15eb63fc9385f38e452704113d679511d9574d09b2cf9183ae7d20dc9"}, 514 | {file = "Flask-2.0.1.tar.gz", hash = "sha256:1c4c257b1892aec1398784c63791cbaa43062f1f7aeb555c4da961b20ee68f55"}, 515 | ] 516 | flask-restx = [ 517 | {file = "flask-restx-0.5.0.tar.gz", hash = "sha256:7e9f7cd5e843dd653a71fafb7c8ce9d7b4fef29f982a2254b1e0ebb3fac1fe12"}, 518 | {file = "flask_restx-0.5.0-py2.py3-none-any.whl", hash = "sha256:c3c2b724e688c0a50ee5e78f2a508b7f0c34644f00f64170fa8a3d0cdc34f67a"}, 519 | ] 520 | importlib-metadata = [ 521 | {file = "importlib_metadata-4.6.4-py3-none-any.whl", hash = "sha256:ed5157fef23a4bc4594615a0dd8eba94b2bb36bf2a343fa3d8bb2fa0a62a99d5"}, 522 | {file = "importlib_metadata-4.6.4.tar.gz", hash = "sha256:7b30a78db2922d78a6f47fb30683156a14f3c6aa5cc23f77cc8967e9ab2d002f"}, 523 | ] 524 | isort = [ 525 | {file = "isort-5.9.3-py3-none-any.whl", hash = "sha256:e17d6e2b81095c9db0a03a8025a957f334d6ea30b26f9ec70805411e5c7c81f2"}, 526 | {file = "isort-5.9.3.tar.gz", hash = "sha256:9c2ea1e62d871267b78307fe511c0838ba0da28698c5732d54e2790bf3ba9899"}, 527 | ] 528 | itsdangerous = [ 529 | {file = "itsdangerous-2.0.1-py3-none-any.whl", hash = "sha256:5174094b9637652bdb841a3029700391451bd092ba3db90600dea710ba28e97c"}, 530 | {file = "itsdangerous-2.0.1.tar.gz", hash = "sha256:9e724d68fc22902a1435351f84c3fb8623f303fffcc566a4cb952df8c572cff0"}, 531 | ] 532 | jinja2 = [ 533 | {file = "Jinja2-3.0.1-py3-none-any.whl", hash = "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4"}, 534 | {file = "Jinja2-3.0.1.tar.gz", hash = "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"}, 535 | ] 536 | jsonschema = [ 537 | {file = "jsonschema-3.2.0-py2.py3-none-any.whl", hash = "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163"}, 538 | {file = "jsonschema-3.2.0.tar.gz", hash = "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a"}, 539 | ] 540 | lazy-object-proxy = [ 541 | {file = "lazy-object-proxy-1.6.0.tar.gz", hash = "sha256:489000d368377571c6f982fba6497f2aa13c6d1facc40660963da62f5c379726"}, 542 | {file = "lazy_object_proxy-1.6.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:c6938967f8528b3668622a9ed3b31d145fab161a32f5891ea7b84f6b790be05b"}, 543 | {file = "lazy_object_proxy-1.6.0-cp27-cp27m-win32.whl", hash = "sha256:ebfd274dcd5133e0afae738e6d9da4323c3eb021b3e13052d8cbd0e457b1256e"}, 544 | {file = "lazy_object_proxy-1.6.0-cp27-cp27m-win_amd64.whl", hash = "sha256:ed361bb83436f117f9917d282a456f9e5009ea12fd6de8742d1a4752c3017e93"}, 545 | {file = "lazy_object_proxy-1.6.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d900d949b707778696fdf01036f58c9876a0d8bfe116e8d220cfd4b15f14e741"}, 546 | {file = "lazy_object_proxy-1.6.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5743a5ab42ae40caa8421b320ebf3a998f89c85cdc8376d6b2e00bd12bd1b587"}, 547 | {file = "lazy_object_proxy-1.6.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:bf34e368e8dd976423396555078def5cfc3039ebc6fc06d1ae2c5a65eebbcde4"}, 548 | {file = "lazy_object_proxy-1.6.0-cp36-cp36m-win32.whl", hash = "sha256:b579f8acbf2bdd9ea200b1d5dea36abd93cabf56cf626ab9c744a432e15c815f"}, 549 | {file = "lazy_object_proxy-1.6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:4f60460e9f1eb632584c9685bccea152f4ac2130e299784dbaf9fae9f49891b3"}, 550 | {file = "lazy_object_proxy-1.6.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d7124f52f3bd259f510651450e18e0fd081ed82f3c08541dffc7b94b883aa981"}, 551 | {file = "lazy_object_proxy-1.6.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:22ddd618cefe54305df49e4c069fa65715be4ad0e78e8d252a33debf00f6ede2"}, 552 | {file = "lazy_object_proxy-1.6.0-cp37-cp37m-win32.whl", hash = "sha256:9d397bf41caad3f489e10774667310d73cb9c4258e9aed94b9ec734b34b495fd"}, 553 | {file = "lazy_object_proxy-1.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:24a5045889cc2729033b3e604d496c2b6f588c754f7a62027ad4437a7ecc4837"}, 554 | {file = "lazy_object_proxy-1.6.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:17e0967ba374fc24141738c69736da90e94419338fd4c7c7bef01ee26b339653"}, 555 | {file = "lazy_object_proxy-1.6.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:410283732af311b51b837894fa2f24f2c0039aa7f220135192b38fcc42bd43d3"}, 556 | {file = "lazy_object_proxy-1.6.0-cp38-cp38-win32.whl", hash = "sha256:85fb7608121fd5621cc4377a8961d0b32ccf84a7285b4f1d21988b2eae2868e8"}, 557 | {file = "lazy_object_proxy-1.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:d1c2676e3d840852a2de7c7d5d76407c772927addff8d742b9808fe0afccebdf"}, 558 | {file = "lazy_object_proxy-1.6.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:b865b01a2e7f96db0c5d12cfea590f98d8c5ba64ad222300d93ce6ff9138bcad"}, 559 | {file = "lazy_object_proxy-1.6.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4732c765372bd78a2d6b2150a6e99d00a78ec963375f236979c0626b97ed8e43"}, 560 | {file = "lazy_object_proxy-1.6.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:9698110e36e2df951c7c36b6729e96429c9c32b3331989ef19976592c5f3c77a"}, 561 | {file = "lazy_object_proxy-1.6.0-cp39-cp39-win32.whl", hash = "sha256:1fee665d2638491f4d6e55bd483e15ef21f6c8c2095f235fef72601021e64f61"}, 562 | {file = "lazy_object_proxy-1.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:f5144c75445ae3ca2057faac03fda5a902eff196702b0a24daf1d6ce0650514b"}, 563 | ] 564 | markupsafe = [ 565 | {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, 566 | {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, 567 | {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, 568 | {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"}, 569 | {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"}, 570 | {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"}, 571 | {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"}, 572 | {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, 573 | {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, 574 | {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, 575 | {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, 576 | {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, 577 | {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, 578 | {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"}, 579 | {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"}, 580 | {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"}, 581 | {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, 582 | {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, 583 | {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, 584 | {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"}, 585 | {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"}, 586 | {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, 587 | {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, 588 | {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, 589 | {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"}, 590 | {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"}, 591 | {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"}, 592 | {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, 593 | {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, 594 | {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"}, 595 | {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, 596 | {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, 597 | {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, 598 | {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, 599 | {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, 600 | {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, 601 | {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"}, 602 | {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"}, 603 | {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"}, 604 | {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, 605 | {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, 606 | {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, 607 | {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"}, 608 | {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"}, 609 | {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"}, 610 | {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, 611 | {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, 612 | {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, 613 | {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"}, 614 | {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"}, 615 | {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"}, 616 | {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, 617 | {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, 618 | {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, 619 | ] 620 | mccabe = [ 621 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, 622 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, 623 | ] 624 | more-itertools = [ 625 | {file = "more-itertools-8.8.0.tar.gz", hash = "sha256:83f0308e05477c68f56ea3a888172c78ed5d5b3c282addb67508e7ba6c8f813a"}, 626 | {file = "more_itertools-8.8.0-py3-none-any.whl", hash = "sha256:2cf89ec599962f2ddc4d568a05defc40e0a587fbc10d5989713638864c36be4d"}, 627 | ] 628 | mypy = [ 629 | {file = "mypy-0.910-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457"}, 630 | {file = "mypy-0.910-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b94e4b785e304a04ea0828759172a15add27088520dc7e49ceade7834275bedb"}, 631 | {file = "mypy-0.910-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:088cd9c7904b4ad80bec811053272986611b84221835e079be5bcad029e79dd9"}, 632 | {file = "mypy-0.910-cp35-cp35m-win_amd64.whl", hash = "sha256:adaeee09bfde366d2c13fe6093a7df5df83c9a2ba98638c7d76b010694db760e"}, 633 | {file = "mypy-0.910-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ecd2c3fe726758037234c93df7e98deb257fd15c24c9180dacf1ef829da5f921"}, 634 | {file = "mypy-0.910-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d9dd839eb0dc1bbe866a288ba3c1afc33a202015d2ad83b31e875b5905a079b6"}, 635 | {file = "mypy-0.910-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:3e382b29f8e0ccf19a2df2b29a167591245df90c0b5a2542249873b5c1d78212"}, 636 | {file = "mypy-0.910-cp36-cp36m-win_amd64.whl", hash = "sha256:53fd2eb27a8ee2892614370896956af2ff61254c275aaee4c230ae771cadd885"}, 637 | {file = "mypy-0.910-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b6fb13123aeef4a3abbcfd7e71773ff3ff1526a7d3dc538f3929a49b42be03f0"}, 638 | {file = "mypy-0.910-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e4dab234478e3bd3ce83bac4193b2ecd9cf94e720ddd95ce69840273bf44f6de"}, 639 | {file = "mypy-0.910-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:7df1ead20c81371ccd6091fa3e2878559b5c4d4caadaf1a484cf88d93ca06703"}, 640 | {file = "mypy-0.910-cp37-cp37m-win_amd64.whl", hash = "sha256:0aadfb2d3935988ec3815952e44058a3100499f5be5b28c34ac9d79f002a4a9a"}, 641 | {file = "mypy-0.910-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ec4e0cd079db280b6bdabdc807047ff3e199f334050db5cbb91ba3e959a67504"}, 642 | {file = "mypy-0.910-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:119bed3832d961f3a880787bf621634ba042cb8dc850a7429f643508eeac97b9"}, 643 | {file = "mypy-0.910-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:866c41f28cee548475f146aa4d39a51cf3b6a84246969f3759cb3e9c742fc072"}, 644 | {file = "mypy-0.910-cp38-cp38-win_amd64.whl", hash = "sha256:ceb6e0a6e27fb364fb3853389607cf7eb3a126ad335790fa1e14ed02fba50811"}, 645 | {file = "mypy-0.910-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a85e280d4d217150ce8cb1a6dddffd14e753a4e0c3cf90baabb32cefa41b59e"}, 646 | {file = "mypy-0.910-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:42c266ced41b65ed40a282c575705325fa7991af370036d3f134518336636f5b"}, 647 | {file = "mypy-0.910-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:3c4b8ca36877fc75339253721f69603a9c7fdb5d4d5a95a1a1b899d8b86a4de2"}, 648 | {file = "mypy-0.910-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:c0df2d30ed496a08de5daed2a9ea807d07c21ae0ab23acf541ab88c24b26ab97"}, 649 | {file = "mypy-0.910-cp39-cp39-win_amd64.whl", hash = "sha256:c6c2602dffb74867498f86e6129fd52a2770c48b7cd3ece77ada4fa38f94eba8"}, 650 | {file = "mypy-0.910-py3-none-any.whl", hash = "sha256:ef565033fa5a958e62796867b1df10c40263ea9ded87164d67572834e57a174d"}, 651 | {file = "mypy-0.910.tar.gz", hash = "sha256:704098302473cb31a218f1775a873b376b30b4c18229421e9e9dc8916fd16150"}, 652 | ] 653 | mypy-extensions = [ 654 | {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, 655 | {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, 656 | ] 657 | packaging = [ 658 | {file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"}, 659 | {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"}, 660 | ] 661 | pluggy = [ 662 | {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, 663 | {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, 664 | ] 665 | py = [ 666 | {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, 667 | {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, 668 | ] 669 | pydantic = [ 670 | {file = "pydantic-1.8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739"}, 671 | {file = "pydantic-1.8.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4"}, 672 | {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:589eb6cd6361e8ac341db97602eb7f354551482368a37f4fd086c0733548308e"}, 673 | {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:10e5622224245941efc193ad1d159887872776df7a8fd592ed746aa25d071840"}, 674 | {file = "pydantic-1.8.2-cp36-cp36m-win_amd64.whl", hash = "sha256:99a9fc39470010c45c161a1dc584997f1feb13f689ecf645f59bb4ba623e586b"}, 675 | {file = "pydantic-1.8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a83db7205f60c6a86f2c44a61791d993dff4b73135df1973ecd9eed5ea0bda20"}, 676 | {file = "pydantic-1.8.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:41b542c0b3c42dc17da70554bc6f38cbc30d7066d2c2815a94499b5684582ecb"}, 677 | {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:ea5cb40a3b23b3265f6325727ddfc45141b08ed665458be8c6285e7b85bd73a1"}, 678 | {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:18b5ea242dd3e62dbf89b2b0ec9ba6c7b5abaf6af85b95a97b00279f65845a23"}, 679 | {file = "pydantic-1.8.2-cp37-cp37m-win_amd64.whl", hash = "sha256:234a6c19f1c14e25e362cb05c68afb7f183eb931dd3cd4605eafff055ebbf287"}, 680 | {file = "pydantic-1.8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:021ea0e4133e8c824775a0cfe098677acf6fa5a3cbf9206a376eed3fc09302cd"}, 681 | {file = "pydantic-1.8.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e710876437bc07bd414ff453ac8ec63d219e7690128d925c6e82889d674bb505"}, 682 | {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:ac8eed4ca3bd3aadc58a13c2aa93cd8a884bcf21cb019f8cfecaae3b6ce3746e"}, 683 | {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:4a03cbbe743e9c7247ceae6f0d8898f7a64bb65800a45cbdc52d65e370570820"}, 684 | {file = "pydantic-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:8621559dcf5afacf0069ed194278f35c255dc1a1385c28b32dd6c110fd6531b3"}, 685 | {file = "pydantic-1.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8b223557f9510cf0bfd8b01316bf6dd281cf41826607eada99662f5e4963f316"}, 686 | {file = "pydantic-1.8.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:244ad78eeb388a43b0c927e74d3af78008e944074b7d0f4f696ddd5b2af43c62"}, 687 | {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:05ef5246a7ffd2ce12a619cbb29f3307b7c4509307b1b49f456657b43529dc6f"}, 688 | {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:54cd5121383f4a461ff7644c7ca20c0419d58052db70d8791eacbbe31528916b"}, 689 | {file = "pydantic-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:4be75bebf676a5f0f87937c6ddb061fa39cbea067240d98e298508c1bda6f3f3"}, 690 | {file = "pydantic-1.8.2-py3-none-any.whl", hash = "sha256:fec866a0b59f372b7e776f2d7308511784dace622e0992a0b59ea3ccee0ae833"}, 691 | {file = "pydantic-1.8.2.tar.gz", hash = "sha256:26464e57ccaafe72b7ad156fdaa4e9b9ef051f69e175dbbb463283000c05ab7b"}, 692 | ] 693 | pylint = [ 694 | {file = "pylint-2.9.6-py3-none-any.whl", hash = "sha256:2e1a0eb2e8ab41d6b5dbada87f066492bb1557b12b76c47c2ee8aa8a11186594"}, 695 | {file = "pylint-2.9.6.tar.gz", hash = "sha256:8b838c8983ee1904b2de66cce9d0b96649a91901350e956d78f289c3bc87b48e"}, 696 | ] 697 | pyparsing = [ 698 | {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, 699 | {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, 700 | ] 701 | pyrsistent = [ 702 | {file = "pyrsistent-0.18.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f4c8cabb46ff8e5d61f56a037974228e978f26bfefce4f61a4b1ac0ba7a2ab72"}, 703 | {file = "pyrsistent-0.18.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:da6e5e818d18459fa46fac0a4a4e543507fe1110e808101277c5a2b5bab0cd2d"}, 704 | {file = "pyrsistent-0.18.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5e4395bbf841693eaebaa5bb5c8f5cdbb1d139e07c975c682ec4e4f8126e03d2"}, 705 | {file = "pyrsistent-0.18.0-cp36-cp36m-win32.whl", hash = "sha256:527be2bfa8dc80f6f8ddd65242ba476a6c4fb4e3aedbf281dfbac1b1ed4165b1"}, 706 | {file = "pyrsistent-0.18.0-cp36-cp36m-win_amd64.whl", hash = "sha256:2aaf19dc8ce517a8653746d98e962ef480ff34b6bc563fc067be6401ffb457c7"}, 707 | {file = "pyrsistent-0.18.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58a70d93fb79dc585b21f9d72487b929a6fe58da0754fa4cb9f279bb92369396"}, 708 | {file = "pyrsistent-0.18.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:4916c10896721e472ee12c95cdc2891ce5890898d2f9907b1b4ae0f53588b710"}, 709 | {file = "pyrsistent-0.18.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:73ff61b1411e3fb0ba144b8f08d6749749775fe89688093e1efef9839d2dcc35"}, 710 | {file = "pyrsistent-0.18.0-cp37-cp37m-win32.whl", hash = "sha256:b29b869cf58412ca5738d23691e96d8aff535e17390128a1a52717c9a109da4f"}, 711 | {file = "pyrsistent-0.18.0-cp37-cp37m-win_amd64.whl", hash = "sha256:097b96f129dd36a8c9e33594e7ebb151b1515eb52cceb08474c10a5479e799f2"}, 712 | {file = "pyrsistent-0.18.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:772e94c2c6864f2cd2ffbe58bb3bdefbe2a32afa0acb1a77e472aac831f83427"}, 713 | {file = "pyrsistent-0.18.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:c1a9ff320fa699337e05edcaae79ef8c2880b52720bc031b219e5b5008ebbdef"}, 714 | {file = "pyrsistent-0.18.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:cd3caef37a415fd0dae6148a1b6957a8c5f275a62cca02e18474608cb263640c"}, 715 | {file = "pyrsistent-0.18.0-cp38-cp38-win32.whl", hash = "sha256:e79d94ca58fcafef6395f6352383fa1a76922268fa02caa2272fff501c2fdc78"}, 716 | {file = "pyrsistent-0.18.0-cp38-cp38-win_amd64.whl", hash = "sha256:a0c772d791c38bbc77be659af29bb14c38ced151433592e326361610250c605b"}, 717 | {file = "pyrsistent-0.18.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d5ec194c9c573aafaceebf05fc400656722793dac57f254cd4741f3c27ae57b4"}, 718 | {file = "pyrsistent-0.18.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:6b5eed00e597b5b5773b4ca30bd48a5774ef1e96f2a45d105db5b4ebb4bca680"}, 719 | {file = "pyrsistent-0.18.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:48578680353f41dca1ca3dc48629fb77dfc745128b56fc01096b2530c13fd426"}, 720 | {file = "pyrsistent-0.18.0-cp39-cp39-win32.whl", hash = "sha256:f3ef98d7b76da5eb19c37fda834d50262ff9167c65658d1d8f974d2e4d90676b"}, 721 | {file = "pyrsistent-0.18.0-cp39-cp39-win_amd64.whl", hash = "sha256:404e1f1d254d314d55adb8d87f4f465c8693d6f902f67eb6ef5b4526dc58e6ea"}, 722 | {file = "pyrsistent-0.18.0.tar.gz", hash = "sha256:773c781216f8c2900b42a7b638d5b517bb134ae1acbebe4d1e8f1f41ea60eb4b"}, 723 | ] 724 | pytest = [ 725 | {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"}, 726 | {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"}, 727 | ] 728 | pytz = [ 729 | {file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"}, 730 | {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"}, 731 | ] 732 | six = [ 733 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 734 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 735 | ] 736 | toml = [ 737 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 738 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 739 | ] 740 | typed-ast = [ 741 | {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, 742 | {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, 743 | {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"}, 744 | {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"}, 745 | {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"}, 746 | {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"}, 747 | {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"}, 748 | {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"}, 749 | {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"}, 750 | {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"}, 751 | {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"}, 752 | {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"}, 753 | {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"}, 754 | {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"}, 755 | {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"}, 756 | {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"}, 757 | {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"}, 758 | {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"}, 759 | {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"}, 760 | {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"}, 761 | {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"}, 762 | {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"}, 763 | {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"}, 764 | {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"}, 765 | {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"}, 766 | {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"}, 767 | {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"}, 768 | {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"}, 769 | {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, 770 | {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, 771 | ] 772 | typing-extensions = [ 773 | {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"}, 774 | {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"}, 775 | {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"}, 776 | ] 777 | wcwidth = [ 778 | {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, 779 | {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, 780 | ] 781 | werkzeug = [ 782 | {file = "Werkzeug-2.0.1-py3-none-any.whl", hash = "sha256:6c1ec500dcdba0baa27600f6a22f6333d8b662d22027ff9f6202e3367413caa8"}, 783 | {file = "Werkzeug-2.0.1.tar.gz", hash = "sha256:1de1db30d010ff1af14a009224ec49ab2329ad2cde454c8a708130642d579c42"}, 784 | ] 785 | wrapt = [ 786 | {file = "wrapt-1.12.1.tar.gz", hash = "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7"}, 787 | ] 788 | zipp = [ 789 | {file = "zipp-3.5.0-py3-none-any.whl", hash = "sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3"}, 790 | {file = "zipp-3.5.0.tar.gz", hash = "sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4"}, 791 | ] 792 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "flask-fastx" 3 | version = "0.1.4" 4 | description = "Flask-Fastx is a Fast API style support for Flask. It Gives you MyPy types with the flexibility of flask." 5 | authors = ["nadaabdelmaboud seifashraf1 ahmedihabb2 omargamal253"] 6 | readme = "README.rst" 7 | homepage = "https://github.com/tactful-ai/flask-faster-api" 8 | repository = "https://github.com/tactful-ai/flask-faster-api" 9 | keywords = ["flask", "flask-restx","Mypy"] 10 | license = "MIT" 11 | 12 | [tool.poetry.dependencies] 13 | python = "^3.7" 14 | flask-restx = "^0.5.0" 15 | pydantic = "^1.8.2" 16 | 17 | [tool.poetry.dev-dependencies] 18 | pytest = "^5.2" 19 | coverage = "^5.5" 20 | pylint = "^2.9.6" 21 | mypy = "^0.910" 22 | 23 | [build-system] 24 | requires = ["poetry-core>=1.0.0"] 25 | build-backend = "poetry.core.masonry.api" 26 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aniso8601==9.0.1; python_version >= "3.5" 2 | astroid==2.6.6; python_version >= "3.6" and python_version < "4.0" 3 | atomicwrites==1.4.0; python_version >= "3.5" and python_full_version < "3.0.0" and sys_platform == "win32" or sys_platform == "win32" and python_version >= "3.5" and python_full_version >= "3.4.0" 4 | attrs==21.2.0; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.5" 5 | click==8.0.1; python_version >= "3.6" 6 | colorama==0.4.4; python_version >= "3.6" and python_full_version < "3.0.0" and sys_platform == "win32" and python_version < "4.0" and platform_system == "Windows" or sys_platform == "win32" and python_version >= "3.6" and python_full_version >= "3.5.0" and python_version < "4.0" and platform_system == "Windows" 7 | coverage==5.5; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0" and python_version < "4") 8 | flask-restx==0.5.0 9 | flask==2.0.1; python_version >= "3.6" 10 | importlib-metadata==4.6.4; python_version < "3.8" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.8" or python_full_version >= "3.4.0" and python_version >= "3.6" and python_version < "3.8") 11 | isort==5.9.3; python_full_version >= "3.6.1" and python_version < "4.0" and python_version >= "3.6" 12 | itsdangerous==2.0.1; python_version >= "3.6" 13 | jinja2==3.0.1; python_version >= "3.6" 14 | jsonschema==3.2.0 15 | lazy-object-proxy==1.6.0; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.6.0" 16 | markupsafe==2.0.1; python_version >= "3.6" 17 | mccabe==0.6.1; python_version >= "3.6" and python_version < "4.0" 18 | more-itertools==8.8.0; python_version >= "3.5" 19 | mypy-extensions==0.4.3; python_version >= "3.5" 20 | mypy==0.910; python_version >= "3.5" 21 | packaging==21.0; python_version >= "3.6" 22 | pluggy==0.13.1; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.5" 23 | py==1.10.0; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.5" 24 | pydantic==1.8.2; python_full_version >= "3.6.1" 25 | pylint==2.9.6; python_version >= "3.6" and python_version < "4.0" 26 | pyparsing==2.4.7; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6" 27 | pyrsistent==0.18.0; python_version >= "3.6" 28 | pytest==5.4.3; python_version >= "3.5" 29 | pytz==2021.1 30 | six==1.16.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" 31 | toml==0.10.2; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.3.0" 32 | typed-ast==1.4.3; python_version < "3.8" and python_version >= "3.6" and implementation_name == "cpython" 33 | typing-extensions==3.10.0.0; python_full_version >= "3.6.1" and python_version >= "3.6" and python_version < "3.8" 34 | wcwidth==0.2.5; python_version >= "3.5" 35 | werkzeug==2.0.1; python_version >= "3.6" 36 | wrapt==1.12.1; python_version >= "3.6" and python_version < "4.0" 37 | zipp==3.5.0; python_version < "3.8" and python_version >= "3.6" 38 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tactful-ai/flask-faster-api/60a3bf6fe5ff240bdc2795951923246e2f287f6d/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_flask_fastx.py: -------------------------------------------------------------------------------- 1 | from flask_fastx import __version__ 2 | 3 | 4 | def test_version(): 5 | assert __version__ == '0.1.0' 6 | -------------------------------------------------------------------------------- /tests/test_model.py: -------------------------------------------------------------------------------- 1 | # This is the unit test of the create_model function 2 | # Author: Seif Ashraf 3 | 4 | # Testing Methodology 5 | # send return types example as a dict to the function to simulate the output of the annotations function 6 | # let create_model() process input and get its output 7 | # compare its output types with a sample model 8 | # NOTE: typing.List needs comparing its type of fields.List with the create_model output type cuz idk why 9 | 10 | from flask_fastx import * 11 | from flask_fastx.model_api import create_model 12 | import unittest 13 | from utils import * 14 | 15 | 16 | # the unit testing function 17 | 18 | descriptions = dict() 19 | 20 | 21 | class TestModel(unittest.TestCase): 22 | def test_one(self): 23 | self.assertEqual(ModelMatcher( 24 | create_model(input, descriptions)), model) 25 | 26 | 27 | if __name__ == '__main__': 28 | 29 | unittest.main() 30 | -------------------------------------------------------------------------------- /tests/test_parser.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from inspect import Parameter 3 | from typing import List, Tuple 4 | from flask_fastx.params import Query, Body, Header, File 5 | from werkzeug.datastructures import FileStorage 6 | from flask_fastx.parser_api import get_param_location, get_list_type, get_literal_tuple, get_param_type 7 | try: 8 | from typing import Literal 9 | except ImportError: 10 | from typing_extensions import Literal 11 | 12 | 13 | parameters = { 14 | "id": Parameter('id', Parameter.KEYWORD_ONLY, default=Query(None), annotation=int), 15 | "name": Parameter('name', Parameter.KEYWORD_ONLY, default=Query(None), annotation=str), 16 | "teachers": Parameter('teachers', Parameter.KEYWORD_ONLY, default=Query(None), annotation=List[str]), 17 | "students_id": Parameter('students_id', Parameter.KEYWORD_ONLY, default=Body(None), annotation=List[int]), 18 | "option1": Parameter('option1', Parameter.KEYWORD_ONLY, default=Query(None), annotation=Literal[1, 5, 20]), 19 | "finish": Parameter('finish', Parameter.KEYWORD_ONLY, default=Query(None), annotation=bool), 20 | "duration": Parameter('duration', Parameter.KEYWORD_ONLY, default=Query(None), annotation=float), 21 | "student_data": Parameter('student_data', Parameter.KEYWORD_ONLY, default=Body(None), annotation=dict), 22 | "file1": Parameter('file1', Parameter.KEYWORD_ONLY, default=File(None), annotation=FileStorage), 23 | "option2": Parameter('option2', Parameter.KEYWORD_ONLY, default=Header(None), 24 | annotation=Literal["course1", "course2", "course3"]) 25 | } 26 | 27 | # test sub methods which used for create parser 28 | # get_param_type() 29 | # get_param_location() 30 | # get_literal_tuple() 31 | # get_list_type() 32 | 33 | 34 | class TestParser(unittest.TestCase): 35 | def test_param_type(self): 36 | param_type = get_param_type(parameters["id"]) 37 | self.assertEqual(param_type, int) 38 | 39 | param_type = get_param_type(parameters["teachers"]) 40 | self.assertEqual(param_type, List[str]) 41 | 42 | param_type = get_param_type(parameters["option1"]) 43 | self.assertEqual(param_type, Literal[1, 5, 20]) 44 | 45 | param_type = get_param_type(parameters["student_data"]) 46 | self.assertEqual(param_type, dict) 47 | 48 | param_type = get_param_type(parameters["file1"]) 49 | self.assertEqual(param_type, FileStorage) 50 | 51 | def test_param_location(self): 52 | location = get_param_location(parameters["id"]) 53 | self.assertIsNotNone(location) 54 | self.assertNotEqual(location, 'form') 55 | self.assertEqual(location, 'args') 56 | 57 | location = get_param_location(parameters["students_id"]) 58 | self.assertIsNotNone(location) 59 | self.assertNotEqual(location, 'headers') 60 | self.assertEqual(location, 'json') 61 | 62 | location = get_param_location(parameters["option2"]) 63 | self.assertNotEqual(location, 'args') 64 | self.assertEqual(location, 'headers') 65 | 66 | location = get_param_location(parameters["name"]) 67 | self.assertNotEqual(location, 'headers') 68 | self.assertEqual(location, 'args') 69 | 70 | location = get_param_location(parameters["file1"]) 71 | self.assertEqual(location, 'files') 72 | 73 | def test_literal_param(self): 74 | res = get_literal_tuple(parameters["option1"].annotation) 75 | self.assertIsNotNone(res) 76 | self.assertNotEqual(res, tuple(("1", "5", "50"))) 77 | self.assertEqual(res, tuple((1, 5, 20))) 78 | 79 | res = get_literal_tuple(parameters["option2"].annotation) 80 | self.assertIsNotNone(res) 81 | self.assertNotEqual(res, tuple(("xx", "yy", "zz"))) 82 | self.assertNotEqual(res, tuple((55, -8888))) 83 | self.assertEqual(res, tuple(("course1", "course2", "course3"))) 84 | 85 | def test_list_typeof(self): 86 | type_ = get_list_type(parameters["teachers"].annotation) 87 | self.assertNotEqual(type_, int) 88 | self.assertNotEqual(type_, complex) 89 | self.assertIsNotNone(type_) 90 | self.assertEqual(type_, str) 91 | 92 | type_ = get_list_type(parameters["students_id"].annotation) 93 | self.assertNotEqual(type_, str) 94 | self.assertNotEqual(type_, float) 95 | self.assertIsNotNone(type_) 96 | self.assertEqual(type_, int) 97 | 98 | 99 | if __name__ == '__main__': 100 | unittest.main() 101 | -------------------------------------------------------------------------------- /tests/utils.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List, Optional, OrderedDict, get_type_hints 2 | from flask_restx import fields # type: ignore 3 | from datetime import date 4 | 5 | # class for the model model to match it to the model generated 6 | 7 | 8 | class ModelMatcher: 9 | model: dict 10 | 11 | def __init__(self, model): 12 | self.model = model 13 | 14 | def __eq__(self, other): 15 | 16 | return type(self.model['id']) == other['id'] and \ 17 | type(self.model['title']) == other['title'] and \ 18 | type(self.model['duration']) == other['duration'] and \ 19 | type(self.model['teachers']) == type(other['teachers']) and \ 20 | type(self.model['intlist']) == type(other['intlist']) and \ 21 | type(self.model['bool']) == other['bool'] and \ 22 | type(self.model['float']) == other['float'] and \ 23 | type(self.model['dict']) == other['dict'] 24 | 25 | 26 | model = { 27 | 'id': fields.Integer, 28 | 'title': fields.String, 29 | 'duration': fields.Integer, 30 | 'teachers': fields.List(fields.String), 31 | 'intlist': fields.List(fields.Integer), 32 | 'bool': fields.Boolean, 33 | 'float': fields.Float, 34 | 'dict': fields.Raw, 35 | } 36 | 37 | 38 | input = { 39 | 'id': "", 40 | 'title': "", 41 | 'duration': "", 42 | 'teachers': "typing.List[str]", 43 | 'intlist': "typing.List[int]", 44 | 'bool': "", 45 | 'float': "", 46 | 'dict': "", 47 | } 48 | -------------------------------------------------------------------------------- /verify.sh: -------------------------------------------------------------------------------- 1 | #verify-script 2 | clear 3 | 4 | set -e 5 | echo "starting verify with pylint and mypy... "; 6 | 7 | 8 | # -------------------------- 9 | mypy "./flask_fastx" ; 10 | pylint -d R0801 "./flask_fastx"; 11 | 12 | 13 | 14 | echo "end verify. " 15 | --------------------------------------------------------------------------------