Compare commits

...
11 Commits
Author SHA1 Message Date
Esa Kataja 895ac1e276 Version bump 2024-11-20 10:45:02 +02:00
Esa Kataja f8609ac317 Add Poster url and Add pandas 2024-11-17 13:04:24 +02:00
Esa Kataja b32e689d11 ADD Docker support 2024-11-14 15:52:12 +02:00
Esa Kataja 474c677d78 Add Card seed data 2024-11-14 15:05:54 +02:00
Esa Kataja 0ad971cc49 Add Card add remove endpoints 2024-11-09 22:21:44 +02:00
Esa Kataja 6c961db275 Add card endpoint 2024-11-09 22:17:30 +02:00
Esa Kataja c8884ebeec Add movie admin endpoints 2024-11-09 19:36:35 +02:00
Esa Kataja 28d4fe749a Add Messages to response models 2024-11-09 19:36:08 +02:00
Esa Kataja 6f7665c5d1 Add Value sanitation on DB insert 2024-11-09 19:29:40 +02:00
Esa Kataja 2b42be2b64 Change Movie name must be unique 2024-11-09 19:14:57 +02:00
Esa Kataja 2a8595b5fd Add Admin add / remove users endpoints 2024-11-09 19:13:46 +02:00
15 changed files with 382 additions and 54 deletions
+16
View File
@@ -0,0 +1,16 @@
FROM ghcr.io/astral-sh/uv:python3.12-alpine
WORKDIR /app
ENV UV_COMPILE_BYTECODE=1
ADD ./pyproject.toml .
ADD ./uv.lock .
ADD ./src .
RUN uv sync --frozen --no-dev
ENV PATH="/app/.venv/bin:$PATH"
ENTRYPOINT []
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
+8
View File
@@ -0,0 +1,8 @@
services:
web:
# Build the image from the Dockerfile in the current directory
build: .
# Host the FastAPI application on port 8000
ports:
- "8000:8000"
+2 -1
View File
@@ -1,6 +1,6 @@
[project] [project]
name = "pjl" name = "pjl"
version = "0.1.0" version = "0.9"
description = "Add your description here" description = "Add your description here"
readme = "README.md" readme = "README.md"
requires-python = ">=3.12" requires-python = ">=3.12"
@@ -14,4 +14,5 @@ dependencies = [
"requests>=2.32.3", "requests>=2.32.3",
"rich>=13.9.4", "rich>=13.9.4",
"uvicorn>=0.32.0", "uvicorn>=0.32.0",
"pandas>=2.2.3",
] ]
+1
View File
@@ -15,6 +15,7 @@ app = FastAPI(
title="Paska joululeffa 2024", title="Paska joululeffa 2024",
name="Paska joululeffa 2024", name="Paska joululeffa 2024",
description="API for Paska joululeffa 2024", description="API for Paska joululeffa 2024",
version="0.9",
) )
app.add_middleware( app.add_middleware(
+90
View File
@@ -0,0 +1,90 @@
from lib.database.database import db_run
from models import Message
from models.user import UserCredentials
from models.movie import Movie
from models.card import Card
import bcrypt
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:
return Message(message="User already exists", message_type="error")
return Message(message="User added successfully", message_type="success")
def remove_user(user_id: str):
sql = f"DELETE FROM users WHERE id = '{user_id}'"
try:
db_run(sql)
except:
return Message(message="User not found", message_type="error")
return Message(message="User removed successfully", message_type="success")
def add_movie(movie: Movie):
sql = "INSERT INTO movies (name, imdb_id, actors, release_date, plot, showtime) VALUES (?, ?, ?, ?, ?, ?)"
try:
db_run(
sql,
(
movie.name,
movie.imdb_id,
movie.actors,
movie.release_date,
movie.plot,
movie.showtime,
),
)
except:
return Message(message="Failed to add movie", message_type="error")
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:
return Message(message="Failed to set movie as watched", 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}"}
+20
View File
@@ -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
+2 -2
View File
@@ -15,9 +15,9 @@ def get_db():
conn.close() conn.close()
def db_run(sql): def db_run(sql, values: tuple = None):
with get_db() as conn: with get_db() as conn:
result = conn.execute(sql) result = conn.execute(sql, values)
return result return result
+43 -5
View File
@@ -13,11 +13,12 @@ CREATE TABLE users (
CREATE TABLE movies ( CREATE TABLE movies (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY, id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
name TEXT NOT NULL, name TEXT UNIQUE NOT NULL,
imdb_id TEXT UNIQUE, imdb_id TEXT UNIQUE,
actors TEXT[], actors TEXT[],
release_date DATE DEFAULT NULL, release_date DATE DEFAULT NULL,
plot TEXT, plot TEXT DEFAULT NULL,
poster_url TEXT DEFAULT NULL,
showtime DATE DEFAULT NULL, showtime DATE DEFAULT NULL,
is_watched BOOLEAN DEFAULT FALSE, is_watched BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
@@ -52,11 +53,48 @@ CREATE TABLE log (
); );
-- Add placeholder data -- Add placeholder data
INSERT INTO movies (name, imdb_id, actors, release_date, plot, showtime) INSERT INTO movies (name, imdb_id, actors, release_date, plot, poster_url, showtime)
VALUES ('A Maple Valley Christmas', VALUES ('A Maple Valley Christmas',
'tt21841642', 'tt21841642',
['Peyton List', 'Andrew W. Walker', 'Frances Flanagan'], ['Peyton List', 'Andrew W. Walker', 'Frances Flanagan'],
'2022-11-05', '2022-11-05',
'Erica is a rancher who has spent her whole life working the family farm with her mother and sister. 'Erica on karjatilallinen, joka on viettänyt koko elämänsä työskennellen perhetilalla äitinsä ja sisarensa kanssa.
When Aaron arrives and disrupts her plans, she starts to question what it is she actually wants.', Kun Aaron saapuu ja häiritsee hänen suunnitelmiaan, hän alkaa kyseenalaistaa, mitä hän oikeastaan haluaa.',
'https://m.media-amazon.com/images/M/MV5BZjliMTRlMGItOGQzZC00NTI2LWJkODctZWZmMDIzOGRmYTQ3XkEyXkFqcGc@._V1_.jpg',
'2024-12-01'); '2024-12-01');
INSERT INTO cards (title, description, point_value)
VALUES
('Punainen villakangastakki', 'Päähenkilöllä on aina kirkkaanpunainen, täydellisesti istuva talvitakki.', 1),
('Keskeytetty suudelma', 'Juuri, kun romanttinen hetki on käsillä, jokin keskeyttää ensisuudelman.', 1),
('Miespääosa yh', 'Miespäähenkilö on yksinhuoltaja, joka tasapainoilee uran ja lapsenhoidon välillä.', 2),
('Leskimies', 'Päähenkilö on menettänyt puolisonsa ja yrittää nyt toipua elämässään.', 2),
('Jouluoksennus', 'Koti, työpaikka tai jokin muu koristeltu liioitellusti jouluteemalla, ilman mitään hillittyä tyylitajua.', 1),
('Evergreen', 'Tarinan kylä tai kaupunki on idyllinen paikka, nimeltään jotain jouluista, kuten ''Evergreen'' tai ''Snowville''.', 3),
('Yritys pulassa', 'Päähenkilön yritys on konkurssin partaalla tai vaarassa tulla ostetuksi.', 2),
('Naispääosa pitkä blondi', 'Naispääosalla pitkät blondit hiukset.', 1),
('Joulumörkö', 'Hahmo, joka vihaa joulua, mutta päätyy lopulta rakastamaan sitä.', 1),
('Pääosa jumissa', 'Päähenkilö jää jumiin esimerkiksi kaukaiseen kylään tai pikkukaupunkiin.', 1),
('Kantakahvila', 'Pieni, kodikas kahvila toimii kaikkien tapaamispaikkana ja keskustelujen keskuksena.', 2),
('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),
('Työkriisi', 'Päähenkilön työelämässä on kriisi, joka vaatii kaiken huomion joulun alla.', 1),
('Kylän joulutapahtuma', 'Joulukulkue, jouluvalotapahtuma tai kuusen valaiseminen, jossa kaikki kokoontuvat yhteen.', 1),
('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),
('Suuri salaisuus', 'Joku hahmo kantaa suurta salaisuutta, joka paljastuu juuri oikealla hetkellä.', 2),
('Himo jouluttaja', 'Yksi hahmoista on ylitsepursuava jouluihminen, jolla on täydellisesti koristeltu koti.', 1),
('Kuusen valinta', 'Kohtaus, jossa kuusen valinnasta tehdään koko tarinan käännekohta.', 2),
('Täysikuu', 'Jouluyön täysikuu valaisee maiseman ja tuo taianomaisen tunnelman.', 3),
('Kuuma kaakao', 'Kuumaa kaakaota juodaan lukemattomia kertoja, aina kermavaahdon kera.', 2),
('Punainen lava-auto', 'Joku hahmo ajaa punaisella lava-autolla, usein ilman mitään käytännön syytä.', 1),
('Lumiukko', 'Lumiukon rakentaminen on täydellisen joulupäivän kohokohta.', 3),
('Hengailua kirjastossa', 'Pieni kirjakauppa tai kirjasto on keskeinen kohtauspaikka ja keskustelujen paikka.', 3),
('Jouluenkeli', 'Jouluenkeli joko koristeena tai todellisena ilmestyksenä ohjaa hahmoja oikealle tielle.', 3),
('Lumipyry', 'Suuri lumipyry eristää päähenkilöt mökkiin tai kylään.', 3),
('Yllättävä vieras', 'Joulun alla joku saapuu yllättäen perhejuhliin tai kylään.', 3),
('Vanha kirje', 'Päähenkilö löytää vanhan kirjeen, joka muuttaa hänen elämänsä.', 3),
('Pakollinen luistelukohtaus', 'Pari luisteluhetkellä, jonka aikana tunteet alkavat syttyä.', 2),
('Vanhan parin rakkausneuvo', 'Iäkäs pariskunta antaa päähenkilölle neuvoja rakkaudesta.', 3),
('Ensilumi', 'Juuri kun kaikki toivo on menetetty, ensilumi alkaa sataa.', 3),
('Lapsen joulutoive', 'Pieni lapsi toivoo joululta jotain suurta, yleensä perheeseen liittyvää.', 3);
+6 -16
View File
@@ -27,25 +27,15 @@ def get_all_movies() -> list[Movie]:
return return
def get_next_movie() -> Movie: def get_next_movie() -> Movie | None:
sql = "SELECT * FROM movies WHERE is_watched = FALSE AND showtime > CURRENT_TIMESTAMP ORDER BY showtime ASC" sql = "SELECT * FROM movies WHERE is_watched = FALSE AND showtime > CURRENT_TIMESTAMP ORDER BY showtime ASC"
with get_db() as conn: with get_db() as conn:
result = conn.execute(sql).fetchone() result = conn.execute(sql).fetchdf()
if result is None: if result.empty:
return None return None
movie = Movie( response_data = result.to_dict(orient="records")[0]
id=str(result[0]), response_data["id"] = str(response_data["id"])
name=result[1], movie = Movie(**response_data)
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],
)
return movie return movie
return return
+25 -8
View File
@@ -1,12 +1,29 @@
from sqlmodel import Field, SQLModel from pydantic import BaseModel, Field
from datetime import datetime from datetime import datetime
from typing import Optional from typing import Optional
class Card(SQLModel, table=True): class Card(BaseModel):
id: Optional[int] = Field(default=None, primary_key=True) id: Optional[str] = Field(
title: str default=None,
description: str description="UUID representation of the card",
score: int examples=["123e4567-e89b-12d3-a456-426655440000"],
created_at: datetime = Field(default_factory=datetime.utcnow) )
updated_at: datetime = Field(default_factory=datetime.utcnow) 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()],
)
+3
View File
@@ -21,6 +21,9 @@ class Movie(BaseModel):
release_date: datetime = Field( release_date: datetime = Field(
description="Release date of the movie", examples=["2022-11-05"] description="Release date of the movie", examples=["2022-11-05"]
) )
poster_url: str = Field(
description="Poster URL of the movie", examples=["https://..."]
)
showtime: datetime = Field( showtime: datetime = Field(
description="Showtime of the movie", examples=["2024-12-01"] description="Showtime of the movie", examples=["2024-12-01"]
) )
+10 -1
View File
@@ -1,6 +1,7 @@
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from datetime import datetime from datetime import datetime
from typing import Optional from typing import Optional
import bcrypt
from lib import settings from lib import settings
@@ -44,9 +45,17 @@ class UserBase(BaseModel):
class UserIn(UserBase): class UserIn(UserBase):
password: Optional[str] = Field( 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): class UserOut(UserBase):
pass pass
+32 -19
View File
@@ -1,5 +1,18 @@
from fastapi import APIRouter from fastapi import APIRouter
from models import Message
from models.user import UserCredentials
from models.movie import Movie
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( router = APIRouter(
prefix="/admin", prefix="/admin",
tags=["admin"], tags=["admin"],
@@ -7,37 +20,37 @@ router = APIRouter(
) )
@router.post("/user") @router.post("/user", response_model=Message)
async def add_user(user: str): async def add_user_endpoint(user: UserCredentials):
"""Add a user to the database""" """Add a user to the database"""
return {"user": user} return add_user(user)
@router.delete("/user") @router.delete("/user", response_model=Message)
async def remove_user(user: str): async def remove_user_endpoint(user_id: str):
"""Remove a user from the database""" """Remove a user from the database"""
return {"user": user} return remove_user(user_id)
@router.post("/movie") @router.post("/movie", response_model=Message)
async def add_movie(movie: str): async def add_movie_endpoint(movie: Movie):
"""Add a movie to the database""" """Add a movie to the database"""
return {"movie": movie} return add_movie(movie)
@router.delete("/movie") @router.patch("/movie", response_model=Message)
async def remove_movie(movie: str): async def set_movie_watched_endpoint(movie_id: str):
"""Remove a movie from the database""" """Set movie as watched"""
return {"movie": movie} return set_movie_watched(movie_id)
@router.post("/card") @router.post("/card", response_model=Message)
async def add_card(card: str): async def add_card_endpoint(card: Card):
"""Add a card to the database""" """Add a card to the database"""
return {"card": card} return add_card(card)
@router.delete("/card") @router.delete("/card", response_model=Message)
async def remove_card(card: str): async def remove_card_endpoint(card_id: str):
"""Remove a card from the database""" """Remove a card from the database"""
return {"card": card} return remove_card(card_id)
+9
View File
@@ -1,7 +1,16 @@
from fastapi import APIRouter from fastapi import APIRouter
from models.card import Card
from lib.database.card import get_all_cards
router = APIRouter( router = APIRouter(
prefix="/card", prefix="/card",
tags=["card"], tags=["card"],
responses={404: {"description": "Not found"}}, responses={404: {"description": "Not found"}},
) )
@router.get("/", response_model=list[Card])
async def get_cards() -> list[Card]:
"""Get all cards"""
return get_all_cards()
Generated
+114 -1
View File
@@ -216,15 +216,88 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 },
] ]
[[package]]
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]] [[package]]
name = "pjl" name = "pjl"
version = "0.1.0" version = "0.9"
source = { virtual = "." } source = { virtual = "." }
dependencies = [ dependencies = [
{ name = "bcrypt" }, { name = "bcrypt" },
{ name = "duckdb" }, { name = "duckdb" },
{ name = "fastapi" }, { name = "fastapi" },
{ name = "loguru" }, { name = "loguru" },
{ name = "pandas" },
{ name = "pydantic" }, { name = "pydantic" },
{ name = "pydantic-settings" }, { name = "pydantic-settings" },
{ name = "requests" }, { name = "requests" },
@@ -238,6 +311,7 @@ requires-dist = [
{ name = "duckdb", specifier = ">=1.1.3" }, { name = "duckdb", specifier = ">=1.1.3" },
{ name = "fastapi", specifier = ">=0.115.4" }, { name = "fastapi", specifier = ">=0.115.4" },
{ name = "loguru", specifier = ">=0.7.2" }, { name = "loguru", specifier = ">=0.7.2" },
{ name = "pandas", specifier = ">=2.2.3" },
{ name = "pydantic", specifier = ">=2.9.2" }, { name = "pydantic", specifier = ">=2.9.2" },
{ name = "pydantic-settings", specifier = ">=2.6.1" }, { name = "pydantic-settings", specifier = ">=2.6.1" },
{ name = "requests", specifier = ">=2.32.3" }, { name = "requests", specifier = ">=2.32.3" },
@@ -316,6 +390,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513 }, { 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]] [[package]]
name = "python-dotenv" name = "python-dotenv"
version = "1.0.1" version = "1.0.1"
@@ -325,6 +411,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 }, { 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]] [[package]]
name = "requests" name = "requests"
version = "2.32.3" version = "2.32.3"
@@ -353,6 +448,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424 }, { url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424 },
] ]
[[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]] [[package]]
name = "sniffio" name = "sniffio"
version = "1.3.1" version = "1.3.1"
@@ -383,6 +487,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 }, { 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]] [[package]]
name = "urllib3" name = "urllib3"
version = "2.2.3" version = "2.2.3"