chore: 오류 수정, 실행 시 alembic 적용

This commit is contained in:
zephyrdark 2026-02-07 10:25:14 +09:00
parent d6f7d4a307
commit 1dcb381a36
9 changed files with 368 additions and 87 deletions

View File

@ -30,5 +30,5 @@ EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1
# Run the application
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
# Run migrations and start the application
CMD ["sh", "-c", "alembic upgrade head && uvicorn app.main:app --host 0.0.0.0 --port 8000"]

View File

@ -19,7 +19,8 @@ config = context.config
# Override sqlalchemy.url with environment variable if available
database_url = os.getenv("DATABASE_URL")
if database_url:
config.set_main_option("sqlalchemy.url", database_url)
# Escape % for ConfigParser interpolation
config.set_main_option("sqlalchemy.url", database_url.replace("%", "%%"))
# Interpret the config file for Python logging.
if config.config_file_name is not None:

View File

@ -0,0 +1,270 @@
"""initial
Revision ID: 882512221354
Revises:
Create Date: 2026-02-06 22:48:52.480626
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '882512221354'
down_revision: Union[str, None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('etf_prices',
sa.Column('ticker', sa.String(length=20), nullable=False),
sa.Column('date', sa.Date(), nullable=False),
sa.Column('close', sa.Numeric(precision=12, scale=2), nullable=False),
sa.Column('nav', sa.Numeric(precision=12, scale=2), nullable=True),
sa.Column('volume', sa.BigInteger(), nullable=True),
sa.PrimaryKeyConstraint('ticker', 'date')
)
op.create_table('etfs',
sa.Column('ticker', sa.String(length=20), nullable=False),
sa.Column('name', sa.String(length=100), nullable=False),
sa.Column('asset_class', sa.Enum('EQUITY', 'BOND', 'GOLD', 'MIXED', name='assetclass'), nullable=False),
sa.Column('market', sa.String(length=20), nullable=False),
sa.Column('expense_ratio', sa.Numeric(precision=5, scale=4), nullable=True),
sa.PrimaryKeyConstraint('ticker')
)
op.create_table('financials',
sa.Column('ticker', sa.String(length=20), nullable=False),
sa.Column('base_date', sa.Date(), nullable=False),
sa.Column('report_type', sa.Enum('ANNUAL', 'QUARTERLY', name='reporttype'), nullable=False),
sa.Column('account', sa.String(length=50), nullable=False),
sa.Column('value', sa.Numeric(precision=20, scale=2), nullable=True),
sa.PrimaryKeyConstraint('ticker', 'base_date', 'report_type', 'account')
)
op.create_table('job_logs',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('job_name', sa.String(length=50), nullable=False),
sa.Column('status', sa.String(length=20), nullable=False),
sa.Column('started_at', sa.DateTime(), nullable=True),
sa.Column('finished_at', sa.DateTime(), nullable=True),
sa.Column('records_count', sa.Integer(), nullable=True),
sa.Column('error_msg', sa.Text(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_job_logs_id'), 'job_logs', ['id'], unique=False)
op.create_table('prices',
sa.Column('ticker', sa.String(length=20), nullable=False),
sa.Column('date', sa.Date(), nullable=False),
sa.Column('open', sa.Numeric(precision=12, scale=2), nullable=False),
sa.Column('high', sa.Numeric(precision=12, scale=2), nullable=False),
sa.Column('low', sa.Numeric(precision=12, scale=2), nullable=False),
sa.Column('close', sa.Numeric(precision=12, scale=2), nullable=False),
sa.Column('volume', sa.BigInteger(), nullable=False),
sa.PrimaryKeyConstraint('ticker', 'date')
)
op.create_table('sectors',
sa.Column('ticker', sa.String(length=20), nullable=False),
sa.Column('sector_code', sa.String(length=10), nullable=False),
sa.Column('company_name', sa.String(length=100), nullable=False),
sa.Column('sector_name', sa.String(length=50), nullable=False),
sa.Column('base_date', sa.Date(), nullable=False),
sa.PrimaryKeyConstraint('ticker')
)
op.create_table('stocks',
sa.Column('ticker', sa.String(length=20), nullable=False),
sa.Column('name', sa.String(length=100), nullable=False),
sa.Column('market', sa.String(length=20), nullable=False),
sa.Column('close_price', sa.Numeric(precision=12, scale=2), nullable=True),
sa.Column('market_cap', sa.BigInteger(), nullable=True),
sa.Column('eps', sa.Numeric(precision=12, scale=2), nullable=True),
sa.Column('forward_eps', sa.Numeric(precision=12, scale=2), nullable=True),
sa.Column('bps', sa.Numeric(precision=12, scale=2), nullable=True),
sa.Column('dividend_per_share', sa.Numeric(precision=12, scale=2), nullable=True),
sa.Column('stock_type', sa.Enum('COMMON', 'SPAC', 'PREFERRED', 'REIT', 'OTHER', name='stocktype'), nullable=True),
sa.Column('base_date', sa.Date(), nullable=False),
sa.PrimaryKeyConstraint('ticker')
)
op.create_table('users',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('username', sa.String(length=50), nullable=False),
sa.Column('email', sa.String(length=100), nullable=False),
sa.Column('hashed_password', sa.String(length=255), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_users_email'), 'users', ['email'], unique=True)
op.create_index(op.f('ix_users_id'), 'users', ['id'], unique=False)
op.create_index(op.f('ix_users_username'), 'users', ['username'], unique=True)
op.create_table('valuations',
sa.Column('ticker', sa.String(length=20), nullable=False),
sa.Column('base_date', sa.Date(), nullable=False),
sa.Column('per', sa.Numeric(precision=10, scale=2), nullable=True),
sa.Column('pbr', sa.Numeric(precision=10, scale=2), nullable=True),
sa.Column('psr', sa.Numeric(precision=10, scale=2), nullable=True),
sa.Column('pcr', sa.Numeric(precision=10, scale=2), nullable=True),
sa.Column('dividend_yield', sa.Numeric(precision=6, scale=2), nullable=True),
sa.PrimaryKeyConstraint('ticker', 'base_date')
)
op.create_table('backtests',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.Column('strategy_type', sa.String(length=50), nullable=False),
sa.Column('strategy_params', sa.JSON(), nullable=True),
sa.Column('start_date', sa.Date(), nullable=False),
sa.Column('end_date', sa.Date(), nullable=False),
sa.Column('rebalance_period', sa.Enum('MONTHLY', 'QUARTERLY', 'SEMI_ANNUAL', 'ANNUAL', name='rebalanceperiod'), nullable=True),
sa.Column('initial_capital', sa.Numeric(precision=20, scale=2), nullable=False),
sa.Column('commission_rate', sa.Numeric(precision=10, scale=6), nullable=True),
sa.Column('slippage_rate', sa.Numeric(precision=10, scale=6), nullable=True),
sa.Column('benchmark', sa.String(length=20), nullable=True),
sa.Column('top_n', sa.Integer(), nullable=True),
sa.Column('status', sa.Enum('PENDING', 'RUNNING', 'COMPLETED', 'FAILED', name='backteststatus'), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('completed_at', sa.DateTime(), nullable=True),
sa.Column('error_message', sa.Text(), nullable=True),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_backtests_id'), 'backtests', ['id'], unique=False)
op.create_table('portfolios',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=100), nullable=False),
sa.Column('portfolio_type', sa.Enum('PENSION', 'GENERAL', name='portfoliotype'), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_portfolios_id'), 'portfolios', ['id'], unique=False)
op.create_table('backtest_equity_curve',
sa.Column('backtest_id', sa.Integer(), nullable=False),
sa.Column('date', sa.Date(), nullable=False),
sa.Column('portfolio_value', sa.Numeric(precision=20, scale=2), nullable=True),
sa.Column('benchmark_value', sa.Numeric(precision=20, scale=2), nullable=True),
sa.Column('drawdown', sa.Numeric(precision=10, scale=4), nullable=True),
sa.ForeignKeyConstraint(['backtest_id'], ['backtests.id'], ),
sa.PrimaryKeyConstraint('backtest_id', 'date')
)
op.create_table('backtest_holdings',
sa.Column('backtest_id', sa.Integer(), nullable=False),
sa.Column('rebalance_date', sa.Date(), nullable=False),
sa.Column('ticker', sa.String(length=20), nullable=False),
sa.Column('name', sa.String(length=100), nullable=True),
sa.Column('weight', sa.Numeric(precision=10, scale=4), nullable=True),
sa.Column('shares', sa.Integer(), nullable=True),
sa.Column('price', sa.Numeric(precision=12, scale=2), nullable=True),
sa.ForeignKeyConstraint(['backtest_id'], ['backtests.id'], ),
sa.PrimaryKeyConstraint('backtest_id', 'rebalance_date', 'ticker')
)
op.create_table('backtest_results',
sa.Column('backtest_id', sa.Integer(), nullable=False),
sa.Column('total_return', sa.Numeric(precision=10, scale=4), nullable=True),
sa.Column('cagr', sa.Numeric(precision=10, scale=4), nullable=True),
sa.Column('mdd', sa.Numeric(precision=10, scale=4), nullable=True),
sa.Column('sharpe_ratio', sa.Numeric(precision=10, scale=4), nullable=True),
sa.Column('volatility', sa.Numeric(precision=10, scale=4), nullable=True),
sa.Column('benchmark_return', sa.Numeric(precision=10, scale=4), nullable=True),
sa.Column('excess_return', sa.Numeric(precision=10, scale=4), nullable=True),
sa.ForeignKeyConstraint(['backtest_id'], ['backtests.id'], ),
sa.PrimaryKeyConstraint('backtest_id')
)
op.create_table('backtest_transactions',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('backtest_id', sa.Integer(), nullable=False),
sa.Column('date', sa.Date(), nullable=False),
sa.Column('ticker', sa.String(length=20), nullable=False),
sa.Column('action', sa.String(length=10), nullable=False),
sa.Column('shares', sa.Integer(), nullable=False),
sa.Column('price', sa.Numeric(precision=12, scale=2), nullable=False),
sa.Column('commission', sa.Numeric(precision=12, scale=2), nullable=False),
sa.ForeignKeyConstraint(['backtest_id'], ['backtests.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_backtest_transactions_id'), 'backtest_transactions', ['id'], unique=False)
op.create_table('holdings',
sa.Column('portfolio_id', sa.Integer(), nullable=False),
sa.Column('ticker', sa.String(length=20), nullable=False),
sa.Column('quantity', sa.Integer(), nullable=False),
sa.Column('avg_price', sa.Numeric(precision=12, scale=2), nullable=False),
sa.ForeignKeyConstraint(['portfolio_id'], ['portfolios.id'], ),
sa.PrimaryKeyConstraint('portfolio_id', 'ticker')
)
op.create_table('portfolio_snapshots',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('portfolio_id', sa.Integer(), nullable=False),
sa.Column('total_value', sa.Numeric(precision=15, scale=2), nullable=False),
sa.Column('snapshot_date', sa.Date(), nullable=False),
sa.ForeignKeyConstraint(['portfolio_id'], ['portfolios.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_portfolio_snapshots_id'), 'portfolio_snapshots', ['id'], unique=False)
op.create_table('targets',
sa.Column('portfolio_id', sa.Integer(), nullable=False),
sa.Column('ticker', sa.String(length=20), nullable=False),
sa.Column('target_ratio', sa.Numeric(precision=5, scale=2), nullable=False),
sa.ForeignKeyConstraint(['portfolio_id'], ['portfolios.id'], ),
sa.PrimaryKeyConstraint('portfolio_id', 'ticker')
)
op.create_table('transactions',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('portfolio_id', sa.Integer(), nullable=False),
sa.Column('ticker', sa.String(length=20), nullable=False),
sa.Column('tx_type', sa.Enum('BUY', 'SELL', name='transactiontype'), nullable=False),
sa.Column('quantity', sa.Integer(), nullable=False),
sa.Column('price', sa.Numeric(precision=12, scale=2), nullable=False),
sa.Column('executed_at', sa.DateTime(), nullable=False),
sa.Column('memo', sa.Text(), nullable=True),
sa.ForeignKeyConstraint(['portfolio_id'], ['portfolios.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_transactions_id'), 'transactions', ['id'], unique=False)
op.create_table('snapshot_holdings',
sa.Column('snapshot_id', sa.Integer(), nullable=False),
sa.Column('ticker', sa.String(length=20), nullable=False),
sa.Column('quantity', sa.Integer(), nullable=False),
sa.Column('price', sa.Numeric(precision=12, scale=2), nullable=False),
sa.Column('value', sa.Numeric(precision=15, scale=2), nullable=False),
sa.Column('current_ratio', sa.Numeric(precision=5, scale=2), nullable=False),
sa.ForeignKeyConstraint(['snapshot_id'], ['portfolio_snapshots.id'], ),
sa.PrimaryKeyConstraint('snapshot_id', 'ticker')
)
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('snapshot_holdings')
op.drop_index(op.f('ix_transactions_id'), table_name='transactions')
op.drop_table('transactions')
op.drop_table('targets')
op.drop_index(op.f('ix_portfolio_snapshots_id'), table_name='portfolio_snapshots')
op.drop_table('portfolio_snapshots')
op.drop_table('holdings')
op.drop_index(op.f('ix_backtest_transactions_id'), table_name='backtest_transactions')
op.drop_table('backtest_transactions')
op.drop_table('backtest_results')
op.drop_table('backtest_holdings')
op.drop_table('backtest_equity_curve')
op.drop_index(op.f('ix_portfolios_id'), table_name='portfolios')
op.drop_table('portfolios')
op.drop_index(op.f('ix_backtests_id'), table_name='backtests')
op.drop_table('backtests')
op.drop_table('valuations')
op.drop_index(op.f('ix_users_username'), table_name='users')
op.drop_index(op.f('ix_users_id'), table_name='users')
op.drop_index(op.f('ix_users_email'), table_name='users')
op.drop_table('users')
op.drop_table('stocks')
op.drop_table('sectors')
op.drop_table('prices')
op.drop_index(op.f('ix_job_logs_id'), table_name='job_logs')
op.drop_table('job_logs')
op.drop_table('financials')
op.drop_table('etfs')
op.drop_table('etf_prices')
# ### end Alembic commands ###

View File

@ -13,7 +13,7 @@ from sqlalchemy.pool import StaticPool
from app.main import app
from app.core.database import Base, get_db
from app.models.user import User
from app.core.auth import get_password_hash, create_access_token
from app.core.security import get_password_hash, create_access_token
# Use in-memory SQLite for tests
@ -71,7 +71,6 @@ def test_user(db: Session) -> User:
username="testuser",
email="test@example.com",
hashed_password=get_password_hash("testpassword"),
is_admin=False,
)
db.add(user)
db.commit()
@ -86,7 +85,6 @@ def admin_user(db: Session) -> User:
username="admin",
email="admin@example.com",
hashed_password=get_password_hash("adminpassword"),
is_admin=True,
)
db.add(user)
db.commit()

View File

@ -76,7 +76,7 @@ def test_targets_flow(client: TestClient, auth_headers):
assert response.status_code == 200
result = response.json()
assert len(result) == 3
assert sum(t["target_ratio"] for t in result) == 100
assert sum(float(t["target_ratio"]) for t in result) == 100
# Get targets
response = client.get(

View File

@ -10,14 +10,16 @@ def test_multi_factor_strategy(client: TestClient, auth_headers):
response = client.post(
"/api/strategy/multi-factor",
json={
"market": "KOSPI",
"min_market_cap": 100000000000,
"universe": {
"markets": ["KOSPI"],
"min_market_cap": 10000, # 1조원 (억원 단위)
},
"top_n": 20,
"weights": {
"value": 0.3,
"quality": 0.3,
"momentum": 0.2,
"f_score": 0.2,
"low_vol": 0.2,
},
},
headers=auth_headers,
@ -28,8 +30,8 @@ def test_multi_factor_strategy(client: TestClient, auth_headers):
if response.status_code == 200:
data = response.json()
assert "stocks" in data
assert "strategy_type" in data
assert data["strategy_type"] == "multi_factor"
assert "strategy_name" in data
assert data["strategy_name"] == "multi_factor"
def test_quality_strategy(client: TestClient, auth_headers):
@ -37,10 +39,12 @@ def test_quality_strategy(client: TestClient, auth_headers):
response = client.post(
"/api/strategy/quality",
json={
"market": "KOSPI",
"min_market_cap": 100000000000,
"universe": {
"markets": ["KOSPI"],
"min_market_cap": 10000,
},
"top_n": 20,
"min_f_score": 6,
"min_fscore": 6,
},
headers=auth_headers,
)
@ -49,7 +53,7 @@ def test_quality_strategy(client: TestClient, auth_headers):
if response.status_code == 200:
data = response.json()
assert "stocks" in data
assert data["strategy_type"] == "quality"
assert data["strategy_name"] == "quality"
def test_value_momentum_strategy(client: TestClient, auth_headers):
@ -57,8 +61,10 @@ def test_value_momentum_strategy(client: TestClient, auth_headers):
response = client.post(
"/api/strategy/value-momentum",
json={
"market": "KOSPI",
"min_market_cap": 100000000000,
"universe": {
"markets": ["KOSPI"],
"min_market_cap": 10000,
},
"top_n": 20,
"value_weight": 0.5,
"momentum_weight": 0.5,
@ -70,7 +76,7 @@ def test_value_momentum_strategy(client: TestClient, auth_headers):
if response.status_code == 200:
data = response.json()
assert "stocks" in data
assert data["strategy_type"] == "value_momentum"
assert data["strategy_name"] == "value_momentum"
def test_strategy_requires_auth(client: TestClient):
@ -78,8 +84,9 @@ def test_strategy_requires_auth(client: TestClient):
response = client.post(
"/api/strategy/multi-factor",
json={
"market": "KOSPI",
"min_market_cap": 100000000000,
"universe": {
"markets": ["KOSPI"],
},
"top_n": 20,
},
)

119
backend/uv.lock generated
View File

@ -54,68 +54,21 @@ wheels = [
[[package]]
name = "bcrypt"
version = "5.0.0"
version = "4.0.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d4/36/3329e2518d70ad8e2e5817d5a4cac6bba05a47767ec416c7d020a965f408/bcrypt-5.0.0.tar.gz", hash = "sha256:f748f7c2d6fd375cc93d3fba7ef4a9e3a092421b8dbf34d8d4dc06be9492dfdd", size = 25386, upload-time = "2025-09-25T19:50:47.829Z" }
sdist = { url = "https://files.pythonhosted.org/packages/8c/ae/3af7d006aacf513975fd1948a6b4d6f8b4a307f8a244e1a3d3774b297aad/bcrypt-4.0.1.tar.gz", hash = "sha256:27d375903ac8261cfe4047f6709d16f7d18d39b1ec92aaf72af989552a650ebd", size = 25498, upload-time = "2022-10-09T15:36:49.775Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/13/85/3e65e01985fddf25b64ca67275bb5bdb4040bd1a53b66d355c6c37c8a680/bcrypt-5.0.0-cp313-cp313t-macosx_10_12_universal2.whl", hash = "sha256:f3c08197f3039bec79cee59a606d62b96b16669cff3949f21e74796b6e3cd2be", size = 481806, upload-time = "2025-09-25T19:49:05.102Z" },
{ url = "https://files.pythonhosted.org/packages/44/dc/01eb79f12b177017a726cbf78330eb0eb442fae0e7b3dfd84ea2849552f3/bcrypt-5.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:200af71bc25f22006f4069060c88ed36f8aa4ff7f53e67ff04d2ab3f1e79a5b2", size = 268626, upload-time = "2025-09-25T19:49:06.723Z" },
{ url = "https://files.pythonhosted.org/packages/8c/cf/e82388ad5959c40d6afd94fb4743cc077129d45b952d46bdc3180310e2df/bcrypt-5.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:baade0a5657654c2984468efb7d6c110db87ea63ef5a4b54732e7e337253e44f", size = 271853, upload-time = "2025-09-25T19:49:08.028Z" },
{ url = "https://files.pythonhosted.org/packages/ec/86/7134b9dae7cf0efa85671651341f6afa695857fae172615e960fb6a466fa/bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:c58b56cdfb03202b3bcc9fd8daee8e8e9b6d7e3163aa97c631dfcfcc24d36c86", size = 269793, upload-time = "2025-09-25T19:49:09.727Z" },
{ url = "https://files.pythonhosted.org/packages/cc/82/6296688ac1b9e503d034e7d0614d56e80c5d1a08402ff856a4549cb59207/bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4bfd2a34de661f34d0bda43c3e4e79df586e4716ef401fe31ea39d69d581ef23", size = 289930, upload-time = "2025-09-25T19:49:11.204Z" },
{ url = "https://files.pythonhosted.org/packages/d1/18/884a44aa47f2a3b88dd09bc05a1e40b57878ecd111d17e5bba6f09f8bb77/bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:ed2e1365e31fc73f1825fa830f1c8f8917ca1b3ca6185773b349c20fd606cec2", size = 272194, upload-time = "2025-09-25T19:49:12.524Z" },
{ url = "https://files.pythonhosted.org/packages/0e/8f/371a3ab33c6982070b674f1788e05b656cfbf5685894acbfef0c65483a59/bcrypt-5.0.0-cp313-cp313t-manylinux_2_34_aarch64.whl", hash = "sha256:83e787d7a84dbbfba6f250dd7a5efd689e935f03dd83b0f919d39349e1f23f83", size = 269381, upload-time = "2025-09-25T19:49:14.308Z" },
{ url = "https://files.pythonhosted.org/packages/b1/34/7e4e6abb7a8778db6422e88b1f06eb07c47682313997ee8a8f9352e5a6f1/bcrypt-5.0.0-cp313-cp313t-manylinux_2_34_x86_64.whl", hash = "sha256:137c5156524328a24b9fac1cb5db0ba618bc97d11970b39184c1d87dc4bf1746", size = 271750, upload-time = "2025-09-25T19:49:15.584Z" },
{ url = "https://files.pythonhosted.org/packages/c0/1b/54f416be2499bd72123c70d98d36c6cd61a4e33d9b89562c22481c81bb30/bcrypt-5.0.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:38cac74101777a6a7d3b3e3cfefa57089b5ada650dce2baf0cbdd9d65db22a9e", size = 303757, upload-time = "2025-09-25T19:49:17.244Z" },
{ url = "https://files.pythonhosted.org/packages/13/62/062c24c7bcf9d2826a1a843d0d605c65a755bc98002923d01fd61270705a/bcrypt-5.0.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:d8d65b564ec849643d9f7ea05c6d9f0cd7ca23bdd4ac0c2dbef1104ab504543d", size = 306740, upload-time = "2025-09-25T19:49:18.693Z" },
{ url = "https://files.pythonhosted.org/packages/d5/c8/1fdbfc8c0f20875b6b4020f3c7dc447b8de60aa0be5faaf009d24242aec9/bcrypt-5.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:741449132f64b3524e95cd30e5cd3343006ce146088f074f31ab26b94e6c75ba", size = 334197, upload-time = "2025-09-25T19:49:20.523Z" },
{ url = "https://files.pythonhosted.org/packages/a6/c1/8b84545382d75bef226fbc6588af0f7b7d095f7cd6a670b42a86243183cd/bcrypt-5.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:212139484ab3207b1f0c00633d3be92fef3c5f0af17cad155679d03ff2ee1e41", size = 352974, upload-time = "2025-09-25T19:49:22.254Z" },
{ url = "https://files.pythonhosted.org/packages/10/a6/ffb49d4254ed085e62e3e5dd05982b4393e32fe1e49bb1130186617c29cd/bcrypt-5.0.0-cp313-cp313t-win32.whl", hash = "sha256:9d52ed507c2488eddd6a95bccee4e808d3234fa78dd370e24bac65a21212b861", size = 148498, upload-time = "2025-09-25T19:49:24.134Z" },
{ url = "https://files.pythonhosted.org/packages/48/a9/259559edc85258b6d5fc5471a62a3299a6aa37a6611a169756bf4689323c/bcrypt-5.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f6984a24db30548fd39a44360532898c33528b74aedf81c26cf29c51ee47057e", size = 145853, upload-time = "2025-09-25T19:49:25.702Z" },
{ url = "https://files.pythonhosted.org/packages/2d/df/9714173403c7e8b245acf8e4be8876aac64a209d1b392af457c79e60492e/bcrypt-5.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:9fffdb387abe6aa775af36ef16f55e318dcda4194ddbf82007a6f21da29de8f5", size = 139626, upload-time = "2025-09-25T19:49:26.928Z" },
{ url = "https://files.pythonhosted.org/packages/f8/14/c18006f91816606a4abe294ccc5d1e6f0e42304df5a33710e9e8e95416e1/bcrypt-5.0.0-cp314-cp314t-macosx_10_12_universal2.whl", hash = "sha256:4870a52610537037adb382444fefd3706d96d663ac44cbb2f37e3919dca3d7ef", size = 481862, upload-time = "2025-09-25T19:49:28.365Z" },
{ url = "https://files.pythonhosted.org/packages/67/49/dd074d831f00e589537e07a0725cf0e220d1f0d5d8e85ad5bbff251c45aa/bcrypt-5.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48f753100931605686f74e27a7b49238122aa761a9aefe9373265b8b7aa43ea4", size = 268544, upload-time = "2025-09-25T19:49:30.39Z" },
{ url = "https://files.pythonhosted.org/packages/f5/91/50ccba088b8c474545b034a1424d05195d9fcbaaf802ab8bfe2be5a4e0d7/bcrypt-5.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f70aadb7a809305226daedf75d90379c397b094755a710d7014b8b117df1ebbf", size = 271787, upload-time = "2025-09-25T19:49:32.144Z" },
{ url = "https://files.pythonhosted.org/packages/aa/e7/d7dba133e02abcda3b52087a7eea8c0d4f64d3e593b4fffc10c31b7061f3/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:744d3c6b164caa658adcb72cb8cc9ad9b4b75c7db507ab4bc2480474a51989da", size = 269753, upload-time = "2025-09-25T19:49:33.885Z" },
{ url = "https://files.pythonhosted.org/packages/33/fc/5b145673c4b8d01018307b5c2c1fc87a6f5a436f0ad56607aee389de8ee3/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a28bc05039bdf3289d757f49d616ab3efe8cf40d8e8001ccdd621cd4f98f4fc9", size = 289587, upload-time = "2025-09-25T19:49:35.144Z" },
{ url = "https://files.pythonhosted.org/packages/27/d7/1ff22703ec6d4f90e62f1a5654b8867ef96bafb8e8102c2288333e1a6ca6/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:7f277a4b3390ab4bebe597800a90da0edae882c6196d3038a73adf446c4f969f", size = 272178, upload-time = "2025-09-25T19:49:36.793Z" },
{ url = "https://files.pythonhosted.org/packages/c8/88/815b6d558a1e4d40ece04a2f84865b0fef233513bd85fd0e40c294272d62/bcrypt-5.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:79cfa161eda8d2ddf29acad370356b47f02387153b11d46042e93a0a95127493", size = 269295, upload-time = "2025-09-25T19:49:38.164Z" },
{ url = "https://files.pythonhosted.org/packages/51/8c/e0db387c79ab4931fc89827d37608c31cc57b6edc08ccd2386139028dc0d/bcrypt-5.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:a5393eae5722bcef046a990b84dff02b954904c36a194f6cfc817d7dca6c6f0b", size = 271700, upload-time = "2025-09-25T19:49:39.917Z" },
{ url = "https://files.pythonhosted.org/packages/06/83/1570edddd150f572dbe9fc00f6203a89fc7d4226821f67328a85c330f239/bcrypt-5.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7f4c94dec1b5ab5d522750cb059bb9409ea8872d4494fd152b53cca99f1ddd8c", size = 334034, upload-time = "2025-09-25T19:49:41.227Z" },
{ url = "https://files.pythonhosted.org/packages/c9/f2/ea64e51a65e56ae7a8a4ec236c2bfbdd4b23008abd50ac33fbb2d1d15424/bcrypt-5.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0cae4cb350934dfd74c020525eeae0a5f79257e8a201c0c176f4b84fdbf2a4b4", size = 352766, upload-time = "2025-09-25T19:49:43.08Z" },
{ url = "https://files.pythonhosted.org/packages/d7/d4/1a388d21ee66876f27d1a1f41287897d0c0f1712ef97d395d708ba93004c/bcrypt-5.0.0-cp314-cp314t-win32.whl", hash = "sha256:b17366316c654e1ad0306a6858e189fc835eca39f7eb2cafd6aaca8ce0c40a2e", size = 152449, upload-time = "2025-09-25T19:49:44.971Z" },
{ url = "https://files.pythonhosted.org/packages/3f/61/3291c2243ae0229e5bca5d19f4032cecad5dfb05a2557169d3a69dc0ba91/bcrypt-5.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:92864f54fb48b4c718fc92a32825d0e42265a627f956bc0361fe869f1adc3e7d", size = 149310, upload-time = "2025-09-25T19:49:46.162Z" },
{ url = "https://files.pythonhosted.org/packages/3e/89/4b01c52ae0c1a681d4021e5dd3e45b111a8fb47254a274fa9a378d8d834b/bcrypt-5.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dd19cf5184a90c873009244586396a6a884d591a5323f0e8a5922560718d4993", size = 143761, upload-time = "2025-09-25T19:49:47.345Z" },
{ url = "https://files.pythonhosted.org/packages/84/29/6237f151fbfe295fe3e074ecc6d44228faa1e842a81f6d34a02937ee1736/bcrypt-5.0.0-cp38-abi3-macosx_10_12_universal2.whl", hash = "sha256:fc746432b951e92b58317af8e0ca746efe93e66555f1b40888865ef5bf56446b", size = 494553, upload-time = "2025-09-25T19:49:49.006Z" },
{ url = "https://files.pythonhosted.org/packages/45/b6/4c1205dde5e464ea3bd88e8742e19f899c16fa8916fb8510a851fae985b5/bcrypt-5.0.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c2388ca94ffee269b6038d48747f4ce8df0ffbea43f31abfa18ac72f0218effb", size = 275009, upload-time = "2025-09-25T19:49:50.581Z" },
{ url = "https://files.pythonhosted.org/packages/3b/71/427945e6ead72ccffe77894b2655b695ccf14ae1866cd977e185d606dd2f/bcrypt-5.0.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:560ddb6ec730386e7b3b26b8b4c88197aaed924430e7b74666a586ac997249ef", size = 278029, upload-time = "2025-09-25T19:49:52.533Z" },
{ url = "https://files.pythonhosted.org/packages/17/72/c344825e3b83c5389a369c8a8e58ffe1480b8a699f46c127c34580c4666b/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d79e5c65dcc9af213594d6f7f1fa2c98ad3fc10431e7aa53c176b441943efbdd", size = 275907, upload-time = "2025-09-25T19:49:54.709Z" },
{ url = "https://files.pythonhosted.org/packages/0b/7e/d4e47d2df1641a36d1212e5c0514f5291e1a956a7749f1e595c07a972038/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2b732e7d388fa22d48920baa267ba5d97cca38070b69c0e2d37087b381c681fd", size = 296500, upload-time = "2025-09-25T19:49:56.013Z" },
{ url = "https://files.pythonhosted.org/packages/0f/c3/0ae57a68be2039287ec28bc463b82e4b8dc23f9d12c0be331f4782e19108/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0c8e093ea2532601a6f686edbc2c6b2ec24131ff5c52f7610dd64fa4553b5464", size = 278412, upload-time = "2025-09-25T19:49:57.356Z" },
{ url = "https://files.pythonhosted.org/packages/45/2b/77424511adb11e6a99e3a00dcc7745034bee89036ad7d7e255a7e47be7d8/bcrypt-5.0.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5b1589f4839a0899c146e8892efe320c0fa096568abd9b95593efac50a87cb75", size = 275486, upload-time = "2025-09-25T19:49:59.116Z" },
{ url = "https://files.pythonhosted.org/packages/43/0a/405c753f6158e0f3f14b00b462d8bca31296f7ecfc8fc8bc7919c0c7d73a/bcrypt-5.0.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:89042e61b5e808b67daf24a434d89bab164d4de1746b37a8d173b6b14f3db9ff", size = 277940, upload-time = "2025-09-25T19:50:00.869Z" },
{ url = "https://files.pythonhosted.org/packages/62/83/b3efc285d4aadc1fa83db385ec64dcfa1707e890eb42f03b127d66ac1b7b/bcrypt-5.0.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:e3cf5b2560c7b5a142286f69bde914494b6d8f901aaa71e453078388a50881c4", size = 310776, upload-time = "2025-09-25T19:50:02.393Z" },
{ url = "https://files.pythonhosted.org/packages/95/7d/47ee337dacecde6d234890fe929936cb03ebc4c3a7460854bbd9c97780b8/bcrypt-5.0.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f632fd56fc4e61564f78b46a2269153122db34988e78b6be8b32d28507b7eaeb", size = 312922, upload-time = "2025-09-25T19:50:04.232Z" },
{ url = "https://files.pythonhosted.org/packages/d6/3a/43d494dfb728f55f4e1cf8fd435d50c16a2d75493225b54c8d06122523c6/bcrypt-5.0.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:801cad5ccb6b87d1b430f183269b94c24f248dddbbc5c1f78b6ed231743e001c", size = 341367, upload-time = "2025-09-25T19:50:05.559Z" },
{ url = "https://files.pythonhosted.org/packages/55/ab/a0727a4547e383e2e22a630e0f908113db37904f58719dc48d4622139b5c/bcrypt-5.0.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3cf67a804fc66fc217e6914a5635000259fbbbb12e78a99488e4d5ba445a71eb", size = 359187, upload-time = "2025-09-25T19:50:06.916Z" },
{ url = "https://files.pythonhosted.org/packages/1b/bb/461f352fdca663524b4643d8b09e8435b4990f17fbf4fea6bc2a90aa0cc7/bcrypt-5.0.0-cp38-abi3-win32.whl", hash = "sha256:3abeb543874b2c0524ff40c57a4e14e5d3a66ff33fb423529c88f180fd756538", size = 153752, upload-time = "2025-09-25T19:50:08.515Z" },
{ url = "https://files.pythonhosted.org/packages/41/aa/4190e60921927b7056820291f56fc57d00d04757c8b316b2d3c0d1d6da2c/bcrypt-5.0.0-cp38-abi3-win_amd64.whl", hash = "sha256:35a77ec55b541e5e583eb3436ffbbf53b0ffa1fa16ca6782279daf95d146dcd9", size = 150881, upload-time = "2025-09-25T19:50:09.742Z" },
{ url = "https://files.pythonhosted.org/packages/54/12/cd77221719d0b39ac0b55dbd39358db1cd1246e0282e104366ebbfb8266a/bcrypt-5.0.0-cp38-abi3-win_arm64.whl", hash = "sha256:cde08734f12c6a4e28dc6755cd11d3bdfea608d93d958fffbe95a7026ebe4980", size = 144931, upload-time = "2025-09-25T19:50:11.016Z" },
{ url = "https://files.pythonhosted.org/packages/5d/ba/2af136406e1c3839aea9ecadc2f6be2bcd1eff255bd451dd39bcf302c47a/bcrypt-5.0.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0c418ca99fd47e9c59a301744d63328f17798b5947b0f791e9af3c1c499c2d0a", size = 495313, upload-time = "2025-09-25T19:50:12.309Z" },
{ url = "https://files.pythonhosted.org/packages/ac/ee/2f4985dbad090ace5ad1f7dd8ff94477fe089b5fab2040bd784a3d5f187b/bcrypt-5.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddb4e1500f6efdd402218ffe34d040a1196c072e07929b9820f363a1fd1f4191", size = 275290, upload-time = "2025-09-25T19:50:13.673Z" },
{ url = "https://files.pythonhosted.org/packages/e4/6e/b77ade812672d15cf50842e167eead80ac3514f3beacac8902915417f8b7/bcrypt-5.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7aeef54b60ceddb6f30ee3db090351ecf0d40ec6e2abf41430997407a46d2254", size = 278253, upload-time = "2025-09-25T19:50:15.089Z" },
{ url = "https://files.pythonhosted.org/packages/36/c4/ed00ed32f1040f7990dac7115f82273e3c03da1e1a1587a778d8cea496d8/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f0ce778135f60799d89c9693b9b398819d15f1921ba15fe719acb3178215a7db", size = 276084, upload-time = "2025-09-25T19:50:16.699Z" },
{ url = "https://files.pythonhosted.org/packages/e7/c4/fa6e16145e145e87f1fa351bbd54b429354fd72145cd3d4e0c5157cf4c70/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a71f70ee269671460b37a449f5ff26982a6f2ba493b3eabdd687b4bf35f875ac", size = 297185, upload-time = "2025-09-25T19:50:18.525Z" },
{ url = "https://files.pythonhosted.org/packages/24/b4/11f8a31d8b67cca3371e046db49baa7c0594d71eb40ac8121e2fc0888db0/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f8429e1c410b4073944f03bd778a9e066e7fad723564a52ff91841d278dfc822", size = 278656, upload-time = "2025-09-25T19:50:19.809Z" },
{ url = "https://files.pythonhosted.org/packages/ac/31/79f11865f8078e192847d2cb526e3fa27c200933c982c5b2869720fa5fce/bcrypt-5.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:edfcdcedd0d0f05850c52ba3127b1fce70b9f89e0fe5ff16517df7e81fa3cbb8", size = 275662, upload-time = "2025-09-25T19:50:21.567Z" },
{ url = "https://files.pythonhosted.org/packages/d4/8d/5e43d9584b3b3591a6f9b68f755a4da879a59712981ef5ad2a0ac1379f7a/bcrypt-5.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:611f0a17aa4a25a69362dcc299fda5c8a3d4f160e2abb3831041feb77393a14a", size = 278240, upload-time = "2025-09-25T19:50:23.305Z" },
{ url = "https://files.pythonhosted.org/packages/89/48/44590e3fc158620f680a978aafe8f87a4c4320da81ed11552f0323aa9a57/bcrypt-5.0.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:db99dca3b1fdc3db87d7c57eac0c82281242d1eabf19dcb8a6b10eb29a2e72d1", size = 311152, upload-time = "2025-09-25T19:50:24.597Z" },
{ url = "https://files.pythonhosted.org/packages/5f/85/e4fbfc46f14f47b0d20493669a625da5827d07e8a88ee460af6cd9768b44/bcrypt-5.0.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:5feebf85a9cefda32966d8171f5db7e3ba964b77fdfe31919622256f80f9cf42", size = 313284, upload-time = "2025-09-25T19:50:26.268Z" },
{ url = "https://files.pythonhosted.org/packages/25/ae/479f81d3f4594456a01ea2f05b132a519eff9ab5768a70430fa1132384b1/bcrypt-5.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3ca8a166b1140436e058298a34d88032ab62f15aae1c598580333dc21d27ef10", size = 341643, upload-time = "2025-09-25T19:50:28.02Z" },
{ url = "https://files.pythonhosted.org/packages/df/d2/36a086dee1473b14276cd6ea7f61aef3b2648710b5d7f1c9e032c29b859f/bcrypt-5.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:61afc381250c3182d9078551e3ac3a41da14154fbff647ddf52a769f588c4172", size = 359698, upload-time = "2025-09-25T19:50:31.347Z" },
{ url = "https://files.pythonhosted.org/packages/c0/f6/688d2cd64bfd0b14d805ddb8a565e11ca1fb0fd6817175d58b10052b6d88/bcrypt-5.0.0-cp39-abi3-win32.whl", hash = "sha256:64d7ce196203e468c457c37ec22390f1a61c85c6f0b8160fd752940ccfb3a683", size = 153725, upload-time = "2025-09-25T19:50:34.384Z" },
{ url = "https://files.pythonhosted.org/packages/9f/b9/9d9a641194a730bda138b3dfe53f584d61c58cd5230e37566e83ec2ffa0d/bcrypt-5.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:64ee8434b0da054d830fa8e89e1c8bf30061d539044a39524ff7dec90481e5c2", size = 150912, upload-time = "2025-09-25T19:50:35.69Z" },
{ url = "https://files.pythonhosted.org/packages/27/44/d2ef5e87509158ad2187f4dd0852df80695bb1ee0cfe0a684727b01a69e0/bcrypt-5.0.0-cp39-abi3-win_arm64.whl", hash = "sha256:f2347d3534e76bf50bca5500989d6c1d05ed64b440408057a37673282c654927", size = 144953, upload-time = "2025-09-25T19:50:37.32Z" },
{ url = "https://files.pythonhosted.org/packages/78/d4/3b2657bd58ef02b23a07729b0df26f21af97169dbd0b5797afa9e97ebb49/bcrypt-4.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:b1023030aec778185a6c16cf70f359cbb6e0c289fd564a7cfa29e727a1c38f8f", size = 473446, upload-time = "2022-10-09T15:36:25.481Z" },
{ url = "https://files.pythonhosted.org/packages/ec/0a/1582790232fef6c2aa201f345577306b8bfe465c2c665dec04c86a016879/bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:08d2947c490093a11416df18043c27abe3921558d2c03e2076ccb28a116cb6d0", size = 583044, upload-time = "2022-10-09T15:37:09.447Z" },
{ url = "https://files.pythonhosted.org/packages/41/16/49ff5146fb815742ad58cafb5034907aa7f166b1344d0ddd7fd1c818bd17/bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0eaa47d4661c326bfc9d08d16debbc4edf78778e6aaba29c1bc7ce67214d4410", size = 583189, upload-time = "2022-10-09T15:37:10.69Z" },
{ url = "https://files.pythonhosted.org/packages/aa/48/fd2b197a9741fa790ba0b88a9b10b5e88e62ff5cf3e1bc96d8354d7ce613/bcrypt-4.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae88eca3024bb34bb3430f964beab71226e761f51b912de5133470b649d82344", size = 593473, upload-time = "2022-10-09T15:36:27.195Z" },
{ url = "https://files.pythonhosted.org/packages/7d/50/e683d8418974a602ba40899c8a5c38b3decaf5a4d36c32fc65dce454d8a8/bcrypt-4.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:a522427293d77e1c29e303fc282e2d71864579527a04ddcfda6d4f8396c6c36a", size = 593249, upload-time = "2022-10-09T15:36:28.481Z" },
{ url = "https://files.pythonhosted.org/packages/fb/a7/ee4561fd9b78ca23c8e5591c150cc58626a5dfb169345ab18e1c2c664ee0/bcrypt-4.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:fbdaec13c5105f0c4e5c52614d04f0bca5f5af007910daa8b6b12095edaa67b3", size = 583586, upload-time = "2022-10-09T15:37:11.962Z" },
{ url = "https://files.pythonhosted.org/packages/64/fe/da28a5916128d541da0993328dc5cf4b43dfbf6655f2c7a2abe26ca2dc88/bcrypt-4.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ca3204d00d3cb2dfed07f2d74a25f12fc12f73e606fcaa6975d1f7ae69cacbb2", size = 593659, upload-time = "2022-10-09T15:36:30.049Z" },
{ url = "https://files.pythonhosted.org/packages/dd/4f/3632a69ce344c1551f7c9803196b191a8181c6a1ad2362c225581ef0d383/bcrypt-4.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:089098effa1bc35dc055366740a067a2fc76987e8ec75349eb9484061c54f535", size = 613116, upload-time = "2022-10-09T15:37:14.107Z" },
{ url = "https://files.pythonhosted.org/packages/87/69/edacb37481d360d06fc947dab5734aaf511acb7d1a1f9e2849454376c0f8/bcrypt-4.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:e9a51bbfe7e9802b5f3508687758b564069ba937748ad7b9e890086290d2f79e", size = 624290, upload-time = "2022-10-09T15:36:31.251Z" },
{ url = "https://files.pythonhosted.org/packages/aa/ca/6a534669890725cbb8c1fb4622019be31813c8edaa7b6d5b62fc9360a17e/bcrypt-4.0.1-cp36-abi3-win32.whl", hash = "sha256:2caffdae059e06ac23fce178d31b4a702f2a3264c20bfb5ff541b338194d8fab", size = 159428, upload-time = "2022-10-09T15:36:32.893Z" },
{ url = "https://files.pythonhosted.org/packages/46/81/d8c22cd7e5e1c6a7d48e41a1d1d46c92f17dae70a54d9814f746e6027dec/bcrypt-4.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:8a68f4341daf7522fe8d73874de8906f3a339048ba406be6ddc1b3ccb16fc0d9", size = 152930, upload-time = "2022-10-09T15:36:34.635Z" },
]
[[package]]
@ -427,6 +380,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/84/d0/205d54408c08b13550c733c4b85429e7ead111c7f0014309637425520a9a/deprecated-1.3.1-py2.py3-none-any.whl", hash = "sha256:597bfef186b6f60181535a29fbe44865ce137a5079f295b479886c82729d5f3f", size = 11298, upload-time = "2025-10-30T08:19:00.758Z" },
]
[[package]]
name = "dnspython"
version = "2.8.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" },
]
[[package]]
name = "ecdsa"
version = "0.19.1"
@ -439,6 +401,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/cb/a3/460c57f094a4a165c84a1341c373b0a4f5ec6ac244b998d5021aade89b77/ecdsa-0.19.1-py2.py3-none-any.whl", hash = "sha256:30638e27cf77b7e15c4c4cc1973720149e1033827cfd00661ca5c8cc0cdb24c3", size = 150607, upload-time = "2025-03-13T11:52:41.757Z" },
]
[[package]]
name = "email-validator"
version = "2.3.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "dnspython" },
{ name = "idna" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f5/22/900cb125c76b7aaa450ce02fd727f452243f2e91a61af068b40adba60ea9/email_validator-2.3.0.tar.gz", hash = "sha256:9fc05c37f2f6cf439ff414f8fc46d917929974a82244c20eb10231ba60c54426", size = 51238, upload-time = "2025-08-26T13:09:06.831Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl", hash = "sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4", size = 35604, upload-time = "2025-08-26T13:09:05.858Z" },
]
[[package]]
name = "fastapi"
version = "0.115.6"
@ -495,12 +470,13 @@ wheels = [
]
[[package]]
name = "galaxy-po-backend"
name = "galaxis-po-backend"
version = "1.0.0"
source = { virtual = "." }
dependencies = [
{ name = "alembic" },
{ name = "apscheduler" },
{ name = "bcrypt" },
{ name = "beautifulsoup4" },
{ name = "fastapi" },
{ name = "httpx" },
@ -509,12 +485,13 @@ dependencies = [
{ name = "pandas" },
{ name = "passlib", extra = ["bcrypt"] },
{ name = "psycopg2-binary" },
{ name = "pydantic" },
{ name = "pydantic", extra = ["email"] },
{ name = "pydantic-settings" },
{ name = "pykrx" },
{ name = "python-jose", extra = ["cryptography"] },
{ name = "python-multipart" },
{ name = "requests" },
{ name = "setuptools" },
{ name = "sqlalchemy" },
{ name = "uvicorn", extra = ["standard"] },
]
@ -535,6 +512,7 @@ dev = [
requires-dist = [
{ name = "alembic", specifier = "==1.14.0" },
{ name = "apscheduler", specifier = "==3.10.4" },
{ name = "bcrypt", specifier = "==4.0.1" },
{ name = "beautifulsoup4", specifier = "==4.12.3" },
{ name = "fastapi", specifier = "==0.115.6" },
{ name = "httpx", specifier = "==0.28.1" },
@ -543,7 +521,7 @@ requires-dist = [
{ name = "pandas", specifier = "==2.2.3" },
{ name = "passlib", extras = ["bcrypt"], specifier = "==1.7.4" },
{ name = "psycopg2-binary", specifier = "==2.9.10" },
{ name = "pydantic", specifier = "==2.10.4" },
{ name = "pydantic", extras = ["email"], specifier = "==2.10.4" },
{ name = "pydantic-settings", specifier = "==2.7.1" },
{ name = "pykrx", specifier = "==1.0.45" },
{ name = "pytest", marker = "extra == 'dev'", specifier = "==8.3.4" },
@ -551,6 +529,7 @@ requires-dist = [
{ name = "python-jose", extras = ["cryptography"], specifier = "==3.3.0" },
{ name = "python-multipart", specifier = "==0.0.20" },
{ name = "requests", specifier = "==2.32.3" },
{ name = "setuptools" },
{ name = "sqlalchemy", specifier = "==2.0.36" },
{ name = "uvicorn", extras = ["standard"], specifier = "==0.34.0" },
]
@ -1173,6 +1152,11 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/f3/26/3e1bbe954fde7ee22a6e7d31582c642aad9e84ffe4b5fb61e63b87cd326f/pydantic-2.10.4-py3-none-any.whl", hash = "sha256:597e135ea68be3a37552fb524bc7d0d66dcf93d395acd93a00682f1efcb8ee3d", size = 431765, upload-time = "2024-12-18T17:09:21.953Z" },
]
[package.optional-dependencies]
email = [
{ name = "email-validator" },
]
[[package]]
name = "pydantic-core"
version = "2.27.2"
@ -1410,6 +1394,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" },
]
[[package]]
name = "setuptools"
version = "80.10.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/76/95/faf61eb8363f26aa7e1d762267a8d602a1b26d4f3a1e758e92cb3cb8b054/setuptools-80.10.2.tar.gz", hash = "sha256:8b0e9d10c784bf7d262c4e5ec5d4ec94127ce206e8738f29a437945fbc219b70", size = 1200343, upload-time = "2026-01-25T22:38:17.252Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/94/b8/f1f62a5e3c0ad2ff1d189590bfa4c46b4f3b6e49cef6f26c6ee4e575394d/setuptools-80.10.2-py3-none-any.whl", hash = "sha256:95b30ddfb717250edb492926c92b5221f7ef3fbcc2b07579bcd4a27da21d0173", size = 1064234, upload-time = "2026-01-25T22:38:15.216Z" },
]
[[package]]
name = "six"
version = "1.17.0"

View File

@ -1,10 +1,19 @@
import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import { Inter, Noto_Sans_KR } from 'next/font/google';
import './globals.css';
import { ThemeProvider } from '@/components/providers/theme-provider';
import { Toaster } from '@/components/ui/sonner';
const inter = Inter({ subsets: ['latin'] });
const inter = Inter({
subsets: ['latin'],
variable: '--font-inter',
});
const notoSansKR = Noto_Sans_KR({
subsets: ['latin'],
weight: ['400', '500', '600', '700'],
variable: '--font-noto-sans-kr',
});
export const metadata: Metadata = {
title: 'Galaxis-Po',
@ -18,7 +27,7 @@ export default function RootLayout({
}>) {
return (
<html lang="ko" suppressHydrationWarning>
<body className={inter.className}>
<body className={`${inter.variable} ${notoSansKR.variable} font-sans`}>
<ThemeProvider
attribute="class"
defaultTheme="system"

View File

@ -9,6 +9,9 @@ const config: Config = {
],
theme: {
extend: {
fontFamily: {
sans: ['var(--font-noto-sans-kr)', 'var(--font-inter)', 'system-ui', 'sans-serif'],
},
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",