mirror of
https://github.com/fastapi-admin/fastapi-admin.git
synced 2025-08-14 10:47:30 +08:00
add Makefile
update cli
This commit is contained in:
@ -7,6 +7,7 @@ ChangeLog
|
||||
0.2.6
|
||||
-----
|
||||
- Fix createsuperuser error.
|
||||
- Update cli.
|
||||
|
||||
0.2.5
|
||||
-----
|
||||
|
42
Makefile
Normal file
42
Makefile
Normal file
@ -0,0 +1,42 @@
|
||||
checkfiles = fastapi_admin/ examples/ tests/
|
||||
black_opts = -l 100 -t py38
|
||||
py_warn = PYTHONDEVMODE=1
|
||||
|
||||
help:
|
||||
@echo "FastAPI-Admin development makefile"
|
||||
@echo
|
||||
@echo "usage: make <target>"
|
||||
@echo "Targets:"
|
||||
@echo " deps Ensure dev/test dependencies are installed"
|
||||
@echo " check Checks that build is sane"
|
||||
@echo " lint Reports all linter violations"
|
||||
@echo " test Runs all tests"
|
||||
@echo " style Auto-formats the code"
|
||||
|
||||
deps:
|
||||
@pip install -r requirements-dev.txt
|
||||
|
||||
style: deps
|
||||
isort -rc $(checkfiles)
|
||||
black $(black_opts) $(checkfiles)
|
||||
|
||||
check: deps
|
||||
ifneq ($(shell which black),)
|
||||
black --check $(black_opts) $(checkfiles) || (echo "Please run 'make style' to auto-fix style issues" && false)
|
||||
endif
|
||||
flake8 $(checkfiles)
|
||||
mypy $(checkfiles)
|
||||
pylint -d C,W,R $(checkfiles)
|
||||
bandit -r $(checkfiles)
|
||||
python setup.py check -mrs
|
||||
|
||||
test: deps
|
||||
$(py_warn) py.test
|
||||
|
||||
publish: deps
|
||||
rm -fR dist/
|
||||
python setup.py sdist
|
||||
twine upload dist/*
|
||||
|
||||
ci:
|
||||
@act -P ubuntu-latest=nektos/act-environments-ubuntu:18.04 -b
|
@ -1,3 +0,0 @@
|
||||
|
||||
|
||||
|
||||
|
@ -9,10 +9,7 @@ class ProductType(EnumMixin, IntEnum):
|
||||
|
||||
@classmethod
|
||||
def choices(cls):
|
||||
return {
|
||||
cls.article: 'Article',
|
||||
cls.page: 'Page'
|
||||
}
|
||||
return {cls.article: "Article", cls.page: "Page"}
|
||||
|
||||
|
||||
class Status(EnumMixin, IntEnum):
|
||||
@ -21,7 +18,4 @@ class Status(EnumMixin, IntEnum):
|
||||
|
||||
@classmethod
|
||||
def choices(cls):
|
||||
return {
|
||||
cls.on: 'On',
|
||||
cls.off: 'Off'
|
||||
}
|
||||
return {cls.on: "On", cls.off: "Off"}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import os
|
||||
|
||||
import uvicorn
|
||||
from fastapi import FastAPI, Depends
|
||||
from fastapi import Depends, FastAPI
|
||||
from starlette.middleware.cors import CORSMiddleware
|
||||
from starlette.templating import Jinja2Templates
|
||||
from tortoise.contrib.fastapi import register_tortoise
|
||||
@ -13,53 +13,42 @@ from fastapi_admin.schemas import BulkIn
|
||||
from fastapi_admin.site import Site
|
||||
|
||||
TORTOISE_ORM = {
|
||||
'connections': {
|
||||
'default': os.getenv('DATABASE_URL')
|
||||
},
|
||||
'apps': {
|
||||
'models': {
|
||||
'models': ['examples.models', 'fastapi_admin.models'],
|
||||
'default_connection': 'default',
|
||||
"connections": {"default": os.getenv("DATABASE_URL")},
|
||||
"apps": {
|
||||
"models": {
|
||||
"models": ["examples.models", "fastapi_admin.models"],
|
||||
"default_connection": "default",
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
templates = Jinja2Templates(directory='examples/templates')
|
||||
templates = Jinja2Templates(directory="examples/templates")
|
||||
|
||||
|
||||
@admin_app.post(
|
||||
'/rest/{resource}/bulk/test_bulk'
|
||||
)
|
||||
async def test_bulk(
|
||||
bulk_in: BulkIn,
|
||||
model=Depends(get_model)
|
||||
):
|
||||
@admin_app.post("/rest/{resource}/bulk/test_bulk")
|
||||
async def test_bulk(bulk_in: BulkIn, model=Depends(get_model)):
|
||||
qs = model.filter(pk__in=bulk_in.pk_list)
|
||||
pydantic = pydantic_queryset_creator(model)
|
||||
ret = await pydantic.from_queryset(qs)
|
||||
return ret.dict()
|
||||
|
||||
|
||||
@admin_app.get(
|
||||
'/home',
|
||||
)
|
||||
@admin_app.get("/home",)
|
||||
async def home():
|
||||
return {
|
||||
'html': templates.get_template('home.html').render()
|
||||
}
|
||||
return {"html": templates.get_template("home.html").render()}
|
||||
|
||||
|
||||
def create_app():
|
||||
fast_app = FastAPI(debug=False)
|
||||
register_tortoise(fast_app, config=TORTOISE_ORM, generate_schemas=True)
|
||||
fast_app.mount('/admin', admin_app)
|
||||
fast_app.mount("/admin", admin_app)
|
||||
|
||||
fast_app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=['*'],
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=['*'],
|
||||
allow_headers=['*'],
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
return fast_app
|
||||
@ -68,25 +57,25 @@ def create_app():
|
||||
app = create_app()
|
||||
|
||||
|
||||
@app.on_event('startup')
|
||||
@app.on_event("startup")
|
||||
async def start_up():
|
||||
admin_app.debug = False
|
||||
admin_app.init(
|
||||
user_model='User',
|
||||
tortoise_app='models',
|
||||
admin_secret='test',
|
||||
user_model="User",
|
||||
tortoise_app="models",
|
||||
admin_secret="test",
|
||||
permission=True,
|
||||
site=Site(
|
||||
name='FastAPI-Admin DEMO',
|
||||
logo='https://github.com/long2ice/fastapi-admin/raw/master/front/static/img/logo.png',
|
||||
login_footer='FASTAPI ADMIN - FastAPI Admin Dashboard',
|
||||
login_description='FastAPI Admin Dashboard',
|
||||
locale='en-US',
|
||||
name="FastAPI-Admin DEMO",
|
||||
logo="https://github.com/long2ice/fastapi-admin/raw/master/front/static/img/logo.png",
|
||||
login_footer="FASTAPI ADMIN - FastAPI Admin Dashboard",
|
||||
login_description="FastAPI Admin Dashboard",
|
||||
locale="en-US",
|
||||
locale_switcher=True,
|
||||
theme_switcher=True,
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
uvicorn.run('main:app', port=8000, debug=False, reload=False, lifespan='on')
|
||||
if __name__ == "__main__":
|
||||
uvicorn.run("main:app", port=8000, debug=False, reload=False, lifespan="on")
|
||||
|
@ -1,21 +1,22 @@
|
||||
import datetime
|
||||
|
||||
from tortoise import fields, Model
|
||||
from tortoise import Model, fields
|
||||
|
||||
from fastapi_admin.models import User as AdminUser
|
||||
|
||||
from .enums import ProductType, Status
|
||||
|
||||
|
||||
class User(AdminUser):
|
||||
last_login = fields.DatetimeField(description='Last Login', default=datetime.datetime.now)
|
||||
is_active = fields.BooleanField(default=True, description='Is Active')
|
||||
is_superuser = fields.BooleanField(default=False, description='Is SuperUser')
|
||||
avatar = fields.CharField(max_length=200, default='')
|
||||
intro = fields.TextField(default='')
|
||||
last_login = fields.DatetimeField(description="Last Login", default=datetime.datetime.now)
|
||||
is_active = fields.BooleanField(default=True, description="Is Active")
|
||||
is_superuser = fields.BooleanField(default=False, description="Is SuperUser")
|
||||
avatar = fields.CharField(max_length=200, default="")
|
||||
intro = fields.TextField(default="")
|
||||
created_at = fields.DatetimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.pk}#{self.username}'
|
||||
return f"{self.pk}#{self.username}"
|
||||
|
||||
|
||||
class Category(Model):
|
||||
@ -24,22 +25,22 @@ class Category(Model):
|
||||
created_at = fields.DatetimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.pk}#{self.name}'
|
||||
return f"{self.pk}#{self.name}"
|
||||
|
||||
|
||||
class Product(Model):
|
||||
categories = fields.ManyToManyField('models.Category')
|
||||
categories = fields.ManyToManyField("models.Category")
|
||||
name = fields.CharField(max_length=50)
|
||||
view_num = fields.IntField(description='View Num')
|
||||
view_num = fields.IntField(description="View Num")
|
||||
sort = fields.IntField()
|
||||
is_reviewed = fields.BooleanField(description='Is Reviewed')
|
||||
type = fields.IntEnumField(ProductType, description='Product Type')
|
||||
is_reviewed = fields.BooleanField(description="Is Reviewed")
|
||||
type = fields.IntEnumField(ProductType, description="Product Type")
|
||||
image = fields.CharField(max_length=200)
|
||||
body = fields.TextField()
|
||||
created_at = fields.DatetimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.pk}#{self.name}'
|
||||
return f"{self.pk}#{self.name}"
|
||||
|
||||
|
||||
class Config(Model):
|
||||
@ -49,4 +50,4 @@ class Config(Model):
|
||||
status: Status = fields.IntEnumField(Status, default=Status.on)
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.pk}#{self.label}'
|
||||
return f"{self.pk}#{self.label}"
|
||||
|
@ -1,3 +1,3 @@
|
||||
from . import routes
|
||||
|
||||
__version__ = '0.2.5'
|
||||
__version__ = "0.2.5"
|
||||
|
@ -44,62 +44,60 @@ async def register_permissions(args):
|
||||
|
||||
if args.clean:
|
||||
await Permission.all().delete()
|
||||
Logger.waring('Cleaned all permissions success.')
|
||||
models = Tortoise.apps.get('models').keys()
|
||||
Logger.waring("Cleaned all permissions success.")
|
||||
models = Tortoise.apps.get("models").keys()
|
||||
models = list(models)
|
||||
for model in models:
|
||||
for action in enums.PermissionAction:
|
||||
label = f'{enums.PermissionAction.choices().get(action)} {model}'
|
||||
defaults = dict(
|
||||
label=label,
|
||||
model=model,
|
||||
action=action,
|
||||
)
|
||||
_, created = await Permission.get_or_create(
|
||||
**defaults,
|
||||
)
|
||||
label = f"{enums.PermissionAction.choices().get(action)} {model}"
|
||||
defaults = dict(label=label, model=model, action=action,)
|
||||
_, created = await Permission.get_or_create(**defaults,)
|
||||
if created:
|
||||
Logger.success(f'Create permission {label} success.')
|
||||
Logger.success(f"Create permission {label} success.")
|
||||
|
||||
|
||||
async def createsuperuser(args):
|
||||
await init_tortoise(args)
|
||||
|
||||
user_model = Tortoise.apps.get('models').get(args.user_model)
|
||||
user_model = Tortoise.apps.get("models").get(args.user_model)
|
||||
prompt = PromptSession()
|
||||
while True:
|
||||
try:
|
||||
username = await prompt.prompt_async('Username: ')
|
||||
password = await prompt.prompt_async('Password: ', is_password=True)
|
||||
username = await prompt.prompt_async("Username: ")
|
||||
password = await prompt.prompt_async("Password: ", is_password=True)
|
||||
try:
|
||||
await user_model.create(
|
||||
username=username,
|
||||
password=pwd_context.hash(password),
|
||||
is_superuser=True
|
||||
username=username, password=pwd_context.hash(password), is_superuser=True
|
||||
)
|
||||
Logger.success(f'Create superuser {username} success.')
|
||||
Logger.success(f"Create superuser {username} success.")
|
||||
return
|
||||
except Exception as e:
|
||||
Logger.error(f'Create superuser {username} error,{e}')
|
||||
Logger.error(f"Create superuser {username} error,{e}")
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
Logger.success(f'Exit success!')
|
||||
Logger.success(f"Exit success!")
|
||||
return
|
||||
|
||||
|
||||
def cli():
|
||||
parser = argparse.ArgumentParser()
|
||||
subparsers = parser.add_subparsers(title='subcommands')
|
||||
parser.add_argument('-c', '--config', required=True,
|
||||
help='Tortoise-orm config dict import path,like settings.TORTOISE_ORM.')
|
||||
subparsers = parser.add_subparsers(title="subcommands")
|
||||
parser.add_argument(
|
||||
"-c",
|
||||
"--config",
|
||||
required=True,
|
||||
help="Tortoise-orm config dict import path,like settings.TORTOISE_ORM.",
|
||||
)
|
||||
|
||||
parser_register_permissions = subparsers.add_parser('register_permissions')
|
||||
parser_register_permissions.add_argument('--clean', required=False, action='store_true',
|
||||
help='Clean up old permissions then renew.')
|
||||
parser_register_permissions = subparsers.add_parser("register_permissions")
|
||||
parser_register_permissions.add_argument(
|
||||
"--clean", required=False, action="store_true", help="Clean up old permissions then renew."
|
||||
)
|
||||
parser_register_permissions.set_defaults(func=register_permissions)
|
||||
|
||||
parser_createsuperuser = subparsers.add_parser('createsuperuser')
|
||||
parser_createsuperuser.add_argument('--user-model', required=True,
|
||||
help='User model import path,like examples.models.User.')
|
||||
parser_createsuperuser = subparsers.add_parser("createsuperuser")
|
||||
parser_createsuperuser.add_argument(
|
||||
"--user", required=True, help="User model name, like User or Admin."
|
||||
)
|
||||
parser_createsuperuser.set_defaults(func=createsuperuser)
|
||||
|
||||
parse_args = parser.parse_args()
|
||||
|
@ -2,7 +2,7 @@ from copy import deepcopy
|
||||
|
||||
from passlib.context import CryptContext
|
||||
|
||||
pwd_context = CryptContext(schemes=['bcrypt'], deprecated='auto')
|
||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
|
||||
|
||||
async def handle_m2m_fields_create_or_update(body, m2m_fields, model, create=True, pk=None):
|
||||
|
@ -1,7 +1,7 @@
|
||||
import json
|
||||
|
||||
import jwt
|
||||
from fastapi import Query, Path, Depends, HTTPException
|
||||
from fastapi import Depends, HTTPException, Path, Query
|
||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||
from fastapi.security.utils import get_authorization_scheme_param
|
||||
from pydantic import BaseModel
|
||||
@ -14,16 +14,18 @@ from .factory import app
|
||||
auth_schema = HTTPBearer()
|
||||
|
||||
|
||||
async def jwt_required(request: Request, token: HTTPAuthorizationCredentials = Depends(auth_schema)):
|
||||
async def jwt_required(
|
||||
request: Request, token: HTTPAuthorizationCredentials = Depends(auth_schema)
|
||||
):
|
||||
credentials_exception = HTTPException(HTTP_401_UNAUTHORIZED)
|
||||
try:
|
||||
payload = jwt.decode(token.credentials, app.admin_secret)
|
||||
user_id = payload.get('user_id')
|
||||
user_id = payload.get("user_id")
|
||||
if user_id is None:
|
||||
raise credentials_exception
|
||||
except jwt.PyJWTError:
|
||||
raise credentials_exception
|
||||
request.scope['user_id'] = user_id
|
||||
request.scope["user_id"] = user_id
|
||||
return user_id
|
||||
|
||||
|
||||
@ -33,8 +35,8 @@ async def jwt_optional(request: Request):
|
||||
if credentials:
|
||||
try:
|
||||
payload = jwt.decode(credentials, app.admin_secret)
|
||||
user_id = payload.get('user_id')
|
||||
request.scope['user_id'] = user_id
|
||||
user_id = payload.get("user_id")
|
||||
request.scope["user_id"] = user_id
|
||||
return user_id
|
||||
except jwt.PyJWTError:
|
||||
pass
|
||||
@ -50,9 +52,7 @@ class QueryItem(BaseModel):
|
||||
sort: dict = {}
|
||||
|
||||
class Config:
|
||||
fields = {
|
||||
'with_': 'with'
|
||||
}
|
||||
fields = {"with_": "with"}
|
||||
|
||||
|
||||
def get_query(query=Query(...)):
|
||||
@ -94,7 +94,7 @@ class PermissionsChecker:
|
||||
if not user.is_active:
|
||||
raise HTTPException(status_code=HTTP_403_FORBIDDEN)
|
||||
has_permission = False
|
||||
await user.fetch_related('roles')
|
||||
await user.fetch_related("roles")
|
||||
for role in user.roles:
|
||||
if await role.permissions.filter(model=resource, action=self.action):
|
||||
has_permission = True
|
||||
|
@ -18,8 +18,8 @@ class PermissionAction(EnumMixin, IntEnum):
|
||||
@classmethod
|
||||
def choices(cls):
|
||||
return {
|
||||
cls.create: 'Create',
|
||||
cls.delete: 'Delete',
|
||||
cls.update: 'Update',
|
||||
cls.read: 'Read',
|
||||
cls.create: "Create",
|
||||
cls.delete: "Delete",
|
||||
cls.update: "Update",
|
||||
cls.read: "Read",
|
||||
}
|
||||
|
@ -1,10 +1,7 @@
|
||||
from starlette.requests import Request
|
||||
from fastapi.exceptions import HTTPException
|
||||
from starlette.requests import Request
|
||||
from starlette.responses import UJSONResponse
|
||||
|
||||
|
||||
async def exception_handler(request: Request, exc: HTTPException):
|
||||
return UJSONResponse(
|
||||
status_code=exc.status_code,
|
||||
content={'msg': exc.detail},
|
||||
)
|
||||
return UJSONResponse(status_code=exc.status_code, content={"msg": exc.detail},)
|
||||
|
@ -1,11 +1,11 @@
|
||||
from copy import deepcopy
|
||||
from typing import Type, List, Dict, Any, Optional
|
||||
from typing import Any, Dict, List, Optional, Type
|
||||
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from tortoise import Model, Tortoise
|
||||
|
||||
from .exceptions import exception_handler
|
||||
from .site import Site, Resource, Field, Menu
|
||||
from .site import Field, Menu, Resource, Site
|
||||
|
||||
|
||||
class AdminApp(FastAPI):
|
||||
@ -16,57 +16,54 @@ class AdminApp(FastAPI):
|
||||
permission: bool
|
||||
_inited: bool = False
|
||||
_field_type_mapping = {
|
||||
'IntField': 'number',
|
||||
'BooleanField': 'checkbox',
|
||||
'DatetimeField': 'datetime',
|
||||
'DateField': 'date',
|
||||
'IntEnumFieldInstance': 'select',
|
||||
'CharEnumFieldInstance': 'select',
|
||||
'DecimalField': 'number',
|
||||
'FloatField': 'number',
|
||||
'TextField': 'textarea',
|
||||
'SmallIntField': 'number',
|
||||
'JSONField': 'json',
|
||||
"IntField": "number",
|
||||
"BooleanField": "checkbox",
|
||||
"DatetimeField": "datetime",
|
||||
"DateField": "date",
|
||||
"IntEnumFieldInstance": "select",
|
||||
"CharEnumFieldInstance": "select",
|
||||
"DecimalField": "number",
|
||||
"FloatField": "number",
|
||||
"TextField": "textarea",
|
||||
"SmallIntField": "number",
|
||||
"JSONField": "json",
|
||||
}
|
||||
model_menu_mapping: Dict[str, Menu] = {}
|
||||
|
||||
def _get_model_menu_mapping(self, menus: List[Menu]):
|
||||
for menu in filter(lambda x: (x.url and 'rest' in x.url) or x.children, menus):
|
||||
for menu in filter(lambda x: (x.url and "rest" in x.url) or x.children, menus):
|
||||
if menu.children:
|
||||
self._get_model_menu_mapping(menu.children)
|
||||
else:
|
||||
self.model_menu_mapping[menu.url.split('?')[0].split('/')[-1]] = menu
|
||||
self.model_menu_mapping[menu.url.split("?")[0].split("/")[-1]] = menu
|
||||
|
||||
def _get_model_fields_type(self, model: Type[Model]) -> Dict:
|
||||
model_describe = model.describe()
|
||||
ret = {}
|
||||
data_fields = model_describe.get('data_fields')
|
||||
pk_field = model_describe.get('pk_field')
|
||||
fk_fields = model_describe.get('fk_fields')
|
||||
m2m_fields = model_describe.get('m2m_fields')
|
||||
data_fields = model_describe.get("data_fields")
|
||||
pk_field = model_describe.get("pk_field")
|
||||
fk_fields = model_describe.get("fk_fields")
|
||||
m2m_fields = model_describe.get("m2m_fields")
|
||||
fields = [pk_field] + data_fields + fk_fields + m2m_fields
|
||||
for field in fields:
|
||||
ret[field.get('name')] = self._get_field_type(field.get('name'), field.get('field_type'))
|
||||
ret[field.get("name")] = self._get_field_type(
|
||||
field.get("name"), field.get("field_type")
|
||||
)
|
||||
return ret
|
||||
|
||||
def _build_content_menus(self) -> List[Menu]:
|
||||
models = deepcopy(self.models) # type:Dict[str,Type[Model]]
|
||||
models.pop('Role', None)
|
||||
models.pop('User', None)
|
||||
models.pop('Permission', None)
|
||||
models.pop("Role", None)
|
||||
models.pop("User", None)
|
||||
models.pop("Permission", None)
|
||||
menus = []
|
||||
for k, v in models.items():
|
||||
menu = Menu(
|
||||
name=v._meta.table_description or k,
|
||||
url=f'/rest/{k}',
|
||||
url=f"/rest/{k}",
|
||||
fields_type=self._get_model_fields_type(v),
|
||||
icon='icon-list',
|
||||
bulk_actions=[
|
||||
{
|
||||
'value': 'delete',
|
||||
'text': 'delete_all',
|
||||
},
|
||||
]
|
||||
icon="icon-list",
|
||||
bulk_actions=[{"value": "delete", "text": "delete_all",},],
|
||||
)
|
||||
menus.append(menu)
|
||||
return menus
|
||||
@ -78,66 +75,47 @@ class AdminApp(FastAPI):
|
||||
"""
|
||||
|
||||
menus = [
|
||||
Menu(
|
||||
name='Home',
|
||||
url='/',
|
||||
icon='fa fa-home'
|
||||
),
|
||||
Menu(
|
||||
name='Content',
|
||||
title=True
|
||||
),
|
||||
Menu(name="Home", url="/", icon="fa fa-home"),
|
||||
Menu(name="Content", title=True),
|
||||
*self._build_content_menus(),
|
||||
Menu(name="External", title=True),
|
||||
Menu(
|
||||
name='External',
|
||||
title=True
|
||||
),
|
||||
Menu(
|
||||
name='Github',
|
||||
url='https://github.com/long2ice/fastapi-admin',
|
||||
icon='fa fa-github',
|
||||
external=True
|
||||
name="Github",
|
||||
url="https://github.com/long2ice/fastapi-admin",
|
||||
icon="fa fa-github",
|
||||
external=True,
|
||||
),
|
||||
]
|
||||
if permission:
|
||||
permission_menus = [
|
||||
Menu(name="Auth", title=True),
|
||||
Menu(
|
||||
name='Auth',
|
||||
title=True
|
||||
name="User",
|
||||
url="/rest/User",
|
||||
icon="fa fa-user",
|
||||
exclude=("password",),
|
||||
search_fields=("username",),
|
||||
),
|
||||
Menu(name="Role", url="/rest/Role", icon="fa fa-group", actions={"delete": False}),
|
||||
Menu(
|
||||
name='User',
|
||||
url='/rest/User',
|
||||
icon='fa fa-user',
|
||||
exclude=('password',),
|
||||
search_fields=('username',),
|
||||
name="Permission",
|
||||
url="/rest/Permission",
|
||||
icon="fa fa-user-plus",
|
||||
actions={"delete": False},
|
||||
),
|
||||
Menu(
|
||||
name='Role',
|
||||
url='/rest/Role',
|
||||
icon='fa fa-group',
|
||||
actions={
|
||||
'delete': False
|
||||
}
|
||||
),
|
||||
Menu(
|
||||
name='Permission',
|
||||
url='/rest/Permission',
|
||||
icon='fa fa-user-plus',
|
||||
actions={
|
||||
'delete': False
|
||||
}
|
||||
),
|
||||
Menu(
|
||||
name='Logout',
|
||||
url='/logout',
|
||||
icon='fa fa-lock',
|
||||
)
|
||||
Menu(name="Logout", url="/logout", icon="fa fa-lock",),
|
||||
]
|
||||
menus += permission_menus
|
||||
return menus
|
||||
|
||||
def init(self, site: Site, user_model: str, tortoise_app: str, admin_secret: str, permission: bool = False):
|
||||
def init(
|
||||
self,
|
||||
site: Site,
|
||||
user_model: str,
|
||||
tortoise_app: str,
|
||||
admin_secret: str,
|
||||
permission: bool = False,
|
||||
):
|
||||
"""
|
||||
init admin site
|
||||
:param tortoise_app:
|
||||
@ -180,13 +158,20 @@ class AdminApp(FastAPI):
|
||||
:param field_type:
|
||||
:return:
|
||||
"""
|
||||
field_type = self._field_type_mapping.get(field_type) or 'text'
|
||||
field_type = self._field_type_mapping.get(field_type) or "text"
|
||||
if menu:
|
||||
field_type = menu.fields_type.get(name) or field_type
|
||||
return field_type
|
||||
|
||||
async def _build_resource_from_model_describe(self, resource: str, model: Type[Model], model_describe: dict,
|
||||
exclude_pk: bool, exclude_m2m_field=True, exclude_actions=False):
|
||||
async def _build_resource_from_model_describe(
|
||||
self,
|
||||
resource: str,
|
||||
model: Type[Model],
|
||||
model_describe: dict,
|
||||
exclude_pk: bool,
|
||||
exclude_m2m_field=True,
|
||||
exclude_actions=False,
|
||||
):
|
||||
"""
|
||||
build resource
|
||||
:param resource:
|
||||
@ -196,113 +181,112 @@ class AdminApp(FastAPI):
|
||||
:param exclude_m2m_field:
|
||||
:return:
|
||||
"""
|
||||
data_fields = model_describe.get('data_fields')
|
||||
pk_field = model_describe.get('pk_field')
|
||||
fk_fields = model_describe.get('fk_fields')
|
||||
m2m_fields = model_describe.get('m2m_fields')
|
||||
data_fields = model_describe.get("data_fields")
|
||||
pk_field = model_describe.get("pk_field")
|
||||
fk_fields = model_describe.get("fk_fields")
|
||||
m2m_fields = model_describe.get("m2m_fields")
|
||||
menu = self.model_menu_mapping[resource]
|
||||
search_fields_ret = {}
|
||||
search_fields = menu.search_fields
|
||||
sort_fields = menu.sort_fields
|
||||
fields = {}
|
||||
pk = name = pk_field.get('name')
|
||||
pk = name = pk_field.get("name")
|
||||
if not exclude_pk and not self._exclude_field(resource, name):
|
||||
fields = {
|
||||
name: Field(
|
||||
label=pk_field.get('name').title(),
|
||||
label=pk_field.get("name").title(),
|
||||
required=True,
|
||||
type=self._get_field_type(name, pk_field.get('field_type').__name__, menu),
|
||||
type=self._get_field_type(name, pk_field.get("field_type").__name__, menu),
|
||||
sortable=name in sort_fields,
|
||||
**menu.attrs.get(name) or {}
|
||||
**menu.attrs.get(name) or {},
|
||||
)
|
||||
}
|
||||
if not exclude_actions and menu.actions:
|
||||
fields['_actions'] = menu.actions
|
||||
fields["_actions"] = menu.actions
|
||||
|
||||
for data_field in data_fields:
|
||||
readonly = data_field.get('constraints').get('readOnly')
|
||||
field_type = data_field.get('field_type').__name__
|
||||
name = data_field.get('name')
|
||||
readonly = data_field.get("constraints").get("readOnly")
|
||||
field_type = data_field.get("field_type").__name__
|
||||
name = data_field.get("name")
|
||||
if self._exclude_field(resource, name):
|
||||
continue
|
||||
|
||||
type_ = self._get_field_type(name, field_type, menu)
|
||||
options = []
|
||||
if type_ == 'select' or type_ == 'radiolist':
|
||||
if type_ == "select" or type_ == "radiolist":
|
||||
for k, v in model._meta.fields_map[name].enum_type.choices().items():
|
||||
options.append({'text': v, 'value': k})
|
||||
options.append({"text": v, "value": k})
|
||||
|
||||
label = data_field.get('description') or data_field.get('name').title()
|
||||
label = data_field.get("description") or data_field.get("name").title()
|
||||
field = Field(
|
||||
label=label,
|
||||
required=not data_field.get('nullable'),
|
||||
required=not data_field.get("nullable"),
|
||||
type=type_,
|
||||
options=options,
|
||||
sortable=name in sort_fields,
|
||||
disabled=readonly,
|
||||
**menu.attrs.get(name) or {}
|
||||
**menu.attrs.get(name) or {},
|
||||
)
|
||||
fields[name] = field
|
||||
if name in search_fields:
|
||||
search_fields_ret[name] = field
|
||||
|
||||
for fk_field in fk_fields:
|
||||
name = fk_field.get('name')
|
||||
name = fk_field.get("name")
|
||||
if not self._exclude_field(resource, name):
|
||||
if name not in menu.raw_id_fields:
|
||||
fk_model_class = fk_field.get('python_type')
|
||||
fk_model_class = fk_field.get("python_type")
|
||||
objs = await fk_model_class.all()
|
||||
raw_field = fk_field.get('raw_field')
|
||||
label = fk_field.get('description') or name.title()
|
||||
options = list(map(lambda x: {'text': str(x), 'value': x.pk}, objs))
|
||||
raw_field = fk_field.get("raw_field")
|
||||
label = fk_field.get("description") or name.title()
|
||||
options = list(map(lambda x: {"text": str(x), "value": x.pk}, objs))
|
||||
field = Field(
|
||||
label=label,
|
||||
required=True,
|
||||
type='select',
|
||||
type="select",
|
||||
options=options,
|
||||
sortable=name in sort_fields,
|
||||
**menu.attrs.get(name) or {}
|
||||
**menu.attrs.get(name) or {},
|
||||
)
|
||||
fields[raw_field] = field
|
||||
if name in search_fields:
|
||||
search_fields_ret[raw_field] = field
|
||||
if not exclude_m2m_field:
|
||||
for m2m_field in m2m_fields:
|
||||
name = m2m_field.get('name')
|
||||
name = m2m_field.get("name")
|
||||
if not self._exclude_field(resource, name):
|
||||
label = m2m_field.get('description') or name.title()
|
||||
m2m_model_class = m2m_field.get('python_type')
|
||||
label = m2m_field.get("description") or name.title()
|
||||
m2m_model_class = m2m_field.get("python_type")
|
||||
objs = await m2m_model_class.all()
|
||||
options = list(map(lambda x: {'text': str(x), 'value': x.pk}, objs))
|
||||
options = list(map(lambda x: {"text": str(x), "value": x.pk}, objs))
|
||||
fields[name] = Field(
|
||||
label=label,
|
||||
type='tree',
|
||||
type="tree",
|
||||
options=options,
|
||||
multiple=True,
|
||||
**menu.attrs.get(name) or {}
|
||||
**menu.attrs.get(name) or {},
|
||||
)
|
||||
return pk, fields, search_fields_ret
|
||||
|
||||
async def get_resource(self, resource: str, exclude_pk=False, exclude_m2m_field=True,
|
||||
exclude_actions=False) -> Resource:
|
||||
assert self._inited, 'must call init() first!'
|
||||
async def get_resource(
|
||||
self, resource: str, exclude_pk=False, exclude_m2m_field=True, exclude_actions=False
|
||||
) -> Resource:
|
||||
assert self._inited, "must call init() first!"
|
||||
model = self.models.get(resource) # type:Type[Model]
|
||||
model_describe = model.describe(serializable=False)
|
||||
pk, fields, search_fields = await self._build_resource_from_model_describe(resource, model, model_describe,
|
||||
exclude_pk, exclude_m2m_field,
|
||||
exclude_actions)
|
||||
pk, fields, search_fields = await self._build_resource_from_model_describe(
|
||||
resource, model, model_describe, exclude_pk, exclude_m2m_field, exclude_actions
|
||||
)
|
||||
menu = self.model_menu_mapping[resource]
|
||||
return Resource(
|
||||
title=model_describe.get('description') or resource.title(),
|
||||
title=model_describe.get("description") or resource.title(),
|
||||
fields=fields,
|
||||
searchFields=search_fields,
|
||||
pk=pk,
|
||||
bulk_actions=menu.bulk_actions,
|
||||
export=menu.export
|
||||
export=menu.export,
|
||||
)
|
||||
|
||||
|
||||
app = AdminApp(
|
||||
openapi_prefix='/admin',
|
||||
)
|
||||
app = AdminApp(openapi_prefix="/admin",)
|
||||
app.add_exception_handler(HTTPException, exception_handler)
|
||||
|
@ -14,8 +14,9 @@ class User(Model):
|
||||
class Permission(Model):
|
||||
label = fields.CharField(max_length=50)
|
||||
model = fields.CharField(max_length=50)
|
||||
action: enums.PermissionAction = fields.IntEnumField(enums.PermissionAction, default=enums.PermissionAction.read,
|
||||
description='Permission Action')
|
||||
action: enums.PermissionAction = fields.IntEnumField(
|
||||
enums.PermissionAction, default=enums.PermissionAction.read, description="Permission Action"
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.label
|
||||
@ -23,9 +24,9 @@ class Permission(Model):
|
||||
|
||||
class Role(Model):
|
||||
label = fields.CharField(max_length=50)
|
||||
users = fields.ManyToManyField('models.User')
|
||||
users = fields.ManyToManyField("models.User")
|
||||
|
||||
permissions: fields.ManyToManyRelation[Permission] = fields.ManyToManyField('models.Permission')
|
||||
permissions: fields.ManyToManyRelation[Permission] = fields.ManyToManyField("models.Permission")
|
||||
|
||||
def __str__(self):
|
||||
return self.label
|
||||
|
@ -1,4 +1,5 @@
|
||||
from typing import Sequence, Dict
|
||||
from typing import Dict, Sequence
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
from fastapi import Depends
|
||||
|
||||
from . import login, site, rest
|
||||
from ..depends import jwt_required
|
||||
from ..factory import app
|
||||
from . import login, rest, site
|
||||
|
||||
app.include_router(login.router)
|
||||
app.include_router(site.router)
|
||||
app.include_router(rest.router, dependencies=[Depends(jwt_required)], prefix='/rest')
|
||||
app.include_router(rest.router, dependencies=[Depends(jwt_required)], prefix="/rest")
|
||||
|
@ -10,25 +10,20 @@ from ..shortcuts import get_object_or_404
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post(
|
||||
'/login',
|
||||
)
|
||||
async def login(
|
||||
login_in: LoginIn
|
||||
):
|
||||
@router.post("/login",)
|
||||
async def login(login_in: LoginIn):
|
||||
user_model = app.user_model
|
||||
user = await get_object_or_404(user_model, username=login_in.username)
|
||||
if not user.is_active:
|
||||
raise HTTPException(status_code=HTTP_403_FORBIDDEN, detail='User is not Active!')
|
||||
raise HTTPException(status_code=HTTP_403_FORBIDDEN, detail="User is not Active!")
|
||||
if not pwd_context.verify(login_in.password, user.password):
|
||||
raise HTTPException(status_code=HTTP_403_FORBIDDEN, detail='Incorrect Password!')
|
||||
raise HTTPException(status_code=HTTP_403_FORBIDDEN, detail="Incorrect Password!")
|
||||
ret = {
|
||||
'user': {
|
||||
'username': user.username,
|
||||
'is_superuser': user.is_superuser,
|
||||
'avatar': user.avatar if hasattr(user, 'avatar') else None
|
||||
|
||||
"user": {
|
||||
"username": user.username,
|
||||
"is_superuser": user.is_superuser,
|
||||
"avatar": user.avatar if hasattr(user, "avatar") else None,
|
||||
},
|
||||
'token': jwt.encode({'user_id': user.pk}, app.admin_secret, algorithm='HS256')
|
||||
"token": jwt.encode({"user_id": user.pk}, app.admin_secret, algorithm="HS256"),
|
||||
}
|
||||
return ret
|
||||
|
@ -1,7 +1,7 @@
|
||||
import io
|
||||
|
||||
import xlsxwriter
|
||||
from fastapi import Depends, APIRouter
|
||||
from fastapi import APIRouter, Depends
|
||||
from fastapi.responses import UJSONResponse
|
||||
from starlette.responses import StreamingResponse
|
||||
from starlette.status import HTTP_409_CONFLICT
|
||||
@ -11,8 +11,16 @@ from tortoise.exceptions import IntegrityError
|
||||
from tortoise.fields import ManyToManyRelation
|
||||
|
||||
from ..common import handle_m2m_fields_create_or_update
|
||||
from ..depends import QueryItem, get_query, parse_body, get_model, read_checker, delete_checker, update_checker, \
|
||||
create_checker
|
||||
from ..depends import (
|
||||
QueryItem,
|
||||
create_checker,
|
||||
delete_checker,
|
||||
get_model,
|
||||
get_query,
|
||||
parse_body,
|
||||
read_checker,
|
||||
update_checker,
|
||||
)
|
||||
from ..factory import app
|
||||
from ..responses import GetManyOut
|
||||
from ..schemas import BulkIn
|
||||
@ -21,20 +29,16 @@ from ..shortcuts import get_object_or_404
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get(
|
||||
'/{resource}/export'
|
||||
)
|
||||
async def export(
|
||||
resource: str,
|
||||
query: QueryItem = Depends(get_query),
|
||||
model=Depends(get_model)
|
||||
):
|
||||
@router.get("/{resource}/export")
|
||||
async def export(resource: str, query: QueryItem = Depends(get_query), model=Depends(get_model)):
|
||||
qs = model.all()
|
||||
if query.where:
|
||||
qs = qs.filter(**query.where)
|
||||
resource = await app.get_resource(resource)
|
||||
result = await qs
|
||||
creator = pydantic_model_creator(model, include=resource.resource_fields.keys(), exclude=model._meta.m2m_fields)
|
||||
creator = pydantic_model_creator(
|
||||
model, include=resource.resource_fields.keys(), exclude=model._meta.m2m_fields
|
||||
)
|
||||
data = map(lambda x: creator.from_orm(x).dict(), result)
|
||||
|
||||
output = io.BytesIO()
|
||||
@ -54,14 +58,9 @@ async def export(
|
||||
return StreamingResponse(output)
|
||||
|
||||
|
||||
@router.get(
|
||||
'/{resource}',
|
||||
dependencies=[Depends(read_checker)]
|
||||
)
|
||||
@router.get("/{resource}", dependencies=[Depends(read_checker)])
|
||||
async def get_resource(
|
||||
resource: str,
|
||||
query: QueryItem = Depends(get_query),
|
||||
model=Depends(get_model)
|
||||
resource: str, query: QueryItem = Depends(get_query), model=Depends(get_model)
|
||||
):
|
||||
menu = app.model_menu_mapping[resource]
|
||||
qs = model.all()
|
||||
@ -71,125 +70,81 @@ async def get_resource(
|
||||
for k, v in sort.items():
|
||||
if k in menu.sort_fields:
|
||||
if v == -1:
|
||||
qs = qs.order_by(f'-{k}')
|
||||
qs = qs.order_by(f"-{k}")
|
||||
elif v == 1:
|
||||
qs = qs.order_by(k)
|
||||
resource = await app.get_resource(resource)
|
||||
result = await qs.limit(query.size).offset((query.page - 1) * query.size)
|
||||
creator = pydantic_model_creator(model, include=resource.resource_fields.keys(), exclude=model._meta.m2m_fields)
|
||||
creator = pydantic_model_creator(
|
||||
model, include=resource.resource_fields.keys(), exclude=model._meta.m2m_fields
|
||||
)
|
||||
return GetManyOut(
|
||||
total=await qs.count(),
|
||||
data=list(map(lambda x: creator.from_orm(x).dict(), result))
|
||||
total=await qs.count(), data=list(map(lambda x: creator.from_orm(x).dict(), result))
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
'/{resource}/form',
|
||||
dependencies=[Depends(read_checker)]
|
||||
)
|
||||
async def form(
|
||||
resource: str,
|
||||
):
|
||||
resource = await app.get_resource(resource, exclude_pk=True, exclude_m2m_field=False, exclude_actions=True)
|
||||
@router.get("/{resource}/form", dependencies=[Depends(read_checker)])
|
||||
async def form(resource: str,):
|
||||
resource = await app.get_resource(
|
||||
resource, exclude_pk=True, exclude_m2m_field=False, exclude_actions=True
|
||||
)
|
||||
return resource.dict(by_alias=True, exclude_unset=True)
|
||||
|
||||
|
||||
@router.get(
|
||||
'/{resource}/grid',
|
||||
dependencies=[Depends(read_checker)]
|
||||
)
|
||||
async def grid(
|
||||
resource: str,
|
||||
):
|
||||
@router.get("/{resource}/grid", dependencies=[Depends(read_checker)])
|
||||
async def grid(resource: str,):
|
||||
resource = await app.get_resource(resource)
|
||||
return resource.dict(by_alias=True, exclude_unset=True)
|
||||
|
||||
|
||||
@router.get(
|
||||
'/{resource}/view',
|
||||
dependencies=[Depends(read_checker)]
|
||||
)
|
||||
async def view(
|
||||
resource: str,
|
||||
):
|
||||
@router.get("/{resource}/view", dependencies=[Depends(read_checker)])
|
||||
async def view(resource: str,):
|
||||
resource = await app.get_resource(resource)
|
||||
return resource.dict(by_alias=True, exclude_unset=True)
|
||||
|
||||
|
||||
@router.post(
|
||||
'/{resource}/bulk/delete',
|
||||
dependencies=[Depends(delete_checker)]
|
||||
)
|
||||
async def bulk_delete(
|
||||
bulk_in: BulkIn,
|
||||
model=Depends(get_model)
|
||||
):
|
||||
@router.post("/{resource}/bulk/delete", dependencies=[Depends(delete_checker)])
|
||||
async def bulk_delete(bulk_in: BulkIn, model=Depends(get_model)):
|
||||
await model.filter(pk__in=bulk_in.pk_list).delete()
|
||||
return {'success': True}
|
||||
return {"success": True}
|
||||
|
||||
|
||||
@router.delete(
|
||||
'/{resource}/{id}',
|
||||
dependencies=[Depends(delete_checker)]
|
||||
)
|
||||
async def delete_one(
|
||||
id: int,
|
||||
model=Depends(get_model)
|
||||
):
|
||||
@router.delete("/{resource}/{id}", dependencies=[Depends(delete_checker)])
|
||||
async def delete_one(id: int, model=Depends(get_model)):
|
||||
await model.filter(pk=id).delete()
|
||||
return {'success': True}
|
||||
return {"success": True}
|
||||
|
||||
|
||||
@router.put(
|
||||
'/{resource}/{id}',
|
||||
dependencies=[Depends(update_checker)]
|
||||
)
|
||||
async def update_one(
|
||||
id: int,
|
||||
parsed=Depends(parse_body),
|
||||
model=Depends(get_model)
|
||||
):
|
||||
@router.put("/{resource}/{id}", dependencies=[Depends(update_checker)])
|
||||
async def update_one(id: int, parsed=Depends(parse_body), model=Depends(get_model)):
|
||||
body, resource_fields = parsed
|
||||
m2m_fields = model._meta.m2m_fields
|
||||
try:
|
||||
obj = await handle_m2m_fields_create_or_update(body, m2m_fields, model, False, id)
|
||||
except IntegrityError as e:
|
||||
return UJSONResponse(status_code=HTTP_409_CONFLICT, content=dict(
|
||||
message=f'Update Error,{e}'
|
||||
))
|
||||
return UJSONResponse(
|
||||
status_code=HTTP_409_CONFLICT, content=dict(message=f"Update Error,{e}")
|
||||
)
|
||||
creator = pydantic_model_creator(model, include=resource_fields, exclude=m2m_fields)
|
||||
return creator.from_orm(obj).dict()
|
||||
|
||||
|
||||
@router.post(
|
||||
'/{resource}',
|
||||
dependencies=[Depends(create_checker)]
|
||||
)
|
||||
async def create_one(
|
||||
parsed=Depends(parse_body),
|
||||
model=Depends(get_model)
|
||||
):
|
||||
@router.post("/{resource}", dependencies=[Depends(create_checker)])
|
||||
async def create_one(parsed=Depends(parse_body), model=Depends(get_model)):
|
||||
body, resource_fields = parsed
|
||||
m2m_fields = model._meta.m2m_fields
|
||||
creator = pydantic_model_creator(model, include=resource_fields, exclude=m2m_fields)
|
||||
try:
|
||||
obj = await handle_m2m_fields_create_or_update(body, m2m_fields, model)
|
||||
except IntegrityError as e:
|
||||
return UJSONResponse(status_code=HTTP_409_CONFLICT, content=dict(
|
||||
message=f'Create Error,{e}'
|
||||
))
|
||||
return UJSONResponse(
|
||||
status_code=HTTP_409_CONFLICT, content=dict(message=f"Create Error,{e}")
|
||||
)
|
||||
return creator.from_orm(obj).dict()
|
||||
|
||||
|
||||
@router.get(
|
||||
'/{resource}/{id}',
|
||||
dependencies=[Depends(read_checker)]
|
||||
)
|
||||
async def get_one(
|
||||
id: int,
|
||||
resource: str,
|
||||
model=Depends(get_model)
|
||||
):
|
||||
@router.get("/{resource}/{id}", dependencies=[Depends(read_checker)])
|
||||
async def get_one(id: int, resource: str, model=Depends(get_model)):
|
||||
obj = await get_object_or_404(model, pk=id) # type:Model
|
||||
m2m_fields = model._meta.m2m_fields
|
||||
resource = await app.get_resource(resource, exclude_m2m_field=False)
|
||||
@ -201,5 +156,5 @@ async def get_one(
|
||||
relate_model = getattr(obj, m2m_field) # type:ManyToManyRelation
|
||||
ids = await relate_model.all().values_list(relate_model.remote_model._meta.pk_attr)
|
||||
ret[m2m_field] = list(map(lambda x: x[0], ids))
|
||||
ret['__str__'] = str(obj)
|
||||
ret["__str__"] = str(obj)
|
||||
return ret
|
||||
|
@ -9,26 +9,22 @@ from ..shortcuts import get_object_or_404
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get(
|
||||
'/site',
|
||||
)
|
||||
async def site(
|
||||
user_id=Depends(jwt_optional)
|
||||
):
|
||||
@router.get("/site",)
|
||||
async def site(user_id=Depends(jwt_optional)):
|
||||
site_ = app.site
|
||||
user = None
|
||||
if user_id:
|
||||
user = await get_object_or_404(app.user_model, pk=user_id)
|
||||
if user and app.permission and not user.is_superuser:
|
||||
site_ = deepcopy(site_)
|
||||
await user.fetch_related('roles')
|
||||
filter_menus = filter(lambda x: (x.url and 'rest' in x.url) or x.children, site_.menus)
|
||||
await user.fetch_related("roles")
|
||||
filter_menus = filter(lambda x: (x.url and "rest" in x.url) or x.children, site_.menus)
|
||||
hide_menus = []
|
||||
for menu in filter_menus:
|
||||
has_permission = False
|
||||
for role in user.roles:
|
||||
if not has_permission:
|
||||
model = menu.url.split('/')[-1]
|
||||
model = menu.url.split("/")[-1]
|
||||
permission = await role.permissions.filter(model=model)
|
||||
if permission:
|
||||
has_permission = True
|
||||
|
@ -5,8 +5,8 @@ from pydantic import BaseModel
|
||||
|
||||
|
||||
class LoginIn(BaseModel):
|
||||
username: str = Body(..., example='long2ice')
|
||||
password: str = Body(..., example='123456')
|
||||
username: str = Body(..., example="long2ice")
|
||||
password: str = Body(..., example="123456")
|
||||
|
||||
|
||||
class BulkIn(BaseModel):
|
||||
|
@ -14,5 +14,5 @@ async def get_object_or_404(model: Generic[MODEL], **kwargs):
|
||||
"""
|
||||
obj = await model.filter(**kwargs).first() # type:model
|
||||
if not obj:
|
||||
raise HTTPException(HTTP_404_NOT_FOUND, 'Not Found')
|
||||
raise HTTPException(HTTP_404_NOT_FOUND, "Not Found")
|
||||
return obj
|
||||
|
@ -1,4 +1,4 @@
|
||||
from typing import Optional, Tuple, Dict, Union, List, Set
|
||||
from typing import Dict, List, Optional, Set, Tuple, Union
|
||||
|
||||
from pydantic import BaseModel, HttpUrl
|
||||
|
||||
@ -11,7 +11,7 @@ class Menu(BaseModel):
|
||||
url: Optional[str]
|
||||
icon: Optional[str]
|
||||
# children menu
|
||||
children: Optional[List['Menu']] = []
|
||||
children: Optional[List["Menu"]] = []
|
||||
# include fields
|
||||
include: Optional[Tuple[str]]
|
||||
# exclude fields
|
||||
@ -31,10 +31,7 @@ class Menu(BaseModel):
|
||||
# active table export
|
||||
export: bool = True
|
||||
actions: Optional[Dict]
|
||||
bulk_actions: List[Dict] = [{
|
||||
'value': 'delete',
|
||||
'text': 'delete_all',
|
||||
}]
|
||||
bulk_actions: List[Dict] = [{"value": "delete", "text": "delete_all",}]
|
||||
|
||||
|
||||
Menu.update_forward_refs()
|
||||
@ -86,5 +83,5 @@ class Resource(BaseModel):
|
||||
|
||||
class Config:
|
||||
fields = {
|
||||
'resource_fields': 'fields',
|
||||
"resource_fields": "fields",
|
||||
}
|
||||
|
384
poetry.lock
generated
384
poetry.lock
generated
@ -36,6 +36,22 @@ optional = false
|
||||
python-versions = "*"
|
||||
version = "7.0.0"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "apipkg: namespace control and lazy-import mechanism"
|
||||
name = "apipkg"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "1.5"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||
name = "appdirs"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "1.4.4"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "AsyncExitStack backport for Python 3.5+"
|
||||
@ -60,6 +76,29 @@ optional = false
|
||||
python-versions = ">=3.5"
|
||||
version = "0.13.0"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Atomic file writes."
|
||||
marker = "sys_platform == \"win32\""
|
||||
name = "atomicwrites"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "1.4.0"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Classes Without Boilerplate"
|
||||
name = "attrs"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "19.3.0"
|
||||
|
||||
[package.extras]
|
||||
azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"]
|
||||
dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"]
|
||||
docs = ["sphinx", "zope.interface"]
|
||||
tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Modern password hashing for your software and your servers"
|
||||
@ -75,6 +114,26 @@ six = ">=1.4.1"
|
||||
[package.extras]
|
||||
tests = ["pytest (>=3.2.1,<3.3.0 || >3.3.0)"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "The uncompromising code formatter."
|
||||
name = "black"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
version = "19.10b0"
|
||||
|
||||
[package.dependencies]
|
||||
appdirs = "*"
|
||||
attrs = ">=18.1.0"
|
||||
click = ">=6.5"
|
||||
pathspec = ">=0.6,<1"
|
||||
regex = "*"
|
||||
toml = ">=0.9.4"
|
||||
typed-ast = ">=1.4.0"
|
||||
|
||||
[package.extras]
|
||||
d = ["aiohttp (>=3.3.2)", "aiohttp-cors"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
@ -170,6 +229,20 @@ version = "1.1.1"
|
||||
dnspython = ">=1.15.0"
|
||||
idna = ">=2.0.0"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "execnet: rapid multi-Python deployment"
|
||||
name = "execnet"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "1.7.1"
|
||||
|
||||
[package.dependencies]
|
||||
apipkg = ">=1.4"
|
||||
|
||||
[package.extras]
|
||||
testing = ["pre-commit"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
|
||||
@ -240,6 +313,19 @@ dev = ["pyjwt", "passlib", "autoflake", "flake8", "uvicorn", "graphene"]
|
||||
doc = ["mkdocs", "mkdocs-material", "markdown-include", "typer", "typer-cli", "pyyaml"]
|
||||
test = ["pytest (>=4.0.0)", "pytest-cov", "mypy", "black", "isort", "requests", "email-validator", "sqlalchemy", "peewee", "databases", "orjson", "async-exit-stack", "async-generator", "python-multipart", "aiofiles", "flask"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "the modular source code checker: pep8 pyflakes and co"
|
||||
name = "flake8"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
|
||||
version = "3.8.1"
|
||||
|
||||
[package.dependencies]
|
||||
mccabe = ">=0.6.0,<0.7.0"
|
||||
pycodestyle = ">=2.6.0a1,<2.7.0"
|
||||
pyflakes = ">=2.2.0,<2.3.0"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "GraphQL Framework for Python"
|
||||
@ -326,6 +412,20 @@ optional = false
|
||||
python-versions = "*"
|
||||
version = "0.1.12"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "A Python utility / library to sort Python imports."
|
||||
name = "isort"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "4.3.21"
|
||||
|
||||
[package.extras]
|
||||
pipfile = ["pipreqs", "requirementslib"]
|
||||
pyproject = ["toml"]
|
||||
requirements = ["pipreqs", "pip-api"]
|
||||
xdg_home = ["appdirs (>=1.4.0)"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Various helpers to pass data to untrusted environments and back."
|
||||
@ -356,6 +456,22 @@ optional = false
|
||||
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
|
||||
version = "1.1.1"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "McCabe checker, plugin for flake8"
|
||||
name = "mccabe"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "0.6.1"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "More routines for operating on iterables, beyond itertools"
|
||||
name = "more-itertools"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
version = "8.3.0"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy"
|
||||
@ -364,6 +480,18 @@ optional = false
|
||||
python-versions = ">=3.6"
|
||||
version = "3.0.1"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Core utilities for Python packages"
|
||||
name = "packaging"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "20.4"
|
||||
|
||||
[package.dependencies]
|
||||
pyparsing = ">=2.0.2"
|
||||
six = "*"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "comprehensive password hashing framework supporting over 30 schemes"
|
||||
@ -378,6 +506,25 @@ bcrypt = ["bcrypt (>=3.1.0)"]
|
||||
build_docs = ["sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)", "cloud-sptheme (>=1.10.0)"]
|
||||
totp = ["cryptography"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Utility library for gitignore style pattern matching of file paths."
|
||||
name = "pathspec"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
version = "0.8.0"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "plugin and hook calling mechanisms for python"
|
||||
name = "pluggy"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "0.13.1"
|
||||
|
||||
[package.extras]
|
||||
dev = ["pre-commit", "tox"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Promises/A+ implementation for Python"
|
||||
@ -403,6 +550,22 @@ version = "3.0.5"
|
||||
[package.dependencies]
|
||||
wcwidth = "*"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "library with cross-python path, ini-parsing, io, code, log facilities"
|
||||
name = "py"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "1.8.1"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Python style guide checker"
|
||||
name = "pycodestyle"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "2.6.0"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "C parser in Python"
|
||||
@ -424,6 +587,14 @@ dotenv = ["python-dotenv (>=0.10.4)"]
|
||||
email = ["email-validator (>=1.0.3)"]
|
||||
typing_extensions = ["typing-extensions (>=3.7.2)"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "passive checker of Python programs"
|
||||
name = "pyflakes"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "2.2.0"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "JSON Web Token implementation in Python"
|
||||
@ -448,6 +619,14 @@ version = "0.9.2"
|
||||
[package.dependencies]
|
||||
cryptography = "*"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Python parsing module"
|
||||
name = "pyparsing"
|
||||
optional = false
|
||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
version = "2.4.7"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "A SQL query builder API for Python"
|
||||
@ -456,6 +635,56 @@ optional = false
|
||||
python-versions = "*"
|
||||
version = "0.37.6"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "pytest: simple powerful testing with Python"
|
||||
name = "pytest"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
version = "5.4.2"
|
||||
|
||||
[package.dependencies]
|
||||
atomicwrites = ">=1.0"
|
||||
attrs = ">=17.4.0"
|
||||
colorama = "*"
|
||||
more-itertools = ">=4.0.0"
|
||||
packaging = "*"
|
||||
pluggy = ">=0.12,<1.0"
|
||||
py = ">=1.5.0"
|
||||
wcwidth = "*"
|
||||
|
||||
[package.extras]
|
||||
checkqa-mypy = ["mypy (v0.761)"]
|
||||
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "run tests in isolated forked subprocesses"
|
||||
name = "pytest-forked"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "1.1.3"
|
||||
|
||||
[package.dependencies]
|
||||
pytest = ">=3.1.0"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "pytest xdist plugin for distributed testing and loop-on-failing modes"
|
||||
name = "pytest-xdist"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
version = "1.32.0"
|
||||
|
||||
[package.dependencies]
|
||||
execnet = ">=1.1"
|
||||
pytest = ">=4.4.0"
|
||||
pytest-forked = "*"
|
||||
six = "*"
|
||||
|
||||
[package.extras]
|
||||
testing = ["filelock"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Add .env support to your django/flask apps in development and deployments"
|
||||
@ -494,6 +723,14 @@ optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
version = "5.3.1"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Alternative regular expression module, to replace re."
|
||||
name = "regex"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "2020.5.14"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Python HTTP for Humans."
|
||||
@ -526,7 +763,7 @@ description = "Python 2 and 3 compatibility utilities"
|
||||
name = "six"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
version = "1.14.0"
|
||||
version = "1.15.0"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
@ -573,6 +810,14 @@ iso8601 = ">=0.1.12"
|
||||
pypika = ">=0.36.5"
|
||||
typing-extensions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "a fork of Python 2 and 3 ast modules with type comment support"
|
||||
name = "typed-ast"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "1.4.1"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Backported and Experimental Type Hints for Python 3.5+"
|
||||
@ -653,7 +898,7 @@ python-versions = "*"
|
||||
version = "1.2.8"
|
||||
|
||||
[metadata]
|
||||
content-hash = "02e9b18be076e5c7b8f95c5176d95ea55274ad0bb37157e3bf8bc7fd6a68a912"
|
||||
content-hash = "136bc2fdede1a36872a4d17b125eb604420fe935f2291156bf593fbf52ae6bc4"
|
||||
python-versions = "^3.8"
|
||||
|
||||
[metadata.files]
|
||||
@ -673,6 +918,14 @@ aniso8601 = [
|
||||
{file = "aniso8601-7.0.0-py2.py3-none-any.whl", hash = "sha256:d10a4bf949f619f719b227ef5386e31f49a2b6d453004b21f02661ccc8670c7b"},
|
||||
{file = "aniso8601-7.0.0.tar.gz", hash = "sha256:513d2b6637b7853806ae79ffaca6f3e8754bdd547048f5ccc1420aec4b714f1e"},
|
||||
]
|
||||
apipkg = [
|
||||
{file = "apipkg-1.5-py2.py3-none-any.whl", hash = "sha256:58587dd4dc3daefad0487f6d9ae32b4542b185e1c36db6993290e7c41ca2b47c"},
|
||||
{file = "apipkg-1.5.tar.gz", hash = "sha256:37228cda29411948b422fae072f57e31d3396d2ee1c9783775980ee9c9990af6"},
|
||||
]
|
||||
appdirs = [
|
||||
{file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
|
||||
{file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
|
||||
]
|
||||
async-exit-stack = [
|
||||
{file = "async_exit_stack-1.0.1-py3-none-any.whl", hash = "sha256:9b43b17683b3438f428ef3bbec20689f5abbb052aa4b564c643397330adfaa99"},
|
||||
{file = "async_exit_stack-1.0.1.tar.gz", hash = "sha256:24de1ad6d0ff27be97c89d6709fa49bf20db179eaf1f4d2e6e9b4409b80e747d"},
|
||||
@ -685,6 +938,14 @@ asynctest = [
|
||||
{file = "asynctest-0.13.0-py3-none-any.whl", hash = "sha256:5da6118a7e6d6b54d83a8f7197769d046922a44d2a99c21382f0a6e4fadae676"},
|
||||
{file = "asynctest-0.13.0.tar.gz", hash = "sha256:c27862842d15d83e6a34eb0b2866c323880eb3a75e4485b079ea11748fd77fac"},
|
||||
]
|
||||
atomicwrites = [
|
||||
{file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"},
|
||||
{file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
|
||||
]
|
||||
attrs = [
|
||||
{file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"},
|
||||
{file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"},
|
||||
]
|
||||
bcrypt = [
|
||||
{file = "bcrypt-3.1.7-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:d7bdc26475679dd073ba0ed2766445bb5b20ca4793ca0db32b399dccc6bc84b7"},
|
||||
{file = "bcrypt-3.1.7-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:69361315039878c0680be456640f8705d76cb4a3a3fe1e057e0f261b74be4b31"},
|
||||
@ -705,6 +966,10 @@ bcrypt = [
|
||||
{file = "bcrypt-3.1.7-cp38-cp38-win_amd64.whl", hash = "sha256:6305557019906466fc42dbc53b46da004e72fd7a551c044a827e572c82191752"},
|
||||
{file = "bcrypt-3.1.7.tar.gz", hash = "sha256:0b0069c752ec14172c5f78208f1863d7ad6755a6fae6fe76ec2c80d13be41e42"},
|
||||
]
|
||||
black = [
|
||||
{file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"},
|
||||
{file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"},
|
||||
]
|
||||
certifi = [
|
||||
{file = "certifi-2020.4.5.1-py2.py3-none-any.whl", hash = "sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304"},
|
||||
{file = "certifi-2020.4.5.1.tar.gz", hash = "sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519"},
|
||||
@ -783,10 +1048,18 @@ email-validator = [
|
||||
{file = "email_validator-1.1.1-py2.py3-none-any.whl", hash = "sha256:5f246ae8d81ce3000eade06595b7bb55a4cf350d559e890182a1466a21f25067"},
|
||||
{file = "email_validator-1.1.1.tar.gz", hash = "sha256:63094045c3e802c3d3d575b18b004a531c36243ca8d1cec785ff6bfcb04185bb"},
|
||||
]
|
||||
execnet = [
|
||||
{file = "execnet-1.7.1-py2.py3-none-any.whl", hash = "sha256:d4efd397930c46415f62f8a31388d6be4f27a91d7550eb79bc64a756e0056547"},
|
||||
{file = "execnet-1.7.1.tar.gz", hash = "sha256:cacb9df31c9680ec5f95553976c4da484d407e85e41c83cb812aa014f0eddc50"},
|
||||
]
|
||||
fastapi = [
|
||||
{file = "fastapi-0.54.2-py3-none-any.whl", hash = "sha256:c8651f8316956240c2ffe5bc05c334c8359a3887e642720a9b23319c51e82907"},
|
||||
{file = "fastapi-0.54.2.tar.gz", hash = "sha256:fff1b4a7fdf4812abb4507fb7aa30ef4206a0435839626ebe3b2871ec9aa367f"},
|
||||
]
|
||||
flake8 = [
|
||||
{file = "flake8-3.8.1-py2.py3-none-any.whl", hash = "sha256:6c1193b0c3f853ef763969238f6c81e9e63ace9d024518edc020d5f1d6d93195"},
|
||||
{file = "flake8-3.8.1.tar.gz", hash = "sha256:ea6623797bf9a52f4c9577d780da0bb17d65f870213f7b5bcc9fca82540c31d5"},
|
||||
]
|
||||
graphene = [
|
||||
{file = "graphene-2.1.8-py2.py3-none-any.whl", hash = "sha256:09165f03e1591b76bf57b133482db9be6dac72c74b0a628d3c93182af9c5a896"},
|
||||
{file = "graphene-2.1.8.tar.gz", hash = "sha256:2cbe6d4ef15cfc7b7805e0760a0e5b80747161ce1b0f990dfdc0d2cf497c12f9"},
|
||||
@ -826,6 +1099,10 @@ iso8601 = [
|
||||
{file = "iso8601-0.1.12-py3-none-any.whl", hash = "sha256:bbbae5fb4a7abfe71d4688fd64bff70b91bbd74ef6a99d964bab18f7fdf286dd"},
|
||||
{file = "iso8601-0.1.12.tar.gz", hash = "sha256:49c4b20e1f38aa5cf109ddcd39647ac419f928512c869dc01d5c7098eddede82"},
|
||||
]
|
||||
isort = [
|
||||
{file = "isort-4.3.21-py2.py3-none-any.whl", hash = "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"},
|
||||
{file = "isort-4.3.21.tar.gz", hash = "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1"},
|
||||
]
|
||||
itsdangerous = [
|
||||
{file = "itsdangerous-1.1.0-py2.py3-none-any.whl", hash = "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"},
|
||||
{file = "itsdangerous-1.1.0.tar.gz", hash = "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19"},
|
||||
@ -862,13 +1139,16 @@ markupsafe = [
|
||||
{file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"},
|
||||
{file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"},
|
||||
{file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"},
|
||||
{file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"},
|
||||
{file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"},
|
||||
{file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"},
|
||||
{file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"},
|
||||
{file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"},
|
||||
{file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"},
|
||||
]
|
||||
mccabe = [
|
||||
{file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
|
||||
{file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
|
||||
]
|
||||
more-itertools = [
|
||||
{file = "more-itertools-8.3.0.tar.gz", hash = "sha256:558bb897a2232f5e4f8e2399089e35aecb746e1f9191b6584a151647e89267be"},
|
||||
{file = "more_itertools-8.3.0-py3-none-any.whl", hash = "sha256:7818f596b1e87be009031c7653d01acc46ed422e6656b394b0f765ce66ed4982"},
|
||||
]
|
||||
orjson = [
|
||||
{file = "orjson-3.0.1-cp36-cp36m-macosx_10_7_x86_64.whl", hash = "sha256:5bf352dac1a9433a55b3558cea484f3548e58e137f883eabbf7a8eb489389fca"},
|
||||
{file = "orjson-3.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:6ecdb764a81260bb65fc37b1733bbb02666c5aa97835a87febbad130c6b4b9ac"},
|
||||
@ -886,10 +1166,22 @@ orjson = [
|
||||
{file = "orjson-3.0.1-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:b9c0f7d14cac42a41084d784b93288ebdf61537fa2d160610c1f1dd9a6f1d62f"},
|
||||
{file = "orjson-3.0.1.tar.gz", hash = "sha256:a75f72c8a7cb0602c2fc7e9806554912b70bf79bbe559e55e1951b1f279c0ad2"},
|
||||
]
|
||||
packaging = [
|
||||
{file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"},
|
||||
{file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"},
|
||||
]
|
||||
passlib = [
|
||||
{file = "passlib-1.7.2-py2.py3-none-any.whl", hash = "sha256:68c35c98a7968850e17f1b6892720764cc7eed0ef2b7cb3116a89a28e43fe177"},
|
||||
{file = "passlib-1.7.2.tar.gz", hash = "sha256:8d666cef936198bc2ab47ee9b0410c94adf2ba798e5a84bf220be079ae7ab6a8"},
|
||||
]
|
||||
pathspec = [
|
||||
{file = "pathspec-0.8.0-py2.py3-none-any.whl", hash = "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0"},
|
||||
{file = "pathspec-0.8.0.tar.gz", hash = "sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061"},
|
||||
]
|
||||
pluggy = [
|
||||
{file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
|
||||
{file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
|
||||
]
|
||||
promise = [
|
||||
{file = "promise-2.3.tar.gz", hash = "sha256:dfd18337c523ba4b6a58801c164c1904a9d4d1b1747c7d5dbf45b693a49d93d0"},
|
||||
]
|
||||
@ -897,6 +1189,14 @@ prompt-toolkit = [
|
||||
{file = "prompt_toolkit-3.0.5-py3-none-any.whl", hash = "sha256:df7e9e63aea609b1da3a65641ceaf5bc7d05e0a04de5bd45d05dbeffbabf9e04"},
|
||||
{file = "prompt_toolkit-3.0.5.tar.gz", hash = "sha256:563d1a4140b63ff9dd587bda9557cffb2fe73650205ab6f4383092fb882e7dc8"},
|
||||
]
|
||||
py = [
|
||||
{file = "py-1.8.1-py2.py3-none-any.whl", hash = "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"},
|
||||
{file = "py-1.8.1.tar.gz", hash = "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa"},
|
||||
]
|
||||
pycodestyle = [
|
||||
{file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"},
|
||||
{file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"},
|
||||
]
|
||||
pycparser = [
|
||||
{file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"},
|
||||
{file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"},
|
||||
@ -920,6 +1220,10 @@ pydantic = [
|
||||
{file = "pydantic-1.5.1-py36.py37.py38-none-any.whl", hash = "sha256:70f27d2f0268f490fe3de0a9b6fca7b7492b8fd6623f9fecd25b221ebee385e3"},
|
||||
{file = "pydantic-1.5.1.tar.gz", hash = "sha256:f0018613c7a0d19df3240c2a913849786f21b6539b9f23d85ce4067489dfacfa"},
|
||||
]
|
||||
pyflakes = [
|
||||
{file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"},
|
||||
{file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"},
|
||||
]
|
||||
pyjwt = [
|
||||
{file = "PyJWT-1.7.1-py2.py3-none-any.whl", hash = "sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e"},
|
||||
{file = "PyJWT-1.7.1.tar.gz", hash = "sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96"},
|
||||
@ -928,9 +1232,25 @@ pymysql = [
|
||||
{file = "PyMySQL-0.9.2-py2.py3-none-any.whl", hash = "sha256:95f057328357e0e13a30e67857a8c694878b0175797a9a203ee7adbfb9b1ec5f"},
|
||||
{file = "PyMySQL-0.9.2.tar.gz", hash = "sha256:9ec760cbb251c158c19d6c88c17ca00a8632bac713890e465b2be01fdc30713f"},
|
||||
]
|
||||
pyparsing = [
|
||||
{file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
|
||||
{file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
|
||||
]
|
||||
pypika = [
|
||||
{file = "PyPika-0.37.6.tar.gz", hash = "sha256:64510fa36667e8bb654bdc1be5a3a77bac1dbc2f03d4848efac08e39d9cac6f5"},
|
||||
]
|
||||
pytest = [
|
||||
{file = "pytest-5.4.2-py3-none-any.whl", hash = "sha256:95c710d0a72d91c13fae35dce195633c929c3792f54125919847fdcdf7caa0d3"},
|
||||
{file = "pytest-5.4.2.tar.gz", hash = "sha256:eb2b5e935f6a019317e455b6da83dd8650ac9ffd2ee73a7b657a30873d67a698"},
|
||||
]
|
||||
pytest-forked = [
|
||||
{file = "pytest-forked-1.1.3.tar.gz", hash = "sha256:1805699ed9c9e60cb7a8179b8d4fa2b8898098e82d229b0825d8095f0f261100"},
|
||||
{file = "pytest_forked-1.1.3-py2.py3-none-any.whl", hash = "sha256:1ae25dba8ee2e56fb47311c9638f9e58552691da87e82d25b0ce0e4bf52b7d87"},
|
||||
]
|
||||
pytest-xdist = [
|
||||
{file = "pytest-xdist-1.32.0.tar.gz", hash = "sha256:1d4166dcac69adb38eeaedb88c8fada8588348258a3492ab49ba9161f2971129"},
|
||||
{file = "pytest_xdist-1.32.0-py2.py3-none-any.whl", hash = "sha256:ba5ec9fde3410bd9a116ff7e4f26c92e02fa3d27975ef3ad03f330b3d4b54e91"},
|
||||
]
|
||||
python-dotenv = [
|
||||
{file = "python-dotenv-0.13.0.tar.gz", hash = "sha256:3b9909bc96b0edc6b01586e1eed05e71174ef4e04c71da5786370cebea53ad74"},
|
||||
{file = "python_dotenv-0.13.0-py2.py3-none-any.whl", hash = "sha256:25c0ff1a3e12f4bde8d592cc254ab075cfe734fc5dd989036716fd17ee7e5ec7"},
|
||||
@ -982,6 +1302,29 @@ pyyaml = [
|
||||
{file = "PyYAML-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee"},
|
||||
{file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"},
|
||||
]
|
||||
regex = [
|
||||
{file = "regex-2020.5.14-cp27-cp27m-win32.whl", hash = "sha256:e565569fc28e3ba3e475ec344d87ed3cd8ba2d575335359749298a0899fe122e"},
|
||||
{file = "regex-2020.5.14-cp27-cp27m-win_amd64.whl", hash = "sha256:d466967ac8e45244b9dfe302bbe5e3337f8dc4dec8d7d10f5e950d83b140d33a"},
|
||||
{file = "regex-2020.5.14-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:27ff7325b297fb6e5ebb70d10437592433601c423f5acf86e5bc1ee2919b9561"},
|
||||
{file = "regex-2020.5.14-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ea55b80eb0d1c3f1d8d784264a6764f931e172480a2f1868f2536444c5f01e01"},
|
||||
{file = "regex-2020.5.14-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:c9bce6e006fbe771a02bda468ec40ffccbf954803b470a0345ad39c603402577"},
|
||||
{file = "regex-2020.5.14-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:d881c2e657c51d89f02ae4c21d9adbef76b8325fe4d5cf0e9ad62f850f3a98fd"},
|
||||
{file = "regex-2020.5.14-cp36-cp36m-win32.whl", hash = "sha256:99568f00f7bf820c620f01721485cad230f3fb28f57d8fbf4a7967ec2e446994"},
|
||||
{file = "regex-2020.5.14-cp36-cp36m-win_amd64.whl", hash = "sha256:70c14743320a68c5dac7fc5a0f685be63bc2024b062fe2aaccc4acc3d01b14a1"},
|
||||
{file = "regex-2020.5.14-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:a7c37f048ec3920783abab99f8f4036561a174f1314302ccfa4e9ad31cb00eb4"},
|
||||
{file = "regex-2020.5.14-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:89d76ce33d3266173f5be80bd4efcbd5196cafc34100fdab814f9b228dee0fa4"},
|
||||
{file = "regex-2020.5.14-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:51f17abbe973c7673a61863516bdc9c0ef467407a940f39501e786a07406699c"},
|
||||
{file = "regex-2020.5.14-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:ce5cc53aa9fbbf6712e92c7cf268274eaff30f6bd12a0754e8133d85a8fb0f5f"},
|
||||
{file = "regex-2020.5.14-cp37-cp37m-win32.whl", hash = "sha256:8044d1c085d49673aadb3d7dc20ef5cb5b030c7a4fa253a593dda2eab3059929"},
|
||||
{file = "regex-2020.5.14-cp37-cp37m-win_amd64.whl", hash = "sha256:c2062c7d470751b648f1cacc3f54460aebfc261285f14bc6da49c6943bd48bdd"},
|
||||
{file = "regex-2020.5.14-cp38-cp38-manylinux1_i686.whl", hash = "sha256:329ba35d711e3428db6b45a53b1b13a0a8ba07cbbcf10bbed291a7da45f106c3"},
|
||||
{file = "regex-2020.5.14-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:579ea215c81d18da550b62ff97ee187b99f1b135fd894a13451e00986a080cad"},
|
||||
{file = "regex-2020.5.14-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:3a9394197664e35566242686d84dfd264c07b20f93514e2e09d3c2b3ffdf78fe"},
|
||||
{file = "regex-2020.5.14-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ce367d21f33e23a84fb83a641b3834dd7dd8e9318ad8ff677fbfae5915a239f7"},
|
||||
{file = "regex-2020.5.14-cp38-cp38-win32.whl", hash = "sha256:1386e75c9d1574f6aa2e4eb5355374c8e55f9aac97e224a8a5a6abded0f9c927"},
|
||||
{file = "regex-2020.5.14-cp38-cp38-win_amd64.whl", hash = "sha256:7e61be8a2900897803c293247ef87366d5df86bf701083b6c43119c7c6c99108"},
|
||||
{file = "regex-2020.5.14.tar.gz", hash = "sha256:ce450ffbfec93821ab1fea94779a8440e10cf63819be6e176eb1973a6017aff5"},
|
||||
]
|
||||
requests = [
|
||||
{file = "requests-2.23.0-py2.py3-none-any.whl", hash = "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee"},
|
||||
{file = "requests-2.23.0.tar.gz", hash = "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"},
|
||||
@ -991,8 +1334,8 @@ rx = [
|
||||
{file = "Rx-1.6.1.tar.gz", hash = "sha256:13a1d8d9e252625c173dc795471e614eadfe1cf40ffc684e08b8fff0d9748c23"},
|
||||
]
|
||||
six = [
|
||||
{file = "six-1.14.0-py2.py3-none-any.whl", hash = "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"},
|
||||
{file = "six-1.14.0.tar.gz", hash = "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a"},
|
||||
{file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"},
|
||||
{file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"},
|
||||
]
|
||||
starlette = [
|
||||
{file = "starlette-0.13.2-py3-none-any.whl", hash = "sha256:6169ee78ded501095d1dda7b141a1dc9f9934d37ad23196e180150ace2c6449b"},
|
||||
@ -1009,6 +1352,29 @@ toml = [
|
||||
tortoise-orm = [
|
||||
{file = "tortoise-orm-0.16.11.tar.gz", hash = "sha256:08a25c59a171bdabe9469d1606f8c4ae41516504cc501f5d60c1b953f3e186bb"},
|
||||
]
|
||||
typed-ast = [
|
||||
{file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"},
|
||||
{file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"},
|
||||
{file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"},
|
||||
{file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"},
|
||||
{file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"},
|
||||
{file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"},
|
||||
{file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"},
|
||||
{file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"},
|
||||
{file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"},
|
||||
{file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"},
|
||||
{file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"},
|
||||
{file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"},
|
||||
{file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"},
|
||||
{file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"},
|
||||
{file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"},
|
||||
{file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"},
|
||||
{file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"},
|
||||
{file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"},
|
||||
{file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"},
|
||||
{file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"},
|
||||
{file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"},
|
||||
]
|
||||
typing-extensions = [
|
||||
{file = "typing_extensions-3.7.4.2-py2-none-any.whl", hash = "sha256:f8d2bd89d25bc39dabe7d23df520442fa1d8969b82544370e03d88b5a591c392"},
|
||||
{file = "typing_extensions-3.7.4.2-py3-none-any.whl", hash = "sha256:6e95524d8a547a91e08f404ae485bbb71962de46967e1b71a0cb89af24e761c5"},
|
||||
|
@ -2,7 +2,7 @@
|
||||
name = "fastapi-admin"
|
||||
version = "0.2.6"
|
||||
description = "Fast Admin Dashboard based on fastapi and tortoise-orm and rest-admin."
|
||||
authors = ["long2ice <long2ice@prismslight.com>"]
|
||||
authors = ["long2ice <long2ice@gmail.com>"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.8"
|
||||
@ -23,10 +23,16 @@ prompt_toolkit = "*"
|
||||
[tool.poetry.dev-dependencies]
|
||||
taskipy = "*"
|
||||
asynctest = "*"
|
||||
flake8 = "*"
|
||||
isort = "*"
|
||||
black = "^19.10b0"
|
||||
pytest = "*"
|
||||
pytest-xdist = "*"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry>=0.12"]
|
||||
build-backend = "poetry.masonry.api"
|
||||
|
||||
[tool.taskipy.tasks]
|
||||
export = "poetry export -f requirements.txt --without-hashes > requirements.txt"
|
||||
export = "poetry export -f requirements.txt --without-hashes > requirements.txt"
|
||||
export-dev = "poetry export -f requirements.txt --dev --without-hashes > requirements-dev.txt"
|
||||
|
78
requirements-dev.txt
Normal file
78
requirements-dev.txt
Normal file
@ -0,0 +1,78 @@
|
||||
aiofiles==0.5.0
|
||||
aiomysql==0.0.20
|
||||
aiosqlite==0.13.0
|
||||
aniso8601==7.0.0
|
||||
apipkg==1.5
|
||||
appdirs==1.4.4
|
||||
async-exit-stack==1.0.1
|
||||
async-generator==1.10
|
||||
asynctest==0.13.0
|
||||
atomicwrites==1.4.0; sys_platform == "win32"
|
||||
attrs==19.3.0
|
||||
bcrypt==3.1.7
|
||||
black==19.10b0
|
||||
certifi==2020.4.5.1
|
||||
cffi==1.14.0
|
||||
chardet==3.0.4
|
||||
ciso8601==2.1.3; sys_platform != "win32" and implementation_name == "cpython"
|
||||
click==7.1.2
|
||||
colorama==0.4.3
|
||||
cryptography==2.9.2
|
||||
dnspython==1.16.0
|
||||
email-validator==1.1.1
|
||||
execnet==1.7.1
|
||||
fastapi==0.54.2
|
||||
flake8==3.8.1
|
||||
graphene==2.1.8
|
||||
graphql-core==2.3.2
|
||||
graphql-relay==2.0.1
|
||||
h11==0.9.0
|
||||
httptools==0.1.1; sys_platform != "win32" and sys_platform != "cygwin" and platform_python_implementation != "PyPy"
|
||||
idna==2.9
|
||||
iso8601==0.1.12; sys_platform == "win32" or implementation_name != "cpython"
|
||||
isort==4.3.21
|
||||
itsdangerous==1.1.0
|
||||
jinja2==2.11.2
|
||||
markupsafe==1.1.1
|
||||
mccabe==0.6.1
|
||||
more-itertools==8.3.0
|
||||
orjson==3.0.1
|
||||
packaging==20.4
|
||||
passlib==1.7.2
|
||||
pathspec==0.8.0
|
||||
pluggy==0.13.1
|
||||
promise==2.3
|
||||
prompt-toolkit==3.0.5
|
||||
py==1.8.1
|
||||
pycodestyle==2.6.0
|
||||
pycparser==2.20
|
||||
pydantic==1.5.1
|
||||
pyflakes==2.2.0
|
||||
pyjwt==1.7.1
|
||||
pymysql==0.9.2
|
||||
pyparsing==2.4.7
|
||||
pypika==0.37.6
|
||||
pytest==5.4.2
|
||||
pytest-forked==1.1.3
|
||||
pytest-xdist==1.32.0
|
||||
python-dotenv==0.13.0
|
||||
python-multipart==0.0.5
|
||||
python-rapidjson==0.9.1
|
||||
pyyaml==5.3.1
|
||||
regex==2020.5.14
|
||||
requests==2.23.0
|
||||
rx==1.6.1
|
||||
six==1.15.0
|
||||
starlette==0.13.2
|
||||
taskipy==1.2.1
|
||||
toml==0.10.1
|
||||
tortoise-orm==0.16.11
|
||||
typed-ast==1.4.1
|
||||
typing-extensions==3.7.4.2
|
||||
ujson==2.0.3
|
||||
urllib3==1.25.9
|
||||
uvicorn==0.11.5
|
||||
uvloop==0.14.0
|
||||
wcwidth==0.1.9
|
||||
websockets==8.1
|
||||
xlsxwriter==1.2.8
|
@ -40,7 +40,7 @@ python-rapidjson==0.9.1
|
||||
pyyaml==5.3.1
|
||||
requests==2.23.0
|
||||
rx==1.6.1
|
||||
six==1.14.0
|
||||
six==1.15.0
|
||||
starlette==0.13.2
|
||||
tortoise-orm==0.16.11
|
||||
typing-extensions==3.7.4.2
|
||||
|
Reference in New Issue
Block a user