Compare commits
29
Commits
a98c022795
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2beef62041 | ||
|
|
9c2a3ac42e | ||
|
|
b5e693ab9f | ||
|
|
cecd9a4458 | ||
|
|
ab7372cc9c | ||
|
|
6d7eae5ea4 | ||
|
|
10303accd2 | ||
|
|
7f6dcefb91 | ||
|
|
960856cdd8 | ||
|
|
6e8b62ffe1 | ||
|
|
8ecd30db0e | ||
|
|
cc0fd8ec64 | ||
|
|
6beba3945e | ||
|
|
9a3b4d9699 | ||
|
|
90b9a3a717 | ||
|
|
90dbed634d | ||
|
|
eb3b4478fc | ||
|
|
da41fb5990 | ||
|
|
895ac1e276 | ||
|
|
f8609ac317 | ||
|
|
b32e689d11 | ||
|
|
474c677d78 | ||
|
|
0ad971cc49 | ||
|
|
6c961db275 | ||
|
|
c8884ebeec | ||
|
|
28d4fe749a | ||
|
|
6f7665c5d1 | ||
|
|
2b42be2b64 | ||
|
|
2a8595b5fd |
+2
-1
@@ -9,7 +9,8 @@ wheels/
|
||||
# Virtual environments
|
||||
.venv
|
||||
|
||||
.env
|
||||
.env*
|
||||
.ruff_cache
|
||||
|
||||
*.sqlite
|
||||
*.duckdb
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
FROM ghcr.io/astral-sh/uv:python3.12-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ENV UV_COMPILE_BYTECODE=1
|
||||
|
||||
ADD pyproject.toml uv.lock .
|
||||
RUN mkdir -p /app/data
|
||||
RUN uv sync --frozen --no-dev
|
||||
|
||||
ADD /src .
|
||||
|
||||
ENV PATH="/app/.venv/bin:$PATH"
|
||||
|
||||
ENTRYPOINT []
|
||||
|
||||
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
services:
|
||||
pjl-backend:
|
||||
# Build the image from the Dockerfile in the current directory
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
image: pjl-backend:1.0rc1
|
||||
|
||||
env_file:
|
||||
- .env.prod
|
||||
|
||||
Environment:
|
||||
DB_URL: /app/data/pjl.duckdb
|
||||
DEFAULT_AVATAR_URL: https://api.dicebear.com/9.x/adventurer-neutral/svg?seed=Kingston
|
||||
OMDB_API_KEY: ${OMDB_API_KEY}
|
||||
STAGE: prod
|
||||
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
|
||||
# Host the FastAPI application on port 8000
|
||||
ports:
|
||||
- "8000:8000"
|
||||
|
||||
+14
-3
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "pjl"
|
||||
version = "0.1.0"
|
||||
name = "pjl-backend"
|
||||
version = "1.0rc1"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
@@ -11,7 +11,18 @@ dependencies = [
|
||||
"loguru>=0.7.2",
|
||||
"pydantic-settings>=2.6.1",
|
||||
"pydantic>=2.9.2",
|
||||
"requests>=2.32.3",
|
||||
"rich>=13.9.4",
|
||||
"uvicorn>=0.32.0",
|
||||
"pandas>=2.2.3",
|
||||
"requests>=2.32.3",
|
||||
]
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"ruff>=0.8.0",
|
||||
|
||||
]
|
||||
|
||||
[tool.ruff]
|
||||
lint.extend-select = ["C4", "SIM", "TCH", "ERA", "FAST", "DOC"]
|
||||
target-version = "py312"
|
||||
@@ -15,6 +15,7 @@ app = FastAPI(
|
||||
title="Paska joululeffa 2024",
|
||||
name="Paska joululeffa 2024",
|
||||
description="API for Paska joululeffa 2024",
|
||||
version="0.9",
|
||||
)
|
||||
|
||||
app.add_middleware(
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
from datetime import datetime
|
||||
import bcrypt
|
||||
import requests
|
||||
|
||||
from lib.database.database import db_run
|
||||
from models import Message
|
||||
from models.user import UserCredentials
|
||||
from models.movie import MovieIn, Movie
|
||||
from models.card import Card
|
||||
from lib.settings import settings
|
||||
from lib.logger import logger
|
||||
|
||||
|
||||
def add_user(user: UserCredentials):
|
||||
hashed_password = bcrypt.hashpw(
|
||||
user.password.encode("utf-8"), bcrypt.gensalt()
|
||||
).decode("utf-8")
|
||||
sql = f"INSERT INTO users (username, password) VALUES ('{user.username}', '{hashed_password}')"
|
||||
try:
|
||||
db_run(sql)
|
||||
except Exception as e:
|
||||
return Message(message=f"User already exists. Error: {e}", message_type="error")
|
||||
|
||||
return Message(message="User added successfully", message_type="success")
|
||||
|
||||
|
||||
def remove_user(user_id: str):
|
||||
logger.debug(f"Removing user {user_id}")
|
||||
sql = f"DELETE FROM users WHERE id = '{user_id}'"
|
||||
|
||||
try:
|
||||
db_run(sql)
|
||||
except Exception as e:
|
||||
return Message(message=f"User not found. Error: {e}", message_type="error")
|
||||
|
||||
return Message(message="User removed successfully", message_type="success")
|
||||
|
||||
|
||||
def add_movie(movie: MovieIn):
|
||||
logger.info(f"Adding movie {movie.imdb_id}")
|
||||
logger.debug(f"Requesting movie data from OMDB: {movie.imdb_id}")
|
||||
request_url = "https://omdbapi.com/"
|
||||
request_params = {
|
||||
"apikey": settings.OMDB_API_KEY,
|
||||
"i": movie.imdb_id,
|
||||
}
|
||||
|
||||
response = requests.get(request_url, params=request_params)
|
||||
if response.status_code != 200:
|
||||
logger.error(f"Failed to add movie {movie.imdb_id}")
|
||||
return Message(message="Failed to add movie", message_type="error")
|
||||
movie_data = response.json()
|
||||
logger.debug(f"Movie data received from OMDB: {movie_data}")
|
||||
if "Released" in movie_data:
|
||||
movie_data["Released"] = datetime.strptime(movie_data["Released"], "%d %b %Y")
|
||||
|
||||
new_movie = Movie(
|
||||
name=movie_data["Title"],
|
||||
imdb_id=movie_data["imdbID"],
|
||||
actors=[actor.strip() for actor in movie_data["Actors"].split(",")],
|
||||
release_date=movie_data["Released"],
|
||||
plot=movie.plot,
|
||||
poster_url=movie_data["Poster"],
|
||||
showtime=movie.showtime,
|
||||
)
|
||||
logger.debug(f"Movie data: {new_movie.model_dump()}")
|
||||
sql = "INSERT INTO movies (name, imdb_id, actors, release_date, plot, showtime, poster_url) VALUES (?, ?, ?, ?, ?, ?, ?)"
|
||||
try:
|
||||
db_run(
|
||||
sql,
|
||||
(
|
||||
new_movie.name,
|
||||
new_movie.imdb_id,
|
||||
new_movie.actors,
|
||||
new_movie.release_date,
|
||||
new_movie.plot,
|
||||
new_movie.showtime,
|
||||
new_movie.poster_url,
|
||||
),
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to add movie {movie.imdb_id}. Error: {e}")
|
||||
return Message(message="Failed to add movie", message_type="error")
|
||||
|
||||
logger.info(f"Movie {movie.imdb_id} added successfully")
|
||||
return Message(message="Movie added successfully", message_type="success")
|
||||
|
||||
|
||||
def set_movie_watched(movie_id: str):
|
||||
sql = f"UPDATE movies SET is_watched = TRUE WHERE id = '{movie_id}'"
|
||||
try:
|
||||
db_run(sql)
|
||||
except Exception as e:
|
||||
return Message(
|
||||
message=f"Failed to set movie as watched. Error: {e}", message_type="error"
|
||||
)
|
||||
|
||||
return Message(message="Movie set as watched successfully", message_type="success")
|
||||
|
||||
|
||||
def add_card(card: Card):
|
||||
sql = """
|
||||
INSERT INTO cards (title, description, point_value)
|
||||
VALUES (?, ?, ?)
|
||||
"""
|
||||
values = (
|
||||
card.title,
|
||||
card.description,
|
||||
card.point_value,
|
||||
)
|
||||
try:
|
||||
db_run(sql, values)
|
||||
return {"message": "Card added successfully"}
|
||||
except Exception as e:
|
||||
return {"message": f"Error adding card: {e}"}
|
||||
|
||||
|
||||
def remove_card(card_id: str):
|
||||
sql = f"""
|
||||
DELETE FROM cards WHERE id = '{card_id}'
|
||||
"""
|
||||
|
||||
try:
|
||||
db_run(sql)
|
||||
return {"message": "Card removed successfully"}
|
||||
except Exception as e:
|
||||
return {"message": f"Error removing card: {e}"}
|
||||
@@ -0,0 +1,20 @@
|
||||
from models.card import Card
|
||||
from lib.database.database import get_db
|
||||
|
||||
|
||||
def get_all_cards() -> list[Card]:
|
||||
sql = "SELECT * FROM cards"
|
||||
with get_db() as conn:
|
||||
result = conn.execute(sql).fetchall()
|
||||
cards: list[Card] = []
|
||||
for row in result:
|
||||
card = Card(
|
||||
id=str(row[0]),
|
||||
title=row[1],
|
||||
description=row[2],
|
||||
point_value=row[3],
|
||||
created_at=row[4],
|
||||
updated_at=row[5],
|
||||
)
|
||||
cards.append(card)
|
||||
return cards
|
||||
@@ -1,33 +1,34 @@
|
||||
from contextlib import contextmanager
|
||||
import duckdb
|
||||
import bcrypt
|
||||
from rich import print
|
||||
|
||||
from lib import settings
|
||||
from lib.logger import logger
|
||||
|
||||
|
||||
@contextmanager
|
||||
def get_db():
|
||||
try:
|
||||
conn = duckdb.connect(database=settings.db_url)
|
||||
logger.debug(f"Connecting to database {settings.db_url.absolute()}")
|
||||
conn = duckdb.connect(database=settings.db_url.absolute())
|
||||
yield conn
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def db_run(sql):
|
||||
def db_run(sql, values: tuple = None):
|
||||
with get_db() as conn:
|
||||
result = conn.execute(sql)
|
||||
result = conn.execute(sql, values)
|
||||
return result
|
||||
|
||||
|
||||
def init_db():
|
||||
if settings.db_url.exists():
|
||||
pass
|
||||
# TODO: Development feature. Remove this in production
|
||||
from os import unlink
|
||||
# from os import unlink
|
||||
|
||||
unlink(settings.db_url)
|
||||
# return
|
||||
# unlink(settings.db_url)
|
||||
sql = ""
|
||||
with open("lib/database/database.sql") as f:
|
||||
sql = f.read()
|
||||
@@ -38,8 +39,5 @@ def init_db():
|
||||
|
||||
hashed_password = bcrypt.hashpw("password".encode("utf-8"), bcrypt.gensalt())
|
||||
|
||||
sql = f"INSERT INTO users (username, password, is_admin) VALUES ('admin', '{hashed_password.decode('utf-8')}', true)"
|
||||
db_run(sql)
|
||||
|
||||
sql = f"INSERT INTO users (username, password, is_admin) VALUES ('test', '{hashed_password.decode('utf-8')}', false)"
|
||||
sql = f"INSERT INTO users (username, password, is_admin) VALUES ('admin', '{hashed_password.decode('utf-8')}', true), ('kessinen', '{hashed_password.decode('utf-8')}', false), ('jensku', '{hashed_password.decode('utf-8')}', false), ('emppu', '{hashed_password.decode('utf-8')}', false)"
|
||||
db_run(sql)
|
||||
|
||||
@@ -13,11 +13,12 @@ CREATE TABLE users (
|
||||
|
||||
CREATE TABLE movies (
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
name TEXT UNIQUE NOT NULL,
|
||||
imdb_id TEXT UNIQUE,
|
||||
actors TEXT[],
|
||||
release_date DATE DEFAULT NULL,
|
||||
plot TEXT,
|
||||
plot TEXT DEFAULT NULL,
|
||||
poster_url TEXT DEFAULT NULL,
|
||||
showtime DATE DEFAULT NULL,
|
||||
is_watched BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
@@ -52,11 +53,55 @@ CREATE TABLE log (
|
||||
);
|
||||
|
||||
-- Add placeholder data
|
||||
INSERT INTO movies (name, imdb_id, actors, release_date, plot, showtime)
|
||||
VALUES ('A Maple Valley Christmas',
|
||||
'tt21841642',
|
||||
['Peyton List', 'Andrew W. Walker', 'Frances Flanagan'],
|
||||
|
||||
INSERT INTO movies (name, imdb_id, actors, release_date, plot, poster_url, showtime)
|
||||
VALUES ('Christmas Sail',
|
||||
'tt15758364',
|
||||
['Katee Sackhoff', 'Patrick Sabongui', 'Terry O''Quinn'],
|
||||
'2022-11-05',
|
||||
'Erica is a rancher who has spent her whole life working the family farm with her mother and sister.
|
||||
When Aaron arrives and disrupts her plans, she starts to question what it is she actually wants.',
|
||||
'2024-12-01');
|
||||
'Lizin etääntynyt isä pyytää apua ja Liz palaa kotikaupunkiinsa. Hän päättää luoda täydellisen joulun tyttärelleen ja saada ihmissuhteensa kuntoon.',
|
||||
'https://m.media-amazon.com/images/M/MV5BZTA0MjA2MmMtNWZhNy00N2UyLWI5YjItZTA4ZDVkZmNmMWVjXkEyXkFqcGc@._V1_SX300.jpg',
|
||||
'2024-12-02'),
|
||||
('The Holiday Stocking',
|
||||
'tt22308008',
|
||||
['Nadine Ellis', 'Tamala Jones', 'B.J. Britt'],
|
||||
'2022-12-03','Enkeli palaa maan päälle vieraana ja saa mahdollisuuden auttaa sisariaan sovintoon - asia mitä hän ei onnistunut tekemään elinaikanaan.',
|
||||
'https://m.media-amazon.com/images/M/MV5BZTI5OTc5OTYtOWVlMC00YmRhLWFhMmQtMGIwYjNmZTI1Y2Y5XkEyXkFqcGc@._V1_SX300.jpg',
|
||||
'2024-12-03'),;
|
||||
|
||||
INSERT INTO cards (title, description, point_value)
|
||||
VALUES
|
||||
('Blondi tuli taloon', 'Naispääosalla pitkät blondit hiukset.', 1),
|
||||
('Ensilumi', 'Juuri kun kaikki toivo on menetetty, ensilumi alkaa sataa.', 3),
|
||||
('Evergreen', 'Tarinan kylä tai kaupunki on idyllinen paikka, nimeltään jotain jouluista, kuten ''Evergreen'' tai ''Snowville''.', 3),
|
||||
('Hengailua kirjastossa', 'Pieni kirjakauppa tai kirjasto on keskeinen kohtauspaikka ja keskustelujen paikka.', 3),
|
||||
('Himo jouluttaja', 'Yksi hahmoista on ylitsepursuava jouluihminen, jolla on täydellisesti koristeltu koti.', 1),
|
||||
('Jouluenkeli', 'Jouluenkeli – joko koristeena tai todellisena ilmestyksenä – ohjaa hahmoja oikealle tielle.', 3),
|
||||
('Joulukuusten Invaasio', 'Elokuvassa on koti jossa on vähintään 5 joulukuusta. Kuuset voivat olla eri kokoisia.', 2),
|
||||
('Joulumörkö', 'Hahmo, joka vihaa joulua, mutta päätyy lopulta rakastamaan sitä.', 1),
|
||||
('Jouluoksennus', 'Koti, työpaikka tai jokin muu koristeltu liioitellusti jouluteemalla, ilman mitään hillittyä tyylitajua.', 1),
|
||||
('Kantakahvila', 'Pieni, kodikas kahvila toimii kaikkien tapaamispaikkana ja keskustelujen keskuksena.', 2),
|
||||
('Keskeytetty suudelma', 'Juuri, kun romanttinen hetki on käsillä, jokin keskeyttää ensisuudelman.', 1),
|
||||
('Kuuma kaakao', 'Kuumaa kaakaota juodaan lukemattomia kertoja, aina kermavaahdon kera.', 2),
|
||||
('Kuusen valinta', 'Kohtaus, jossa kuusen valinnasta tehdään koko tarinan käännekohta.', 2),
|
||||
('Kylän joulutapahtuma', 'Joulukulkue, jouluvalotapahtuma tai kuusen valaiseminen, jossa kaikki kokoontuvat yhteen.', 1),
|
||||
('Lapsen joulutoive', 'Pieni lapsi toivoo joululta jotain suurta, yleensä perheeseen liittyvää.', 3),
|
||||
('Leskimies', 'Päähenkilö on menettänyt puolisonsa ja yrittää nyt toipua elämässään.', 2),
|
||||
('Lumipyry', 'Suuri lumipyry eristää päähenkilöt mökkiin tai kylään.', 3),
|
||||
('Lumiukko', 'Lumiukon rakentaminen on täydellisen joulupäivän kohokohta.', 3),
|
||||
('Majatalo', 'Tarinaan kuuluu aina viehättävä majatalo, jossa on perinteinen joulukoristelu.', 2),
|
||||
('Miespääosa eräjorma', 'Miespäähenkilö on karskin komea eränkävijä, jolla on piilotettu herkkä puoli.', 2),
|
||||
('Naispääosa kaupungista', 'Uraohjautunut nainen suurkaupungista, jolla on asenneongelma pientä kylää kohtaan.', 1),
|
||||
('Naispääosan tuore ero', 'Päähenkilö on juuri eronnut ja toipuu kipeästä ihmissuhteesta.', 2),
|
||||
('Pääosa jumissa', 'Päähenkilö jää jumiin esimerkiksi kaukaiseen kylään tai pikkukaupunkiin.', 1),
|
||||
('Pakollinen luistelukohtaus', 'Pari luisteluhetkellä, jonka aikana tunteet alkavat syttyä.', 2),
|
||||
('Punainen lava-auto', 'Joku hahmo ajaa punaisella lava-autolla, usein ilman mitään käytännön syytä.', 1),
|
||||
('Punainen villakangastakki', 'Päähenkilöllä on aina kirkkaanpunainen, täydellisesti istuva talvitakki.', 1),
|
||||
('Suuri salaisuus', 'Joku hahmo kantaa suurta salaisuutta, joka paljastuu juuri oikealla hetkellä.', 2),
|
||||
('Täysikuu', 'Jouluyön täysikuu valaisee maiseman ja tuo taianomaisen tunnelman.', 3),
|
||||
('The Huoltaja', 'Miespäähenkilö on yksinhuoltaja, joka tasapainoilee uran ja lapsenhoidon välillä.', 2),
|
||||
('Työkriisi', 'Päähenkilön työelämässä on kriisi, joka vaatii kaiken huomion joulun alla.', 1),
|
||||
('Vanha kirje', 'Päähenkilö löytää vanhan kirjeen, joka muuttaa hänen elämänsä.', 3),
|
||||
('Vanhan parin rakkausneuvo', 'Iäkäs pariskunta antaa päähenkilölle neuvoja rakkaudesta.', 3),
|
||||
('Yllättävä vieras', 'Joulun alla joku saapuu yllättäen perhejuhliin tai kylään.', 3),
|
||||
('Yritys pulassa', 'Päähenkilön yritys on konkurssin partaalla tai vaarassa tulla ostetuksi.', 2);
|
||||
|
||||
+18
-31
@@ -1,25 +1,22 @@
|
||||
from models.movie import Movie
|
||||
from lib.database.database import get_db
|
||||
from lib.logger import logger
|
||||
|
||||
|
||||
@logger.catch
|
||||
def get_all_movies() -> list[Movie]:
|
||||
logger.info("Getting all movies")
|
||||
sql = "SELECT * FROM movies"
|
||||
with get_db() as conn:
|
||||
result = conn.execute(sql).fetchall()
|
||||
result = conn.execute(sql).df()
|
||||
result_dict = result.to_dict(orient="records")
|
||||
movies = []
|
||||
for row in result:
|
||||
movie = Movie(
|
||||
id=str(row[0]),
|
||||
name=row[1],
|
||||
imdb_id=row[2],
|
||||
actors=row[3],
|
||||
release_date=row[4],
|
||||
plot=row[5],
|
||||
showtime=row[6],
|
||||
is_watched=row[7],
|
||||
created_at=row[8],
|
||||
updated_at=row[9],
|
||||
)
|
||||
for row in result_dict:
|
||||
row["id"] = str(row["id"])
|
||||
logger.debug(f"Found movie: {row}")
|
||||
print("Type of name:", type(row["name"]))
|
||||
movie = Movie(**row)
|
||||
|
||||
movies.append(movie)
|
||||
|
||||
return movies
|
||||
@@ -27,25 +24,15 @@ def get_all_movies() -> list[Movie]:
|
||||
return
|
||||
|
||||
|
||||
def get_next_movie() -> Movie:
|
||||
sql = "SELECT * FROM movies WHERE is_watched = FALSE AND showtime > CURRENT_TIMESTAMP ORDER BY showtime ASC"
|
||||
def get_next_movie() -> Movie | None:
|
||||
sql = "SELECT * FROM movies WHERE is_watched = FALSE AND showtime = CURRENT_DATE ORDER BY showtime ASC"
|
||||
with get_db() as conn:
|
||||
result = conn.execute(sql).fetchone()
|
||||
if result is None:
|
||||
result = conn.execute(sql).fetchdf()
|
||||
if result.empty:
|
||||
return None
|
||||
movie = Movie(
|
||||
id=str(result[0]),
|
||||
name=result[1],
|
||||
imdb_id=result[2],
|
||||
actors=result[3],
|
||||
release_date=result[4],
|
||||
plot=result[5],
|
||||
showtime=result[6],
|
||||
is_watched=result[7],
|
||||
created_at=result[8],
|
||||
updated_at=result[9],
|
||||
)
|
||||
|
||||
response_data = result.to_dict(orient="records")[0]
|
||||
response_data["id"] = str(response_data["id"])
|
||||
movie = Movie(**response_data)
|
||||
return movie
|
||||
|
||||
return
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
from models.score import ScoreBase
|
||||
from lib.database.database import get_db
|
||||
from models.general import Message
|
||||
|
||||
|
||||
def set_score(score: ScoreBase) -> Message:
|
||||
sql = "INSERT INTO scores (movie_id, user_id, score) VALUES (?, ?, ?)"
|
||||
with get_db() as conn:
|
||||
conn.execute(sql, (score.movie_id, score.user_id, score.score))
|
||||
|
||||
return Message(message="Score added successfully", message_type="success")
|
||||
+18
-13
@@ -1,24 +1,29 @@
|
||||
from datetime import datetime
|
||||
|
||||
from models.user import UserIn, UserOut
|
||||
from lib.database.database import get_db
|
||||
from lib.logger import logger
|
||||
|
||||
|
||||
def set_last_login(user_id: str) -> datetime:
|
||||
logger.debug(f"Setting last login for user {user_id}")
|
||||
cur_time = datetime.now()
|
||||
sql = f"UPDATE users SET last_login = '{cur_time}' WHERE id = '{user_id}'"
|
||||
with get_db() as conn:
|
||||
conn.execute(sql)
|
||||
|
||||
return cur_time
|
||||
|
||||
|
||||
def get_user_by_username(username) -> UserIn | None:
|
||||
sql = f"SELECT * FROM users WHERE username = '{username}'"
|
||||
with get_db() as conn:
|
||||
result = conn.execute(sql).fetchone()
|
||||
result = conn.execute(sql).df().to_dict(orient="records")[0]
|
||||
if result is not None:
|
||||
return UserIn(
|
||||
id=str(result[0]),
|
||||
username=result[1],
|
||||
password=result[2],
|
||||
email=result[3],
|
||||
avatar_url=result[4],
|
||||
is_active=result[5],
|
||||
is_admin=result[6],
|
||||
last_login=result[7],
|
||||
created_at=result[8],
|
||||
updated_at=result[9],
|
||||
)
|
||||
last_login = set_last_login(result["id"])
|
||||
result["id"] = str(result["id"])
|
||||
result["last_login"] = last_login
|
||||
return UserIn(**result)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
+2
-4
@@ -4,14 +4,12 @@ from pathlib import Path
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
db_url: Path = Field(Path("pjl.duckdb"), env="DB_URL")
|
||||
db_url: Path = Field(Path("pjl_default.duckdb"), env="DB_URL")
|
||||
default_avatar_url: str = Field(
|
||||
"https://api.dicebear.com/9.x/adventurer-neutral/svg?seed=Kingston",
|
||||
env="DEFAULT_AVATAR_URL",
|
||||
)
|
||||
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
OMDB_API_KEY: str = Field(env="OMDB_API_KEY")
|
||||
|
||||
|
||||
settings = Settings()
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
# from .movie import Movie
|
||||
# from .score import Score
|
||||
# from .card import Card
|
||||
from .general import Message
|
||||
|
||||
__all__ = [
|
||||
# "Movie",
|
||||
# "Score",
|
||||
# "Card",
|
||||
"Message",
|
||||
]
|
||||
|
||||
+25
-8
@@ -1,12 +1,29 @@
|
||||
from sqlmodel import Field, SQLModel
|
||||
from pydantic import BaseModel, Field
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class Card(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
title: str
|
||||
description: str
|
||||
score: int
|
||||
created_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
updated_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
class Card(BaseModel):
|
||||
id: Optional[str] = Field(
|
||||
default=None,
|
||||
description="UUID representation of the card",
|
||||
examples=["123e4567-e89b-12d3-a456-426655440000"],
|
||||
)
|
||||
title: str = Field(
|
||||
description="Title of the card", examples=["The Interupted Kiss"]
|
||||
)
|
||||
description: str = Field(
|
||||
description="Description of the card",
|
||||
examples=["The couples first kiss is interrupted"],
|
||||
)
|
||||
point_value: int = Field(description="Score of the card", examples=[2])
|
||||
created_at: datetime = Field(
|
||||
default_factory=datetime.utcnow,
|
||||
description="Creation date of the card",
|
||||
examples=[datetime.utcnow()],
|
||||
)
|
||||
modified_at: datetime = Field(
|
||||
default_factory=datetime.utcnow,
|
||||
description="Modification date of the card",
|
||||
examples=[datetime.utcnow()],
|
||||
)
|
||||
|
||||
+25
-26
@@ -1,41 +1,40 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from datetime import datetime
|
||||
from datetime import datetime, date
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class Movie(BaseModel):
|
||||
id: Optional[str] = Field(
|
||||
default=None, examples=["123e4567-e89b-12d3-a456-426655440000"]
|
||||
)
|
||||
name: str = Field(
|
||||
description="Name of the movie", examples=["A Maple Valley Christmas"]
|
||||
class MovieBase(BaseModel):
|
||||
name: Optional[str] | None = Field(
|
||||
None, description="Name of the movie", examples=["A Maple Valley Christmas"]
|
||||
)
|
||||
imdb_id: str = Field(description="IMDB ID of the movie", examples=["tt1234567"])
|
||||
actors: list[str] = Field(
|
||||
description="Actors of the movie",
|
||||
examples=[["Peyton List", "Andrew W. Walker"]],
|
||||
)
|
||||
showtime: date = Field(description="Showtime of the movie", examples=["2024-12-01"])
|
||||
plot: str = Field(
|
||||
description="Plot of the movie", examples=["Erica is a rancher..."]
|
||||
)
|
||||
release_date: datetime = Field(
|
||||
description="Release date of the movie", examples=["2022-11-05"]
|
||||
|
||||
|
||||
class MovieIn(MovieBase):
|
||||
actors: Optional[list[str]] | None = Field(
|
||||
None,
|
||||
description="Actors of the movie",
|
||||
examples=[["Peyton List", "Andrew W. Walker"]],
|
||||
)
|
||||
showtime: datetime = Field(
|
||||
description="Showtime of the movie", examples=["2024-12-01"]
|
||||
release_date: datetime | None = Field(
|
||||
None, description="Release date of the movie", examples=["2022-11-05"]
|
||||
)
|
||||
is_watched: bool = Field(
|
||||
default=False,
|
||||
description="Whether the movie is watched or not",
|
||||
examples=[True],
|
||||
poster_url: str | None = Field(
|
||||
None, description="Poster URL of the movie", examples=["https://..."]
|
||||
)
|
||||
|
||||
|
||||
class Movie(MovieIn):
|
||||
id: Optional[str] = Field(
|
||||
default=None, examples=["123e4567-e89b-12d3-a456-426655440000"]
|
||||
)
|
||||
created_at: datetime = Field(
|
||||
default_factory=datetime.utcnow,
|
||||
description="Creation date of the movie",
|
||||
examples=[datetime.utcnow()],
|
||||
default_factory=datetime.utcnow, description="Creation date of the movie"
|
||||
)
|
||||
updated_at: datetime = Field(
|
||||
default_factory=datetime.utcnow,
|
||||
description="Modification date of the movie",
|
||||
examples=[datetime.utcnow()],
|
||||
modified_at: datetime = Field(
|
||||
default_factory=datetime.utcnow, description="Modification date of the movie"
|
||||
)
|
||||
|
||||
+17
-8
@@ -1,12 +1,21 @@
|
||||
from sqlmodel import Field, SQLModel
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class Score(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
user_id: Optional[int] = Field(default=None, foreign_key="user.id")
|
||||
movie_id: Optional[int] = Field(default=None, foreign_key="movie.id")
|
||||
score: int
|
||||
created_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
updated_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
class ScoreBase(BaseModel):
|
||||
user_id: str = Field(
|
||||
description="UUID representation of the user",
|
||||
examples=["123e4567-e89b-12d3-a456-426655440000"],
|
||||
)
|
||||
movie_id: str = Field(
|
||||
description="UUID representation of the movie",
|
||||
examples=["123e4567-e89b-12d3-a456-426655440000"],
|
||||
)
|
||||
score: int = Field(description="Score of the movie", examples=[5], ge=-18, le=33)
|
||||
created_at: Optional[datetime] = Field(default_factory=datetime.utcnow)
|
||||
updated_at: Optional[datetime] = Field(default_factory=datetime.utcnow)
|
||||
|
||||
|
||||
class ScoreDB(ScoreBase):
|
||||
id: Optional[str]
|
||||
|
||||
+10
-1
@@ -1,6 +1,7 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
import bcrypt
|
||||
|
||||
from lib import settings
|
||||
|
||||
@@ -44,9 +45,17 @@ class UserBase(BaseModel):
|
||||
|
||||
class UserIn(UserBase):
|
||||
password: Optional[str] = Field(
|
||||
default=None, description="Password of the user", examples=["password"]
|
||||
default=None,
|
||||
description="Password of the user",
|
||||
examples=[bcrypt.hashpw("password".encode("utf-8"), bcrypt.gensalt())],
|
||||
)
|
||||
|
||||
@property
|
||||
def hashed_password(self):
|
||||
return bcrypt.hashpw(self.password.encode("utf-8"), bcrypt.gensalt()).decode(
|
||||
"utf-8"
|
||||
)
|
||||
|
||||
|
||||
class UserOut(UserBase):
|
||||
pass
|
||||
|
||||
+79
-24
@@ -1,5 +1,18 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
from models import Message
|
||||
from models.user import UserCredentials
|
||||
from models.movie import MovieBase
|
||||
from models.card import Card
|
||||
from lib.database.admin import (
|
||||
add_user,
|
||||
remove_user,
|
||||
add_movie,
|
||||
set_movie_watched,
|
||||
add_card,
|
||||
remove_card,
|
||||
)
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/admin",
|
||||
tags=["admin"],
|
||||
@@ -7,37 +20,79 @@ router = APIRouter(
|
||||
)
|
||||
|
||||
|
||||
@router.post("/user")
|
||||
async def add_user(user: str):
|
||||
"""Add a user to the database"""
|
||||
return {"user": user}
|
||||
@router.post("/user", response_model=Message)
|
||||
async def add_user_endpoint(user: UserCredentials):
|
||||
"""Add a user to the database
|
||||
|
||||
Args:
|
||||
user (UserCredentials): The user to add
|
||||
|
||||
Returns:
|
||||
Message: A message indicating the result of the operation
|
||||
"""
|
||||
return add_user(user)
|
||||
|
||||
|
||||
@router.delete("/user")
|
||||
async def remove_user(user: str):
|
||||
"""Remove a user from the database"""
|
||||
return {"user": user}
|
||||
@router.delete("/user", response_model=Message)
|
||||
async def remove_user_endpoint(user_id: str):
|
||||
"""Remove a user from the database
|
||||
|
||||
Args:
|
||||
user_id (str): The id of the user to remove
|
||||
|
||||
Returns:
|
||||
Message: A message indicating the result of the operation
|
||||
"""
|
||||
return remove_user(user_id)
|
||||
|
||||
|
||||
@router.post("/movie")
|
||||
async def add_movie(movie: str):
|
||||
"""Add a movie to the database"""
|
||||
return {"movie": movie}
|
||||
@router.post("/movie", response_model=Message)
|
||||
async def add_movie_endpoint(movie: MovieBase):
|
||||
"""Add a movie to the database
|
||||
|
||||
Args:
|
||||
movie (MovieBase): The movie to add
|
||||
|
||||
Returns:
|
||||
Message: A message indicating the result of the operation
|
||||
"""
|
||||
return add_movie(movie)
|
||||
|
||||
|
||||
@router.delete("/movie")
|
||||
async def remove_movie(movie: str):
|
||||
"""Remove a movie from the database"""
|
||||
return {"movie": movie}
|
||||
@router.patch("/movie", response_model=Message)
|
||||
async def set_movie_watched_endpoint(movie_id: str):
|
||||
"""Set movie as watched
|
||||
|
||||
Args:
|
||||
movie_id (str): The id of the movie to set as watched
|
||||
|
||||
Returns:
|
||||
Message: A message indicating the result of the operation
|
||||
"""
|
||||
return set_movie_watched(movie_id)
|
||||
|
||||
|
||||
@router.post("/card")
|
||||
async def add_card(card: str):
|
||||
"""Add a card to the database"""
|
||||
return {"card": card}
|
||||
@router.post("/card", response_model=Message)
|
||||
async def add_card_endpoint(card: Card):
|
||||
"""Add a card to the database
|
||||
|
||||
Args:
|
||||
card (Card): The card to add
|
||||
|
||||
Returns:
|
||||
Message: A message indicating the result of the operation
|
||||
"""
|
||||
return add_card(card)
|
||||
|
||||
|
||||
@router.delete("/card")
|
||||
async def remove_card(card: str):
|
||||
"""Remove a card from the database"""
|
||||
return {"card": card}
|
||||
@router.delete("/card", response_model=Message)
|
||||
async def remove_card_endpoint(card_id: str):
|
||||
"""Remove a card from the database
|
||||
|
||||
Args:
|
||||
card_id (str): The id of the card to remove
|
||||
|
||||
Returns:
|
||||
Message: A message indicating the result of the operation
|
||||
"""
|
||||
return remove_card(card_id)
|
||||
|
||||
@@ -1,7 +1,20 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
from models.card import Card
|
||||
from lib.database.card import get_all_cards
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/card",
|
||||
tags=["card"],
|
||||
responses={404: {"description": "Not found"}},
|
||||
)
|
||||
|
||||
|
||||
@router.get("/", responses={200: {"model": list[Card]}})
|
||||
async def get_cards() -> list[Card]:
|
||||
"""Get all cards
|
||||
|
||||
Returns:
|
||||
list[Card]: List of cards
|
||||
"""
|
||||
return get_all_cards()
|
||||
|
||||
+5
-1
@@ -18,7 +18,11 @@ async def get_movies():
|
||||
|
||||
@router.get("/next", response_model=Movie)
|
||||
async def get_next_movie_endpoint():
|
||||
"""Get the next movie to watch"""
|
||||
"""Get the next movie to watch
|
||||
|
||||
Returns:
|
||||
Movie: The next movie to watch
|
||||
"""
|
||||
next_movie = get_next_movie()
|
||||
if next_movie is None:
|
||||
return Response(
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
from models.score import ScoreBase
|
||||
from lib.database.score import set_score
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/score",
|
||||
tags=["score"],
|
||||
@@ -10,3 +13,8 @@ router = APIRouter(
|
||||
@router.get("/leaderboard")
|
||||
async def get_leaderboard():
|
||||
return {"leaderboard": ["user1", "user2", "user3"]}
|
||||
|
||||
|
||||
@router.post("/")
|
||||
async def add_score(score: ScoreBase):
|
||||
return set_score(score)
|
||||
|
||||
+21
-6
@@ -14,11 +14,14 @@ router = APIRouter(
|
||||
|
||||
@router.get(
|
||||
"/",
|
||||
response_model=list[UserOut],
|
||||
responses={200: {"model": UserOut}, 404: {"model": Message}},
|
||||
responses={200: {"model": list[UserOut]}, 404: {"model": Message}},
|
||||
)
|
||||
async def get_users() -> list[UserOut]:
|
||||
"""Get all users"""
|
||||
"""Get all users
|
||||
|
||||
Returns:
|
||||
list[UserOut]: List of users
|
||||
"""
|
||||
return get_all_users()
|
||||
|
||||
|
||||
@@ -29,11 +32,20 @@ async def get_users() -> list[UserOut]:
|
||||
400: {"model": Message},
|
||||
404: {"model": Message},
|
||||
},
|
||||
summary="Login a user",
|
||||
description="Login a user by providing their username and password",
|
||||
)
|
||||
async def login(user: UserCredentials):
|
||||
"""Login a user"""
|
||||
user_from_db: UserIn = get_user_by_username(user.username)
|
||||
"""Login a user by providing their username and password
|
||||
|
||||
Returns:
|
||||
UserOut: User information
|
||||
Response: Error message
|
||||
"""
|
||||
# Get the user from the database
|
||||
user_from_db: UserIn | None = get_user_by_username(user.username)
|
||||
if user_from_db is None:
|
||||
# User not found
|
||||
return Response(
|
||||
status_code=404,
|
||||
media_type="application/json",
|
||||
@@ -41,11 +53,14 @@ async def login(user: UserCredentials):
|
||||
message="User not found", message_type="error"
|
||||
).model_dump_json(),
|
||||
)
|
||||
password = user.password.encode("utf-8")
|
||||
|
||||
# Check the password
|
||||
password = user.password.encode("utf-8")
|
||||
if bcrypt.checkpw(password, user_from_db.password.encode("utf-8")):
|
||||
# Login successful
|
||||
return UserOut(**user_from_db.model_dump())
|
||||
|
||||
# Invalid password
|
||||
return Response(
|
||||
status_code=400,
|
||||
media_type="application/json",
|
||||
|
||||
@@ -217,14 +217,87 @@ wheels = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pjl"
|
||||
version = "0.1.0"
|
||||
name = "numpy"
|
||||
version = "2.1.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/25/ca/1166b75c21abd1da445b97bf1fa2f14f423c6cfb4fc7c4ef31dccf9f6a94/numpy-2.1.3.tar.gz", hash = "sha256:aa08e04e08aaf974d4458def539dece0d28146d866a39da5639596f4921fd761", size = 20166090 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/f0/385eb9970309643cbca4fc6eebc8bb16e560de129c91258dfaa18498da8b/numpy-2.1.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f55ba01150f52b1027829b50d70ef1dafd9821ea82905b63936668403c3b471e", size = 20849658 },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/4a/765b4607f0fecbb239638d610d04ec0a0ded9b4951c56dc68cef79026abf/numpy-2.1.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:13138eadd4f4da03074851a698ffa7e405f41a0845a6b1ad135b81596e4e9958", size = 13492258 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/a7/2332679479c70b68dccbf4a8eb9c9b5ee383164b161bee9284ac141fbd33/numpy-2.1.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:a6b46587b14b888e95e4a24d7b13ae91fa22386c199ee7b418f449032b2fa3b8", size = 5090249 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/67/4aa00316b3b981a822c7a239d3a8135be2a6945d1fd11d0efb25d361711a/numpy-2.1.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:0fa14563cc46422e99daef53d725d0c326e99e468a9320a240affffe87852564", size = 6621704 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/da/1a429ae58b3b6c364eeec93bf044c532f2ff7b48a52e41050896cf15d5b1/numpy-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8637dcd2caa676e475503d1f8fdb327bc495554e10838019651b76d17b98e512", size = 13606089 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/3e/3757f304c704f2f0294a6b8340fcf2be244038be07da4cccf390fa678a9f/numpy-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2312b2aa89e1f43ecea6da6ea9a810d06aae08321609d8dc0d0eda6d946a541b", size = 16043185 },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/97/75329c28fea3113d00c8d2daf9bc5828d58d78ed661d8e05e234f86f0f6d/numpy-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a38c19106902bb19351b83802531fea19dee18e5b37b36454f27f11ff956f7fc", size = 16410751 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/7a/442965e98b34e0ae9da319f075b387bcb9a1e0658276cc63adb8c9686f7b/numpy-2.1.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:02135ade8b8a84011cbb67dc44e07c58f28575cf9ecf8ab304e51c05528c19f0", size = 14082705 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ac/b6/26108cf2cfa5c7e03fb969b595c93131eab4a399762b51ce9ebec2332e80/numpy-2.1.3-cp312-cp312-win32.whl", hash = "sha256:e6988e90fcf617da2b5c78902fe8e668361b43b4fe26dbf2d7b0f8034d4cafb9", size = 6239077 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/84/fa11dad3404b7634aaab50733581ce11e5350383311ea7a7010f464c0170/numpy-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:0d30c543f02e84e92c4b1f415b7c6b5326cbe45ee7882b6b77db7195fb971e3a", size = 12566858 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/0b/620591441457e25f3404c8057eb924d04f161244cb8a3680d529419aa86e/numpy-2.1.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96fe52fcdb9345b7cd82ecd34547fca4321f7656d500eca497eb7ea5a926692f", size = 20836263 },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/e1/210b2d8b31ce9119145433e6ea78046e30771de3fe353f313b2778142f34/numpy-2.1.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f653490b33e9c3a4c1c01d41bc2aef08f9475af51146e4a7710c450cf9761598", size = 13507771 },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/44/aa9ee3caee02fa5a45f2c3b95cafe59c44e4b278fbbf895a93e88b308555/numpy-2.1.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:dc258a761a16daa791081d026f0ed4399b582712e6fc887a95af09df10c5ca57", size = 5075805 },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/d6/61de6e7e31915ba4d87bbe1ae859e83e6582ea14c6add07c8f7eefd8488f/numpy-2.1.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:016d0f6f5e77b0f0d45d77387ffa4bb89816b57c835580c3ce8e099ef830befe", size = 6608380 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/46/48bdf9b7241e317e6cf94276fe11ba673c06d1fdf115d8b4ebf616affd1a/numpy-2.1.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c181ba05ce8299c7aa3125c27b9c2167bca4a4445b7ce73d5febc411ca692e43", size = 13602451 },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/50/73f9a5aa0810cdccda9c1d20be3cbe4a4d6ea6bfd6931464a44c95eef731/numpy-2.1.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5641516794ca9e5f8a4d17bb45446998c6554704d888f86df9b200e66bdcce56", size = 16039822 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/cd/098bc1d5a5bc5307cfc65ee9369d0ca658ed88fbd7307b0d49fab6ca5fa5/numpy-2.1.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ea4dedd6e394a9c180b33c2c872b92f7ce0f8e7ad93e9585312b0c5a04777a4a", size = 16411822 },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/a2/7d4467a2a6d984549053b37945620209e702cf96a8bc658bc04bba13c9e2/numpy-2.1.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0df3635b9c8ef48bd3be5f862cf71b0a4716fa0e702155c45067c6b711ddcef", size = 14079598 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/6a/d64514dcecb2ee70bfdfad10c42b76cab657e7ee31944ff7a600f141d9e9/numpy-2.1.3-cp313-cp313-win32.whl", hash = "sha256:50ca6aba6e163363f132b5c101ba078b8cbd3fa92c7865fd7d4d62d9779ac29f", size = 6236021 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/f9/12297ed8d8301a401e7d8eb6b418d32547f1d700ed3c038d325a605421a4/numpy-2.1.3-cp313-cp313-win_amd64.whl", hash = "sha256:747641635d3d44bcb380d950679462fae44f54b131be347d5ec2bce47d3df9ed", size = 12560405 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/45/7f9244cd792e163b334e3a7f02dff1239d2890b6f37ebf9e82cbe17debc0/numpy-2.1.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:996bb9399059c5b82f76b53ff8bb686069c05acc94656bb259b1d63d04a9506f", size = 20859062 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/b4/a084218e7e92b506d634105b13e27a3a6645312b93e1c699cc9025adb0e1/numpy-2.1.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:45966d859916ad02b779706bb43b954281db43e185015df6eb3323120188f9e4", size = 13515839 },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/45/58ed3f88028dcf80e6ea580311dc3edefdd94248f5770deb980500ef85dd/numpy-2.1.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:baed7e8d7481bfe0874b566850cb0b85243e982388b7b23348c6db2ee2b2ae8e", size = 5116031 },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/a8/eb689432eb977d83229094b58b0f53249d2209742f7de529c49d61a124a0/numpy-2.1.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a9f7f672a3388133335589cfca93ed468509cb7b93ba3105fce780d04a6576a0", size = 6629977 },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/a3/5355ad51ac73c23334c7caaed01adadfda49544f646fcbfbb4331deb267b/numpy-2.1.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7aac50327da5d208db2eec22eb11e491e3fe13d22653dce51b0f4109101b408", size = 13575951 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/70/ea9646d203104e647988cb7d7279f135257a6b7e3354ea6c56f8bafdb095/numpy-2.1.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4394bc0dbd074b7f9b52024832d16e019decebf86caf909d94f6b3f77a8ee3b6", size = 16022655 },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/ce/7fc0612903e91ff9d0b3f2eda4e18ef9904814afcae5b0f08edb7f637883/numpy-2.1.3-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:50d18c4358a0a8a53f12a8ba9d772ab2d460321e6a93d6064fc22443d189853f", size = 16399902 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/62/1d3204313357591c913c32132a28f09a26357e33ea3c4e2fe81269e0dca1/numpy-2.1.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:14e253bd43fc6b37af4921b10f6add6925878a42a0c5fe83daee390bca80bc17", size = 14067180 },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/d7/78a40ed1d80e23a774cb8a34ae8a9493ba1b4271dde96e56ccdbab1620ef/numpy-2.1.3-cp313-cp313t-win32.whl", hash = "sha256:08788d27a5fd867a663f6fc753fd7c3ad7e92747efc73c53bca2f19f8bc06f48", size = 6291907 },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/09/a5ab407bd7f5f5599e6a9261f964ace03a73e7c6928de906981c31c38082/numpy-2.1.3-cp313-cp313t-win_amd64.whl", hash = "sha256:2564fbdf2b99b3f815f2107c1bbc93e2de8ee655a69c261363a1172a79a257d4", size = 12644098 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pandas"
|
||||
version = "2.2.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "numpy" },
|
||||
{ name = "python-dateutil" },
|
||||
{ name = "pytz" },
|
||||
{ name = "tzdata" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9c/d6/9f8431bacc2e19dca897724cd097b1bb224a6ad5433784a44b587c7c13af/pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667", size = 4399213 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/17/a3/fb2734118db0af37ea7433f57f722c0a56687e14b14690edff0cdb4b7e58/pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9", size = 12529893 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/0c/ad295fd74bfac85358fd579e271cded3ac969de81f62dd0142c426b9da91/pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4", size = 11363475 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/2a/4bba3f03f7d07207481fed47f5b35f556c7441acddc368ec43d6643c5777/pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3", size = 15188645 },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/f8/d8fddee9ed0d0c0f4a2132c1dfcf0e3e53265055da8df952a53e7eaf178c/pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319", size = 12739445 },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/e8/45a05d9c39d2cea61ab175dbe6a2de1d05b679e8de2011da4ee190d7e748/pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8", size = 16359235 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/99/617d07a6a5e429ff90c90da64d428516605a1ec7d7bea494235e1c3882de/pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a", size = 14056756 },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/d4/1244ab8edf173a10fd601f7e13b9566c1b525c4f365d6bee918e68381889/pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13", size = 11504248 },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/22/3b8f4e0ed70644e85cfdcd57454686b9057c6c38d2f74fe4b8bc2527214a/pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015", size = 12477643 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/93/b3f5d1838500e22c8d793625da672f3eec046b1a99257666c94446969282/pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28", size = 11281573 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/94/6c79b07f0e5aab1dcfa35a75f4817f5c4f677931d4234afcd75f0e6a66ca/pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0", size = 15196085 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/31/aa8da88ca0eadbabd0a639788a6da13bb2ff6edbbb9f29aa786450a30a91/pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24", size = 12711809 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/7c/c6dbdb0cb2a4344cacfb8de1c5808ca885b2e4dcfde8008266608f9372af/pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659", size = 16356316 },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/b7/8b757e7d92023b832869fa8881a992696a0bfe2e26f72c9ae9f255988d42/pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb", size = 14022055 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/bc/4b18e2b8c002572c5a441a64826252ce5da2aa738855747247a971988043/pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d", size = 11481175 },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/a3/a5d88146815e972d40d19247b2c162e88213ef51c7c25993942c39dbf41d/pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468", size = 12615650 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/8c/f0fd18f6140ddafc0c24122c8a964e48294acc579d47def376fef12bcb4a/pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18", size = 11290177 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/f9/e995754eab9c0f14c6777401f7eece0943840b7a9fc932221c19d1abee9f/pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2", size = 14651526 },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/b0/98d6ae2e1abac4f35230aa756005e8654649d305df9a28b16b9ae4353bff/pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4", size = 11871013 },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/57/0f72a10f9db6a4628744c8e8f0df4e6e21de01212c7c981d31e50ffc8328/pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d", size = 15711620 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/5f/b38085618b950b79d2d9164a711c52b10aefc0ae6833b96f626b7021b2ed/pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a", size = 13098436 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pjl-backend"
|
||||
version = "1.0rc1"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "bcrypt" },
|
||||
{ name = "duckdb" },
|
||||
{ name = "fastapi" },
|
||||
{ name = "loguru" },
|
||||
{ name = "pandas" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "pydantic-settings" },
|
||||
{ name = "requests" },
|
||||
@@ -232,12 +305,18 @@ dependencies = [
|
||||
{ name = "uvicorn" },
|
||||
]
|
||||
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "ruff" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "bcrypt", specifier = ">=4.2.0" },
|
||||
{ name = "duckdb", specifier = ">=1.1.3" },
|
||||
{ name = "fastapi", specifier = ">=0.115.4" },
|
||||
{ name = "loguru", specifier = ">=0.7.2" },
|
||||
{ name = "pandas", specifier = ">=2.2.3" },
|
||||
{ name = "pydantic", specifier = ">=2.9.2" },
|
||||
{ name = "pydantic-settings", specifier = ">=2.6.1" },
|
||||
{ name = "requests", specifier = ">=2.32.3" },
|
||||
@@ -245,6 +324,9 @@ requires-dist = [
|
||||
{ name = "uvicorn", specifier = ">=0.32.0" },
|
||||
]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [{ name = "ruff", specifier = ">=0.8.0" }]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.9.2"
|
||||
@@ -316,6 +398,18 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dateutil"
|
||||
version = "2.9.0.post0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "six" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dotenv"
|
||||
version = "1.0.1"
|
||||
@@ -325,6 +419,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytz"
|
||||
version = "2024.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3a/31/3c70bf7603cc2dca0f19bdc53b4537a797747a58875b552c8c413d963a3f/pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a", size = 319692 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/11/c3/005fcca25ce078d2cc29fd559379817424e94885510568bc1bc53d7d5846/pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725", size = 508002 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.32.3"
|
||||
@@ -353,6 +456,40 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.8.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b2/d6/a2373f3ba7180ddb44420d2a9d1f1510e1a4d162b3d27282bedcb09c8da9/ruff-0.8.0.tar.gz", hash = "sha256:a7ccfe6331bf8c8dad715753e157457faf7351c2b69f62f32c165c2dbcbacd44", size = 3276537 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/77/e889ee3ce7fd8baa3ed1b77a03b9fb8ec1be68be1418261522fd6a5405e0/ruff-0.8.0-py3-none-linux_armv6l.whl", hash = "sha256:fcb1bf2cc6706adae9d79c8d86478677e3bbd4ced796ccad106fd4776d395fea", size = 10518283 },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/c8/0a47de01edf19fb22f5f9b7964f46a68d0bdff20144d134556ffd1ba9154/ruff-0.8.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:295bb4c02d58ff2ef4378a1870c20af30723013f441c9d1637a008baaf928c8b", size = 10317691 },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/17/9885e4a0eeae07abd2a4ebabc3246f556719f24efa477ba2739146c4635a/ruff-0.8.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7b1f1c76b47c18fa92ee78b60d2d20d7e866c55ee603e7d19c1e991fad933a9a", size = 9940999 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/cd/46b6f7043597eb318b5f5482c8ae8f5491cccce771e85f59d23106f2d179/ruff-0.8.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb0d4f250a7711b67ad513fde67e8870109e5ce590a801c3722580fe98c33a99", size = 10772437 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/87/afc95aeb8bc78b1d8a3461717a4419c05aa8aa943d4c9cbd441630f85584/ruff-0.8.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e55cce9aa93c5d0d4e3937e47b169035c7e91c8655b0974e61bb79cf398d49c", size = 10299156 },
|
||||
{ url = "https://files.pythonhosted.org/packages/65/fa/04c647bb809c4d65e8eae1ed1c654d9481b21dd942e743cd33511687b9f9/ruff-0.8.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f4cd64916d8e732ce6b87f3f5296a8942d285bbbc161acee7fe561134af64f9", size = 11325819 },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/26/7dad6e7d833d391a8a1afe4ee70ca6f36c4a297d3cca83ef10e83e9aacf3/ruff-0.8.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c5c1466be2a2ebdf7c5450dd5d980cc87c8ba6976fb82582fea18823da6fa362", size = 12023927 },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/a0/be5296dda6428ba8a13bda8d09fbc0e14c810b485478733886e61597ae2b/ruff-0.8.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2dabfd05b96b7b8f2da00d53c514eea842bff83e41e1cceb08ae1966254a51df", size = 11589702 },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/3f/7602eb11d2886db545834182a9dbe500b8211fcbc9b4064bf9d358bbbbb4/ruff-0.8.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:facebdfe5a5af6b1588a1d26d170635ead6892d0e314477e80256ef4a8470cf3", size = 12782936 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/5d/083181bdec4ec92a431c1291d3fff65eef3ded630a4b55eb735000ef5f3b/ruff-0.8.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87a8e86bae0dbd749c815211ca11e3a7bd559b9710746c559ed63106d382bd9c", size = 11138488 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/23/c12cdef58413cee2436d6a177aa06f7a366ebbca916cf10820706f632459/ruff-0.8.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:85e654f0ded7befe2d61eeaf3d3b1e4ef3894469cd664ffa85006c7720f1e4a2", size = 10744474 },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/61/a12f3b81520083cd7c5caa24ba61bb99fd1060256482eff0ef04cc5ccd1b/ruff-0.8.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:83a55679c4cb449fa527b8497cadf54f076603cc36779b2170b24f704171ce70", size = 10369029 },
|
||||
{ url = "https://files.pythonhosted.org/packages/08/2a/c013f4f3e4a54596c369cee74c24870ed1d534f31a35504908b1fc97017a/ruff-0.8.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:812e2052121634cf13cd6fddf0c1871d0ead1aad40a1a258753c04c18bb71bbd", size = 10867481 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/f7/685b1e1d42a3e94ceb25eab23c70bdd8c0ab66a43121ef83fe6db5a58756/ruff-0.8.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:780d5d8523c04202184405e60c98d7595bdb498c3c6abba3b6d4cdf2ca2af426", size = 11237117 },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/20/401132c0908e8837625e3b7e32df9962e7cd681a4df1e16a10e2a5b4ecda/ruff-0.8.0-py3-none-win32.whl", hash = "sha256:5fdb6efecc3eb60bba5819679466471fd7d13c53487df7248d6e27146e985468", size = 8783511 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/5c/4d800fca7854f62ad77f2c0d99b4b585f03e2d87a6ec1ecea85543a14a3c/ruff-0.8.0-py3-none-win_amd64.whl", hash = "sha256:582891c57b96228d146725975fbb942e1f30a0c4ba19722e692ca3eb25cc9b4f", size = 9559876 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/bc/cc8a6a5ca4960b226dc15dd8fb511dd11f2014ff89d325c0b9b9faa9871f/ruff-0.8.0-py3-none-win_arm64.whl", hash = "sha256:ba93e6294e9a737cd726b74b09a6972e36bb511f9a102f1d9a7e1ce94dd206a6", size = 8939733 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.16.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", size = 34041 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sniffio"
|
||||
version = "1.3.1"
|
||||
@@ -383,6 +520,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tzdata"
|
||||
version = "2024.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e1/34/943888654477a574a86a98e9896bae89c7aa15078ec29f490fef2f1e5384/tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc", size = 193282 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/ab/7e5f53c3b9d14972843a647d8d7a853969a58aecc7559cb3267302c94774/tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd", size = 346586 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.2.3"
|
||||
|
||||
Reference in New Issue
Block a user