🎨 black

This commit is contained in:
Mawoka
2022-04-09 11:06:36 +02:00
parent fd0b51d7e3
commit 6d5ba5ce74
18 changed files with 403 additions and 159 deletions
+3
View File
@@ -0,0 +1,3 @@
[flake8]
max-line-length = 120
extend-ignore = E203
+1
View File
@@ -28,6 +28,7 @@ coverage = "*"
pytest = "*" pytest = "*"
pytest-asyncio = "*" pytest-asyncio = "*"
flake8 = "*" flake8 = "*"
black = "*"
[requires] [requires]
python_version = "3.10" python_version = "3.10"
Generated
+64 -5
View File
@@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "5764345626b30779222e0a592f2ab91ce0938823d1baa12a4168ceed03a95170" "sha256": "075e07880b28878645b61e807c6091ae0ecdef50b3486e047aa853023fc2ae33"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@@ -331,7 +331,7 @@
"sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e", "sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e",
"sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f" "sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f"
], ],
"markers": "python_version >= '3.6' and python_full_version < '4.0.0'", "markers": "python_version >= '3.6' and python_version < '4'",
"version": "==2.2.1" "version": "==2.2.1"
}, },
"ecdsa": { "ecdsa": {
@@ -933,7 +933,7 @@
"sha256:5c6bd9dc7a543b7fe4304a631f8a8a3b674e2bbfc49c2ae96200cdbe55df6b17", "sha256:5c6bd9dc7a543b7fe4304a631f8a8a3b674e2bbfc49c2ae96200cdbe55df6b17",
"sha256:95c5d300c4e879ee69708c428ba566c59478fd653cc3a22243eeb8ed846950bb" "sha256:95c5d300c4e879ee69708c428ba566c59478fd653cc3a22243eeb8ed846950bb"
], ],
"markers": "python_version >= '3.6' and python_full_version < '4.0.0'", "markers": "python_version >= '3.6' and python_version < '4'",
"version": "==4.8" "version": "==4.8"
}, },
"sentry-sdk": { "sentry-sdk": {
@@ -1031,7 +1031,7 @@
"sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14", "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14",
"sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e" "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_full_version < '4.0.0'", "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
"version": "==1.26.9" "version": "==1.26.9"
}, },
"uvicorn": { "uvicorn": {
@@ -1284,6 +1284,43 @@
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==21.4.0" "version": "==21.4.0"
}, },
"black": {
"hashes": [
"sha256:06f9d8846f2340dfac80ceb20200ea5d1b3f181dd0556b47af4e8e0b24fa0a6b",
"sha256:10dbe6e6d2988049b4655b2b739f98785a884d4d6b85bc35133a8fb9a2233176",
"sha256:2497f9c2386572e28921fa8bec7be3e51de6801f7459dffd6e62492531c47e09",
"sha256:30d78ba6bf080eeaf0b7b875d924b15cd46fec5fd044ddfbad38c8ea9171043a",
"sha256:328efc0cc70ccb23429d6be184a15ce613f676bdfc85e5fe8ea2a9354b4e9015",
"sha256:35020b8886c022ced9282b51b5a875b6d1ab0c387b31a065b84db7c33085ca79",
"sha256:5795a0375eb87bfe902e80e0c8cfaedf8af4d49694d69161e5bd3206c18618bb",
"sha256:5891ef8abc06576985de8fa88e95ab70641de6c1fca97e2a15820a9b69e51b20",
"sha256:637a4014c63fbf42a692d22b55d8ad6968a946b4a6ebc385c5505d9625b6a464",
"sha256:67c8301ec94e3bcc8906740fe071391bce40a862b7be0b86fb5382beefecd968",
"sha256:6d2fc92002d44746d3e7db7cf9313cf4452f43e9ea77a2c939defce3b10b5c82",
"sha256:6ee227b696ca60dd1c507be80a6bc849a5a6ab57ac7352aad1ffec9e8b805f21",
"sha256:863714200ada56cbc366dc9ae5291ceb936573155f8bf8e9de92aef51f3ad0f0",
"sha256:9b542ced1ec0ceeff5b37d69838106a6348e60db7b8fdd245294dc1d26136265",
"sha256:a6342964b43a99dbc72f72812bf88cad8f0217ae9acb47c0d4f141a6416d2d7b",
"sha256:ad4efa5fad66b903b4a5f96d91461d90b9507a812b3c5de657d544215bb7877a",
"sha256:bc58025940a896d7e5356952228b68f793cf5fcb342be703c3a2669a1488cb72",
"sha256:cc1e1de68c8e5444e8f94c3670bb48a2beef0e91dddfd4fcc29595ebd90bb9ce",
"sha256:cee3e11161dde1b2a33a904b850b0899e0424cc331b7295f2a9698e79f9a69a0",
"sha256:e3556168e2e5c49629f7b0f377070240bd5511e45e25a4497bb0073d9dda776a",
"sha256:e8477ec6bbfe0312c128e74644ac8a02ca06bcdb8982d4ee06f209be28cdf163",
"sha256:ee8f1f7228cce7dffc2b464f07ce769f478968bfb3dd1254a4c2eeed84928aad",
"sha256:fd57160949179ec517d32ac2ac898b5f20d68ed1a9c977346efbac9c2f1e779d"
],
"index": "pypi",
"version": "==22.3.0"
},
"click": {
"hashes": [
"sha256:24e1a4a9ec5bf6299411369b208c1df2188d9eb8d916302fe6bf03faed227f1e",
"sha256:479707fe14d9ec9a0757618b7a100a0ae4c4e236fac5b7f80ca68028141a1a72"
],
"markers": "python_version >= '3.7'",
"version": "==8.1.2"
},
"coverage": { "coverage": {
"hashes": [ "hashes": [
"sha256:03e2a7826086b91ef345ff18742ee9fc47a6839ccd517061ef8fa1976e652ce9", "sha256:03e2a7826086b91ef345ff18742ee9fc47a6839ccd517061ef8fa1976e652ce9",
@@ -1353,6 +1390,13 @@
], ],
"version": "==0.6.1" "version": "==0.6.1"
}, },
"mypy-extensions": {
"hashes": [
"sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d",
"sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"
],
"version": "==0.4.3"
},
"packaging": { "packaging": {
"hashes": [ "hashes": [
"sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb",
@@ -1361,6 +1405,21 @@
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==21.3" "version": "==21.3"
}, },
"pathspec": {
"hashes": [
"sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a",
"sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"
],
"version": "==0.9.0"
},
"platformdirs": {
"hashes": [
"sha256:7535e70dfa32e84d4b34996ea99c5e432fa29a708d0f4e394bbcb2a8faa4f16d",
"sha256:bcae7cab893c2d310a711b70b24efb93334febe65f8de776ee320b517471e227"
],
"markers": "python_version >= '3.7'",
"version": "==2.5.1"
},
"pluggy": { "pluggy": {
"hashes": [ "hashes": [
"sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159",
@@ -1423,7 +1482,7 @@
"sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc",
"sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version < '3.11'",
"version": "==2.0.1" "version": "==2.0.1"
} }
} }
+5 -5
View File
@@ -28,11 +28,11 @@ ACCESS_TOKEN_EXPIRE_MINUTES = settings.access_token_expire_minutes
class OAuth2PasswordBearerWithCookie(OAuth2): class OAuth2PasswordBearerWithCookie(OAuth2):
def __init__( def __init__(
self, self,
tokenUrl: str, tokenUrl: str,
scheme_name: Optional[str] = None, scheme_name: Optional[str] = None,
scopes: Optional[Dict[str, str]] = None, scopes: Optional[Dict[str, str]] = None,
auto_error: bool = True, auto_error: bool = True,
): ):
if not scopes: if not scopes:
scopes = {} scopes = {}
+8 -3
View File
@@ -10,6 +10,7 @@ class Settings(BaseSettings):
""" """
Settings class for the shop app. Settings class for the shop app.
""" """
root_address: str = "http://127.0.0.1:8000" root_address: str = "http://127.0.0.1:8000"
redis: RedisDsn = "redis://localhost:6379/0?decode_responses=True" redis: RedisDsn = "redis://localhost:6379/0?decode_responses=True"
skip_email_verification: bool = False skip_email_verification: bool = False
@@ -36,7 +37,7 @@ class Settings(BaseSettings):
class Config: class Config:
env_file = ".env" env_file = ".env"
env_file_encoding = 'utf-8' env_file_encoding = "utf-8"
@lru_cache() @lru_cache()
@@ -45,5 +46,9 @@ def settings() -> Settings:
redis: redis_lib.client.Redis = redis_lib.Redis().from_url(settings().redis) redis: redis_lib.client.Redis = redis_lib.Redis().from_url(settings().redis)
storage: Storage = Storage(backend=settings().storage_backend, deta_key=settings().deta_project_key, storage: Storage = Storage(
deta_id=settings().deta_project_id, storage_path=settings().storage_path) backend=settings().storage_backend,
deta_key=settings().deta_project_key,
deta_id=settings().deta_project_id,
storage_path=settings().storage_path,
)
+8 -3
View File
@@ -11,6 +11,7 @@ class User(ormar.Model):
""" """
The user model in the database The user model in the database
""" """
id: uuid.UUID = ormar.UUID(primary_key=True, default=uuid.uuid4()) id: uuid.UUID = ormar.UUID(primary_key=True, default=uuid.uuid4())
email: str = ormar.String(unique=True, max_length=100) email: str = ormar.String(unique=True, max_length=100)
username: str = ormar.String(unique=True, max_length=100) username: str = ormar.String(unique=True, max_length=100)
@@ -20,7 +21,7 @@ class User(ormar.Model):
created_at: datetime = ormar.DateTime(default=datetime.now()) created_at: datetime = ormar.DateTime(default=datetime.now())
class Meta: class Meta:
tablename = 'users' tablename = "users"
metadata = metadata metadata = metadata
database = database database = database
@@ -29,6 +30,7 @@ class UserSession(ormar.Model):
""" """
The user session model for user-sessions The user session model for user-sessions
""" """
id: uuid.UUID = ormar.UUID(primary_key=True, default=uuid.uuid4()) id: uuid.UUID = ormar.UUID(primary_key=True, default=uuid.uuid4())
user: uuid.UUID = ormar.ForeignKey(User) user: uuid.UUID = ormar.ForeignKey(User)
session_key: str = ormar.String(unique=True, max_length=64) session_key: str = ormar.String(unique=True, max_length=64)
@@ -38,7 +40,7 @@ class UserSession(ormar.Model):
last_seen: datetime = ormar.DateTime(default=datetime.now()) last_seen: datetime = ormar.DateTime(default=datetime.now())
class Meta: class Meta:
tablename = 'user_sessions' tablename = "user_sessions"
metadata = metadata metadata = metadata
database = database database = database
@@ -73,7 +75,7 @@ class Quiz(ormar.Model):
questions: Json[list[QuizQuestion]] = ormar.JSON(nullable=False) questions: Json[list[QuizQuestion]] = ormar.JSON(nullable=False)
class Meta: class Meta:
tablename = 'quiz' tablename = "quiz"
metadata = metadata metadata = metadata
database = database database = database
@@ -82,6 +84,7 @@ class Token(BaseModel):
""" """
For JWT For JWT
""" """
access_token: str access_token: str
token_type: str token_type: str
@@ -90,6 +93,7 @@ class TokenData(BaseModel):
""" """
For JWT For JWT
""" """
email: str | None = None email: str | None = None
@@ -97,6 +101,7 @@ class PlayGame(BaseModel):
""" """
For JWT For JWT
""" """
quiz_id: uuid.UUID | str quiz_id: uuid.UUID | str
description: str description: str
title: str title: str
+17 -22
View File
@@ -12,17 +12,18 @@ from classquiz.db.models import User
settings = settings() settings = settings()
jinja = Environment( jinja = Environment(
loader=PackageLoader('classquiz.emails', 'templates'), loader=PackageLoader("classquiz.emails", "templates"),
autoescape=select_autoescape(['html', 'xml']), autoescape=select_autoescape(["html", "xml"]),
enable_async=True) enable_async=True,
)
def _sendMail(template: str, to: str, subject: str): def _sendMail(template: str, to: str, subject: str):
msg = MIMEMultipart('alternative') msg = MIMEMultipart("alternative")
msg['Subject'] = subject msg["Subject"] = subject
msg['From'] = settings.mail_address msg["From"] = settings.mail_address
msg['To'] = to msg["To"] = to
msg.attach(MIMEText(template, 'html')) msg.attach(MIMEText(template, "html"))
context = ssl.SSLContext(ssl.PROTOCOL_TLS) context = ssl.SSLContext(ssl.PROTOCOL_TLS)
server = smtplib.SMTP(host=settings.mail_server, port=settings.mail_port) server = smtplib.SMTP(host=settings.mail_server, port=settings.mail_port)
server.ehlo() server.ehlo()
@@ -35,25 +36,19 @@ def _sendMail(template: str, to: str, subject: str):
async def send_register_email(email: str): async def send_register_email(email: str):
user = await User.objects.get_or_none(email=email, verified=False) user = await User.objects.get_or_none(email=email, verified=False)
if user is None: if user is None:
raise ValueError('User not found') raise ValueError("User not found")
template = jinja.get_template('register.jinja2') template = jinja.get_template("register.jinja2")
template = await template.render_async( template = await template.render_async(base_url=settings.root_address, token=user.verify_key)
base_url=settings.root_address, _sendMail(template=template, to=email, subject="Verify your email")
token=user.verify_key
)
_sendMail(template=template, to=email, subject='Verify your email')
async def send_forgotten_password_email(email: str): async def send_forgotten_password_email(email: str):
user = await User.objects.get_or_none(email=email) user = await User.objects.get_or_none(email=email)
if user is None: if user is None:
raise ValueError('User not found') raise ValueError("User not found")
template = jinja.get_template('forgotten_password.jinja2') template = jinja.get_template("forgotten_password.jinja2")
token = os.urandom(32).hex() token = os.urandom(32).hex()
template = await template.render_async( template = await template.render_async(base_url=settings.root_address, token=token)
base_url=settings.root_address,
token=token
)
await redis.set(f"reset_passwd:{token}", str(user.id), ex=3600) await redis.set(f"reset_passwd:{token}", str(user.id), ex=3600)
_sendMail(template=template, to=email, subject='Reset your password') _sendMail(template=template, to=email, subject="Reset your password")
pass pass
+18 -4
View File
@@ -53,6 +53,7 @@ async def _download_image(url: str) -> bytes:
# data = await resp.json() # data = await resp.json()
# return data["data"]["id"] # return data["data"]["id"]
async def import_quiz(quiz_id: str, user: User) -> Quiz | str: async def import_quiz(quiz_id: str, user: User) -> Quiz | str:
""" """
Imports a quiz from Kahoot. Imports a quiz from Kahoot.
@@ -78,8 +79,21 @@ async def import_quiz(quiz_id: str, user: User) -> Quiz | str:
for a in q.choices: for a in q.choices:
answers.append((QuizAnswer(right=a.correct, answer=html.unescape(a.answer)))) answers.append((QuizAnswer(right=a.correct, answer=html.unescape(a.answer))))
quiz_questions.append( quiz_questions.append(
QuizQuestion(question=q.question, answers=answers, time=str(q.time / 1000), image=image).dict()) QuizQuestion(
quiz_data = Quiz(id=quiz_id, public=False, title=quiz.kahoot.title, description=quiz.kahoot.description, question=q.question,
created_at=datetime.now(), updated_at=datetime.now(), user_id=user.id, answers=answers,
questions=json.dumps(quiz_questions)) time=str(q.time / 1000),
image=image,
).dict()
)
quiz_data = Quiz(
id=quiz_id,
public=False,
title=quiz.kahoot.title,
description=quiz.kahoot.description,
created_at=datetime.now(),
updated_at=datetime.now(),
user_id=user.id,
questions=json.dumps(quiz_questions),
)
return await quiz_data.save() return await quiz_data.save()
+8 -4
View File
@@ -14,8 +14,13 @@ class _Response(BaseModel):
pageTimestamp: int pageTimestamp: int
async def search(query: str | None, limit: int | None = 9, cursor: int | None = 1, async def search(
search_cluster: int | None = 1, inventory_item_id: str | None = "ANY") -> _Response: query: str | None,
limit: int | None = 9,
cursor: int | None = 1,
search_cluster: int | None = 1,
inventory_item_id: str | None = "ANY",
) -> _Response:
""" """
:param inventory_item_id: I dkon't know :param inventory_item_id: I dkon't know
@@ -27,8 +32,7 @@ async def search(query: str | None, limit: int | None = 9, cursor: int | None =
""" """
async with ClientSession() as session: async with ClientSession() as session:
async with session.get( async with session.get(
f"https://create.kahoot.it/rest/kahoots/?query={query}&limit={limit}&cursor={cursor}&searchCluster={search_cluster}&includeExtendedCounters=false&inventoryItemId={inventory_item_id}" # noqa : E501 f"https://create.kahoot.it/rest/kahoots/?query={query}&limit={limit}&cursor={cursor}&searchCluster={search_cluster}&includeExtendedCounters=false&inventoryItemId={inventory_item_id}" # noqa : E501
) as response: ) as response:
# print( # print(
# f"https://create.kahoot.it/rest/kahoots/?query={query}&limit={limit}&cursor={cursor}&searchCluster={search_cluster}&includeExtendedCounters=false&inventoryItemId={inventory_item_id}") # f"https://create.kahoot.it/rest/kahoots/?query={query}&limit={limit}&cursor={cursor}&searchCluster={search_cluster}&includeExtendedCounters=false&inventoryItemId={inventory_item_id}")
+8 -2
View File
@@ -62,8 +62,14 @@ async def start_quiz(quiz_id: str, user: User = Depends(get_current_user)):
return JSONResponse(status_code=404, content={"detail": "quiz not found"}) return JSONResponse(status_code=404, content={"detail": "quiz not found"})
else: else:
game_pin = randint(10000000, 99999999) game_pin = randint(10000000, 99999999)
game = PlayGame(quiz_id=quiz_id, game_pin=str(game_pin), questions=quiz.questions, game_id=uuid.uuid4(), game = PlayGame(
title=quiz.title, description=quiz.description) quiz_id=quiz_id,
game_pin=str(game_pin),
questions=quiz.questions,
game_id=uuid.uuid4(),
title=quiz.title,
description=quiz.description,
)
await redis.set(f"game:{str(game.game_pin)}", (game.json()), ex=18000) await redis.set(f"game:{str(game.game_pin)}", (game.json()), ex=18000)
return {**quiz.dict(exclude={"id"}), **game.dict(exclude={"questions"})} return {**quiz.dict(exclude={"id"}), **game.dict(exclude={"questions"})}
+6 -3
View File
@@ -12,7 +12,7 @@ router = APIRouter()
file_regex = r"^[a-z0-9]{8}-[a-z0-9-]{27}--[a-z0-9-]{36}$" file_regex = r"^[a-z0-9]{8}-[a-z0-9-]{27}--[a-z0-9-]{36}$"
@router.get('/download/{file_name}') @router.get("/download/{file_name}")
async def download_file(file_name: str): async def download_file(file_name: str):
if not re.match(file_regex, file_name): if not re.match(file_regex, file_name):
raise HTTPException(status_code=400, detail="Invalid file name") raise HTTPException(status_code=400, detail="Invalid file name")
@@ -24,5 +24,8 @@ async def download_file(file_name: str):
def iter_file(): def iter_file():
yield from download yield from download
return StreamingResponse(iter_file(), media_type='image/*', return StreamingResponse(
headers={"Cache-Control": "public, immutable, max-age=31536000"}) iter_file(),
media_type="image/*",
headers={"Cache-Control": "public, immutable, max-age=31536000"},
)
+57 -21
View File
@@ -7,18 +7,32 @@ from fastapi.background import BackgroundTasks
from fastapi.responses import JSONResponse, RedirectResponse from fastapi.responses import JSONResponse, RedirectResponse
from fastapi.security import OAuth2PasswordRequestForm from fastapi.security import OAuth2PasswordRequestForm
from classquiz.auth import get_password_hash, verify_password, authenticate_user, create_access_token, get_current_user from classquiz.auth import (
get_password_hash,
verify_password,
authenticate_user,
create_access_token,
get_current_user,
)
from classquiz.cache import clear_cache_for_account from classquiz.cache import clear_cache_for_account
from classquiz.config import redis, settings from classquiz.config import redis, settings
import uuid import uuid
from pydantic import BaseModel from pydantic import BaseModel
from classquiz.db.models import User, UserSession, UpdatePassword, Token from classquiz.db.models import User, UserSession, UpdatePassword, Token
from classquiz.emails import send_register_email, send_forgotten_password_email from classquiz.emails import send_register_email, send_forgotten_password_email
settings = settings() settings = settings()
router = APIRouter() router = APIRouter()
route_user = User.get_pydantic( route_user = User.get_pydantic(
exclude={"id": ..., "verified": ..., "verify_key": ..., "created_at": ..., "usersessions": ...}) exclude={
"id": ...,
"verified": ...,
"verify_key": ...,
"created_at": ...,
"usersessions": ...,
}
)
async def _sign_out_everywhere(user: User) -> None: async def _sign_out_everywhere(user: User) -> None:
@@ -26,8 +40,11 @@ async def _sign_out_everywhere(user: User) -> None:
await clear_cache_for_account(user) await clear_cache_for_account(user)
@router.post("/create", response_model=User, @router.post(
response_model_include={"id": ..., "verified": ..., "email": ...}) "/create",
response_model=User,
response_model_include={"id": ..., "verified": ..., "email": ...},
)
async def create_user(user: route_user, background_task: BackgroundTasks) -> User | JSONResponse: async def create_user(user: route_user, background_task: BackgroundTasks) -> User | JSONResponse:
user = User(**user.dict(), id=uuid.uuid4()) user = User(**user.dict(), id=uuid.uuid4())
try: try:
@@ -50,8 +67,11 @@ async def create_user(user: route_user, background_task: BackgroundTasks) -> Use
@router.post("/token/cookie", response_model=Token) @router.post("/token/cookie", response_model=Token)
async def login_for_cookie_access_token(request: Request, response: Response, async def login_for_cookie_access_token(
form_data: OAuth2PasswordRequestForm = Depends()): request: Request,
response: Response,
form_data: OAuth2PasswordRequestForm = Depends(),
):
user = await authenticate_user(form_data.username, form_data.password) user = await authenticate_user(form_data.username, form_data.password)
if not user: if not user:
raise HTTPException( raise HTTPException(
@@ -59,21 +79,29 @@ async def login_for_cookie_access_token(request: Request, response: Response,
detail="Incorrect username or password", detail="Incorrect username or password",
) )
session_key = os.urandom(32).hex() session_key = os.urandom(32).hex()
user_session = UserSession(user=user, session_key=session_key, ip_address=request.client.host, user_session = UserSession(
user_agent=request.headers.get("User-Agent"), id=uuid.uuid4()) user=user,
session_key=session_key,
ip_address=request.client.host,
user_agent=request.headers.get("User-Agent"),
id=uuid.uuid4(),
)
await user_session.save() await user_session.save()
# await user_session.save() # await user_session.save()
access_token_expires = timedelta(minutes=settings.access_token_expire_minutes) access_token_expires = timedelta(minutes=settings.access_token_expire_minutes)
access_token = create_access_token( access_token = create_access_token(data={"sub": user.email}, expires_delta=access_token_expires)
data={"sub": user.email}, expires_delta=access_token_expires
)
await redis.set(access_token, user.email, ex=settings.access_token_expire_minutes * 60) await redis.set(access_token, user.email, ex=settings.access_token_expire_minutes * 60)
response.set_cookie(key="access_token", value=f"Bearer {access_token}", response.set_cookie(
httponly=True, samesite='strict', max_age=settings.access_token_expire_minutes * 60) key="access_token",
value=f"Bearer {access_token}",
httponly=True,
samesite="strict",
max_age=settings.access_token_expire_minutes * 60,
)
response.set_cookie(key="expiry", value="", max_age=settings.access_token_expire_minutes * 60) response.set_cookie(key="expiry", value="", max_age=settings.access_token_expire_minutes * 60)
response.set_cookie(key="rememberme", value="") response.set_cookie(key="rememberme", value="")
response.set_cookie(key="rememberme_token", value=session_key, httponly=True, samesite='strict') response.set_cookie(key="rememberme_token", value=session_key, httponly=True, samesite="strict")
return {"access_token": access_token, "token_type": "bearer"} return {"access_token": access_token, "token_type": "bearer"}
@@ -82,16 +110,20 @@ async def rememberme_token(request: Request, response: Response):
rememberme_cookie = request.cookies.get("rememberme_token") rememberme_cookie = request.cookies.get("rememberme_token")
if rememberme_cookie is None: if rememberme_cookie is None:
raise HTTPException(status_code=400, detail="No rememberme cookie") raise HTTPException(status_code=400, detail="No rememberme cookie")
user_session: UserSession | None = await UserSession.objects.filter(session_key=rememberme_cookie).select_related( user_session: UserSession | None = (
UserSession.user).get_or_none() await UserSession.objects.filter(session_key=rememberme_cookie).select_related(UserSession.user).get_or_none()
)
if (user_session is None) or (user_session.user is None): if (user_session is None) or (user_session.user is None):
raise HTTPException(status_code=401, detail="No user session") raise HTTPException(status_code=401, detail="No user session")
access_token_expires = timedelta(minutes=settings.access_token_expire_minutes * 60) access_token_expires = timedelta(minutes=settings.access_token_expire_minutes * 60)
access_token = create_access_token( access_token = create_access_token(data={"sub": user_session.user.email}, expires_delta=access_token_expires)
data={"sub": user_session.user.email}, expires_delta=access_token_expires response.set_cookie(
key="access_token",
value=f"Bearer {access_token}",
httponly=True,
samesite="strict",
max_age=settings.access_token_expire_minutes * 60,
) )
response.set_cookie(key="access_token", value=f"Bearer {access_token}",
httponly=True, samesite='strict', max_age=settings.access_token_expire_minutes * 60)
response.set_cookie(key="expiry", value="", max_age=settings.access_token_expire_minutes * 60) response.set_cookie(key="expiry", value="", max_age=settings.access_token_expire_minutes * 60)
await user_session.update(last_seen=datetime.now()) await user_session.update(last_seen=datetime.now())
@@ -127,7 +159,11 @@ async def verify_user(verify_key: str):
@router.put("/password/update") @router.put("/password/update")
async def change_password(password_data: UpdatePassword, response: Response, user: User = Depends(get_current_user)): async def change_password(
password_data: UpdatePassword,
response: Response,
user: User = Depends(get_current_user),
):
if not verify_password(password_data.old_password, user.password): if not verify_password(password_data.old_password, user.password):
raise HTTPException(status_code=400, detail="Incorrect password") raise HTTPException(status_code=400, detail="Incorrect password")
user.password = get_password_hash(password_data.new_password) user.password = get_password_hash(password_data.new_password)
+66 -23
View File
@@ -14,8 +14,10 @@ settings = settings()
async def join_game(sid, data): async def join_game(sid, data):
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
try: try:
async with session.post("https://hcaptcha.com/siteverify", async with session.post(
data={"response": data["captcha"], "secret": settings.hcaptcha_key}) as resp: "https://hcaptcha.com/siteverify",
data={"response": data["captcha"], "secret": settings.hcaptcha_key},
) as resp:
resp_data = await resp.json() resp_data = await resp.json()
if not resp_data["success"]: if not resp_data["success"]:
print("CAPTCHA FAILED") print("CAPTCHA FAILED")
@@ -27,17 +29,34 @@ async def join_game(sid, data):
if redis_res is None: if redis_res is None:
await sio.emit("game_not_found", room=sid) await sio.emit("game_not_found", room=sid)
else: else:
session = {'game_pin': data["game_pin"], "username": data["username"], "admin": False} session = {
"game_pin": data["game_pin"],
"username": data["username"],
"admin": False,
}
await sio.save_session(sid, session) await sio.save_session(sid, session)
await sio.emit("joined_game", redis_res, room=sid) await sio.emit("joined_game", redis_res, room=sid)
redis_res = (await redis.get(f"game_session:{data['game_pin']}")) redis_res = await redis.get(f"game_session:{data['game_pin']}")
redis_res = json.loads(redis_res) redis_res = json.loads(redis_res)
# print(redis_res) # print(redis_res)
redis_res["players"].append({"username": data["username"], "sid": sid}) redis_res["players"].append({"username": data["username"], "sid": sid})
await redis.set(f"game_session:{data['game_pin']}", await redis.set(
json.dumps({"admin": redis_res["admin"], "game_id": redis_res["game_id"], f"game_session:{data['game_pin']}",
"players": redis_res["players"], "answers": []}), ex=18000) json.dumps(
await sio.emit("player_joined", {"username": data["username"], "sid": sid}, room=redis_res["admin"]) {
"admin": redis_res["admin"],
"game_id": redis_res["game_id"],
"players": redis_res["players"],
"answers": [],
}
),
ex=18000,
)
await sio.emit(
"player_joined",
{"username": data["username"], "sid": sid},
room=redis_res["admin"],
)
sio.enter_room(sid, data["game_pin"]) # TODO: make more secure sio.enter_room(sid, data["game_pin"]) # TODO: make more secure
@@ -55,14 +74,19 @@ async def register_as_admin(sid, data):
game_pin = data["game_pin"] game_pin = data["game_pin"]
game_id = data["game_id"] game_id = data["game_id"]
if (await redis.get(f"game_session:{game_pin}")) is None: if (await redis.get(f"game_session:{game_pin}")) is None:
await redis.set(f"game_session:{game_pin}", json.dumps({"admin": sid, "game_id": game_id, "players": []}), await redis.set(
ex=18000) f"game_session:{game_pin}",
json.dumps({"admin": sid, "game_id": game_id, "players": []}),
ex=18000,
)
await sio.emit("registered_as_admin", {"game_id": game_id, "game": await redis.get( await sio.emit(
f"game:{data['game_pin']}")}, "registered_as_admin",
room=sid) {"game_id": game_id, "game": await redis.get(f"game:{data['game_pin']}")},
room=sid,
)
async with sio.session(sid) as session: async with sio.session(sid) as session:
session['game_pin'] = data["game_pin"] session["game_pin"] = data["game_pin"]
session["admin"] = True session["admin"] = True
sio.enter_room(sid, data["game_pin"]) sio.enter_room(sid, data["game_pin"])
else: else:
@@ -74,7 +98,7 @@ async def get_question_results(sid, data):
session = await sio.get_session(sid) session = await sio.get_session(sid)
if session["admin"]: if session["admin"]:
redis_res = await redis.get(f"game_session:{session['game_pin']}:{data['question_number']}") redis_res = await redis.get(f"game_session:{session['game_pin']}:{data['question_number']}")
game_pin = session['game_pin'] game_pin = session["game_pin"]
await sio.emit("question_results", redis_res, room=game_pin) await sio.emit("question_results", redis_res, room=game_pin)
@@ -82,7 +106,7 @@ async def get_question_results(sid, data):
async def set_question_number(sid, data): async def set_question_number(sid, data):
session = await sio.get_session(sid) session = await sio.get_session(sid)
if session["admin"]: if session["admin"]:
game_pin = session['game_pin'] game_pin = session["game_pin"]
await sio.emit("set_question_number", data, room=game_pin) await sio.emit("set_question_number", data, room=game_pin)
@@ -100,21 +124,40 @@ async def submit_answer(sid, data):
break break
answers = await redis.get(f"game_session:{session['game_pin']}:{data['question_index']}") answers = await redis.get(f"game_session:{session['game_pin']}:{data['question_index']}")
if answers is None: if answers is None:
await redis.set(f"game_session:{session['game_pin']}:{data['question_index']}", await redis.set(
json.dumps( f"game_session:{session['game_pin']}:{data['question_index']}",
[{"username": session["username"], "answer": data["answer"], "right": answer_right}]), json.dumps(
ex=18000) [
{
"username": session["username"],
"answer": data["answer"],
"right": answer_right,
}
]
),
ex=18000,
)
else: else:
answers = json.loads(answers) answers = json.loads(answers)
answers.append({"username": session["username"], "answer": data["answer"], "right": answer_right}) answers.append(
await redis.set(f"game_session:{session['game_pin']}:{data['question_index']}", json.dumps(answers), ex=18000) {
"username": session["username"],
"answer": data["answer"],
"right": answer_right,
}
)
await redis.set(
f"game_session:{session['game_pin']}:{data['question_index']}",
json.dumps(answers),
ex=18000,
)
# await redis.set(f"game_data:{session['game_pin']}", json.dumps(data)) # await redis.set(f"game_data:{session['game_pin']}", json.dumps(data))
@sio.event @sio.event
async def get_game_data(sid, _data): async def get_game_data(sid, _data):
game_pin = (await sio.get_session(sid))['game_pin'] game_pin = (await sio.get_session(sid))["game_pin"]
game_data = await redis.get(f"game:{game_pin}") game_data = await redis.get(f"game:{game_pin}")
if game_data is not None: if game_data is not None:
await sio.emit("game_data", json.loads(game_data), room=game_pin) await sio.emit("game_data", json.loads(game_data), room=game_pin)
+13 -5
View File
@@ -6,7 +6,13 @@ from .local_storage import LocalStorage
class Storage: class Storage:
def __init__(self, backend: str, deta_key: Optional[str], deta_id: Optional[str], storage_path: Optional[str]): def __init__(
self,
backend: str,
deta_key: Optional[str],
deta_id: Optional[str],
storage_path: Optional[str],
):
self.backend = backend self.backend = backend
self.deta_key: str | None = deta_key self.deta_key: str | None = deta_key
self.deta_id: str | None = deta_id self.deta_id: str | None = deta_id
@@ -15,8 +21,11 @@ class Storage:
if deta_key is None or deta_id is None: if deta_key is None or deta_id is None:
raise ValueError("deta_key and deta_id must be provided") raise ValueError("deta_key and deta_id must be provided")
else: else:
self.deta_instance = DetaStorage(deta_base_url=self.deta_base_url, deta_key=self.deta_key, self.deta_instance = DetaStorage(
deta_id=self.deta_id) deta_base_url=self.deta_base_url,
deta_key=self.deta_key,
deta_id=self.deta_id,
)
elif backend == "local": elif backend == "local":
if storage_path is None: if storage_path is None:
@@ -29,8 +38,7 @@ class Storage:
async def download(self, file_name: str) -> BytesIO | None: async def download(self, file_name: str) -> BytesIO | None:
if self.backend == "deta": if self.backend == "deta":
return await self.deta_instance.download( return await self.deta_instance.download(file_name)
file_name)
elif self.backend == "local": elif self.backend == "local":
return await self.local_instance.get_file(file_name) return await self.local_instance.get_file(file_name)
+2 -2
View File
@@ -11,13 +11,13 @@ class LocalStorage:
async def get_file(self, file_name: str) -> io.BytesIO | None: async def get_file(self, file_name: str) -> io.BytesIO | None:
try: try:
async with aiofiles.open(file=os.path.join(self.base_path, file_name), mode='rb') as f: async with aiofiles.open(file=os.path.join(self.base_path, file_name), mode="rb") as f:
return io.BytesIO(await f.read()) return io.BytesIO(await f.read())
except FileNotFoundError: except FileNotFoundError:
return None return None
async def write_file(self, file_name: str, data: bytes) -> None: async def write_file(self, file_name: str, data: bytes) -> None:
async with aiofiles.open(file=os.path.join(self.base_path, file_name), mode='wb') as f: async with aiofiles.open(file=os.path.join(self.base_path, file_name), mode="wb") as f:
await f.write(data) await f.write(data)
async def delete_file(self, file_names: [str]) -> None: async def delete_file(self, file_names: [str]) -> None:
+103 -51
View File
@@ -5,60 +5,112 @@ from classquiz.kahoot_importer.get import get
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_get(): async def test_get():
await get('1f95eb0b-fcf4-4db2-879b-5418ef75116b') await get("1f95eb0b-fcf4-4db2-879b-5418ef75116b")
await get("c63d4d39-767e-4754-8c16-66c3c2d9633c") await get("c63d4d39-767e-4754-8c16-66c3c2d9633c")
await get("c5d31de6-4598-4574-8e67-ca9a2aba49f8") await get("c5d31de6-4598-4574-8e67-ca9a2aba49f8")
await get("3d545fc3-b830-44b6-94ac-c8e4a3c9fb88") await get("3d545fc3-b830-44b6-94ac-c8e4a3c9fb88")
quiz_list = ['c63d4d39-767e-4754-8c16-66c3c2d9633c', 'a73689d2-1108-4524-88e1-10c54282346c', quiz_list = [
'e8e2d869-7c75-4ad6-8872-031bc8e37fa9', '387b4139-9e77-4b47-93b7-dbb6d75c54d1', "c63d4d39-767e-4754-8c16-66c3c2d9633c",
'a1fff657-b99d-4547-93cf-4e9629994844', '0880d792-a77e-4cba-8a8b-359de40ffe76', "a73689d2-1108-4524-88e1-10c54282346c",
'e2d12f01-a3dd-4d0e-a6c4-29f38046117f', 'e0af9b8e-8840-4a7d-8d51-3d02bb6a07e7', "e8e2d869-7c75-4ad6-8872-031bc8e37fa9",
'e655c086-558d-4602-99fd-5fc429ffe0ce', '1642f29d-b5cb-4213-82e6-7c1fae401aca', "387b4139-9e77-4b47-93b7-dbb6d75c54d1",
'fabdd572-da3e-4621-b912-41b9bc5a18fb', 'ded3cbbf-5af3-4761-a6da-b3b6ca040e1a', "a1fff657-b99d-4547-93cf-4e9629994844",
'370b3e33-ed97-4d36-9362-7f76aa9d12b9', 'cfa32a28-1a01-47f1-9bca-c85640bceeb8', "0880d792-a77e-4cba-8a8b-359de40ffe76",
'993f615d-6890-4dd3-ac08-f462de9ac5a3', '7f08a377-e371-425e-95f9-7196401ae7ff', "e2d12f01-a3dd-4d0e-a6c4-29f38046117f",
'19656fed-5891-48e7-ae7f-1358f623bfd0', '5e766d32-930a-4640-82a1-87c58612f579', "e0af9b8e-8840-4a7d-8d51-3d02bb6a07e7",
'ab2450e8-ecbb-4aed-bb3c-87e47b2b81f5', 'dd3c9175-addd-48df-8297-7598d4f12006', "e655c086-558d-4602-99fd-5fc429ffe0ce",
'b8de862c-fc4b-4df6-9fa5-81b91868fc3b', '3b082f4e-9c50-45ea-98c4-3845b14e400c', "1642f29d-b5cb-4213-82e6-7c1fae401aca",
'10179f67-a344-4fd0-80a1-d1e1b6ca7953', '8288c1dd-9675-411c-9175-4180004da3c3', "fabdd572-da3e-4621-b912-41b9bc5a18fb",
'af838c22-dcbb-497d-8c7b-f6499835b83d', '152c5fdb-3b0a-4cdf-a0d9-1984de0643ad', "ded3cbbf-5af3-4761-a6da-b3b6ca040e1a",
'846fd2bb-6b95-4333-b5d4-ef0a363e73a9', 'dd6b446f-817b-469b-9e6b-e1d34c466a7f', "370b3e33-ed97-4d36-9362-7f76aa9d12b9",
'2720d636-8918-4c6a-86a9-928c489382f4', 'a6985641-df97-4e56-97fc-e0901e32b464', "cfa32a28-1a01-47f1-9bca-c85640bceeb8",
'1d2705b4-b79e-4b51-8d61-cfb60fc7d1b9', '677fa485-4231-4479-beb2-fe757053bf41', "993f615d-6890-4dd3-ac08-f462de9ac5a3",
'2b6379f8-450e-46c0-a854-5e368a03d5a6', 'f3ba42fe-11e8-47fc-8978-8d67bc0ce0b1', "7f08a377-e371-425e-95f9-7196401ae7ff",
'51d2e839-a9d7-4f26-b1fd-fb0743c3a236', 'f2212243-4553-456d-87e7-7ddfd6ee7563', "19656fed-5891-48e7-ae7f-1358f623bfd0",
'2d6982d3-7b46-4022-bbb6-cb5451c581ba', '1cccae22-5140-46b5-ba6a-aa17cec14dc3', "5e766d32-930a-4640-82a1-87c58612f579",
'b01dd2c2-ffcf-4c02-a274-e12296dbe4e1', '2669d193-a81f-4752-a003-dd2433beef92', "ab2450e8-ecbb-4aed-bb3c-87e47b2b81f5",
'06203ee6-3615-4e26-bb08-4fab249698a4', '933c1426-c759-43a3-9770-c8e3948db998', "dd3c9175-addd-48df-8297-7598d4f12006",
'02046384-fb05-4f1d-855a-f0e52a0761e3', 'b5266e9b-7372-4df0-94a9-ec8a6c0aabc6', "b8de862c-fc4b-4df6-9fa5-81b91868fc3b",
'fb8e3a3d-505a-4157-bd9a-b73b00591f10', '42092675-9171-400f-ab4d-c898ec9f70bd', "3b082f4e-9c50-45ea-98c4-3845b14e400c",
'1d6307e2-98c2-42f2-b765-d02a68e0da80', '8f431ed0-31df-4435-bcba-aff46929fe82', "10179f67-a344-4fd0-80a1-d1e1b6ca7953",
'd573d76f-d715-4a54-bdde-7ac7b6235fb2', 'feb8985b-84fb-4d41-bc41-70c322f508fa', "8288c1dd-9675-411c-9175-4180004da3c3",
'f9f945cc-c166-4f7d-a95f-f0be154c43cf', '009b130a-1709-41a6-943d-85eee4889792', "af838c22-dcbb-497d-8c7b-f6499835b83d",
'a3f765f7-2329-47e0-83eb-0837b4b54b43', 'fea9e558-206f-444a-8d7f-a3f0301fda8d', "152c5fdb-3b0a-4cdf-a0d9-1984de0643ad",
'9b419098-ccfc-467d-9de0-db18b60d6c28', 'e6c2174e-3a3f-4193-9782-1bf3b5cfae63', "846fd2bb-6b95-4333-b5d4-ef0a363e73a9",
'd42c962e-1691-4fa4-8926-c33157423bd2', '8d27aa83-a103-4f05-8230-69e857bc3ffd', "dd6b446f-817b-469b-9e6b-e1d34c466a7f",
'60cd19b2-6858-4704-b07b-5196e8e24325', '31624ea4-1e4f-4f90-ae4f-759a0eb93b61', "2720d636-8918-4c6a-86a9-928c489382f4",
'fcd038f4-6b05-485b-afe3-ce67e05b1695', '1207bbe5-2df5-4c86-8f54-1b2675a86859', "a6985641-df97-4e56-97fc-e0901e32b464",
'eea3b0b0-deae-41de-b010-d6d50855d016', 'ca8b0278-744c-4a3f-a2b8-6ab29d76fa7f', "1d2705b4-b79e-4b51-8d61-cfb60fc7d1b9",
'68d37bc8-28b9-49fd-9e02-48a3c9195fdf', '8bf13f61-c5cc-4332-aab9-37405d1fdd14', "677fa485-4231-4479-beb2-fe757053bf41",
'8ceef7de-9408-48d1-8bb9-b8ac3ef73596', '7e053a85-b04c-484b-8815-d26ce646c748', "2b6379f8-450e-46c0-a854-5e368a03d5a6",
'dfc09237-85b7-4596-b4e8-0bfbfd1aaeb7', 'd8d291ab-9dfa-45c0-bfc2-0a81167e3c63', "f3ba42fe-11e8-47fc-8978-8d67bc0ce0b1",
'd0834157-d464-4632-b620-147e211e0614', '30758f35-fabc-48bc-b410-47450de64c82', "51d2e839-a9d7-4f26-b1fd-fb0743c3a236",
'7232bc80-a9cb-4668-9449-16b9282a2331', '29a194bd-2ecc-44bc-a6a9-6b8a25c3d860', "f2212243-4553-456d-87e7-7ddfd6ee7563",
'52b43400-117a-4e16-81b8-b510fe987750', '52ef0c45-9ba1-4d5e-ba0c-82703196ec6c', "2d6982d3-7b46-4022-bbb6-cb5451c581ba",
'9f0fa778-e6ae-4f7b-b4fd-2ccb1bd37eb7', '54b57afd-c91c-4d5f-8177-c3fe940bc723', "1cccae22-5140-46b5-ba6a-aa17cec14dc3",
'23620661-6eb6-41df-9de4-18ff99e6f35f', 'c983fe89-6ac3-4395-882e-4b11aa11a99d', "b01dd2c2-ffcf-4c02-a274-e12296dbe4e1",
'd4d01494-e7d0-4a38-aa93-523a77855371', 'e5a77df6-fd29-48c7-8df6-f625de0179e7', "2669d193-a81f-4752-a003-dd2433beef92",
'bca45eef-fc64-465f-9106-403814f180a0', '3b5c305d-be5e-4209-9ba2-c2d1620a5a4f', "06203ee6-3615-4e26-bb08-4fab249698a4",
'14c533a5-c15c-493b-8c7d-7c20a0bc5ea1', '7b3b1e40-c75d-4c2c-843b-1599c10123c2', "933c1426-c759-43a3-9770-c8e3948db998",
'54604482-72aa-4d64-a3bb-9d8dc4a38b41', 'baf6f609-2dce-4978-840b-b74261c5ba6d', "02046384-fb05-4f1d-855a-f0e52a0761e3",
'250c536f-6cd9-4707-8706-b682961be032', 'a9b08140-ae44-409a-9941-22794c3fd097', "b5266e9b-7372-4df0-94a9-ec8a6c0aabc6",
'e516bd18-c46f-46b5-88dc-c7eac5a0f5a9', '6a220133-c71f-44df-8912-16f5b72568cb', "fb8e3a3d-505a-4157-bd9a-b73b00591f10",
'b63c2adf-b8f3-4e43-8651-44574475621f', '01d88287-387b-4a34-b6d3-0931aba8eb1a', "42092675-9171-400f-ab4d-c898ec9f70bd",
'eb0cef3d-4e2b-45b3-88df-fc547a763d01', 'a79dee0f-8579-4e0a-b77f-0a468a3ca71f', "1d6307e2-98c2-42f2-b765-d02a68e0da80",
'62e50edb-03e6-4f95-a6f4-8ff3ef28a32b', '337fffe8-cc68-44b7-9636-2433f86ed5a9', "8f431ed0-31df-4435-bcba-aff46929fe82",
'bd68f7b7-f8bb-489e-ac29-f8cbfe1978b3', '93d2a7d6-06a6-41bf-9632-c24e1f1c917a'] "d573d76f-d715-4a54-bdde-7ac7b6235fb2",
"feb8985b-84fb-4d41-bc41-70c322f508fa",
"f9f945cc-c166-4f7d-a95f-f0be154c43cf",
"009b130a-1709-41a6-943d-85eee4889792",
"a3f765f7-2329-47e0-83eb-0837b4b54b43",
"fea9e558-206f-444a-8d7f-a3f0301fda8d",
"9b419098-ccfc-467d-9de0-db18b60d6c28",
"e6c2174e-3a3f-4193-9782-1bf3b5cfae63",
"d42c962e-1691-4fa4-8926-c33157423bd2",
"8d27aa83-a103-4f05-8230-69e857bc3ffd",
"60cd19b2-6858-4704-b07b-5196e8e24325",
"31624ea4-1e4f-4f90-ae4f-759a0eb93b61",
"fcd038f4-6b05-485b-afe3-ce67e05b1695",
"1207bbe5-2df5-4c86-8f54-1b2675a86859",
"eea3b0b0-deae-41de-b010-d6d50855d016",
"ca8b0278-744c-4a3f-a2b8-6ab29d76fa7f",
"68d37bc8-28b9-49fd-9e02-48a3c9195fdf",
"8bf13f61-c5cc-4332-aab9-37405d1fdd14",
"8ceef7de-9408-48d1-8bb9-b8ac3ef73596",
"7e053a85-b04c-484b-8815-d26ce646c748",
"dfc09237-85b7-4596-b4e8-0bfbfd1aaeb7",
"d8d291ab-9dfa-45c0-bfc2-0a81167e3c63",
"d0834157-d464-4632-b620-147e211e0614",
"30758f35-fabc-48bc-b410-47450de64c82",
"7232bc80-a9cb-4668-9449-16b9282a2331",
"29a194bd-2ecc-44bc-a6a9-6b8a25c3d860",
"52b43400-117a-4e16-81b8-b510fe987750",
"52ef0c45-9ba1-4d5e-ba0c-82703196ec6c",
"9f0fa778-e6ae-4f7b-b4fd-2ccb1bd37eb7",
"54b57afd-c91c-4d5f-8177-c3fe940bc723",
"23620661-6eb6-41df-9de4-18ff99e6f35f",
"c983fe89-6ac3-4395-882e-4b11aa11a99d",
"d4d01494-e7d0-4a38-aa93-523a77855371",
"e5a77df6-fd29-48c7-8df6-f625de0179e7",
"bca45eef-fc64-465f-9106-403814f180a0",
"3b5c305d-be5e-4209-9ba2-c2d1620a5a4f",
"14c533a5-c15c-493b-8c7d-7c20a0bc5ea1",
"7b3b1e40-c75d-4c2c-843b-1599c10123c2",
"54604482-72aa-4d64-a3bb-9d8dc4a38b41",
"baf6f609-2dce-4978-840b-b74261c5ba6d",
"250c536f-6cd9-4707-8706-b682961be032",
"a9b08140-ae44-409a-9941-22794c3fd097",
"e516bd18-c46f-46b5-88dc-c7eac5a0f5a9",
"6a220133-c71f-44df-8912-16f5b72568cb",
"b63c2adf-b8f3-4e43-8651-44574475621f",
"01d88287-387b-4a34-b6d3-0931aba8eb1a",
"eb0cef3d-4e2b-45b3-88df-fc547a763d01",
"a79dee0f-8579-4e0a-b77f-0a468a3ca71f",
"62e50edb-03e6-4f95-a6f4-8ff3ef28a32b",
"337fffe8-cc68-44b7-9636-2433f86ed5a9",
"bd68f7b7-f8bb-489e-ac29-f8cbfe1978b3",
"93d2a7d6-06a6-41bf-9632-c24e1f1c917a",
]
rounds = 0 rounds = 0
for i in quiz_list: for i in quiz_list:
rounds = rounds + 1 rounds = rounds + 1
+13 -6
View File
@@ -10,11 +10,14 @@ file_contents = b"hello world"
def test_storage_init(): def test_storage_init():
with pytest.raises(NotImplementedError): with pytest.raises(NotImplementedError):
Storage(backend="asdsad", deta_key=settings.deta_project_key, Storage(
deta_id=settings.deta_project_id, storage_path=settings.storage_path) backend="asdsad",
deta_key=settings.deta_project_key,
deta_id=settings.deta_project_id,
storage_path=settings.storage_path,
)
with pytest.raises(ValueError): with pytest.raises(ValueError):
Storage(backend="deta", deta_key=None, Storage(backend="deta", deta_key=None, deta_id=None, storage_path=None)
deta_id=None, storage_path=None)
with pytest.raises(ValueError): with pytest.raises(ValueError):
Storage(backend="local", storage_path=None, deta_key=None, deta_id=None) Storage(backend="local", storage_path=None, deta_key=None, deta_id=None)
@@ -34,8 +37,12 @@ async def storage_tester(storage: Storage):
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_deta(): async def test_deta():
storage: Storage = Storage(backend="deta", deta_key=settings.deta_project_key, storage: Storage = Storage(
deta_id=settings.deta_project_id, storage_path=settings.storage_path) backend="deta",
deta_key=settings.deta_project_key,
deta_id=settings.deta_project_id,
storage_path=settings.storage_path,
)
await storage_tester(storage) await storage_tester(storage)
+3
View File
@@ -0,0 +1,3 @@
[tool.black]
line-length = 120
target-version = ["py310"]