chore(merge): merge branch 'main' into feat/multiplayer-api
This commit is contained in:
@@ -1,56 +1,71 @@
|
||||
{
|
||||
"name": "OSU Lazer API",
|
||||
"dockerComposeFile": "docker-compose.yml",
|
||||
"service": "devcontainer",
|
||||
"shutdownAction": "stopCompose",
|
||||
"workspaceFolder": "/workspaces/osu_lazer_api",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"ms-python.python",
|
||||
"ms-python.vscode-pylance",
|
||||
"detachhead.basedpyright",
|
||||
"charliermarsh.ruff",
|
||||
"ms-python.debugpy",
|
||||
"ms-vscode.vscode-json",
|
||||
"redhat.vscode-yaml",
|
||||
"ms-vscode.docker"
|
||||
],
|
||||
"settings": {
|
||||
"python.defaultInterpreterPath": "/usr/local/bin/python",
|
||||
"python.terminal.activateEnvironment": true,
|
||||
"python.linting.enabled": true,
|
||||
"python.linting.pylintEnabled": false,
|
||||
"python.linting.flake8Enabled": false,
|
||||
"python.formatting.provider": "none",
|
||||
"[python]": {
|
||||
"editor.defaultFormatter": "charliermarsh.ruff",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": "explicit"
|
||||
}
|
||||
},
|
||||
"ruff.enable": true,
|
||||
"ruff.lint.enable": true,
|
||||
"ruff.format.enable": true,
|
||||
"ruff.importStrategy": "fromEnvironment",
|
||||
"files.exclude": {
|
||||
"**/__pycache__": true,
|
||||
"**/*.pyc": true
|
||||
},
|
||||
"python.testing.pytestEnabled": true,
|
||||
"python.testing.unittestEnabled": false,
|
||||
"python.testing.pytestArgs": [
|
||||
"."
|
||||
],
|
||||
"terminal.integrated.defaultProfile.linux": "bash"
|
||||
}
|
||||
}
|
||||
},
|
||||
"features": {
|
||||
"ghcr.io/va-h/devcontainers-features/uv:1": {}
|
||||
},
|
||||
"forwardPorts": [8000, 3306, 6379],
|
||||
"postCreateCommand": "uv sync --dev && uv run pre-commit install",
|
||||
"remoteUser": "vscode"
|
||||
}
|
||||
{
|
||||
"name": "OSU Lazer API",
|
||||
"dockerComposeFile": "docker-compose.yml",
|
||||
"service": "devcontainer",
|
||||
"shutdownAction": "stopCompose",
|
||||
"workspaceFolder": "/workspaces/osu_lazer_api",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"ms-python.python",
|
||||
"detachhead.basedpyright",
|
||||
"charliermarsh.ruff",
|
||||
"ms-python.debugpy",
|
||||
"ms-vscode.vscode-json",
|
||||
"redhat.vscode-yaml",
|
||||
"ms-vscode.docker",
|
||||
"rust-lang.rust-analyzer"
|
||||
],
|
||||
"settings": {
|
||||
"python.defaultInterpreterPath": "/usr/local/bin/python",
|
||||
"python.terminal.activateEnvironment": true,
|
||||
"python.linting.enabled": true,
|
||||
"python.linting.pylintEnabled": false,
|
||||
"python.linting.flake8Enabled": false,
|
||||
"python.formatting.provider": "none",
|
||||
"[python]": {
|
||||
"editor.defaultFormatter": "charliermarsh.ruff",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": "explicit"
|
||||
}
|
||||
},
|
||||
"ruff.enable": true,
|
||||
"ruff.lint.enable": true,
|
||||
"ruff.format.enable": true,
|
||||
"ruff.importStrategy": "fromEnvironment",
|
||||
"files.exclude": {
|
||||
"**/__pycache__": true,
|
||||
"**/*.pyc": true
|
||||
},
|
||||
"python.testing.pytestEnabled": true,
|
||||
"python.testing.unittestEnabled": false,
|
||||
"python.testing.pytestArgs": [
|
||||
"."
|
||||
],
|
||||
"terminal.integrated.defaultProfile.linux": "bash",
|
||||
"rust-analyzer.server.path": "rust-analyzer",
|
||||
"rust-analyzer.cargo.buildScripts.enable": true,
|
||||
"rust-analyzer.procMacro.enable": true,
|
||||
"[rust]": {
|
||||
"editor.defaultFormatter": "rust-lang.rust-analyzer",
|
||||
"editor.formatOnSave": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"features": {
|
||||
"ghcr.io/va-h/devcontainers-features/uv:1": {},
|
||||
"ghcr.io/devcontainers/features/rust:1": {
|
||||
"version": "latest",
|
||||
"profile": "default"
|
||||
}
|
||||
},
|
||||
"forwardPorts": [
|
||||
8000,
|
||||
3306,
|
||||
6379
|
||||
],
|
||||
"postCreateCommand": "uv sync --dev && uv run pre-commit install && cd packages/msgpack_lazer_api && cargo check",
|
||||
"remoteUser": "vscode"
|
||||
}
|
||||
|
||||
104
app/calculator.py
Normal file
104
app/calculator.py
Normal file
@@ -0,0 +1,104 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from app.models.beatmap import BeatmapAttributes
|
||||
from app.models.mods import APIMod
|
||||
from app.models.score import GameMode
|
||||
|
||||
import rosu_pp_py as rosu
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from app.database.score import Score
|
||||
|
||||
|
||||
def clamp[T: int | float](n: T, min_value: T, max_value: T) -> T:
|
||||
if n < min_value:
|
||||
return min_value
|
||||
elif n > max_value:
|
||||
return max_value
|
||||
else:
|
||||
return n
|
||||
|
||||
|
||||
def calculate_beatmap_attribute(
|
||||
beatmap: str,
|
||||
gamemode: GameMode | None = None,
|
||||
mods: int | list[APIMod] | list[str] = 0,
|
||||
) -> BeatmapAttributes:
|
||||
map = rosu.Beatmap(content=beatmap)
|
||||
if gamemode is not None:
|
||||
map.convert(gamemode.to_rosu(), mods) # pyright: ignore[reportArgumentType]
|
||||
diff = rosu.Difficulty(mods=mods).calculate(map)
|
||||
return BeatmapAttributes(
|
||||
star_rating=diff.stars,
|
||||
max_combo=diff.max_combo,
|
||||
aim_difficulty=diff.aim,
|
||||
aim_difficult_slider_count=diff.aim_difficult_slider_count,
|
||||
speed_difficulty=diff.speed,
|
||||
speed_note_count=diff.speed_note_count,
|
||||
slider_factor=diff.slider_factor,
|
||||
aim_difficult_strain_count=diff.aim_difficult_strain_count,
|
||||
speed_difficult_strain_count=diff.speed_difficult_strain_count,
|
||||
mono_stamina_factor=diff.stamina,
|
||||
)
|
||||
|
||||
|
||||
def calculate_pp(
|
||||
score: "Score",
|
||||
beatmap: str,
|
||||
) -> float:
|
||||
map = rosu.Beatmap(content=beatmap)
|
||||
map.convert(score.gamemode.to_rosu(), score.mods) # pyright: ignore[reportArgumentType]
|
||||
if map.is_suspicious():
|
||||
return 0.0
|
||||
perf = rosu.Performance(
|
||||
mods=score.mods,
|
||||
lazer=True,
|
||||
accuracy=score.accuracy,
|
||||
combo=score.max_combo,
|
||||
large_tick_hits=score.nlarge_tick_hit or 0,
|
||||
slider_end_hits=score.nslider_tail_hit or 0,
|
||||
small_tick_hits=score.nsmall_tick_hit or 0,
|
||||
n_geki=score.ngeki,
|
||||
n_katu=score.nkatu,
|
||||
n300=score.n300,
|
||||
n100=score.n100,
|
||||
n50=score.n50,
|
||||
misses=score.nmiss,
|
||||
hitresult_priority=rosu.HitResultPriority.Fastest,
|
||||
)
|
||||
attrs = perf.calculate(map)
|
||||
return attrs.pp
|
||||
|
||||
|
||||
# https://osu.ppy.sh/wiki/Gameplay/Score/Total_score
|
||||
def calculate_level_to_score(level: int) -> float:
|
||||
if level <= 100:
|
||||
# 55 = 4^3 - 3^2
|
||||
return 5000 / 3 * (55 - level) + 1.25 * math.pow(1.8, level - 60)
|
||||
else:
|
||||
return 26_931_190_827 + 99_999_999_999 * (level - 100)
|
||||
|
||||
|
||||
def calculate_score_to_level(score: float) -> int:
|
||||
if score < 5000:
|
||||
return int(55 - (3 * score / 5000)) # 55 = 4^3 - 3^2
|
||||
elif score < 26_931_190_827:
|
||||
return int(60 + math.log(score / 1.25, 1.8))
|
||||
else:
|
||||
return int((score - 26_931_190_827) / 99_999_999_999 + 100)
|
||||
|
||||
|
||||
# https://osu.ppy.sh/wiki/Performance_points/Weighting_system
|
||||
def calculate_pp_weight(index: int) -> float:
|
||||
return math.pow(0.95, index)
|
||||
|
||||
|
||||
def calculate_weighted_pp(pp: float, index: int) -> float:
|
||||
return calculate_pp_weight(index) * pp if pp > 0 else 0.0
|
||||
|
||||
|
||||
def calculate_weighted_acc(acc: float, index: int) -> float:
|
||||
return calculate_pp_weight(index) * acc if acc > 0 else 0.0
|
||||
@@ -7,6 +7,7 @@ from .beatmapset import (
|
||||
Beatmapset as Beatmapset,
|
||||
BeatmapsetResp as BeatmapsetResp,
|
||||
)
|
||||
from .best_score import BestScore
|
||||
from .legacy import LegacyOAuthToken, LegacyUserStatistics
|
||||
from .relationship import Relationship, RelationshipResp, RelationshipType
|
||||
from .score import (
|
||||
@@ -44,6 +45,7 @@ __all__ = [
|
||||
"BeatmapResp",
|
||||
"Beatmapset",
|
||||
"BeatmapsetResp",
|
||||
"BestScore",
|
||||
"DailyChallengeStats",
|
||||
"LazerUserAchievement",
|
||||
"LazerUserBadge",
|
||||
|
||||
41
app/database/best_score.py
Normal file
41
app/database/best_score.py
Normal file
@@ -0,0 +1,41 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from app.models.score import GameMode
|
||||
|
||||
from .user import User
|
||||
|
||||
from sqlmodel import (
|
||||
BigInteger,
|
||||
Column,
|
||||
Field,
|
||||
Float,
|
||||
ForeignKey,
|
||||
Relationship,
|
||||
SQLModel,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .beatmap import Beatmap
|
||||
from .score import Score
|
||||
|
||||
|
||||
class BestScore(SQLModel, table=True):
|
||||
__tablename__ = "best_scores" # pyright: ignore[reportAssignmentType]
|
||||
user_id: int = Field(
|
||||
sa_column=Column(BigInteger, ForeignKey("users.id"), index=True)
|
||||
)
|
||||
score_id: int = Field(
|
||||
sa_column=Column(BigInteger, ForeignKey("scores.id"), primary_key=True)
|
||||
)
|
||||
beatmap_id: int = Field(foreign_key="beatmaps.id", index=True)
|
||||
gamemode: GameMode = Field(index=True)
|
||||
pp: float = Field(
|
||||
sa_column=Column(Float, default=0),
|
||||
)
|
||||
acc: float = Field(
|
||||
sa_column=Column(Float, default=0),
|
||||
)
|
||||
|
||||
user: User = Relationship()
|
||||
score: "Score" = Relationship()
|
||||
beatmap: "Beatmap" = Relationship()
|
||||
@@ -1,6 +1,8 @@
|
||||
from enum import Enum
|
||||
|
||||
from .user import User
|
||||
from app.models.user import User as APIUser
|
||||
|
||||
from .user import User as DBUser
|
||||
|
||||
from pydantic import BaseModel
|
||||
from sqlmodel import (
|
||||
@@ -41,14 +43,14 @@ class Relationship(SQLModel, table=True):
|
||||
),
|
||||
)
|
||||
type: RelationshipType = Field(default=RelationshipType.FOLLOW, nullable=False)
|
||||
target: "User" = SQLRelationship(
|
||||
target: DBUser = SQLRelationship(
|
||||
sa_relationship_kwargs={"foreign_keys": "[Relationship.target_id]"}
|
||||
)
|
||||
|
||||
|
||||
class RelationshipResp(BaseModel):
|
||||
target_id: int
|
||||
# FIXME: target: User
|
||||
target: APIUser
|
||||
mutual: bool = False
|
||||
type: RelationshipType
|
||||
|
||||
@@ -56,6 +58,8 @@ class RelationshipResp(BaseModel):
|
||||
async def from_db(
|
||||
cls, session: AsyncSession, relationship: Relationship
|
||||
) -> "RelationshipResp":
|
||||
from app.utils import convert_db_user_to_api_user
|
||||
|
||||
target_relationship = (
|
||||
await session.exec(
|
||||
select(Relationship).where(
|
||||
@@ -71,7 +75,7 @@ class RelationshipResp(BaseModel):
|
||||
)
|
||||
return cls(
|
||||
target_id=relationship.target_id,
|
||||
# target=relationship.target,
|
||||
target=await convert_db_user_to_api_user(relationship.target),
|
||||
mutual=mutual,
|
||||
type=relationship.type,
|
||||
)
|
||||
|
||||
@@ -1,21 +1,38 @@
|
||||
from datetime import datetime
|
||||
import asyncio
|
||||
from collections.abc import Sequence
|
||||
from datetime import UTC, datetime
|
||||
import math
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from app.database.user import User
|
||||
from app.calculator import (
|
||||
calculate_pp,
|
||||
calculate_pp_weight,
|
||||
calculate_score_to_level,
|
||||
calculate_weighted_acc,
|
||||
calculate_weighted_pp,
|
||||
clamp,
|
||||
)
|
||||
from app.database.score_token import ScoreToken
|
||||
from app.database.user import LazerUserStatistics, User
|
||||
from app.models.beatmap import BeatmapRankStatus
|
||||
from app.models.mods import APIMod
|
||||
from app.models.mods import APIMod, mods_can_get_pp
|
||||
from app.models.score import (
|
||||
INT_TO_MODE,
|
||||
MODE_TO_INT,
|
||||
GameMode,
|
||||
HitResult,
|
||||
LeaderboardType,
|
||||
Rank,
|
||||
ScoreStatistics,
|
||||
SoloScoreSubmissionInfo,
|
||||
)
|
||||
from app.models.user import User as APIUser
|
||||
|
||||
from .beatmap import Beatmap, BeatmapResp
|
||||
from .beatmapset import Beatmapset, BeatmapsetResp
|
||||
from .best_score import BestScore
|
||||
|
||||
from redis import Redis
|
||||
from sqlalchemy import Column, ColumnExpressionArgument, DateTime
|
||||
from sqlalchemy.orm import aliased, joinedload
|
||||
from sqlmodel import (
|
||||
@@ -33,12 +50,14 @@ from sqlmodel import (
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
from sqlmodel.sql._expression_select_cls import SelectOfScalar
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from app.fetcher import Fetcher
|
||||
|
||||
|
||||
class ScoreBase(SQLModel):
|
||||
# 基本字段
|
||||
accuracy: float
|
||||
map_md5: str = Field(max_length=32, index=True)
|
||||
best_id: int | None = Field(default=None)
|
||||
build_id: int | None = Field(default=None)
|
||||
classic_total_score: int | None = Field(
|
||||
default=0, sa_column=Column(BigInteger)
|
||||
@@ -49,7 +68,7 @@ class ScoreBase(SQLModel):
|
||||
mods: list[APIMod] = Field(sa_column=Column(JSON))
|
||||
passed: bool
|
||||
playlist_item_id: int | None = Field(default=None) # multiplayer
|
||||
pp: float
|
||||
pp: float = Field(default=0.0)
|
||||
preserve: bool = Field(default=True)
|
||||
rank: Rank
|
||||
room_id: int | None = Field(default=None) # multiplayer
|
||||
@@ -87,7 +106,9 @@ class Score(ScoreBase, table=True):
|
||||
ngeki: int = Field(exclude=True)
|
||||
nkatu: int = Field(exclude=True)
|
||||
nlarge_tick_miss: int | None = Field(default=None, exclude=True)
|
||||
nlarge_tick_hit: int | None = Field(default=None, exclude=True)
|
||||
nslider_tail_hit: int | None = Field(default=None, exclude=True)
|
||||
nsmall_tick_hit: int | None = Field(default=None, exclude=True)
|
||||
gamemode: GameMode = Field(index=True)
|
||||
|
||||
# optional
|
||||
@@ -99,15 +120,19 @@ class Score(ScoreBase, table=True):
|
||||
return self.max_combo == self.beatmap.max_combo
|
||||
|
||||
@staticmethod
|
||||
def select_clause() -> SelectOfScalar["Score"]:
|
||||
return select(Score).options(
|
||||
def select_clause(with_user: bool = True) -> SelectOfScalar["Score"]:
|
||||
clause = select(Score).options(
|
||||
joinedload(Score.beatmap) # pyright: ignore[reportArgumentType]
|
||||
.joinedload(Beatmap.beatmapset) # pyright: ignore[reportArgumentType]
|
||||
.selectinload(
|
||||
Beatmapset.beatmaps # pyright: ignore[reportArgumentType]
|
||||
),
|
||||
joinedload(Score.user).joinedload(User.lazer_profile), # pyright: ignore[reportArgumentType]
|
||||
)
|
||||
if with_user:
|
||||
return clause.options(
|
||||
joinedload(Score.user).options(*User.all_select_option()) # pyright: ignore[reportArgumentType]
|
||||
)
|
||||
return clause
|
||||
|
||||
@staticmethod
|
||||
def select_clause_unique(
|
||||
@@ -131,7 +156,7 @@ class Score(ScoreBase, table=True):
|
||||
.selectinload(
|
||||
Beatmapset.beatmaps # pyright: ignore[reportArgumentType]
|
||||
),
|
||||
joinedload(best.user).joinedload(User.lazer_profile), # pyright: ignore[reportArgumentType]
|
||||
joinedload(best.user).options(*User.all_select_option()), # pyright: ignore[reportArgumentType]
|
||||
)
|
||||
)
|
||||
|
||||
@@ -144,16 +169,22 @@ class ScoreResp(ScoreBase):
|
||||
legacy_total_score: int = 0 # FIXME
|
||||
processed: bool = False # solo_score
|
||||
weight: float = 0.0
|
||||
best_id: int | None = None
|
||||
ruleset_id: int | None = None
|
||||
beatmap: BeatmapResp | None = None
|
||||
beatmapset: BeatmapsetResp | None = None
|
||||
# FIXME: user: APIUser | None = None
|
||||
user: APIUser | None = None
|
||||
statistics: ScoreStatistics | None = None
|
||||
maximum_statistics: ScoreStatistics | None = None
|
||||
rank_global: int | None = None
|
||||
rank_country: int | None = None
|
||||
|
||||
@classmethod
|
||||
async def from_db(cls, session: AsyncSession, score: Score) -> "ScoreResp":
|
||||
async def from_db(
|
||||
cls, session: AsyncSession, score: Score, user: User | None = None
|
||||
) -> "ScoreResp":
|
||||
from app.utils import convert_db_user_to_api_user
|
||||
|
||||
s = cls.model_validate(score.model_dump())
|
||||
assert score.id
|
||||
s.beatmap = BeatmapResp.from_db(score.beatmap)
|
||||
@@ -161,9 +192,10 @@ class ScoreResp(ScoreBase):
|
||||
s.is_perfect_combo = s.max_combo == s.beatmap.max_combo
|
||||
s.legacy_perfect = s.max_combo == s.beatmap.max_combo
|
||||
s.ruleset_id = MODE_TO_INT[score.gamemode]
|
||||
if score.best_id:
|
||||
# https://osu.ppy.sh/wiki/Performance_points/Weighting_system
|
||||
s.weight = math.pow(0.95, score.best_id)
|
||||
best_id = await get_best_id(session, score.id)
|
||||
if best_id:
|
||||
s.best_id = best_id
|
||||
s.weight = calculate_pp_weight(best_id - 1)
|
||||
s.statistics = {
|
||||
HitResult.MISS: score.nmiss,
|
||||
HitResult.MEH: score.n50,
|
||||
@@ -176,14 +208,27 @@ class ScoreResp(ScoreBase):
|
||||
s.statistics[HitResult.LARGE_TICK_MISS] = score.nlarge_tick_miss
|
||||
if score.nslider_tail_hit is not None:
|
||||
s.statistics[HitResult.SLIDER_TAIL_HIT] = score.nslider_tail_hit
|
||||
# s.user = await convert_db_user_to_api_user(score.user)
|
||||
if score.nsmall_tick_hit is not None:
|
||||
s.statistics[HitResult.SMALL_TICK_HIT] = score.nsmall_tick_hit
|
||||
if score.nlarge_tick_hit is not None:
|
||||
s.statistics[HitResult.LARGE_TICK_HIT] = score.nlarge_tick_hit
|
||||
if score.gamemode == GameMode.MANIA:
|
||||
s.maximum_statistics = {
|
||||
HitResult.PERFECT: score.beatmap.max_combo,
|
||||
}
|
||||
else:
|
||||
s.maximum_statistics = {
|
||||
HitResult.GREAT: score.beatmap.max_combo,
|
||||
}
|
||||
if user:
|
||||
s.user = await convert_db_user_to_api_user(user)
|
||||
s.rank_global = (
|
||||
await get_score_position_by_id(
|
||||
session,
|
||||
score.map_md5,
|
||||
score.id,
|
||||
mode=score.gamemode,
|
||||
user=score.user,
|
||||
user=user or score.user,
|
||||
)
|
||||
or None
|
||||
)
|
||||
@@ -193,13 +238,25 @@ class ScoreResp(ScoreBase):
|
||||
score.map_md5,
|
||||
score.id,
|
||||
score.gamemode,
|
||||
score.user,
|
||||
user or score.user,
|
||||
)
|
||||
or None
|
||||
)
|
||||
return s
|
||||
|
||||
|
||||
async def get_best_id(session: AsyncSession, score_id: int) -> None:
|
||||
rownum = (
|
||||
func.row_number()
|
||||
.over(partition_by=col(BestScore.user_id), order_by=col(BestScore.pp).desc())
|
||||
.label("rn")
|
||||
)
|
||||
subq = select(BestScore, rownum).subquery()
|
||||
stmt = select(subq.c.rn).where(subq.c.score_id == score_id)
|
||||
result = await session.exec(stmt)
|
||||
return result.one_or_none()
|
||||
|
||||
|
||||
async def get_leaderboard(
|
||||
session: AsyncSession,
|
||||
beatmap_md5: str,
|
||||
@@ -381,3 +438,207 @@ async def get_score_position_by_id(
|
||||
result = await session.exec(stmt)
|
||||
s = result.one_or_none()
|
||||
return s if s else 0
|
||||
|
||||
|
||||
async def get_user_best_score_in_beatmap(
|
||||
session: AsyncSession,
|
||||
beatmap: int,
|
||||
user: int,
|
||||
mode: GameMode | None = None,
|
||||
) -> Score | None:
|
||||
return (
|
||||
await session.exec(
|
||||
Score.select_clause(False)
|
||||
.where(
|
||||
Score.gamemode == mode if mode is not None else True,
|
||||
Score.beatmap_id == beatmap,
|
||||
Score.user_id == user,
|
||||
)
|
||||
.order_by(col(Score.total_score).desc())
|
||||
)
|
||||
).first()
|
||||
|
||||
|
||||
async def get_user_best_pp_in_beatmap(
|
||||
session: AsyncSession,
|
||||
beatmap: int,
|
||||
user: int,
|
||||
mode: GameMode,
|
||||
) -> BestScore | None:
|
||||
return (
|
||||
await session.exec(
|
||||
select(BestScore).where(
|
||||
BestScore.beatmap_id == beatmap,
|
||||
BestScore.user_id == user,
|
||||
BestScore.gamemode == mode,
|
||||
)
|
||||
)
|
||||
).first()
|
||||
|
||||
|
||||
async def get_user_best_pp(
|
||||
session: AsyncSession,
|
||||
user: int,
|
||||
limit: int = 200,
|
||||
) -> Sequence[BestScore]:
|
||||
return (
|
||||
await session.exec(
|
||||
select(BestScore)
|
||||
.where(BestScore.user_id == user)
|
||||
.order_by(col(BestScore.pp).desc())
|
||||
.limit(limit)
|
||||
)
|
||||
).all()
|
||||
|
||||
|
||||
async def process_user(
|
||||
session: AsyncSession, user: User, score: Score, ranked: bool = False
|
||||
):
|
||||
previous_score_best = await get_user_best_score_in_beatmap(
|
||||
session, score.beatmap_id, user.id, score.gamemode
|
||||
)
|
||||
statistics = None
|
||||
add_to_db = False
|
||||
for i in user.lazer_statistics:
|
||||
if i.mode == score.gamemode.value:
|
||||
statistics = i
|
||||
break
|
||||
if statistics is None:
|
||||
statistics = LazerUserStatistics(
|
||||
mode=score.gamemode.value,
|
||||
user_id=user.id,
|
||||
)
|
||||
add_to_db = True
|
||||
|
||||
# pc, pt, tth, tts
|
||||
statistics.total_score += score.total_score
|
||||
difference = (
|
||||
score.total_score - previous_score_best.total_score
|
||||
if previous_score_best and previous_score_best.id != score.id
|
||||
else score.total_score
|
||||
)
|
||||
if difference > 0 and score.passed and ranked:
|
||||
match score.rank:
|
||||
case Rank.X:
|
||||
statistics.grade_ss += 1
|
||||
case Rank.XH:
|
||||
statistics.grade_ssh += 1
|
||||
case Rank.S:
|
||||
statistics.grade_s += 1
|
||||
case Rank.SH:
|
||||
statistics.grade_sh += 1
|
||||
case Rank.A:
|
||||
statistics.grade_a += 1
|
||||
if previous_score_best is not None:
|
||||
match previous_score_best.rank:
|
||||
case Rank.X:
|
||||
statistics.grade_ss -= 1
|
||||
case Rank.XH:
|
||||
statistics.grade_ssh -= 1
|
||||
case Rank.S:
|
||||
statistics.grade_s -= 1
|
||||
case Rank.SH:
|
||||
statistics.grade_sh -= 1
|
||||
case Rank.A:
|
||||
statistics.grade_a -= 1
|
||||
statistics.ranked_score += difference
|
||||
statistics.level_current = calculate_score_to_level(statistics.ranked_score)
|
||||
statistics.maximum_combo = max(statistics.maximum_combo, score.max_combo)
|
||||
statistics.play_count += 1
|
||||
statistics.play_time += int((score.ended_at - score.started_at).total_seconds())
|
||||
statistics.total_hits += (
|
||||
score.n300 + score.n100 + score.n50 + score.ngeki + score.nkatu
|
||||
)
|
||||
|
||||
if score.passed and ranked:
|
||||
best_pp_scores = await get_user_best_pp(session, user.id)
|
||||
pp_sum = 0.0
|
||||
acc_sum = 0.0
|
||||
for i, bp in enumerate(best_pp_scores):
|
||||
pp_sum += calculate_weighted_pp(bp.pp, i)
|
||||
acc_sum += calculate_weighted_acc(bp.acc, i)
|
||||
if len(best_pp_scores):
|
||||
# https://github.com/ppy/osu-queue-score-statistics/blob/c538ae/osu.Server.Queues.ScoreStatisticsProcessor/Helpers/UserTotalPerformanceAggregateHelper.cs#L41-L45
|
||||
acc_sum *= 100 / (20 * (1 - math.pow(0.95, len(best_pp_scores))))
|
||||
acc_sum = clamp(acc_sum, 0.0, 100.0)
|
||||
statistics.pp = pp_sum
|
||||
statistics.hit_accuracy = acc_sum
|
||||
|
||||
statistics.updated_at = datetime.now(UTC)
|
||||
|
||||
if add_to_db:
|
||||
session.add(statistics)
|
||||
await session.commit()
|
||||
await session.refresh(user)
|
||||
|
||||
|
||||
async def process_score(
|
||||
user: User,
|
||||
beatmap_id: int,
|
||||
ranked: bool,
|
||||
score_token: ScoreToken,
|
||||
info: SoloScoreSubmissionInfo,
|
||||
fetcher: "Fetcher",
|
||||
session: AsyncSession,
|
||||
redis: Redis,
|
||||
) -> Score:
|
||||
score = Score(
|
||||
accuracy=info.accuracy,
|
||||
max_combo=info.max_combo,
|
||||
# maximum_statistics=info.maximum_statistics,
|
||||
mods=info.mods,
|
||||
passed=info.passed,
|
||||
rank=info.rank,
|
||||
total_score=info.total_score,
|
||||
total_score_without_mods=info.total_score_without_mods,
|
||||
beatmap_id=beatmap_id,
|
||||
ended_at=datetime.now(UTC),
|
||||
gamemode=INT_TO_MODE[info.ruleset_id],
|
||||
started_at=score_token.created_at,
|
||||
user_id=user.id,
|
||||
preserve=info.passed,
|
||||
map_md5=score_token.beatmap.checksum,
|
||||
has_replay=False,
|
||||
type="solo",
|
||||
n300=info.statistics.get(HitResult.GREAT, 0),
|
||||
n100=info.statistics.get(HitResult.OK, 0),
|
||||
n50=info.statistics.get(HitResult.MEH, 0),
|
||||
nmiss=info.statistics.get(HitResult.MISS, 0),
|
||||
ngeki=info.statistics.get(HitResult.PERFECT, 0),
|
||||
nkatu=info.statistics.get(HitResult.GOOD, 0),
|
||||
nlarge_tick_miss=info.statistics.get(HitResult.LARGE_TICK_MISS, 0),
|
||||
nsmall_tick_hit=info.statistics.get(HitResult.SMALL_TICK_HIT, 0),
|
||||
nlarge_tick_hit=info.statistics.get(HitResult.LARGE_TICK_HIT, 0),
|
||||
nslider_tail_hit=info.statistics.get(HitResult.SLIDER_TAIL_HIT, 0),
|
||||
)
|
||||
if info.passed and ranked and mods_can_get_pp(info.ruleset_id, info.mods):
|
||||
beatmap_raw = await fetcher.get_or_fetch_beatmap_raw(redis, beatmap_id)
|
||||
pp = await asyncio.get_event_loop().run_in_executor(
|
||||
None, calculate_pp, score, beatmap_raw
|
||||
)
|
||||
score.pp = pp
|
||||
session.add(score)
|
||||
user_id = user.id
|
||||
await session.commit()
|
||||
await session.refresh(score)
|
||||
if score.passed and ranked:
|
||||
previous_pp_best = await get_user_best_pp_in_beatmap(
|
||||
session, beatmap_id, user_id, score.gamemode
|
||||
)
|
||||
if previous_pp_best is None or score.pp > previous_pp_best.pp:
|
||||
assert score.id
|
||||
best_score = BestScore(
|
||||
user_id=user_id,
|
||||
score_id=score.id,
|
||||
beatmap_id=beatmap_id,
|
||||
gamemode=score.gamemode,
|
||||
pp=score.pp,
|
||||
acc=score.accuracy,
|
||||
)
|
||||
session.add(best_score)
|
||||
session.delete(previous_pp_best) if previous_pp_best else None
|
||||
await session.commit()
|
||||
await session.refresh(score)
|
||||
await session.refresh(score_token)
|
||||
await session.refresh(user)
|
||||
return score
|
||||
|
||||
@@ -102,8 +102,8 @@ class User(SQLModel, table=True):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def all_select_clause(cls):
|
||||
return select(cls).options(
|
||||
def all_select_option(cls):
|
||||
return (
|
||||
joinedload(cls.lazer_profile), # pyright: ignore[reportArgumentType]
|
||||
joinedload(cls.lazer_counts), # pyright: ignore[reportArgumentType]
|
||||
joinedload(cls.daily_challenge_stats), # pyright: ignore[reportArgumentType]
|
||||
@@ -121,6 +121,10 @@ class User(SQLModel, table=True):
|
||||
selectinload(cls.lazer_replays_watched), # pyright: ignore[reportArgumentType]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def all_select_clause(cls):
|
||||
return select(cls).options(*cls.all_select_option())
|
||||
|
||||
|
||||
# ============================================
|
||||
# Lazer API 专用表模型
|
||||
|
||||
@@ -4,6 +4,7 @@ from ._base import BaseFetcher
|
||||
|
||||
from httpx import AsyncClient
|
||||
from loguru import logger
|
||||
import redis
|
||||
|
||||
|
||||
class OsuDotDirectFetcher(BaseFetcher):
|
||||
@@ -17,3 +18,12 @@ class OsuDotDirectFetcher(BaseFetcher):
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.text
|
||||
|
||||
async def get_or_fetch_beatmap_raw(
|
||||
self, redis: redis.Redis, beatmap_id: int
|
||||
) -> str:
|
||||
if redis.exists(f"beatmap:{beatmap_id}:raw"):
|
||||
return redis.get(f"beatmap:{beatmap_id}:raw") # pyright: ignore[reportReturnType]
|
||||
raw = await self.get_beatmap_raw(beatmap_id)
|
||||
redis.set(f"beatmap:{beatmap_id}:raw", raw, ex=60 * 60 * 24)
|
||||
return raw
|
||||
|
||||
@@ -105,3 +105,65 @@ def mods_to_int(mods: list[APIMod]) -> int:
|
||||
for mod in mods:
|
||||
sum_ |= API_MOD_TO_LEGACY.get(mod["acronym"], 0)
|
||||
return sum_
|
||||
|
||||
|
||||
NO_CHECK = "DO_NO_CHECK"
|
||||
|
||||
# FIXME: 这里为空表示了两种情况:mod 没有配置项;任何时候都可以获得 pp
|
||||
# 如果是后者,则 mod 更新的时候可能会误判。
|
||||
COMMON_CONFIG: dict[str, dict] = {
|
||||
"EZ": {"retries": 2},
|
||||
"NF": {},
|
||||
"HT": {"speed_change": 0.75, "adjust_pitch": NO_CHECK},
|
||||
"DC": {"speed_change": 0.75},
|
||||
"HR": {},
|
||||
"SD": {},
|
||||
"PF": {},
|
||||
"HD": {},
|
||||
"DT": {"speed_change": 1.5, "adjust_pitch": NO_CHECK},
|
||||
"NC": {"speed_change": 1.5},
|
||||
"FL": {"size_multiplier": 1.0, "combo_based_size": True},
|
||||
"AC": {},
|
||||
"MU": {},
|
||||
"TD": {},
|
||||
}
|
||||
|
||||
RANKED_MODS: dict[int, dict[str, dict]] = {
|
||||
0: COMMON_CONFIG,
|
||||
1: COMMON_CONFIG,
|
||||
2: COMMON_CONFIG,
|
||||
3: COMMON_CONFIG,
|
||||
}
|
||||
# osu
|
||||
RANKED_MODS[0]["HD"]["only_fade_approach_circles"] = False
|
||||
RANKED_MODS[0]["FL"]["follow_delay"] = 1.0
|
||||
RANKED_MODS[0]["BL"] = {}
|
||||
RANKED_MODS[0]["NS"] = {}
|
||||
RANKED_MODS[0]["SO"] = {}
|
||||
RANKED_MODS[0]["TC"] = {}
|
||||
# taiko
|
||||
del RANKED_MODS[1]["EZ"]["retries"]
|
||||
# catch
|
||||
RANKED_MODS[2]["NS"] = {}
|
||||
# mania
|
||||
del RANKED_MODS[3]["HR"]
|
||||
RANKED_MODS[3]["FL"]["combo_based_size"] = False
|
||||
RANKED_MODS[3]["MR"] = {}
|
||||
for i in range(4, 10):
|
||||
RANKED_MODS[3][f"{i}K"] = {}
|
||||
|
||||
|
||||
def mods_can_get_pp(ruleset_id: int, mods: list[APIMod]) -> bool:
|
||||
ranked_mods = RANKED_MODS[ruleset_id]
|
||||
for mod in mods:
|
||||
mod["settings"] = mod.get("settings", {})
|
||||
if (settings := ranked_mods.get(mod["acronym"])) is None:
|
||||
return False
|
||||
if settings == {}:
|
||||
continue
|
||||
for setting, value in mod["settings"].items():
|
||||
if (expected_value := settings.get(setting)) is None:
|
||||
return False
|
||||
if expected_value != NO_CHECK and value != expected_value:
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -44,6 +44,16 @@ class Rank(str, Enum):
|
||||
D = "D"
|
||||
F = "F"
|
||||
|
||||
@property
|
||||
def in_statisctics(self):
|
||||
return self in {
|
||||
Rank.X,
|
||||
Rank.XH,
|
||||
Rank.S,
|
||||
Rank.SH,
|
||||
Rank.A,
|
||||
}
|
||||
|
||||
|
||||
# https://github.com/ppy/osu/blob/master/osu.Game/Rulesets/Scoring/HitResult.cs
|
||||
class HitResult(str, Enum):
|
||||
|
||||
@@ -3,7 +3,6 @@ from __future__ import annotations
|
||||
import datetime
|
||||
from typing import Any, get_origin
|
||||
|
||||
import msgpack
|
||||
from pydantic import (
|
||||
BaseModel,
|
||||
ConfigDict,
|
||||
@@ -24,11 +23,11 @@ def serialize_to_list(value: BaseModel) -> list[Any]:
|
||||
elif anno and issubclass(anno, list):
|
||||
data.append(
|
||||
TypeAdapter(
|
||||
info.annotation,
|
||||
info.annotation, config=ConfigDict(arbitrary_types_allowed=True)
|
||||
).dump_python(v)
|
||||
)
|
||||
elif isinstance(v, datetime.datetime):
|
||||
data.append([msgpack.ext.Timestamp.from_datetime(v), 0])
|
||||
data.append([v, 0])
|
||||
else:
|
||||
data.append(v)
|
||||
return data
|
||||
|
||||
@@ -11,15 +11,8 @@ from .score import (
|
||||
)
|
||||
from .signalr import MessagePackArrayModel, UserState
|
||||
|
||||
import msgpack
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
|
||||
class APIMod(MessagePackArrayModel):
|
||||
acronym: str
|
||||
settings: dict[str, Any] | list = Field(
|
||||
default_factory=dict
|
||||
) # FIXME: with settings
|
||||
from msgpack_lazer_api import APIMod
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||
|
||||
|
||||
class SpectatedUserState(IntEnum):
|
||||
@@ -32,6 +25,8 @@ class SpectatedUserState(IntEnum):
|
||||
|
||||
|
||||
class SpectatorState(MessagePackArrayModel):
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
|
||||
beatmap_id: int | None = None
|
||||
ruleset_id: int | None = None # 0,1,2,3
|
||||
mods: list[APIMod] = Field(default_factory=list)
|
||||
@@ -58,6 +53,8 @@ class ScoreProcessorStatistics(MessagePackArrayModel):
|
||||
|
||||
|
||||
class FrameHeader(MessagePackArrayModel):
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
|
||||
total_score: int
|
||||
acc: float
|
||||
combo: int
|
||||
@@ -70,10 +67,8 @@ class FrameHeader(MessagePackArrayModel):
|
||||
@field_validator("received_time", mode="before")
|
||||
@classmethod
|
||||
def validate_timestamp(cls, v: Any) -> datetime.datetime:
|
||||
if isinstance(v, msgpack.ext.Timestamp):
|
||||
return v.to_datetime()
|
||||
if isinstance(v, list):
|
||||
return v[0].to_datetime()
|
||||
return v[0]
|
||||
if isinstance(v, datetime.datetime):
|
||||
return v
|
||||
if isinstance(v, int | float):
|
||||
@@ -111,6 +106,8 @@ class APIUser(BaseModel):
|
||||
|
||||
|
||||
class ScoreInfo(BaseModel):
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
|
||||
mods: list[APIMod]
|
||||
user: APIUser
|
||||
ruleset: int
|
||||
|
||||
@@ -2,16 +2,15 @@ from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
|
||||
from app.database import (
|
||||
LazerUserAchievement,
|
||||
Team as Team,
|
||||
)
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .score import GameMode
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from app.database import LazerUserAchievement, Team
|
||||
|
||||
|
||||
class PlayStyle(str, Enum):
|
||||
MOUSE = "mouse"
|
||||
@@ -83,7 +82,11 @@ class UserAchievement(BaseModel):
|
||||
achievement_id: int
|
||||
|
||||
# 添加数据库模型转换方法
|
||||
def to_db_model(self, user_id: int) -> LazerUserAchievement:
|
||||
def to_db_model(self, user_id: int) -> "LazerUserAchievement":
|
||||
from app.database import (
|
||||
LazerUserAchievement,
|
||||
)
|
||||
|
||||
return LazerUserAchievement(
|
||||
user_id=user_id,
|
||||
achievement_id=self.achievement_id,
|
||||
@@ -207,7 +210,7 @@ class User(BaseModel):
|
||||
rank_history: RankHistory | None = None
|
||||
rankHistory: RankHistory | None = None # 兼容性别名
|
||||
replays_watched_counts: list[dict] = []
|
||||
team: Team | None = None
|
||||
team: "Team | None" = None
|
||||
user_achievements: list[UserAchievement] = []
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import asyncio
|
||||
import hashlib
|
||||
import json
|
||||
|
||||
from app.calculator import calculate_beatmap_attribute
|
||||
from app.database import (
|
||||
Beatmap,
|
||||
BeatmapResp,
|
||||
@@ -20,7 +21,6 @@ from app.models.score import (
|
||||
INT_TO_MODE,
|
||||
GameMode,
|
||||
)
|
||||
from app.utils import calculate_beatmap_attribute
|
||||
|
||||
from .api_router import router
|
||||
|
||||
@@ -157,7 +157,7 @@ async def get_beatmap_attributes(
|
||||
return BeatmapAttributes.model_validate_json(redis.get(key)) # pyright: ignore[reportArgumentType]
|
||||
|
||||
try:
|
||||
resp = await fetcher.get_beatmap_raw(beatmap)
|
||||
resp = await fetcher.get_or_fetch_beatmap_raw(redis, beatmap)
|
||||
try:
|
||||
attr = await asyncio.get_event_loop().run_in_executor(
|
||||
None, calculate_beatmap_attribute, resp, ruleset, mods_
|
||||
|
||||
@@ -8,6 +8,7 @@ from app.dependencies.user import get_current_user
|
||||
from .api_router import router
|
||||
|
||||
from fastapi import Depends, HTTPException, Query, Request
|
||||
from sqlalchemy.orm import joinedload
|
||||
from sqlmodel import select
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
|
||||
@@ -25,7 +26,9 @@ async def get_relationship(
|
||||
else RelationshipType.BLOCK
|
||||
)
|
||||
relationships = await db.exec(
|
||||
select(Relationship).where(
|
||||
select(Relationship)
|
||||
.options(joinedload(Relationship.target).options(*DBUser.all_select_option())) # pyright: ignore[reportArgumentType]
|
||||
.where(
|
||||
Relationship.user_id == current_user.id,
|
||||
Relationship.type == relationship_type,
|
||||
)
|
||||
@@ -67,7 +70,8 @@ async def add_relationship(
|
||||
type=relationship_type,
|
||||
)
|
||||
db.add(relationship)
|
||||
if relationship.type == RelationshipType.BLOCK:
|
||||
origin_type = relationship.type
|
||||
if origin_type == RelationshipType.BLOCK:
|
||||
target_relationship = (
|
||||
await db.exec(
|
||||
select(Relationship).where(
|
||||
@@ -78,9 +82,22 @@ async def add_relationship(
|
||||
).first()
|
||||
if target_relationship and target_relationship.type == RelationshipType.FOLLOW:
|
||||
await db.delete(target_relationship)
|
||||
current_user_id = current_user.id
|
||||
await db.commit()
|
||||
await db.refresh(relationship)
|
||||
if relationship.type == RelationshipType.FOLLOW:
|
||||
if origin_type == RelationshipType.FOLLOW:
|
||||
relationship = (
|
||||
await db.exec(
|
||||
select(Relationship)
|
||||
.where(
|
||||
Relationship.user_id == current_user_id,
|
||||
Relationship.target_id == target,
|
||||
)
|
||||
.options(
|
||||
joinedload(Relationship.target).options(*DBUser.all_select_option()) # pyright: ignore[reportArgumentType]
|
||||
)
|
||||
)
|
||||
).first()
|
||||
assert relationship, "Relationship should exist after commit"
|
||||
return await RelationshipResp.from_db(db, relationship)
|
||||
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
|
||||
from app.database import (
|
||||
User as DBUser,
|
||||
)
|
||||
from app.database.score import Score, ScoreResp
|
||||
from app.database.beatmap import Beatmap
|
||||
from app.database.score import Score, ScoreResp, process_score, process_user
|
||||
from app.database.score_token import ScoreToken, ScoreTokenResp
|
||||
from app.dependencies.database import get_db
|
||||
from app.dependencies.database import get_db, get_redis
|
||||
from app.dependencies.fetcher import get_fetcher
|
||||
from app.dependencies.user import get_current_user
|
||||
from app.models.beatmap import BeatmapRankStatus
|
||||
from app.models.score import (
|
||||
INT_TO_MODE,
|
||||
GameMode,
|
||||
HitResult,
|
||||
Rank,
|
||||
SoloScoreSubmissionInfo,
|
||||
)
|
||||
@@ -21,6 +21,7 @@ from .api_router import router
|
||||
|
||||
from fastapi import Depends, Form, HTTPException, Query
|
||||
from pydantic import BaseModel
|
||||
from redis import Redis
|
||||
from sqlalchemy.orm import joinedload
|
||||
from sqlmodel import col, select, true
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
@@ -70,8 +71,10 @@ async def get_beatmap_scores(
|
||||
).first()
|
||||
|
||||
return BeatmapScores(
|
||||
scores=[await ScoreResp.from_db(db, score) for score in all_scores],
|
||||
userScore=await ScoreResp.from_db(db, user_score) if user_score else None,
|
||||
scores=[await ScoreResp.from_db(db, score, score.user) for score in all_scores],
|
||||
userScore=await ScoreResp.from_db(db, user_score, user_score.user)
|
||||
if user_score
|
||||
else None,
|
||||
)
|
||||
|
||||
|
||||
@@ -100,7 +103,7 @@ async def get_user_beatmap_score(
|
||||
)
|
||||
user_score = (
|
||||
await db.exec(
|
||||
Score.select_clause()
|
||||
Score.select_clause(True)
|
||||
.where(
|
||||
Score.gamemode == mode if mode is not None else True,
|
||||
Score.beatmap_id == beatmap,
|
||||
@@ -117,7 +120,7 @@ async def get_user_beatmap_score(
|
||||
else:
|
||||
return BeatmapUserScore(
|
||||
position=user_score.position if user_score.position is not None else 0,
|
||||
score=await ScoreResp.from_db(db, user_score),
|
||||
score=await ScoreResp.from_db(db, user_score, user_score.user),
|
||||
)
|
||||
|
||||
|
||||
@@ -150,7 +153,9 @@ async def get_user_all_beatmap_scores(
|
||||
)
|
||||
).all()
|
||||
|
||||
return [await ScoreResp.from_db(db, score) for score in all_user_scores]
|
||||
return [
|
||||
await ScoreResp.from_db(db, score, current_user) for score in all_user_scores
|
||||
]
|
||||
|
||||
|
||||
@router.post(
|
||||
@@ -187,6 +192,8 @@ async def submit_solo_score(
|
||||
info: SoloScoreSubmissionInfo,
|
||||
current_user: DBUser = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
redis: Redis = Depends(get_redis),
|
||||
fetcher=Depends(get_fetcher),
|
||||
):
|
||||
if not info.passed:
|
||||
info.rank = Rank.F
|
||||
@@ -214,40 +221,33 @@ async def submit_solo_score(
|
||||
if not score:
|
||||
raise HTTPException(status_code=404, detail="Score not found")
|
||||
else:
|
||||
score = Score(
|
||||
accuracy=info.accuracy,
|
||||
max_combo=info.max_combo,
|
||||
# maximum_statistics=info.maximum_statistics,
|
||||
mods=info.mods,
|
||||
passed=info.passed,
|
||||
rank=info.rank,
|
||||
total_score=info.total_score,
|
||||
total_score_without_mods=info.total_score_without_mods,
|
||||
beatmap_id=beatmap,
|
||||
ended_at=datetime.datetime.now(datetime.UTC),
|
||||
gamemode=INT_TO_MODE[info.ruleset_id],
|
||||
started_at=score_token.created_at,
|
||||
user_id=current_user.id,
|
||||
preserve=info.passed,
|
||||
map_md5=score_token.beatmap.checksum,
|
||||
has_replay=False,
|
||||
pp=info.pp,
|
||||
type="solo",
|
||||
n300=info.statistics.get(HitResult.GREAT, 0),
|
||||
n100=info.statistics.get(HitResult.OK, 0),
|
||||
n50=info.statistics.get(HitResult.MEH, 0),
|
||||
nmiss=info.statistics.get(HitResult.MISS, 0),
|
||||
ngeki=info.statistics.get(HitResult.PERFECT, 0),
|
||||
nkatu=info.statistics.get(HitResult.GOOD, 0),
|
||||
beatmap_status = (
|
||||
await db.exec(
|
||||
select(Beatmap.beatmap_status).where(Beatmap.id == beatmap)
|
||||
)
|
||||
).first()
|
||||
if beatmap_status is None:
|
||||
raise HTTPException(status_code=404, detail="Beatmap not found")
|
||||
ranked = beatmap_status in {
|
||||
BeatmapRankStatus.RANKED,
|
||||
BeatmapRankStatus.APPROVED,
|
||||
}
|
||||
score = await process_score(
|
||||
current_user,
|
||||
beatmap,
|
||||
ranked,
|
||||
score_token,
|
||||
info,
|
||||
fetcher,
|
||||
db,
|
||||
redis,
|
||||
)
|
||||
db.add(score)
|
||||
await db.commit()
|
||||
await db.refresh(score)
|
||||
await db.refresh(current_user)
|
||||
score_id = score.id
|
||||
score_token.score_id = score_id
|
||||
await db.commit()
|
||||
await process_user(db, current_user, score, ranked)
|
||||
score = (
|
||||
await db.exec(Score.select_clause().where(Score.id == score_id))
|
||||
).first()
|
||||
assert score is not None
|
||||
return await ScoreResp.from_db(db, score)
|
||||
return await ScoreResp.from_db(db, score, current_user)
|
||||
|
||||
@@ -2,57 +2,51 @@ from __future__ import annotations
|
||||
|
||||
from typing import Literal
|
||||
|
||||
from app.database import (
|
||||
User as DBUser,
|
||||
)
|
||||
from app.dependencies import get_current_user
|
||||
from app.database import User as DBUser
|
||||
from app.dependencies.database import get_db
|
||||
from app.models.score import INT_TO_MODE
|
||||
from app.models.user import (
|
||||
User as ApiUser,
|
||||
)
|
||||
from app.models.user import User as ApiUser
|
||||
from app.utils import convert_db_user_to_api_user
|
||||
|
||||
from .api_router import router
|
||||
|
||||
from fastapi import Depends, HTTPException, Query
|
||||
from pydantic import BaseModel
|
||||
from sqlmodel import select
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
from sqlmodel.sql.expression import col
|
||||
|
||||
|
||||
@router.get("/users/{user}/{ruleset}", response_model=ApiUser)
|
||||
@router.get("/users/{user}", response_model=ApiUser)
|
||||
async def get_user_info_default(
|
||||
user: str,
|
||||
ruleset: Literal["osu", "taiko", "fruits", "mania"] = "osu",
|
||||
current_user: DBUser = Depends(get_current_user),
|
||||
session: AsyncSession = Depends(get_db),
|
||||
):
|
||||
searched_user = (
|
||||
await session.exec(
|
||||
DBUser.all_select_clause().where(
|
||||
DBUser.id == int(user)
|
||||
if user.isdigit()
|
||||
else DBUser.name == user.removeprefix("@")
|
||||
)
|
||||
)
|
||||
).first()
|
||||
if not searched_user:
|
||||
raise HTTPException(404, detail="User not found")
|
||||
return await convert_db_user_to_api_user(searched_user, ruleset=ruleset)
|
||||
# ---------- Shared Utility ----------
|
||||
async def get_user_by_lookup(
|
||||
db: AsyncSession, lookup: str, key: str = "id"
|
||||
) -> DBUser | None:
|
||||
"""根据查找方式获取用户"""
|
||||
if key == "id":
|
||||
try:
|
||||
user_id = int(lookup)
|
||||
result = await db.exec(select(DBUser).where(DBUser.id == user_id))
|
||||
return result.first()
|
||||
except ValueError:
|
||||
return None
|
||||
elif key == "username":
|
||||
result = await db.exec(select(DBUser).where(DBUser.name == lookup))
|
||||
return result.first()
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
# ---------- Batch Users ----------
|
||||
class BatchUserResponse(BaseModel):
|
||||
users: list[ApiUser]
|
||||
|
||||
|
||||
@router.get("/users", response_model=BatchUserResponse)
|
||||
@router.get("/users/lookup", response_model=BatchUserResponse)
|
||||
@router.get("/users/lookup/", response_model=BatchUserResponse)
|
||||
async def get_users(
|
||||
user_ids: list[int] = Query(default_factory=list, alias="ids[]"),
|
||||
include_variant_statistics: bool = Query(default=False), # TODO
|
||||
current_user: DBUser = Depends(get_current_user),
|
||||
include_variant_statistics: bool = Query(default=False), # TODO: future use
|
||||
session: AsyncSession = Depends(get_db),
|
||||
):
|
||||
if user_ids:
|
||||
@@ -68,8 +62,64 @@ async def get_users(
|
||||
return BatchUserResponse(
|
||||
users=[
|
||||
await convert_db_user_to_api_user(
|
||||
searched_user, ruleset=INT_TO_MODE[current_user.preferred_mode].value
|
||||
searched_user, ruleset=INT_TO_MODE[searched_user.preferred_mode].value
|
||||
)
|
||||
for searched_user in searched_users
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
# # ---------- Individual User ----------
|
||||
# @router.get("/users/{user_lookup}/{mode}", response_model=ApiUser)
|
||||
# @router.get("/users/{user_lookup}/{mode}/", response_model=ApiUser)
|
||||
# async def get_user_with_mode(
|
||||
# user_lookup: str,
|
||||
# mode: Literal["osu", "taiko", "fruits", "mania"],
|
||||
# key: Literal["id", "username"] = Query("id"),
|
||||
# current_user: DBUser = Depends(get_current_user),
|
||||
# db: AsyncSession = Depends(get_db),
|
||||
# ):
|
||||
# """获取指定游戏模式的用户信息"""
|
||||
# user = await get_user_by_lookup(db, user_lookup, key)
|
||||
# if not user:
|
||||
# raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
# return await convert_db_user_to_api_user(user, mode)
|
||||
|
||||
|
||||
# @router.get("/users/{user_lookup}", response_model=ApiUser)
|
||||
# @router.get("/users/{user_lookup}/", response_model=ApiUser)
|
||||
# async def get_user_default(
|
||||
# user_lookup: str,
|
||||
# key: Literal["id", "username"] = Query("id"),
|
||||
# current_user: DBUser = Depends(get_current_user),
|
||||
# db: AsyncSession = Depends(get_db),
|
||||
# ):
|
||||
# """获取用户信息(默认使用osu模式,但包含所有模式的统计信息)"""
|
||||
# user = await get_user_by_lookup(db, user_lookup, key)
|
||||
# if not user:
|
||||
# raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
# return await convert_db_user_to_api_user(user, "osu")
|
||||
|
||||
|
||||
@router.get("/users/{user}/{ruleset}", response_model=ApiUser)
|
||||
@router.get("/users/{user}/", response_model=ApiUser)
|
||||
@router.get("/users/{user}", response_model=ApiUser)
|
||||
async def get_user_info(
|
||||
user: str,
|
||||
ruleset: Literal["osu", "taiko", "fruits", "mania"] = "osu",
|
||||
session: AsyncSession = Depends(get_db),
|
||||
):
|
||||
searched_user = (
|
||||
await session.exec(
|
||||
DBUser.all_select_clause().where(
|
||||
DBUser.id == int(user)
|
||||
if user.isdigit()
|
||||
else DBUser.name == user.removeprefix("@")
|
||||
)
|
||||
)
|
||||
).first()
|
||||
if not searched_user:
|
||||
raise HTTPException(404, detail="User not found")
|
||||
return await convert_db_user_to_api_user(searched_user, ruleset=ruleset)
|
||||
|
||||
@@ -8,7 +8,7 @@ from typing import (
|
||||
Protocol as TypingProtocol,
|
||||
)
|
||||
|
||||
import msgpack
|
||||
import msgpack_lazer_api as m
|
||||
|
||||
SEP = b"\x1e"
|
||||
|
||||
@@ -104,11 +104,7 @@ class MsgpackProtocol:
|
||||
def decode(input: bytes) -> list[Packet]:
|
||||
length, offset = MsgpackProtocol._decode_varint(input)
|
||||
message_data = input[offset : offset + length]
|
||||
# FIXME: custom deserializer for APIMod
|
||||
# https://github.com/ppy/osu/blob/master/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs
|
||||
unpacked = msgpack.unpackb(
|
||||
message_data, raw=False, strict_map_key=False, use_list=True
|
||||
)
|
||||
unpacked = m.decode(message_data)
|
||||
packet_type = PacketType(unpacked[0])
|
||||
if packet_type not in PACKETS:
|
||||
raise ValueError(f"Unknown packet type: {packet_type}")
|
||||
@@ -180,7 +176,7 @@ class MsgpackProtocol:
|
||||
)
|
||||
elif isinstance(packet, PingPacket):
|
||||
payload.pop(-1)
|
||||
data = msgpack.packb(payload, use_bin_type=True, datetime=True)
|
||||
data = m.encode(payload)
|
||||
return MsgpackProtocol._encode_varint(len(data)) + data
|
||||
|
||||
|
||||
|
||||
119
app/utils.py
119
app/utils.py
@@ -8,12 +8,10 @@ from app.database import (
|
||||
LazerUserStatistics,
|
||||
User as DBUser,
|
||||
)
|
||||
from app.models.beatmap import BeatmapAttributes
|
||||
from app.models.mods import APIMod
|
||||
from app.models.score import GameMode
|
||||
from app.models.user import (
|
||||
Country,
|
||||
Cover,
|
||||
DailyChallengeStats,
|
||||
GradeCounts,
|
||||
Kudosu,
|
||||
Level,
|
||||
@@ -25,8 +23,6 @@ from app.models.user import (
|
||||
UserAchievement,
|
||||
)
|
||||
|
||||
import rosu_pp_py as rosu
|
||||
|
||||
|
||||
def unix_timestamp_to_windows(timestamp: int) -> int:
|
||||
"""Convert a Unix timestamp to a Windows timestamp."""
|
||||
@@ -115,34 +111,37 @@ async def convert_db_user_to_api_user(db_user: DBUser, ruleset: str = "osu") ->
|
||||
|
||||
# 转换所有模式的统计信息
|
||||
statistics_rulesets = {}
|
||||
for stat in db_user.statistics:
|
||||
statistics_rulesets[stat.mode] = Statistics(
|
||||
count_100=stat.count_100,
|
||||
count_300=stat.count_300,
|
||||
count_50=stat.count_50,
|
||||
count_miss=stat.count_miss,
|
||||
level=Level(current=stat.level_current, progress=stat.level_progress),
|
||||
global_rank=stat.global_rank,
|
||||
global_rank_exp=stat.global_rank_exp,
|
||||
pp=stat.pp,
|
||||
pp_exp=stat.pp_exp,
|
||||
ranked_score=stat.ranked_score,
|
||||
hit_accuracy=stat.hit_accuracy,
|
||||
play_count=stat.play_count,
|
||||
play_time=stat.play_time,
|
||||
total_score=stat.total_score,
|
||||
total_hits=stat.total_hits,
|
||||
maximum_combo=stat.maximum_combo,
|
||||
replays_watched_by_others=stat.replays_watched_by_others,
|
||||
is_ranked=stat.is_ranked,
|
||||
grade_counts=GradeCounts(
|
||||
ss=stat.grade_ss,
|
||||
ssh=stat.grade_ssh,
|
||||
s=stat.grade_s,
|
||||
sh=stat.grade_sh,
|
||||
a=stat.grade_a,
|
||||
),
|
||||
)
|
||||
if db_user.lazer_statistics:
|
||||
for stat in db_user.lazer_statistics:
|
||||
statistics_rulesets[stat.mode] = Statistics(
|
||||
count_100=stat.count_100,
|
||||
count_300=stat.count_300,
|
||||
count_50=stat.count_50,
|
||||
count_miss=stat.count_miss,
|
||||
level=Level(current=stat.level_current, progress=stat.level_progress),
|
||||
global_rank=stat.global_rank,
|
||||
global_rank_exp=stat.global_rank_exp,
|
||||
pp=float(stat.pp) if stat.pp else 0.0,
|
||||
pp_exp=float(stat.pp_exp) if stat.pp_exp else 0.0,
|
||||
ranked_score=stat.ranked_score,
|
||||
hit_accuracy=float(stat.hit_accuracy) if stat.hit_accuracy else 0.0,
|
||||
play_count=stat.play_count,
|
||||
play_time=stat.play_time,
|
||||
total_score=stat.total_score,
|
||||
total_hits=stat.total_hits,
|
||||
maximum_combo=stat.maximum_combo,
|
||||
replays_watched_by_others=stat.replays_watched_by_others,
|
||||
is_ranked=stat.is_ranked,
|
||||
grade_counts=GradeCounts(
|
||||
ss=stat.grade_ss,
|
||||
ssh=stat.grade_ssh,
|
||||
s=stat.grade_s,
|
||||
sh=stat.grade_sh,
|
||||
a=stat.grade_a,
|
||||
),
|
||||
country_rank=stat.country_rank,
|
||||
rank={"country": stat.country_rank} if stat.country_rank else None,
|
||||
)
|
||||
|
||||
# 转换国家信息
|
||||
country = Country(code=user_country_code, name=get_country_name(user_country_code))
|
||||
@@ -401,7 +400,36 @@ async def convert_db_user_to_api_user(db_user: DBUser, ruleset: str = "osu") ->
|
||||
active_tournament_banners=active_tournament_banners,
|
||||
badges=badges,
|
||||
current_season_stats=None,
|
||||
daily_challenge_user_stats=None,
|
||||
daily_challenge_user_stats=DailyChallengeStats(
|
||||
user_id=user_id,
|
||||
daily_streak_best=db_user.daily_challenge_stats.daily_streak_best
|
||||
if db_user.daily_challenge_stats
|
||||
else 0,
|
||||
daily_streak_current=db_user.daily_challenge_stats.daily_streak_current
|
||||
if db_user.daily_challenge_stats
|
||||
else 0,
|
||||
last_update=db_user.daily_challenge_stats.last_update
|
||||
if db_user.daily_challenge_stats
|
||||
else None,
|
||||
last_weekly_streak=db_user.daily_challenge_stats.last_weekly_streak
|
||||
if db_user.daily_challenge_stats
|
||||
else None,
|
||||
playcount=db_user.daily_challenge_stats.playcount
|
||||
if db_user.daily_challenge_stats
|
||||
else 0,
|
||||
top_10p_placements=db_user.daily_challenge_stats.top_10p_placements
|
||||
if db_user.daily_challenge_stats
|
||||
else 0,
|
||||
top_50p_placements=db_user.daily_challenge_stats.top_50p_placements
|
||||
if db_user.daily_challenge_stats
|
||||
else 0,
|
||||
weekly_streak_best=db_user.daily_challenge_stats.weekly_streak_best
|
||||
if db_user.daily_challenge_stats
|
||||
else 0,
|
||||
weekly_streak_current=db_user.daily_challenge_stats.weekly_streak_current
|
||||
if db_user.daily_challenge_stats
|
||||
else 0,
|
||||
),
|
||||
groups=[],
|
||||
monthly_playcounts=monthly_playcounts,
|
||||
page=Page(html=profile.page_html or "", raw=profile.page_raw or "")
|
||||
@@ -435,26 +463,3 @@ def get_country_name(country_code: str) -> str:
|
||||
# 可以添加更多国家
|
||||
}
|
||||
return country_names.get(country_code, "Unknown")
|
||||
|
||||
|
||||
def calculate_beatmap_attribute(
|
||||
beatmap: str,
|
||||
gamemode: GameMode | None = None,
|
||||
mods: int | list[APIMod] | list[str] = 0,
|
||||
) -> BeatmapAttributes:
|
||||
map = rosu.Beatmap(content=beatmap)
|
||||
if gamemode is not None:
|
||||
map.convert(gamemode.to_rosu(), mods)
|
||||
diff = rosu.Difficulty(mods=mods).calculate(map)
|
||||
return BeatmapAttributes(
|
||||
star_rating=diff.stars,
|
||||
max_combo=diff.max_combo,
|
||||
aim_difficulty=diff.aim,
|
||||
aim_difficult_slider_count=diff.aim_difficult_slider_count,
|
||||
speed_difficulty=diff.speed,
|
||||
speed_note_count=diff.speed_note_count,
|
||||
slider_factor=diff.slider_factor,
|
||||
aim_difficult_strain_count=diff.aim_difficult_strain_count,
|
||||
speed_difficult_strain_count=diff.speed_difficult_strain_count,
|
||||
mono_stamina_factor=diff.stamina,
|
||||
)
|
||||
|
||||
@@ -38,6 +38,7 @@ async def create_sample_user():
|
||||
existing_user2 = result2.first()
|
||||
if existing_user is not None and existing_user2 is not None:
|
||||
print("示例用户已存在,跳过创建")
|
||||
return
|
||||
|
||||
# 当前时间戳
|
||||
# current_timestamp = int(time.time())
|
||||
|
||||
4
main.py
4
main.py
@@ -4,12 +4,16 @@ from contextlib import asynccontextmanager
|
||||
from datetime import datetime
|
||||
|
||||
from app.config import settings
|
||||
from app.database import Team # noqa: F401
|
||||
from app.dependencies.database import create_tables, engine
|
||||
from app.dependencies.fetcher import get_fetcher
|
||||
from app.models.user import User
|
||||
from app.router import api_router, auth_router, fetcher_router, signalr_router
|
||||
|
||||
from fastapi import FastAPI
|
||||
|
||||
User.model_rebuild()
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
"""score: remove best_id in database
|
||||
|
||||
Revision ID: 78be13c71791
|
||||
Revises: dc4d25c428c7
|
||||
Create Date: 2025-07-29 07:57:33.764517
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import mysql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = "78be13c71791"
|
||||
down_revision: str | Sequence[str] | None = "dc4d25c428c7"
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column("scores", "best_id")
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column(
|
||||
"scores",
|
||||
sa.Column("best_id", mysql.INTEGER(), autoincrement=False, nullable=True),
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
@@ -0,0 +1,36 @@
|
||||
"""score: add nlarge_tick_hit & nsmall_tick_hit for pp calculator
|
||||
|
||||
Revision ID: dc4d25c428c7
|
||||
Revises:
|
||||
Create Date: 2025-07-29 01:43:40.221070
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = "dc4d25c428c7"
|
||||
down_revision: str | Sequence[str] | None = None
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column("scores", sa.Column("nlarge_tick_hit", sa.Integer(), nullable=True))
|
||||
op.add_column("scores", sa.Column("nsmall_tick_hit", sa.Integer(), nullable=True))
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column("scores", "nsmall_tick_hit")
|
||||
op.drop_column("scores", "nlarge_tick_hit")
|
||||
# ### end Alembic commands ###
|
||||
11
osu_lazer_api.code-workspace
Normal file
11
osu_lazer_api.code-workspace
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
},
|
||||
{
|
||||
"path": "packages/msgpack_lazer_api"
|
||||
}
|
||||
],
|
||||
"settings": {}
|
||||
}
|
||||
424
packages/msgpack_lazer_api/Cargo.lock
generated
Normal file
424
packages/msgpack_lazer_api/Cargo.lock
generated
Normal file
@@ -0,0 +1,424 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indoc"
|
||||
version = "2.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.77"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.174"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "msgpack-lazer-api"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"pyo3",
|
||||
"rmp",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3"
|
||||
version = "0.25.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8970a78afe0628a3e3430376fc5fd76b6b45c4d43360ffd6cdd40bdde72b682a"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"indoc",
|
||||
"libc",
|
||||
"memoffset",
|
||||
"once_cell",
|
||||
"portable-atomic",
|
||||
"pyo3-build-config",
|
||||
"pyo3-ffi",
|
||||
"pyo3-macros",
|
||||
"unindent",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-build-config"
|
||||
version = "0.25.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "458eb0c55e7ece017adeba38f2248ff3ac615e53660d7c71a238d7d2a01c7598"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"target-lexicon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-ffi"
|
||||
version = "0.25.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7114fe5457c61b276ab77c5055f206295b812608083644a5c5b2640c3102565c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"pyo3-build-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-macros"
|
||||
version = "0.25.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8725c0a622b374d6cb051d11a0983786448f7785336139c3c94f5aa6bef7e50"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"pyo3-macros-backend",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-macros-backend"
|
||||
version = "0.25.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4109984c22491085343c05b0dbc54ddc405c3cf7b4374fc533f5c3313a572ccc"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"pyo3-build-config",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rmp"
|
||||
version = "0.8.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"num-traits",
|
||||
"paste",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "target-lexicon"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
|
||||
[[package]]
|
||||
name = "unindent"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
14
packages/msgpack_lazer_api/Cargo.toml
Normal file
14
packages/msgpack_lazer_api/Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "msgpack-lazer-api"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[lib]
|
||||
name = "msgpack_lazer_api"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.4.41"
|
||||
pyo3 = { version = "0.25.0", features = ["extension-module", "chrono"] }
|
||||
rmp = "0.8.14"
|
||||
11
packages/msgpack_lazer_api/msgpack_lazer_api.pyi
Normal file
11
packages/msgpack_lazer_api/msgpack_lazer_api.pyi
Normal file
@@ -0,0 +1,11 @@
|
||||
from typing import Any
|
||||
|
||||
class APIMod:
|
||||
def __init__(self, acronym: str, settings: dict[str, Any]) -> None: ...
|
||||
@property
|
||||
def acronym(self) -> str: ...
|
||||
@property
|
||||
def settings(self) -> str: ...
|
||||
|
||||
def encode(obj: Any) -> bytes: ...
|
||||
def decode(data: bytes) -> Any: ...
|
||||
16
packages/msgpack_lazer_api/pyproject.toml
Normal file
16
packages/msgpack_lazer_api/pyproject.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[build-system]
|
||||
requires = ["maturin>=1.9,<2.0"]
|
||||
build-backend = "maturin"
|
||||
|
||||
[project]
|
||||
name = "msgpack-lazer-api"
|
||||
requires-python = ">=3.12"
|
||||
classifiers = [
|
||||
"Programming Language :: Rust",
|
||||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
"Programming Language :: Python :: Implementation :: PyPy",
|
||||
]
|
||||
dynamic = ["version"]
|
||||
|
||||
[tool.maturin]
|
||||
features = ["pyo3/extension-module"]
|
||||
315
packages/msgpack_lazer_api/src/decode.rs
Normal file
315
packages/msgpack_lazer_api/src/decode.rs
Normal file
@@ -0,0 +1,315 @@
|
||||
use crate::APIMod;
|
||||
use chrono::{TimeZone, Utc};
|
||||
use pyo3::types::PyDict;
|
||||
use pyo3::{prelude::*, IntoPyObjectExt};
|
||||
use std::collections::HashMap;
|
||||
use std::io::Read;
|
||||
|
||||
pub fn read_object(
|
||||
py: Python<'_>,
|
||||
cursor: &mut std::io::Cursor<&[u8]>,
|
||||
api_mod: bool,
|
||||
) -> PyResult<PyObject> {
|
||||
match rmp::decode::read_marker(cursor) {
|
||||
Ok(marker) => match marker {
|
||||
rmp::Marker::Null => Ok(py.None()),
|
||||
rmp::Marker::FixPos(val) => Ok(val.into_pyobject(py)?.into_any().unbind()),
|
||||
rmp::Marker::FixNeg(val) => Ok(val.into_pyobject(py)?.into_any().unbind()),
|
||||
rmp::Marker::U8 => {
|
||||
let mut buf = [0u8; 1];
|
||||
cursor.read_exact(&mut buf).map_err(to_py_err)?;
|
||||
Ok(buf[0].into_pyobject(py)?.into_any().unbind())
|
||||
}
|
||||
rmp::Marker::U16 => {
|
||||
let mut buf = [0u8; 2];
|
||||
cursor.read_exact(&mut buf).map_err(to_py_err)?;
|
||||
let val = u16::from_be_bytes(buf);
|
||||
Ok(val.into_pyobject(py)?.into_any().unbind())
|
||||
}
|
||||
rmp::Marker::U32 => {
|
||||
let mut buf = [0u8; 4];
|
||||
cursor.read_exact(&mut buf).map_err(to_py_err)?;
|
||||
let val = u32::from_be_bytes(buf);
|
||||
Ok(val.into_pyobject(py)?.into_any().unbind())
|
||||
}
|
||||
rmp::Marker::U64 => {
|
||||
let mut buf = [0u8; 8];
|
||||
cursor.read_exact(&mut buf).map_err(to_py_err)?;
|
||||
let val = u64::from_be_bytes(buf);
|
||||
Ok(val.into_pyobject(py)?.into_any().unbind())
|
||||
}
|
||||
rmp::Marker::I8 => {
|
||||
let mut buf = [0u8; 1];
|
||||
cursor.read_exact(&mut buf).map_err(to_py_err)?;
|
||||
let val = i8::from_be_bytes(buf);
|
||||
Ok(val.into_pyobject(py)?.into_any().unbind())
|
||||
}
|
||||
rmp::Marker::I16 => {
|
||||
let mut buf = [0u8; 2];
|
||||
cursor.read_exact(&mut buf).map_err(to_py_err)?;
|
||||
let val = i16::from_be_bytes(buf);
|
||||
Ok(val.into_pyobject(py)?.into_any().unbind())
|
||||
}
|
||||
rmp::Marker::I32 => {
|
||||
let mut buf = [0u8; 4];
|
||||
cursor.read_exact(&mut buf).map_err(to_py_err)?;
|
||||
let val = i32::from_be_bytes(buf);
|
||||
Ok(val.into_pyobject(py)?.into_any().unbind())
|
||||
}
|
||||
rmp::Marker::I64 => {
|
||||
let mut buf = [0u8; 8];
|
||||
cursor.read_exact(&mut buf).map_err(to_py_err)?;
|
||||
let val = i64::from_be_bytes(buf);
|
||||
Ok(val.into_pyobject(py)?.into_any().unbind())
|
||||
}
|
||||
rmp::Marker::Bin8 => {
|
||||
let mut buf = [0u8; 1];
|
||||
cursor.read_exact(&mut buf).map_err(to_py_err)?;
|
||||
let len = buf[0] as u32;
|
||||
let mut data = vec![0u8; len as usize];
|
||||
cursor.read_exact(&mut data).map_err(to_py_err)?;
|
||||
Ok(data.into_pyobject(py)?.into_any().unbind())
|
||||
}
|
||||
rmp::Marker::Bin16 => {
|
||||
let mut buf = [0u8; 2];
|
||||
cursor.read_exact(&mut buf).map_err(to_py_err)?;
|
||||
let len = u16::from_be_bytes(buf) as u32;
|
||||
let mut data = vec![0u8; len as usize];
|
||||
cursor.read_exact(&mut data).map_err(to_py_err)?;
|
||||
Ok(data.into_pyobject(py)?.into_any().unbind())
|
||||
}
|
||||
rmp::Marker::Bin32 => {
|
||||
let mut buf = [0u8; 4];
|
||||
cursor.read_exact(&mut buf).map_err(to_py_err)?;
|
||||
let len = u32::from_be_bytes(buf);
|
||||
let mut data = vec![0u8; len as usize];
|
||||
cursor.read_exact(&mut data).map_err(to_py_err)?;
|
||||
Ok(data.into_pyobject(py)?.into_any().unbind())
|
||||
}
|
||||
rmp::Marker::True => Ok(true.into_py_any(py)?),
|
||||
rmp::Marker::False => Ok(false.into_py_any(py)?),
|
||||
rmp::Marker::FixStr(len) => read_string(py, cursor, len as u32),
|
||||
rmp::Marker::Str8 => {
|
||||
let mut buf = [0u8; 1];
|
||||
cursor.read_exact(&mut buf).map_err(to_py_err)?;
|
||||
let len = buf[0] as u32;
|
||||
read_string(py, cursor, len)
|
||||
}
|
||||
rmp::Marker::Str16 => {
|
||||
let mut buf = [0u8; 2];
|
||||
cursor.read_exact(&mut buf).map_err(to_py_err)?;
|
||||
let len = u16::from_be_bytes(buf) as u32;
|
||||
read_string(py, cursor, len)
|
||||
}
|
||||
rmp::Marker::Str32 => {
|
||||
let mut buf = [0u8; 4];
|
||||
cursor.read_exact(&mut buf).map_err(to_py_err)?;
|
||||
let len = u32::from_be_bytes(buf);
|
||||
read_string(py, cursor, len)
|
||||
}
|
||||
rmp::Marker::FixArray(len) => read_array(py, cursor, len as u32, api_mod),
|
||||
rmp::Marker::Array16 => {
|
||||
let mut buf = [0u8; 2];
|
||||
cursor.read_exact(&mut buf).map_err(to_py_err)?;
|
||||
let len = u16::from_be_bytes(buf) as u32;
|
||||
read_array(py, cursor, len, api_mod)
|
||||
}
|
||||
rmp::Marker::Array32 => {
|
||||
let mut buf = [0u8; 4];
|
||||
cursor.read_exact(&mut buf).map_err(to_py_err)?;
|
||||
let len = u32::from_be_bytes(buf);
|
||||
read_array(py, cursor, len, api_mod)
|
||||
}
|
||||
rmp::Marker::FixMap(len) => read_map(py, cursor, len as u32),
|
||||
rmp::Marker::Map16 => {
|
||||
let mut buf = [0u8; 2];
|
||||
cursor.read_exact(&mut buf).map_err(to_py_err)?;
|
||||
let len = u16::from_be_bytes(buf) as u32;
|
||||
read_map(py, cursor, len)
|
||||
}
|
||||
rmp::Marker::Map32 => {
|
||||
let mut buf = [0u8; 4];
|
||||
cursor.read_exact(&mut buf).map_err(to_py_err)?;
|
||||
let len = u32::from_be_bytes(buf);
|
||||
read_map(py, cursor, len)
|
||||
}
|
||||
rmp::Marker::F32 => {
|
||||
let mut buf = [0u8; 4];
|
||||
cursor.read_exact(&mut buf).map_err(to_py_err)?;
|
||||
let val = f32::from_be_bytes(buf);
|
||||
Ok(val.into_pyobject(py)?.into_any().unbind())
|
||||
}
|
||||
rmp::Marker::F64 => {
|
||||
let mut buf = [0u8; 8];
|
||||
cursor.read_exact(&mut buf).map_err(to_py_err)?;
|
||||
let val = f64::from_be_bytes(buf);
|
||||
Ok(val.into_pyobject(py)?.into_any().unbind())
|
||||
}
|
||||
rmp::Marker::FixExt1 => read_ext(py, cursor, 1),
|
||||
rmp::Marker::FixExt2 => read_ext(py, cursor, 2),
|
||||
rmp::Marker::FixExt4 => read_ext(py, cursor, 4),
|
||||
rmp::Marker::FixExt8 => read_ext(py, cursor, 8),
|
||||
rmp::Marker::FixExt16 => read_ext(py, cursor, 16),
|
||||
rmp::Marker::Ext8 => {
|
||||
let mut buf = [0u8; 1];
|
||||
cursor.read_exact(&mut buf).map_err(to_py_err)?;
|
||||
let len = buf[0] as u32;
|
||||
read_ext(py, cursor, len)
|
||||
}
|
||||
rmp::Marker::Ext16 => {
|
||||
let mut buf = [0u8; 2];
|
||||
cursor.read_exact(&mut buf).map_err(to_py_err)?;
|
||||
let len = u16::from_be_bytes(buf) as u32;
|
||||
read_ext(py, cursor, len)
|
||||
}
|
||||
rmp::Marker::Ext32 => {
|
||||
let mut buf = [0u8; 4];
|
||||
cursor.read_exact(&mut buf).map_err(to_py_err)?;
|
||||
let len = u32::from_be_bytes(buf);
|
||||
read_ext(py, cursor, len)
|
||||
}
|
||||
_ => Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
|
||||
"Unsupported MessagePack marker",
|
||||
)),
|
||||
},
|
||||
Err(e) => Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
|
||||
"Failed to read marker: {:?}",
|
||||
e
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_string(
|
||||
py: Python<'_>,
|
||||
cursor: &mut std::io::Cursor<&[u8]>,
|
||||
len: u32,
|
||||
) -> PyResult<PyObject> {
|
||||
let mut buf = vec![0u8; len as usize];
|
||||
cursor.read_exact(&mut buf).map_err(to_py_err)?;
|
||||
let s = String::from_utf8(buf)
|
||||
.map_err(|_| PyErr::new::<pyo3::exceptions::PyUnicodeDecodeError, _>("Invalid UTF-8"))?;
|
||||
Ok(s.into_pyobject(py)?.into_any().unbind())
|
||||
}
|
||||
|
||||
fn read_array(
|
||||
py: Python,
|
||||
cursor: &mut std::io::Cursor<&[u8]>,
|
||||
len: u32,
|
||||
api_mod: bool,
|
||||
) -> PyResult<PyObject> {
|
||||
let mut items = Vec::new();
|
||||
let array_len = if api_mod { len * 2 } else { len };
|
||||
let dict = PyDict::new(py);
|
||||
let mut i = 0;
|
||||
if len == 2 && !api_mod {
|
||||
// 姑且这样判断:列表长度为2,第一个元素为长度为2的字符串,api_mod 模式未启用(不存在嵌套 APIMod)
|
||||
let obj1 = read_object(py, cursor, false)?;
|
||||
if obj1.extract::<String>(py).map_or(false, |k| k.len() == 2) {
|
||||
let obj2 = read_object(py, cursor, true)?;
|
||||
return Ok(APIMod {
|
||||
acronym: obj1.extract::<String>(py)?,
|
||||
settings: obj2.extract::<HashMap<String, PyObject>>(py)?,
|
||||
}
|
||||
.into_pyobject(py)?
|
||||
.into_any()
|
||||
.unbind());
|
||||
} else {
|
||||
items.push(obj1);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
while i < array_len {
|
||||
if api_mod && i % 2 == 0 {
|
||||
let key = read_object(py, cursor, false)?;
|
||||
let value = read_object(py, cursor, false)?;
|
||||
dict.set_item(key, value)?;
|
||||
i += 2;
|
||||
} else {
|
||||
let item = read_object(py, cursor, api_mod)?;
|
||||
items.push(item);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if api_mod {
|
||||
return Ok(dict.into_pyobject(py)?.into_any().unbind());
|
||||
} else {
|
||||
Ok(items.into_pyobject(py)?.into_any().unbind())
|
||||
}
|
||||
}
|
||||
|
||||
fn read_map(py: Python, cursor: &mut std::io::Cursor<&[u8]>, len: u32) -> PyResult<PyObject> {
|
||||
let mut pairs = Vec::new();
|
||||
for _ in 0..len {
|
||||
let key = read_object(py, cursor, false)?;
|
||||
let value = read_object(py, cursor, false)?;
|
||||
pairs.push((key, value));
|
||||
}
|
||||
|
||||
let dict = PyDict::new(py);
|
||||
for (key, value) in pairs {
|
||||
dict.set_item(key, value)?;
|
||||
}
|
||||
return Ok(dict.into_pyobject(py)?.into_any().unbind());
|
||||
}
|
||||
|
||||
fn to_py_err(err: std::io::Error) -> PyErr {
|
||||
PyErr::new::<pyo3::exceptions::PyIOError, _>(format!("IO error: {}", err))
|
||||
}
|
||||
|
||||
fn read_ext(py: Python, cursor: &mut std::io::Cursor<&[u8]>, len: u32) -> PyResult<PyObject> {
|
||||
// Read the extension type
|
||||
let mut type_buf = [0u8; 1];
|
||||
cursor.read_exact(&mut type_buf).map_err(to_py_err)?;
|
||||
let ext_type = type_buf[0] as i8;
|
||||
|
||||
// Read the extension data
|
||||
let mut data = vec![0u8; len as usize];
|
||||
cursor.read_exact(&mut data).map_err(to_py_err)?;
|
||||
|
||||
// Handle timestamp extension (type = -1)
|
||||
if ext_type == -1 {
|
||||
read_timestamp(py, &data)
|
||||
} else {
|
||||
// For other extension types, return as bytes or handle as needed
|
||||
Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
|
||||
"Unsupported extension type: {}",
|
||||
ext_type
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
fn read_timestamp(py: Python, data: &[u8]) -> PyResult<PyObject> {
|
||||
let (secs, nsec) = match data.len() {
|
||||
4 => {
|
||||
// timestamp32: 4-byte big endian seconds
|
||||
let secs = u32::from_be_bytes([data[0], data[1], data[2], data[3]]) as u64;
|
||||
(secs, 0u32)
|
||||
}
|
||||
8 => {
|
||||
// timestamp64: 8-byte packed => upper 34 bits nsec, lower 30 bits secs
|
||||
let packed = u64::from_be_bytes([
|
||||
data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
|
||||
]);
|
||||
let nsec = (packed >> 34) as u32;
|
||||
let secs = packed & 0x3FFFFFFFF; // lower 34 bits
|
||||
(secs, nsec)
|
||||
}
|
||||
12 => {
|
||||
// timestamp96: 12 bytes = 4-byte nsec + 8-byte seconds signed
|
||||
let nsec = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
|
||||
let secs = i64::from_be_bytes([
|
||||
data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11],
|
||||
]) as u64;
|
||||
(secs, nsec)
|
||||
}
|
||||
_ => {
|
||||
return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
|
||||
"Invalid timestamp data length: {}",
|
||||
data.len()
|
||||
)));
|
||||
}
|
||||
};
|
||||
let time = Utc.timestamp_opt(secs as i64, nsec).single();
|
||||
Ok(time.into_pyobject(py)?.into_any().unbind())
|
||||
}
|
||||
132
packages/msgpack_lazer_api/src/encode.rs
Normal file
132
packages/msgpack_lazer_api/src/encode.rs
Normal file
@@ -0,0 +1,132 @@
|
||||
use crate::APIMod;
|
||||
use chrono::{DateTime, Utc};
|
||||
use pyo3::prelude::{PyAnyMethods, PyDictMethods, PyListMethods, PyStringMethods};
|
||||
use pyo3::types::{PyBool, PyBytes, PyDateTime, PyDict, PyFloat, PyInt, PyList, PyNone, PyString};
|
||||
use pyo3::{Bound, PyAny, PyRef, Python};
|
||||
use std::io::Write;
|
||||
|
||||
fn write_list(buf: &mut Vec<u8>, obj: &Bound<'_, PyList>) {
|
||||
rmp::encode::write_array_len(buf, obj.len() as u32).unwrap();
|
||||
for item in obj.iter() {
|
||||
write_object(buf, &item);
|
||||
}
|
||||
}
|
||||
|
||||
fn write_string(buf: &mut Vec<u8>, obj: &Bound<'_, PyString>) {
|
||||
let s = obj.to_string_lossy();
|
||||
rmp::encode::write_str(buf, &s).unwrap();
|
||||
}
|
||||
|
||||
fn write_integer(buf: &mut Vec<u8>, obj: &Bound<'_, PyInt>) {
|
||||
if let Ok(val) = obj.extract::<i32>() {
|
||||
rmp::encode::write_i32(buf, val).unwrap();
|
||||
} else if let Ok(val) = obj.extract::<i64>() {
|
||||
rmp::encode::write_i64(buf, val).unwrap();
|
||||
} else {
|
||||
panic!("Unsupported integer type");
|
||||
}
|
||||
}
|
||||
|
||||
fn write_float(buf: &mut Vec<u8>, obj: &Bound<'_, PyAny>) {
|
||||
if let Ok(val) = obj.extract::<f32>() {
|
||||
rmp::encode::write_f32(buf, val).unwrap();
|
||||
} else if let Ok(val) = obj.extract::<f64>() {
|
||||
rmp::encode::write_f64(buf, val).unwrap();
|
||||
} else {
|
||||
panic!("Unsupported float type");
|
||||
}
|
||||
}
|
||||
|
||||
fn write_bool(buf: &mut Vec<u8>, obj: &Bound<'_, PyBool>) {
|
||||
if let Ok(b) = obj.extract::<bool>() {
|
||||
rmp::encode::write_bool(buf, b).unwrap();
|
||||
} else {
|
||||
panic!("Unsupported boolean type");
|
||||
}
|
||||
}
|
||||
|
||||
fn write_bin(buf: &mut Vec<u8>, obj: &Bound<'_, PyBytes>) {
|
||||
if let Ok(bytes) = obj.extract::<Vec<u8>>() {
|
||||
rmp::encode::write_bin(buf, &bytes).unwrap();
|
||||
} else {
|
||||
panic!("Unsupported binary type");
|
||||
}
|
||||
}
|
||||
|
||||
fn write_hashmap(buf: &mut Vec<u8>, obj: &Bound<'_, PyDict>) {
|
||||
rmp::encode::write_map_len(buf, obj.len() as u32).unwrap();
|
||||
for (key, value) in obj.iter() {
|
||||
write_object(buf, &key);
|
||||
write_object(buf, &value);
|
||||
}
|
||||
}
|
||||
|
||||
fn write_nil(buf: &mut Vec<u8>){
|
||||
rmp::encode::write_nil(buf).unwrap();
|
||||
}
|
||||
|
||||
// https://github.com/ppy/osu/blob/3dced3/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs
|
||||
fn write_api_mod(buf: &mut Vec<u8>, api_mod: PyRef<APIMod>) {
|
||||
rmp::encode::write_array_len(buf, 2).unwrap();
|
||||
rmp::encode::write_str(buf, &api_mod.acronym).unwrap();
|
||||
rmp::encode::write_array_len(buf, api_mod.settings.len() as u32).unwrap();
|
||||
for (k, v) in api_mod.settings.iter() {
|
||||
rmp::encode::write_str(buf, k).unwrap();
|
||||
Python::with_gil(|py| write_object(buf, &v.bind(py)));
|
||||
}
|
||||
}
|
||||
|
||||
fn write_datetime(buf: &mut Vec<u8>, obj: &Bound<'_, PyDateTime>) {
|
||||
if let Ok(dt) = obj.extract::<DateTime<Utc>>() {
|
||||
let secs = dt.timestamp();
|
||||
let nsec = dt.timestamp_subsec_nanos();
|
||||
write_timestamp(buf, secs, nsec);
|
||||
} else {
|
||||
panic!("Unsupported datetime type. Check your input, timezone is needed.");
|
||||
}
|
||||
}
|
||||
|
||||
fn write_timestamp(wr: &mut Vec<u8>, secs: i64, nsec: u32) {
|
||||
let buf: Vec<u8> = if nsec == 0 && secs >= 0 && secs <= u32::MAX as i64 {
|
||||
// timestamp32: 4-byte big endian seconds
|
||||
secs.to_be_bytes()[4..].to_vec()
|
||||
} else if secs >= -(1 << 34) && secs < (1 << 34) {
|
||||
// timestamp64: 8-byte packed => upper 34 bits nsec, lower 34 bits secs
|
||||
let packed = ((nsec as u64) << 34) | (secs as u64 & ((1 << 34) - 1));
|
||||
packed.to_be_bytes().to_vec()
|
||||
} else {
|
||||
// timestamp96: 12 bytes = 4-byte nsec + 8-byte seconds signed
|
||||
let mut v = Vec::with_capacity(12);
|
||||
v.extend_from_slice(&nsec.to_be_bytes());
|
||||
v.extend_from_slice(&secs.to_be_bytes());
|
||||
v
|
||||
};
|
||||
rmp::encode::write_ext_meta(wr, buf.len() as u32, -1).unwrap();
|
||||
wr.write_all(&buf).unwrap();
|
||||
}
|
||||
|
||||
pub fn write_object(buf: &mut Vec<u8>, obj: &Bound<'_, PyAny>) {
|
||||
if let Ok(list) = obj.downcast::<PyList>() {
|
||||
write_list(buf, list);
|
||||
} else if let Ok(string) = obj.downcast::<PyString>() {
|
||||
write_string(buf, string);
|
||||
} else if let Ok(integer) = obj.downcast::<PyInt>() {
|
||||
write_integer(buf, integer);
|
||||
} else if let Ok(float) = obj.downcast::<PyFloat>() {
|
||||
write_float(buf, float);
|
||||
} else if let Ok(boolean) = obj.downcast::<PyBool>() {
|
||||
write_bool(buf, boolean);
|
||||
} else if let Ok(bytes) = obj.downcast::<PyBytes>() {
|
||||
write_bin(buf, bytes);
|
||||
} else if let Ok(dict) = obj.downcast::<PyDict>() {
|
||||
write_hashmap(buf, dict);
|
||||
} else if let Ok(_none) = obj.downcast::<PyNone>() {
|
||||
write_nil(buf);
|
||||
} else if let Ok(datetime) = obj.downcast::<PyDateTime>() {
|
||||
write_datetime(buf, datetime);
|
||||
} else if let Ok(api_mod) = obj.extract::<PyRef<APIMod>>() {
|
||||
write_api_mod(buf, api_mod);
|
||||
} else {
|
||||
panic!("Unsupported type");
|
||||
}
|
||||
}
|
||||
51
packages/msgpack_lazer_api/src/lib.rs
Normal file
51
packages/msgpack_lazer_api/src/lib.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
mod decode;
|
||||
mod encode;
|
||||
|
||||
use pyo3::prelude::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[pyclass]
|
||||
struct APIMod {
|
||||
#[pyo3(get, set)]
|
||||
acronym: String,
|
||||
#[pyo3(get, set)]
|
||||
settings: HashMap<String, PyObject>,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl APIMod {
|
||||
#[new]
|
||||
fn new(acronym: String, settings: HashMap<String, PyObject>) -> Self {
|
||||
APIMod { acronym, settings }
|
||||
}
|
||||
|
||||
fn __repr__(&self) -> String {
|
||||
format!(
|
||||
"APIMod(acronym='{}', settings={:?})",
|
||||
self.acronym, self.settings
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
#[pyo3(name = "encode")]
|
||||
fn encode_py(obj: &Bound<'_, PyAny>) -> PyResult<Vec<u8>> {
|
||||
let mut buf = Vec::new();
|
||||
encode::write_object(&mut buf, obj);
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
#[pyo3(name = "decode")]
|
||||
fn decode_py(py: Python, data: &[u8]) -> PyResult<PyObject> {
|
||||
let mut cursor = std::io::Cursor::new(data);
|
||||
decode::read_object(py, &mut cursor, false)
|
||||
}
|
||||
|
||||
#[pymodule]
|
||||
fn msgpack_lazer_api(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(encode_py, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(decode_py, m)?)?;
|
||||
m.add_class::<APIMod>()?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -12,7 +12,7 @@ dependencies = [
|
||||
"fastapi>=0.104.1",
|
||||
"httpx>=0.28.1",
|
||||
"loguru>=0.7.3",
|
||||
"msgpack>=1.1.1",
|
||||
"msgpack-lazer-api",
|
||||
"passlib[bcrypt]>=1.7.4",
|
||||
"pydantic[email]>=2.5.0",
|
||||
"python-dotenv>=1.0.0",
|
||||
@@ -87,9 +87,20 @@ disableBytesTypePromotions = true
|
||||
reportIncompatibleMethodOverride = false
|
||||
reportIncompatibleVariableOverride = false
|
||||
|
||||
[tool.uv.workspace]
|
||||
members = [
|
||||
"packages/msgpack_lazer_api",
|
||||
]
|
||||
|
||||
[tool.uv.sources]
|
||||
msgpack-lazer-api = { workspace = true }
|
||||
|
||||
[tool.uv]
|
||||
cache-keys = [{file = "pyproject.toml"}, {file = "packages/msgpack_lazer_api/Cargo.toml"}, {file = "**/*.rs"}]
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"msgpack-types>=0.5.0",
|
||||
"maturin>=1.9.2",
|
||||
"pre-commit>=4.2.0",
|
||||
"ruff>=0.12.4",
|
||||
]
|
||||
|
||||
792
requirements.txt
792
requirements.txt
@@ -1,735 +1,57 @@
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv export
|
||||
aiomysql==0.2.0 \
|
||||
--hash=sha256:558b9c26d580d08b8c5fd1be23c5231ce3aeff2dadad989540fee740253deb67 \
|
||||
--hash=sha256:b7c26da0daf23a5ec5e0b133c03d20657276e4eae9b73e040b72787f6f6ade0a
|
||||
# via osu-lazer-api
|
||||
alembic==1.16.4 \
|
||||
--hash=sha256:b05e51e8e82efc1abd14ba2af6392897e145930c3e0a2faf2b0da2f7f7fd660d \
|
||||
--hash=sha256:efab6ada0dd0fae2c92060800e0bf5c1dc26af15a10e02fb4babff164b4725e2
|
||||
# via osu-lazer-api
|
||||
annotated-types==0.7.0 \
|
||||
--hash=sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53 \
|
||||
--hash=sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89
|
||||
# via pydantic
|
||||
anyio==4.9.0 \
|
||||
--hash=sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028 \
|
||||
--hash=sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c
|
||||
# via
|
||||
# httpx
|
||||
# starlette
|
||||
# watchfiles
|
||||
async-timeout==5.0.1 ; python_full_version < '3.11.3' \
|
||||
--hash=sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c \
|
||||
--hash=sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3
|
||||
# via redis
|
||||
bcrypt==4.3.0 \
|
||||
--hash=sha256:0042b2e342e9ae3d2ed22727c1262f76cc4f345683b5c1715f0250cf4277294f \
|
||||
--hash=sha256:0142b2cb84a009f8452c8c5a33ace5e3dfec4159e7735f5afe9a4d50a8ea722d \
|
||||
--hash=sha256:08bacc884fd302b611226c01014eca277d48f0a05187666bca23aac0dad6fe24 \
|
||||
--hash=sha256:0d3efb1157edebfd9128e4e46e2ac1a64e0c1fe46fb023158a407c7892b0f8c3 \
|
||||
--hash=sha256:0e30e5e67aed0187a1764911af023043b4542e70a7461ad20e837e94d23e1d6c \
|
||||
--hash=sha256:12fa6ce40cde3f0b899729dbd7d5e8811cb892d31b6f7d0334a1f37748b789fd \
|
||||
--hash=sha256:17a854d9a7a476a89dcef6c8bd119ad23e0f82557afbd2c442777a16408e614f \
|
||||
--hash=sha256:191354ebfe305e84f344c5964c7cd5f924a3bfc5d405c75ad07f232b6dffb49f \
|
||||
--hash=sha256:2ef6630e0ec01376f59a006dc72918b1bf436c3b571b80fa1968d775fa02fe7d \
|
||||
--hash=sha256:3004df1b323d10021fda07a813fd33e0fd57bef0e9a480bb143877f6cba996fe \
|
||||
--hash=sha256:335a420cfd63fc5bc27308e929bee231c15c85cc4c496610ffb17923abf7f231 \
|
||||
--hash=sha256:33752b1ba962ee793fa2b6321404bf20011fe45b9afd2a842139de3011898fef \
|
||||
--hash=sha256:3a3fd2204178b6d2adcf09cb4f6426ffef54762577a7c9b54c159008cb288c18 \
|
||||
--hash=sha256:3b8d62290ebefd49ee0b3ce7500f5dbdcf13b81402c05f6dafab9a1e1b27212f \
|
||||
--hash=sha256:3e36506d001e93bffe59754397572f21bb5dc7c83f54454c990c74a468cd589e \
|
||||
--hash=sha256:41261d64150858eeb5ff43c753c4b216991e0ae16614a308a15d909503617732 \
|
||||
--hash=sha256:50e6e80a4bfd23a25f5c05b90167c19030cf9f87930f7cb2eacb99f45d1c3304 \
|
||||
--hash=sha256:531457e5c839d8caea9b589a1bcfe3756b0547d7814e9ce3d437f17da75c32b0 \
|
||||
--hash=sha256:55a935b8e9a1d2def0626c4269db3fcd26728cbff1e84f0341465c31c4ee56d8 \
|
||||
--hash=sha256:57967b7a28d855313a963aaea51bf6df89f833db4320da458e5b3c5ab6d4c938 \
|
||||
--hash=sha256:584027857bc2843772114717a7490a37f68da563b3620f78a849bcb54dc11e62 \
|
||||
--hash=sha256:59e1aa0e2cd871b08ca146ed08445038f42ff75968c7ae50d2fdd7860ade2180 \
|
||||
--hash=sha256:5bd3cca1f2aa5dbcf39e2aa13dd094ea181f48959e1071265de49cc2b82525af \
|
||||
--hash=sha256:5c1949bf259a388863ced887c7861da1df681cb2388645766c89fdfd9004c669 \
|
||||
--hash=sha256:62f26585e8b219cdc909b6a0069efc5e4267e25d4a3770a364ac58024f62a761 \
|
||||
--hash=sha256:67a561c4d9fb9465ec866177e7aebcad08fe23aaf6fbd692a6fab69088abfc51 \
|
||||
--hash=sha256:6fb1fd3ab08c0cbc6826a2e0447610c6f09e983a281b919ed721ad32236b8b23 \
|
||||
--hash=sha256:74a8d21a09f5e025a9a23e7c0fd2c7fe8e7503e4d356c0a2c1486ba010619f09 \
|
||||
--hash=sha256:79e70b8342a33b52b55d93b3a59223a844962bef479f6a0ea318ebbcadf71505 \
|
||||
--hash=sha256:7a4be4cbf241afee43f1c3969b9103a41b40bcb3a3f467ab19f891d9bc4642e4 \
|
||||
--hash=sha256:7c03296b85cb87db865d91da79bf63d5609284fc0cab9472fdd8367bbd830753 \
|
||||
--hash=sha256:842d08d75d9fe9fb94b18b071090220697f9f184d4547179b60734846461ed59 \
|
||||
--hash=sha256:864f8f19adbe13b7de11ba15d85d4a428c7e2f344bac110f667676a0ff84924b \
|
||||
--hash=sha256:97eea7408db3a5bcce4a55d13245ab3fa566e23b4c67cd227062bb49e26c585d \
|
||||
--hash=sha256:a839320bf27d474e52ef8cb16449bb2ce0ba03ca9f44daba6d93fa1d8828e48a \
|
||||
--hash=sha256:afe327968aaf13fc143a56a3360cb27d4ad0345e34da12c7290f1b00b8fe9a8b \
|
||||
--hash=sha256:b4d4e57f0a63fd0b358eb765063ff661328f69a04494427265950c71b992a39a \
|
||||
--hash=sha256:bdc6a24e754a555d7316fa4774e64c6c3997d27ed2d1964d55920c7c227bc4ce \
|
||||
--hash=sha256:beeefe437218a65322fbd0069eb437e7c98137e08f22c4660ac2dc795c31f8bb \
|
||||
--hash=sha256:c5eeac541cefd0bb887a371ef73c62c3cd78535e4887b310626036a7c0a817bb \
|
||||
--hash=sha256:d9af79d322e735b1fc33404b5765108ae0ff232d4b54666d46730f8ac1a43676 \
|
||||
--hash=sha256:e53e074b120f2877a35cc6c736b8eb161377caae8925c17688bd46ba56daaa5b \
|
||||
--hash=sha256:e965a9c1e9a393b8005031ff52583cedc15b7884fce7deb8b0346388837d6cfe \
|
||||
--hash=sha256:f01e060f14b6b57bbb72fc5b4a83ac21c443c9a2ee708e04a10e9192f90a6281 \
|
||||
--hash=sha256:f1e3ffa1365e8702dc48c8b360fef8d7afeca482809c5e45e653af82ccd088c1 \
|
||||
--hash=sha256:f6746e6fec103fcd509b96bacdfdaa2fbde9a553245dbada284435173a6f1aef \
|
||||
--hash=sha256:f81b0ed2639568bf14749112298f9e4e2b28853dab50a8b357e31798686a036d
|
||||
# via
|
||||
# osu-lazer-api
|
||||
# passlib
|
||||
certifi==2025.7.14 \
|
||||
--hash=sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2 \
|
||||
--hash=sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995
|
||||
# via
|
||||
# httpcore
|
||||
# httpx
|
||||
cffi==1.17.1 ; platform_python_implementation != 'PyPy' \
|
||||
--hash=sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2 \
|
||||
--hash=sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36 \
|
||||
--hash=sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824 \
|
||||
--hash=sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf \
|
||||
--hash=sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3 \
|
||||
--hash=sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed \
|
||||
--hash=sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1 \
|
||||
--hash=sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8 \
|
||||
--hash=sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903 \
|
||||
--hash=sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d \
|
||||
--hash=sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683 \
|
||||
--hash=sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9 \
|
||||
--hash=sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c \
|
||||
--hash=sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4 \
|
||||
--hash=sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655 \
|
||||
--hash=sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65 \
|
||||
--hash=sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41 \
|
||||
--hash=sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6 \
|
||||
--hash=sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401 \
|
||||
--hash=sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6 \
|
||||
--hash=sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93 \
|
||||
--hash=sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4 \
|
||||
--hash=sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0 \
|
||||
--hash=sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3 \
|
||||
--hash=sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff \
|
||||
--hash=sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5 \
|
||||
--hash=sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd \
|
||||
--hash=sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f \
|
||||
--hash=sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5 \
|
||||
--hash=sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d \
|
||||
--hash=sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e \
|
||||
--hash=sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a \
|
||||
--hash=sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4 \
|
||||
--hash=sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99 \
|
||||
--hash=sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b
|
||||
# via cryptography
|
||||
cfgv==3.4.0 \
|
||||
--hash=sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9 \
|
||||
--hash=sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560
|
||||
# via pre-commit
|
||||
click==8.2.1 \
|
||||
--hash=sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202 \
|
||||
--hash=sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b
|
||||
# via uvicorn
|
||||
colorama==0.4.6 ; sys_platform == 'win32' \
|
||||
--hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
|
||||
--hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
|
||||
# via
|
||||
# click
|
||||
# uvicorn
|
||||
cryptography==45.0.5 \
|
||||
--hash=sha256:0027d566d65a38497bc37e0dd7c2f8ceda73597d2ac9ba93810204f56f52ebc7 \
|
||||
--hash=sha256:101ee65078f6dd3e5a028d4f19c07ffa4dd22cce6a20eaa160f8b5219911e7d8 \
|
||||
--hash=sha256:12e55281d993a793b0e883066f590c1ae1e802e3acb67f8b442e721e475e6463 \
|
||||
--hash=sha256:1e1da5accc0c750056c556a93c3e9cb828970206c68867712ca5805e46dc806f \
|
||||
--hash=sha256:2089cc8f70a6e454601525e5bf2779e665d7865af002a5dec8d14e561002e135 \
|
||||
--hash=sha256:3a264aae5f7fbb089dbc01e0242d3b67dffe3e6292e1f5182122bdf58e65215d \
|
||||
--hash=sha256:3af26738f2db354aafe492fb3869e955b12b2ef2e16908c8b9cb928128d42c57 \
|
||||
--hash=sha256:3fcfbefc4a7f332dece7272a88e410f611e79458fab97b5efe14e54fe476f4fd \
|
||||
--hash=sha256:460f8c39ba66af7db0545a8c6f2eabcbc5a5528fc1cf6c3fa9a1e44cec33385e \
|
||||
--hash=sha256:5aa1e32983d4443e310f726ee4b071ab7569f58eedfdd65e9675484a4eb67bd1 \
|
||||
--hash=sha256:6ff8728d8d890b3dda5765276d1bc6fb099252915a2cd3aff960c4c195745dd0 \
|
||||
--hash=sha256:7259038202a47fdecee7e62e0fd0b0738b6daa335354396c6ddebdbe1206af2a \
|
||||
--hash=sha256:72e76caa004ab63accdf26023fccd1d087f6d90ec6048ff33ad0445abf7f605a \
|
||||
--hash=sha256:7760c1c2e1a7084153a0f68fab76e754083b126a47d0117c9ed15e69e2103492 \
|
||||
--hash=sha256:8c4a6ff8a30e9e3d38ac0539e9a9e02540ab3f827a3394f8852432f6b0ea152e \
|
||||
--hash=sha256:9024beb59aca9d31d36fcdc1604dd9bbeed0a55bface9f1908df19178e2f116e \
|
||||
--hash=sha256:90cb0a7bb35959f37e23303b7eed0a32280510030daba3f7fdfbb65defde6a97 \
|
||||
--hash=sha256:91098f02ca81579c85f66df8a588c78f331ca19089763d733e34ad359f474174 \
|
||||
--hash=sha256:926c3ea71a6043921050eaa639137e13dbe7b4ab25800932a8498364fc1abec9 \
|
||||
--hash=sha256:982518cd64c54fcada9d7e5cf28eabd3ee76bd03ab18e08a48cad7e8b6f31b18 \
|
||||
--hash=sha256:9b4cf6318915dccfe218e69bbec417fdd7c7185aa7aab139a2c0beb7468c89f0 \
|
||||
--hash=sha256:ad0caded895a00261a5b4aa9af828baede54638754b51955a0ac75576b831b27 \
|
||||
--hash=sha256:b85980d1e345fe769cfc57c57db2b59cff5464ee0c045d52c0df087e926fbe63 \
|
||||
--hash=sha256:bd4c45986472694e5121084c6ebbd112aa919a25e783b87eb95953c9573906d6 \
|
||||
--hash=sha256:be97d3a19c16a9be00edf79dca949c8fa7eff621763666a145f9f9535a5d7f42 \
|
||||
--hash=sha256:d05a38884db2ba215218745f0781775806bde4f32e07b135348355fe8e4991d9 \
|
||||
--hash=sha256:dd420e577921c8c2d31289536c386aaa30140b473835e97f83bc71ea9d2baf2d \
|
||||
--hash=sha256:e357286c1b76403dd384d938f93c46b2b058ed4dfcdce64a770f0537ed3feb6f \
|
||||
--hash=sha256:e6c00130ed423201c5bc5544c23359141660b07999ad82e34e7bb8f882bb78e0 \
|
||||
--hash=sha256:e74d30ec9c7cb2f404af331d5b4099a9b322a8a6b25c4632755c8757345baac5 \
|
||||
--hash=sha256:f3562c2f23c612f2e4a6964a61d942f891d29ee320edb62ff48ffb99f3de9ae8
|
||||
# via
|
||||
# osu-lazer-api
|
||||
# python-jose
|
||||
distlib==0.4.0 \
|
||||
--hash=sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16 \
|
||||
--hash=sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d
|
||||
# via virtualenv
|
||||
dnspython==2.7.0 \
|
||||
--hash=sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86 \
|
||||
--hash=sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1
|
||||
# via email-validator
|
||||
ecdsa==0.19.1 \
|
||||
--hash=sha256:30638e27cf77b7e15c4c4cc1973720149e1033827cfd00661ca5c8cc0cdb24c3 \
|
||||
--hash=sha256:478cba7b62555866fcb3bb3fe985e06decbdb68ef55713c4e5ab98c57d508e61
|
||||
# via python-jose
|
||||
email-validator==2.2.0 \
|
||||
--hash=sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631 \
|
||||
--hash=sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7
|
||||
# via pydantic
|
||||
fastapi==0.116.1 \
|
||||
--hash=sha256:c46ac7c312df840f0c9e220f7964bada936781bc4e2e6eb71f1c4d7553786565 \
|
||||
--hash=sha256:ed52cbf946abfd70c5a0dccb24673f0670deeb517a88b3544d03c2a6bf283143
|
||||
# via osu-lazer-api
|
||||
filelock==3.18.0 \
|
||||
--hash=sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2 \
|
||||
--hash=sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de
|
||||
# via virtualenv
|
||||
greenlet==3.2.3 ; (python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64') \
|
||||
--hash=sha256:024571bbce5f2c1cfff08bf3fbaa43bbc7444f580ae13b0099e95d0e6e67ed36 \
|
||||
--hash=sha256:02b0df6f63cd15012bed5401b47829cfd2e97052dc89da3cfaf2c779124eb892 \
|
||||
--hash=sha256:0921ac4ea42a5315d3446120ad48f90c3a6b9bb93dd9b3cf4e4d84a66e42de83 \
|
||||
--hash=sha256:0cc73378150b8b78b0c9fe2ce56e166695e67478550769536a6742dca3651688 \
|
||||
--hash=sha256:25ad29caed5783d4bd7a85c9251c651696164622494c00802a139c00d639242d \
|
||||
--hash=sha256:29e184536ba333003540790ba29829ac14bb645514fbd7e32af331e8202a62a5 \
|
||||
--hash=sha256:2c724620a101f8170065d7dded3f962a2aea7a7dae133a009cada42847e04a7b \
|
||||
--hash=sha256:2d8aa5423cd4a396792f6d4580f88bdc6efcb9205891c9d40d20f6e670992efb \
|
||||
--hash=sha256:3d04332dddb10b4a211b68111dabaee2e1a073663d117dc10247b5b1642bac86 \
|
||||
--hash=sha256:419e60f80709510c343c57b4bb5a339d8767bf9aef9b8ce43f4f143240f88b7c \
|
||||
--hash=sha256:500b8689aa9dd1ab26872a34084503aeddefcb438e2e7317b89b11eaea1901ad \
|
||||
--hash=sha256:5035d77a27b7c62db6cf41cf786cfe2242644a7a337a0e155c80960598baab95 \
|
||||
--hash=sha256:5195fb1e75e592dd04ce79881c8a22becdfa3e6f500e7feb059b1e6fdd54d3e3 \
|
||||
--hash=sha256:592c12fb1165be74592f5de0d70f82bc5ba552ac44800d632214b76089945147 \
|
||||
--hash=sha256:706d016a03e78df129f68c4c9b4c4f963f7d73534e48a24f5f5a7101ed13dbbb \
|
||||
--hash=sha256:7454d37c740bb27bdeddfc3f358f26956a07d5220818ceb467a483197d84f849 \
|
||||
--hash=sha256:751261fc5ad7b6705f5f76726567375bb2104a059454e0226e1eef6c756748ba \
|
||||
--hash=sha256:784ae58bba89fa1fa5733d170d42486580cab9decda3484779f4759345b29822 \
|
||||
--hash=sha256:8186162dffde068a465deab08fc72c767196895c39db26ab1c17c0b77a6d8b97 \
|
||||
--hash=sha256:83a8761c75312361aa2b5b903b79da97f13f556164a7dd2d5448655425bd4c34 \
|
||||
--hash=sha256:86c2d68e87107c1792e2e8d5399acec2487a4e993ab76c792408e59394d52141 \
|
||||
--hash=sha256:8704b3768d2f51150626962f4b9a9e4a17d2e37c8a8d9867bbd9fa4eb938d3b3 \
|
||||
--hash=sha256:873abe55f134c48e1f2a6f53f7d1419192a3d1a4e873bace00499a4e45ea6af0 \
|
||||
--hash=sha256:88cd97bf37fe24a6710ec6a3a7799f3f81d9cd33317dcf565ff9950c83f55e0b \
|
||||
--hash=sha256:8b0dd8ae4c0d6f5e54ee55ba935eeb3d735a9b58a8a1e5b5cbab64e01a39f365 \
|
||||
--hash=sha256:8c47aae8fbbfcf82cc13327ae802ba13c9c36753b67e760023fd116bc124a62a \
|
||||
--hash=sha256:93c0bb79844a367782ec4f429d07589417052e621aa39a5ac1fb99c5aa308edc \
|
||||
--hash=sha256:93d48533fade144203816783373f27a97e4193177ebaaf0fc396db19e5d61163 \
|
||||
--hash=sha256:a07d3472c2a93117af3b0136f246b2833fdc0b542d4a9799ae5f41c28323faef \
|
||||
--hash=sha256:baeedccca94880d2f5666b4fa16fc20ef50ba1ee353ee2d7092b383a243b0b0d \
|
||||
--hash=sha256:be52af4b6292baecfa0f397f3edb3c6092ce071b499dd6fe292c9ac9f2c8f264 \
|
||||
--hash=sha256:c667c0bf9d406b77a15c924ef3285e1e05250948001220368e039b6aa5b5034b \
|
||||
--hash=sha256:d2971d93bb99e05f8c2c0c2f4aa9484a18d98c4c3bd3c62b65b7e6ae33dfcfaf \
|
||||
--hash=sha256:ed6cfa9200484d234d8394c70f5492f144b20d4533f69262d530a1a082f6ee9a \
|
||||
--hash=sha256:f4bfbaa6096b1b7a200024784217defedf46a07c2eee1a498e94a1b5f8ec5728
|
||||
# via sqlalchemy
|
||||
h11==0.16.0 \
|
||||
--hash=sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1 \
|
||||
--hash=sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86
|
||||
# via
|
||||
# httpcore
|
||||
# uvicorn
|
||||
httpcore==1.0.9 \
|
||||
--hash=sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55 \
|
||||
--hash=sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8
|
||||
# via httpx
|
||||
httptools==0.6.4 \
|
||||
--hash=sha256:0614154d5454c21b6410fdf5262b4a3ddb0f53f1e1721cfd59d55f32138c578a \
|
||||
--hash=sha256:16e603a3bff50db08cd578d54f07032ca1631450ceb972c2f834c2b860c28ea2 \
|
||||
--hash=sha256:288cd628406cc53f9a541cfaf06041b4c71d751856bab45e3702191f931ccd17 \
|
||||
--hash=sha256:28908df1b9bb8187393d5b5db91435ccc9c8e891657f9cbb42a2541b44c82fc8 \
|
||||
--hash=sha256:322d20ea9cdd1fa98bd6a74b77e2ec5b818abdc3d36695ab402a0de8ef2865a3 \
|
||||
--hash=sha256:342dd6946aa6bda4b8f18c734576106b8a31f2fe31492881a9a160ec84ff4bd5 \
|
||||
--hash=sha256:40a5ec98d3f49904b9fe36827dcf1aadfef3b89e2bd05b0e35e94f97c2b14721 \
|
||||
--hash=sha256:40b0f7fe4fd38e6a507bdb751db0379df1e99120c65fbdc8ee6c1d044897a636 \
|
||||
--hash=sha256:4b36913ba52008249223042dca46e69967985fb4051951f94357ea681e1f5dc0 \
|
||||
--hash=sha256:4d87b29bd4486c0093fc64dea80231f7c7f7eb4dc70ae394d70a495ab8436071 \
|
||||
--hash=sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c \
|
||||
--hash=sha256:69422b7f458c5af875922cdb5bd586cc1f1033295aa9ff63ee196a87519ac8e1 \
|
||||
--hash=sha256:85071a1e8c2d051b507161f6c3e26155b5c790e4e28d7f236422dbacc2a9cc44 \
|
||||
--hash=sha256:856f4bc0478ae143bad54a4242fccb1f3f86a6e1be5548fecfd4102061b3a083 \
|
||||
--hash=sha256:ade273d7e767d5fae13fa637f4d53b6e961fb7fd93c7797562663f0171c26660 \
|
||||
--hash=sha256:dacdd3d10ea1b4ca9df97a0a303cbacafc04b5cd375fa98732678151643d4988 \
|
||||
--hash=sha256:db78cb9ca56b59b016e64b6031eda5653be0589dba2b1b43453f6e8b405a0970 \
|
||||
--hash=sha256:df017d6c780287d5c80601dafa31f17bddb170232d85c066604d8558683711a2 \
|
||||
--hash=sha256:ec4f178901fa1834d4a060320d2f3abc5c9e39766953d038f1458cb885f47e81 \
|
||||
--hash=sha256:f47f8ed67cc0ff862b84a1189831d1d33c963fb3ce1ee0c65d3b0cbe7b711069 \
|
||||
--hash=sha256:f8787367fbdfccae38e35abf7641dafc5310310a5987b689f4c32cc8cc3ee975 \
|
||||
--hash=sha256:f9eb89ecf8b290f2e293325c646a211ff1c2493222798bb80a530c5e7502494f
|
||||
# via uvicorn
|
||||
httpx==0.28.1 \
|
||||
--hash=sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc \
|
||||
--hash=sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad
|
||||
# via osu-lazer-api
|
||||
identify==2.6.12 \
|
||||
--hash=sha256:ad9672d5a72e0d2ff7c5c8809b62dfa60458626352fb0eb7b55e69bdc45334a2 \
|
||||
--hash=sha256:d8de45749f1efb108badef65ee8386f0f7bb19a7f26185f74de6367bffbaf0e6
|
||||
# via pre-commit
|
||||
idna==3.10 \
|
||||
--hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \
|
||||
--hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3
|
||||
# via
|
||||
# anyio
|
||||
# email-validator
|
||||
# httpx
|
||||
mako==1.3.10 \
|
||||
--hash=sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28 \
|
||||
--hash=sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59
|
||||
# via alembic
|
||||
markupsafe==3.0.2 \
|
||||
--hash=sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4 \
|
||||
--hash=sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30 \
|
||||
--hash=sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9 \
|
||||
--hash=sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396 \
|
||||
--hash=sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028 \
|
||||
--hash=sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca \
|
||||
--hash=sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557 \
|
||||
--hash=sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832 \
|
||||
--hash=sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a \
|
||||
--hash=sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c \
|
||||
--hash=sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c \
|
||||
--hash=sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22 \
|
||||
--hash=sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094 \
|
||||
--hash=sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e \
|
||||
--hash=sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5 \
|
||||
--hash=sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d \
|
||||
--hash=sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b \
|
||||
--hash=sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225 \
|
||||
--hash=sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c \
|
||||
--hash=sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87 \
|
||||
--hash=sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d \
|
||||
--hash=sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93 \
|
||||
--hash=sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf \
|
||||
--hash=sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84 \
|
||||
--hash=sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb \
|
||||
--hash=sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48 \
|
||||
--hash=sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c \
|
||||
--hash=sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6 \
|
||||
--hash=sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd \
|
||||
--hash=sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1 \
|
||||
--hash=sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d \
|
||||
--hash=sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca \
|
||||
--hash=sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a \
|
||||
--hash=sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe \
|
||||
--hash=sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798 \
|
||||
--hash=sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8 \
|
||||
--hash=sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f \
|
||||
--hash=sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f \
|
||||
--hash=sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0 \
|
||||
--hash=sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79 \
|
||||
--hash=sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430
|
||||
# via mako
|
||||
msgpack==1.1.1 \
|
||||
--hash=sha256:196a736f0526a03653d829d7d4c5500a97eea3648aebfd4b6743875f28aa2af8 \
|
||||
--hash=sha256:33be9ab121df9b6b461ff91baac6f2731f83d9b27ed948c5b9d1978ae28bf157 \
|
||||
--hash=sha256:36043272c6aede309d29d56851f8841ba907a1a3d04435e43e8a19928e243c1d \
|
||||
--hash=sha256:3765afa6bd4832fc11c3749be4ba4b69a0e8d7b728f78e68120a157a4c5d41f0 \
|
||||
--hash=sha256:4147151acabb9caed4e474c3344181e91ff7a388b888f1e19ea04f7e73dc7ad5 \
|
||||
--hash=sha256:4a28e8072ae9779f20427af07f53bbb8b4aa81151054e882aee333b158da8752 \
|
||||
--hash=sha256:4df2311b0ce24f06ba253fda361f938dfecd7b961576f9be3f3fbd60e87130ac \
|
||||
--hash=sha256:4fd6b577e4541676e0cc9ddc1709d25014d3ad9a66caa19962c4f5de30fc09ef \
|
||||
--hash=sha256:500e85823a27d6d9bba1d057c871b4210c1dd6fb01fbb764e37e4e8847376323 \
|
||||
--hash=sha256:5692095123007180dca3e788bb4c399cc26626da51629a31d40207cb262e67f4 \
|
||||
--hash=sha256:5fd1b58e1431008a57247d6e7cc4faa41c3607e8e7d4aaf81f7c29ea013cb458 \
|
||||
--hash=sha256:6d489fba546295983abd142812bda76b57e33d0b9f5d5b71c09a583285506f69 \
|
||||
--hash=sha256:6f64ae8fe7ffba251fecb8408540c34ee9df1c26674c50c4544d72dbf792e5ce \
|
||||
--hash=sha256:71ef05c1726884e44f8b1d1773604ab5d4d17729d8491403a705e649116c9558 \
|
||||
--hash=sha256:77b79ce34a2bdab2594f490c8e80dd62a02d650b91a75159a63ec413b8d104cd \
|
||||
--hash=sha256:7da8831f9a0fdb526621ba09a281fadc58ea12701bc709e7b8cbc362feabc295 \
|
||||
--hash=sha256:870b9a626280c86cff9c576ec0d9cbcc54a1e5ebda9cd26dab12baf41fee218c \
|
||||
--hash=sha256:88daaf7d146e48ec71212ce21109b66e06a98e5e44dca47d853cbfe171d6c8d2 \
|
||||
--hash=sha256:8a8b10fdb84a43e50d38057b06901ec9da52baac6983d3f709d8507f3889d43f \
|
||||
--hash=sha256:8ddb2bcfd1a8b9e431c8d6f4f7db0773084e107730ecf3472f1dfe9ad583f3d9 \
|
||||
--hash=sha256:9d592d06e3cc2f537ceeeb23d38799c6ad83255289bb84c2e5792e5a8dea268a \
|
||||
--hash=sha256:a32747b1b39c3ac27d0670122b57e6e57f28eefb725e0b625618d1b59bf9d1e0 \
|
||||
--hash=sha256:a494554874691720ba5891c9b0b39474ba43ffb1aaf32a5dac874effb1619e1a \
|
||||
--hash=sha256:ae497b11f4c21558d95de9f64fff7053544f4d1a17731c866143ed6bb4591238 \
|
||||
--hash=sha256:b1ce7f41670c5a69e1389420436f41385b1aa2504c3b0c30620764b15dded2e7 \
|
||||
--hash=sha256:ba0c325c3f485dc54ec298d8b024e134acf07c10d494ffa24373bea729acf704 \
|
||||
--hash=sha256:bb29aaa613c0a1c40d1af111abf025f1732cab333f96f285d6a93b934738a68a \
|
||||
--hash=sha256:cb643284ab0ed26f6957d969fe0dd8bb17beb567beb8998140b5e38a90974f6c \
|
||||
--hash=sha256:d275a9e3c81b1093c060c3837e580c37f47c51eca031f7b5fb76f7b8470f5f9b \
|
||||
--hash=sha256:d8b55ea20dc59b181d3f47103f113e6f28a5e1c89fd5b67b9140edb442ab67f2 \
|
||||
--hash=sha256:e4141c5a32b5e37905b5940aacbc59739f036930367d7acce7a64e4dec1f5e0b
|
||||
# via
|
||||
# msgpack-types
|
||||
# osu-lazer-api
|
||||
msgpack-types==0.5.0 \
|
||||
--hash=sha256:8b633ed75e495a555fa0615843de559a74b1d176828d59bb393d266e51f6bda7 \
|
||||
--hash=sha256:aebd1b8da23f8f9966d66ebb1a43bd261b95751c6a267bd21a124d2ccac84201
|
||||
nodeenv==1.9.1 \
|
||||
--hash=sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f \
|
||||
--hash=sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9
|
||||
# via pre-commit
|
||||
passlib==1.7.4 \
|
||||
--hash=sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1 \
|
||||
--hash=sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04
|
||||
# via osu-lazer-api
|
||||
platformdirs==4.3.8 \
|
||||
--hash=sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc \
|
||||
--hash=sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4
|
||||
# via virtualenv
|
||||
pre-commit==4.2.0 \
|
||||
--hash=sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146 \
|
||||
--hash=sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd
|
||||
pyasn1==0.6.1 \
|
||||
--hash=sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629 \
|
||||
--hash=sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034
|
||||
# via
|
||||
# python-jose
|
||||
# rsa
|
||||
pycparser==2.22 ; platform_python_implementation != 'PyPy' \
|
||||
--hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \
|
||||
--hash=sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc
|
||||
# via cffi
|
||||
pydantic==2.11.7 \
|
||||
--hash=sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db \
|
||||
--hash=sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b
|
||||
# via
|
||||
# fastapi
|
||||
# osu-lazer-api
|
||||
# sqlmodel
|
||||
pydantic-core==2.33.2 \
|
||||
--hash=sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56 \
|
||||
--hash=sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef \
|
||||
--hash=sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a \
|
||||
--hash=sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f \
|
||||
--hash=sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab \
|
||||
--hash=sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916 \
|
||||
--hash=sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf \
|
||||
--hash=sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a \
|
||||
--hash=sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7 \
|
||||
--hash=sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612 \
|
||||
--hash=sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1 \
|
||||
--hash=sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7 \
|
||||
--hash=sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a \
|
||||
--hash=sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7 \
|
||||
--hash=sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025 \
|
||||
--hash=sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849 \
|
||||
--hash=sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b \
|
||||
--hash=sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e \
|
||||
--hash=sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea \
|
||||
--hash=sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac \
|
||||
--hash=sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51 \
|
||||
--hash=sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e \
|
||||
--hash=sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162 \
|
||||
--hash=sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65 \
|
||||
--hash=sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de \
|
||||
--hash=sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc \
|
||||
--hash=sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb \
|
||||
--hash=sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef \
|
||||
--hash=sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1 \
|
||||
--hash=sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5 \
|
||||
--hash=sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88 \
|
||||
--hash=sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290 \
|
||||
--hash=sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d \
|
||||
--hash=sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc \
|
||||
--hash=sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc \
|
||||
--hash=sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30 \
|
||||
--hash=sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e \
|
||||
--hash=sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9 \
|
||||
--hash=sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9 \
|
||||
--hash=sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f \
|
||||
--hash=sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5 \
|
||||
--hash=sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab \
|
||||
--hash=sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593 \
|
||||
--hash=sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1 \
|
||||
--hash=sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f \
|
||||
--hash=sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8 \
|
||||
--hash=sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf \
|
||||
--hash=sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246 \
|
||||
--hash=sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9 \
|
||||
--hash=sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011 \
|
||||
--hash=sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6 \
|
||||
--hash=sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8 \
|
||||
--hash=sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2 \
|
||||
--hash=sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6 \
|
||||
--hash=sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d
|
||||
# via pydantic
|
||||
pymysql==1.1.1 \
|
||||
--hash=sha256:4de15da4c61dc132f4fb9ab763063e693d521a80fd0e87943b9a453dd4c19d6c \
|
||||
--hash=sha256:e127611aaf2b417403c60bf4dc570124aeb4a57f5f37b8e95ae399a42f904cd0
|
||||
# via aiomysql
|
||||
python-dotenv==1.1.1 \
|
||||
--hash=sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc \
|
||||
--hash=sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab
|
||||
# via
|
||||
# osu-lazer-api
|
||||
# uvicorn
|
||||
python-jose==3.5.0 \
|
||||
--hash=sha256:abd1202f23d34dfad2c3d28cb8617b90acf34132c7afd60abd0b0b7d3cb55771 \
|
||||
--hash=sha256:fb4eaa44dbeb1c26dcc69e4bd7ec54a1cb8dd64d3b4d81ef08d90ff453f2b01b
|
||||
# via osu-lazer-api
|
||||
python-multipart==0.0.20 \
|
||||
--hash=sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104 \
|
||||
--hash=sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13
|
||||
# via osu-lazer-api
|
||||
pyyaml==6.0.2 \
|
||||
--hash=sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48 \
|
||||
--hash=sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133 \
|
||||
--hash=sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5 \
|
||||
--hash=sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484 \
|
||||
--hash=sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee \
|
||||
--hash=sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5 \
|
||||
--hash=sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85 \
|
||||
--hash=sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc \
|
||||
--hash=sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1 \
|
||||
--hash=sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317 \
|
||||
--hash=sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c \
|
||||
--hash=sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652 \
|
||||
--hash=sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5 \
|
||||
--hash=sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e \
|
||||
--hash=sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8 \
|
||||
--hash=sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476 \
|
||||
--hash=sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563 \
|
||||
--hash=sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b \
|
||||
--hash=sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425 \
|
||||
--hash=sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183 \
|
||||
--hash=sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab \
|
||||
--hash=sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774 \
|
||||
--hash=sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725 \
|
||||
--hash=sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e \
|
||||
--hash=sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44 \
|
||||
--hash=sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4 \
|
||||
--hash=sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba \
|
||||
--hash=sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4
|
||||
# via
|
||||
# pre-commit
|
||||
# uvicorn
|
||||
redis==6.2.0 \
|
||||
--hash=sha256:c8ddf316ee0aab65f04a11229e94a64b2618451dab7a67cb2f77eb799d872d5e \
|
||||
--hash=sha256:e821f129b75dde6cb99dd35e5c76e8c49512a5a0d8dfdc560b2fbd44b85ca977
|
||||
# via osu-lazer-api
|
||||
rsa==4.9.1 \
|
||||
--hash=sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762 \
|
||||
--hash=sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75
|
||||
# via python-jose
|
||||
ruff==0.12.4 \
|
||||
--hash=sha256:0618ec4442a83ab545e5b71202a5c0ed7791e8471435b94e655b570a5031a98e \
|
||||
--hash=sha256:0fc426bec2e4e5f4c4f182b9d2ce6a75c85ba9bcdbe5c6f2a74fcb8df437df4b \
|
||||
--hash=sha256:13efa16df6c6eeb7d0f091abae50f58e9522f3843edb40d56ad52a5a4a4b6873 \
|
||||
--hash=sha256:2abc48f3d9667fdc74022380b5c745873499ff827393a636f7a59da1515e7c57 \
|
||||
--hash=sha256:2b2449dc0c138d877d629bea151bee8c0ae3b8e9c43f5fcaafcd0c0d0726b184 \
|
||||
--hash=sha256:478fccdb82ca148a98a9ff43658944f7ab5ec41c3c49d77cd99d44da019371a1 \
|
||||
--hash=sha256:4de27977827893cdfb1211d42d84bc180fceb7b72471104671c59be37041cf93 \
|
||||
--hash=sha256:55c0f4ca9769408d9b9bac530c30d3e66490bd2beb2d3dae3e4128a1f05c7442 \
|
||||
--hash=sha256:56e45bb11f625db55f9b70477062e6a1a04d53628eda7784dce6e0f55fd549eb \
|
||||
--hash=sha256:a7dea966bcb55d4ecc4cc3270bccb6f87a337326c9dcd3c07d5b97000dbff41c \
|
||||
--hash=sha256:a8224cc3722c9ad9044da7f89c4c1ec452aef2cfe3904365025dd2f51daeae0e \
|
||||
--hash=sha256:afcfa3ab5ab5dd0e1c39bf286d829e042a15e966b3726eea79528e2e24d8371a \
|
||||
--hash=sha256:be0593c69df9ad1465e8a2d10e3defd111fdb62dcd5be23ae2c06da77e8fcffb \
|
||||
--hash=sha256:c057ce464b1413c926cdb203a0f858cd52f3e73dcb3270a3318d1630f6395bb3 \
|
||||
--hash=sha256:cb0d261dac457ab939aeb247e804125a5d521b21adf27e721895b0d3f83a0d0a \
|
||||
--hash=sha256:e64b90d1122dc2713330350626b10d60818930819623abbb56535c6466cce045 \
|
||||
--hash=sha256:e9949d01d64fa3672449a51ddb5d7548b33e130240ad418884ee6efa7a229586 \
|
||||
--hash=sha256:fe0b9e9eb23736b453143d72d2ceca5db323963330d5b7859d60d101147d461a
|
||||
six==1.17.0 \
|
||||
--hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \
|
||||
--hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81
|
||||
# via ecdsa
|
||||
sniffio==1.3.1 \
|
||||
--hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \
|
||||
--hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc
|
||||
# via anyio
|
||||
sqlalchemy==2.0.41 \
|
||||
--hash=sha256:03968a349db483936c249f4d9cd14ff2c296adfa1290b660ba6516f973139582 \
|
||||
--hash=sha256:293cd444d82b18da48c9f71cd7005844dbbd06ca19be1ccf6779154439eec0b8 \
|
||||
--hash=sha256:32f9dc8c44acdee06c8fc6440db9eae8b4af8b01e4b1aee7bdd7241c22edff4f \
|
||||
--hash=sha256:3d3549fc3e40667ec7199033a4e40a2f669898a00a7b18a931d3efb4c7900504 \
|
||||
--hash=sha256:41836fe661cc98abfae476e14ba1906220f92c4e528771a8a3ae6a151242d2ae \
|
||||
--hash=sha256:4eeb195cdedaf17aab6b247894ff2734dcead6c08f748e617bfe05bd5a218443 \
|
||||
--hash=sha256:4f67766965996e63bb46cfbf2ce5355fc32d9dd3b8ad7e536a920ff9ee422e23 \
|
||||
--hash=sha256:57df5dc6fdb5ed1a88a1ed2195fd31927e705cad62dedd86b46972752a80f576 \
|
||||
--hash=sha256:598d9ebc1e796431bbd068e41e4de4dc34312b7aa3292571bb3674a0cb415dd1 \
|
||||
--hash=sha256:5b14e97886199c1f52c14629c11d90c11fbb09e9334fa7bb5f6d068d9ced0ce0 \
|
||||
--hash=sha256:6145afea51ff0af7f2564a05fa95eb46f542919e6523729663a5d285ecb3cf5e \
|
||||
--hash=sha256:6375cd674fe82d7aa9816d1cb96ec592bac1726c11e0cafbf40eeee9a4516b5f \
|
||||
--hash=sha256:81f413674d85cfd0dfcd6512e10e0f33c19c21860342a4890c3a2b59479929f9 \
|
||||
--hash=sha256:82ca366a844eb551daff9d2e6e7a9e5e76d2612c8564f58db6c19a726869c1df \
|
||||
--hash=sha256:90c11ceb9a1f482c752a71f203a81858625d8df5746d787a4786bca4ffdf71c6 \
|
||||
--hash=sha256:911cc493ebd60de5f285bcae0491a60b4f2a9f0f5c270edd1c4dbaef7a38fc04 \
|
||||
--hash=sha256:9f8c9fdd15a55d9465e590a402f42082705d66b05afc3ffd2d2eb3c6ba919560 \
|
||||
--hash=sha256:a104c5694dfd2d864a6f91b0956eb5d5883234119cb40010115fd45a16da5e70 \
|
||||
--hash=sha256:a62448526dd9ed3e3beedc93df9bb6b55a436ed1474db31a2af13b313a70a7e1 \
|
||||
--hash=sha256:a8808d5cf866c781150d36a3c8eb3adccfa41a8105d031bf27e92c251e3969d6 \
|
||||
--hash=sha256:b46fa6eae1cd1c20e6e6f44e19984d438b6b2d8616d21d783d150df714f44078 \
|
||||
--hash=sha256:bfc9064f6658a3d1cadeaa0ba07570b83ce6801a1314985bf98ec9b95d74e15f \
|
||||
--hash=sha256:c153265408d18de4cc5ded1941dcd8315894572cddd3c58df5d5b5705b3fa28d \
|
||||
--hash=sha256:d4ae769b9c1c7757e4ccce94b0641bc203bbdf43ba7a2413ab2523d8d047d8dc \
|
||||
--hash=sha256:dc56c9788617b8964ad02e8fcfeed4001c1f8ba91a9e1f31483c0dffb207002a \
|
||||
--hash=sha256:edba70118c4be3c2b1f90754d308d0b79c6fe2c0fdc52d8ddf603916f83f4db9
|
||||
# via
|
||||
# alembic
|
||||
# osu-lazer-api
|
||||
# sqlmodel
|
||||
sqlmodel==0.0.24 \
|
||||
--hash=sha256:6778852f09370908985b667d6a3ab92910d0d5ec88adcaf23dbc242715ff7193 \
|
||||
--hash=sha256:cc5c7613c1a5533c9c7867e1aab2fd489a76c9e8a061984da11b4e613c182423
|
||||
# via osu-lazer-api
|
||||
starlette==0.47.2 \
|
||||
--hash=sha256:6ae9aa5db235e4846decc1e7b79c4f346adf41e9777aebeb49dfd09bbd7023d8 \
|
||||
--hash=sha256:c5847e96134e5c5371ee9fac6fdf1a67336d5815e09eb2a01fdb57a351ef915b
|
||||
# via fastapi
|
||||
typing-extensions==4.14.1 \
|
||||
--hash=sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36 \
|
||||
--hash=sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76
|
||||
# via
|
||||
# alembic
|
||||
# anyio
|
||||
# fastapi
|
||||
# msgpack-types
|
||||
# pydantic
|
||||
# pydantic-core
|
||||
# sqlalchemy
|
||||
# starlette
|
||||
# typing-inspection
|
||||
typing-inspection==0.4.1 \
|
||||
--hash=sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51 \
|
||||
--hash=sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28
|
||||
# via pydantic
|
||||
uvicorn==0.35.0 \
|
||||
--hash=sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a \
|
||||
--hash=sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01
|
||||
# via osu-lazer-api
|
||||
uvloop==0.21.0 ; platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32' \
|
||||
--hash=sha256:0878c2640cf341b269b7e128b1a5fed890adc4455513ca710d77d5e93aa6d6a0 \
|
||||
--hash=sha256:183aef7c8730e54c9a3ee3227464daed66e37ba13040bb3f350bc2ddc040f22f \
|
||||
--hash=sha256:359ec2c888397b9e592a889c4d72ba3d6befba8b2bb01743f72fffbde663b59c \
|
||||
--hash=sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3 \
|
||||
--hash=sha256:4509360fcc4c3bd2c70d87573ad472de40c13387f5fda8cb58350a1d7475e58d \
|
||||
--hash=sha256:461d9ae6660fbbafedd07559c6a2e57cd553b34b0065b6550685f6653a98c1cb \
|
||||
--hash=sha256:5ee4d4ef48036ff6e5cfffb09dd192c7a5027153948d85b8da7ff705065bacc6 \
|
||||
--hash=sha256:787ae31ad8a2856fc4e7c095341cccc7209bd657d0e71ad0dc2ea83c4a6fa8af \
|
||||
--hash=sha256:86975dca1c773a2c9864f4c52c5a55631038e387b47eaf56210f873887b6c8dc \
|
||||
--hash=sha256:8a375441696e2eda1c43c44ccb66e04d61ceeffcd76e4929e527b7fa401b90fb \
|
||||
--hash=sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553 \
|
||||
--hash=sha256:b9fb766bb57b7388745d8bcc53a359b116b8a04c83a2288069809d2b3466c37e \
|
||||
--hash=sha256:baa0e6291d91649c6ba4ed4b2f982f9fa165b5bbd50a9e203c416a2797bab3c6 \
|
||||
--hash=sha256:baa4dcdbd9ae0a372f2167a207cd98c9f9a1ea1188a8a526431eef2f8116cc8d \
|
||||
--hash=sha256:bd53ecc9a0f3d87ab847503c2e1552b690362e005ab54e8a48ba97da3924c0dc \
|
||||
--hash=sha256:bfd55dfcc2a512316e65f16e503e9e450cab148ef11df4e4e679b5e8253a5281 \
|
||||
--hash=sha256:c0f3fa6200b3108919f8bdabb9a7f87f20e7097ea3c543754cabc7d717d95cf8 \
|
||||
--hash=sha256:f3df876acd7ec037a3d005b3ab85a7e4110422e4d9c1571d4fc89b0fc41b6816 \
|
||||
--hash=sha256:f7089d2dc73179ce5ac255bdf37c236a9f914b264825fdaacaded6990a7fb4c2
|
||||
# via uvicorn
|
||||
virtualenv==20.32.0 \
|
||||
--hash=sha256:2c310aecb62e5aa1b06103ed7c2977b81e042695de2697d01017ff0f1034af56 \
|
||||
--hash=sha256:886bf75cadfdc964674e6e33eb74d787dff31ca314ceace03ca5810620f4ecf0
|
||||
# via pre-commit
|
||||
watchfiles==1.1.0 \
|
||||
--hash=sha256:00645eb79a3faa70d9cb15c8d4187bb72970b2470e938670240c7998dad9f13a \
|
||||
--hash=sha256:0a7d40b77f07be87c6faa93d0951a0fcd8cbca1ddff60a1b65d741bac6f3a9f6 \
|
||||
--hash=sha256:0ece16b563b17ab26eaa2d52230c9a7ae46cf01759621f4fbbca280e438267b3 \
|
||||
--hash=sha256:11ee4444250fcbeb47459a877e5e80ed994ce8e8d20283857fc128be1715dac7 \
|
||||
--hash=sha256:12b0a02a91762c08f7264e2e79542f76870c3040bbc847fb67410ab81474932a \
|
||||
--hash=sha256:12fe8eaffaf0faa7906895b4f8bb88264035b3f0243275e0bf24af0436b27259 \
|
||||
--hash=sha256:130fc497b8ee68dce163e4254d9b0356411d1490e868bd8790028bc46c5cc297 \
|
||||
--hash=sha256:17ab167cca6339c2b830b744eaf10803d2a5b6683be4d79d8475d88b4a8a4be1 \
|
||||
--hash=sha256:20ecc8abbd957046f1fe9562757903f5eaf57c3bce70929fda6c7711bb58074a \
|
||||
--hash=sha256:239736577e848678e13b201bba14e89718f5c2133dfd6b1f7846fa1b58a8532b \
|
||||
--hash=sha256:249590eb75ccc117f488e2fabd1bfa33c580e24b96f00658ad88e38844a040bb \
|
||||
--hash=sha256:29e7bc2eee15cbb339c68445959108803dc14ee0c7b4eea556400131a8de462b \
|
||||
--hash=sha256:328dbc9bff7205c215a7807da7c18dce37da7da718e798356212d22696404339 \
|
||||
--hash=sha256:32d6d4e583593cb8576e129879ea0991660b935177c0f93c6681359b3654bfa9 \
|
||||
--hash=sha256:3434e401f3ce0ed6b42569128b3d1e3af773d7ec18751b918b89cd49c14eaafb \
|
||||
--hash=sha256:37d3d3f7defb13f62ece99e9be912afe9dd8a0077b7c45ee5a57c74811d581a4 \
|
||||
--hash=sha256:406520216186b99374cdb58bc48e34bb74535adec160c8459894884c983a149c \
|
||||
--hash=sha256:4281cd9fce9fc0a9dbf0fc1217f39bf9cf2b4d315d9626ef1d4e87b84699e7e8 \
|
||||
--hash=sha256:48aa25e5992b61debc908a61ab4d3f216b64f44fdaa71eb082d8b2de846b7d12 \
|
||||
--hash=sha256:5007f860c7f1f8df471e4e04aaa8c43673429047d63205d1630880f7637bca30 \
|
||||
--hash=sha256:50a51a90610d0845a5931a780d8e51d7bd7f309ebc25132ba975aca016b576a0 \
|
||||
--hash=sha256:51b81e55d40c4b4aa8658427a3ee7ea847c591ae9e8b81ef94a90b668999353c \
|
||||
--hash=sha256:5366164391873ed76bfdf618818c82084c9db7fac82b64a20c44d335eec9ced5 \
|
||||
--hash=sha256:60022527e71d1d1fda67a33150ee42869042bce3d0fcc9cc49be009a9cded3fb \
|
||||
--hash=sha256:622d6b2c06be19f6e89b1d951485a232e3b59618def88dbeda575ed8f0d8dbf2 \
|
||||
--hash=sha256:62cc7a30eeb0e20ecc5f4bd113cd69dcdb745a07c68c0370cea919f373f65d9e \
|
||||
--hash=sha256:693ed7ec72cbfcee399e92c895362b6e66d63dac6b91e2c11ae03d10d503e575 \
|
||||
--hash=sha256:6d2404af8db1329f9a3c9b79ff63e0ae7131986446901582067d9304ae8aaf7f \
|
||||
--hash=sha256:7080c4bb3efd70a07b1cc2df99a7aa51d98685be56be6038c3169199d0a1c69f \
|
||||
--hash=sha256:7738027989881e70e3723c75921f1efa45225084228788fc59ea8c6d732eb30d \
|
||||
--hash=sha256:7fd1b3879a578a8ec2076c7961076df540b9af317123f84569f5a9ddee64ce92 \
|
||||
--hash=sha256:80f811146831c8c86ab17b640801c25dc0a88c630e855e2bef3568f30434d52b \
|
||||
--hash=sha256:891c69e027748b4a73847335d208e374ce54ca3c335907d381fde4e41661b13b \
|
||||
--hash=sha256:8ac164e20d17cc285f2b94dc31c384bc3aa3dd5e7490473b3db043dd70fbccfd \
|
||||
--hash=sha256:8c5701dc474b041e2934a26d31d39f90fac8a3dee2322b39f7729867f932b1d4 \
|
||||
--hash=sha256:923fec6e5461c42bd7e3fd5ec37492c6f3468be0499bc0707b4bbbc16ac21792 \
|
||||
--hash=sha256:95ab1594377effac17110e1352989bdd7bdfca9ff0e5eeccd8c69c5389b826d0 \
|
||||
--hash=sha256:9974d2f7dc561cce3bb88dfa8eb309dab64c729de85fba32e98d75cf24b66297 \
|
||||
--hash=sha256:9c733cda03b6d636b4219625a4acb5c6ffb10803338e437fb614fef9516825ef \
|
||||
--hash=sha256:9dc001c3e10de4725c749d4c2f2bdc6ae24de5a88a339c4bce32300a31ede179 \
|
||||
--hash=sha256:a543492513a93b001975ae283a51f4b67973662a375a403ae82f420d2c7205ee \
|
||||
--hash=sha256:a8f6f72974a19efead54195bc9bed4d850fc047bb7aa971268fd9a8387c89011 \
|
||||
--hash=sha256:a9ccbf1f129480ed3044f540c0fdbc4ee556f7175e5ab40fe077ff6baf286d4e \
|
||||
--hash=sha256:adb4167043d3a78280d5d05ce0ba22055c266cf8655ce942f2fb881262ff3cdf \
|
||||
--hash=sha256:af06c863f152005c7592df1d6a7009c836a247c9d8adb78fef8575a5a98699db \
|
||||
--hash=sha256:b067915e3c3936966a8607f6fe5487df0c9c4afb85226613b520890049deea20 \
|
||||
--hash=sha256:b7c5f6fe273291f4d414d55b2c80d33c457b8a42677ad14b4b47ff025d0893e4 \
|
||||
--hash=sha256:b915daeb2d8c1f5cee4b970f2e2c988ce6514aace3c9296e58dd64dc9aa5d575 \
|
||||
--hash=sha256:ba0e3255b0396cac3cc7bbace76404dd72b5438bf0d8e7cefa2f79a7f3649caa \
|
||||
--hash=sha256:bda8136e6a80bdea23e5e74e09df0362744d24ffb8cd59c4a95a6ce3d142f79c \
|
||||
--hash=sha256:bfe3c517c283e484843cb2e357dd57ba009cff351edf45fb455b5fbd1f45b15f \
|
||||
--hash=sha256:c68e9f1fcb4d43798ad8814c4c1b61547b014b667216cb754e606bfade587018 \
|
||||
--hash=sha256:c9649dfc57cc1f9835551deb17689e8d44666315f2e82d337b9f07bd76ae3aa2 \
|
||||
--hash=sha256:cb45350fd1dc75cd68d3d72c47f5b513cb0578da716df5fba02fff31c69d5f2d \
|
||||
--hash=sha256:cbcf8630ef4afb05dc30107bfa17f16c0896bb30ee48fc24bf64c1f970f3b1fd \
|
||||
--hash=sha256:cbd949bdd87567b0ad183d7676feb98136cde5bb9025403794a4c0db28ed3a47 \
|
||||
--hash=sha256:cc08ef8b90d78bfac66f0def80240b0197008e4852c9f285907377b2947ffdcb \
|
||||
--hash=sha256:d05686b5487cfa2e2c28ff1aa370ea3e6c5accfe6435944ddea1e10d93872147 \
|
||||
--hash=sha256:d0e10e6f8f6dc5762adee7dece33b722282e1f59aa6a55da5d493a97282fedd8 \
|
||||
--hash=sha256:d181ef50923c29cf0450c3cd47e2f0557b62218c50b2ab8ce2ecaa02bd97e670 \
|
||||
--hash=sha256:d9481174d3ed982e269c090f780122fb59cee6c3796f74efe74e70f7780ed94c \
|
||||
--hash=sha256:d9ba68ec283153dead62cbe81872d28e053745f12335d037de9cbd14bd1877f5 \
|
||||
--hash=sha256:dc44678a72ac0910bac46fa6a0de6af9ba1355669b3dfaf1ce5f05ca7a74364e \
|
||||
--hash=sha256:e78b6ed8165996013165eeabd875c5dfc19d41b54f94b40e9fff0eb3193e5e8e \
|
||||
--hash=sha256:ed8fc66786de8d0376f9f913c09e963c66e90ced9aa11997f93bdb30f7c872a8 \
|
||||
--hash=sha256:eff4b8d89f444f7e49136dc695599a591ff769300734446c0a86cba2eb2f9895 \
|
||||
--hash=sha256:f21af781a4a6fbad54f03c598ab620e3a77032c5878f3d780448421a6e1818c7 \
|
||||
--hash=sha256:f2bcdc54ea267fe72bfc7d83c041e4eb58d7d8dc6f578dfddb52f037ce62f432 \
|
||||
--hash=sha256:f2f0498b7d2a3c072766dba3274fe22a183dbea1f99d188f1c6c72209a1063dc \
|
||||
--hash=sha256:f7208ab6e009c627b7557ce55c465c98967e8caa8b11833531fdf95799372633 \
|
||||
--hash=sha256:f7590d5a455321e53857892ab8879dce62d1f4b04748769f5adf2e707afb9d4f \
|
||||
--hash=sha256:fa257a4d0d21fcbca5b5fcba9dca5a78011cb93c0323fb8855c6d2dfbc76eb77 \
|
||||
--hash=sha256:fba9b62da882c1be1280a7584ec4515d0a6006a94d6e5819730ec2eab60ffe12 \
|
||||
--hash=sha256:fe4371595edf78c41ef8ac8df20df3943e13defd0efcb732b2e393b5a8a7a71f
|
||||
# via uvicorn
|
||||
websockets==15.0.1 \
|
||||
--hash=sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2 \
|
||||
--hash=sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5 \
|
||||
--hash=sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8 \
|
||||
--hash=sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85 \
|
||||
--hash=sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375 \
|
||||
--hash=sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065 \
|
||||
--hash=sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597 \
|
||||
--hash=sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f \
|
||||
--hash=sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3 \
|
||||
--hash=sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf \
|
||||
--hash=sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4 \
|
||||
--hash=sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665 \
|
||||
--hash=sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22 \
|
||||
--hash=sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675 \
|
||||
--hash=sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4 \
|
||||
--hash=sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65 \
|
||||
--hash=sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792 \
|
||||
--hash=sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57 \
|
||||
--hash=sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3 \
|
||||
--hash=sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151 \
|
||||
--hash=sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d \
|
||||
--hash=sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431 \
|
||||
--hash=sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee \
|
||||
--hash=sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413 \
|
||||
--hash=sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8 \
|
||||
--hash=sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa \
|
||||
--hash=sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9 \
|
||||
--hash=sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905 \
|
||||
--hash=sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe \
|
||||
--hash=sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562 \
|
||||
--hash=sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561 \
|
||||
--hash=sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215 \
|
||||
--hash=sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931 \
|
||||
--hash=sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f \
|
||||
--hash=sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7
|
||||
# via uvicorn
|
||||
aiomysql==0.2.0
|
||||
alembic==1.16.4
|
||||
annotated-types==0.7.0
|
||||
anyio==4.9.0
|
||||
bcrypt==4.3.0
|
||||
certifi==2025.7.14
|
||||
cffi==1.17.1
|
||||
cfgv==3.4.0
|
||||
click==8.2.1
|
||||
cryptography==45.0.5
|
||||
distlib==0.4.0
|
||||
dnspython==2.7.0
|
||||
ecdsa==0.19.1
|
||||
email-validator==2.2.0
|
||||
fastapi==0.116.1
|
||||
filelock==3.18.0
|
||||
greenlet==3.2.3
|
||||
h11==0.16.0
|
||||
httpcore==1.0.9
|
||||
httptools==0.6.4
|
||||
httpx==0.28.1
|
||||
identify==2.6.12
|
||||
idna==3.10
|
||||
loguru==0.7.3
|
||||
mako==1.3.10
|
||||
markupsafe==3.0.2
|
||||
maturin==1.9.2
|
||||
-e file:///workspaces/osu_lazer_api/packages/msgpack_lazer_api
|
||||
nodeenv==1.9.1
|
||||
passlib==1.7.4
|
||||
platformdirs==4.3.8
|
||||
pre-commit==4.2.0
|
||||
pyasn1==0.6.1
|
||||
pycparser==2.22
|
||||
pydantic==2.11.7
|
||||
pydantic-core==2.33.2
|
||||
pymysql==1.1.1
|
||||
python-dotenv==1.1.1
|
||||
python-jose==3.5.0
|
||||
python-multipart==0.0.20
|
||||
pyyaml==6.0.2
|
||||
redis==6.2.0
|
||||
rosu-pp-py==3.1.0
|
||||
rsa==4.9.1
|
||||
ruff==0.12.4
|
||||
six==1.17.0
|
||||
sniffio==1.3.1
|
||||
sqlalchemy==2.0.41
|
||||
sqlmodel==0.0.24
|
||||
starlette==0.47.2
|
||||
typing-extensions==4.14.1
|
||||
typing-inspection==0.4.1
|
||||
uvicorn==0.35.0
|
||||
uvloop==0.21.0
|
||||
virtualenv==20.32.0
|
||||
watchfiles==1.1.0
|
||||
websockets==15.0.1
|
||||
|
||||
66
uv.lock
generated
66
uv.lock
generated
@@ -2,6 +2,12 @@ version = 1
|
||||
revision = 2
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[manifest]
|
||||
members = [
|
||||
"msgpack-lazer-api",
|
||||
"osu-lazer-api",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aiomysql"
|
||||
version = "0.2.0"
|
||||
@@ -448,45 +454,28 @@ wheels = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "msgpack"
|
||||
version = "1.1.1"
|
||||
name = "maturin"
|
||||
version = "1.9.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/45/b1/ea4f68038a18c77c9467400d166d74c4ffa536f34761f7983a104357e614/msgpack-1.1.1.tar.gz", hash = "sha256:77b79ce34a2bdab2594f490c8e80dd62a02d650b91a75159a63ec413b8d104cd", size = 173555, upload-time = "2025-06-13T06:52:51.324Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b1/b5/8d9843ba4d2a107ea83499d0fb6758d6d9376a3e2202dbcc5ffa972e2e4a/maturin-1.9.2.tar.gz", hash = "sha256:8b534d3a8acb922fc7a01ec89c12ba950dccdc11b57457c1d4c2661ae24bf96d", size = 211836, upload-time = "2025-07-27T19:18:26.089Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/26/389b9c593eda2b8551b2e7126ad3a06af6f9b44274eb3a4f054d48ff7e47/msgpack-1.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ae497b11f4c21558d95de9f64fff7053544f4d1a17731c866143ed6bb4591238", size = 82359, upload-time = "2025-06-13T06:52:03.909Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/65/7d1de38c8a22cf8b1551469159d4b6cf49be2126adc2482de50976084d78/msgpack-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:33be9ab121df9b6b461ff91baac6f2731f83d9b27ed948c5b9d1978ae28bf157", size = 79172, upload-time = "2025-06-13T06:52:05.246Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/bd/cacf208b64d9577a62c74b677e1ada005caa9b69a05a599889d6fc2ab20a/msgpack-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f64ae8fe7ffba251fecb8408540c34ee9df1c26674c50c4544d72dbf792e5ce", size = 425013, upload-time = "2025-06-13T06:52:06.341Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/ec/fd869e2567cc9c01278a736cfd1697941ba0d4b81a43e0aa2e8d71dab208/msgpack-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a494554874691720ba5891c9b0b39474ba43ffb1aaf32a5dac874effb1619e1a", size = 426905, upload-time = "2025-06-13T06:52:07.501Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/2a/35860f33229075bce803a5593d046d8b489d7ba2fc85701e714fc1aaf898/msgpack-1.1.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb643284ab0ed26f6957d969fe0dd8bb17beb567beb8998140b5e38a90974f6c", size = 407336, upload-time = "2025-06-13T06:52:09.047Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/16/69ed8f3ada150bf92745fb4921bd621fd2cdf5a42e25eb50bcc57a5328f0/msgpack-1.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d275a9e3c81b1093c060c3837e580c37f47c51eca031f7b5fb76f7b8470f5f9b", size = 409485, upload-time = "2025-06-13T06:52:10.382Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/b6/0c398039e4c6d0b2e37c61d7e0e9d13439f91f780686deb8ee64ecf1ae71/msgpack-1.1.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4fd6b577e4541676e0cc9ddc1709d25014d3ad9a66caa19962c4f5de30fc09ef", size = 412182, upload-time = "2025-06-13T06:52:11.644Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/d0/0cf4a6ecb9bc960d624c93effaeaae75cbf00b3bc4a54f35c8507273cda1/msgpack-1.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb29aaa613c0a1c40d1af111abf025f1732cab333f96f285d6a93b934738a68a", size = 419883, upload-time = "2025-06-13T06:52:12.806Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/83/9697c211720fa71a2dfb632cad6196a8af3abea56eece220fde4674dc44b/msgpack-1.1.1-cp312-cp312-win32.whl", hash = "sha256:870b9a626280c86cff9c576ec0d9cbcc54a1e5ebda9cd26dab12baf41fee218c", size = 65406, upload-time = "2025-06-13T06:52:14.271Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/23/0abb886e80eab08f5e8c485d6f13924028602829f63b8f5fa25a06636628/msgpack-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:5692095123007180dca3e788bb4c399cc26626da51629a31d40207cb262e67f4", size = 72558, upload-time = "2025-06-13T06:52:15.252Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/38/561f01cf3577430b59b340b51329803d3a5bf6a45864a55f4ef308ac11e3/msgpack-1.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3765afa6bd4832fc11c3749be4ba4b69a0e8d7b728f78e68120a157a4c5d41f0", size = 81677, upload-time = "2025-06-13T06:52:16.64Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/48/54a89579ea36b6ae0ee001cba8c61f776451fad3c9306cd80f5b5c55be87/msgpack-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8ddb2bcfd1a8b9e431c8d6f4f7db0773084e107730ecf3472f1dfe9ad583f3d9", size = 78603, upload-time = "2025-06-13T06:52:17.843Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/60/daba2699b308e95ae792cdc2ef092a38eb5ee422f9d2fbd4101526d8a210/msgpack-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:196a736f0526a03653d829d7d4c5500a97eea3648aebfd4b6743875f28aa2af8", size = 420504, upload-time = "2025-06-13T06:52:18.982Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/22/2ebae7ae43cd8f2debc35c631172ddf14e2a87ffcc04cf43ff9df9fff0d3/msgpack-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d592d06e3cc2f537ceeeb23d38799c6ad83255289bb84c2e5792e5a8dea268a", size = 423749, upload-time = "2025-06-13T06:52:20.211Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/1b/54c08dd5452427e1179a40b4b607e37e2664bca1c790c60c442c8e972e47/msgpack-1.1.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4df2311b0ce24f06ba253fda361f938dfecd7b961576f9be3f3fbd60e87130ac", size = 404458, upload-time = "2025-06-13T06:52:21.429Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/60/6bb17e9ffb080616a51f09928fdd5cac1353c9becc6c4a8abd4e57269a16/msgpack-1.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e4141c5a32b5e37905b5940aacbc59739f036930367d7acce7a64e4dec1f5e0b", size = 405976, upload-time = "2025-06-13T06:52:22.995Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/97/88983e266572e8707c1f4b99c8fd04f9eb97b43f2db40e3172d87d8642db/msgpack-1.1.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b1ce7f41670c5a69e1389420436f41385b1aa2504c3b0c30620764b15dded2e7", size = 408607, upload-time = "2025-06-13T06:52:24.152Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/66/36c78af2efaffcc15a5a61ae0df53a1d025f2680122e2a9eb8442fed3ae4/msgpack-1.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4147151acabb9caed4e474c3344181e91ff7a388b888f1e19ea04f7e73dc7ad5", size = 424172, upload-time = "2025-06-13T06:52:25.704Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/87/a75eb622b555708fe0427fab96056d39d4c9892b0c784b3a721088c7ee37/msgpack-1.1.1-cp313-cp313-win32.whl", hash = "sha256:500e85823a27d6d9bba1d057c871b4210c1dd6fb01fbb764e37e4e8847376323", size = 65347, upload-time = "2025-06-13T06:52:26.846Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/91/7dc28d5e2a11a5ad804cf2b7f7a5fcb1eb5a4966d66a5d2b41aee6376543/msgpack-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:6d489fba546295983abd142812bda76b57e33d0b9f5d5b71c09a583285506f69", size = 72341, upload-time = "2025-06-13T06:52:27.835Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/db/f20d4ccdfac063bde8c502d6bc6fae10c41ac8e2b1eadbce75ee706d5050/maturin-1.9.2-py3-none-linux_armv6l.whl", hash = "sha256:8f5d448b58c67ba8ef62c066ca02cf154557f2df20791d63e392e08e7721a03b", size = 8271893, upload-time = "2025-07-27T19:18:02.749Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/c7/abfc2320a5e03d4c9d69bfa1c222f376f44c07374ed7c4c777b75099d265/maturin-1.9.2-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:8dc091e8d98a9f7b8740b151f79685f43f305aa5ec444dbb3e49cacc036eb991", size = 16083703, upload-time = "2025-07-27T19:18:05.857Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/8c/2bde4220b74cb3ccf85cd1d8bd69493810540b079690624de0e4539bfa70/maturin-1.9.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ee768507f25da3ae8e18d2fb2e430a44552b3192a40d3ab1eae3f4a67f5792b5", size = 8423450, upload-time = "2025-07-27T19:18:07.885Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/2c/eccb9705470ac331feb5887293348be3d6a04f37cd611ee2cd2e78339a47/maturin-1.9.2-py3-none-manylinux_2_12_i686.manylinux2010_i686.musllinux_1_1_i686.whl", hash = "sha256:b7dc9a38089bbd1ac93d9046a9db14a41d011efdb7d6059bb557ebd97ab207e7", size = 8267146, upload-time = "2025-07-27T19:18:09.887Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/40/90f43c8334e7a498817188728f758f628a2c2250ab8dcd062ce184303668/maturin-1.9.2-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:234b8cb12f14173452d71293c7ec86998b2d1c9f247b66023aa8b306d288817c", size = 8792134, upload-time = "2025-07-27T19:18:11.871Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/44/94d50a59311c9a41af88d96be7d074190d1908709876b1c9b136fb65141b/maturin-1.9.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:0fe1480ac341b9ca733fdbf16a499fd2771e797819de416b5a47846ed0f8a17d", size = 8071546, upload-time = "2025-07-27T19:18:13.91Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/8b/3a45dc1dbe4561658923b86a50bb6413bc0e9e56fd5d0f5293c7919dc4c7/maturin-1.9.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:029572e692af5bac4f58586f294883732b14d41abb3ba83a587a9c68c6824a2a", size = 8132544, upload-time = "2025-07-27T19:18:15.72Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/f2/56c78ebd3d305a0912b5043b53bbdb627a2d3e2bb027e5e19305fe0ae557/maturin-1.9.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.musllinux_1_1_ppc64le.whl", hash = "sha256:58d69b388e52e93f43d25429afccaf4d1b04bb94606d46ddb21f7a97003ec174", size = 10616860, upload-time = "2025-07-27T19:18:17.478Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/ab/77c5ef78b069c4a3bda175eafa922537bccebe038735f165ed1a04220748/maturin-1.9.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:09750de66dce2ae04ad3ac7df1266dfe77abdcf84b7b6cbe43a41cd7e89cf958", size = 8939318, upload-time = "2025-07-27T19:18:19.097Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/f8/1897ecf4b0115b8df71011d8be3f5aa3a6dde47366d724c064fdb40fa357/maturin-1.9.2-py3-none-win32.whl", hash = "sha256:b2b12e88f5e3145cc4d749a1fb8958a83842f647c459112e9a8d75285521798f", size = 7276436, upload-time = "2025-07-27T19:18:21.318Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/4f/437d363c4ee97bd9b88d68ff432067aaf07b10ffa0fa0bad3329d2567919/maturin-1.9.2-py3-none-win_amd64.whl", hash = "sha256:1df15e896b3a05396a91ec2e60295bd70075f984736b8c84e8b14941bcba40c8", size = 8283346, upload-time = "2025-07-27T19:18:22.652Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/04/946552db9cfa831cee2df1bf3c857b06a8ed04f63ded9a3692dd6f88bdee/maturin-1.9.2-py3-none-win_arm64.whl", hash = "sha256:95a08e849fda883e1a961218b7834af6f128eb9fc9ace4b90b65031da6028251", size = 6942238, upload-time = "2025-07-27T19:18:24.432Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "msgpack-types"
|
||||
version = "0.5.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "msgpack" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7f/26/a15707f2af5681333cd598724bedd1948844ac2af45eafc4175af0671a8d/msgpack_types-0.5.0.tar.gz", hash = "sha256:aebd1b8da23f8f9966d66ebb1a43bd261b95751c6a267bd21a124d2ccac84201", size = 6702, upload-time = "2024-09-21T13:55:05.587Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/19/dd/cd9d2b0ef506f6164cd81d4e92e408095041f28523d751b9f7dabdc244eb/msgpack_types-0.5.0-py3-none-any.whl", hash = "sha256:8b633ed75e495a555fa0615843de559a74b1d176828d59bb393d266e51f6bda7", size = 8182, upload-time = "2024-09-21T13:55:04.232Z" },
|
||||
]
|
||||
name = "msgpack-lazer-api"
|
||||
source = { editable = "packages/msgpack_lazer_api" }
|
||||
|
||||
[[package]]
|
||||
name = "nodeenv"
|
||||
@@ -509,7 +498,7 @@ dependencies = [
|
||||
{ name = "fastapi" },
|
||||
{ name = "httpx" },
|
||||
{ name = "loguru" },
|
||||
{ name = "msgpack" },
|
||||
{ name = "msgpack-lazer-api" },
|
||||
{ name = "passlib", extra = ["bcrypt"] },
|
||||
{ name = "pydantic", extra = ["email"] },
|
||||
{ name = "python-dotenv" },
|
||||
@@ -524,7 +513,7 @@ dependencies = [
|
||||
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "msgpack-types" },
|
||||
{ name = "maturin" },
|
||||
{ name = "pre-commit" },
|
||||
{ name = "ruff" },
|
||||
]
|
||||
@@ -538,7 +527,7 @@ requires-dist = [
|
||||
{ name = "fastapi", specifier = ">=0.104.1" },
|
||||
{ name = "httpx", specifier = ">=0.28.1" },
|
||||
{ name = "loguru", specifier = ">=0.7.3" },
|
||||
{ name = "msgpack", specifier = ">=1.1.1" },
|
||||
{ name = "msgpack-lazer-api", editable = "packages/msgpack_lazer_api" },
|
||||
{ name = "passlib", extras = ["bcrypt"], specifier = ">=1.7.4" },
|
||||
{ name = "pydantic", extras = ["email"], specifier = ">=2.5.0" },
|
||||
{ name = "python-dotenv", specifier = ">=1.0.0" },
|
||||
@@ -553,11 +542,10 @@ requires-dist = [
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
{ name = "msgpack-types", specifier = ">=0.5.0" },
|
||||
{ name = "maturin", specifier = ">=1.9.2" },
|
||||
{ name = "pre-commit", specifier = ">=4.2.0" },
|
||||
{ name = "ruff", specifier = ">=0.12.4" },
|
||||
]
|
||||
migration = []
|
||||
|
||||
[[package]]
|
||||
name = "passlib"
|
||||
|
||||
Reference in New Issue
Block a user