mirror of
https://github.com/fastapi-admin/fastapi-admin.git
synced 2025-08-14 18:58:13 +08:00
add custom login_view
This commit is contained in:
@ -4,6 +4,10 @@ ChangeLog
|
||||
|
||||
0.2
|
||||
===
|
||||
0.2.7
|
||||
-----
|
||||
- Add custom login_view.
|
||||
|
||||
0.2.6
|
||||
-----
|
||||
- Fix createsuperuser error.
|
||||
|
@ -1,15 +1,11 @@
|
||||
import os
|
||||
|
||||
import uvicorn
|
||||
from fastapi import Depends, FastAPI
|
||||
from fastapi import FastAPI
|
||||
from starlette.middleware.cors import CORSMiddleware
|
||||
from starlette.templating import Jinja2Templates
|
||||
from tortoise.contrib.fastapi import register_tortoise
|
||||
from tortoise.contrib.pydantic import pydantic_queryset_creator
|
||||
|
||||
from fastapi_admin.depends import get_model
|
||||
from fastapi_admin.factory import app as admin_app
|
||||
from fastapi_admin.schemas import BulkIn
|
||||
from fastapi_admin.site import Site
|
||||
|
||||
TORTOISE_ORM = {
|
||||
@ -22,21 +18,6 @@ TORTOISE_ORM = {
|
||||
},
|
||||
}
|
||||
|
||||
templates = Jinja2Templates(directory="examples/templates")
|
||||
|
||||
|
||||
@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",)
|
||||
async def home():
|
||||
return {"html": templates.get_template("home.html").render()}
|
||||
|
||||
|
||||
def create_app():
|
||||
fast_app = FastAPI(debug=False)
|
||||
@ -73,6 +54,7 @@ async def start_up():
|
||||
locale_switcher=True,
|
||||
theme_switcher=True,
|
||||
),
|
||||
login_view="examples.routes.login",
|
||||
)
|
||||
|
||||
|
||||
|
37
examples/routes.py
Normal file
37
examples/routes.py
Normal file
@ -0,0 +1,37 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from starlette.templating import Jinja2Templates
|
||||
from tortoise.contrib.pydantic import pydantic_queryset_creator
|
||||
|
||||
from fastapi_admin.depends import get_model
|
||||
from fastapi_admin.factory import app
|
||||
from fastapi_admin.schemas import BulkIn
|
||||
|
||||
templates = Jinja2Templates(directory="templates")
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.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()
|
||||
|
||||
|
||||
@router.get("/home",)
|
||||
async def home():
|
||||
return {"html": templates.get_template("home.html").render()}
|
||||
|
||||
|
||||
async def login():
|
||||
return {
|
||||
"user": {
|
||||
"username": "admin",
|
||||
"is_superuser": False,
|
||||
"avatar": "https://avatars2.githubusercontent.com/u/13377178?s=460&u=d150d522579f41a52a0b3dd8ea997e0161313b6e&v=4",
|
||||
},
|
||||
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoyfQ.HSlcYkOEQewxyPuaqcVwCcw_wkbLB50Ws1-ZxfPoLAQ",
|
||||
}
|
||||
|
||||
|
||||
app.include_router(router)
|
@ -1,5 +1,4 @@
|
||||
import argparse
|
||||
import importlib
|
||||
import sys
|
||||
|
||||
from colorama import Fore, init
|
||||
@ -7,7 +6,7 @@ from prompt_toolkit import PromptSession
|
||||
from tortoise import Tortoise, run_async
|
||||
|
||||
from fastapi_admin import enums
|
||||
from fastapi_admin.common import pwd_context
|
||||
from fastapi_admin.common import import_obj, pwd_context
|
||||
from fastapi_admin.models import Permission
|
||||
|
||||
init(autoreset=True)
|
||||
@ -27,13 +26,6 @@ class Logger:
|
||||
print(Fore.RED + text)
|
||||
|
||||
|
||||
def import_obj(path):
|
||||
splits = path.split(".")
|
||||
module = ".".join(splits[:-1])
|
||||
class_name = splits[-1]
|
||||
return getattr(importlib.import_module(module), class_name)
|
||||
|
||||
|
||||
async def init_tortoise(args):
|
||||
await Tortoise.init(config=import_obj(args.config))
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
import importlib
|
||||
from copy import deepcopy
|
||||
|
||||
from passlib.context import CryptContext
|
||||
@ -33,3 +34,15 @@ async def handle_m2m_fields_create_or_update(body, m2m_fields, model, create=Tru
|
||||
m2m_objs = await m2m_model.filter(pk__in=v)
|
||||
await m2m_related.add(*m2m_objs)
|
||||
return obj
|
||||
|
||||
|
||||
def import_obj(path: str):
|
||||
"""
|
||||
import obj from module path
|
||||
:param path:
|
||||
:return:
|
||||
"""
|
||||
splits = path.split(".")
|
||||
module = ".".join(splits[:-1])
|
||||
class_name = splits[-1]
|
||||
return getattr(importlib.import_module(module), class_name)
|
||||
|
@ -1,13 +1,36 @@
|
||||
from copy import deepcopy
|
||||
from typing import Any, Dict, List, Optional, Type
|
||||
|
||||
import jwt
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from starlette.status import HTTP_403_FORBIDDEN
|
||||
from tortoise import Model, Tortoise
|
||||
|
||||
from .common import import_obj, pwd_context
|
||||
from .exceptions import exception_handler
|
||||
from .schemas import LoginIn
|
||||
from .shortcuts import get_object_or_404
|
||||
from .site import Field, Menu, Resource, Site
|
||||
|
||||
|
||||
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!")
|
||||
if not pwd_context.verify(login_in.password, user.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,
|
||||
},
|
||||
"token": jwt.encode({"user_id": user.pk}, app.admin_secret, algorithm="HS256"),
|
||||
}
|
||||
return ret
|
||||
|
||||
|
||||
class AdminApp(FastAPI):
|
||||
models: Any
|
||||
admin_secret: str
|
||||
@ -115,9 +138,11 @@ class AdminApp(FastAPI):
|
||||
tortoise_app: str,
|
||||
admin_secret: str,
|
||||
permission: bool = False,
|
||||
login_view: Optional[str] = None,
|
||||
):
|
||||
"""
|
||||
init admin site
|
||||
:param login_view:
|
||||
:param tortoise_app:
|
||||
:param permission: active builtin permission
|
||||
:param site:
|
||||
@ -134,6 +159,10 @@ class AdminApp(FastAPI):
|
||||
if not site.menus:
|
||||
site.menus = self._build_default_menus(permission)
|
||||
self._get_model_menu_mapping(site.menus)
|
||||
if login_view:
|
||||
self.add_api_route("/login", import_obj(login_view), methods=["POST"])
|
||||
else:
|
||||
self.add_api_route("/login", login, methods=["POST"])
|
||||
|
||||
def _exclude_field(self, resource: str, field: str):
|
||||
"""
|
||||
|
@ -2,8 +2,7 @@ from fastapi import Depends
|
||||
|
||||
from ..depends import jwt_required
|
||||
from ..factory import app
|
||||
from . import login, rest, site
|
||||
from . import rest, site
|
||||
|
||||
app.include_router(login.router)
|
||||
app.include_router(site.router)
|
||||
app.include_router(rest.router, dependencies=[Depends(jwt_required)], prefix="/rest")
|
||||
|
@ -1,29 +0,0 @@
|
||||
import jwt
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from starlette.status import HTTP_403_FORBIDDEN
|
||||
|
||||
from ..common import pwd_context
|
||||
from ..factory import app
|
||||
from ..schemas import LoginIn
|
||||
from ..shortcuts import get_object_or_404
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@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!")
|
||||
if not pwd_context.verify(login_in.password, user.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,
|
||||
},
|
||||
"token": jwt.encode({"user_id": user.pk}, app.admin_secret, algorithm="HS256"),
|
||||
}
|
||||
return ret
|
66
poetry.lock
generated
66
poetry.lock
generated
@ -161,15 +161,6 @@ optional = false
|
||||
python-versions = "*"
|
||||
version = "3.0.4"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Fast ISO8601 date time parser for Python written in C"
|
||||
marker = "sys_platform != \"win32\" and implementation_name == \"cpython\""
|
||||
name = "ciso8601"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "2.1.3"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Composable command line interface toolkit"
|
||||
@ -406,7 +397,6 @@ version = "2.9"
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Simple module to parse ISO 8601 dates"
|
||||
marker = "sys_platform == \"win32\" or implementation_name != \"cpython\""
|
||||
name = "iso8601"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
@ -641,7 +631,7 @@ description = "pytest: simple powerful testing with Python"
|
||||
name = "pytest"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
version = "5.4.2"
|
||||
version = "5.4.3"
|
||||
|
||||
[package.dependencies]
|
||||
atomicwrites = ">=1.0"
|
||||
@ -801,15 +791,17 @@ description = "Easy async ORM for python, built with relations in mind"
|
||||
name = "tortoise-orm"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "0.16.12"
|
||||
version = "0.16.13"
|
||||
|
||||
[package.dependencies]
|
||||
aiosqlite = ">=0.11.0"
|
||||
ciso8601 = ">=2.1.2"
|
||||
iso8601 = ">=0.1.12"
|
||||
pypika = ">=0.36.5"
|
||||
typing-extensions = ">=3.7"
|
||||
|
||||
[package.extras]
|
||||
accel = ["python-rapidjson", "ciso8601 (>=2.1.2)", "uvloop (>=0.12.0)"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "a fork of Python 2 and 3 ast modules with type comment support"
|
||||
@ -831,8 +823,8 @@ category = "main"
|
||||
description = "Ultra fast JSON encoder and decoder for Python"
|
||||
name = "ujson"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
version = "2.0.3"
|
||||
python-versions = ">=3.5"
|
||||
version = "3.0.0"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
@ -879,7 +871,7 @@ description = "Measures the displayed width of unicode strings in a terminal"
|
||||
name = "wcwidth"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "0.2.2"
|
||||
version = "0.2.3"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
@ -1008,9 +1000,6 @@ chardet = [
|
||||
{file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"},
|
||||
{file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"},
|
||||
]
|
||||
ciso8601 = [
|
||||
{file = "ciso8601-2.1.3.tar.gz", hash = "sha256:bdbb5b366058b1c87735603b23060962c439ac9be66f1ae91e8c7dbd7d59e262"},
|
||||
]
|
||||
click = [
|
||||
{file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"},
|
||||
{file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"},
|
||||
@ -1139,6 +1128,11 @@ 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 = [
|
||||
@ -1240,8 +1234,8 @@ pypika = [
|
||||
{file = "PyPika-0.37.7.tar.gz", hash = "sha256:20bebc05983cd401d428e3beb62d037e5f0271daab2bb5aba82f4e092d4a3694"},
|
||||
]
|
||||
pytest = [
|
||||
{file = "pytest-5.4.2-py3-none-any.whl", hash = "sha256:95c710d0a72d91c13fae35dce195633c929c3792f54125919847fdcdf7caa0d3"},
|
||||
{file = "pytest-5.4.2.tar.gz", hash = "sha256:eb2b5e935f6a019317e455b6da83dd8650ac9ffd2ee73a7b657a30873d67a698"},
|
||||
{file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"},
|
||||
{file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"},
|
||||
]
|
||||
pytest-forked = [
|
||||
{file = "pytest-forked-1.1.3.tar.gz", hash = "sha256:1805699ed9c9e60cb7a8179b8d4fa2b8898098e82d229b0825d8095f0f261100"},
|
||||
@ -1350,7 +1344,7 @@ toml = [
|
||||
{file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"},
|
||||
]
|
||||
tortoise-orm = [
|
||||
{file = "tortoise-orm-0.16.12.tar.gz", hash = "sha256:170e4bbfe1c98223ad1fba33d7fded7923e4bb49c9d74c78bd173a0ebc861658"},
|
||||
{file = "tortoise-orm-0.16.13.tar.gz", hash = "sha256:5f6fa4430a570172cb49517a97d45338dbfb1a690ed707030467efd154e67855"},
|
||||
]
|
||||
typed-ast = [
|
||||
{file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"},
|
||||
@ -1381,13 +1375,23 @@ typing-extensions = [
|
||||
{file = "typing_extensions-3.7.4.2.tar.gz", hash = "sha256:79ee589a3caca649a9bfd2a8de4709837400dfa00b6cc81962a1e6a1815969ae"},
|
||||
]
|
||||
ujson = [
|
||||
{file = "ujson-2.0.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:7ae13733d9467d16ccac2f38212cdee841b49ae927085c533425be9076b0bc9d"},
|
||||
{file = "ujson-2.0.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:6217c63a36e9b26e9271e686d212397ce7fb04c07d85509dd4e2ed73493320f8"},
|
||||
{file = "ujson-2.0.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c8369ef49169804944e920c427e350182e33756422b69989c55608fc28bebf98"},
|
||||
{file = "ujson-2.0.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0c23f21e8d2b60efab57bc6ce9d1fb7c4e96f4bfefbf5a6043a3f3309e2a738a"},
|
||||
{file = "ujson-2.0.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:3d1f4705a4ec1e48ff383a4d92299d8ec25e9a8158bcea619912440948117634"},
|
||||
{file = "ujson-2.0.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2ab88e330405315512afe9276f29a60e9b3439187b273665630a57ed7fe1d936"},
|
||||
{file = "ujson-2.0.3.tar.gz", hash = "sha256:bd2deffc983827510e5145fb66e4cc0f577480c62fe0b4882139f8f7d27ae9a3"},
|
||||
{file = "ujson-3.0.0-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:0959a5b569e192459b492b007e3fd63d8f4b4bcb4f69dcddca850a9b9dfe3e7a"},
|
||||
{file = "ujson-3.0.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:154f778f0b028390067aaedce8399730d4f528a16a1c214fe4eeb9c4e4f51810"},
|
||||
{file = "ujson-3.0.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:019a17e7162f26e264f1645bb41630f7103d178c092ea4bb8f3b16126c3ea210"},
|
||||
{file = "ujson-3.0.0-cp35-cp35m-win_amd64.whl", hash = "sha256:670018d4ab4b0755a7234a9f4791723abcd0506c0eed33b2ed50579c4aff31f2"},
|
||||
{file = "ujson-3.0.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:634c206f4fb3be7e4523768c636d2dd41cb9c7130e2d219ef8305b8fb6f4838e"},
|
||||
{file = "ujson-3.0.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:3bd791d17a175c1c6566aeaec1755b58e3f021fe9bb62f10f02b656b299199f5"},
|
||||
{file = "ujson-3.0.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0379ffc7484b862a292e924c15ad5f1c5306d4271e2efd162144812afb08ff97"},
|
||||
{file = "ujson-3.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f40bb0d0cb534aad3e24884cf864bda7a71eb5984bd1da61d1711bbfb3be2c38"},
|
||||
{file = "ujson-3.0.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:0f33359908df32033195bfdd59ba2bfb90a23cb280ef9a0ba11e5013a53d7fd9"},
|
||||
{file = "ujson-3.0.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:bea2958c7b5bf4f191f0def751b6f7c8b208edb5f7277e21776329f2ca042385"},
|
||||
{file = "ujson-3.0.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f854702a9aff3a445f4a0b715d240f2a3d84014d8ae8aad05a982c7ffab12525"},
|
||||
{file = "ujson-3.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c04d253fec814657fd9f150ef2333dbd0bc6f46208355aa753a29e0696b7fa7e"},
|
||||
{file = "ujson-3.0.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:a32f2def62b10e8a19084d17d40363c4da1ac5f52d300a9e99d7efb49fe5f34a"},
|
||||
{file = "ujson-3.0.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:9c68557da3e3ad57e0105aceba0cce5f8f7cd07d207c3860e59c0b3044532830"},
|
||||
{file = "ujson-3.0.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:0e2352b60c4ac4fc75b723435faf36ef5e7f3bfb988adb4d589b5e0e6e1d90aa"},
|
||||
{file = "ujson-3.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:c841a6450d64c24c64cbcca429bab22cdb6daef5eaddfdfebe798a5e9e5aff4c"},
|
||||
{file = "ujson-3.0.0.tar.gz", hash = "sha256:e0199849d61cc6418f94d52a314c6a27524d65e82174d2a043fb718f73d1520d"},
|
||||
]
|
||||
urllib3 = [
|
||||
{file = "urllib3-1.25.9-py2.py3-none-any.whl", hash = "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"},
|
||||
@ -1409,8 +1413,8 @@ uvloop = [
|
||||
{file = "uvloop-0.14.0.tar.gz", hash = "sha256:123ac9c0c7dd71464f58f1b4ee0bbd81285d96cdda8bc3519281b8973e3a461e"},
|
||||
]
|
||||
wcwidth = [
|
||||
{file = "wcwidth-0.2.2-py2.py3-none-any.whl", hash = "sha256:b651b6b081476420e4e9ae61239ac4c1b49d0c5ace42b2e81dc2ff49ed50c566"},
|
||||
{file = "wcwidth-0.2.2.tar.gz", hash = "sha256:3de2e41158cb650b91f9654cbf9a3e053cee0719c9df4ddc11e4b568669e9829"},
|
||||
{file = "wcwidth-0.2.3-py2.py3-none-any.whl", hash = "sha256:980fbf4f3c196c0f329cdcd1e84c554d6a211f18e252e525a0cf4223154a41d6"},
|
||||
{file = "wcwidth-0.2.3.tar.gz", hash = "sha256:edbc2b718b4db6cdf393eefe3a420183947d6aa312505ce6754516f458ff8830"},
|
||||
]
|
||||
websockets = [
|
||||
{file = "websockets-8.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:3762791ab8b38948f0c4d281c8b2ddfa99b7e510e46bd8dfa942a5fff621068c"},
|
||||
|
Reference in New Issue
Block a user