feature: now using uv + moved to asyncpg from psycopg2
This commit is contained in:
@@ -6,6 +6,25 @@ compose.yaml
|
||||
compose.yml
|
||||
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
.github
|
||||
|
||||
# Python / uv
|
||||
.venv
|
||||
__pycache__
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
.python-version
|
||||
|
||||
# Tests
|
||||
test.py
|
||||
test/
|
||||
test.sql
|
||||
tables.sql
|
||||
|
||||
# Docs / env
|
||||
*.md
|
||||
.env
|
||||
example.env
|
||||
|
||||
+20
-8
@@ -3,25 +3,31 @@ services:
|
||||
image: git.frik.su/Beesquit/picrinth-server:latest
|
||||
container_name: prcrinth-server
|
||||
environment:
|
||||
# This should be configured
|
||||
access_token_expiration_time: 10080
|
||||
# Configuration
|
||||
secret_key: "your-key"
|
||||
swagger_enabled: true
|
||||
access_token_expiration_time: 10080
|
||||
join_code_length: 8
|
||||
# This part should not be touched. Probably
|
||||
swagger_enabled: false
|
||||
# Extra configuration (don't touch if you don't know what this is)
|
||||
algorithm: "HS256"
|
||||
db_host: "127.0.0.1" # review this later
|
||||
db_port: 5434
|
||||
# DB (postgres) settings
|
||||
# change if you have another instance of postgress you want to use
|
||||
db_host: "picrinth-postgres"
|
||||
db_port: 5432
|
||||
db_name: "picrinth"
|
||||
db_user: "postgres"
|
||||
db_password: "postgres"
|
||||
ports:
|
||||
- 8000:8000
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- local
|
||||
|
||||
postgres:
|
||||
image: postgres:latest
|
||||
container_name: picrinth_postgres
|
||||
container_name: picrinth-postgres
|
||||
environment:
|
||||
POSTGRES_USER: "postgres"
|
||||
POSTGRES_PASSWORD: "postgres"
|
||||
@@ -29,7 +35,7 @@ services:
|
||||
PGDATA: /var/lib/postgresql/data/pgdata
|
||||
POSTGRES_INITDB_ARGS: "--auth-host=scram-sha-256"
|
||||
ports:
|
||||
- 5432:5432
|
||||
- :5432
|
||||
volumes:
|
||||
- ./pgdata:/var/lib/postgresql/data/pgdata
|
||||
healthcheck:
|
||||
@@ -40,7 +46,13 @@ services:
|
||||
restart: unless-stopped
|
||||
tty: true
|
||||
stdin_open: true
|
||||
networks:
|
||||
- local
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
local:
|
||||
driver: bridge
|
||||
|
||||
+19
-5
@@ -1,9 +1,23 @@
|
||||
FROM python:3.13-slim
|
||||
FROM python:3.13-slim AS base
|
||||
|
||||
ENV UV_LINK_MODE=copy \
|
||||
UV_COMPILE_BYTECODE=1 \
|
||||
UV_PYTHON_DOWNLOADS=never \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONPATH=/app/src
|
||||
|
||||
COPY --from=ghcr.io/astral-sh/uv:0.11.23 /uv /uvx /usr/local/bin/
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt ./
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
COPY pyproject.toml uv.lock ./
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
uv sync --frozen --no-install-project --no-dev
|
||||
|
||||
COPY . .
|
||||
CMD ["python", "src/__main__.py"]
|
||||
COPY src src
|
||||
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
uv sync --frozen --no-dev
|
||||
|
||||
CMD ["uv", "run", "python", "src/__main__.py"]
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
[project]
|
||||
name = "picrinth-server"
|
||||
version = "0.1.0"
|
||||
description = "Server part for the Picrinth app"
|
||||
requires-python = ">=3.13"
|
||||
readme = "README.md"
|
||||
authors = [
|
||||
{ name = "Beesquit" }
|
||||
]
|
||||
dependencies = [
|
||||
"annotated-types==0.7.0",
|
||||
"anyio==4.9.0",
|
||||
"asyncpg==0.30.0",
|
||||
"attrs==25.3.0",
|
||||
"bcrypt==4.3.0",
|
||||
"certifi==2025.8.3",
|
||||
"charset-normalizer==3.4.2",
|
||||
"click==8.2.1",
|
||||
"cloudscraper==1.2.71",
|
||||
"fastapi==0.116.1",
|
||||
"gppt==4.1.1",
|
||||
"h11==0.16.0",
|
||||
"idna==3.10",
|
||||
"loguru==0.7.3",
|
||||
"outcome==1.3.0.post0",
|
||||
"passlib==1.7.4",
|
||||
"pixivpy3==3.7.5",
|
||||
"pwinput==1.0.3",
|
||||
"pydantic==2.11.7",
|
||||
"pydantic-core==2.33.2",
|
||||
"pyjwt==2.10.1",
|
||||
"pyparsing==3.2.3",
|
||||
"pysocks==1.7.1",
|
||||
"python-decouple==3.8",
|
||||
"python-multipart==0.0.20",
|
||||
"requests==2.32.4",
|
||||
"requests-toolbelt==1.0.0",
|
||||
"selenium==4.34.2",
|
||||
"sniffio==1.3.1",
|
||||
"sortedcontainers==2.4.0",
|
||||
"starlette==0.47.2",
|
||||
"trio==0.30.0",
|
||||
"trio-websocket==0.12.2",
|
||||
"typing-extensions==4.14.1",
|
||||
"typing-inspection==0.4.1",
|
||||
"urllib3==2.5.0",
|
||||
"uvicorn==0.35.0",
|
||||
"websocket-client==1.8.0",
|
||||
"wsproto==1.2.0",
|
||||
]
|
||||
|
||||
[tool.uv]
|
||||
package = false
|
||||
@@ -1,39 +0,0 @@
|
||||
annotated-types==0.7.0
|
||||
anyio==4.9.0
|
||||
attrs==25.3.0
|
||||
bcrypt==4.3.0
|
||||
certifi==2025.8.3
|
||||
charset-normalizer==3.4.2
|
||||
click==8.2.1
|
||||
cloudscraper==1.2.71
|
||||
fastapi==0.116.1
|
||||
gppt==4.1.1
|
||||
h11==0.16.0
|
||||
idna==3.10
|
||||
loguru==0.7.3
|
||||
outcome==1.3.0.post0
|
||||
passlib==1.7.4
|
||||
pixivpy3==3.7.5
|
||||
psycopg2-binary==2.9.10
|
||||
pwinput==1.0.3
|
||||
pydantic==2.11.7
|
||||
pydantic_core==2.33.2
|
||||
PyJWT==2.10.1
|
||||
pyparsing==3.2.3
|
||||
PySocks==1.7.1
|
||||
python-decouple==3.8
|
||||
python-multipart==0.0.20
|
||||
requests==2.32.4
|
||||
requests-toolbelt==1.0.0
|
||||
selenium==4.34.2
|
||||
sniffio==1.3.1
|
||||
sortedcontainers==2.4.0
|
||||
starlette==0.47.2
|
||||
trio==0.30.0
|
||||
trio-websocket==0.12.2
|
||||
typing-inspection==0.4.1
|
||||
typing_extensions==4.14.1
|
||||
urllib3==2.5.0
|
||||
uvicorn==0.35.0
|
||||
websocket-client==1.8.0
|
||||
wsproto==1.2.0
|
||||
+13
-13
@@ -1,7 +1,7 @@
|
||||
from typing import Annotated
|
||||
|
||||
from asyncpg import Connection
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from psycopg2._psycopg import connection
|
||||
|
||||
import db.accounts as db
|
||||
import settings.settings as settings
|
||||
@@ -17,22 +17,22 @@ accounts_router = APIRouter(prefix="/api/accounts", tags=["accounts"])
|
||||
@accounts_router.post("/group")
|
||||
async def read_accounts_by_group(
|
||||
groupname: str,
|
||||
conn: Annotated[connection, Depends(get_db_connection)],
|
||||
conn: Annotated[Connection, Depends(get_db_connection)],
|
||||
current_user: Annotated[User, Depends(get_current_user)]
|
||||
):
|
||||
if not check_group_author(conn, groupname, current_user.username) and current_user.role not in settings.settings.admin_roles:
|
||||
if not await check_group_author(conn, groupname, current_user.username) and current_user.role not in settings.settings.admin_roles:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Not allowed",
|
||||
)
|
||||
return db.get_accounts_by_group(conn, groupname)
|
||||
return await db.get_accounts_by_group(conn, groupname)
|
||||
|
||||
|
||||
@accounts_router.post("/group/platform")
|
||||
async def read_accounts_by_group_platform(
|
||||
groupname: str,
|
||||
platform: str,
|
||||
conn: Annotated[connection, Depends(get_db_connection)],
|
||||
conn: Annotated[Connection, Depends(get_db_connection)],
|
||||
current_user: Annotated[User, Depends(get_current_user)]
|
||||
):
|
||||
if current_user.role not in settings.settings.admin_roles:
|
||||
@@ -40,7 +40,7 @@ async def read_accounts_by_group_platform(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Not allowed",
|
||||
)
|
||||
account_data = db.get_accounts_by_group_platform(conn, groupname, platform.lower())
|
||||
account_data = await db.get_accounts_by_group_platform(conn, groupname, platform.lower())
|
||||
if account_data is None:
|
||||
return HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
@@ -56,21 +56,21 @@ async def add_account(
|
||||
login: str,
|
||||
password: str,
|
||||
metadata: dict,
|
||||
conn: Annotated[connection, Depends(get_db_connection)],
|
||||
conn: Annotated[Connection, Depends(get_db_connection)],
|
||||
current_user: Annotated[User, Depends(get_current_user)]
|
||||
):
|
||||
if not check_group_author(conn, groupname, current_user.username) and current_user.role not in settings.settings.admin_roles:
|
||||
if not await check_group_author(conn, groupname, current_user.username) and current_user.role not in settings.settings.admin_roles:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Not allowed",
|
||||
)
|
||||
if db.check_account_existence(conn, groupname, platform.lower()):
|
||||
if await db.check_account_existence(conn, groupname, platform.lower()):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail="Account already exists",
|
||||
)
|
||||
hashed_password = encode_str(password)
|
||||
return db.create_account(conn, groupname, current_user.username, platform.lower(), login, hashed_password, metadata)
|
||||
return await db.create_account(conn, groupname, current_user.username, platform.lower(), login, hashed_password, metadata)
|
||||
|
||||
|
||||
@accounts_router.post("/update")
|
||||
@@ -81,10 +81,10 @@ async def update_account(
|
||||
login: str,
|
||||
password: str,
|
||||
metadata: dict,
|
||||
conn: Annotated[connection, Depends(get_db_connection)],
|
||||
conn: Annotated[Connection, Depends(get_db_connection)],
|
||||
current_user: Annotated[User, Depends(get_current_user)]
|
||||
):
|
||||
account_data = db.get_accounts_by_group_platform(conn, groupname, platform.lower())
|
||||
account_data = await db.get_accounts_by_group_platform(conn, groupname, platform.lower())
|
||||
if account_data is None:
|
||||
return HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
@@ -97,4 +97,4 @@ async def update_account(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Not allowed",
|
||||
)
|
||||
return db.update_account(conn, groupname, author, platform.lower(), login, password, metadata)
|
||||
return await db.update_account(conn, groupname, author, platform.lower(), login, password, metadata)
|
||||
|
||||
+7
-7
@@ -1,7 +1,7 @@
|
||||
from typing import Annotated
|
||||
|
||||
from asyncpg import Connection
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from psycopg2._psycopg import connection
|
||||
|
||||
import db.users as db
|
||||
import settings.settings as settings
|
||||
@@ -15,36 +15,36 @@ anon_router = APIRouter(prefix="/api/anon", tags=["anon"])
|
||||
async def add_admin(
|
||||
username: str,
|
||||
password: str,
|
||||
conn: Annotated[connection, Depends(get_db_connection)]
|
||||
conn: Annotated[Connection, Depends(get_db_connection)]
|
||||
):
|
||||
if not settings.settings.allow_create_admins:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Not allowed",
|
||||
)
|
||||
if db.check_user_existence(conn, username):
|
||||
if await db.check_user_existence(conn, username):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail="User already exists",
|
||||
)
|
||||
hashed_password = get_password_hash(password)
|
||||
return db.create_user(conn, username, hashed_password, "admin")
|
||||
return await db.create_user(conn, username, hashed_password, "admin")
|
||||
|
||||
@anon_router.post("/add/user")
|
||||
async def add_user(
|
||||
username: str,
|
||||
password: str,
|
||||
conn: Annotated[connection, Depends(get_db_connection)]
|
||||
conn: Annotated[Connection, Depends(get_db_connection)]
|
||||
):
|
||||
if not settings.settings.allow_create_users:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Not allowed",
|
||||
)
|
||||
if db.check_user_existence(conn, username):
|
||||
if await db.check_user_existence(conn, username):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail="User already exists",
|
||||
)
|
||||
hashed_password = get_password_hash(password)
|
||||
return db.create_user(conn, username, hashed_password, "user")
|
||||
return await db.create_user(conn, username, hashed_password, "user")
|
||||
|
||||
+4
-4
@@ -1,9 +1,9 @@
|
||||
from datetime import timedelta
|
||||
from typing import Annotated
|
||||
|
||||
from asyncpg import Connection
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
from psycopg2._psycopg import connection
|
||||
|
||||
from api.models import Token
|
||||
from api.utils import authenticate_user, create_access_token
|
||||
@@ -17,9 +17,9 @@ auth_router = APIRouter(prefix="/api", tags=["auth"])
|
||||
@auth_router.post("/token")
|
||||
async def login(
|
||||
form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
|
||||
conn: Annotated[connection, Depends(get_db_connection)]
|
||||
conn: Annotated[Connection, Depends(get_db_connection)]
|
||||
) -> Token:
|
||||
password_correct = authenticate_user(conn, form_data.username, form_data.password)
|
||||
password_correct = await authenticate_user(conn, form_data.username, form_data.password)
|
||||
if not password_correct:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
@@ -27,7 +27,7 @@ async def login(
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
user_disabled = check_user_disabled(conn, form_data.username)
|
||||
user_disabled = await check_user_disabled(conn, form_data.username)
|
||||
if user_disabled:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
|
||||
+12
-12
@@ -1,7 +1,7 @@
|
||||
from typing import Annotated
|
||||
|
||||
from asyncpg import Connection
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from psycopg2._psycopg import connection
|
||||
|
||||
import db.feeds as db
|
||||
import settings.settings as settings
|
||||
@@ -20,10 +20,10 @@ feeds_router = APIRouter(prefix="/api/feeds", tags=["feeds"])
|
||||
@feeds_router.post("/feed")
|
||||
async def read_feed(
|
||||
feed_id: int,
|
||||
conn: Annotated[connection, Depends(get_db_connection)],
|
||||
conn: Annotated[Connection, Depends(get_db_connection)],
|
||||
current_user: Annotated[User, Depends(get_current_user)]
|
||||
):
|
||||
feed_data = db.get_feed(conn, feed_id)
|
||||
feed_data = await db.get_feed(conn, feed_id)
|
||||
if feed_data is None:
|
||||
return HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
@@ -31,14 +31,14 @@ async def read_feed(
|
||||
)
|
||||
feed = Feed().fill(feed_data)
|
||||
|
||||
groupname = get_groupname_by_feed_id(conn, feed_id)
|
||||
groupname = await get_groupname_by_feed_id(conn, feed_id)
|
||||
if groupname is None:
|
||||
return HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="No such feed",
|
||||
)
|
||||
|
||||
if not check_membership_exists(conn, current_user.username, groupname) and current_user.role not in settings.settings.admin_roles:
|
||||
if not await check_membership_exists(conn, current_user.username, groupname) and current_user.role not in settings.settings.admin_roles:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Not allowed",
|
||||
@@ -51,19 +51,19 @@ async def read_feed(
|
||||
@feeds_router.post("/new")
|
||||
async def new_feed(
|
||||
groupname: str,
|
||||
conn: Annotated[connection, Depends(get_db_connection)],
|
||||
conn: Annotated[Connection, Depends(get_db_connection)],
|
||||
current_user: Annotated[User, Depends(get_current_user)]
|
||||
):
|
||||
if not check_group_author(conn, groupname, current_user.username) and current_user.role not in settings.settings.admin_roles:
|
||||
if not await check_group_author(conn, groupname, current_user.username) and current_user.role not in settings.settings.admin_roles:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Not allowed",
|
||||
)
|
||||
|
||||
accounts = get_accounts_by_group(conn, groupname)
|
||||
feed = generate_feed(conn, accounts)
|
||||
accounts = await get_accounts_by_group(conn, groupname)
|
||||
feed = await generate_feed(conn, accounts)
|
||||
if feed:
|
||||
return db.create_feed(conn, groupname, feed)
|
||||
return await db.create_feed(conn, groupname, feed)
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_424_FAILED_DEPENDENCY,
|
||||
@@ -75,11 +75,11 @@ async def new_feed(
|
||||
@feeds_router.post("/delete")
|
||||
async def delete_feed(
|
||||
feed_id: int,
|
||||
conn: Annotated[connection, Depends(get_db_connection)],
|
||||
conn: Annotated[Connection, Depends(get_db_connection)],
|
||||
current_user: Annotated[User, Depends(get_current_user)]
|
||||
):
|
||||
if current_user.role in settings.settings.admin_roles:
|
||||
return db.delete_feed(conn, feed_id)
|
||||
return await db.delete_feed(conn, feed_id)
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
|
||||
+39
-39
@@ -1,8 +1,8 @@
|
||||
import secrets
|
||||
from typing import Annotated
|
||||
|
||||
from asyncpg import Connection
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from psycopg2._psycopg import connection
|
||||
|
||||
import db.groups as db
|
||||
import settings.settings as settings
|
||||
@@ -19,7 +19,7 @@ groups_router = APIRouter(prefix="/api/groups", tags=["groups"])
|
||||
@groups_router.post("/group")
|
||||
async def read_any_group(
|
||||
groupname: str,
|
||||
conn: Annotated[connection, Depends(get_db_connection)],
|
||||
conn: Annotated[Connection, Depends(get_db_connection)],
|
||||
current_user: Annotated[User, Depends(get_current_user)]
|
||||
):
|
||||
if current_user.role not in settings.settings.admin_roles:
|
||||
@@ -27,7 +27,7 @@ async def read_any_group(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Not allowed",
|
||||
)
|
||||
group_data = db.get_group(conn, groupname)
|
||||
group_data = await db.get_group(conn, groupname)
|
||||
if group_data is None:
|
||||
return HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
@@ -38,15 +38,15 @@ async def read_any_group(
|
||||
@groups_router.post("/invite_code")
|
||||
async def read_group_invite_code(
|
||||
groupname: str,
|
||||
conn: Annotated[connection, Depends(get_db_connection)],
|
||||
conn: Annotated[Connection, Depends(get_db_connection)],
|
||||
current_user: Annotated[User, Depends(get_current_user)]
|
||||
):
|
||||
if not check_membership_exists(conn, current_user.username, groupname) and current_user.role not in settings.settings.admin_roles:
|
||||
if not await check_membership_exists(conn, current_user.username, groupname) and current_user.role not in settings.settings.admin_roles:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Not allowed",
|
||||
)
|
||||
invite_code = db.get_group_invite_code(conn, groupname)
|
||||
invite_code = await db.get_group_invite_code(conn, groupname)
|
||||
if invite_code is None:
|
||||
return HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
@@ -57,20 +57,20 @@ async def read_group_invite_code(
|
||||
@groups_router.post("/last_feed")
|
||||
async def read_group_last_feed_id(
|
||||
groupname: str,
|
||||
conn: Annotated[connection, Depends(get_db_connection)],
|
||||
conn: Annotated[Connection, Depends(get_db_connection)],
|
||||
current_user: Annotated[User, Depends(get_current_user)]
|
||||
):
|
||||
if not check_membership_exists(conn, current_user.username, groupname) and current_user.role not in settings.settings.admin_roles:
|
||||
if not await check_membership_exists(conn, current_user.username, groupname) and current_user.role not in settings.settings.admin_roles:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Not allowed",
|
||||
)
|
||||
return db.get_group_last_feed_id(conn, groupname)
|
||||
return await db.get_group_last_feed_id(conn, groupname)
|
||||
|
||||
|
||||
@groups_router.post("/add")
|
||||
async def add_group(
|
||||
conn: Annotated[connection, Depends(get_db_connection)],
|
||||
conn: Annotated[Connection, Depends(get_db_connection)],
|
||||
current_user: Annotated[User, Depends(get_current_user)],
|
||||
groupname: str,
|
||||
allow_skips: bool = True,
|
||||
@@ -82,30 +82,30 @@ async def add_group(
|
||||
detail="Not allowed",
|
||||
)
|
||||
|
||||
if db.check_group_existence(conn, groupname):
|
||||
if await db.check_group_existence(conn, groupname):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail="Group already exists",
|
||||
)
|
||||
|
||||
invite_code = "".join(secrets.choice(JOIN_CODE_SYMBOLS) for _ in range(startup_settings.join_code_length))
|
||||
while db.check_invite_code(conn, invite_code):
|
||||
while await db.check_invite_code(conn, invite_code):
|
||||
invite_code = "".join(secrets.choice(JOIN_CODE_SYMBOLS) for _ in range(startup_settings.join_code_length))
|
||||
return {
|
||||
"result": db.create_group(conn, groupname, invite_code, current_user.username, allow_skips, feed_interval_minutes),
|
||||
"result": await db.create_group(conn, groupname, invite_code, current_user.username, allow_skips, feed_interval_minutes),
|
||||
"invite code": invite_code
|
||||
}
|
||||
|
||||
@groups_router.post("/delete")
|
||||
async def delete_group(
|
||||
groupname: str,
|
||||
conn: Annotated[connection, Depends(get_db_connection)],
|
||||
conn: Annotated[Connection, Depends(get_db_connection)],
|
||||
current_user: Annotated[User, Depends(get_current_user)]
|
||||
):
|
||||
if current_user.role in settings.settings.admin_roles:
|
||||
return db.delete_group(conn, groupname)
|
||||
if db.check_group_author(conn, groupname, current_user.username):
|
||||
return db.delete_group(conn, groupname)
|
||||
return await db.delete_group(conn, groupname)
|
||||
if await db.check_group_author(conn, groupname, current_user.username):
|
||||
return await db.delete_group(conn, groupname)
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
@@ -117,19 +117,19 @@ async def delete_group(
|
||||
async def update_groupname(
|
||||
groupname: str,
|
||||
new_groupname: str,
|
||||
conn: Annotated[connection, Depends(get_db_connection)],
|
||||
conn: Annotated[Connection, Depends(get_db_connection)],
|
||||
current_user: Annotated[User, Depends(get_current_user)]
|
||||
):
|
||||
if db.check_group_existence(conn, new_groupname):
|
||||
if await db.check_group_existence(conn, new_groupname):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail="Groupname is already taken",
|
||||
)
|
||||
|
||||
if current_user.role in settings.settings.admin_roles:
|
||||
return db.update_group_groupname(conn, groupname, new_groupname)
|
||||
if db.check_group_author(conn, groupname, current_user.username):
|
||||
return db.update_group_groupname(conn, groupname, new_groupname)
|
||||
return await db.update_group_groupname(conn, groupname, new_groupname)
|
||||
if await db.check_group_author(conn, groupname, current_user.username):
|
||||
return await db.update_group_groupname(conn, groupname, new_groupname)
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
@@ -140,13 +140,13 @@ async def update_groupname(
|
||||
async def update_author(
|
||||
groupname: str,
|
||||
new_author: str,
|
||||
conn: Annotated[connection, Depends(get_db_connection)],
|
||||
conn: Annotated[Connection, Depends(get_db_connection)],
|
||||
current_user: Annotated[User, Depends(get_current_user)]
|
||||
):
|
||||
if current_user.role in settings.settings.admin_roles:
|
||||
return db.update_group_author(conn, groupname, new_author)
|
||||
if db.check_group_author(conn, groupname, current_user.username):
|
||||
return db.update_group_author(conn, groupname, new_author)
|
||||
return await db.update_group_author(conn, groupname, new_author)
|
||||
if await db.check_group_author(conn, groupname, current_user.username):
|
||||
return await db.update_group_author(conn, groupname, new_author)
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
@@ -156,21 +156,21 @@ async def update_author(
|
||||
@groups_router.get("/update/invite_code")
|
||||
async def update_invite_code(
|
||||
groupname: str,
|
||||
conn: Annotated[connection, Depends(get_db_connection)],
|
||||
conn: Annotated[Connection, Depends(get_db_connection)],
|
||||
current_user: Annotated[User, Depends(get_current_user)]
|
||||
):
|
||||
invite_code = "".join(secrets.choice(JOIN_CODE_SYMBOLS) for _ in range(startup_settings.join_code_length))
|
||||
while db.check_invite_code(conn, invite_code):
|
||||
while await db.check_invite_code(conn, invite_code):
|
||||
invite_code = "".join(secrets.choice(JOIN_CODE_SYMBOLS) for _ in range(startup_settings.join_code_length))
|
||||
|
||||
if current_user.role in settings.settings.admin_roles:
|
||||
return {
|
||||
"result": db.update_group_invite_code(conn, groupname, invite_code),
|
||||
"result": await db.update_group_invite_code(conn, groupname, invite_code),
|
||||
"invite code": invite_code
|
||||
}
|
||||
if db.check_group_author(conn, groupname, current_user.username):
|
||||
if await db.check_group_author(conn, groupname, current_user.username):
|
||||
return {
|
||||
"result": db.update_group_invite_code(conn, groupname, invite_code),
|
||||
"result": await db.update_group_invite_code(conn, groupname, invite_code),
|
||||
"invite code": invite_code
|
||||
}
|
||||
else:
|
||||
@@ -184,13 +184,13 @@ async def update_invite_code(
|
||||
async def update_allow_skips(
|
||||
groupname: str,
|
||||
allow_skips: bool,
|
||||
conn: Annotated[connection, Depends(get_db_connection)],
|
||||
conn: Annotated[Connection, Depends(get_db_connection)],
|
||||
current_user: Annotated[User, Depends(get_current_user)]
|
||||
):
|
||||
if current_user.role in settings.settings.admin_roles:
|
||||
return db.update_group_allow_skips(conn, groupname, allow_skips)
|
||||
if db.check_group_author(conn, groupname, current_user.username):
|
||||
return db.update_group_allow_skips(conn, groupname, allow_skips)
|
||||
return await db.update_group_allow_skips(conn, groupname, allow_skips)
|
||||
if await db.check_group_author(conn, groupname, current_user.username):
|
||||
return await db.update_group_allow_skips(conn, groupname, allow_skips)
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
@@ -202,13 +202,13 @@ async def update_allow_skips(
|
||||
async def update_feed_interval(
|
||||
groupname: str,
|
||||
feed_interval: int,
|
||||
conn: Annotated[connection, Depends(get_db_connection)],
|
||||
conn: Annotated[Connection, Depends(get_db_connection)],
|
||||
current_user: Annotated[User, Depends(get_current_user)]
|
||||
):
|
||||
if current_user.role in settings.settings.admin_roles:
|
||||
return db.update_group_feed_interval(conn, groupname, feed_interval)
|
||||
if db.check_group_author(conn, groupname, current_user.username):
|
||||
return db.update_group_feed_interval(conn, groupname, feed_interval)
|
||||
return await db.update_group_feed_interval(conn, groupname, feed_interval)
|
||||
if await db.check_group_author(conn, groupname, current_user.username):
|
||||
return await db.update_group_feed_interval(conn, groupname, feed_interval)
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
|
||||
+19
-19
@@ -1,7 +1,7 @@
|
||||
from typing import Annotated
|
||||
|
||||
from asyncpg import Connection
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from psycopg2._psycopg import connection
|
||||
|
||||
import db.memberships as db
|
||||
import settings.settings as settings
|
||||
@@ -20,18 +20,18 @@ memberships_router = APIRouter(prefix="/api/membership", tags=["memberships"])
|
||||
|
||||
@memberships_router.get("/me")
|
||||
async def read_users_groups(
|
||||
conn: Annotated[connection, Depends(get_db_connection)],
|
||||
conn: Annotated[Connection, Depends(get_db_connection)],
|
||||
current_user: Annotated[User, Depends(get_current_user)]
|
||||
):
|
||||
return db.get_memberships_by_username(conn, current_user.username)
|
||||
return await db.get_memberships_by_username(conn, current_user.username)
|
||||
|
||||
@memberships_router.post("/user")
|
||||
async def read_users_any_memberships(
|
||||
username: str,
|
||||
conn: Annotated[connection, Depends(get_db_connection)],
|
||||
conn: Annotated[Connection, Depends(get_db_connection)],
|
||||
current_user: Annotated[User, Depends(get_current_user)]
|
||||
):
|
||||
if not check_user_existence(conn, username):
|
||||
if not await check_user_existence(conn, username):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="User does not exist",
|
||||
@@ -43,35 +43,35 @@ async def read_users_any_memberships(
|
||||
detail="Not allowed",
|
||||
)
|
||||
|
||||
return db.get_memberships_by_username(conn, username)
|
||||
return await db.get_memberships_by_username(conn, username)
|
||||
|
||||
@memberships_router.post("/group")
|
||||
async def read_any_group_members(
|
||||
groupname: str,
|
||||
conn: Annotated[connection, Depends(get_db_connection)],
|
||||
conn: Annotated[Connection, Depends(get_db_connection)],
|
||||
current_user: Annotated[User, Depends(get_current_user)]
|
||||
):
|
||||
user_is_in_group = db.check_membership_exists(conn, current_user.username, groupname)
|
||||
user_is_in_group = await db.check_membership_exists(conn, current_user.username, groupname)
|
||||
if not user_is_in_group and current_user.role not in settings.settings.admin_roles:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Not allowed",
|
||||
)
|
||||
|
||||
if not check_group_existence(conn, groupname):
|
||||
if not await check_group_existence(conn, groupname):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="No such group",
|
||||
)
|
||||
|
||||
return db.get_memberships_by_groupname(conn, groupname)
|
||||
return await db.get_memberships_by_groupname(conn, groupname)
|
||||
|
||||
|
||||
@memberships_router.post("/add")
|
||||
async def add_membership(
|
||||
username: str,
|
||||
invite_code: str,
|
||||
conn: Annotated[connection, Depends(get_db_connection)],
|
||||
conn: Annotated[Connection, Depends(get_db_connection)],
|
||||
current_user: Annotated[User, Depends(get_current_user)]
|
||||
):
|
||||
if username != current_user.username and current_user.role not in settings.settings.admin_roles:
|
||||
@@ -80,39 +80,39 @@ async def add_membership(
|
||||
detail="Not allowed",
|
||||
)
|
||||
|
||||
groupname = get_groupname_by_invite_code(conn, invite_code)
|
||||
groupname = await get_groupname_by_invite_code(conn, invite_code)
|
||||
if groupname is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Invite code is incorrect",
|
||||
)
|
||||
|
||||
if not check_user_existence(conn, username):
|
||||
if not await check_user_existence(conn, username):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="User does not exist",
|
||||
)
|
||||
|
||||
if db.check_membership_exists(conn, username, groupname):
|
||||
if await db.check_membership_exists(conn, username, groupname):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail="User is already a member",
|
||||
)
|
||||
|
||||
return db.create_membership(conn, username, groupname)
|
||||
return await db.create_membership(conn, username, groupname)
|
||||
|
||||
@memberships_router.post("/delete")
|
||||
async def delete_membership(
|
||||
username: str,
|
||||
groupname: str,
|
||||
conn: Annotated[connection, Depends(get_db_connection)],
|
||||
conn: Annotated[Connection, Depends(get_db_connection)],
|
||||
current_user: Annotated[User, Depends(get_current_user)]
|
||||
):
|
||||
if current_user.role in settings.settings.admin_roles:
|
||||
return db.delete_membership(conn, username, groupname)
|
||||
return await db.delete_membership(conn, username, groupname)
|
||||
|
||||
if check_group_author(conn, groupname, current_user.username):
|
||||
return db.delete_membership(conn, username, groupname)
|
||||
if await check_group_author(conn, groupname, current_user.username):
|
||||
return await db.delete_membership(conn, username, groupname)
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
|
||||
+7
-7
@@ -1,7 +1,7 @@
|
||||
from typing import Annotated
|
||||
|
||||
from asyncpg import Connection
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from psycopg2._psycopg import connection
|
||||
|
||||
import db.pictures as db
|
||||
import settings.settings as settings
|
||||
@@ -15,10 +15,10 @@ pictures_router = APIRouter(prefix="/api/pictures", tags=["pictures"])
|
||||
@pictures_router.post("/picture")
|
||||
async def read_picture(
|
||||
id: int,
|
||||
conn: Annotated[connection, Depends(get_db_connection)],
|
||||
conn: Annotated[Connection, Depends(get_db_connection)],
|
||||
current_user: Annotated[User, Depends(get_current_user)]
|
||||
):
|
||||
picture_data = db.get_picture(conn, id)
|
||||
picture_data = await db.get_picture(conn, id)
|
||||
if picture_data is None:
|
||||
return HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
@@ -29,7 +29,7 @@ async def read_picture(
|
||||
|
||||
@pictures_router.post("/add")
|
||||
async def add_picture(
|
||||
conn: Annotated[connection, Depends(get_db_connection)],
|
||||
conn: Annotated[Connection, Depends(get_db_connection)],
|
||||
current_user: Annotated[User, Depends(get_current_user)],
|
||||
source: str,
|
||||
external_id: str,
|
||||
@@ -43,18 +43,18 @@ async def add_picture(
|
||||
)
|
||||
|
||||
return {
|
||||
"id": db.create_picture(conn, source, external_id, url, metadata)
|
||||
"id": await db.create_picture(conn, source, external_id, url, metadata)
|
||||
}
|
||||
|
||||
|
||||
@pictures_router.post("/delete")
|
||||
async def delete_picture(
|
||||
picture_id: int,
|
||||
conn: Annotated[connection, Depends(get_db_connection)],
|
||||
conn: Annotated[Connection, Depends(get_db_connection)],
|
||||
current_user: Annotated[User, Depends(get_current_user)]
|
||||
):
|
||||
if current_user.role in settings.settings.admin_roles:
|
||||
return db.delete_picture(conn, picture_id)
|
||||
return await db.delete_picture(conn, picture_id)
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
|
||||
+19
-19
@@ -1,7 +1,7 @@
|
||||
from typing import Annotated
|
||||
|
||||
from asyncpg import Connection
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from psycopg2._psycopg import connection
|
||||
|
||||
import db.swipes as db
|
||||
import settings.settings as settings
|
||||
@@ -23,22 +23,22 @@ async def read_swipe(
|
||||
username: str,
|
||||
feed_id: int,
|
||||
picture_id: int,
|
||||
conn: Annotated[connection, Depends(get_db_connection)],
|
||||
conn: Annotated[Connection, Depends(get_db_connection)],
|
||||
current_user: Annotated[User, Depends(get_current_user)]
|
||||
):
|
||||
groupname = get_groupname_by_feed_id(conn, feed_id)
|
||||
groupname = await get_groupname_by_feed_id(conn, feed_id)
|
||||
if groupname is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="No such feed or feed is not linked to group",
|
||||
)
|
||||
if not check_membership_exists(conn, current_user.username, groupname) and current_user.role not in settings.settings.admin_roles:
|
||||
if not await check_membership_exists(conn, current_user.username, groupname) and current_user.role not in settings.settings.admin_roles:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Not allowed",
|
||||
)
|
||||
|
||||
swipe_data = db.get_swipe(conn, username, feed_id, picture_id)
|
||||
swipe_data = await db.get_swipe(conn, username, feed_id, picture_id)
|
||||
if swipe_data is None:
|
||||
return HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
@@ -51,42 +51,42 @@ async def read_swipe(
|
||||
async def read_swipes_by_picture_id(
|
||||
feed_id: int,
|
||||
picture_id: int,
|
||||
conn: Annotated[connection, Depends(get_db_connection)],
|
||||
conn: Annotated[Connection, Depends(get_db_connection)],
|
||||
current_user: Annotated[User, Depends(get_current_user)]
|
||||
):
|
||||
groupname = get_groupname_by_feed_id(conn, feed_id)
|
||||
groupname = await get_groupname_by_feed_id(conn, feed_id)
|
||||
if groupname is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="No such feed or feed is not linked to group",
|
||||
)
|
||||
if not check_membership_exists(conn, current_user.username, groupname) and current_user.role not in settings.settings.admin_roles:
|
||||
if not await check_membership_exists(conn, current_user.username, groupname) and current_user.role not in settings.settings.admin_roles:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Not allowed",
|
||||
)
|
||||
return db.get_swipes_by_picture_id(conn, picture_id, feed_id)
|
||||
return await db.get_swipes_by_picture_id(conn, picture_id, feed_id)
|
||||
|
||||
|
||||
@swipes_router.post("/swipe/user")
|
||||
async def read_user_swipes(
|
||||
username: str,
|
||||
feed_id: int,
|
||||
conn: Annotated[connection, Depends(get_db_connection)],
|
||||
conn: Annotated[Connection, Depends(get_db_connection)],
|
||||
current_user: Annotated[User, Depends(get_current_user)]
|
||||
):
|
||||
groupname = get_groupname_by_feed_id(conn, feed_id)
|
||||
groupname = await get_groupname_by_feed_id(conn, feed_id)
|
||||
if groupname is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="No such feed or feed is not linked to group",
|
||||
)
|
||||
if not check_membership_exists(conn, current_user.username, groupname) and current_user.role not in settings.settings.admin_roles:
|
||||
if not await check_membership_exists(conn, current_user.username, groupname) and current_user.role not in settings.settings.admin_roles:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Not allowed",
|
||||
)
|
||||
return db.get_swipes_by_user(conn, username, feed_id)
|
||||
return await db.get_swipes_by_user(conn, username, feed_id)
|
||||
|
||||
|
||||
@swipes_router.post("/add")
|
||||
@@ -94,17 +94,17 @@ async def add_swipe(
|
||||
feed_id: int,
|
||||
picture_id: int,
|
||||
value: int,
|
||||
conn: Annotated[connection, Depends(get_db_connection)],
|
||||
conn: Annotated[Connection, Depends(get_db_connection)],
|
||||
current_user: Annotated[User, Depends(get_current_user)]
|
||||
):
|
||||
groupname = get_groupname_by_feed_id(conn, feed_id)
|
||||
groupname = await get_groupname_by_feed_id(conn, feed_id)
|
||||
if groupname is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="No such feed or feed is not linked to group",
|
||||
)
|
||||
|
||||
group_data = get_group(conn, groupname)
|
||||
group_data = await get_group(conn, groupname)
|
||||
if group_data is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
@@ -120,7 +120,7 @@ async def add_swipe(
|
||||
detail="Skips are disallowed in the group",
|
||||
)
|
||||
|
||||
result = db.create_swipe(conn, current_user.username, feed_id, picture_id, value)
|
||||
result = await db.create_swipe(conn, current_user.username, feed_id, picture_id, value)
|
||||
if result:
|
||||
# TODO: call function to like picture on the platform
|
||||
pass
|
||||
@@ -132,11 +132,11 @@ async def delete_swipe(
|
||||
username: str,
|
||||
feed_id: int,
|
||||
picture_id: int,
|
||||
conn: Annotated[connection, Depends(get_db_connection)],
|
||||
conn: Annotated[Connection, Depends(get_db_connection)],
|
||||
current_user: Annotated[User, Depends(get_current_user)]
|
||||
):
|
||||
if current_user.role in settings.settings.admin_roles:
|
||||
return db.delete_swipe(conn, username, feed_id, picture_id)
|
||||
return await db.delete_swipe(conn, username, feed_id, picture_id)
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
|
||||
+22
-22
@@ -1,7 +1,7 @@
|
||||
from typing import Annotated
|
||||
|
||||
from asyncpg import Connection
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from psycopg2._psycopg import connection
|
||||
|
||||
import db.users as db
|
||||
import settings.settings as settings
|
||||
@@ -19,7 +19,7 @@ async def read_users_me(current_user: Annotated[User, Depends(get_current_user)]
|
||||
@users_router.post("/user")
|
||||
async def read_users_any(
|
||||
username: str,
|
||||
conn: Annotated[connection, Depends(get_db_connection)],
|
||||
conn: Annotated[Connection, Depends(get_db_connection)],
|
||||
current_user: Annotated[User, Depends(get_current_user)]
|
||||
):
|
||||
if current_user.role not in settings.settings.admin_roles:
|
||||
@@ -27,7 +27,7 @@ async def read_users_any(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Not allowed",
|
||||
)
|
||||
user_data = db.get_user(conn, username)
|
||||
user_data = await db.get_user(conn, username)
|
||||
if user_data is None:
|
||||
return HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
@@ -41,7 +41,7 @@ async def read_users_any(
|
||||
async def add_admin(
|
||||
username: str,
|
||||
password: str,
|
||||
conn: Annotated[connection, Depends(get_db_connection)],
|
||||
conn: Annotated[Connection, Depends(get_db_connection)],
|
||||
current_user: Annotated[User, Depends(get_current_user)]
|
||||
):
|
||||
if not settings.settings.allow_create_admins_by_admins:
|
||||
@@ -54,19 +54,19 @@ async def add_admin(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Not allowed",
|
||||
)
|
||||
if db.check_user_existence(conn, username):
|
||||
if await db.check_user_existence(conn, username):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail="User already exists",
|
||||
)
|
||||
hashed_password = get_password_hash(password)
|
||||
return db.create_user(conn, username, hashed_password, "admin")
|
||||
return await db.create_user(conn, username, hashed_password, "admin")
|
||||
|
||||
@users_router.post("/add/user")
|
||||
async def add_user(
|
||||
username: str,
|
||||
password: str,
|
||||
conn: Annotated[connection, Depends(get_db_connection)],
|
||||
conn: Annotated[Connection, Depends(get_db_connection)],
|
||||
current_user: Annotated[User, Depends(get_current_user)]
|
||||
):
|
||||
if not settings.settings.allow_create_users and current_user.role not in settings.settings.admin_roles:
|
||||
@@ -74,23 +74,23 @@ async def add_user(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Not allowed",
|
||||
)
|
||||
if db.check_user_existence(conn, username):
|
||||
if await db.check_user_existence(conn, username):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail="User already exists",
|
||||
)
|
||||
hashed_password = get_password_hash(password)
|
||||
return db.create_user(conn, username, hashed_password, "user")
|
||||
return await db.create_user(conn, username, hashed_password, "user")
|
||||
|
||||
|
||||
@users_router.post("/delete")
|
||||
async def delete_user(
|
||||
username: str,
|
||||
conn: Annotated[connection, Depends(get_db_connection)],
|
||||
conn: Annotated[Connection, Depends(get_db_connection)],
|
||||
current_user: Annotated[User, Depends(get_current_user)]
|
||||
):
|
||||
if current_user.username == username or current_user.role in settings.settings.admin_roles:
|
||||
return db.delete_user(conn, username)
|
||||
return await db.delete_user(conn, username)
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
@@ -102,11 +102,11 @@ async def delete_user(
|
||||
async def update_disabled(
|
||||
username: str,
|
||||
disabled: bool,
|
||||
conn: Annotated[connection, Depends(get_db_connection)],
|
||||
conn: Annotated[Connection, Depends(get_db_connection)],
|
||||
current_user: Annotated[User, Depends(get_current_user)]
|
||||
):
|
||||
if current_user.role in settings.settings.admin_roles:
|
||||
return db.update_user_disabled(conn, username, disabled)
|
||||
return await db.update_user_disabled(conn, username, disabled)
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
@@ -117,11 +117,11 @@ async def update_disabled(
|
||||
async def update_role(
|
||||
username: str,
|
||||
role: str,
|
||||
conn: Annotated[connection, Depends(get_db_connection)],
|
||||
conn: Annotated[Connection, Depends(get_db_connection)],
|
||||
current_user: Annotated[User, Depends(get_current_user)]
|
||||
):
|
||||
if current_user.role in settings.settings.admin_roles:
|
||||
return db.update_user_role(conn, username, role)
|
||||
return await db.update_user_role(conn, username, role)
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
@@ -133,16 +133,16 @@ async def update_role(
|
||||
async def update_username(
|
||||
username: str,
|
||||
new_username: str,
|
||||
conn: Annotated[connection, Depends(get_db_connection)],
|
||||
conn: Annotated[Connection, Depends(get_db_connection)],
|
||||
current_user: Annotated[User, Depends(get_current_user)]
|
||||
):
|
||||
if db.check_user_existence(conn, new_username):
|
||||
if await db.check_user_existence(conn, new_username):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail="Username is already taken",
|
||||
)
|
||||
if current_user.username == username or current_user.role in settings.settings.admin_roles:
|
||||
return db.update_user_username(conn, username, new_username)
|
||||
return await db.update_user_username(conn, username, new_username)
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
@@ -153,12 +153,12 @@ async def update_username(
|
||||
async def update_password(
|
||||
username: str,
|
||||
password: str,
|
||||
conn: Annotated[connection, Depends(get_db_connection)],
|
||||
conn: Annotated[Connection, Depends(get_db_connection)],
|
||||
current_user: Annotated[User, Depends(get_current_user)]
|
||||
):
|
||||
if current_user.username == username or current_user.role in settings.settings.admin_roles:
|
||||
hashed_password = get_password_hash(password)
|
||||
return db.update_user_password(conn, username, hashed_password)
|
||||
return await db.update_user_password(conn, username, hashed_password)
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
@@ -168,7 +168,7 @@ async def update_password(
|
||||
|
||||
@users_router.get("/update/last_seen")
|
||||
async def update_last_seen(
|
||||
conn: Annotated[connection, Depends(get_db_connection)],
|
||||
conn: Annotated[Connection, Depends(get_db_connection)],
|
||||
current_user: Annotated[User, Depends(get_current_user)]
|
||||
):
|
||||
return db.update_user_last_seen(conn, current_user.username)
|
||||
return await db.update_user_last_seen(conn, current_user.username)
|
||||
|
||||
+9
-9
@@ -1,12 +1,12 @@
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Annotated
|
||||
|
||||
from asyncpg import Connection
|
||||
import bcrypt
|
||||
import jwt
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from jwt.exceptions import InvalidTokenError
|
||||
from psycopg2._psycopg import connection
|
||||
|
||||
import db.groups
|
||||
import db.users
|
||||
@@ -30,15 +30,15 @@ def encode_token(payload):
|
||||
return jwt.encode(payload, startup_settings.secret_key, algorithm=startup_settings.algorithm)
|
||||
|
||||
|
||||
def authenticate_user(
|
||||
conn: connection,
|
||||
async def authenticate_user(
|
||||
conn: Connection,
|
||||
username: str,
|
||||
user_password: str
|
||||
):
|
||||
if not user_password:
|
||||
return False
|
||||
|
||||
db_user_password = db.users.get_user_password(conn, username)
|
||||
db_user_password = await db.users.get_user_password(conn, username)
|
||||
if db_user_password is None:
|
||||
return False
|
||||
|
||||
@@ -60,7 +60,7 @@ def create_access_token(
|
||||
|
||||
async def get_current_user(
|
||||
token: Annotated[str, Depends(oauth2_scheme)],
|
||||
conn: Annotated[connection, Depends(get_db_connection)]
|
||||
conn: Annotated[Connection, Depends(get_db_connection)]
|
||||
):
|
||||
credentials_exception = HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
@@ -77,7 +77,7 @@ async def get_current_user(
|
||||
except InvalidTokenError:
|
||||
raise credentials_exception
|
||||
|
||||
user_data = db.users.get_user(conn, token_data.username)
|
||||
user_data = await db.users.get_user(conn, token_data.username)
|
||||
if user_data is None:
|
||||
raise credentials_exception
|
||||
|
||||
@@ -92,15 +92,15 @@ async def get_current_user(
|
||||
return user
|
||||
|
||||
|
||||
def get_group_by_name(
|
||||
conn: connection,
|
||||
async def get_group_by_name(
|
||||
conn: Connection,
|
||||
groupname: str
|
||||
):
|
||||
group_exception = HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="No such group"
|
||||
)
|
||||
group_data = db.groups.get_group(conn, groupname)
|
||||
group_data = await db.groups.get_group(conn, groupname)
|
||||
if group_data is None:
|
||||
raise group_exception
|
||||
|
||||
|
||||
+2
-1
@@ -13,7 +13,7 @@ from api.memberships import memberships_router
|
||||
from api.pictures import pictures_router
|
||||
from api.swipes import swipes_router
|
||||
from api.users import users_router
|
||||
from db.internal import connect_db, disconnect_db
|
||||
from db.internal import connect_db, disconnect_db, initialize_db
|
||||
from settings import startup_settings
|
||||
from settings.settings import settings_down, settings_up
|
||||
from settings.startup_settings import setup_settings
|
||||
@@ -41,6 +41,7 @@ def create_app():
|
||||
|
||||
app.add_event_handler("startup", setup_settings)
|
||||
app.add_event_handler("startup", connect_db)
|
||||
app.add_event_handler("startup", initialize_db)
|
||||
app.add_event_handler("startup", settings_up)
|
||||
|
||||
app.include_router(general_router)
|
||||
|
||||
+87
-101
@@ -1,14 +1,13 @@
|
||||
import json
|
||||
|
||||
import psycopg2.extras
|
||||
from psycopg2._psycopg import connection
|
||||
from asyncpg import Connection
|
||||
|
||||
from api.models import Account
|
||||
|
||||
# account create and delete
|
||||
|
||||
def create_account(
|
||||
conn: connection,
|
||||
async def create_account(
|
||||
conn: Connection,
|
||||
groupname: str,
|
||||
author: str,
|
||||
platform: str,
|
||||
@@ -16,65 +15,59 @@ def create_account(
|
||||
password: str,
|
||||
metadata: dict
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
insert into picrinth.accounts
|
||||
(groupname, author, platform, login,
|
||||
password, metadata, created_at)
|
||||
values (%s, %s, %s, %s, now())
|
||||
returning id
|
||||
""",
|
||||
(groupname, author, platform, login, password, json.dumps(metadata)),
|
||||
)
|
||||
result = cur.fetchone()
|
||||
conn.commit()
|
||||
if result is None:
|
||||
return None
|
||||
return result[0]
|
||||
result = await conn.fetchrow(
|
||||
"""
|
||||
insert into picrinth.accounts
|
||||
(groupname, author, platform, login,
|
||||
password, metadata, created_at)
|
||||
values ($1, $2, $3, $4, $5, $6, now())
|
||||
returning id
|
||||
""",
|
||||
groupname, author, platform, login, password, json.dumps(metadata),
|
||||
)
|
||||
if result is None:
|
||||
return None
|
||||
return result["id"]
|
||||
|
||||
|
||||
def delete_account(
|
||||
conn: connection,
|
||||
async def delete_account(
|
||||
conn: Connection,
|
||||
account_id: str
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
delete from picrinth.accounts
|
||||
where account_id = %s
|
||||
""",
|
||||
(account_id),
|
||||
)
|
||||
conn.commit()
|
||||
return cur.rowcount > 0
|
||||
result = await conn.execute(
|
||||
"""
|
||||
delete from picrinth.accounts
|
||||
where account_id = $1
|
||||
""",
|
||||
account_id,
|
||||
)
|
||||
return result != "DELETE 0"
|
||||
|
||||
|
||||
# account checks
|
||||
|
||||
def check_account_existence(
|
||||
conn: connection,
|
||||
async def check_account_existence(
|
||||
conn: Connection,
|
||||
groupname: str,
|
||||
platform: str
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
select exists(
|
||||
select 1
|
||||
from picrinth.accounts
|
||||
where groupname = %s and platform = %s
|
||||
);
|
||||
""",
|
||||
(groupname, platform),
|
||||
)
|
||||
return cur.fetchone()[0] # type: ignore
|
||||
row = await conn.fetchrow(
|
||||
"""
|
||||
select exists(
|
||||
select 1
|
||||
from picrinth.accounts
|
||||
where groupname = $1 and platform = $2
|
||||
);
|
||||
""",
|
||||
groupname, platform,
|
||||
)
|
||||
return row["exists"] # type: ignore
|
||||
|
||||
|
||||
# account update
|
||||
|
||||
def update_account(
|
||||
conn: connection,
|
||||
async def update_account(
|
||||
conn: Connection,
|
||||
groupname: str,
|
||||
author: str,
|
||||
platform: str,
|
||||
@@ -82,73 +75,66 @@ def update_account(
|
||||
password: str,
|
||||
metadata: dict
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
update picrinth.accounts
|
||||
SET author = %s,
|
||||
login = %s,
|
||||
password = %s,
|
||||
metadata = %s
|
||||
where groupname = %s and platform = %s
|
||||
""",
|
||||
(author, login, password, json.dumps(metadata), groupname, platform),
|
||||
)
|
||||
conn.commit()
|
||||
return cur.rowcount > 0
|
||||
result = await conn.execute(
|
||||
"""
|
||||
update picrinth.accounts
|
||||
SET author = $1,
|
||||
login = $2,
|
||||
password = $3,
|
||||
metadata = $4
|
||||
where groupname = $5 and platform = $6
|
||||
""",
|
||||
author, login, password, json.dumps(metadata), groupname, platform,
|
||||
)
|
||||
return result != "UPDATE 0"
|
||||
|
||||
|
||||
def update_account_metadata(
|
||||
conn: connection,
|
||||
async def update_account_metadata(
|
||||
conn: Connection,
|
||||
groupname: str,
|
||||
platform: str,
|
||||
metadata: dict
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
update picrinth.accounts
|
||||
SET metadata = %s
|
||||
where groupname = %s and platform = %s
|
||||
""",
|
||||
(json.dumps(metadata), groupname, platform),
|
||||
)
|
||||
conn.commit()
|
||||
return cur.rowcount > 0
|
||||
result = await conn.execute(
|
||||
"""
|
||||
update picrinth.accounts
|
||||
SET metadata = $1
|
||||
where groupname = $2 and platform = $3
|
||||
""",
|
||||
json.dumps(metadata), groupname, platform,
|
||||
)
|
||||
return result != "UPDATE 0"
|
||||
|
||||
|
||||
# account receiving
|
||||
|
||||
def get_accounts_by_group(
|
||||
conn: connection,
|
||||
async def get_accounts_by_group(
|
||||
conn: Connection,
|
||||
groupname: str
|
||||
) -> list[Account]:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
select *
|
||||
from picrinth.accounts
|
||||
where groupname = %s
|
||||
""",
|
||||
(groupname,),
|
||||
)
|
||||
return [Account().fill(account_data) for (account_data,) in cur.fetchall()]
|
||||
rows = await conn.fetch(
|
||||
"""
|
||||
select *
|
||||
from picrinth.accounts
|
||||
where groupname = $1
|
||||
""",
|
||||
groupname,
|
||||
)
|
||||
return [Account().fill(account_data) for account_data in rows]
|
||||
|
||||
|
||||
def get_accounts_by_group_platform(
|
||||
conn: connection,
|
||||
async def get_accounts_by_group_platform(
|
||||
conn: Connection,
|
||||
groupname: str,
|
||||
platform: str
|
||||
):
|
||||
with conn.cursor(cursor_factory=psycopg2.extras.DictCursor) as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
select groupname, author,
|
||||
platform, login, password,
|
||||
metadata, created_at
|
||||
from picrinth.accounts
|
||||
where groupname = %s and platform = %s
|
||||
""",
|
||||
(groupname, platform),
|
||||
)
|
||||
return cur.fetchone()
|
||||
return await conn.fetchrow(
|
||||
"""
|
||||
select groupname, author,
|
||||
platform, login, password,
|
||||
metadata, created_at
|
||||
from picrinth.accounts
|
||||
where groupname = $1 and platform = $2
|
||||
""",
|
||||
groupname, platform,
|
||||
)
|
||||
|
||||
+51
-61
@@ -1,83 +1,73 @@
|
||||
import psycopg2.extras
|
||||
from psycopg2._psycopg import connection
|
||||
from asyncpg import Connection
|
||||
|
||||
# feed create and delete
|
||||
|
||||
def create_feed(
|
||||
conn: connection,
|
||||
async def create_feed(
|
||||
conn: Connection,
|
||||
groupname: str,
|
||||
image_ids: list[int]
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
insert into picrinth.feeds
|
||||
(groupname, image_ids, created_at)
|
||||
values (%s, %s, now())
|
||||
returning id
|
||||
""",
|
||||
(groupname, image_ids),
|
||||
)
|
||||
result = cur.fetchone()
|
||||
conn.commit()
|
||||
if result is None:
|
||||
return None
|
||||
return result[0]
|
||||
result = await conn.fetchrow(
|
||||
"""
|
||||
insert into picrinth.feeds
|
||||
(groupname, image_ids, created_at)
|
||||
values ($1, $2, now())
|
||||
returning id
|
||||
""",
|
||||
groupname, image_ids,
|
||||
)
|
||||
if result is None:
|
||||
return None
|
||||
return result["id"]
|
||||
|
||||
|
||||
def delete_feed(
|
||||
conn: connection,
|
||||
async def delete_feed(
|
||||
conn: Connection,
|
||||
feed_id: int
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
delete from picrinth.feeds
|
||||
where id = %s
|
||||
""",
|
||||
(feed_id,),
|
||||
)
|
||||
conn.commit()
|
||||
return cur.rowcount > 0
|
||||
result = await conn.execute(
|
||||
"""
|
||||
delete from picrinth.feeds
|
||||
where id = $1
|
||||
""",
|
||||
feed_id,
|
||||
)
|
||||
return result != "DELETE 0"
|
||||
|
||||
|
||||
# feed receiving
|
||||
|
||||
def get_feed(
|
||||
conn: connection,
|
||||
async def get_feed(
|
||||
conn: Connection,
|
||||
feed_id: int
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
select groupname
|
||||
from picrinth.feeds
|
||||
where id = %s
|
||||
""",
|
||||
(feed_id,),
|
||||
)
|
||||
result = cur.fetchone()
|
||||
if result is None:
|
||||
return None
|
||||
return cur.fetchone()[0] # type: ignore
|
||||
result = await conn.fetchrow(
|
||||
"""
|
||||
select groupname
|
||||
from picrinth.feeds
|
||||
where id = $1
|
||||
""",
|
||||
feed_id,
|
||||
)
|
||||
if result is None:
|
||||
return None
|
||||
return result["groupname"] # type: ignore
|
||||
|
||||
|
||||
# additional
|
||||
|
||||
def get_groupname_by_feed_id(
|
||||
conn: connection,
|
||||
async def get_groupname_by_feed_id(
|
||||
conn: Connection,
|
||||
feed_id: int
|
||||
):
|
||||
with conn.cursor(cursor_factory=psycopg2.extras.DictCursor) as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
select groupname
|
||||
from picrinth.feeds
|
||||
where id = %s
|
||||
""",
|
||||
(feed_id,),
|
||||
)
|
||||
result = cur.fetchone()
|
||||
if result is None:
|
||||
return None
|
||||
return cur.fetchone()[0] # type: ignore
|
||||
result = await conn.fetchrow(
|
||||
"""
|
||||
select groupname
|
||||
from picrinth.feeds
|
||||
where id = $1
|
||||
""",
|
||||
feed_id,
|
||||
)
|
||||
if result is None:
|
||||
return None
|
||||
return result["groupname"] # type: ignore
|
||||
|
||||
+193
-223
@@ -1,300 +1,270 @@
|
||||
import psycopg2.extras
|
||||
from psycopg2._psycopg import connection
|
||||
from asyncpg import Connection
|
||||
|
||||
# group create and delete
|
||||
|
||||
def create_group(
|
||||
conn: connection,
|
||||
async def create_group(
|
||||
conn: Connection,
|
||||
groupname: str,
|
||||
invite_code: str,
|
||||
author: str,
|
||||
allow_skips: bool = True,
|
||||
feed_interval_minutes: int = 1440,
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
insert into picrinth.groups
|
||||
(groupname, invite_code, author, allow_skips,
|
||||
feed_interval_minutes, created_at)
|
||||
values (%s, %s, %s, %s, %s, now())
|
||||
""",
|
||||
(groupname, invite_code, author, allow_skips, feed_interval_minutes),
|
||||
)
|
||||
conn.commit()
|
||||
return cur.rowcount > 0
|
||||
result = await conn.execute(
|
||||
"""
|
||||
insert into picrinth.groups
|
||||
(groupname, invite_code, author, allow_skips,
|
||||
feed_interval_minutes, created_at)
|
||||
values ($1, $2, $3, $4, $5, now())
|
||||
""",
|
||||
groupname, invite_code, author, allow_skips, feed_interval_minutes,
|
||||
)
|
||||
return result != "INSERT 0 0"
|
||||
|
||||
|
||||
def delete_group(
|
||||
conn: connection,
|
||||
async def delete_group(
|
||||
conn: Connection,
|
||||
groupname: str
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
delete from picrinth.groups
|
||||
where groupname = %s
|
||||
""",
|
||||
(groupname,),
|
||||
)
|
||||
conn.commit()
|
||||
return cur.rowcount > 0
|
||||
result = await conn.execute(
|
||||
"""
|
||||
delete from picrinth.groups
|
||||
where groupname = $1
|
||||
""",
|
||||
groupname,
|
||||
)
|
||||
return result != "DELETE 0"
|
||||
|
||||
|
||||
# group checks
|
||||
|
||||
def check_group_existence(
|
||||
conn: connection,
|
||||
async def check_group_existence(
|
||||
conn: Connection,
|
||||
groupname: str
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
select exists(
|
||||
select 1
|
||||
from picrinth.groups
|
||||
where groupname = %s
|
||||
);
|
||||
""",
|
||||
(groupname,),
|
||||
)
|
||||
return cur.fetchone()[0] # type: ignore
|
||||
row = await conn.fetchrow(
|
||||
"""
|
||||
select exists(
|
||||
select 1
|
||||
from picrinth.groups
|
||||
where groupname = $1
|
||||
);
|
||||
""",
|
||||
groupname,
|
||||
)
|
||||
return row["exists"] # type: ignore
|
||||
|
||||
|
||||
def check_invite_code(
|
||||
conn: connection,
|
||||
async def check_invite_code(
|
||||
conn: Connection,
|
||||
invite_code: str
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
select exists(
|
||||
select 1
|
||||
from picrinth.groups
|
||||
where invite_code = %s
|
||||
);
|
||||
""",
|
||||
(invite_code,),
|
||||
)
|
||||
return cur.fetchone()[0] # type: ignore
|
||||
row = await conn.fetchrow(
|
||||
"""
|
||||
select exists(
|
||||
select 1
|
||||
from picrinth.groups
|
||||
where invite_code = $1
|
||||
);
|
||||
""",
|
||||
invite_code,
|
||||
)
|
||||
return row["exists"] # type: ignore
|
||||
|
||||
|
||||
def check_group_author(
|
||||
conn: connection,
|
||||
async def check_group_author(
|
||||
conn: Connection,
|
||||
groupname: str,
|
||||
author: str
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
select exists(
|
||||
select 1
|
||||
from picrinth.groups
|
||||
where groupname = %s and author = %s
|
||||
);
|
||||
""",
|
||||
(groupname, author),
|
||||
)
|
||||
return cur.fetchone()[0] # type: ignore
|
||||
row = await conn.fetchrow(
|
||||
"""
|
||||
select exists(
|
||||
select 1
|
||||
from picrinth.groups
|
||||
where groupname = $1 and author = $2
|
||||
);
|
||||
""",
|
||||
groupname, author,
|
||||
)
|
||||
return row["exists"] # type: ignore
|
||||
|
||||
|
||||
# group updates
|
||||
|
||||
def update_group_groupname(
|
||||
conn: connection,
|
||||
async def update_group_groupname(
|
||||
conn: Connection,
|
||||
groupname: str,
|
||||
new_groupname: str
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
update picrinth.groups
|
||||
set groupname = %s
|
||||
where groupname = %s
|
||||
""",
|
||||
(new_groupname, groupname),
|
||||
)
|
||||
conn.commit()
|
||||
return cur.rowcount > 0
|
||||
result = await conn.execute(
|
||||
"""
|
||||
update picrinth.groups
|
||||
set groupname = $1
|
||||
where groupname = $2
|
||||
""",
|
||||
new_groupname, groupname,
|
||||
)
|
||||
return result != "UPDATE 0"
|
||||
|
||||
def update_group_author(
|
||||
conn: connection,
|
||||
async def update_group_author(
|
||||
conn: Connection,
|
||||
groupname: str,
|
||||
author: str
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
update picrinth.groups
|
||||
set author = %s
|
||||
where groupname = %s
|
||||
""",
|
||||
(author, groupname),
|
||||
)
|
||||
conn.commit()
|
||||
return cur.rowcount > 0
|
||||
result = await conn.execute(
|
||||
"""
|
||||
update picrinth.groups
|
||||
set author = $1
|
||||
where groupname = $2
|
||||
""",
|
||||
author, groupname,
|
||||
)
|
||||
return result != "UPDATE 0"
|
||||
|
||||
def update_group_invite_code(
|
||||
conn: connection,
|
||||
async def update_group_invite_code(
|
||||
conn: Connection,
|
||||
groupname: str,
|
||||
invite_code: str
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
update picrinth.groups
|
||||
set invite_code = %s
|
||||
where groupname = %s
|
||||
""",
|
||||
(invite_code, groupname),
|
||||
)
|
||||
conn.commit()
|
||||
return cur.rowcount > 0
|
||||
result = await conn.execute(
|
||||
"""
|
||||
update picrinth.groups
|
||||
set invite_code = $1
|
||||
where groupname = $2
|
||||
""",
|
||||
invite_code, groupname,
|
||||
)
|
||||
return result != "UPDATE 0"
|
||||
|
||||
|
||||
def update_group_allow_skips(
|
||||
conn: connection,
|
||||
async def update_group_allow_skips(
|
||||
conn: Connection,
|
||||
groupname: str,
|
||||
allow_skips: bool
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
update picrinth.groups
|
||||
set allow_skips = %s
|
||||
where groupname = %s
|
||||
""",
|
||||
(allow_skips, groupname),
|
||||
)
|
||||
conn.commit()
|
||||
return cur.rowcount > 0
|
||||
result = await conn.execute(
|
||||
"""
|
||||
update picrinth.groups
|
||||
set allow_skips = $1
|
||||
where groupname = $2
|
||||
""",
|
||||
allow_skips, groupname,
|
||||
)
|
||||
return result != "UPDATE 0"
|
||||
|
||||
def update_group_feed_interval(
|
||||
conn: connection,
|
||||
async def update_group_feed_interval(
|
||||
conn: Connection,
|
||||
groupname: str,
|
||||
feed_interval: int
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
update picrinth.groups
|
||||
set feed_interval_minutes = %s
|
||||
where groupname = %s
|
||||
""",
|
||||
(feed_interval, groupname),
|
||||
)
|
||||
conn.commit()
|
||||
return cur.rowcount > 0
|
||||
result = await conn.execute(
|
||||
"""
|
||||
update picrinth.groups
|
||||
set feed_interval_minutes = $1
|
||||
where groupname = $2
|
||||
""",
|
||||
feed_interval, groupname,
|
||||
)
|
||||
return result != "UPDATE 0"
|
||||
|
||||
def update_group_last_feed(
|
||||
conn: connection,
|
||||
async def update_group_last_feed(
|
||||
conn: Connection,
|
||||
groupname: str,
|
||||
last_feed_id: int
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
update picrinth.groups
|
||||
set last_feed_id = %s
|
||||
where groupname = %s
|
||||
""",
|
||||
(last_feed_id, groupname),
|
||||
)
|
||||
conn.commit()
|
||||
return cur.rowcount > 0
|
||||
result = await conn.execute(
|
||||
"""
|
||||
update picrinth.groups
|
||||
set last_feed_id = $1
|
||||
where groupname = $2
|
||||
""",
|
||||
last_feed_id, groupname,
|
||||
)
|
||||
return result != "UPDATE 0"
|
||||
|
||||
|
||||
# group receiving
|
||||
|
||||
def get_group(
|
||||
conn: connection,
|
||||
async def get_group(
|
||||
conn: Connection,
|
||||
groupname: str
|
||||
):
|
||||
with conn.cursor(cursor_factory=psycopg2.extras.DictCursor) as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
select groupname, author,
|
||||
invite_code, allow_skips,
|
||||
feed_interval_minutes,
|
||||
last_feed_id, created_at
|
||||
from picrinth.groups
|
||||
where groupname = %s
|
||||
""",
|
||||
(groupname,),
|
||||
)
|
||||
return cur.fetchone()
|
||||
return await conn.fetchrow(
|
||||
"""
|
||||
select groupname, author,
|
||||
invite_code, allow_skips,
|
||||
feed_interval_minutes,
|
||||
last_feed_id, created_at
|
||||
from picrinth.groups
|
||||
where groupname = $1
|
||||
""",
|
||||
groupname,
|
||||
)
|
||||
|
||||
|
||||
def get_group_by_invite_code(
|
||||
conn: connection,
|
||||
async def get_group_by_invite_code(
|
||||
conn: Connection,
|
||||
invite_code: str
|
||||
):
|
||||
with conn.cursor(cursor_factory=psycopg2.extras.DictCursor) as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
select groupname, author,
|
||||
invite_code, allow_skips,
|
||||
feed_interval_minutes,
|
||||
last_feed_id, created_at
|
||||
from picrinth.groups
|
||||
where invite_code = %s
|
||||
""",
|
||||
(invite_code,),
|
||||
)
|
||||
return cur.fetchone()
|
||||
return await conn.fetchrow(
|
||||
"""
|
||||
select groupname, author,
|
||||
invite_code, allow_skips,
|
||||
feed_interval_minutes,
|
||||
last_feed_id, created_at
|
||||
from picrinth.groups
|
||||
where invite_code = $1
|
||||
""",
|
||||
invite_code,
|
||||
)
|
||||
|
||||
|
||||
def get_groupname_by_invite_code(
|
||||
conn: connection,
|
||||
async def get_groupname_by_invite_code(
|
||||
conn: Connection,
|
||||
invite_code: str
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
select groupname
|
||||
from picrinth.groups
|
||||
where invite_code = %s
|
||||
""",
|
||||
(invite_code,),
|
||||
)
|
||||
result = cur.fetchone()
|
||||
if result is None:
|
||||
return None
|
||||
return result[0] # type: ignore
|
||||
result = await conn.fetchrow(
|
||||
"""
|
||||
select groupname
|
||||
from picrinth.groups
|
||||
where invite_code = $1
|
||||
""",
|
||||
invite_code,
|
||||
)
|
||||
if result is None:
|
||||
return None
|
||||
return result["groupname"] # type: ignore
|
||||
|
||||
|
||||
def get_group_invite_code(
|
||||
conn: connection,
|
||||
async def get_group_invite_code(
|
||||
conn: Connection,
|
||||
groupname: str
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
select invite_code
|
||||
from picrinth.groups
|
||||
where groupname = %s
|
||||
""",
|
||||
(groupname,),
|
||||
)
|
||||
result = cur.fetchone()
|
||||
if result is None:
|
||||
return None
|
||||
return result[0] # type: ignore
|
||||
result = await conn.fetchrow(
|
||||
"""
|
||||
select invite_code
|
||||
from picrinth.groups
|
||||
where groupname = $1
|
||||
""",
|
||||
groupname,
|
||||
)
|
||||
if result is None:
|
||||
return None
|
||||
return result["invite_code"] # type: ignore
|
||||
|
||||
def get_group_last_feed_id(
|
||||
conn: connection,
|
||||
async def get_group_last_feed_id(
|
||||
conn: Connection,
|
||||
groupname: str
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
select last_feed_id
|
||||
from picrinth.groups
|
||||
where groupname = %s
|
||||
""",
|
||||
(groupname,),
|
||||
)
|
||||
result = cur.fetchone()
|
||||
if result is None:
|
||||
return None
|
||||
return result[0] # type: ignore
|
||||
result = await conn.fetchrow(
|
||||
"""
|
||||
select last_feed_id
|
||||
from picrinth.groups
|
||||
where groupname = $1
|
||||
""",
|
||||
groupname,
|
||||
)
|
||||
if result is None:
|
||||
return None
|
||||
return result["last_feed_id"] # type: ignore
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
import asyncpg
|
||||
|
||||
|
||||
async def is_db_initialized(conn: asyncpg.Connection) -> bool:
|
||||
row = await conn.fetchrow(
|
||||
"""
|
||||
select exists(
|
||||
select 1
|
||||
from information_schema.schemata
|
||||
where schema_name = 'picrinth'
|
||||
);
|
||||
""",
|
||||
)
|
||||
return row["exists"] # type: ignore
|
||||
|
||||
|
||||
async def initialize_db(conn: asyncpg.Connection) -> None:
|
||||
await conn.execute(
|
||||
"""
|
||||
create schema picrinth;
|
||||
|
||||
create table picrinth.users (
|
||||
id serial not null,
|
||||
username text not null,
|
||||
"password" text not null,
|
||||
"role" text not null default 'user',
|
||||
"disabled" bool not null default false,
|
||||
last_seen_at timestamp with time zone null,
|
||||
created_at timestamp with time zone default now(),
|
||||
constraint username_unique unique (username)
|
||||
);
|
||||
|
||||
create table picrinth.groups (
|
||||
id serial not null,
|
||||
groupname text not null,
|
||||
invite_code text not null,
|
||||
author text null,
|
||||
allow_skips bool not null default true,
|
||||
feed_interval_minutes integer null default 1440,
|
||||
last_feed_id int null,
|
||||
created_at timestamp with time zone default now(),
|
||||
constraint groupname_unique unique (groupname),
|
||||
constraint invite_code_unique unique (invite_code)
|
||||
);
|
||||
|
||||
create table picrinth.memberships (
|
||||
username text,
|
||||
groupname text,
|
||||
joined_at timestamp with time zone null,
|
||||
primary key (username, groupname),
|
||||
foreign key (username) references picrinth.users (username) on delete cascade on update cascade,
|
||||
foreign key (groupname) references picrinth.groups (groupname) on delete cascade on update cascade
|
||||
);
|
||||
|
||||
create table picrinth.pictures (
|
||||
id serial not null,
|
||||
source text not null,
|
||||
external_id text not null,
|
||||
"url" text not null,
|
||||
metadata jsonb null,
|
||||
created_at timestamp with time zone default now(),
|
||||
constraint pictures_pkey primary key (id),
|
||||
constraint url_unique unique (url)
|
||||
);
|
||||
|
||||
create table picrinth.feeds (
|
||||
id serial not null,
|
||||
groupname text not null,
|
||||
image_ids integer[] not null,
|
||||
created_at timestamp with time zone default now(),
|
||||
constraint feeds_pkey primary key (id),
|
||||
foreign key (groupname) references picrinth.groups (groupname) on delete cascade on update cascade,
|
||||
constraint unique_feed_per_timestamp_per_group unique (groupname, created_at)
|
||||
);
|
||||
|
||||
create table picrinth.swipes (
|
||||
id serial not null,
|
||||
username text not null,
|
||||
feed_id integer not null,
|
||||
picture_id integer not null,
|
||||
"value" smallint not null,
|
||||
created_at timestamp with time zone default now(),
|
||||
primary key (id),
|
||||
foreign key (username) references picrinth.users (username) on delete cascade on update cascade,
|
||||
foreign key (feed_id) references picrinth.feeds (id) on delete cascade on update cascade,
|
||||
foreign key (picture_id) references picrinth.pictures (id) on delete cascade on update cascade,
|
||||
constraint swipes_unique unique (username, feed_id, picture_id)
|
||||
);
|
||||
|
||||
create table picrinth.accounts (
|
||||
groupname text not null,
|
||||
author text null,
|
||||
platform text not null,
|
||||
"login" text not null,
|
||||
"password" text not null,
|
||||
metadata jsonb null,
|
||||
created_at timestamp with time zone default now(),
|
||||
foreign key (groupname) references picrinth.groups (groupname) on delete cascade on update cascade,
|
||||
-- foreign key (author) references picrinth.groups (author) on delete cascade on update cascade,
|
||||
constraint unique_account_for_group_per_platform unique (groupname, platform)
|
||||
);
|
||||
""",
|
||||
)
|
||||
+32
-11
@@ -1,17 +1,18 @@
|
||||
import sys
|
||||
|
||||
import psycopg2
|
||||
import asyncpg
|
||||
from loguru import logger
|
||||
|
||||
import db.initialization
|
||||
from db.models import database
|
||||
from settings import startup_settings
|
||||
|
||||
|
||||
def connect_db():
|
||||
async def connect_db():
|
||||
logger.info("Initializing DB connection")
|
||||
try:
|
||||
database.conn = psycopg2.connect(
|
||||
dbname=startup_settings.db_name,
|
||||
database.pool = await asyncpg.create_pool(
|
||||
database=startup_settings.db_name,
|
||||
user=startup_settings.db_user,
|
||||
password=startup_settings.db_password,
|
||||
host=startup_settings.db_host,
|
||||
@@ -23,22 +24,42 @@ def connect_db():
|
||||
logger.success("Successfully initialized DB connection")
|
||||
|
||||
|
||||
def disconnect_db():
|
||||
async def disconnect_db():
|
||||
logger.info("Closing DB connection")
|
||||
if database.conn:
|
||||
if database.pool:
|
||||
try:
|
||||
database.conn.close()
|
||||
await database.pool.close()
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to disconnect from DB: {e}")
|
||||
return
|
||||
else:
|
||||
logger.error("Failed to disconnect from DB: no connection")
|
||||
logger.error("Failed to disconnect from DB: no connection pool")
|
||||
logger.success("Successfully closed DB connection")
|
||||
|
||||
|
||||
def get_db_connection():
|
||||
if database.conn is not None:
|
||||
yield database.conn
|
||||
async def initialize_db():
|
||||
if database.pool is None:
|
||||
logger.error("No connection pool")
|
||||
sys.exit(1)
|
||||
|
||||
async with database.pool.acquire() as conn:
|
||||
try:
|
||||
if await db.initialization.is_db_initialized(conn):
|
||||
logger.success("DB already initialized, skipping setup")
|
||||
return
|
||||
else:
|
||||
logger.info("Initializing DB schema and tables")
|
||||
await db.initialization.initialize_db(conn)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to initialize DB: {e}")
|
||||
sys.exit(1)
|
||||
logger.success("Successfully initialized DB schema and tables")
|
||||
|
||||
|
||||
async def get_db_connection():
|
||||
if database.pool is not None:
|
||||
async with database.pool.acquire() as connection:
|
||||
yield connection
|
||||
else:
|
||||
logger.error("No connection pool")
|
||||
sys.exit(1)
|
||||
|
||||
+55
-64
@@ -1,92 +1,83 @@
|
||||
from psycopg2._psycopg import connection
|
||||
from asyncpg import Connection
|
||||
|
||||
# membership create and delete
|
||||
|
||||
def create_membership(
|
||||
conn: connection,
|
||||
async def create_membership(
|
||||
conn: Connection,
|
||||
username: str,
|
||||
groupname: str
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
insert into picrinth.memberships
|
||||
(username, groupname, joined_at)
|
||||
values (%s, %s, now())
|
||||
""",
|
||||
(username, groupname),
|
||||
)
|
||||
conn.commit()
|
||||
return cur.rowcount > 0
|
||||
result = await conn.execute(
|
||||
"""
|
||||
insert into picrinth.memberships
|
||||
(username, groupname, joined_at)
|
||||
values ($1, $2, now())
|
||||
""",
|
||||
username, groupname,
|
||||
)
|
||||
return result != "INSERT 0 0"
|
||||
|
||||
|
||||
def delete_membership(
|
||||
conn: connection,
|
||||
async def delete_membership(
|
||||
conn: Connection,
|
||||
username: str,
|
||||
groupname: str
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
delete from picrinth.memberships
|
||||
where username = %s and groupname = %s
|
||||
""",
|
||||
(username, groupname),
|
||||
)
|
||||
conn.commit()
|
||||
return cur.rowcount > 0
|
||||
result = await conn.execute(
|
||||
"""
|
||||
delete from picrinth.memberships
|
||||
where username = $1 and groupname = $2
|
||||
""",
|
||||
username, groupname,
|
||||
)
|
||||
return result != "DELETE 0"
|
||||
|
||||
|
||||
# membership checks
|
||||
|
||||
def check_membership_exists(
|
||||
conn: connection,
|
||||
async def check_membership_exists(
|
||||
conn: Connection,
|
||||
username: str,
|
||||
groupname: str
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
select exists(
|
||||
select 1
|
||||
from picrinth.memberships
|
||||
where username = %s and groupname = %s
|
||||
);
|
||||
""",
|
||||
(username, groupname),
|
||||
)
|
||||
return cur.fetchone()[0] # type: ignore
|
||||
row = await conn.fetchrow(
|
||||
"""
|
||||
select exists(
|
||||
select 1
|
||||
from picrinth.memberships
|
||||
where username = $1 and groupname = $2
|
||||
);
|
||||
""",
|
||||
username, groupname,
|
||||
)
|
||||
return row["exists"] # type: ignore
|
||||
|
||||
|
||||
# membership receiving
|
||||
|
||||
def get_memberships_by_username(
|
||||
conn: connection,
|
||||
async def get_memberships_by_username(
|
||||
conn: Connection,
|
||||
username: str
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
select *
|
||||
from picrinth.memberships
|
||||
where username = %s
|
||||
""",
|
||||
(username,),
|
||||
)
|
||||
return cur.fetchall()
|
||||
return await conn.fetch(
|
||||
"""
|
||||
select *
|
||||
from picrinth.memberships
|
||||
where username = $1
|
||||
""",
|
||||
username,
|
||||
)
|
||||
|
||||
|
||||
def get_memberships_by_groupname(
|
||||
conn: connection,
|
||||
async def get_memberships_by_groupname(
|
||||
conn: Connection,
|
||||
groupname: str
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
select *
|
||||
from picrinth.memberships
|
||||
where groupname = %s
|
||||
""",
|
||||
(groupname,),
|
||||
)
|
||||
return cur.fetchall()
|
||||
return await conn.fetch(
|
||||
"""
|
||||
select *
|
||||
from picrinth.memberships
|
||||
where groupname = $1
|
||||
""",
|
||||
groupname,
|
||||
)
|
||||
|
||||
+4
-2
@@ -1,8 +1,10 @@
|
||||
from psycopg2._psycopg import connection
|
||||
import asyncpg
|
||||
|
||||
from asyncpg import Pool
|
||||
|
||||
|
||||
class DataBase:
|
||||
conn: connection | None = None
|
||||
pool: Pool | None = None
|
||||
|
||||
|
||||
database = DataBase()
|
||||
|
||||
+46
-55
@@ -1,78 +1,69 @@
|
||||
import json
|
||||
|
||||
import psycopg2.extras
|
||||
from psycopg2._psycopg import connection
|
||||
from asyncpg import Connection
|
||||
|
||||
# picture create and delete
|
||||
|
||||
def create_picture(
|
||||
conn: connection,
|
||||
async def create_picture(
|
||||
conn: Connection,
|
||||
source: str,
|
||||
external_id: str,
|
||||
url: str,
|
||||
metadata: dict
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
insert into picrinth.pictures
|
||||
(source, external_id, url, metadata, created_at)
|
||||
values (%s, %s, %s, %s, now())
|
||||
returning id
|
||||
""",
|
||||
(source, external_id, url, json.dumps(metadata)),
|
||||
)
|
||||
result = cur.fetchone()
|
||||
conn.commit()
|
||||
if result is None:
|
||||
return None
|
||||
return result[0]
|
||||
result = await conn.fetchrow(
|
||||
"""
|
||||
insert into picrinth.pictures
|
||||
(source, external_id, url, metadata, created_at)
|
||||
values ($1, $2, $3, $4, now())
|
||||
returning id
|
||||
""",
|
||||
source, external_id, url, json.dumps(metadata),
|
||||
)
|
||||
if result is None:
|
||||
return None
|
||||
return result["id"]
|
||||
|
||||
|
||||
def delete_picture(
|
||||
conn: connection,
|
||||
async def delete_picture(
|
||||
conn: Connection,
|
||||
id: int
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
delete from picrinth.pictures
|
||||
where id = %s
|
||||
""",
|
||||
(id,),
|
||||
)
|
||||
conn.commit()
|
||||
return cur.rowcount > 0
|
||||
result = await conn.execute(
|
||||
"""
|
||||
delete from picrinth.pictures
|
||||
where id = $1
|
||||
""",
|
||||
id,
|
||||
)
|
||||
return result != "DELETE 0"
|
||||
|
||||
|
||||
# picture receiving
|
||||
|
||||
def get_picture(
|
||||
conn: connection,
|
||||
async def get_picture(
|
||||
conn: Connection,
|
||||
id: int
|
||||
):
|
||||
with conn.cursor(cursor_factory=psycopg2.extras.DictCursor) as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
select id, source,
|
||||
external_id, url,
|
||||
metadata, created_at
|
||||
from picrinth.pictures
|
||||
where id = %s
|
||||
""",
|
||||
(id,),
|
||||
)
|
||||
return cur.fetchone()
|
||||
return await conn.fetchrow(
|
||||
"""
|
||||
select id, source,
|
||||
external_id, url,
|
||||
metadata, created_at
|
||||
from picrinth.pictures
|
||||
where id = $1
|
||||
""",
|
||||
id,
|
||||
)
|
||||
|
||||
|
||||
def get_all_pictures_ids(
|
||||
conn: connection,
|
||||
async def get_all_pictures_ids(
|
||||
conn: Connection,
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
select id
|
||||
from picrinth.pictures
|
||||
""",
|
||||
)
|
||||
return [element for (element,) in cur.fetchall()]
|
||||
rows = await conn.fetch(
|
||||
"""
|
||||
select id
|
||||
from picrinth.pictures
|
||||
""",
|
||||
)
|
||||
return [element["id"] for element in rows]
|
||||
|
||||
+57
-69
@@ -1,100 +1,88 @@
|
||||
import psycopg2.extras
|
||||
from psycopg2._psycopg import connection
|
||||
from asyncpg import Connection
|
||||
|
||||
# swipe create and delete
|
||||
|
||||
def create_swipe(
|
||||
conn: connection,
|
||||
async def create_swipe(
|
||||
conn: Connection,
|
||||
username: str,
|
||||
feed_id: int,
|
||||
picture_id: int,
|
||||
value: int
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
insert into picrinth.swipes
|
||||
(username, feed_id, picture_id, value, created_at)
|
||||
values (%s, %s, %s, %s, now())
|
||||
returning id
|
||||
""",
|
||||
(username, feed_id, picture_id, value),
|
||||
)
|
||||
result = cur.fetchone()
|
||||
conn.commit()
|
||||
if result is None:
|
||||
return None
|
||||
return result[0]
|
||||
result = await conn.fetchrow(
|
||||
"""
|
||||
insert into picrinth.swipes
|
||||
(username, feed_id, picture_id, value, created_at)
|
||||
values ($1, $2, $3, $4, now())
|
||||
returning id
|
||||
""",
|
||||
username, feed_id, picture_id, value,
|
||||
)
|
||||
if result is None:
|
||||
return None
|
||||
return result["id"]
|
||||
|
||||
|
||||
def delete_swipe(
|
||||
conn: connection,
|
||||
async def delete_swipe(
|
||||
conn: Connection,
|
||||
username: str,
|
||||
feed_id: int,
|
||||
picture_id: int
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
delete from picrinth.swipes
|
||||
where username = %s and picture_id = %s and feed_id = %s
|
||||
""",
|
||||
(username, picture_id, feed_id),
|
||||
)
|
||||
conn.commit()
|
||||
return cur.rowcount > 0
|
||||
result = await conn.execute(
|
||||
"""
|
||||
delete from picrinth.swipes
|
||||
where username = $1 and picture_id = $2 and feed_id = $3
|
||||
""",
|
||||
username, picture_id, feed_id,
|
||||
)
|
||||
return result != "DELETE 0"
|
||||
|
||||
|
||||
# swipe receiving
|
||||
|
||||
def get_swipe(
|
||||
conn: connection,
|
||||
async def get_swipe(
|
||||
conn: Connection,
|
||||
username: str,
|
||||
feed_id: int,
|
||||
picture_id: int
|
||||
):
|
||||
with conn.cursor(cursor_factory=psycopg2.extras.DictCursor) as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
select username,
|
||||
feed_id, picture_id,
|
||||
value, created_at
|
||||
from picrinth.swipes
|
||||
where username = %s and feed_id = %s and picture_id = %s
|
||||
""",
|
||||
(username, feed_id, picture_id),
|
||||
)
|
||||
return cur.fetchone()
|
||||
return await conn.fetchrow(
|
||||
"""
|
||||
select username,
|
||||
feed_id, picture_id,
|
||||
value, created_at
|
||||
from picrinth.swipes
|
||||
where username = $1 and feed_id = $2 and picture_id = $3
|
||||
""",
|
||||
username, feed_id, picture_id,
|
||||
)
|
||||
|
||||
|
||||
def get_swipes_by_picture_id(
|
||||
conn: connection,
|
||||
async def get_swipes_by_picture_id(
|
||||
conn: Connection,
|
||||
feed_id: int,
|
||||
picture_id: int
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
select *
|
||||
from picrinth.swipes
|
||||
where feed_id = %s and picture_id = %s
|
||||
""",
|
||||
(feed_id, picture_id),
|
||||
)
|
||||
return cur.fetchone()
|
||||
return await conn.fetchrow(
|
||||
"""
|
||||
select *
|
||||
from picrinth.swipes
|
||||
where feed_id = $1 and picture_id = $2
|
||||
""",
|
||||
feed_id, picture_id,
|
||||
)
|
||||
|
||||
def get_swipes_by_user(
|
||||
conn: connection,
|
||||
async def get_swipes_by_user(
|
||||
conn: Connection,
|
||||
username: str,
|
||||
feed_id: int
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
select *
|
||||
from picrinth.swipes
|
||||
where username = %s and feed_id = %s
|
||||
""",
|
||||
(username, feed_id),
|
||||
)
|
||||
return cur.fetchone()
|
||||
return await conn.fetchrow(
|
||||
"""
|
||||
select *
|
||||
from picrinth.swipes
|
||||
where username = $1 and feed_id = $2
|
||||
""",
|
||||
username, feed_id,
|
||||
)
|
||||
|
||||
+129
-151
@@ -1,203 +1,181 @@
|
||||
import psycopg2.extras
|
||||
from psycopg2._psycopg import connection
|
||||
from asyncpg import Connection
|
||||
|
||||
# user create and delete
|
||||
|
||||
def create_user(
|
||||
conn: connection,
|
||||
async def create_user(
|
||||
conn: Connection,
|
||||
username: str,
|
||||
password: str,
|
||||
role: str = "user"
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
insert into picrinth.users
|
||||
(username, password, role, disabled, created_at)
|
||||
values (%s, %s, %s, false, now())
|
||||
""",
|
||||
(username, password, role),
|
||||
)
|
||||
conn.commit()
|
||||
return cur.rowcount > 0
|
||||
result = await conn.execute(
|
||||
"""
|
||||
insert into picrinth.users
|
||||
(username, password, role, disabled, created_at)
|
||||
values ($1, $2, $3, false, now())
|
||||
""",
|
||||
username, password, role,
|
||||
)
|
||||
return result != "INSERT 0 0"
|
||||
|
||||
|
||||
def delete_user(
|
||||
conn: connection,
|
||||
async def delete_user(
|
||||
conn: Connection,
|
||||
username: str
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
delete from picrinth.users
|
||||
where username = %s
|
||||
""",
|
||||
(username,),
|
||||
)
|
||||
conn.commit()
|
||||
return cur.rowcount > 0
|
||||
result = await conn.execute(
|
||||
"""
|
||||
delete from picrinth.users
|
||||
where username = $1
|
||||
""",
|
||||
username,
|
||||
)
|
||||
return result != "DELETE 0"
|
||||
|
||||
|
||||
# user checks
|
||||
|
||||
def check_user_existence(
|
||||
conn: connection,
|
||||
async def check_user_existence(
|
||||
conn: Connection,
|
||||
username: str
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
select exists(
|
||||
select 1
|
||||
from picrinth.users
|
||||
where username = %s
|
||||
);
|
||||
""",
|
||||
(username,),
|
||||
)
|
||||
return cur.fetchone()[0] # type: ignore
|
||||
|
||||
def check_user_disabled(
|
||||
conn: connection,
|
||||
username: str
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
select disabled
|
||||
row = await conn.fetchrow(
|
||||
"""
|
||||
select exists(
|
||||
select 1
|
||||
from picrinth.users
|
||||
where username = %s;
|
||||
""",
|
||||
(username,),
|
||||
)
|
||||
result = cur.fetchone()
|
||||
if result is None:
|
||||
return None
|
||||
return result[0] # type: ignore
|
||||
where username = $1
|
||||
);
|
||||
""",
|
||||
username,
|
||||
)
|
||||
return row["exists"] # type: ignore
|
||||
|
||||
async def check_user_disabled(
|
||||
conn: Connection,
|
||||
username: str
|
||||
):
|
||||
result = await conn.fetchrow(
|
||||
"""
|
||||
select disabled
|
||||
from picrinth.users
|
||||
where username = $1;
|
||||
""",
|
||||
username,
|
||||
)
|
||||
if result is None:
|
||||
return None
|
||||
return result["disabled"] # type: ignore
|
||||
|
||||
|
||||
# user updates
|
||||
|
||||
def update_user_disabled(
|
||||
conn: connection,
|
||||
async def update_user_disabled(
|
||||
conn: Connection,
|
||||
username: str,
|
||||
disabled: bool
|
||||
):
|
||||
# if disabled = True => user is disabled
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
update picrinth.users
|
||||
set disabled = %s
|
||||
where username = %s
|
||||
""",
|
||||
(disabled, username),
|
||||
)
|
||||
conn.commit()
|
||||
return cur.rowcount > 0
|
||||
result = await conn.execute(
|
||||
"""
|
||||
update picrinth.users
|
||||
set disabled = $1
|
||||
where username = $2
|
||||
""",
|
||||
disabled, username,
|
||||
)
|
||||
return result != "UPDATE 0"
|
||||
|
||||
def update_user_role(
|
||||
conn: connection,
|
||||
async def update_user_role(
|
||||
conn: Connection,
|
||||
username: str,
|
||||
role: str
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
update picrinth.users
|
||||
set role = %s
|
||||
where username = %s
|
||||
""",
|
||||
(role, username),
|
||||
)
|
||||
conn.commit()
|
||||
return cur.rowcount > 0
|
||||
result = await conn.execute(
|
||||
"""
|
||||
update picrinth.users
|
||||
set role = $1
|
||||
where username = $2
|
||||
""",
|
||||
role, username,
|
||||
)
|
||||
return result != "UPDATE 0"
|
||||
|
||||
|
||||
def update_user_username(
|
||||
conn: connection,
|
||||
async def update_user_username(
|
||||
conn: Connection,
|
||||
username: str,
|
||||
newUsername: str
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
update picrinth.users
|
||||
set username = %s
|
||||
where username = %s;
|
||||
""",
|
||||
(newUsername, username),
|
||||
)
|
||||
conn.commit()
|
||||
return cur.rowcount > 0
|
||||
result = await conn.execute(
|
||||
"""
|
||||
update picrinth.users
|
||||
set username = $1
|
||||
where username = $2;
|
||||
""",
|
||||
newUsername, username,
|
||||
)
|
||||
return result != "UPDATE 0"
|
||||
|
||||
def update_user_password(
|
||||
conn: connection,
|
||||
async def update_user_password(
|
||||
conn: Connection,
|
||||
username: str,
|
||||
password: str
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
update picrinth.users
|
||||
set password = %s
|
||||
where username = %s
|
||||
""",
|
||||
(password, username),
|
||||
)
|
||||
conn.commit()
|
||||
return cur.rowcount > 0
|
||||
result = await conn.execute(
|
||||
"""
|
||||
update picrinth.users
|
||||
set password = $1
|
||||
where username = $2
|
||||
""",
|
||||
password, username,
|
||||
)
|
||||
return result != "UPDATE 0"
|
||||
|
||||
|
||||
def update_user_last_seen(
|
||||
conn: connection,
|
||||
async def update_user_last_seen(
|
||||
conn: Connection,
|
||||
username: str
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
update picrinth.users
|
||||
set last_seen_at = now()
|
||||
where username = %s
|
||||
""",
|
||||
(username,),
|
||||
)
|
||||
conn.commit()
|
||||
return cur.rowcount > 0
|
||||
result = await conn.execute(
|
||||
"""
|
||||
update picrinth.users
|
||||
set last_seen_at = now()
|
||||
where username = $1
|
||||
""",
|
||||
username,
|
||||
)
|
||||
return result != "UPDATE 0"
|
||||
|
||||
|
||||
# user receiving
|
||||
|
||||
def get_user(
|
||||
conn: connection,
|
||||
async def get_user(
|
||||
conn: Connection,
|
||||
username: str
|
||||
):
|
||||
with conn.cursor(cursor_factory=psycopg2.extras.DictCursor) as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
select username, password, role,
|
||||
disabled, last_seen_at, created_at
|
||||
from picrinth.users
|
||||
where username = %s
|
||||
""",
|
||||
(username,),
|
||||
)
|
||||
return cur.fetchone()
|
||||
return await conn.fetchrow(
|
||||
"""
|
||||
select username, password, role,
|
||||
disabled, last_seen_at, created_at
|
||||
from picrinth.users
|
||||
where username = $1
|
||||
""",
|
||||
username,
|
||||
)
|
||||
|
||||
def get_user_password(
|
||||
conn: connection,
|
||||
async def get_user_password(
|
||||
conn: Connection,
|
||||
username: str
|
||||
):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
select password
|
||||
from picrinth.users
|
||||
where username = %s
|
||||
""",
|
||||
(username,),
|
||||
)
|
||||
result = cur.fetchone()
|
||||
if result is None:
|
||||
return None
|
||||
return result[0] # type: ignore
|
||||
result = await conn.fetchrow(
|
||||
"""
|
||||
select password
|
||||
from picrinth.users
|
||||
where username = $1
|
||||
""",
|
||||
username,
|
||||
)
|
||||
if result is None:
|
||||
return None
|
||||
return result["password"] # type: ignore
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
from psycopg2._psycopg import connection
|
||||
from asyncpg import Connection
|
||||
|
||||
import db.pictures as db
|
||||
from api.models import Account, Picture
|
||||
|
||||
|
||||
def gelbooru(conn: connection, account: Account):
|
||||
async def gelbooru(conn: Connection, account: Account):
|
||||
picture = Picture(external_id = "3", url = "", metadata = {})
|
||||
picture_id = db.create_picture(conn, "gelbooru", picture.external_id, picture.url, picture.metadata)
|
||||
picture_id = await db.create_picture(conn, "gelbooru", picture.external_id, picture.url, picture.metadata)
|
||||
return picture_id
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
from psycopg2._psycopg import connection
|
||||
from asyncpg import Connection
|
||||
|
||||
import db.pictures as db
|
||||
from api.models import Account, Picture
|
||||
|
||||
|
||||
def pinterest(conn: connection, account: Account):
|
||||
async def pinterest(conn: Connection, account: Account):
|
||||
picture = Picture(external_id = "1", url = "", metadata = {})
|
||||
picture_id = db.create_picture(conn, "pinterest", picture.external_id, picture.url, picture.metadata)
|
||||
picture_id = await db.create_picture(conn, "pinterest", picture.external_id, picture.url, picture.metadata)
|
||||
return picture_id
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from asyncpg import Connection
|
||||
from fastapi import HTTPException, status
|
||||
from gppt import GetPixivToken
|
||||
from loguru import logger
|
||||
from pixivpy3 import AppPixivAPI
|
||||
from psycopg2._psycopg import connection
|
||||
|
||||
import db.pictures as db
|
||||
from api.models import Account, Picture
|
||||
@@ -10,14 +10,14 @@ from db.accounts import update_account_metadata
|
||||
|
||||
# Wrapper functions
|
||||
|
||||
def pixiv(conn: connection, account: Account) -> list[int]:
|
||||
async def pixiv(conn: Connection, account: Account) -> list[int]:
|
||||
# Getting refresh token
|
||||
refresh_token = account.metadata.get('refresh_token', '')
|
||||
if not refresh_token:
|
||||
try:
|
||||
refresh_token = get_refresh_token(account.login, account.password)
|
||||
account.metadata['refresh_token'] = refresh_token
|
||||
update_account_metadata(conn, account.groupname, account.platform, account.metadata)
|
||||
await update_account_metadata(conn, account.groupname, account.platform, account.metadata)
|
||||
except Exception as e:
|
||||
# TODO: review ruff "do not use bare `except`"
|
||||
logger.debug(f"Pixiv refresh token missing and creation failed: {e}")
|
||||
@@ -50,18 +50,18 @@ def pixiv(conn: connection, account: Account) -> list[int]:
|
||||
# Saving recommendations as Pictures to DB
|
||||
pictures_ids = []
|
||||
for picture in pictures:
|
||||
pictures_ids.append(db.create_picture(conn, "pixiv", picture.external_id, picture.url, picture.metadata))
|
||||
pictures_ids.append(await db.create_picture(conn, "pixiv", picture.external_id, picture.url, picture.metadata))
|
||||
return pictures_ids
|
||||
|
||||
|
||||
def like_picture(conn: connection, account: Account, picture_id: int):
|
||||
async def like_picture(conn: Connection, account: Account, picture_id: int):
|
||||
# Getting refresh token
|
||||
refresh_token = account.metadata.get('refresh_token', '')
|
||||
if not refresh_token:
|
||||
try:
|
||||
refresh_token = get_refresh_token(account.login, account.password)
|
||||
account.metadata['refresh_token'] = refresh_token
|
||||
update_account_metadata(conn, account.groupname, account.platform, account.metadata)
|
||||
await update_account_metadata(conn, account.groupname, account.platform, account.metadata)
|
||||
except Exception as e:
|
||||
# TODO: review ruff "do not use bare `except`"
|
||||
logger.debug(f"Pixiv refresh token missing and creation failed: {e}")
|
||||
|
||||
+10
-10
@@ -1,5 +1,5 @@
|
||||
from asyncpg import Connection
|
||||
from loguru import logger
|
||||
from psycopg2._psycopg import connection
|
||||
|
||||
import db.pictures as db
|
||||
from api.models import Account, Picture
|
||||
@@ -9,8 +9,8 @@ from scraper.pixiv import pixiv
|
||||
from settings import startup_settings
|
||||
|
||||
|
||||
def generate_feed(
|
||||
conn: connection,
|
||||
async def generate_feed(
|
||||
conn: Connection,
|
||||
accounts: list[Account]
|
||||
) -> list:
|
||||
feed = []
|
||||
@@ -20,22 +20,22 @@ def generate_feed(
|
||||
|
||||
match account.platform:
|
||||
case "pinterest":
|
||||
temp_feed = pinterest(conn, account)
|
||||
temp_feed = await pinterest(conn, account)
|
||||
feed.append(temp_feed)
|
||||
|
||||
case "pixiv":
|
||||
temp_feed = pixiv(conn, account)
|
||||
temp_feed = await pixiv(conn, account)
|
||||
feed.append(temp_feed)
|
||||
|
||||
case "gelbooru":
|
||||
temp_feed = gelbooru(conn, account)
|
||||
temp_feed = await gelbooru(conn, account)
|
||||
feed.append(temp_feed)
|
||||
|
||||
case _:
|
||||
logger.warning(f"Platform for feed generation is not supported: {account.platform}")
|
||||
|
||||
# TODO: remove mock results
|
||||
feed = db.get_all_pictures_ids(conn)
|
||||
feed = await db.get_all_pictures_ids(conn)
|
||||
return feed
|
||||
|
||||
|
||||
@@ -43,11 +43,11 @@ def generate_feed(
|
||||
def process_swipe():
|
||||
return
|
||||
|
||||
def get_picture(
|
||||
conn: connection,
|
||||
async def get_picture(
|
||||
conn: Connection,
|
||||
picture_id: int
|
||||
) -> int:
|
||||
picture_data = db.get_picture(conn, picture_id)
|
||||
picture_data = await db.get_picture(conn, picture_id)
|
||||
if picture_id is None:
|
||||
return -1
|
||||
return Picture().fill(picture_data).id
|
||||
|
||||
@@ -41,7 +41,7 @@ def settings_up():
|
||||
with open(json_path + json_settings_name, "w") as f:
|
||||
json.dump(settings.model_dump_json(), f, ensure_ascii = False, indent=4)
|
||||
logger.info("Wrote settings to the JSON")
|
||||
logger.info("Successfully configured settings")
|
||||
logger.success("Successfully configured settings")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to configure settings during startup: {e}")
|
||||
raise e
|
||||
|
||||
@@ -0,0 +1,658 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.13"
|
||||
|
||||
[[package]]
|
||||
name = "annotated-types"
|
||||
version = "0.7.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.9.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "idna" },
|
||||
{ name = "sniffio" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "asyncpg"
|
||||
version = "0.30.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/2f/4c/7c991e080e106d854809030d8584e15b2e996e26f16aee6d757e387bc17d/asyncpg-0.30.0.tar.gz", hash = "sha256:c551e9928ab6707602f44811817f82ba3c446e018bfe1d3abecc8ba5f3eac851", size = 957746, upload-time = "2024-10-20T00:30:41.127Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/22/e20602e1218dc07692acf70d5b902be820168d6282e69ef0d3cb920dc36f/asyncpg-0.30.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05b185ebb8083c8568ea8a40e896d5f7af4b8554b64d7719c0eaa1eb5a5c3a70", size = 670373, upload-time = "2024-10-20T00:29:55.165Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/b3/0cf269a9d647852a95c06eb00b815d0b95a4eb4b55aa2d6ba680971733b9/asyncpg-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c47806b1a8cbb0a0db896f4cd34d89942effe353a5035c62734ab13b9f938da3", size = 634745, upload-time = "2024-10-20T00:29:57.14Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/6d/a4f31bf358ce8491d2a31bfe0d7bcf25269e80481e49de4d8616c4295a34/asyncpg-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b6fde867a74e8c76c71e2f64f80c64c0f3163e687f1763cfaf21633ec24ec33", size = 3512103, upload-time = "2024-10-20T00:29:58.499Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/19/139227a6e67f407b9c386cb594d9628c6c78c9024f26df87c912fabd4368/asyncpg-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46973045b567972128a27d40001124fbc821c87a6cade040cfcd4fa8a30bcdc4", size = 3592471, upload-time = "2024-10-20T00:30:00.354Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/e4/ab3ca38f628f53f0fd28d3ff20edff1c975dd1cb22482e0061916b4b9a74/asyncpg-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9110df111cabc2ed81aad2f35394a00cadf4f2e0635603db6ebbd0fc896f46a4", size = 3496253, upload-time = "2024-10-20T00:30:02.794Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/5f/0bf65511d4eeac3a1f41c54034a492515a707c6edbc642174ae79034d3ba/asyncpg-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04ff0785ae7eed6cc138e73fc67b8e51d54ee7a3ce9b63666ce55a0bf095f7ba", size = 3662720, upload-time = "2024-10-20T00:30:04.501Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/31/1513d5a6412b98052c3ed9158d783b1e09d0910f51fbe0e05f56cc370bc4/asyncpg-0.30.0-cp313-cp313-win32.whl", hash = "sha256:ae374585f51c2b444510cdf3595b97ece4f233fde739aa14b50e0d64e8a7a590", size = 560404, upload-time = "2024-10-20T00:30:06.537Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/a4/cec76b3389c4c5ff66301cd100fe88c318563ec8a520e0b2e792b5b84972/asyncpg-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:f59b430b8e27557c3fb9869222559f7417ced18688375825f8f12302c34e915e", size = 621623, upload-time = "2024-10-20T00:30:09.024Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "attrs"
|
||||
version = "25.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bcrypt"
|
||||
version = "4.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bb/5d/6d7433e0f3cd46ce0b43cd65e1db465ea024dbb8216fb2404e919c2ad77b/bcrypt-4.3.0.tar.gz", hash = "sha256:3a3fd2204178b6d2adcf09cb4f6426ffef54762577a7c9b54c159008cb288c18", size = 25697, upload-time = "2025-02-28T01:24:09.174Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/2c/3d44e853d1fe969d229bd58d39ae6902b3d924af0e2b5a60d17d4b809ded/bcrypt-4.3.0-cp313-cp313t-macosx_10_12_universal2.whl", hash = "sha256:f01e060f14b6b57bbb72fc5b4a83ac21c443c9a2ee708e04a10e9192f90a6281", size = 483719, upload-time = "2025-02-28T01:22:34.539Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/e2/58ff6e2a22eca2e2cff5370ae56dba29d70b1ea6fc08ee9115c3ae367795/bcrypt-4.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5eeac541cefd0bb887a371ef73c62c3cd78535e4887b310626036a7c0a817bb", size = 272001, upload-time = "2025-02-28T01:22:38.078Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/1f/c55ed8dbe994b1d088309e366749633c9eb90d139af3c0a50c102ba68a1a/bcrypt-4.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59e1aa0e2cd871b08ca146ed08445038f42ff75968c7ae50d2fdd7860ade2180", size = 277451, upload-time = "2025-02-28T01:22:40.787Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/1c/794feb2ecf22fe73dcfb697ea7057f632061faceb7dcf0f155f3443b4d79/bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:0042b2e342e9ae3d2ed22727c1262f76cc4f345683b5c1715f0250cf4277294f", size = 272792, upload-time = "2025-02-28T01:22:43.144Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/b7/0b289506a3f3598c2ae2bdfa0ea66969812ed200264e3f61df77753eee6d/bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74a8d21a09f5e025a9a23e7c0fd2c7fe8e7503e4d356c0a2c1486ba010619f09", size = 289752, upload-time = "2025-02-28T01:22:45.56Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/24/d0fb023788afe9e83cc118895a9f6c57e1044e7e1672f045e46733421fe6/bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:0142b2cb84a009f8452c8c5a33ace5e3dfec4159e7735f5afe9a4d50a8ea722d", size = 277762, upload-time = "2025-02-28T01:22:47.023Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/38/cde58089492e55ac4ef6c49fea7027600c84fd23f7520c62118c03b4625e/bcrypt-4.3.0-cp313-cp313t-manylinux_2_34_aarch64.whl", hash = "sha256:12fa6ce40cde3f0b899729dbd7d5e8811cb892d31b6f7d0334a1f37748b789fd", size = 272384, upload-time = "2025-02-28T01:22:49.221Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/6a/d5026520843490cfc8135d03012a413e4532a400e471e6188b01b2de853f/bcrypt-4.3.0-cp313-cp313t-manylinux_2_34_x86_64.whl", hash = "sha256:5bd3cca1f2aa5dbcf39e2aa13dd094ea181f48959e1071265de49cc2b82525af", size = 277329, upload-time = "2025-02-28T01:22:51.603Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/a3/4fc5255e60486466c389e28c12579d2829b28a527360e9430b4041df4cf9/bcrypt-4.3.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:335a420cfd63fc5bc27308e929bee231c15c85cc4c496610ffb17923abf7f231", size = 305241, upload-time = "2025-02-28T01:22:53.283Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/15/2b37bc07d6ce27cc94e5b10fd5058900eb8fb11642300e932c8c82e25c4a/bcrypt-4.3.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:0e30e5e67aed0187a1764911af023043b4542e70a7461ad20e837e94d23e1d6c", size = 309617, upload-time = "2025-02-28T01:22:55.461Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/1f/99f65edb09e6c935232ba0430c8c13bb98cb3194b6d636e61d93fe60ac59/bcrypt-4.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b8d62290ebefd49ee0b3ce7500f5dbdcf13b81402c05f6dafab9a1e1b27212f", size = 335751, upload-time = "2025-02-28T01:22:57.81Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/1b/b324030c706711c99769988fcb694b3cb23f247ad39a7823a78e361bdbb8/bcrypt-4.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2ef6630e0ec01376f59a006dc72918b1bf436c3b571b80fa1968d775fa02fe7d", size = 355965, upload-time = "2025-02-28T01:22:59.181Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/dd/20372a0579dd915dfc3b1cd4943b3bca431866fcb1dfdfd7518c3caddea6/bcrypt-4.3.0-cp313-cp313t-win32.whl", hash = "sha256:7a4be4cbf241afee43f1c3969b9103a41b40bcb3a3f467ab19f891d9bc4642e4", size = 155316, upload-time = "2025-02-28T01:23:00.763Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/52/45d969fcff6b5577c2bf17098dc36269b4c02197d551371c023130c0f890/bcrypt-4.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c1949bf259a388863ced887c7861da1df681cb2388645766c89fdfd9004c669", size = 147752, upload-time = "2025-02-28T01:23:02.908Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/22/5ada0b9af72b60cbc4c9a399fdde4af0feaa609d27eb0adc61607997a3fa/bcrypt-4.3.0-cp38-abi3-macosx_10_12_universal2.whl", hash = "sha256:f81b0ed2639568bf14749112298f9e4e2b28853dab50a8b357e31798686a036d", size = 498019, upload-time = "2025-02-28T01:23:05.838Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/8c/252a1edc598dc1ce57905be173328eda073083826955ee3c97c7ff5ba584/bcrypt-4.3.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:864f8f19adbe13b7de11ba15d85d4a428c7e2f344bac110f667676a0ff84924b", size = 279174, upload-time = "2025-02-28T01:23:07.274Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/5b/4547d5c49b85f0337c13929f2ccbe08b7283069eea3550a457914fc078aa/bcrypt-4.3.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e36506d001e93bffe59754397572f21bb5dc7c83f54454c990c74a468cd589e", size = 283870, upload-time = "2025-02-28T01:23:09.151Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/21/7dbaf3fa1745cb63f776bb046e481fbababd7d344c5324eab47f5ca92dd2/bcrypt-4.3.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:842d08d75d9fe9fb94b18b071090220697f9f184d4547179b60734846461ed59", size = 279601, upload-time = "2025-02-28T01:23:11.461Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/64/e042fc8262e971347d9230d9abbe70d68b0a549acd8611c83cebd3eaec67/bcrypt-4.3.0-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7c03296b85cb87db865d91da79bf63d5609284fc0cab9472fdd8367bbd830753", size = 297660, upload-time = "2025-02-28T01:23:12.989Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/b8/6294eb84a3fef3b67c69b4470fcdd5326676806bf2519cda79331ab3c3a9/bcrypt-4.3.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:62f26585e8b219cdc909b6a0069efc5e4267e25d4a3770a364ac58024f62a761", size = 284083, upload-time = "2025-02-28T01:23:14.5Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/e6/baff635a4f2c42e8788fe1b1633911c38551ecca9a749d1052d296329da6/bcrypt-4.3.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:beeefe437218a65322fbd0069eb437e7c98137e08f22c4660ac2dc795c31f8bb", size = 279237, upload-time = "2025-02-28T01:23:16.686Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/48/46f623f1b0c7dc2e5de0b8af5e6f5ac4cc26408ac33f3d424e5ad8da4a90/bcrypt-4.3.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:97eea7408db3a5bcce4a55d13245ab3fa566e23b4c67cd227062bb49e26c585d", size = 283737, upload-time = "2025-02-28T01:23:18.897Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/8b/70671c3ce9c0fca4a6cc3cc6ccbaa7e948875a2e62cbd146e04a4011899c/bcrypt-4.3.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:191354ebfe305e84f344c5964c7cd5f924a3bfc5d405c75ad07f232b6dffb49f", size = 312741, upload-time = "2025-02-28T01:23:21.041Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/fb/910d3a1caa2d249b6040a5caf9f9866c52114d51523ac2fb47578a27faee/bcrypt-4.3.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:41261d64150858eeb5ff43c753c4b216991e0ae16614a308a15d909503617732", size = 316472, upload-time = "2025-02-28T01:23:23.183Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/cf/7cf3a05b66ce466cfb575dbbda39718d45a609daa78500f57fa9f36fa3c0/bcrypt-4.3.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:33752b1ba962ee793fa2b6321404bf20011fe45b9afd2a842139de3011898fef", size = 343606, upload-time = "2025-02-28T01:23:25.361Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/b8/e970ecc6d7e355c0d892b7f733480f4aa8509f99b33e71550242cf0b7e63/bcrypt-4.3.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:50e6e80a4bfd23a25f5c05b90167c19030cf9f87930f7cb2eacb99f45d1c3304", size = 362867, upload-time = "2025-02-28T01:23:26.875Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/97/8d3118efd8354c555a3422d544163f40d9f236be5b96c714086463f11699/bcrypt-4.3.0-cp38-abi3-win32.whl", hash = "sha256:67a561c4d9fb9465ec866177e7aebcad08fe23aaf6fbd692a6fab69088abfc51", size = 160589, upload-time = "2025-02-28T01:23:28.381Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/07/416f0b99f7f3997c69815365babbc2e8754181a4b1899d921b3c7d5b6f12/bcrypt-4.3.0-cp38-abi3-win_amd64.whl", hash = "sha256:584027857bc2843772114717a7490a37f68da563b3620f78a849bcb54dc11e62", size = 152794, upload-time = "2025-02-28T01:23:30.187Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/c1/3fa0e9e4e0bfd3fd77eb8b52ec198fd6e1fd7e9402052e43f23483f956dd/bcrypt-4.3.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0d3efb1157edebfd9128e4e46e2ac1a64e0c1fe46fb023158a407c7892b0f8c3", size = 498969, upload-time = "2025-02-28T01:23:31.945Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/d4/755ce19b6743394787fbd7dff6bf271b27ee9b5912a97242e3caf125885b/bcrypt-4.3.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08bacc884fd302b611226c01014eca277d48f0a05187666bca23aac0dad6fe24", size = 279158, upload-time = "2025-02-28T01:23:34.161Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/5d/805ef1a749c965c46b28285dfb5cd272a7ed9fa971f970435a5133250182/bcrypt-4.3.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6746e6fec103fcd509b96bacdfdaa2fbde9a553245dbada284435173a6f1aef", size = 284285, upload-time = "2025-02-28T01:23:35.765Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/2b/698580547a4a4988e415721b71eb45e80c879f0fb04a62da131f45987b96/bcrypt-4.3.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:afe327968aaf13fc143a56a3360cb27d4ad0345e34da12c7290f1b00b8fe9a8b", size = 279583, upload-time = "2025-02-28T01:23:38.021Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/87/62e1e426418204db520f955ffd06f1efd389feca893dad7095bf35612eec/bcrypt-4.3.0-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d9af79d322e735b1fc33404b5765108ae0ff232d4b54666d46730f8ac1a43676", size = 297896, upload-time = "2025-02-28T01:23:39.575Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/c6/8fedca4c2ada1b6e889c52d2943b2f968d3427e5d65f595620ec4c06fa2f/bcrypt-4.3.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f1e3ffa1365e8702dc48c8b360fef8d7afeca482809c5e45e653af82ccd088c1", size = 284492, upload-time = "2025-02-28T01:23:40.901Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/4d/c43332dcaaddb7710a8ff5269fcccba97ed3c85987ddaa808db084267b9a/bcrypt-4.3.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:3004df1b323d10021fda07a813fd33e0fd57bef0e9a480bb143877f6cba996fe", size = 279213, upload-time = "2025-02-28T01:23:42.653Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/7f/1e36379e169a7df3a14a1c160a49b7b918600a6008de43ff20d479e6f4b5/bcrypt-4.3.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:531457e5c839d8caea9b589a1bcfe3756b0547d7814e9ce3d437f17da75c32b0", size = 284162, upload-time = "2025-02-28T01:23:43.964Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/0a/644b2731194b0d7646f3210dc4d80c7fee3ecb3a1f791a6e0ae6bb8684e3/bcrypt-4.3.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:17a854d9a7a476a89dcef6c8bd119ad23e0f82557afbd2c442777a16408e614f", size = 312856, upload-time = "2025-02-28T01:23:46.011Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/62/2a871837c0bb6ab0c9a88bf54de0fc021a6a08832d4ea313ed92a669d437/bcrypt-4.3.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6fb1fd3ab08c0cbc6826a2e0447610c6f09e983a281b919ed721ad32236b8b23", size = 316726, upload-time = "2025-02-28T01:23:47.575Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/a1/9898ea3faac0b156d457fd73a3cb9c2855c6fd063e44b8522925cdd8ce46/bcrypt-4.3.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e965a9c1e9a393b8005031ff52583cedc15b7884fce7deb8b0346388837d6cfe", size = 343664, upload-time = "2025-02-28T01:23:49.059Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/f2/71b4ed65ce38982ecdda0ff20c3ad1b15e71949c78b2c053df53629ce940/bcrypt-4.3.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:79e70b8342a33b52b55d93b3a59223a844962bef479f6a0ea318ebbcadf71505", size = 363128, upload-time = "2025-02-28T01:23:50.399Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/99/12f6a58eca6dea4be992d6c681b7ec9410a1d9f5cf368c61437e31daa879/bcrypt-4.3.0-cp39-abi3-win32.whl", hash = "sha256:b4d4e57f0a63fd0b358eb765063ff661328f69a04494427265950c71b992a39a", size = 160598, upload-time = "2025-02-28T01:23:51.775Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/cf/45fb5261ece3e6b9817d3d82b2f343a505fd58674a92577923bc500bd1aa/bcrypt-4.3.0-cp39-abi3-win_amd64.whl", hash = "sha256:e53e074b120f2877a35cc6c736b8eb161377caae8925c17688bd46ba56daaa5b", size = 152799, upload-time = "2025-02-28T01:23:53.139Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2025.8.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cffi"
|
||||
version = "2.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pycparser", marker = "implementation_name != 'PyPy'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "3.4.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cloudscraper"
|
||||
version = "1.2.71"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pyparsing" },
|
||||
{ name = "requests" },
|
||||
{ name = "requests-toolbelt" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ac/25/6d0481860583f44953bd791de0b7c4f6d7ead7223f8a17e776247b34a5b4/cloudscraper-1.2.71.tar.gz", hash = "sha256:429c6e8aa6916d5bad5c8a5eac50f3ea53c9ac22616f6cb21b18dcc71517d0d3", size = 93261, upload-time = "2023-04-25T23:20:19.467Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/81/97/fc88803a451029688dffd7eb446dc1b529657577aec13aceff1cc9628c5d/cloudscraper-1.2.71-py2.py3-none-any.whl", hash = "sha256:76f50ca529ed2279e220837befdec892626f9511708e200d48d5bb76ded679b0", size = 99652, upload-time = "2023-04-25T23:20:15.974Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastapi"
|
||||
version = "0.116.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pydantic" },
|
||||
{ name = "starlette" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/78/d7/6c8b3bfe33eeffa208183ec037fee0cce9f7f024089ab1c5d12ef04bd27c/fastapi-0.116.1.tar.gz", hash = "sha256:ed52cbf946abfd70c5a0dccb24673f0670deeb517a88b3544d03c2a6bf283143", size = 296485, upload-time = "2025-07-11T16:22:32.057Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/47/d63c60f59a59467fda0f93f46335c9d18526d7071f025cb5b89d5353ea42/fastapi-0.116.1-py3-none-any.whl", hash = "sha256:c46ac7c312df840f0c9e220f7964bada936781bc4e2e6eb71f1c4d7553786565", size = 95631, upload-time = "2025-07-11T16:22:30.485Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gppt"
|
||||
version = "4.1.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pixivpy3" },
|
||||
{ name = "pwinput" },
|
||||
{ name = "requests" },
|
||||
{ name = "selenium" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4c/02/e4031ccf92deb123a28418a6cdf40bdbbb7a3e5e0240717bcea3d3752aef/gppt-4.1.1.tar.gz", hash = "sha256:f43cc59f68f799ffe54b0718a4706487541888bf066926479f7fc6160caffb2f", size = 10935, upload-time = "2025-04-08T13:38:26.787Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/c1/648e7727c06c26becd83016db08716fb93ffeb578f981229a560f7f9ab99/gppt-4.1.1-py3-none-any.whl", hash = "sha256:5a8b8f6e29d4ccc8555d715841a768d3e6cbac3be690742dbc6ad0949fb8990d", size = 12344, upload-time = "2025-04-08T13:38:25.815Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.16.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.10"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "loguru"
|
||||
version = "0.7.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
{ name = "win32-setctime", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3a/05/a1dae3dffd1116099471c643b8924f5aa6524411dc6c63fdae648c4f1aca/loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6", size = 63559, upload-time = "2024-12-06T11:20:56.608Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload-time = "2024-12-06T11:20:54.538Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "outcome"
|
||||
version = "1.3.0.post0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "attrs" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/98/df/77698abfac98571e65ffeb0c1fba8ffd692ab8458d617a0eed7d9a8d38f2/outcome-1.3.0.post0.tar.gz", hash = "sha256:9dcf02e65f2971b80047b377468e72a268e15c0af3cf1238e6ff14f7f91143b8", size = 21060, upload-time = "2023-10-26T04:26:04.361Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/55/8b/5ab7257531a5d830fc8000c476e63c935488d74609b50f9384a643ec0a62/outcome-1.3.0.post0-py2.py3-none-any.whl", hash = "sha256:e771c5ce06d1415e356078d3bdd68523f284b4ce5419828922b6871e65eda82b", size = 10692, upload-time = "2023-10-26T04:26:02.532Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "passlib"
|
||||
version = "1.7.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b6/06/9da9ee59a67fae7761aab3ccc84fa4f3f33f125b370f1ccdb915bf967c11/passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04", size = 689844, upload-time = "2020-10-08T19:00:52.121Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/a4/ab6b7589382ca3df236e03faa71deac88cae040af60c071a78d254a62172/passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1", size = 525554, upload-time = "2020-10-08T19:00:49.856Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "picrinth-server"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "annotated-types" },
|
||||
{ name = "anyio" },
|
||||
{ name = "asyncpg" },
|
||||
{ name = "attrs" },
|
||||
{ name = "bcrypt" },
|
||||
{ name = "certifi" },
|
||||
{ name = "charset-normalizer" },
|
||||
{ name = "click" },
|
||||
{ name = "cloudscraper" },
|
||||
{ name = "fastapi" },
|
||||
{ name = "gppt" },
|
||||
{ name = "h11" },
|
||||
{ name = "idna" },
|
||||
{ name = "loguru" },
|
||||
{ name = "outcome" },
|
||||
{ name = "passlib" },
|
||||
{ name = "pixivpy3" },
|
||||
{ name = "pwinput" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "pydantic-core" },
|
||||
{ name = "pyjwt" },
|
||||
{ name = "pyparsing" },
|
||||
{ name = "pysocks" },
|
||||
{ name = "python-decouple" },
|
||||
{ name = "python-multipart" },
|
||||
{ name = "requests" },
|
||||
{ name = "requests-toolbelt" },
|
||||
{ name = "selenium" },
|
||||
{ name = "sniffio" },
|
||||
{ name = "sortedcontainers" },
|
||||
{ name = "starlette" },
|
||||
{ name = "trio" },
|
||||
{ name = "trio-websocket" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "typing-inspection" },
|
||||
{ name = "urllib3" },
|
||||
{ name = "uvicorn" },
|
||||
{ name = "websocket-client" },
|
||||
{ name = "wsproto" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "annotated-types", specifier = "==0.7.0" },
|
||||
{ name = "anyio", specifier = "==4.9.0" },
|
||||
{ name = "asyncpg", specifier = "==0.30.0" },
|
||||
{ name = "attrs", specifier = "==25.3.0" },
|
||||
{ name = "bcrypt", specifier = "==4.3.0" },
|
||||
{ name = "certifi", specifier = "==2025.8.3" },
|
||||
{ name = "charset-normalizer", specifier = "==3.4.2" },
|
||||
{ name = "click", specifier = "==8.2.1" },
|
||||
{ name = "cloudscraper", specifier = "==1.2.71" },
|
||||
{ name = "fastapi", specifier = "==0.116.1" },
|
||||
{ name = "gppt", specifier = "==4.1.1" },
|
||||
{ name = "h11", specifier = "==0.16.0" },
|
||||
{ name = "idna", specifier = "==3.10" },
|
||||
{ name = "loguru", specifier = "==0.7.3" },
|
||||
{ name = "outcome", specifier = "==1.3.0.post0" },
|
||||
{ name = "passlib", specifier = "==1.7.4" },
|
||||
{ name = "pixivpy3", specifier = "==3.7.5" },
|
||||
{ name = "pwinput", specifier = "==1.0.3" },
|
||||
{ name = "pydantic", specifier = "==2.11.7" },
|
||||
{ name = "pydantic-core", specifier = "==2.33.2" },
|
||||
{ name = "pyjwt", specifier = "==2.10.1" },
|
||||
{ name = "pyparsing", specifier = "==3.2.3" },
|
||||
{ name = "pysocks", specifier = "==1.7.1" },
|
||||
{ name = "python-decouple", specifier = "==3.8" },
|
||||
{ name = "python-multipart", specifier = "==0.0.20" },
|
||||
{ name = "requests", specifier = "==2.32.4" },
|
||||
{ name = "requests-toolbelt", specifier = "==1.0.0" },
|
||||
{ name = "selenium", specifier = "==4.34.2" },
|
||||
{ name = "sniffio", specifier = "==1.3.1" },
|
||||
{ name = "sortedcontainers", specifier = "==2.4.0" },
|
||||
{ name = "starlette", specifier = "==0.47.2" },
|
||||
{ name = "trio", specifier = "==0.30.0" },
|
||||
{ name = "trio-websocket", specifier = "==0.12.2" },
|
||||
{ name = "typing-extensions", specifier = "==4.14.1" },
|
||||
{ name = "typing-inspection", specifier = "==0.4.1" },
|
||||
{ name = "urllib3", specifier = "==2.5.0" },
|
||||
{ name = "uvicorn", specifier = "==0.35.0" },
|
||||
{ name = "websocket-client", specifier = "==1.8.0" },
|
||||
{ name = "wsproto", specifier = "==1.2.0" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pixivpy3"
|
||||
version = "3.7.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cloudscraper" },
|
||||
{ name = "requests" },
|
||||
{ name = "requests-toolbelt" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/24/19/4a6798a8552dc5702618e3e340b995de959849704458392f4adbfcd697c8/pixivpy3-3.7.5.tar.gz", hash = "sha256:cfb0c444bea994a9b2c1c01e60f2e8ab1afe57b05f2d51545ef64a7df2f220cc", size = 20527, upload-time = "2024-03-03T10:09:50.747Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/09/f0/8f40168c6e4aa824543d67d17b3104f00c6e9bda53709693c1a0e30e0b72/pixivpy3-3.7.5-py3-none-any.whl", hash = "sha256:3f9c3c9236d9924de9f80390cc3d1cc78bc494175a94cc9903f7b60308efca72", size = 17549, upload-time = "2024-03-03T10:09:48.48Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pwinput"
|
||||
version = "1.0.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4b/c9/f6d03c2214c282251baa38c6be08f5b9f81f1ea488716cd2335e7e190b58/pwinput-1.0.3.tar.gz", hash = "sha256:ca1a8bd06e28872d751dbd4132d8637127c25b408ea3a349377314a5491426f3", size = 4433, upload-time = "2023-02-16T17:04:14.592Z" }
|
||||
|
||||
[[package]]
|
||||
name = "pycparser"
|
||||
version = "3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.11.7"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "annotated-types" },
|
||||
{ name = "pydantic-core" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "typing-inspection" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-core"
|
||||
version = "2.33.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyjwt"
|
||||
version = "2.10.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyparsing"
|
||||
version = "3.2.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bb/22/f1129e69d94ffff626bdb5c835506b3a5b4f3d070f17ea295e12c2c6f60f/pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be", size = 1088608, upload-time = "2025-03-25T05:01:28.114Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf", size = 111120, upload-time = "2025-03-25T05:01:24.908Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pysocks"
|
||||
version = "1.7.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bd/11/293dd436aea955d45fc4e8a35b6ae7270f5b8e00b53cf6c024c83b657a11/PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0", size = 284429, upload-time = "2019-09-20T02:07:35.714Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/59/b4572118e098ac8e46e399a1dd0f2d85403ce8bbaad9ec79373ed6badaf9/PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5", size = 16725, upload-time = "2019-09-20T02:06:22.938Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-decouple"
|
||||
version = "3.8"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e1/97/373dcd5844ec0ea5893e13c39a2c67e7537987ad8de3842fe078db4582fa/python-decouple-3.8.tar.gz", hash = "sha256:ba6e2657d4f376ecc46f77a3a615e058d93ba5e465c01bbe57289bfb7cce680f", size = 9612, upload-time = "2023-03-01T19:38:38.143Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/d4/9193206c4563ec771faf2ccf54815ca7918529fe81f6adb22ee6d0e06622/python_decouple-3.8-py3-none-any.whl", hash = "sha256:d0d45340815b25f4de59c974b855bb38d03151d81b037d9e3f463b0c9f8cbd66", size = 9947, upload-time = "2023-03-01T19:38:36.015Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-multipart"
|
||||
version = "0.0.20"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.32.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "charset-normalizer" },
|
||||
{ name = "idna" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "requests-toolbelt"
|
||||
version = "1.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "requests" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "selenium"
|
||||
version = "4.34.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "trio" },
|
||||
{ name = "trio-websocket" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "urllib3", extra = ["socks"] },
|
||||
{ name = "websocket-client" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b0/e6/646d0a41fb9a64572043c3de80be2a4941f2aeb578f273cf3dae54fc9437/selenium-4.34.2.tar.gz", hash = "sha256:0f6d147595f08c6d4bad87b34c39dcacb4650aedc78e3956c8eac1bb752a3854", size = 896309, upload-time = "2025-07-08T12:54:54.785Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/2b/dee1c58bde0a747b2d75fa7282a190885a726fe95b18b8ce1dc52f9c0983/selenium-4.34.2-py3-none-any.whl", hash = "sha256:ea208f7db9e3b26e58c4a817ea9dd29454576d6ea55937d754df079ad588e1ad", size = 9410676, upload-time = "2025-07-08T12:54:48.725Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sniffio"
|
||||
version = "1.3.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sortedcontainers"
|
||||
version = "2.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "starlette"
|
||||
version = "0.47.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/04/57/d062573f391d062710d4088fa1369428c38d51460ab6fedff920efef932e/starlette-0.47.2.tar.gz", hash = "sha256:6ae9aa5db235e4846decc1e7b79c4f346adf41e9777aebeb49dfd09bbd7023d8", size = 2583948, upload-time = "2025-07-20T17:31:58.522Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/1f/b876b1f83aef204198a42dc101613fefccb32258e5428b5f9259677864b4/starlette-0.47.2-py3-none-any.whl", hash = "sha256:c5847e96134e5c5371ee9fac6fdf1a67336d5815e09eb2a01fdb57a351ef915b", size = 72984, upload-time = "2025-07-20T17:31:56.738Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "trio"
|
||||
version = "0.30.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "attrs" },
|
||||
{ name = "cffi", marker = "implementation_name != 'pypy' and os_name == 'nt'" },
|
||||
{ name = "idna" },
|
||||
{ name = "outcome" },
|
||||
{ name = "sniffio" },
|
||||
{ name = "sortedcontainers" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/01/c1/68d582b4d3a1c1f8118e18042464bb12a7c1b75d64d75111b297687041e3/trio-0.30.0.tar.gz", hash = "sha256:0781c857c0c81f8f51e0089929a26b5bb63d57f927728a5586f7e36171f064df", size = 593776, upload-time = "2025-04-21T00:48:19.507Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/69/8e/3f6dfda475ecd940e786defe6df6c500734e686c9cd0a0f8ef6821e9b2f2/trio-0.30.0-py3-none-any.whl", hash = "sha256:3bf4f06b8decf8d3cf00af85f40a89824669e2d033bb32469d34840edcfc22a5", size = 499194, upload-time = "2025-04-21T00:48:17.167Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "trio-websocket"
|
||||
version = "0.12.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "outcome" },
|
||||
{ name = "trio" },
|
||||
{ name = "wsproto" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d1/3c/8b4358e81f2f2cfe71b66a267f023a91db20a817b9425dd964873796980a/trio_websocket-0.12.2.tar.gz", hash = "sha256:22c72c436f3d1e264d0910a3951934798dcc5b00ae56fc4ee079d46c7cf20fae", size = 33549, upload-time = "2025-02-25T05:16:58.947Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/19/eb640a397bba49ba49ef9dbe2e7e5c04202ba045b6ce2ec36e9cadc51e04/trio_websocket-0.12.2-py3-none-any.whl", hash = "sha256:df605665f1db533f4a386c94525870851096a223adcb97f72a07e8b4beba45b6", size = 21221, upload-time = "2025-02-25T05:16:57.545Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.14.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-inspection"
|
||||
version = "0.4.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.5.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
socks = [
|
||||
{ name = "pysocks" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uvicorn"
|
||||
version = "0.35.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
{ name = "h11" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5e/42/e0e305207bb88c6b8d3061399c6a961ffe5fbb7e2aa63c9234df7259e9cd/uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01", size = 78473, upload-time = "2025-06-28T16:15:46.058Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406, upload-time = "2025-06-28T16:15:44.816Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "websocket-client"
|
||||
version = "1.8.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e6/30/fba0d96b4b5fbf5948ed3f4681f7da2f9f64512e1d303f94b4cc174c24a5/websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da", size = 54648, upload-time = "2024-04-23T22:16:16.976Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526", size = 58826, upload-time = "2024-04-23T22:16:14.422Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "win32-setctime"
|
||||
version = "1.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/705086c9d734d3b663af0e9bb3d4de6578d08f46b1b101c2442fd9aecaa2/win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0", size = 4867, upload-time = "2024-12-07T15:28:28.314Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083, upload-time = "2024-12-07T15:28:26.465Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wsproto"
|
||||
version = "1.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "h11" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c9/4a/44d3c295350d776427904d73c189e10aeae66d7f555bb2feee16d1e4ba5a/wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065", size = 53425, upload-time = "2022-08-23T19:58:21.447Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736", size = 24226, upload-time = "2022-08-23T19:58:19.96Z" },
|
||||
]
|
||||
Reference in New Issue
Block a user