Compare commits
No commits in common. "cf23b04d3e5d11a38f1bf62a4b4b96b5ffb4ca79" and "a284498a4311d22530416a6e684fef06c9b10b6c" have entirely different histories.
cf23b04d3e
...
a284498a43
8
Makefile
8
Makefile
|
|
@ -1,5 +1,5 @@
|
||||||
api:
|
api:
|
||||||
alembic upgrade head && uv run granian --interface asgi server.main:app --host 0.0.0.0
|
uv run granian --interface asgi server.main:app --host 0.0.0.0
|
||||||
|
|
||||||
dog-train:
|
dog-train:
|
||||||
uv run ml/dogs.py
|
uv run ml/dogs.py
|
||||||
|
|
@ -16,9 +16,3 @@ lint:
|
||||||
|
|
||||||
pipinstall:
|
pipinstall:
|
||||||
uv pip sync requirements.txt
|
uv pip sync requirements.txt
|
||||||
|
|
||||||
migrate-up:
|
|
||||||
alembic upgrade head
|
|
||||||
|
|
||||||
migration-generate:
|
|
||||||
git rev-parse --short HEAD | xargs -I {} alembic revision --autogenerate -m "{}"
|
|
||||||
|
|
|
||||||
116
alembic.ini
116
alembic.ini
|
|
@ -1,116 +0,0 @@
|
||||||
# A generic, single database configuration.
|
|
||||||
|
|
||||||
[alembic]
|
|
||||||
# path to migration scripts
|
|
||||||
script_location = server/migration
|
|
||||||
|
|
||||||
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
|
|
||||||
# Uncomment the line below if you want the files to be prepended with date and time
|
|
||||||
# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
|
|
||||||
# for all available tokens
|
|
||||||
file_template = %%(year)d-%%(month).2d-%%(day).2d-%%(hour).2d%%(minute).2d-%%(rev)s-parent-commit-%%(slug)s
|
|
||||||
|
|
||||||
# sys.path path, will be prepended to sys.path if present.
|
|
||||||
# defaults to the current working directory.
|
|
||||||
prepend_sys_path = .
|
|
||||||
|
|
||||||
# timezone to use when rendering the date within the migration file
|
|
||||||
# as well as the filename.
|
|
||||||
# If specified, requires the python-dateutil library that can be
|
|
||||||
# installed by adding `alembic[tz]` to the pip requirements
|
|
||||||
# string value is passed to dateutil.tz.gettz()
|
|
||||||
# leave blank for localtime
|
|
||||||
# timezone =
|
|
||||||
|
|
||||||
# max length of characters to apply to the
|
|
||||||
# "slug" field
|
|
||||||
# truncate_slug_length = 40
|
|
||||||
|
|
||||||
# set to 'true' to run the environment during
|
|
||||||
# the 'revision' command, regardless of autogenerate
|
|
||||||
# revision_environment = false
|
|
||||||
|
|
||||||
# set to 'true' to allow .pyc and .pyo files without
|
|
||||||
# a source .py file to be detected as revisions in the
|
|
||||||
# versions/ directory
|
|
||||||
# sourceless = false
|
|
||||||
|
|
||||||
# version location specification; This defaults
|
|
||||||
# to migration/versions. When using multiple version
|
|
||||||
# directories, initial revisions must be specified with --version-path.
|
|
||||||
# The path separator used here should be the separator specified by "version_path_separator" below.
|
|
||||||
# version_locations = %(here)s/bar:%(here)s/bat:migration/versions
|
|
||||||
|
|
||||||
# version path separator; As mentioned above, this is the character used to split
|
|
||||||
# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
|
|
||||||
# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
|
|
||||||
# Valid values for version_path_separator are:
|
|
||||||
#
|
|
||||||
# version_path_separator = :
|
|
||||||
# version_path_separator = ;
|
|
||||||
# version_path_separator = space
|
|
||||||
version_path_separator = os # Use os.pathsep. Default configuration used for new projects.
|
|
||||||
|
|
||||||
# set to 'true' to search source files recursively
|
|
||||||
# in each "version_locations" directory
|
|
||||||
# new in Alembic version 1.10
|
|
||||||
# recursive_version_locations = false
|
|
||||||
|
|
||||||
# the output encoding used when revision files
|
|
||||||
# are written from script.py.mako
|
|
||||||
# output_encoding = utf-8
|
|
||||||
|
|
||||||
sqlalchemy.url = driver://user:pass@localhost/dbname
|
|
||||||
|
|
||||||
|
|
||||||
[post_write_hooks]
|
|
||||||
# post_write_hooks defines scripts or Python functions that are run
|
|
||||||
# on newly generated revision scripts. See the documentation for further
|
|
||||||
# detail and examples
|
|
||||||
|
|
||||||
# format using "black" - use the console_scripts runner, against the "black" entrypoint
|
|
||||||
# hooks = black
|
|
||||||
# black.type = console_scripts
|
|
||||||
# black.entrypoint = black
|
|
||||||
# black.options = -l 79 REVISION_SCRIPT_FILENAME
|
|
||||||
|
|
||||||
# lint with attempts to fix using "ruff" - use the exec runner, execute a binary
|
|
||||||
# hooks = ruff
|
|
||||||
# ruff.type = exec
|
|
||||||
# ruff.executable = %(here)s/.venv/bin/ruff
|
|
||||||
# ruff.options = --fix REVISION_SCRIPT_FILENAME
|
|
||||||
|
|
||||||
# Logging configuration
|
|
||||||
[loggers]
|
|
||||||
keys = root,sqlalchemy,alembic
|
|
||||||
|
|
||||||
[handlers]
|
|
||||||
keys = console
|
|
||||||
|
|
||||||
[formatters]
|
|
||||||
keys = generic
|
|
||||||
|
|
||||||
[logger_root]
|
|
||||||
level = WARN
|
|
||||||
handlers = console
|
|
||||||
qualname =
|
|
||||||
|
|
||||||
[logger_sqlalchemy]
|
|
||||||
level = WARN
|
|
||||||
handlers =
|
|
||||||
qualname = sqlalchemy.engine
|
|
||||||
|
|
||||||
[logger_alembic]
|
|
||||||
level = INFO
|
|
||||||
handlers =
|
|
||||||
qualname = alembic
|
|
||||||
|
|
||||||
[handler_console]
|
|
||||||
class = StreamHandler
|
|
||||||
args = (sys.stderr,)
|
|
||||||
level = NOTSET
|
|
||||||
formatter = generic
|
|
||||||
|
|
||||||
[formatter_generic]
|
|
||||||
format = %(levelname)-5.5s [%(name)s] %(message)s
|
|
||||||
datefmt = %H:%M:%S
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import json
|
import json
|
||||||
|
|
||||||
import torch.nn as nn
|
|
||||||
from PIL import ImageFile
|
from PIL import ImageFile
|
||||||
from torch.utils.data import DataLoader
|
import torch.nn as nn
|
||||||
from torchvision.datasets import ImageFolder # type: ignore
|
from torchvision.datasets import ImageFolder # type: ignore
|
||||||
from train import DEVICE, get_labels, get_loaders, load_model, show, train # type: ignore
|
from torch.utils.data import DataLoader
|
||||||
|
|
||||||
|
from train import get_labels, load_model, get_loaders, train, show, DEVICE # type: ignore
|
||||||
|
|
||||||
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import json
|
import json
|
||||||
|
|
||||||
import torch.nn as nn
|
|
||||||
from PIL import ImageFile
|
from PIL import ImageFile
|
||||||
from torch.utils.data import DataLoader
|
import torch.nn as nn
|
||||||
from torchvision.datasets import ImageFolder # type: ignore
|
from torchvision.datasets import ImageFolder # type: ignore
|
||||||
from train import DEVICE, get_labels, get_loaders, load_model, show, train # type: ignore
|
from torch.utils.data import DataLoader
|
||||||
|
|
||||||
|
from train import get_labels, load_model, get_loaders, train, show, DEVICE # type: ignore
|
||||||
|
|
||||||
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import json
|
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
|
from torchvision import transforms # type: ignore
|
||||||
import torch.nn.functional as F
|
import torch.nn.functional as F
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from torchvision import transforms # type: ignore
|
import json
|
||||||
|
|
||||||
|
|
||||||
# Создание labels_dict для соответствия классов и индексов
|
# Создание labels_dict для соответствия классов и индексов
|
||||||
with open("labels.json") as f:
|
with open("labels.json", "r") as f:
|
||||||
data_labels = f.read()
|
data_labels = f.read()
|
||||||
labels_dict = json.loads(data_labels)
|
labels_dict = json.loads(data_labels)
|
||||||
|
|
||||||
|
|
|
||||||
16
ml/train.py
16
ml/train.py
|
|
@ -1,13 +1,13 @@
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import matplotlib.pyplot as plt # type: ignore
|
import matplotlib.pyplot as plt # type: ignore
|
||||||
import torch
|
import torch
|
||||||
import torch.nn as nn
|
import torch.nn as nn
|
||||||
import torchvision
|
|
||||||
from torch.utils.data import DataLoader, Dataset, random_split
|
|
||||||
from torchvision import transforms # type: ignore
|
|
||||||
from torchvision.datasets import ImageFolder # type: ignore
|
from torchvision.datasets import ImageFolder # type: ignore
|
||||||
|
from torch.utils.data import Dataset, DataLoader, random_split
|
||||||
|
from torchvision import transforms # type: ignore
|
||||||
|
import torchvision
|
||||||
from torchvision.models import ResNet50_Weights # type: ignore
|
from torchvision.models import ResNet50_Weights # type: ignore
|
||||||
|
from typing import Tuple
|
||||||
|
|
||||||
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
||||||
|
|
||||||
|
|
@ -29,7 +29,7 @@ def get_labels(input_dir, img_size):
|
||||||
return labels_dict, dataset
|
return labels_dict, dataset
|
||||||
|
|
||||||
|
|
||||||
def get_loaders(dataset: Dataset) -> tuple[DataLoader, DataLoader]:
|
def get_loaders(dataset: Dataset) -> Tuple[DataLoader, DataLoader]:
|
||||||
# Разделение данных на тренировочные и валидационные
|
# Разделение данных на тренировочные и валидационные
|
||||||
train_size = int(0.8 * float(len(dataset))) # type: ignore[arg-type]
|
train_size = int(0.8 * float(len(dataset))) # type: ignore[arg-type]
|
||||||
val_size = len(dataset) - train_size # type: ignore[arg-type]
|
val_size = len(dataset) - train_size # type: ignore[arg-type]
|
||||||
|
|
@ -61,7 +61,7 @@ def train(
|
||||||
model: nn.Module,
|
model: nn.Module,
|
||||||
train_loader: DataLoader,
|
train_loader: DataLoader,
|
||||||
val_loader: DataLoader,
|
val_loader: DataLoader,
|
||||||
) -> tuple[list[float], list[float], list[float], list[float]]:
|
) -> Tuple[list[float], list[float], list[float], list[float]]:
|
||||||
criterion = torch.nn.CrossEntropyLoss()
|
criterion = torch.nn.CrossEntropyLoss()
|
||||||
optimizer = torch.optim.Adam(model.fc.parameters(), lr=1e-4) # type: ignore[union-attr]
|
optimizer = torch.optim.Adam(model.fc.parameters(), lr=1e-4) # type: ignore[union-attr]
|
||||||
# История метрик
|
# История метрик
|
||||||
|
|
@ -96,7 +96,9 @@ def train(
|
||||||
train_acc = 100.0 * correct / total
|
train_acc = 100.0 * correct / total
|
||||||
train_loss_history.append(train_loss)
|
train_loss_history.append(train_loss)
|
||||||
train_acc_history.append(train_acc)
|
train_acc_history.append(train_acc)
|
||||||
print(f"Epoch {epoch + 1}/{num_epochs}, Train Loss: {train_loss:.4f}, Train Accuracy: {train_acc:.2f}%")
|
print(
|
||||||
|
f"Epoch {epoch + 1}/{num_epochs}, Train Loss: {train_loss:.4f}, Train Accuracy: {train_acc:.2f}%"
|
||||||
|
)
|
||||||
|
|
||||||
# Оценка на валидационных данных
|
# Оценка на валидационных данных
|
||||||
model.eval()
|
model.eval()
|
||||||
|
|
|
||||||
119
pyproject.toml
119
pyproject.toml
|
|
@ -24,14 +24,11 @@ dependencies = [
|
||||||
"sqlalchemy>=2.0.44",
|
"sqlalchemy>=2.0.44",
|
||||||
"inject>=5.3.0",
|
"inject>=5.3.0",
|
||||||
"aiofiles>=25.1.0",
|
"aiofiles>=25.1.0",
|
||||||
|
"botocore>=1.42.9",
|
||||||
"types-aiofiles>=25.1.0.20251011",
|
"types-aiofiles>=25.1.0.20251011",
|
||||||
"betterconf>=4.5.0",
|
"betterconf>=4.5.0",
|
||||||
"dataclasses-ujson>=0.0.34",
|
"dataclasses-ujson>=0.0.34",
|
||||||
"asyncpg>=0.31.0",
|
"asyncpg>=0.31.0",
|
||||||
"alembic>=1.18.0",
|
|
||||||
"aioboto3>=15.0.0",
|
|
||||||
"python-magic>=0.4.27",
|
|
||||||
"psycopg2-binary>=2.9.11",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
|
|
@ -46,117 +43,3 @@ default = [
|
||||||
"matplotlib>=3.10.1",
|
"matplotlib>=3.10.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
# MYPY
|
|
||||||
|
|
||||||
[tool.mypy]
|
|
||||||
exclude = [
|
|
||||||
".venv",
|
|
||||||
"venv",
|
|
||||||
"tmp",
|
|
||||||
"scripts",
|
|
||||||
"tests"
|
|
||||||
]
|
|
||||||
plugins = ["sqlalchemy.ext.mypy.plugin"]
|
|
||||||
mypy_path = "./stubs"
|
|
||||||
ignore_missing_imports = true
|
|
||||||
|
|
||||||
# RUFF
|
|
||||||
|
|
||||||
[tool.ruff]
|
|
||||||
target-version = "py312"
|
|
||||||
show-fixes = true
|
|
||||||
src = ["app"]
|
|
||||||
# Same as Black.
|
|
||||||
line-length = 120
|
|
||||||
indent-width = 4
|
|
||||||
|
|
||||||
# Exclude a variety of commonly ignored directories.
|
|
||||||
exclude = [
|
|
||||||
".bzr",
|
|
||||||
".direnv",
|
|
||||||
".eggs",
|
|
||||||
".git",
|
|
||||||
".git-rewrite",
|
|
||||||
".hg",
|
|
||||||
".ipynb_checkpoints",
|
|
||||||
".mypy_cache",
|
|
||||||
".nox",
|
|
||||||
".pants.d",
|
|
||||||
".pyenv",
|
|
||||||
".pytest_cache",
|
|
||||||
".pytype",
|
|
||||||
".ruff_cache",
|
|
||||||
".svn",
|
|
||||||
".tox",
|
|
||||||
".venv",
|
|
||||||
".vscode",
|
|
||||||
"__pypackages__",
|
|
||||||
"_build",
|
|
||||||
"buck-out",
|
|
||||||
"build",
|
|
||||||
"dist",
|
|
||||||
"node_modules",
|
|
||||||
"site-packages",
|
|
||||||
"venv",
|
|
||||||
"stubs",
|
|
||||||
"scripts",
|
|
||||||
]
|
|
||||||
|
|
||||||
[tool.ruff.lint.isort]
|
|
||||||
known-first-party = ["app"]
|
|
||||||
|
|
||||||
[tool.ruff.format]
|
|
||||||
# Like Black, use double quotes for strings.
|
|
||||||
quote-style = "double"
|
|
||||||
# Like Black, indent with spaces, rather than tabs.
|
|
||||||
indent-style = "space"
|
|
||||||
# Like Black, respect magic trailing commas.
|
|
||||||
skip-magic-trailing-comma = false
|
|
||||||
# Like Black, automatically detect the appropriate line ending.
|
|
||||||
line-ending = "auto"
|
|
||||||
|
|
||||||
[tool.ruff.lint.per-file-ignores]
|
|
||||||
"stubs/*" = ["F403"]
|
|
||||||
"server/migration/*" = ["E501", "F403"]
|
|
||||||
"server/config/__init__.py" = ["E501"]
|
|
||||||
"scripts/*" = ["T201", "E501"]
|
|
||||||
"server/admin/*" = ["E501", "E711"]
|
|
||||||
"vk_api/*"= ["T201", "C416", "A001", "E501"]
|
|
||||||
"ml/*"= ["T201", "C416", "A001", "E501", "C416", "N812"]
|
|
||||||
"tests/**/*.py" = [
|
|
||||||
"E501", "ASYNC230",
|
|
||||||
# at least this three should be fine in tests:
|
|
||||||
"S101", # asserts allowed in tests...
|
|
||||||
"S106", # Possible hardcoded password assigned to argument: "password"
|
|
||||||
"S110", # consider logging the exception
|
|
||||||
"ARG", # Unused function args -> fixtures nevertheless are functionally relevant...
|
|
||||||
"FBT", # Don't care about booleans as positional arguments in tests, e.g. via @pytest.mark.parametrize()
|
|
||||||
# The below are debateable
|
|
||||||
"PLR2004", # Magic value used in comparison, ...
|
|
||||||
"S311", # Standard pseudo-random generators are not suitable for cryptographic purposes
|
|
||||||
"INP001", # File `...` is part of an implicit namespace package. Add an `__init__.py`.
|
|
||||||
"SLF001", # Private member accessed: `_...`
|
|
||||||
]
|
|
||||||
"tests/__init__.py" = ["I001"]
|
|
||||||
|
|
||||||
[tool.ruff.lint]
|
|
||||||
# https://docs.astral.sh/ruff/rules/
|
|
||||||
select = ["DTZ", "F", "C4", "B", "A", "E", "T", "I", "N", "UP", "ASYNC", "Q"]
|
|
||||||
ignore = ["E712", "B904", "B019", "C417"]
|
|
||||||
|
|
||||||
# Allow fix for all enabled rules (when `--fix`) is provided.
|
|
||||||
fixable = ["ALL"]
|
|
||||||
unfixable = []
|
|
||||||
# Allow unused variables when underscore-prefixed.
|
|
||||||
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
|
||||||
|
|
||||||
[tool.ruff.lint.mccabe]
|
|
||||||
# https://docs.astral.sh/ruff/settings/#mccabe
|
|
||||||
# Flag errors (`C901`) whenever the complexity level exceeds 5.
|
|
||||||
max-complexity = 12
|
|
||||||
|
|
||||||
[tool.ruff.lint.flake8-pytest-style]
|
|
||||||
# https://docs.astral.sh/ruff/settings/#flake8-pytest-style
|
|
||||||
fixture-parentheses = false
|
|
||||||
mark-parentheses = false
|
|
||||||
|
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
import os
|
|
||||||
import random
|
|
||||||
import shutil
|
|
||||||
|
|
||||||
from PIL import Image
|
|
||||||
|
|
||||||
|
|
||||||
def copy_convert_to_webp(
|
|
||||||
source_dir,
|
|
||||||
dest_dir,
|
|
||||||
samples_per_folder=5,
|
|
||||||
size_threshold=1024 * 1024,
|
|
||||||
quality_high=90,
|
|
||||||
quality_low=70,
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Копирует структуру папок и конвертирует изображения в WebP
|
|
||||||
:param source_dir: Исходная директория с изображениями
|
|
||||||
:param dest_dir: Целевая директория для копирования
|
|
||||||
:param samples_per_folder: Количество изображений для выбора из каждой папки
|
|
||||||
:param size_threshold: Пороговый размер файла (в байтах) для сильного сжатия
|
|
||||||
:param quality_high: Качество для маленьких изображений (0-100)
|
|
||||||
:param quality_low: Качество для больших изображений (0-100)
|
|
||||||
"""
|
|
||||||
image_extensions = (".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tiff", ".webp")
|
|
||||||
|
|
||||||
def is_image_file(filename):
|
|
||||||
return filename.lower().endswith(image_extensions)
|
|
||||||
|
|
||||||
def convert_to_webp(src_path, dest_path, quality):
|
|
||||||
try:
|
|
||||||
with Image.open(src_path) as img:
|
|
||||||
# Сохраняем прозрачность для RGBA
|
|
||||||
if img.mode in ("RGBA", "LA"):
|
|
||||||
img = img.convert("RGBA")
|
|
||||||
img.save(dest_path, "WEBP", quality=quality, lossless=False, method=6)
|
|
||||||
else:
|
|
||||||
# Конвертируем в RGB для JPEG-подобных изображений
|
|
||||||
img = img.convert("RGB")
|
|
||||||
img.save(dest_path, "WEBP", quality=quality, method=6)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error converting {src_path}: {e}")
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Создаем корневую целевую папку
|
|
||||||
os.makedirs(dest_dir, exist_ok=True)
|
|
||||||
|
|
||||||
for root, dirs, files in os.walk(source_dir):
|
|
||||||
rel_path = os.path.relpath(root, source_dir)
|
|
||||||
target_dir = os.path.join(dest_dir, rel_path)
|
|
||||||
os.makedirs(target_dir, exist_ok=True)
|
|
||||||
|
|
||||||
images = [f for f in files if is_image_file(f)]
|
|
||||||
if not images:
|
|
||||||
continue
|
|
||||||
|
|
||||||
selected = random.sample(images, min(samples_per_folder, len(images)))
|
|
||||||
|
|
||||||
for file in selected:
|
|
||||||
src_path = os.path.join(root, file)
|
|
||||||
base_name = os.path.splitext(file)[0]
|
|
||||||
dest_path = os.path.join(target_dir, f"{base_name}.webp")
|
|
||||||
|
|
||||||
# Определяем качество на основе размера
|
|
||||||
file_size = os.path.getsize(src_path)
|
|
||||||
quality = quality_low if file_size > size_threshold else quality_high
|
|
||||||
|
|
||||||
# Конвертируем в WebP
|
|
||||||
if not convert_to_webp(src_path, dest_path, quality):
|
|
||||||
# Если ошибка конвертации - копируем оригинал
|
|
||||||
shutil.copy2(src_path, dest_path)
|
|
||||||
print(f"Copied original file instead: {file}")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
copy_convert_to_webp(
|
|
||||||
source_dir="assets",
|
|
||||||
dest_dir="webp_output",
|
|
||||||
samples_per_folder=5,
|
|
||||||
size_threshold=1 * 1024 * 20, # 1 MB
|
|
||||||
quality_high=70,
|
|
||||||
quality_low=70,
|
|
||||||
)
|
|
||||||
|
|
@ -1,167 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
|
||||||
import_beerds_sql.py
|
|
||||||
|
|
||||||
Скрипт читает файлы из каталогов:
|
|
||||||
- server/modules/descriptions/repository/breed_descriptions
|
|
||||||
- server/modules/descriptions/repository/breed_signs
|
|
||||||
|
|
||||||
и вместо непосредственной вставки генерирует PostgreSQL‑SQL‑файл, который
|
|
||||||
можно применить через `psql -f <имя_файла.sql>`:
|
|
||||||
|
|
||||||
CREATE TABLE beerds.beerds (
|
|
||||||
id varchar NOT NULL,
|
|
||||||
name text NOT NULL,
|
|
||||||
descriptions text NOT NULL,
|
|
||||||
signs json NOT NULL,
|
|
||||||
alias text NOT NULL,
|
|
||||||
CONSTRAINT beerds_pkey PRIMARY KEY (id)
|
|
||||||
);
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import json
|
|
||||||
import hashlib
|
|
||||||
import pathlib
|
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
# 2️⃣ Путь к каталогам (можно изменить под свой проект)
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
BASE_DIR = pathlib.Path(__file__).resolve().parent.parent
|
|
||||||
DESC_DIR = BASE_DIR / "server/modules/descriptions/repository/breed_descriptions"
|
|
||||||
SIGN_DIR = BASE_DIR / "server/modules/descriptions/repository/breed_signs"
|
|
||||||
print(f"DESC_DIR = {DESC_DIR}")
|
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
# 3️⃣ Функции чтения файлов
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
def read_text_file(path: pathlib.Path) -> str:
|
|
||||||
"""Возвращает содержимое текстового файла (unicode)."""
|
|
||||||
with path.open("r", encoding="utf-8") as f:
|
|
||||||
return f.read().strip()
|
|
||||||
|
|
||||||
|
|
||||||
def read_json_file(path: pathlib.Path) -> dict:
|
|
||||||
"""Возвращает JSON‑объект из файла."""
|
|
||||||
with path.open("r", encoding="utf-8") as f:
|
|
||||||
return json.load(f)
|
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
# 4️⃣ Формируем словарь: имя → (описание, сигналы)
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
def build_breed_map(desc_dir: pathlib.Path, sign_dir: pathlib.Path) -> dict:
|
|
||||||
"""Возвращает dict: stem → (description, signs)"""
|
|
||||||
desc_files = {p.stem: p for p in desc_dir.glob("*.txt")}
|
|
||||||
sign_files = {p.stem: p for p in sign_dir.glob("*.txt")}
|
|
||||||
|
|
||||||
common_keys = desc_files.keys() & sign_files.keys()
|
|
||||||
if not common_keys:
|
|
||||||
print("⚠️ Нет общих файлов между каталогами описаний и сигналов.")
|
|
||||||
return {}
|
|
||||||
|
|
||||||
breeds = {}
|
|
||||||
for key in sorted(common_keys):
|
|
||||||
desc_path = desc_files[key]
|
|
||||||
sign_path = sign_files[key]
|
|
||||||
try:
|
|
||||||
description = read_text_file(desc_path)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ Не удалось прочитать описание {desc_path}: {e}")
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
signs = read_json_file(sign_path)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ Не удалось прочитать сигналы {sign_path}: {e}")
|
|
||||||
continue
|
|
||||||
breeds[key] = (description, signs)
|
|
||||||
return breeds
|
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
# 5️⃣ Генерация id (если нужно уникальное)
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
def generate_id(name: str, alias: str) -> str:
|
|
||||||
"""Генерирует MD5‑hash из `name:alias`."""
|
|
||||||
key = f"{name}:{alias}"
|
|
||||||
return hashlib.md5(key.encode("utf-8")).hexdigest()
|
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
# 6️⃣ Генерация SQL‑файла
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
def write_sql_file(breeds: dict, out_path: pathlib.Path):
|
|
||||||
"""Создаёт один .sql‑файл, содержащий INSERT‑операции для всех пород."""
|
|
||||||
if not breeds:
|
|
||||||
print("⚠️ Нет данных для генерации SQL.")
|
|
||||||
return
|
|
||||||
|
|
||||||
lines = []
|
|
||||||
header = (
|
|
||||||
"-- Автоматически сгенерированный скрипт вставки пород\n"
|
|
||||||
"-- Запустить: psql -f beerds_insert.sql\n\n"
|
|
||||||
"BEGIN;\n"
|
|
||||||
)
|
|
||||||
lines.append(header)
|
|
||||||
|
|
||||||
# Формируем один блок INSERT с несколькими строками VALUES
|
|
||||||
# 1. Столбцы
|
|
||||||
cols = "(id, name, descriptions, signs, alias)"
|
|
||||||
# 2. Генерируем строки VALUES
|
|
||||||
value_lines = []
|
|
||||||
for name, (description, signs) in breeds.items():
|
|
||||||
alias = name # можно изменить при необходимости
|
|
||||||
breed_id = generate_id(name, alias)
|
|
||||||
# Подготовка значений: экранирование апострофов и JSON
|
|
||||||
escaped_name = name.replace("_", " ").replace("'", "''")
|
|
||||||
escaped_alias = alias.replace("'", "''")
|
|
||||||
escaped_desc = description.replace("'", "''")
|
|
||||||
# JSON как строка (в PostgreSQL json можно передавать как текст)
|
|
||||||
json_str = json.dumps(signs, ensure_ascii=False).replace("'", "''")
|
|
||||||
value_line = f"('{breed_id}', '{escaped_name}', '{escaped_desc}', '{json_str}', '{escaped_alias}')"
|
|
||||||
value_lines.append(value_line)
|
|
||||||
|
|
||||||
# Объединяем VALUES через запятую
|
|
||||||
values_section = ",\n".join(value_lines)
|
|
||||||
insert_stmt = (
|
|
||||||
f"INSERT INTO beerds.beerds {cols}\n"
|
|
||||||
f"VALUES\n{values_section}\n"
|
|
||||||
f"ON CONFLICT (id) DO UPDATE\n"
|
|
||||||
f" SET name = EXCLUDED.name,\n"
|
|
||||||
f" descriptions = EXCLUDED.descriptions,\n"
|
|
||||||
f" signs = EXCLUDED.signs,\n"
|
|
||||||
f" alias = EXCLUDED.alias;\n"
|
|
||||||
)
|
|
||||||
lines.append(insert_stmt)
|
|
||||||
lines.append("\nCOMMIT;")
|
|
||||||
|
|
||||||
# Записываем в файл
|
|
||||||
out_path.write_text("\n".join(lines), encoding="utf-8")
|
|
||||||
print(f"✅ SQL‑файл успешно сгенерирован: {out_path}")
|
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
# 7️⃣ Основная точка входа
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
def main():
|
|
||||||
print("🔍 Читаем файлы…")
|
|
||||||
breeds_map = build_breed_map(DESC_DIR, SIGN_DIR)
|
|
||||||
|
|
||||||
if not breeds_map:
|
|
||||||
print("❌ Ничего не найдено. Завершение.")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# Путь для вывода SQL‑файла
|
|
||||||
sql_file_path = BASE_DIR / "beerds_insert.sql"
|
|
||||||
print(f"📝 Записываем INSERT‑операции в файл: {sql_file_path}")
|
|
||||||
write_sql_file(breeds_map, sql_file_path)
|
|
||||||
|
|
||||||
print("🎉 Готово!")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
import json
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
|
|
||||||
def generate_folder_structure(root_path):
|
|
||||||
result = {}
|
|
||||||
# Поддерживаемые форматы изображений
|
|
||||||
image_ext = {".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".tiff"}
|
|
||||||
|
|
||||||
# Основные категории (cat и dog)
|
|
||||||
for category in ["cat", "dog"]:
|
|
||||||
category_path = Path(root_path) / category
|
|
||||||
if not category_path.is_dir():
|
|
||||||
continue
|
|
||||||
|
|
||||||
category_dict = {}
|
|
||||||
# Обрабатываем подпапки внутри категории
|
|
||||||
for subfolder in category_path.iterdir():
|
|
||||||
if subfolder.is_dir():
|
|
||||||
# Собираем изображения
|
|
||||||
images = [
|
|
||||||
file.name for file in subfolder.iterdir() if file.is_file() and file.suffix.lower() in image_ext
|
|
||||||
]
|
|
||||||
category_dict[subfolder.name] = sorted(images)
|
|
||||||
|
|
||||||
result[category] = category_dict
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def save_to_json(data, output_file):
|
|
||||||
with open(output_file, "w", encoding="utf-8") as f:
|
|
||||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
# Настройки
|
|
||||||
ASSETS_DIR = "server/static/assets"
|
|
||||||
OUTPUT_JSON = "structure.json"
|
|
||||||
|
|
||||||
# Генерация структуры
|
|
||||||
structure = generate_folder_structure(ASSETS_DIR)
|
|
||||||
|
|
||||||
# Сохранение в файл
|
|
||||||
save_to_json(structure, OUTPUT_JSON)
|
|
||||||
print(f"JSON структура сохранена в файл: {OUTPUT_JSON}")
|
|
||||||
|
|
@ -22,11 +22,13 @@ class AppConfig:
|
||||||
sentry_dns: str = field("SENTRY_DNS", default="")
|
sentry_dns: str = field("SENTRY_DNS", default="")
|
||||||
log_level: str = field("LOG_LEVEL", "INFO")
|
log_level: str = field("LOG_LEVEL", "INFO")
|
||||||
|
|
||||||
db_uri: str = field("DB_URI", "postgresql+asyncpg://svcuser:svcpass@localhost:5432/svc")
|
db_uri: str = field(
|
||||||
|
"DB_URI", "postgresql+asyncpg://svcuser:svcpass@localhost:5432/svc"
|
||||||
|
)
|
||||||
db_pass_salt: str = field("DB_PASS_SALT", "")
|
db_pass_salt: str = field("DB_PASS_SALT", "")
|
||||||
db_search_path: str = field("DB_SEARCH_PATH", "beerds")
|
db_search_path: str = field("DB_SEARCH_PATH", "public")
|
||||||
|
|
||||||
fs_local_mount_dir: str = field("FS_LOCAL_MOUNT_DIR", default="./files")
|
fs_local_mount_dir: str = field("FS_LOCAL_MOUNT_DIR", default="./tmp/files")
|
||||||
fs_s3_bucket: str = field("FS_S3_BUCKET", "")
|
fs_s3_bucket: str = field("FS_S3_BUCKET", "")
|
||||||
fs_s3_access_key_id: str = field("FS_ACCESS_KEY_ID", "")
|
fs_s3_access_key_id: str = field("FS_ACCESS_KEY_ID", "")
|
||||||
fs_s3_access_key: str = field("FS_SECRET_ACCESS_KEY", "")
|
fs_s3_access_key: str = field("FS_SECRET_ACCESS_KEY", "")
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
class CacheRepository(metaclass=ABCMeta):
|
class CacheRepository(metaclass=ABCMeta):
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def get(self, key: str) -> str | None:
|
async def get(self, key: str) -> Optional[str]:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def set(self, key: str, data: str, _exp_min: int | None = None):
|
async def set(self, key: str, data: str, _exp_min: Optional[int] = None):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
|
|
@ -22,10 +23,10 @@ class LocalCacheRepository(CacheRepository):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self._data = {}
|
self._data = {}
|
||||||
|
|
||||||
async def get(self, key: str) -> str | None:
|
async def get(self, key: str) -> Optional[str]:
|
||||||
return self._data.get(key)
|
return self._data.get(key)
|
||||||
|
|
||||||
async def set(self, key: str, data: str, _exp_min: int | None = None):
|
async def set(self, key: str, data: str, _exp_min: Optional[int] = None):
|
||||||
self._data[key] = data
|
self._data[key] = data
|
||||||
|
|
||||||
async def delete(self, key: str):
|
async def delete(self, key: str):
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
"""Abstract realiztion for DB"""
|
"""Abstract realiztion for DB"""
|
||||||
|
|
||||||
from collections.abc import Awaitable, Callable
|
from typing import Any, AsyncContextManager, Awaitable, Callable, TypeAlias
|
||||||
from contextlib import AbstractAsyncContextManager as AsyncContextManager
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from server.config import AppConfig
|
from server.config import AppConfig
|
||||||
|
|
||||||
type ExecuteFun = Callable[[Any], Awaitable[None]]
|
ExecuteFun: TypeAlias = Callable[[Any], Awaitable[None]]
|
||||||
|
|
||||||
|
|
||||||
class ConnectError(Exception):
|
class ConnectError(Exception):
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
|
from typing import Type, TypeVar
|
||||||
|
|
||||||
from sqlalchemy.orm import registry
|
from sqlalchemy.orm import registry
|
||||||
|
|
||||||
mapper_registry = registry()
|
mapper_registry = registry()
|
||||||
|
|
||||||
|
DC = TypeVar("DC")
|
||||||
|
|
||||||
def dict_to_dataclass[T](data: dict, class_type: type[T]) -> T:
|
|
||||||
|
def dict_to_dataclass(data: dict, class_type: Type[DC]) -> DC:
|
||||||
return class_type(**data)
|
return class_type(**data)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
from contextlib import AbstractAsyncContextManager as AsyncContextManager
|
from typing import Any, AsyncContextManager
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from server.config import AppConfig
|
from server.config import AppConfig
|
||||||
from server.infra.db.abc import AbstractDB, AbstractSession
|
from server.infra.db.abc import AbstractDB, AbstractSession
|
||||||
|
|
|
||||||
|
|
@ -39,9 +39,3 @@ class AsyncDB(AbstractDB):
|
||||||
|
|
||||||
def new_session(self):
|
def new_session(self):
|
||||||
return asyncio.async_sessionmaker(self.engine, expire_on_commit=False)()
|
return asyncio.async_sessionmaker(self.engine, expire_on_commit=False)()
|
||||||
|
|
||||||
def session_master(self):
|
|
||||||
return self.new_session()
|
|
||||||
|
|
||||||
def session_slave(self):
|
|
||||||
return self.new_session()
|
|
||||||
|
|
|
||||||
|
|
@ -33,10 +33,10 @@ LOGGING_CONFIG = {
|
||||||
"class": "logging.StreamHandler",
|
"class": "logging.StreamHandler",
|
||||||
"stream": "ext://sys.stdout",
|
"stream": "ext://sys.stdout",
|
||||||
},
|
},
|
||||||
# "sentry": {
|
"sentry": {
|
||||||
# "level": "ERROR",
|
"level": "ERROR",
|
||||||
# "class": "sentry_sdk.integrations.logging.EventHandler",
|
"class": "sentry_sdk.integrations.logging.EventHandler",
|
||||||
# },
|
},
|
||||||
"uvicorn_default": {
|
"uvicorn_default": {
|
||||||
"level": "INFO",
|
"level": "INFO",
|
||||||
"formatter": "uvicorn_default",
|
"formatter": "uvicorn_default",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
from copy import copy
|
from copy import copy
|
||||||
from dataclasses import Field, asdict, dataclass
|
from dataclasses import Field, asdict, dataclass
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any, ClassVar, Protocol, assert_never
|
from typing import Any, ClassVar, Optional, Protocol, assert_never
|
||||||
|
|
||||||
from sqlalchemy import Select, and_, or_
|
from sqlalchemy import Select, and_, or_
|
||||||
|
|
||||||
|
|
@ -28,7 +28,7 @@ class FilterLeftField(Protocol):
|
||||||
class Filter:
|
class Filter:
|
||||||
right: Any
|
right: Any
|
||||||
sign: FilterSign
|
sign: FilterSign
|
||||||
left: FilterLeftField | None = None
|
left: Optional[FilterLeftField] = None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def not_eq(f1: Any, f2: Any):
|
def not_eq(f1: Any, f2: Any):
|
||||||
|
|
@ -79,27 +79,33 @@ class RestrictionField:
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class QueryRestriction:
|
class QueryRestriction:
|
||||||
filters: list[Filter] | None = None
|
filters: Optional[list[Filter]] = None
|
||||||
limit: int | None = None
|
limit: Optional[int] = None
|
||||||
offset: int | None = None
|
offset: Optional[int] = None
|
||||||
sort: list[RestrictionField] | None = None
|
sort: Optional[list[RestrictionField]] = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=False)
|
@dataclass(frozen=False)
|
||||||
class FilterQuery:
|
class FilterQuery:
|
||||||
filters: list[Filter]
|
filters: list[Filter]
|
||||||
limit: int | None = None
|
limit: Optional[int] = None
|
||||||
offset: int | None = None
|
offset: Optional[int] = None
|
||||||
sort: list[RestrictionField] | None = None
|
sort: Optional[list[RestrictionField]] = None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def mass_and(fields: list[object], values: list[Any]) -> "FilterQuery":
|
def mass_and(fields: list[object], values: list[Any]) -> "FilterQuery":
|
||||||
return FilterQuery(filters=[Filter.eq(field, val) for field, val in zip(fields, values, strict=True)])
|
return FilterQuery(
|
||||||
|
filters=[Filter.eq(field, val) for field, val in zip(fields, values)]
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def mass_or(fields: list[object], values: list[Any]) -> "FilterQuery":
|
def mass_or(fields: list[object], values: list[Any]) -> "FilterQuery":
|
||||||
return FilterQuery(
|
return FilterQuery(
|
||||||
filters=[Filter.or_([Filter.eq(field, val) for field, val in zip(fields, values, strict=True)])]
|
filters=[
|
||||||
|
Filter.or_(
|
||||||
|
[Filter.eq(field, val) for field, val in zip(fields, values)]
|
||||||
|
)
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
@ -113,7 +119,7 @@ class FilterQuery:
|
||||||
def add_and(self, field: object, value: Any):
|
def add_and(self, field: object, value: Any):
|
||||||
self.filters.append(Filter.eq(field, value))
|
self.filters.append(Filter.eq(field, value))
|
||||||
|
|
||||||
def add_query_restistions(self, q_restriction: QueryRestriction | None = None):
|
def add_query_restistions(self, q_restriction: Optional[QueryRestriction] = None):
|
||||||
if not q_restriction:
|
if not q_restriction:
|
||||||
return None
|
return None
|
||||||
if q_restriction.limit:
|
if q_restriction.limit:
|
||||||
|
|
@ -131,7 +137,9 @@ class DataclassInstance(Protocol):
|
||||||
__dataclass_fields__: ClassVar[dict[str, Field[Any]]]
|
__dataclass_fields__: ClassVar[dict[str, Field[Any]]]
|
||||||
|
|
||||||
|
|
||||||
async def indexes_by_id(input_data: list, values: list[str], id_name="id") -> list[int] | None:
|
async def indexes_by_id(
|
||||||
|
input_data: list, values: list[str], id_name="id"
|
||||||
|
) -> Optional[list[int]]:
|
||||||
r_data: list[int] = []
|
r_data: list[int] = []
|
||||||
for i, _ in enumerate(input_data):
|
for i, _ in enumerate(input_data):
|
||||||
if getattr(input_data[i], id_name) in values:
|
if getattr(input_data[i], id_name) in values:
|
||||||
|
|
@ -141,7 +149,9 @@ async def indexes_by_id(input_data: list, values: list[str], id_name="id") -> li
|
||||||
return r_data
|
return r_data
|
||||||
|
|
||||||
|
|
||||||
def data_by_filter[T: DataclassInstance](input_data: list[T], q: FilterQuery) -> list[T]:
|
def data_by_filter[T: DataclassInstance](
|
||||||
|
input_data: list[T], q: FilterQuery
|
||||||
|
) -> list[T]:
|
||||||
# can't do query AND(OR() + AND())
|
# can't do query AND(OR() + AND())
|
||||||
data: list[T] = []
|
data: list[T] = []
|
||||||
data_or: list[T] = []
|
data_or: list[T] = []
|
||||||
|
|
@ -235,10 +245,14 @@ def sqlalchemy_conditions(q: FilterQuery):
|
||||||
conditions = []
|
conditions = []
|
||||||
for f in q.filters:
|
for f in q.filters:
|
||||||
if f.sign == FilterSign.OR:
|
if f.sign == FilterSign.OR:
|
||||||
conditions.append(or_(*sqlalchemy_conditions(q=FilterQuery(filters=f.right))))
|
conditions.append(
|
||||||
|
or_(*sqlalchemy_conditions(q=FilterQuery(filters=f.right)))
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
if f.sign == FilterSign.AND:
|
if f.sign == FilterSign.AND:
|
||||||
conditions.append(and_(*sqlalchemy_conditions(q=FilterQuery(filters=f.right))))
|
conditions.append(
|
||||||
|
and_(*sqlalchemy_conditions(q=FilterQuery(filters=f.right)))
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
if f.left is None:
|
if f.left is None:
|
||||||
continue
|
continue
|
||||||
|
|
@ -268,7 +282,9 @@ def sqlalchemy_conditions(q: FilterQuery):
|
||||||
return conditions
|
return conditions
|
||||||
|
|
||||||
|
|
||||||
def sqlalchemy_restrictions(f: FilterQuery, q: Select, dict_to_sort: dict | None = None) -> Select:
|
def sqlalchemy_restrictions(
|
||||||
|
f: FilterQuery, q: Select, dict_to_sort: Optional[dict] = None
|
||||||
|
) -> Select:
|
||||||
if f.limit:
|
if f.limit:
|
||||||
q = q.limit(f.limit)
|
q = q.limit(f.limit)
|
||||||
if f.offset:
|
if f.offset:
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
from server.infra.web.description import DescriptionController
|
from server.infra.web.description import DescriptionController
|
||||||
from server.infra.web.recognizer import BreedsController
|
|
||||||
from server.infra.web.seo import SeoController
|
from server.infra.web.seo import SeoController
|
||||||
from server.infra.web.vote import VoteController
|
from server.infra.web.recognizer import BreedsController
|
||||||
|
|
||||||
__all__ = ("DescriptionController", "SeoController", "BreedsController", "VoteController")
|
__all__ = ("DescriptionController", "SeoController", "BreedsController")
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,9 @@ class DescriptionController(Controller):
|
||||||
async def dogs_characteristics(self) -> Template:
|
async def dogs_characteristics(self) -> Template:
|
||||||
characters_service: CharactersService = inject.instance(CharactersService)
|
characters_service: CharactersService = inject.instance(CharactersService)
|
||||||
breeds = await characters_service.get_characters()
|
breeds = await characters_service.get_characters()
|
||||||
return Template(template_name="dogs-characteristics.html", context={"breeds": breeds})
|
return Template(
|
||||||
|
template_name="dogs-characteristics.html", context={"breeds": breeds}
|
||||||
|
)
|
||||||
|
|
||||||
@get("/dogs-characteristics/{name:str}")
|
@get("/dogs-characteristics/{name:str}")
|
||||||
async def beer_description(self, name: str) -> Template:
|
async def beer_description(self, name: str) -> Template:
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
from typing import Annotated
|
|
||||||
|
|
||||||
import inject
|
import inject
|
||||||
from litestar import (
|
from litestar import (
|
||||||
Controller,
|
Controller,
|
||||||
post,
|
post,
|
||||||
)
|
)
|
||||||
from litestar.datastructures import UploadFile
|
|
||||||
from litestar.enums import RequestEncodingType
|
from litestar.enums import RequestEncodingType
|
||||||
|
from litestar.datastructures import UploadFile
|
||||||
from litestar.params import Body
|
from litestar.params import Body
|
||||||
|
|
||||||
from server.modules.recognizer import RecognizerService
|
from server.modules.recognizer import RecognizerService
|
||||||
|
|
@ -16,15 +14,17 @@ class BreedsController(Controller):
|
||||||
path = "/beerds"
|
path = "/beerds"
|
||||||
|
|
||||||
@post("/dogs")
|
@post("/dogs")
|
||||||
async def beerds_dogs(self, data: Annotated[UploadFile, Body(media_type=RequestEncodingType.MULTI_PART)]) -> dict:
|
async def beerds_dogs(
|
||||||
|
self, data: UploadFile = Body(media_type=RequestEncodingType.MULTI_PART)
|
||||||
|
) -> dict:
|
||||||
recognizer_service: RecognizerService = inject.instance(RecognizerService)
|
recognizer_service: RecognizerService = inject.instance(RecognizerService)
|
||||||
body = await data.read()
|
body = await data.read()
|
||||||
result = await recognizer_service.predict_dog_image(body)
|
return await recognizer_service.predict_dog_image(body)
|
||||||
return result.to_serializable()
|
|
||||||
|
|
||||||
@post("/cats")
|
@post("/cats")
|
||||||
async def beerds_cats(self, data: Annotated[UploadFile, Body(media_type=RequestEncodingType.MULTI_PART)]) -> dict:
|
async def beerds_cats(
|
||||||
|
self, data: UploadFile = Body(media_type=RequestEncodingType.MULTI_PART)
|
||||||
|
) -> dict:
|
||||||
recognizer_service: RecognizerService = inject.instance(RecognizerService)
|
recognizer_service: RecognizerService = inject.instance(RecognizerService)
|
||||||
body = await data.read()
|
body = await data.read()
|
||||||
result = await recognizer_service.predict_cat_image(body)
|
return await recognizer_service.predict_cat_image(body)
|
||||||
return result.to_serializable()
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import inject
|
import inject
|
||||||
from litestar import (
|
from litestar import (
|
||||||
Controller,
|
Controller,
|
||||||
MediaType,
|
|
||||||
get,
|
get,
|
||||||
|
MediaType,
|
||||||
)
|
)
|
||||||
|
|
||||||
from server.modules.descriptions import Breed, CharactersService
|
from server.modules.descriptions import CharactersService, Breed
|
||||||
|
|
||||||
|
|
||||||
class SeoController(Controller):
|
class SeoController(Controller):
|
||||||
|
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
from datetime import UTC, datetime
|
|
||||||
from uuid import uuid4
|
|
||||||
|
|
||||||
import inject
|
|
||||||
from litestar import (
|
|
||||||
Controller,
|
|
||||||
post,
|
|
||||||
)
|
|
||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
from server.modules.descriptions import CharactersService
|
|
||||||
from server.modules.rate import Vote, VotesService
|
|
||||||
|
|
||||||
|
|
||||||
class VoteReq(BaseModel):
|
|
||||||
attachment_id: str
|
|
||||||
beerd_name: str
|
|
||||||
rate: int
|
|
||||||
|
|
||||||
def to_domain(self, name_id_convert: dict) -> Vote:
|
|
||||||
return Vote(
|
|
||||||
id=str(uuid4()),
|
|
||||||
attachment_id=self.attachment_id,
|
|
||||||
beerd_id=name_id_convert[self.beerd_name],
|
|
||||||
rate=self.rate,
|
|
||||||
created_at=datetime.now(UTC),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class VoteController(Controller):
|
|
||||||
path = "/votes"
|
|
||||||
|
|
||||||
@post("/do")
|
|
||||||
async def beerds_dogs(self, data: VoteReq) -> dict:
|
|
||||||
rate_service: VotesService = inject.instance(VotesService)
|
|
||||||
characters_service: CharactersService = inject.instance(CharactersService)
|
|
||||||
breeds = await characters_service.get_characters()
|
|
||||||
await rate_service.add_vote(data.to_domain({b.name: b.id for b in breeds}))
|
|
||||||
return {"success": True}
|
|
||||||
|
|
@ -1,22 +1,20 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import os
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
|
||||||
import inject
|
import inject
|
||||||
from litestar import (
|
from litestar import (
|
||||||
Litestar,
|
Litestar,
|
||||||
)
|
)
|
||||||
from litestar.contrib.jinja import JinjaTemplateEngine
|
from litestar.contrib.jinja import JinjaTemplateEngine
|
||||||
from litestar.static_files import create_static_files_router
|
|
||||||
from litestar.template.config import TemplateConfig
|
from litestar.template.config import TemplateConfig
|
||||||
|
from litestar.static_files import create_static_files_router
|
||||||
|
|
||||||
from server.config import get_app_config
|
from server.config import get_app_config
|
||||||
|
from server.infra.web import BreedsController, DescriptionController, SeoController
|
||||||
from server.infra.db import AsyncDB
|
from server.infra.db import AsyncDB
|
||||||
from server.infra.web import BreedsController, DescriptionController, SeoController, VoteController
|
from server.modules.descriptions import CharactersService, CharactersRepository
|
||||||
from server.modules.attachments import AtachmentService, DBAttachmentRepository, S3StorageDriver
|
from server.modules.recognizer import RecognizerService, RecognizerRepository
|
||||||
from server.modules.descriptions import CharactersService, PGCharactersRepository
|
|
||||||
from server.modules.rate import PGVoteRepository, VotesService
|
|
||||||
from server.modules.recognizer import RecognizerRepository, RecognizerService
|
|
||||||
|
|
||||||
os.environ["CUDA_VISIBLE_DEVICES"] = "-1"
|
os.environ["CUDA_VISIBLE_DEVICES"] = "-1"
|
||||||
|
|
||||||
|
|
@ -30,11 +28,8 @@ def inject_config(binder: inject.Binder):
|
||||||
cnf = get_app_config()
|
cnf = get_app_config()
|
||||||
db = AsyncDB(cnf)
|
db = AsyncDB(cnf)
|
||||||
loop.run_until_complete(db.connect())
|
loop.run_until_complete(db.connect())
|
||||||
attach_service = AtachmentService(S3StorageDriver(cnf=cnf), DBAttachmentRepository(db))
|
binder.bind(RecognizerService, RecognizerService(RecognizerRepository()))
|
||||||
binder.bind(RecognizerService, RecognizerService(RecognizerRepository(), attach_service))
|
binder.bind(CharactersService, CharactersService(CharactersRepository()))
|
||||||
binder.bind(CharactersService, CharactersService(PGCharactersRepository(db)))
|
|
||||||
binder.bind(VotesService, VotesService(PGVoteRepository(db)))
|
|
||||||
binder.bind(AtachmentService, attach_service)
|
|
||||||
|
|
||||||
|
|
||||||
inject.configure(inject_config)
|
inject.configure(inject_config)
|
||||||
|
|
@ -44,7 +39,6 @@ app = Litestar(
|
||||||
BreedsController,
|
BreedsController,
|
||||||
DescriptionController,
|
DescriptionController,
|
||||||
SeoController,
|
SeoController,
|
||||||
VoteController,
|
|
||||||
create_static_files_router(path="/static", directories=["server/static"]),
|
create_static_files_router(path="/static", directories=["server/static"]),
|
||||||
],
|
],
|
||||||
template_config=TemplateConfig(
|
template_config=TemplateConfig(
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Generic single-database configuration.
|
|
||||||
|
|
@ -1,86 +0,0 @@
|
||||||
from logging.config import fileConfig
|
|
||||||
|
|
||||||
from alembic import context
|
|
||||||
from server.config import get_app_config
|
|
||||||
from server.infra.db.db_mapper import mapper_registry
|
|
||||||
from server.modules.attachments.repository.models import *
|
|
||||||
from server.modules.descriptions.repository.models import *
|
|
||||||
from server.modules.rate.repository.models import *
|
|
||||||
from sqlalchemy import engine_from_config, pool
|
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
|
||||||
|
|
||||||
# this is the Alembic Config object, which provides
|
|
||||||
# access to the values within the .ini file in use.
|
|
||||||
config = context.config
|
|
||||||
Base = declarative_base()
|
|
||||||
target_metadata = Base.metadata
|
|
||||||
|
|
||||||
for table_name, table in mapper_registry.metadata.tables.items():
|
|
||||||
target_metadata._add_table(table_name, table.schema, table)
|
|
||||||
|
|
||||||
# Interpret the config file for Python logging.
|
|
||||||
# This line sets up loggers basically.
|
|
||||||
if config.config_file_name is not None:
|
|
||||||
fileConfig(config.config_file_name)
|
|
||||||
|
|
||||||
|
|
||||||
def run_migrations_offline() -> None:
|
|
||||||
"""Run migrations in 'offline' mode.
|
|
||||||
|
|
||||||
This configures the context with just a URL
|
|
||||||
and not an Engine, though an Engine is acceptable
|
|
||||||
here as well. By skipping the Engine creation
|
|
||||||
we don't even need a DBAPI to be available.
|
|
||||||
|
|
||||||
Calls to context.execute() here emit the given string to the
|
|
||||||
script output.
|
|
||||||
|
|
||||||
"""
|
|
||||||
url = "{}?options=-c search_path={}".format(
|
|
||||||
str(get_app_config().db_uri).replace("+asyncpg", ""),
|
|
||||||
get_app_config().db_search_path,
|
|
||||||
)
|
|
||||||
context.configure(
|
|
||||||
url=url,
|
|
||||||
target_metadata=target_metadata,
|
|
||||||
literal_binds=True,
|
|
||||||
dialect_opts={"paramstyle": "named"},
|
|
||||||
)
|
|
||||||
|
|
||||||
with context.begin_transaction():
|
|
||||||
context.run_migrations()
|
|
||||||
|
|
||||||
|
|
||||||
def run_migrations_online() -> None:
|
|
||||||
"""Run migrations in 'online' mode.
|
|
||||||
|
|
||||||
In this scenario we need to create an Engine
|
|
||||||
and associate a connection with the context.
|
|
||||||
|
|
||||||
"""
|
|
||||||
alemb_cnf = config.get_section(config.config_ini_section, {})
|
|
||||||
if not alemb_cnf["sqlalchemy.url"] or alemb_cnf["sqlalchemy.url"] == "driver://user:pass@localhost/dbname":
|
|
||||||
alemb_cnf["sqlalchemy.url"] = "{}?options=-c search_path={}".format(
|
|
||||||
str(get_app_config().db_uri).replace("+asyncpg", ""),
|
|
||||||
get_app_config().db_search_path,
|
|
||||||
)
|
|
||||||
connectable = engine_from_config(
|
|
||||||
alemb_cnf,
|
|
||||||
prefix="sqlalchemy.",
|
|
||||||
poolclass=pool.NullPool,
|
|
||||||
)
|
|
||||||
|
|
||||||
with connectable.connect() as connection:
|
|
||||||
context.configure(
|
|
||||||
connection=connection,
|
|
||||||
target_metadata=target_metadata,
|
|
||||||
)
|
|
||||||
|
|
||||||
with context.begin_transaction():
|
|
||||||
context.run_migrations()
|
|
||||||
|
|
||||||
|
|
||||||
if context.is_offline_mode():
|
|
||||||
run_migrations_offline()
|
|
||||||
else:
|
|
||||||
run_migrations_online()
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
"""${message}
|
|
||||||
|
|
||||||
Revision ID: ${up_revision}
|
|
||||||
Revises: ${down_revision | comma,n}
|
|
||||||
Create Date: ${create_date}
|
|
||||||
|
|
||||||
"""
|
|
||||||
from typing import Sequence, Union
|
|
||||||
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
${imports if imports else ""}
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision: str = ${repr(up_revision)}
|
|
||||||
down_revision: Union[str, None] = ${repr(down_revision)}
|
|
||||||
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
|
|
||||||
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
|
||||||
${upgrades if upgrades else "pass"}
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
${downgrades if downgrades else "pass"}
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
"""a284498
|
|
||||||
|
|
||||||
Revision ID: 474b572b7fe2
|
|
||||||
Revises:
|
|
||||||
Create Date: 2026-01-12 18:28:37.783462
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
import pathlib
|
|
||||||
from collections.abc import Sequence
|
|
||||||
|
|
||||||
import sqlalchemy as sa
|
|
||||||
from alembic import op
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision: str = "474b572b7fe2"
|
|
||||||
down_revision: str | None = None
|
|
||||||
branch_labels: str | Sequence[str] | None = None
|
|
||||||
depends_on: str | Sequence[str] | None = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.create_table(
|
|
||||||
"attachments",
|
|
||||||
sa.Column("id", sa.String(), nullable=False),
|
|
||||||
sa.Column("size", sa.BigInteger(), nullable=False),
|
|
||||||
sa.Column("storage_driver_name", sa.String(), nullable=False),
|
|
||||||
sa.Column("path", sa.String(), nullable=False),
|
|
||||||
sa.Column("media_type", sa.String(), nullable=False),
|
|
||||||
sa.Column("content_type", sa.String(), nullable=False),
|
|
||||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
|
|
||||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
|
|
||||||
sa.Column("is_deleted", sa.Boolean(), nullable=False),
|
|
||||||
sa.PrimaryKeyConstraint("id"),
|
|
||||||
)
|
|
||||||
op.create_table(
|
|
||||||
"beerds",
|
|
||||||
sa.Column("id", sa.String(), nullable=False),
|
|
||||||
sa.Column("name", sa.Text(), nullable=False),
|
|
||||||
sa.Column("alias", sa.Text(), nullable=False),
|
|
||||||
sa.Column("descriptions", sa.Text(), nullable=False),
|
|
||||||
sa.Column("signs", sa.JSON(), nullable=False),
|
|
||||||
sa.PrimaryKeyConstraint("id"),
|
|
||||||
)
|
|
||||||
op.create_table(
|
|
||||||
"votes",
|
|
||||||
sa.Column("id", sa.String(), nullable=False),
|
|
||||||
sa.Column("attachment_id", sa.String(), nullable=False),
|
|
||||||
sa.Column("beerd_id", sa.String(), nullable=False),
|
|
||||||
sa.Column("rate", sa.BigInteger(), nullable=False),
|
|
||||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
|
|
||||||
sa.ForeignKeyConstraint(["attachment_id"], ["attachments.id"], name="votes_attachment_id_fk"),
|
|
||||||
sa.ForeignKeyConstraint(["beerd_id"], ["beerds.id"], name="votes_beerd_id_fk"),
|
|
||||||
sa.PrimaryKeyConstraint("id"),
|
|
||||||
)
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
with open(
|
|
||||||
pathlib.Path(__file__).resolve().parent / "dumps/beerds_insert.sql",
|
|
||||||
encoding="utf-8",
|
|
||||||
) as upgrade_file:
|
|
||||||
sql = upgrade_file.read()
|
|
||||||
op.execute(sql)
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.drop_table("votes")
|
|
||||||
op.drop_table("beerds")
|
|
||||||
op.drop_table("attachments")
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -3,7 +3,7 @@ Working with media files - uploading, storing, receiving
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from server.modules.attachments.domains.attachments import Attachment
|
from server.modules.attachments.domains.attachments import Attachment
|
||||||
from server.modules.attachments.repository.attachments import (
|
from server.modules.attachments.repositories.attachments import (
|
||||||
AttachmentRepository,
|
AttachmentRepository,
|
||||||
DBAttachmentRepository,
|
DBAttachmentRepository,
|
||||||
MockAttachmentRepository,
|
MockAttachmentRepository,
|
||||||
|
|
|
||||||
|
|
@ -13,5 +13,6 @@ class Attachment(UJsonMixin):
|
||||||
storage_driver_name: str
|
storage_driver_name: str
|
||||||
path: str
|
path: str
|
||||||
media_type: str
|
media_type: str
|
||||||
|
created_by: str
|
||||||
content_type: str
|
content_type: str
|
||||||
is_deleted: bool = False
|
is_deleted: bool = False
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,19 @@
|
||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod
|
||||||
from datetime import UTC, datetime
|
from datetime import UTC, datetime
|
||||||
|
from typing import Tuple
|
||||||
|
|
||||||
from sqlalchemy import CursorResult, delete, insert, select, update
|
from sqlalchemy import CursorResult, delete, insert, select, update
|
||||||
|
|
||||||
from server.config import get_app_config
|
from server.config import get_app_config
|
||||||
from server.infra.db import AbstractDB, AbstractSession, AsyncDB, MockDB
|
from server.infra.db import AbstractDB, AbstractSession, AsyncDB, MockDB
|
||||||
from server.modules.attachments.repository.models import Attachment
|
from server.modules.attachments.domains.attachments import Attachment
|
||||||
|
|
||||||
|
|
||||||
class AttachmentRepository(metaclass=ABCMeta):
|
class AttachmentRepository(metaclass=ABCMeta):
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def get_by_id(self, session: AbstractSession, attach_id: list[str]) -> list[Attachment]:
|
async def get_by_id(
|
||||||
|
self, session: AbstractSession, attach_id: list[str]
|
||||||
|
) -> list[Attachment]:
|
||||||
"""Get Attachment by ID"""
|
"""Get Attachment by ID"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -86,7 +89,9 @@ class MockAttachmentRepository(AttachmentRepository):
|
||||||
}
|
}
|
||||||
self._db = MockDB(get_app_config())
|
self._db = MockDB(get_app_config())
|
||||||
|
|
||||||
async def get_by_id(self, session: AbstractSession, attach_id: list[str]) -> list[Attachment]:
|
async def get_by_id(
|
||||||
|
self, session: AbstractSession, attach_id: list[str]
|
||||||
|
) -> list[Attachment]:
|
||||||
f: list[Attachment] = []
|
f: list[Attachment] = []
|
||||||
for f_id in attach_id:
|
for f_id in attach_id:
|
||||||
f_item = self._data.get(f_id)
|
f_item = self._data.get(f_id)
|
||||||
|
|
@ -114,12 +119,14 @@ class DBAttachmentRepository(AttachmentRepository):
|
||||||
def __init__(self, db: AsyncDB):
|
def __init__(self, db: AsyncDB):
|
||||||
self._db = db
|
self._db = db
|
||||||
|
|
||||||
async def get_by_id(self, session: AbstractSession, attach_id: list[str]) -> list[Attachment]:
|
async def get_by_id(
|
||||||
|
self, session: AbstractSession, attach_id: list[str]
|
||||||
|
) -> list[Attachment]:
|
||||||
q = select(Attachment).where(
|
q = select(Attachment).where(
|
||||||
Attachment.id.in_(attach_id) # type: ignore
|
Attachment.id.in_(attach_id) # type: ignore
|
||||||
)
|
)
|
||||||
attachment: list[Attachment] = []
|
attachment: list[Attachment] = []
|
||||||
result: CursorResult[tuple[Attachment]] = await session.execute(q) # type: ignore
|
result: CursorResult[Tuple[Attachment]] = await session.execute(q) # type: ignore
|
||||||
for d in result.all():
|
for d in result.all():
|
||||||
attachment.append(d[0])
|
attachment.append(d[0])
|
||||||
return attachment
|
return attachment
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
from dataclasses import dataclass, field
|
|
||||||
from datetime import UTC, datetime
|
|
||||||
|
|
||||||
from dataclasses_ujson.dataclasses_ujson import UJsonMixin # type: ignore
|
|
||||||
from sqlalchemy import BigInteger, Boolean, Column, DateTime, String
|
|
||||||
|
|
||||||
from server.config import get_app_config
|
|
||||||
from server.infra.db.db_mapper import mapper_registry
|
|
||||||
from server.modules.attachments.domains.attachments import Attachment as AttachmentModel
|
|
||||||
|
|
||||||
|
|
||||||
@mapper_registry.mapped
|
|
||||||
@dataclass
|
|
||||||
class Attachment(UJsonMixin):
|
|
||||||
__sa_dataclass_metadata_key__ = "sa"
|
|
||||||
__tablename__ = "attachments"
|
|
||||||
|
|
||||||
id: str = field(metadata={"sa": Column(String(), primary_key=True, nullable=False)})
|
|
||||||
size: int = field(metadata={"sa": Column(BigInteger(), nullable=False)})
|
|
||||||
storage_driver_name: str = field(metadata={"sa": Column(String(), nullable=False)})
|
|
||||||
path: str = field(metadata={"sa": Column(String(), nullable=False)})
|
|
||||||
media_type: str = field(metadata={"sa": Column(String(), nullable=False)})
|
|
||||||
content_type: str = field(metadata={"sa": Column(String(), nullable=False)})
|
|
||||||
|
|
||||||
created_at: datetime = field(
|
|
||||||
default=datetime.now(UTC),
|
|
||||||
metadata={"sa": Column(DateTime(timezone=True), nullable=False)},
|
|
||||||
)
|
|
||||||
updated_at: datetime = field(
|
|
||||||
default=datetime.now(UTC),
|
|
||||||
metadata={"sa": Column(DateTime(timezone=True), nullable=False)},
|
|
||||||
)
|
|
||||||
is_deleted: bool = field(default=False, metadata={"sa": Column(Boolean(), nullable=False, default=False)})
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{get_app_config().app_public_url}/api/v0/attachment/{self.id}.original.ext"
|
|
||||||
|
|
||||||
def to_domain(self) -> AttachmentModel:
|
|
||||||
return AttachmentModel(
|
|
||||||
id=self.id,
|
|
||||||
size=self.size,
|
|
||||||
media_type=self.media_type,
|
|
||||||
content_type=self.content_type,
|
|
||||||
created_at=self.created_at,
|
|
||||||
updated_at=self.updated_at,
|
|
||||||
storage_driver_name=self.storage_driver_name,
|
|
||||||
path=self.path,
|
|
||||||
)
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
import hashlib
|
import hashlib
|
||||||
import os.path
|
import os.path
|
||||||
import uuid
|
|
||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod
|
||||||
from collections.abc import AsyncIterable, AsyncIterator
|
|
||||||
from datetime import UTC, datetime
|
from datetime import UTC, datetime
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any, AsyncIterable, AsyncIterator, Optional
|
||||||
|
import uuid
|
||||||
|
|
||||||
import aioboto3 # type: ignore
|
import aioboto3 # type: ignore
|
||||||
import aiofiles
|
import aiofiles
|
||||||
|
|
@ -20,8 +19,7 @@ from server.config import AppConfig, get_app_config
|
||||||
from server.infra.db import AbstractSession
|
from server.infra.db import AbstractSession
|
||||||
from server.infra.logger import get_logger
|
from server.infra.logger import get_logger
|
||||||
from server.modules.attachments.domains.attachments import Attachment
|
from server.modules.attachments.domains.attachments import Attachment
|
||||||
from server.modules.attachments.repository.attachments import AttachmentRepository
|
from server.modules.attachments.repositories.attachments import AttachmentRepository
|
||||||
from server.modules.attachments.repository.models import Attachment as AttachmentModel
|
|
||||||
|
|
||||||
|
|
||||||
class StorageDriversType(str, Enum):
|
class StorageDriversType(str, Enum):
|
||||||
|
|
@ -48,7 +46,7 @@ class StorageDriver(metaclass=ABCMeta):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def take(self, path: str) -> bytes | None:
|
async def take(self, path: str) -> Optional[bytes]:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
|
|
@ -80,7 +78,7 @@ class LocalStorageDriver(StorageDriver):
|
||||||
await f.write(data)
|
await f.write(data)
|
||||||
return str(path)
|
return str(path)
|
||||||
|
|
||||||
async def take(self, path: str) -> bytes | None:
|
async def take(self, path: str) -> Optional[bytes]:
|
||||||
if not os.path.isfile(path):
|
if not os.path.isfile(path):
|
||||||
return None
|
return None
|
||||||
async with aiofiles.open(path, "rb") as f:
|
async with aiofiles.open(path, "rb") as f:
|
||||||
|
|
@ -114,7 +112,7 @@ class MockStorageDriver(StorageDriver):
|
||||||
self._store[path] = data
|
self._store[path] = data
|
||||||
return path
|
return path
|
||||||
|
|
||||||
async def take(self, path: str) -> bytes | None:
|
async def take(self, path: str) -> Optional[bytes]:
|
||||||
return self._store.get(path)
|
return self._store.get(path)
|
||||||
|
|
||||||
async def delete(self, path: str):
|
async def delete(self, path: str):
|
||||||
|
|
@ -122,7 +120,7 @@ class MockStorageDriver(StorageDriver):
|
||||||
|
|
||||||
|
|
||||||
class S3StorageDriver(StorageDriver):
|
class S3StorageDriver(StorageDriver):
|
||||||
_prefix: str = "beerds"
|
_prefix: str = "pvc-435e5137-052f-43b1-ace2-e350c9d50c76"
|
||||||
|
|
||||||
def __init__(self, cnf: AppConfig) -> None:
|
def __init__(self, cnf: AppConfig) -> None:
|
||||||
self._chunk_size: int = 69 * 1024
|
self._chunk_size: int = 69 * 1024
|
||||||
|
|
@ -140,7 +138,9 @@ class S3StorageDriver(StorageDriver):
|
||||||
return self._session.client("s3", endpoint_url=self._cnf.fs_s3_endpoint)
|
return self._session.client("s3", endpoint_url=self._cnf.fs_s3_endpoint)
|
||||||
|
|
||||||
def _normalize_path(self, path: str) -> str:
|
def _normalize_path(self, path: str) -> str:
|
||||||
return f"{S3StorageDriver._prefix}{path}".replace(self._cnf.fs_local_mount_dir, "")
|
return f"{S3StorageDriver._prefix}{path}".replace(
|
||||||
|
self._cnf.fs_local_mount_dir, ""
|
||||||
|
)
|
||||||
|
|
||||||
async def put(self, data: bytes) -> str:
|
async def put(self, data: bytes) -> str:
|
||||||
sign = hashlib.file_digest(BytesIO(data), "sha256").hexdigest()
|
sign = hashlib.file_digest(BytesIO(data), "sha256").hexdigest()
|
||||||
|
|
@ -176,10 +176,12 @@ class S3StorageDriver(StorageDriver):
|
||||||
self._logger.error(f"stream client error: {str(e)}, path: {path}")
|
self._logger.error(f"stream client error: {str(e)}, path: {path}")
|
||||||
raise FileNotFoundError
|
raise FileNotFoundError
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._logger.error(f"stream error: {type(e).__name__} {str(e)}, path: {path}")
|
self._logger.error(
|
||||||
|
f"stream error: {type(e).__name__} {str(e)}, path: {path}"
|
||||||
|
)
|
||||||
raise FileNotFoundError
|
raise FileNotFoundError
|
||||||
|
|
||||||
async def take(self, path: str) -> bytes | None:
|
async def take(self, path: str) -> Optional[bytes]:
|
||||||
buffer = BytesIO()
|
buffer = BytesIO()
|
||||||
async for chunk in self.stream(path):
|
async for chunk in self.stream(path):
|
||||||
if chunk:
|
if chunk:
|
||||||
|
|
@ -189,7 +191,9 @@ class S3StorageDriver(StorageDriver):
|
||||||
|
|
||||||
async def delete(self, path: str) -> None:
|
async def delete(self, path: str) -> None:
|
||||||
async with await self._client() as s3:
|
async with await self._client() as s3:
|
||||||
await s3.delete_object(Bucket=self._cnf.fs_s3_bucket, Key=self._normalize_path(path))
|
await s3.delete_object(
|
||||||
|
Bucket=self._cnf.fs_s3_bucket, Key=self._normalize_path(path)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
RESIZE_MAX_SIZE = 100_000
|
RESIZE_MAX_SIZE = 100_000
|
||||||
|
|
@ -239,25 +243,30 @@ class AtachmentService:
|
||||||
return parts.replace("/", "")
|
return parts.replace("/", "")
|
||||||
|
|
||||||
def url(self, attachment_id: str, content_type: str | None = None) -> str:
|
def url(self, attachment_id: str, content_type: str | None = None) -> str:
|
||||||
return f"{self._cnf.app_public_url}/api/v0/attachment/{attachment_id}.original.{self.extension(content_type)}"
|
return f"{self._cnf.app_public_url}/api/v0/attachment/{attachment_id}.original.{
|
||||||
|
self.extension(content_type)
|
||||||
|
}"
|
||||||
|
|
||||||
async def create(self, file: bytes) -> Attachment:
|
async def create(self, file: bytes, user_id: str) -> Attachment:
|
||||||
path = await self._driver.put(file)
|
path = await self._driver.put(file)
|
||||||
content_type = self.content_type(file)
|
content_type = self.content_type(file)
|
||||||
attach = AttachmentModel(
|
attach = Attachment(
|
||||||
size=len(file),
|
size=len(file),
|
||||||
storage_driver_name=str(self._driver.get_name()),
|
storage_driver_name=str(self._driver.get_name()),
|
||||||
path=path,
|
path=path,
|
||||||
media_type=self.media_type(content_type),
|
media_type=self.media_type(content_type),
|
||||||
content_type=content_type,
|
content_type=content_type,
|
||||||
|
created_by=user_id,
|
||||||
id=str(uuid.uuid4()),
|
id=str(uuid.uuid4()),
|
||||||
created_at=datetime.now(UTC),
|
created_at=datetime.now(UTC),
|
||||||
updated_at=datetime.now(UTC),
|
updated_at=datetime.now(UTC),
|
||||||
)
|
)
|
||||||
await self._repository.create(attach)
|
await self._repository.create(attach)
|
||||||
return attach.to_domain()
|
return attach
|
||||||
|
|
||||||
async def get_info(self, session: AbstractSession | None, attach_id: list[str]) -> list[Attachment]:
|
async def get_info(
|
||||||
|
self, session: AbstractSession | None, attach_id: list[str]
|
||||||
|
) -> list[Attachment]:
|
||||||
if not attach_id:
|
if not attach_id:
|
||||||
return []
|
return []
|
||||||
if session is not None:
|
if session is not None:
|
||||||
|
|
@ -268,13 +277,17 @@ class AtachmentService:
|
||||||
def get_name(self, attachment: Attachment) -> str:
|
def get_name(self, attachment: Attachment) -> str:
|
||||||
return f"{attachment.id}.{self.extension(attachment.content_type)}"
|
return f"{attachment.id}.{self.extension(attachment.content_type)}"
|
||||||
|
|
||||||
async def get_data(self, session: AbstractSession, attach_id: str) -> bytes | None:
|
async def get_data(
|
||||||
|
self, session: AbstractSession, attach_id: str
|
||||||
|
) -> Optional[bytes]:
|
||||||
file = await self._repository.get_by_id(session, [attach_id])
|
file = await self._repository.get_by_id(session, [attach_id])
|
||||||
if not file:
|
if not file:
|
||||||
return None
|
return None
|
||||||
return await self._driver.take(file[0].path)
|
return await self._driver.take(file[0].path)
|
||||||
|
|
||||||
async def get_stream(self, session: AbstractSession | None, attach_id: str) -> AsyncIterator[bytes]:
|
async def get_stream(
|
||||||
|
self, session: AbstractSession | None, attach_id: str
|
||||||
|
) -> AsyncIterator[bytes]:
|
||||||
async def _stream_iterator(is_empty: bool):
|
async def _stream_iterator(is_empty: bool):
|
||||||
if is_empty:
|
if is_empty:
|
||||||
return
|
return
|
||||||
|
|
@ -330,5 +343,7 @@ class AtachmentService:
|
||||||
f"delete:{item.path}",
|
f"delete:{item.path}",
|
||||||
)
|
)
|
||||||
path = await self._driver.put(d)
|
path = await self._driver.put(d)
|
||||||
await self._repository.update(item.id, path=path, content_type="image/jpeg", size=len(d))
|
await self._repository.update(
|
||||||
|
item.id, path=path, content_type="image/jpeg", size=len(d)
|
||||||
|
)
|
||||||
await self._driver.delete(item.path)
|
await self._driver.delete(item.path)
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
from server.modules.descriptions.domain import Breed
|
from server.modules.descriptions.repository import (
|
||||||
from server.modules.descriptions.repository import ACharactersRepository, CharactersRepository, PGCharactersRepository
|
CharactersRepository,
|
||||||
|
ACharactersRepository,
|
||||||
|
)
|
||||||
from server.modules.descriptions.service import CharactersService
|
from server.modules.descriptions.service import CharactersService
|
||||||
|
from server.modules.descriptions.domain import Breed
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
"CharactersRepository",
|
"CharactersRepository",
|
||||||
"ACharactersRepository",
|
"ACharactersRepository",
|
||||||
"CharactersService",
|
"CharactersService",
|
||||||
"PGCharactersRepository",
|
|
||||||
"Breed",
|
"Breed",
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ from dataclasses import dataclass
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class Breed:
|
class Breed:
|
||||||
id: str
|
|
||||||
name: str
|
name: str
|
||||||
alias: str
|
alias: str
|
||||||
description: str
|
description: str
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
from server.modules.descriptions.repository.repository import (
|
from server.modules.descriptions.repository.repository import (
|
||||||
ACharactersRepository,
|
|
||||||
CharactersRepository,
|
CharactersRepository,
|
||||||
PGCharactersRepository,
|
ACharactersRepository,
|
||||||
)
|
)
|
||||||
|
|
||||||
__all__ = ("CharactersRepository", "ACharactersRepository", "PGCharactersRepository")
|
__all__ = ("CharactersRepository", "ACharactersRepository")
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
|
||||||
|
|
||||||
|
```json
|
||||||
{
|
{
|
||||||
"people_friendly": true,
|
"people_friendly": true,
|
||||||
"child_friendly": true,
|
"child_friendly": true,
|
||||||
|
|
@ -11,3 +14,4 @@
|
||||||
"tolerates_loneliness": false,
|
"tolerates_loneliness": false,
|
||||||
"hypoallergenic": false
|
"hypoallergenic": false
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
|
||||||
|
|
||||||
|
```json
|
||||||
{
|
{
|
||||||
"active": true,
|
"active": true,
|
||||||
"colors": ["blue", "gray", "black"],
|
"colors": ["blue", "gray", "black"],
|
||||||
|
|
@ -7,3 +10,4 @@
|
||||||
"good_health": false,
|
"good_health": false,
|
||||||
"tolerates_loneliness": false
|
"tolerates_loneliness": false
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
|
||||||
|
|
||||||
|
```json
|
||||||
{
|
{
|
||||||
"people_friendly": true,
|
"people_friendly": true,
|
||||||
"child_friendly": true,
|
"child_friendly": true,
|
||||||
|
|
@ -10,3 +13,4 @@
|
||||||
"tolerates_loneliness": false,
|
"tolerates_loneliness": false,
|
||||||
"hypoallergenic": false
|
"hypoallergenic": false
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
|
||||||
|
|
||||||
|
```json
|
||||||
{
|
{
|
||||||
"people_friendly": true,
|
"people_friendly": true,
|
||||||
"child_friendly": true,
|
"child_friendly": true,
|
||||||
|
|
@ -14,3 +17,4 @@
|
||||||
"tolerates_loneliness": false,
|
"tolerates_loneliness": false,
|
||||||
"hypoallergenic": false
|
"hypoallergenic": false
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
|
||||||
|
|
||||||
|
It seems like the text you've shared is repeated multiple times, possibly due to an error. Could you clarify your question or specify what you need help with? For example, are you looking for tips on dog care, training, nutrition, or something else? Let me know, and I'll provide a clear, concise response! 🐾
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
|
||||||
|
|
||||||
|
```json
|
||||||
{
|
{
|
||||||
"people_friendly": true,
|
"people_friendly": true,
|
||||||
"child_friendly": true,
|
"child_friendly": true,
|
||||||
|
|
@ -10,3 +13,4 @@
|
||||||
"tolerates_loneliness": false,
|
"tolerates_loneliness": false,
|
||||||
"hypoallergenic": false
|
"hypoallergenic": false
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
@ -1,7 +1,16 @@
|
||||||
{
|
|
||||||
"people_friendly": true,
|
|
||||||
"child_friendly": true,
|
The Entlebucher Mountain Dog exhibits the following traits based on the provided description:
|
||||||
"active": true,
|
|
||||||
"need_attentions": true,
|
- **Child-friendly**: Yes. The text explicitly states, "Прекрасно ладят с детьми" (Great with children).
|
||||||
"good_health": false
|
- **High energy**: Yes. The breed requires daily walks, games, and activities, and lacks of stimulation may lead to destructive behavior.
|
||||||
}
|
- **Friendly**: Yes. The dog is described as "обычно дружелюбны" (usually friendly) with people and other animals, and it is noted that they are good with children.
|
||||||
|
|
||||||
|
Other attributes:
|
||||||
|
- **People-friendly**: Ambiguous. While the breed is friendly with people, the term "people-friendly" is not explicitly mentioned.
|
||||||
|
- **Dog-friendly**: Unclear. The text notes "обычно дружелюбны" (usually friendly) with other animals but mentions a hunting instinct toward small animals, which may not directly apply to dogs.
|
||||||
|
- **Low maintenance**: No. The breed requires regular grooming (brushing) and active socialization/training.
|
||||||
|
- **Hypoallergenic**: No. There is no mention of hypoallergenic traits.
|
||||||
|
|
||||||
|
**Final Answer**:
|
||||||
|
Child-friendly, High energy, Friendly.
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
from dataclasses import dataclass, field
|
|
||||||
|
|
||||||
from dataclasses_ujson.dataclasses_ujson import UJsonMixin # type: ignore
|
|
||||||
from sqlalchemy import JSON, Column, String, Text
|
|
||||||
|
|
||||||
from server.infra.db.db_mapper import mapper_registry
|
|
||||||
|
|
||||||
|
|
||||||
@mapper_registry.mapped
|
|
||||||
@dataclass
|
|
||||||
class Beerds(UJsonMixin):
|
|
||||||
__sa_dataclass_metadata_key__ = "sa"
|
|
||||||
__tablename__ = "beerds"
|
|
||||||
|
|
||||||
id: str = field(metadata={"sa": Column(String(), primary_key=True, nullable=False)})
|
|
||||||
name: str = field(metadata={"sa": Column(Text(), nullable=False)})
|
|
||||||
alias: str = field(metadata={"sa": Column(Text(), nullable=False)})
|
|
||||||
descriptions: str = field(metadata={"sa": Column(Text(), nullable=False)})
|
|
||||||
signs: dict | None = field(default=None, metadata={"sa": Column(JSON(), nullable=False)})
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from aiocache import Cache, cached # type: ignore
|
from aiocache import cached, Cache # type: ignore
|
||||||
from sqlalchemy import select
|
|
||||||
|
|
||||||
from server.infra.db import AsyncDB
|
from server.infra.db import AsyncDB
|
||||||
from server.modules.descriptions.domain import Breed
|
from server.modules.descriptions.domain import Breed
|
||||||
from server.modules.descriptions.repository import models
|
|
||||||
|
|
||||||
|
|
||||||
class ACharactersRepository(metaclass=ABCMeta):
|
class ACharactersRepository(metaclass=ABCMeta):
|
||||||
|
|
@ -14,7 +12,6 @@ class ACharactersRepository(metaclass=ABCMeta):
|
||||||
async def get_characters(self) -> list[Breed]:
|
async def get_characters(self) -> list[Breed]:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
async def get_character(self, alias: str) -> Breed | None:
|
async def get_character(self, alias: str) -> Breed | None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -31,10 +28,11 @@ class CharactersRepository(ACharactersRepository):
|
||||||
# Идем по каждому текстовому файлу с описанием породы
|
# Идем по каждому текстовому файлу с описанием породы
|
||||||
for breed_file in breed_dir.glob("*.txt"):
|
for breed_file in breed_dir.glob("*.txt"):
|
||||||
breed_name = breed_file.stem # имя файла без расширения - название породы
|
breed_name = breed_file.stem # имя файла без расширения - название породы
|
||||||
description = breed_file.read_text(encoding="utf-8") # читаем описание из файла
|
description = breed_file.read_text(
|
||||||
|
encoding="utf-8"
|
||||||
|
) # читаем описание из файла
|
||||||
breeds.append(
|
breeds.append(
|
||||||
Breed(
|
Breed(
|
||||||
id=breed_name,
|
|
||||||
name=breed_name.replace("_", " "),
|
name=breed_name.replace("_", " "),
|
||||||
alias=breed_file.stem,
|
alias=breed_file.stem,
|
||||||
description=description.strip(),
|
description=description.strip(),
|
||||||
|
|
@ -57,71 +55,30 @@ class PGCharactersRepository(ACharactersRepository):
|
||||||
def __init__(self, db: AsyncDB):
|
def __init__(self, db: AsyncDB):
|
||||||
self._db = db
|
self._db = db
|
||||||
|
|
||||||
# ───────────────────────────────────────────────────────────────────── #
|
@cached(ttl=60, cache=Cache.MEMORY)
|
||||||
# 8️⃣ Кешируемый метод, который возвращает **все** породы
|
|
||||||
# ───────────────────────────────────────────────────────────────────── #
|
|
||||||
@cached(ttl=60, cache=Cache.MEMORY) # 1‑мин. кеш
|
|
||||||
async def get_characters(self) -> list[Breed]:
|
async def get_characters(self) -> list[Breed]:
|
||||||
"""
|
breed_dir = Path("server/modules/descriptions/repository/breed_descriptions")
|
||||||
Читает данные из таблицы `beerds.beerds` и преобразует каждую строку
|
breeds: list[Breed] = []
|
||||||
в экземпляр `Breed`. Поле `signs` игнорируется – в `Breed` его нет.
|
|
||||||
"""
|
|
||||||
|
|
||||||
async with self._db.async_session() as session:
|
# Идем по каждому текстовому файлу с описанием породы
|
||||||
# Писем SELECT‑запрос (получаем все строки)
|
for breed_file in breed_dir.glob("*.txt"):
|
||||||
stmt = select( # type: ignore
|
breed_name = breed_file.stem # имя файла без расширения - название породы
|
||||||
models.Beerds.id,
|
description = breed_file.read_text(
|
||||||
models.Beerds.name,
|
encoding="utf-8"
|
||||||
models.Beerds.alias,
|
) # читаем описание из файла
|
||||||
models.Beerds.descriptions,
|
breeds.append(
|
||||||
|
Breed(
|
||||||
|
name=breed_name.replace("_", " "),
|
||||||
|
alias=breed_file.stem,
|
||||||
|
description=description.strip(),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
result = await session.execute(stmt)
|
breeds.sort(key=lambda b: b.name)
|
||||||
rows = result.fetchall()
|
|
||||||
|
|
||||||
# Конвертируем в Breed
|
|
||||||
breeds: list[Breed] = [
|
|
||||||
Breed(
|
|
||||||
id=str(row.id),
|
|
||||||
name=row.name.strip(),
|
|
||||||
alias=row.alias.strip(),
|
|
||||||
description=row.descriptions.strip(),
|
|
||||||
)
|
|
||||||
for row in rows
|
|
||||||
]
|
|
||||||
|
|
||||||
# Сортируем по имени, как было в файле‑реализации
|
|
||||||
breeds.sort(key=lambda b: b.name.lower())
|
|
||||||
return breeds
|
return breeds
|
||||||
|
|
||||||
# ───────────────────────────────────────────────────────────────────── #
|
|
||||||
# 9️⃣ Получить конкретную породу по псевдониму
|
|
||||||
# ───────────────────────────────────────────────────────────────────── #
|
|
||||||
async def get_character(self, alias: str) -> Breed | None:
|
async def get_character(self, alias: str) -> Breed | None:
|
||||||
"""
|
breeds = await self.get_characters()
|
||||||
Быстрый запрос без получения всех пород. Если результат
|
data = [b for b in breeds if b.alias == alias]
|
||||||
пустой – возвращаем `None`.
|
if len(data) == 0:
|
||||||
"""
|
|
||||||
|
|
||||||
async with self._db.async_session() as session:
|
|
||||||
stmt = (
|
|
||||||
select( # type: ignore
|
|
||||||
models.Beerds.id,
|
|
||||||
models.Beerds.name,
|
|
||||||
models.Beerds.alias,
|
|
||||||
models.Beerds.descriptions,
|
|
||||||
)
|
|
||||||
.where(models.Beerds.alias == alias)
|
|
||||||
.limit(1)
|
|
||||||
)
|
|
||||||
result = await session.execute(stmt)
|
|
||||||
row = result.fetchone()
|
|
||||||
|
|
||||||
if row is None: # pragma: no cover
|
|
||||||
return None
|
return None
|
||||||
|
return data[0]
|
||||||
return Breed(
|
|
||||||
id=str(row.id),
|
|
||||||
name=row.name.strip(),
|
|
||||||
alias=row.alias.strip(),
|
|
||||||
description=row.descriptions.strip(),
|
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
from server.modules.rate.domain import Vote
|
|
||||||
from server.modules.rate.repository import AVoteRepository, PGVoteRepository
|
|
||||||
from server.modules.rate.service import VotesService
|
|
||||||
|
|
||||||
__all__ = (
|
|
||||||
"VotesService",
|
|
||||||
"AVoteRepository",
|
|
||||||
"PGVoteRepository",
|
|
||||||
"Vote",
|
|
||||||
)
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
from dataclasses import dataclass
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Vote:
|
|
||||||
id: str
|
|
||||||
attachment_id: str
|
|
||||||
beerd_id: str
|
|
||||||
rate: int
|
|
||||||
created_at: datetime
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
from server.modules.rate.domain import Vote
|
|
||||||
from server.modules.rate.repository.models import Vote as VoteModel
|
|
||||||
from server.modules.rate.repository.repository import AVoteRepository, PGVoteRepository
|
|
||||||
from server.modules.rate.service import VotesService
|
|
||||||
|
|
||||||
__all__ = (
|
|
||||||
"Vote",
|
|
||||||
"PGVoteRepository",
|
|
||||||
"ACharactersRepository",
|
|
||||||
"AVoteRepository",
|
|
||||||
"VotesService",
|
|
||||||
"VoteModel",
|
|
||||||
)
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
from dataclasses import dataclass, field
|
|
||||||
from datetime import UTC, datetime
|
|
||||||
|
|
||||||
from dataclasses_ujson.dataclasses_ujson import UJsonMixin # type: ignore
|
|
||||||
from sqlalchemy import (
|
|
||||||
BigInteger,
|
|
||||||
Column,
|
|
||||||
DateTime,
|
|
||||||
ForeignKeyConstraint,
|
|
||||||
String,
|
|
||||||
)
|
|
||||||
|
|
||||||
from server.config import get_app_config
|
|
||||||
from server.infra.db.db_mapper import mapper_registry
|
|
||||||
from server.modules.rate import domain
|
|
||||||
|
|
||||||
|
|
||||||
@mapper_registry.mapped
|
|
||||||
@dataclass
|
|
||||||
class Vote(UJsonMixin):
|
|
||||||
__sa_dataclass_metadata_key__ = "sa"
|
|
||||||
__tablename__ = "votes"
|
|
||||||
__table_args__ = (
|
|
||||||
ForeignKeyConstraint(["attachment_id"], ["attachments.id"], "votes_attachment_id_fk"),
|
|
||||||
ForeignKeyConstraint(["beerd_id"], ["beerds.id"], "votes_beerd_id_fk"),
|
|
||||||
)
|
|
||||||
|
|
||||||
id: str = field(metadata={"sa": Column(String(), primary_key=True, nullable=False)})
|
|
||||||
attachment_id: str = field(metadata={"sa": Column(String(), nullable=False)})
|
|
||||||
beerd_id: str = field(metadata={"sa": Column(String(), nullable=False)})
|
|
||||||
rate: int = field(metadata={"sa": Column(BigInteger(), nullable=False)})
|
|
||||||
created_at: datetime = field(
|
|
||||||
default=datetime.now(UTC),
|
|
||||||
metadata={"sa": Column(DateTime(timezone=True), nullable=False)},
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{get_app_config().app_public_url}/api/v0/attachment/{self.id}.original.ext"
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def from_domain(d: domain.Vote) -> "Vote":
|
|
||||||
return Vote(
|
|
||||||
id=d.id,
|
|
||||||
attachment_id=d.attachment_id,
|
|
||||||
beerd_id=d.beerd_id,
|
|
||||||
rate=d.rate,
|
|
||||||
created_at=d.created_at,
|
|
||||||
)
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
"""
|
|
||||||
Repository for working with the :class:`~server.modules.rate.repository.models.Vote` model.
|
|
||||||
Only the “write‑only” operation – adding a vote – is required in the current
|
|
||||||
application logic. The repository keeps the database interaction
|
|
||||||
encapsulated and provides an asynchronous API that can be used by the
|
|
||||||
service layer (or any other consumer).
|
|
||||||
|
|
||||||
The implementation uses the same pattern that is already present in the
|
|
||||||
``CharactersRepository`` – an async session manager from
|
|
||||||
``server.infra.db.AsyncDB`` and an interface that makes unit‑testing
|
|
||||||
straightforward.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from abc import ABCMeta, abstractmethod
|
|
||||||
|
|
||||||
from server.infra.db import AsyncDB
|
|
||||||
from server.modules.rate.repository import models
|
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------------- #
|
|
||||||
# 1️⃣ Base interface
|
|
||||||
# --------------------------------------------------------------------------- #
|
|
||||||
class AVoteRepository(metaclass=ABCMeta):
|
|
||||||
"""
|
|
||||||
Abstract repository that declares the contract for working with votes.
|
|
||||||
At the moment only one operation is required – inserting a new vote.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
async def add_vote(self, vote: models.Vote) -> None:
|
|
||||||
"""Persist ``vote`` into the database."""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------------- #
|
|
||||||
# 2️⃣ Concrete PostgreSQL implementation
|
|
||||||
# --------------------------------------------------------------------------- #
|
|
||||||
class PGVoteRepository(AVoteRepository):
|
|
||||||
"""
|
|
||||||
PostgreSQL implementation of :class:`AVoteRepository`.
|
|
||||||
|
|
||||||
The repository is intentionally *minimal* – only an ``add_vote`` method
|
|
||||||
is exposed. The rest of the CRUD operations are expected to be added
|
|
||||||
later if/when the business logic grows.
|
|
||||||
"""
|
|
||||||
|
|
||||||
_db: AsyncDB
|
|
||||||
|
|
||||||
def __init__(self, db: AsyncDB):
|
|
||||||
self._db = db
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------- #
|
|
||||||
# 2.1 Add a vote
|
|
||||||
# --------------------------------------------------------------------- #
|
|
||||||
async def add_vote(self, vote: models.Vote) -> None:
|
|
||||||
"""
|
|
||||||
Insert ``vote`` into the ``votes`` table.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
vote:
|
|
||||||
Instance of :class:`~server.modules.rate.repository.models.Vote`
|
|
||||||
containing all necessary fields (``id``, ``attachment_id``,
|
|
||||||
``beerd_id``, ``rate`` and ``created_at``). The instance
|
|
||||||
is a *mapped* dataclass, therefore SQLAlchemy can persist it
|
|
||||||
directly.
|
|
||||||
|
|
||||||
Notes
|
|
||||||
-----
|
|
||||||
* No explicit caching – the operation mutates the database and
|
|
||||||
should never be served from a cache.
|
|
||||||
* The method is deliberately ``async`` because it uses
|
|
||||||
``AsyncDB.async_session``.
|
|
||||||
"""
|
|
||||||
async with self._db.async_session() as session:
|
|
||||||
# We use the *instance* directly – SQLAlchemy will handle the
|
|
||||||
# mapping for us. ``insert`` could also be used, but the
|
|
||||||
# instance‑based approach keeps type‑checking and IDE
|
|
||||||
# auto‑completion in sync with the dataclass.
|
|
||||||
session.add(vote)
|
|
||||||
await session.commit()
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
from server.modules.rate.domain import Vote
|
|
||||||
from server.modules.rate.repository import AVoteRepository, VoteModel
|
|
||||||
|
|
||||||
|
|
||||||
class VotesService:
|
|
||||||
__slots__ = ("_repository",)
|
|
||||||
|
|
||||||
def __init__(self, repository: AVoteRepository):
|
|
||||||
self._repository = repository
|
|
||||||
|
|
||||||
async def add_vote(self, vote: Vote):
|
|
||||||
return await self._repository.add_vote(VoteModel.from_domain(vote))
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
from server.modules.recognizer.repository import (
|
from server.modules.recognizer.repository import (
|
||||||
ARecognizerRepository,
|
|
||||||
RecognizerRepository,
|
RecognizerRepository,
|
||||||
|
ARecognizerRepository,
|
||||||
)
|
)
|
||||||
from server.modules.recognizer.service import RecognizerService
|
from server.modules.recognizer.service import RecognizerService
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
from server.modules.recognizer.repository.repository import (
|
from server.modules.recognizer.repository.repository import (
|
||||||
ARecognizerRepository,
|
|
||||||
RecognizerRepository,
|
RecognizerRepository,
|
||||||
|
ARecognizerRepository,
|
||||||
)
|
)
|
||||||
|
|
||||||
__all__ = ("RecognizerRepository", "ARecognizerRepository")
|
__all__ = ("RecognizerRepository", "ARecognizerRepository")
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
|
|
||||||
|
from aiocache import cached, Cache # type: ignore
|
||||||
import ujson
|
import ujson
|
||||||
from aiocache import Cache, cached # type: ignore
|
|
||||||
|
|
||||||
|
|
||||||
class ARecognizerRepository(metaclass=ABCMeta):
|
class ARecognizerRepository(metaclass=ABCMeta):
|
||||||
|
|
@ -29,22 +29,26 @@ class RecognizerRepository(ARecognizerRepository):
|
||||||
|
|
||||||
@cached(ttl=60, cache=Cache.MEMORY)
|
@cached(ttl=60, cache=Cache.MEMORY)
|
||||||
async def images_dogs(self) -> dict:
|
async def images_dogs(self) -> dict:
|
||||||
with open("server/modules/recognizer/repository/meta/images.json") as f: # noqa: ASYNC230
|
with open("server/modules/recognizer/repository/meta/images.json", "r") as f:
|
||||||
return ujson.loads(f.read())["dog"]
|
return ujson.loads(f.read())["dog"]
|
||||||
|
|
||||||
@cached(ttl=60, cache=Cache.MEMORY)
|
@cached(ttl=60, cache=Cache.MEMORY)
|
||||||
async def images_cats(self) -> dict:
|
async def images_cats(self) -> dict:
|
||||||
with open("server/modules/recognizer/repository/meta/images.json") as f: # noqa: ASYNC230
|
with open("server/modules/recognizer/repository/meta/images.json", "r") as f:
|
||||||
return ujson.loads(f.read())["cat"]
|
return ujson.loads(f.read())["cat"]
|
||||||
|
|
||||||
@lru_cache
|
@lru_cache
|
||||||
def labels_cats(self) -> dict:
|
def labels_cats(self) -> dict:
|
||||||
with open("server/modules/recognizer/repository/meta/labels_cats.json") as f: # noqa: ASYNC230
|
with open(
|
||||||
|
"server/modules/recognizer/repository/meta/labels_cats.json", "r"
|
||||||
|
) as f:
|
||||||
data_labels = f.read()
|
data_labels = f.read()
|
||||||
return ujson.loads(data_labels)
|
return ujson.loads(data_labels)
|
||||||
|
|
||||||
@lru_cache
|
@lru_cache
|
||||||
def labels_dogs(self) -> dict:
|
def labels_dogs(self) -> dict:
|
||||||
with open("server/modules/recognizer/repository/meta/labels_dogs.json") as f: # noqa: ASYNC230
|
with open(
|
||||||
|
"server/modules/recognizer/repository/meta/labels_dogs.json", "r"
|
||||||
|
) as f:
|
||||||
data_labels = f.read()
|
data_labels = f.read()
|
||||||
return ujson.loads(data_labels)
|
return ujson.loads(data_labels)
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,16 @@
|
||||||
import io
|
from typing import NewType, Any
|
||||||
import os
|
import os
|
||||||
from dataclasses import dataclass
|
import io
|
||||||
from typing import Any, NewType, Protocol
|
|
||||||
|
|
||||||
from dataclasses_ujson.dataclasses_ujson import UJsonMixin # type: ignore
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
os.environ["CUDA_VISIBLE_DEVICES"] = "-1"
|
os.environ["CUDA_VISIBLE_DEVICES"] = "-1"
|
||||||
import torch
|
import torch
|
||||||
from torchvision import transforms # type: ignore
|
from torchvision import transforms # type: ignore
|
||||||
|
|
||||||
from server.modules.attachments.domains.attachments import Attachment
|
|
||||||
from server.modules.recognizer.repository import ARecognizerRepository
|
from server.modules.recognizer.repository import ARecognizerRepository
|
||||||
|
|
||||||
|
|
||||||
TorchModel = NewType("TorchModel", torch.nn.Module)
|
TorchModel = NewType("TorchModel", torch.nn.Module)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -26,31 +24,11 @@ DOG_MODEL = load_model("server/models/dogs_model.pth")
|
||||||
CAT_MODEL = load_model("server/models/cats_model.pth")
|
CAT_MODEL = load_model("server/models/cats_model.pth")
|
||||||
|
|
||||||
|
|
||||||
class AttachmentService(Protocol):
|
|
||||||
async def create(self, file: bytes) -> Attachment:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class ResultImages(UJsonMixin):
|
|
||||||
name: str
|
|
||||||
url: list[str]
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class RecognizerResult(UJsonMixin):
|
|
||||||
results: dict
|
|
||||||
images: list
|
|
||||||
description: dict[str, list] | None
|
|
||||||
uploaded_attach_id: str | None
|
|
||||||
|
|
||||||
|
|
||||||
class RecognizerService:
|
class RecognizerService:
|
||||||
__slots__ = ("_repository", "_attachment_service")
|
__slots__ = "_repository"
|
||||||
|
|
||||||
def __init__(self, repository: ARecognizerRepository, attachment_service: AttachmentService):
|
def __init__(self, repository: ARecognizerRepository):
|
||||||
self._repository = repository
|
self._repository = repository
|
||||||
self._attachment_service = attachment_service
|
|
||||||
|
|
||||||
async def images_cats(self) -> dict:
|
async def images_cats(self) -> dict:
|
||||||
return await self._repository.images_cats()
|
return await self._repository.images_cats()
|
||||||
|
|
@ -58,8 +36,7 @@ class RecognizerService:
|
||||||
async def images_dogs(self) -> dict:
|
async def images_dogs(self) -> dict:
|
||||||
return await self._repository.images_dogs()
|
return await self._repository.images_dogs()
|
||||||
|
|
||||||
async def predict_dog_image(self, image: bytes) -> RecognizerResult:
|
async def predict_dog_image(self, image: bytes) -> dict:
|
||||||
attachment = await self._attachment_service.create(image)
|
|
||||||
predicted_data = self._predict(image, DOG_MODEL)
|
predicted_data = self._predict(image, DOG_MODEL)
|
||||||
results = {}
|
results = {}
|
||||||
images = []
|
images = []
|
||||||
|
|
@ -67,36 +44,50 @@ class RecognizerService:
|
||||||
images_dogs = await self._repository.images_dogs()
|
images_dogs = await self._repository.images_dogs()
|
||||||
for d in predicted_data:
|
for d in predicted_data:
|
||||||
predicted_idx, probabilities = d
|
predicted_idx, probabilities = d
|
||||||
predicted_label: str = self._repository.labels_dogs()[str(predicted_idx)]
|
predicted_label = self._repository.labels_dogs()[str(predicted_idx)]
|
||||||
name = predicted_label.replace("_", " ")
|
name = predicted_label.replace("_", " ")
|
||||||
images.append(
|
images.append(
|
||||||
ResultImages(
|
{
|
||||||
name=name, url=[f"/static/assets/dog/{predicted_label}/{i}" for i in images_dogs[predicted_label]]
|
"name": name,
|
||||||
)
|
"url": [
|
||||||
|
f"/static/assets/dog/{predicted_label}/{i}"
|
||||||
|
for i in images_dogs[predicted_label]
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
description.setdefault(name, []).append(
|
||||||
|
f"/dogs-characteristics/{name.replace(' ', '_')}"
|
||||||
)
|
)
|
||||||
description.setdefault(name, []).append(f"/dogs-characteristics/{name.replace(' ', '_')}")
|
|
||||||
results[probabilities] = name
|
results[probabilities] = name
|
||||||
return RecognizerResult(
|
return {
|
||||||
results=results, images=images, description=description, uploaded_attach_id=attachment.id
|
"results": results,
|
||||||
)
|
"images": images,
|
||||||
|
"description": description,
|
||||||
|
}
|
||||||
|
|
||||||
async def predict_cat_image(self, image: bytes) -> RecognizerResult:
|
async def predict_cat_image(self, image: bytes) -> dict:
|
||||||
attachment = await self._attachment_service.create(image)
|
|
||||||
predicted_data = self._predict(image, CAT_MODEL)
|
predicted_data = self._predict(image, CAT_MODEL)
|
||||||
results = {}
|
results = {}
|
||||||
images = []
|
images = []
|
||||||
images_cats = await self._repository.images_cats()
|
images_cats = await self._repository.images_cats()
|
||||||
for d in predicted_data:
|
for d in predicted_data:
|
||||||
predicted_idx, probabilities = d
|
predicted_idx, probabilities = d
|
||||||
predicted_label: str = self._repository.labels_cats()[str(predicted_idx)]
|
predicted_label = self._repository.labels_cats()[str(predicted_idx)]
|
||||||
name = predicted_label.replace("_", " ")
|
name = predicted_label.replace("_", " ")
|
||||||
images.append(
|
images.append(
|
||||||
ResultImages(
|
{
|
||||||
name=name, url=[f"/static/assets/cat/{predicted_label}/{i}" for i in images_cats[predicted_label]]
|
"name": name,
|
||||||
)
|
"url": [
|
||||||
|
f"/static/assets/cat/{predicted_label}/{i}"
|
||||||
|
for i in images_cats[predicted_label]
|
||||||
|
],
|
||||||
|
}
|
||||||
)
|
)
|
||||||
results[probabilities] = name
|
results[probabilities] = name
|
||||||
return RecognizerResult(results=results, images=images, description=None, uploaded_attach_id=attachment.id)
|
return {
|
||||||
|
"results": results,
|
||||||
|
"images": images,
|
||||||
|
}
|
||||||
|
|
||||||
def _predict(self, image: bytes, model, device="cpu") -> list[Any]:
|
def _predict(self, image: bytes, model, device="cpu") -> list[Any]:
|
||||||
img_size = (224, 224)
|
img_size = (224, 224)
|
||||||
|
|
@ -108,7 +99,9 @@ class RecognizerService:
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
input_tensor = preprocess(Image.open(io.BytesIO(image)))
|
input_tensor = preprocess(Image.open(io.BytesIO(image)))
|
||||||
input_batch = input_tensor.unsqueeze(0).to(device) # Добавляем dimension для батча
|
input_batch = input_tensor.unsqueeze(0).to(
|
||||||
|
device
|
||||||
|
) # Добавляем dimension для батча
|
||||||
|
|
||||||
with torch.no_grad():
|
with torch.no_grad():
|
||||||
output = model(input_batch)
|
output = model(input_batch)
|
||||||
|
|
@ -119,5 +112,7 @@ class RecognizerService:
|
||||||
|
|
||||||
predicted_data = []
|
predicted_data = []
|
||||||
for i in range(k):
|
for i in range(k):
|
||||||
predicted_data.append((predicted_idx[i].item(), float(topk_probs[i].item())))
|
predicted_data.append(
|
||||||
|
(predicted_idx[i].item(), float(topk_probs[i].item()))
|
||||||
|
)
|
||||||
return predicted_data
|
return predicted_data
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
|
|
||||||
let urlCreator = window.URL || window.webkitURL;
|
let urlCreator = window.URL || window.webkitURL;
|
||||||
|
|
||||||
let current_attachment_id = 0;
|
|
||||||
let current_beerd_name = [];
|
|
||||||
|
|
||||||
async function SavePhoto(self) {
|
async function SavePhoto(self) {
|
||||||
document.getElementById("result").innerHTML = "";
|
document.getElementById("result").innerHTML = "";
|
||||||
let photo = document.getElementById("file-input").files[0];
|
let photo = document.getElementById("file-input").files[0];
|
||||||
|
|
@ -13,8 +10,6 @@ async function SavePhoto(self) {
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
let json = await response.json();
|
let json = await response.json();
|
||||||
current_attachment_id = json.uploaded_attach_id;
|
|
||||||
|
|
||||||
let text = "<h3 class='image-results'>Результаты</h3>";
|
let text = "<h3 class='image-results'>Результаты</h3>";
|
||||||
let uniqChecker = {};
|
let uniqChecker = {};
|
||||||
|
|
||||||
|
|
@ -37,7 +32,6 @@ async function SavePhoto(self) {
|
||||||
|
|
||||||
// Обработка основных результатов
|
// Обработка основных результатов
|
||||||
for (let key in json.results) {
|
for (let key in json.results) {
|
||||||
current_beerd_name.push(json.results[key]);
|
|
||||||
if (json.description != undefined) {
|
if (json.description != undefined) {
|
||||||
text += `<div class='image-block'><div class='image-text'>${json.results[key]} (вероятность: ${Math.round(parseFloat(key) * 100)}%) <br/><a href="${json.description[json.results[key]]}" target='_blank'>Описание </a></div>`;
|
text += `<div class='image-block'><div class='image-text'>${json.results[key]} (вероятность: ${Math.round(parseFloat(key) * 100)}%) <br/><a href="${json.description[json.results[key]]}" target='_blank'>Описание </a></div>`;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -57,6 +51,7 @@ async function SavePhoto(self) {
|
||||||
text += "</div>";
|
text += "</div>";
|
||||||
uniqChecker[json.results[key]] = key;
|
uniqChecker[json.results[key]] = key;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обработка дополнительных результатов
|
// Обработка дополнительных результатов
|
||||||
for (let key in json.results_net) {
|
for (let key in json.results_net) {
|
||||||
if (uniqChecker[json.results_net[key]] !== undefined) continue;
|
if (uniqChecker[json.results_net[key]] !== undefined) continue;
|
||||||
|
|
@ -75,9 +70,6 @@ async function SavePhoto(self) {
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById("result").innerHTML = text;
|
document.getElementById("result").innerHTML = text;
|
||||||
document.querySelector(".star-rating").style.display = "block";
|
|
||||||
document.getElementById("rate-stars").style.display = "block";
|
|
||||||
document.getElementById("feedback").textContent = "";
|
|
||||||
setTimeout(function(){
|
setTimeout(function(){
|
||||||
window.scrollBy({
|
window.scrollBy({
|
||||||
top: 300,
|
top: 300,
|
||||||
|
|
@ -190,89 +182,3 @@ function openModal(imgElement) {
|
||||||
function closeModal() {
|
function closeModal() {
|
||||||
document.getElementById('modal').style.display = "none";
|
document.getElementById('modal').style.display = "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ────────────────────────────────────────────────────── */
|
|
||||||
/* 3️⃣ Скрипт: выбор звезды, отправка POST‑запроса */
|
|
||||||
/* ────────────────────────────────────────────────────── */
|
|
||||||
(() => {
|
|
||||||
const ratingContainer = document.querySelector('.star-rating');
|
|
||||||
const stars = Array.from(ratingContainer.querySelectorAll('.star'));
|
|
||||||
const feedback = document.getElementById('feedback');
|
|
||||||
|
|
||||||
let currentRating = 0; // 0 = пока не выбрано
|
|
||||||
|
|
||||||
// Установить визуальное состояние (цвет, aria‑checked)
|
|
||||||
function setRating(value) {
|
|
||||||
currentRating = value;
|
|
||||||
stars.forEach(star => {
|
|
||||||
const starValue = Number(star.dataset.value);
|
|
||||||
const isSelected = starValue <= value;
|
|
||||||
star.classList.toggle('selected', isSelected);
|
|
||||||
star.setAttribute('aria-checked', isSelected);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Отправка POST /vote
|
|
||||||
async function sendVote(value) {
|
|
||||||
// Подготовьте объект‑payload. Добавьте id‑токены, если нужны.
|
|
||||||
const payload = {
|
|
||||||
rate: value,
|
|
||||||
attachment_id: current_attachment_id,
|
|
||||||
beerd_name: current_beerd_name[0],
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
const resp = await fetch('/votes/do', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify(payload),
|
|
||||||
credentials: 'include' // если ваш API использует cookies
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!resp.ok) {
|
|
||||||
throw new Error(`Ошибка ${resp.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
feedback.textContent = 'Спасибо за оценку!';
|
|
||||||
feedback.style.color = '#080';
|
|
||||||
document.getElementById("rate-stars").style.display = "none";
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
feedback.textContent = 'Произошла ошибка, попробуйте позже.';
|
|
||||||
feedback.style.color = '#a00';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Обработчики клика и клавиатуры
|
|
||||||
stars.forEach(star => {
|
|
||||||
// Клик мышкой
|
|
||||||
star.addEventListener('click', () => {
|
|
||||||
const value = Number(star.dataset.value);
|
|
||||||
setRating(value);
|
|
||||||
sendVote(value);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Навигация клавиатурой: пробел/Enter выбирает звезду
|
|
||||||
star.addEventListener('keydown', e => {
|
|
||||||
if (e.key === ' ' || e.key === 'Enter') {
|
|
||||||
e.preventDefault();
|
|
||||||
const value = Number(star.dataset.value);
|
|
||||||
setRating(value);
|
|
||||||
sendVote(value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// При наведении меняем состояние всех звёзд до текущего
|
|
||||||
star.addEventListener('mouseover', () => {
|
|
||||||
setRating(Number(star.dataset.value));
|
|
||||||
});
|
|
||||||
|
|
||||||
star.addEventListener('mouseout', () => {
|
|
||||||
setRating(currentRating); // вернём к сохранённому
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// По умолчанию не показываем выбранную оценку
|
|
||||||
setRating(0);
|
|
||||||
})();
|
|
||||||
|
|
@ -340,33 +340,3 @@ input[type="text"]:hover {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
/* ────────────────────────────────────────────────────── */
|
|
||||||
/* 1️⃣ Стили для звездочек */
|
|
||||||
/* ────────────────────────────────────────────────────── */
|
|
||||||
.star-rating {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.star-rating span {
|
|
||||||
display: inline-flex;
|
|
||||||
gap: .2rem; /* отступы между звёздами */
|
|
||||||
font-size: 2rem; /* размер звёзд */
|
|
||||||
user-select: none; /* отключаем выделение текста */
|
|
||||||
}
|
|
||||||
|
|
||||||
.star {
|
|
||||||
cursor: pointer;
|
|
||||||
color: #f0f0f0; /* белый (светло‑серый) фон */
|
|
||||||
transition: color .15s ease;/* плавное изменение цвета */
|
|
||||||
}
|
|
||||||
|
|
||||||
.star.selected,
|
|
||||||
.star:hover,
|
|
||||||
.star:focus {
|
|
||||||
color: #ffd700; /* золотой при наведении/выборе */
|
|
||||||
}
|
|
||||||
|
|
||||||
.star:focus-visible { /* обводка для клавиатурного фокуса */
|
|
||||||
outline: 2px solid #333;
|
|
||||||
outline-offset: 2px;
|
|
||||||
}
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
{% block meta %}{% endblock %}
|
{% block meta %}{% endblock %}
|
||||||
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
|
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
|
||||||
<title>{% block title %}{% endblock %}</title>
|
<title>{% block title %}{% endblock %}</title>
|
||||||
<link rel="stylesheet" href="/static/styles.css?v=5">
|
<link rel="stylesheet" href="/static/styles.css?v=4">
|
||||||
<!-- Yandex.Metrika counter -->
|
<!-- Yandex.Metrika counter -->
|
||||||
<script type="text/javascript" >
|
<script type="text/javascript" >
|
||||||
(function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
|
(function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
|
||||||
|
|
@ -45,6 +45,6 @@
|
||||||
{% block form %}{% endblock %}
|
{% block form %}{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
</section>
|
</section>
|
||||||
<script src="/static/scripts.js?v=6"></script>
|
<script src="/static/scripts.js?v=8"></script>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -19,20 +19,6 @@
|
||||||
<div id="upload-image">
|
<div id="upload-image">
|
||||||
<div id="upload-image-text"></div>
|
<div id="upload-image-text"></div>
|
||||||
<img id="image" style="max-width: 200px;"/>
|
<img id="image" style="max-width: 200px;"/>
|
||||||
|
|
||||||
<!-- 2️⃣ Контейнер со звёздочками -->
|
|
||||||
<div class="star-rating" role="radiogroup" aria-label="Оценка от 1 до 5">
|
|
||||||
<h2>Оцените результат</h2>
|
|
||||||
<div id="rate-stars">
|
|
||||||
<!-- 5 звёзд – каждая имеет data-value и tabindex="0" для клавиатуры -->
|
|
||||||
<span class="star" role="radio" aria-checked="false" data-value="1" tabindex="0" aria-label="1 звезда">★</span>
|
|
||||||
<span class="star" role="radio" aria-checked="false" data-value="2" tabindex="0" aria-label="2 звезды">★</span>
|
|
||||||
<span class="star" role="radio" aria-checked="false" data-value="3" tabindex="0" aria-label="3 звезды">★</span>
|
|
||||||
<span class="star" role="radio" aria-checked="false" data-value="4" tabindex="0" aria-label="4 звезды">★</span>
|
|
||||||
<span class="star" role="radio" aria-checked="false" data-value="5" tabindex="0" aria-label="5 звёзд">★</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p id="feedback" style="margin-top:.5rem;"></p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="result"></div>
|
<div id="result"></div>
|
||||||
|
|
|
||||||
509
uv.lock
509
uv.lock
|
|
@ -12,12 +12,11 @@ name = "ai"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = { virtual = "." }
|
source = { virtual = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "aioboto3" },
|
|
||||||
{ name = "aiocache" },
|
{ name = "aiocache" },
|
||||||
{ name = "aiofiles" },
|
{ name = "aiofiles" },
|
||||||
{ name = "alembic" },
|
|
||||||
{ name = "asyncpg" },
|
{ name = "asyncpg" },
|
||||||
{ name = "betterconf" },
|
{ name = "betterconf" },
|
||||||
|
{ name = "botocore" },
|
||||||
{ name = "dataclasses-ujson" },
|
{ name = "dataclasses-ujson" },
|
||||||
{ name = "granian" },
|
{ name = "granian" },
|
||||||
{ name = "inject" },
|
{ name = "inject" },
|
||||||
|
|
@ -27,9 +26,7 @@ dependencies = [
|
||||||
{ name = "mypy" },
|
{ name = "mypy" },
|
||||||
{ name = "numpy" },
|
{ name = "numpy" },
|
||||||
{ name = "pillow" },
|
{ name = "pillow" },
|
||||||
{ name = "psycopg2-binary" },
|
|
||||||
{ name = "pydantic" },
|
{ name = "pydantic" },
|
||||||
{ name = "python-magic" },
|
|
||||||
{ name = "ruff" },
|
{ name = "ruff" },
|
||||||
{ name = "sqlalchemy" },
|
{ name = "sqlalchemy" },
|
||||||
{ name = "torch" },
|
{ name = "torch" },
|
||||||
|
|
@ -55,12 +52,11 @@ default = [
|
||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "aioboto3", specifier = ">=15.0.0" },
|
|
||||||
{ name = "aiocache" },
|
{ name = "aiocache" },
|
||||||
{ name = "aiofiles", specifier = ">=25.1.0" },
|
{ name = "aiofiles", specifier = ">=25.1.0" },
|
||||||
{ name = "alembic", specifier = ">=1.18.0" },
|
|
||||||
{ name = "asyncpg", specifier = ">=0.31.0" },
|
{ name = "asyncpg", specifier = ">=0.31.0" },
|
||||||
{ name = "betterconf", specifier = ">=4.5.0" },
|
{ name = "betterconf", specifier = ">=4.5.0" },
|
||||||
|
{ name = "botocore", specifier = ">=1.42.9" },
|
||||||
{ name = "dataclasses-ujson", specifier = ">=0.0.34" },
|
{ name = "dataclasses-ujson", specifier = ">=0.0.34" },
|
||||||
{ name = "granian", specifier = "==2.5" },
|
{ name = "granian", specifier = "==2.5" },
|
||||||
{ name = "inject", specifier = ">=5.3.0" },
|
{ name = "inject", specifier = ">=5.3.0" },
|
||||||
|
|
@ -72,10 +68,8 @@ requires-dist = [
|
||||||
{ name = "mypy", marker = "extra == 'default'", specifier = ">=1.18" },
|
{ name = "mypy", marker = "extra == 'default'", specifier = ">=1.18" },
|
||||||
{ name = "numpy", specifier = "==2.3.4" },
|
{ name = "numpy", specifier = "==2.3.4" },
|
||||||
{ name = "pillow", specifier = ">=11.1.0" },
|
{ name = "pillow", specifier = ">=11.1.0" },
|
||||||
{ name = "psycopg2-binary", specifier = ">=2.9.11" },
|
|
||||||
{ name = "pydantic", specifier = ">=2.12.4" },
|
{ name = "pydantic", specifier = ">=2.12.4" },
|
||||||
{ name = "pyqt5", marker = "extra == 'default'", specifier = ">=5.15.11" },
|
{ name = "pyqt5", marker = "extra == 'default'", specifier = ">=5.15.11" },
|
||||||
{ name = "python-magic", specifier = ">=0.4.27" },
|
|
||||||
{ name = "requests", marker = "extra == 'default'", specifier = ">=2.32.3" },
|
{ name = "requests", marker = "extra == 'default'", specifier = ">=2.32.3" },
|
||||||
{ name = "ruff", specifier = ">=0.14.5" },
|
{ name = "ruff", specifier = ">=0.14.5" },
|
||||||
{ name = "ruff", marker = "extra == 'default'", specifier = ">=0.11.5" },
|
{ name = "ruff", marker = "extra == 'default'", specifier = ">=0.11.5" },
|
||||||
|
|
@ -93,42 +87,6 @@ requires-dist = [
|
||||||
]
|
]
|
||||||
provides-extras = ["default"]
|
provides-extras = ["default"]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "aioboto3"
|
|
||||||
version = "15.5.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "aiobotocore", extra = ["boto3"] },
|
|
||||||
{ name = "aiofiles" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/a2/01/92e9ab00f36e2899315f49eefcd5b4685fbb19016c7f19a9edf06da80bb0/aioboto3-15.5.0.tar.gz", hash = "sha256:ea8d8787d315594842fbfcf2c4dce3bac2ad61be275bc8584b2ce9a3402a6979", size = 255069 }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e5/3e/e8f5b665bca646d43b916763c901e00a07e40f7746c9128bdc912a089424/aioboto3-15.5.0-py3-none-any.whl", hash = "sha256:cc880c4d6a8481dd7e05da89f41c384dbd841454fc1998ae25ca9c39201437a6", size = 35913 },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "aiobotocore"
|
|
||||||
version = "2.25.1"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "aiohttp" },
|
|
||||||
{ name = "aioitertools" },
|
|
||||||
{ name = "botocore" },
|
|
||||||
{ name = "jmespath" },
|
|
||||||
{ name = "multidict" },
|
|
||||||
{ name = "python-dateutil" },
|
|
||||||
{ name = "wrapt" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/62/94/2e4ec48cf1abb89971cb2612d86f979a6240520f0a659b53a43116d344dc/aiobotocore-2.25.1.tar.gz", hash = "sha256:ea9be739bfd7ece8864f072ec99bb9ed5c7e78ebb2b0b15f29781fbe02daedbc", size = 120560 }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/95/2a/d275ec4ce5cd0096665043995a7d76f5d0524853c76a3d04656de49f8808/aiobotocore-2.25.1-py3-none-any.whl", hash = "sha256:eb6daebe3cbef5b39a0bb2a97cffbe9c7cb46b2fcc399ad141f369f3c2134b1f", size = 86039 },
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.optional-dependencies]
|
|
||||||
boto3 = [
|
|
||||||
{ name = "boto3" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aiocache"
|
name = "aiocache"
|
||||||
version = "0.12.3"
|
version = "0.12.3"
|
||||||
|
|
@ -147,118 +105,6 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/bc/8a/340a1555ae33d7354dbca4faa54948d76d89a27ceef032c8c3bc661d003e/aiofiles-25.1.0-py3-none-any.whl", hash = "sha256:abe311e527c862958650f9438e859c1fa7568a141b22abcd015e120e86a85695", size = 14668 },
|
{ url = "https://files.pythonhosted.org/packages/bc/8a/340a1555ae33d7354dbca4faa54948d76d89a27ceef032c8c3bc661d003e/aiofiles-25.1.0-py3-none-any.whl", hash = "sha256:abe311e527c862958650f9438e859c1fa7568a141b22abcd015e120e86a85695", size = 14668 },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "aiohappyeyeballs"
|
|
||||||
version = "2.6.1"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760 }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265 },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "aiohttp"
|
|
||||||
version = "3.13.3"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "aiohappyeyeballs" },
|
|
||||||
{ name = "aiosignal" },
|
|
||||||
{ name = "attrs" },
|
|
||||||
{ name = "frozenlist" },
|
|
||||||
{ name = "multidict" },
|
|
||||||
{ name = "propcache" },
|
|
||||||
{ name = "yarl" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/50/42/32cf8e7704ceb4481406eb87161349abb46a57fee3f008ba9cb610968646/aiohttp-3.13.3.tar.gz", hash = "sha256:a949eee43d3782f2daae4f4a2819b2cb9b0c5d3b7f7a927067cc84dafdbb9f88", size = 7844556 }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/97/8a/12ca489246ca1faaf5432844adbfce7ff2cc4997733e0af120869345643a/aiohttp-3.13.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5dff64413671b0d3e7d5918ea490bdccb97a4ad29b3f311ed423200b2203e01c", size = 734190 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/32/08/de43984c74ed1fca5c014808963cc83cb00d7bb06af228f132d33862ca76/aiohttp-3.13.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:87b9aab6d6ed88235aa2970294f496ff1a1f9adcd724d800e9b952395a80ffd9", size = 491783 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/17/f8/8dd2cf6112a5a76f81f81a5130c57ca829d101ad583ce57f889179accdda/aiohttp-3.13.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:425c126c0dc43861e22cb1c14ba4c8e45d09516d0a3ae0a3f7494b79f5f233a3", size = 490704 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/6d/40/a46b03ca03936f832bc7eaa47cfbb1ad012ba1be4790122ee4f4f8cba074/aiohttp-3.13.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f9120f7093c2a32d9647abcaf21e6ad275b4fbec5b55969f978b1a97c7c86bf", size = 1720652 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f7/7e/917fe18e3607af92657e4285498f500dca797ff8c918bd7d90b05abf6c2a/aiohttp-3.13.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:697753042d57f4bf7122cab985bf15d0cef23c770864580f5af4f52023a56bd6", size = 1692014 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/71/b6/cefa4cbc00d315d68973b671cf105b21a609c12b82d52e5d0c9ae61d2a09/aiohttp-3.13.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6de499a1a44e7de70735d0b39f67c8f25eb3d91eb3103be99ca0fa882cdd987d", size = 1759777 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/fb/e3/e06ee07b45e59e6d81498b591fc589629be1553abb2a82ce33efe2a7b068/aiohttp-3.13.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:37239e9f9a7ea9ac5bf6b92b0260b01f8a22281996da609206a84df860bc1261", size = 1861276 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/7c/24/75d274228acf35ceeb2850b8ce04de9dd7355ff7a0b49d607ee60c29c518/aiohttp-3.13.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f76c1e3fe7d7c8afad7ed193f89a292e1999608170dcc9751a7462a87dfd5bc0", size = 1743131 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/04/98/3d21dde21889b17ca2eea54fdcff21b27b93f45b7bb94ca029c31ab59dc3/aiohttp-3.13.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fc290605db2a917f6e81b0e1e0796469871f5af381ce15c604a3c5c7e51cb730", size = 1556863 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/9e/84/da0c3ab1192eaf64782b03971ab4055b475d0db07b17eff925e8c93b3aa5/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4021b51936308aeea0367b8f006dc999ca02bc118a0cc78c303f50a2ff6afb91", size = 1682793 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ff/0f/5802ada182f575afa02cbd0ec5180d7e13a402afb7c2c03a9aa5e5d49060/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:49a03727c1bba9a97d3e93c9f93ca03a57300f484b6e935463099841261195d3", size = 1716676 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3f/8c/714d53bd8b5a4560667f7bbbb06b20c2382f9c7847d198370ec6526af39c/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3d9908a48eb7416dc1f4524e69f1d32e5d90e3981e4e37eb0aa1cd18f9cfa2a4", size = 1733217 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/7d/79/e2176f46d2e963facea939f5be2d26368ce543622be6f00a12844d3c991f/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2712039939ec963c237286113c68dbad80a82a4281543f3abf766d9d73228998", size = 1552303 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ab/6a/28ed4dea1759916090587d1fe57087b03e6c784a642b85ef48217b0277ae/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:7bfdc049127717581866fa4708791220970ce291c23e28ccf3922c700740fdc0", size = 1763673 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e8/35/4a3daeb8b9fab49240d21c04d50732313295e4bd813a465d840236dd0ce1/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8057c98e0c8472d8846b9c79f56766bcc57e3e8ac7bfd510482332366c56c591", size = 1721120 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/bc/9f/d643bb3c5fb99547323e635e251c609fbbc660d983144cfebec529e09264/aiohttp-3.13.3-cp313-cp313-win32.whl", hash = "sha256:1449ceddcdbcf2e0446957863af03ebaaa03f94c090f945411b61269e2cb5daf", size = 427383 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/4e/f1/ab0395f8a79933577cdd996dd2f9aa6014af9535f65dddcf88204682fe62/aiohttp-3.13.3-cp313-cp313-win_amd64.whl", hash = "sha256:693781c45a4033d31d4187d2436f5ac701e7bbfe5df40d917736108c1cc7436e", size = 453899 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/99/36/5b6514a9f5d66f4e2597e40dea2e3db271e023eb7a5d22defe96ba560996/aiohttp-3.13.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:ea37047c6b367fd4bd632bff8077449b8fa034b69e812a18e0132a00fae6e808", size = 737238 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f7/49/459327f0d5bcd8c6c9ca69e60fdeebc3622861e696490d8674a6d0cb90a6/aiohttp-3.13.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6fc0e2337d1a4c3e6acafda6a78a39d4c14caea625124817420abceed36e2415", size = 492292 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e8/0b/b97660c5fd05d3495b4eb27f2d0ef18dc1dc4eff7511a9bf371397ff0264/aiohttp-3.13.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c685f2d80bb67ca8c3837823ad76196b3694b0159d232206d1e461d3d434666f", size = 493021 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/54/d4/438efabdf74e30aeceb890c3290bbaa449780583b1270b00661126b8aae4/aiohttp-3.13.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48e377758516d262bde50c2584fc6c578af272559c409eecbdd2bae1601184d6", size = 1717263 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/71/f2/7bddc7fd612367d1459c5bcf598a9e8f7092d6580d98de0e057eb42697ad/aiohttp-3.13.3-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:34749271508078b261c4abb1767d42b8d0c0cc9449c73a4df494777dc55f0687", size = 1669107 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/00/5a/1aeaecca40e22560f97610a329e0e5efef5e0b5afdf9f857f0d93839ab2e/aiohttp-3.13.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:82611aeec80eb144416956ec85b6ca45a64d76429c1ed46ae1b5f86c6e0c9a26", size = 1760196 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f8/f8/0ff6992bea7bd560fc510ea1c815f87eedd745fe035589c71ce05612a19a/aiohttp-3.13.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2fff83cfc93f18f215896e3a190e8e5cb413ce01553901aca925176e7568963a", size = 1843591 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e3/d1/e30e537a15f53485b61f5be525f2157da719819e8377298502aebac45536/aiohttp-3.13.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bbe7d4cecacb439e2e2a8a1a7b935c25b812af7a5fd26503a66dadf428e79ec1", size = 1720277 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/84/45/23f4c451d8192f553d38d838831ebbc156907ea6e05557f39563101b7717/aiohttp-3.13.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b928f30fe49574253644b1ca44b1b8adbd903aa0da4b9054a6c20fc7f4092a25", size = 1548575 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/6a/ed/0a42b127a43712eda7807e7892c083eadfaf8429ca8fb619662a530a3aab/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7b5e8fe4de30df199155baaf64f2fcd604f4c678ed20910db8e2c66dc4b11603", size = 1679455 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/2e/b5/c05f0c2b4b4fe2c9d55e73b6d3ed4fd6c9dc2684b1d81cbdf77e7fad9adb/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:8542f41a62bcc58fc7f11cf7c90e0ec324ce44950003feb70640fc2a9092c32a", size = 1687417 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c9/6b/915bc5dad66aef602b9e459b5a973529304d4e89ca86999d9d75d80cbd0b/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5e1d8c8b8f1d91cd08d8f4a3c2b067bfca6ec043d3ff36de0f3a715feeedf926", size = 1729968 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/11/3b/e84581290a9520024a08640b63d07673057aec5ca548177a82026187ba73/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:90455115e5da1c3c51ab619ac57f877da8fd6d73c05aacd125c5ae9819582aba", size = 1545690 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f5/04/0c3655a566c43fd647c81b895dfe361b9f9ad6d58c19309d45cff52d6c3b/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:042e9e0bcb5fba81886c8b4fbb9a09d6b8a00245fd8d88e4d989c1f96c74164c", size = 1746390 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/1f/53/71165b26978f719c3419381514c9690bd5980e764a09440a10bb816ea4ab/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2eb752b102b12a76ca02dff751a801f028b4ffbbc478840b473597fc91a9ed43", size = 1702188 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/29/a7/cbe6c9e8e136314fa1980da388a59d2f35f35395948a08b6747baebb6aa6/aiohttp-3.13.3-cp314-cp314-win32.whl", hash = "sha256:b556c85915d8efaed322bf1bdae9486aa0f3f764195a0fb6ee962e5c71ef5ce1", size = 433126 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/de/56/982704adea7d3b16614fc5936014e9af85c0e34b58f9046655817f04306e/aiohttp-3.13.3-cp314-cp314-win_amd64.whl", hash = "sha256:9bf9f7a65e7aa20dd764151fb3d616c81088f91f8df39c3893a536e279b4b984", size = 459128 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/6c/2a/3c79b638a9c3d4658d345339d22070241ea341ed4e07b5ac60fb0f418003/aiohttp-3.13.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:05861afbbec40650d8a07ea324367cb93e9e8cc7762e04dd4405df99fa65159c", size = 769512 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/29/b9/3e5014d46c0ab0db8707e0ac2711ed28c4da0218c358a4e7c17bae0d8722/aiohttp-3.13.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2fc82186fadc4a8316768d61f3722c230e2c1dcab4200d52d2ebdf2482e47592", size = 506444 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/90/03/c1d4ef9a054e151cd7839cdc497f2638f00b93cbe8043983986630d7a80c/aiohttp-3.13.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0add0900ff220d1d5c5ebbf99ed88b0c1bbf87aa7e4262300ed1376a6b13414f", size = 510798 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ea/76/8c1e5abbfe8e127c893fe7ead569148a4d5a799f7cf958d8c09f3eedf097/aiohttp-3.13.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:568f416a4072fbfae453dcf9a99194bbb8bdeab718e08ee13dfa2ba0e4bebf29", size = 1868835 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/8e/ac/984c5a6f74c363b01ff97adc96a3976d9c98940b8969a1881575b279ac5d/aiohttp-3.13.3-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:add1da70de90a2569c5e15249ff76a631ccacfe198375eead4aadf3b8dc849dc", size = 1720486 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b2/9a/b7039c5f099c4eb632138728828b33428585031a1e658d693d41d07d89d1/aiohttp-3.13.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:10b47b7ba335d2e9b1239fa571131a87e2d8ec96b333e68b2a305e7a98b0bae2", size = 1847951 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3c/02/3bec2b9a1ba3c19ff89a43a19324202b8eb187ca1e928d8bdac9bbdddebd/aiohttp-3.13.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3dd4dce1c718e38081c8f35f323209d4c1df7d4db4bab1b5c88a6b4d12b74587", size = 1941001 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/37/df/d879401cedeef27ac4717f6426c8c36c3091c6e9f08a9178cc87549c537f/aiohttp-3.13.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34bac00a67a812570d4a460447e1e9e06fae622946955f939051e7cc895cfab8", size = 1797246 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/8d/15/be122de1f67e6953add23335c8ece6d314ab67c8bebb3f181063010795a7/aiohttp-3.13.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a19884d2ee70b06d9204b2727a7b9f983d0c684c650254679e716b0b77920632", size = 1627131 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/12/12/70eedcac9134cfa3219ab7af31ea56bc877395b1ac30d65b1bc4b27d0438/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5f8ca7f2bb6ba8348a3614c7918cc4bb73268c5ac2a207576b7afea19d3d9f64", size = 1795196 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/32/11/b30e1b1cd1f3054af86ebe60df96989c6a414dd87e27ad16950eee420bea/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:b0d95340658b9d2f11d9697f59b3814a9d3bb4b7a7c20b131df4bcef464037c0", size = 1782841 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/88/0d/d98a9367b38912384a17e287850f5695c528cff0f14f791ce8ee2e4f7796/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:a1e53262fd202e4b40b70c3aff944a8155059beedc8a89bba9dc1f9ef06a1b56", size = 1795193 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/43/a5/a2dfd1f5ff5581632c7f6a30e1744deda03808974f94f6534241ef60c751/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:d60ac9663f44168038586cab2157e122e46bdef09e9368b37f2d82d354c23f72", size = 1621979 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/fa/f0/12973c382ae7c1cccbc4417e129c5bf54c374dfb85af70893646e1f0e749/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:90751b8eed69435bac9ff4e3d2f6b3af1f57e37ecb0fbeee59c0174c9e2d41df", size = 1822193 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3c/5f/24155e30ba7f8c96918af1350eb0663e2430aad9e001c0489d89cd708ab1/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fc353029f176fd2b3ec6cfc71be166aba1936fe5d73dd1992ce289ca6647a9aa", size = 1769801 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/eb/f8/7314031ff5c10e6ece114da79b338ec17eeff3a079e53151f7e9f43c4723/aiohttp-3.13.3-cp314-cp314t-win32.whl", hash = "sha256:2e41b18a58da1e474a057b3d35248d8320029f61d70a37629535b16a0c8f3767", size = 466523 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b4/63/278a98c715ae467624eafe375542d8ba9b4383a016df8fdefe0ae28382a7/aiohttp-3.13.3-cp314-cp314t-win_amd64.whl", hash = "sha256:44531a36aa2264a1860089ffd4dce7baf875ee5a6079d5fb42e261c704ef7344", size = 499694 },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "aioitertools"
|
|
||||||
version = "0.13.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/fd/3c/53c4a17a05fb9ea2313ee1777ff53f5e001aefd5cc85aa2f4c2d982e1e38/aioitertools-0.13.0.tar.gz", hash = "sha256:620bd241acc0bbb9ec819f1ab215866871b4bbd1f73836a55f799200ee86950c", size = 19322 }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/10/a1/510b0a7fadc6f43a6ce50152e69dbd86415240835868bb0bd9b5b88b1e06/aioitertools-0.13.0-py3-none-any.whl", hash = "sha256:0be0292b856f08dfac90e31f4739432f4cb6d7520ab9eb73e143f4f2fa5259be", size = 24182 },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "aiosignal"
|
|
||||||
version = "1.4.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "frozenlist" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007 }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490 },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "alembic"
|
|
||||||
version = "1.18.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "mako" },
|
|
||||||
{ name = "sqlalchemy" },
|
|
||||||
{ name = "typing-extensions" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/70/a5/57f989c26c078567a08f1d88c337acfcb69c8c9cac6876a34054f35b8112/alembic-1.18.0.tar.gz", hash = "sha256:0c4c03c927dc54d4c56821bdcc988652f4f63bf7b9017fd9d78d63f09fd22b48", size = 2043788 }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ed/fd/68773667babd452fb48f974c4c1f6e6852c6e41bcf622c745faca1b06605/alembic-1.18.0-py3-none-any.whl", hash = "sha256:3993fcfbc371aa80cdcf13f928b7da21b1c9f783c914f03c3c6375f58efd9250", size = 260967 },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "annotated-types"
|
name = "annotated-types"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
|
|
@ -313,15 +159,6 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/3c/d7/8fb3044eaef08a310acfe23dae9a8e2e07d305edc29a53497e52bc76eca7/asyncpg-0.31.0-cp314-cp314t-win_amd64.whl", hash = "sha256:bd4107bb7cdd0e9e65fae66a62afd3a249663b844fa34d479f6d5b3bef9c04c3", size = 706062 },
|
{ url = "https://files.pythonhosted.org/packages/3c/d7/8fb3044eaef08a310acfe23dae9a8e2e07d305edc29a53497e52bc76eca7/asyncpg-0.31.0-cp314-cp314t-win_amd64.whl", hash = "sha256:bd4107bb7cdd0e9e65fae66a62afd3a249663b844fa34d479f6d5b3bef9c04c3", size = 706062 },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "attrs"
|
|
||||||
version = "25.4.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251 }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615 },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "betterconf"
|
name = "betterconf"
|
||||||
version = "4.5.0"
|
version = "4.5.0"
|
||||||
|
|
@ -331,32 +168,18 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/18/3c/86921e4e407f25413819e04471606c32377f47887af7873e0613f5036946/betterconf-4.5.0-py3-none-any.whl", hash = "sha256:ee6ad8ae4c49a7f977555dcb435eb258eb044e10a2f43fdd77bb67b8ff682118", size = 11769 },
|
{ url = "https://files.pythonhosted.org/packages/18/3c/86921e4e407f25413819e04471606c32377f47887af7873e0613f5036946/betterconf-4.5.0-py3-none-any.whl", hash = "sha256:ee6ad8ae4c49a7f977555dcb435eb258eb044e10a2f43fdd77bb67b8ff682118", size = 11769 },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "boto3"
|
|
||||||
version = "1.40.61"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "botocore" },
|
|
||||||
{ name = "jmespath" },
|
|
||||||
{ name = "s3transfer" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/ed/f9/6ef8feb52c3cce5ec3967a535a6114b57ac7949fd166b0f3090c2b06e4e5/boto3-1.40.61.tar.gz", hash = "sha256:d6c56277251adf6c2bdd25249feae625abe4966831676689ff23b4694dea5b12", size = 111535 }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/61/24/3bf865b07d15fea85b63504856e137029b6acbc73762496064219cdb265d/boto3-1.40.61-py3-none-any.whl", hash = "sha256:6b9c57b2a922b5d8c17766e29ed792586a818098efe84def27c8f582b33f898c", size = 139321 },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "botocore"
|
name = "botocore"
|
||||||
version = "1.40.61"
|
version = "1.42.9"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "jmespath" },
|
{ name = "jmespath" },
|
||||||
{ name = "python-dateutil" },
|
{ name = "python-dateutil" },
|
||||||
{ name = "urllib3" },
|
{ name = "urllib3" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/28/a3/81d3a47c2dbfd76f185d3b894f2ad01a75096c006a2dd91f237dca182188/botocore-1.40.61.tar.gz", hash = "sha256:a2487ad69b090f9cccd64cf07c7021cd80ee9c0655ad974f87045b02f3ef52cd", size = 14393956 }
|
sdist = { url = "https://files.pythonhosted.org/packages/fd/f3/2d2cfb500e2dc00b0e33e3c8743306e6330f3cf219d19e9260dab2f3d6c2/botocore-1.42.9.tar.gz", hash = "sha256:74f69bfd116cc7c8215481284957eecdb48580e071dd50cb8c64356a866abd8c", size = 14861916 }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/38/c5/f6ce561004db45f0b847c2cd9b19c67c6bf348a82018a48cb718be6b58b0/botocore-1.40.61-py3-none-any.whl", hash = "sha256:17ebae412692fd4824f99cde0f08d50126dc97954008e5ba2b522eb049238aa7", size = 14055973 },
|
{ url = "https://files.pythonhosted.org/packages/1f/2a/e9275f40042f7a09915c4be86b092cb02dc4bd74e77ab8864f485d998af1/botocore-1.42.9-py3-none-any.whl", hash = "sha256:f99ba2ca34e24c4ebec150376c815646970753c032eb84f230874b2975a185a8", size = 14537810 },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -558,79 +381,6 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/c7/93/0dd45cd283c32dea1545151d8c3637b4b8c53cdb3a625aeb2885b184d74d/fonttools-4.60.1-py3-none-any.whl", hash = "sha256:906306ac7afe2156fcf0042173d6ebbb05416af70f6b370967b47f8f00103bbb", size = 1143175 },
|
{ url = "https://files.pythonhosted.org/packages/c7/93/0dd45cd283c32dea1545151d8c3637b4b8c53cdb3a625aeb2885b184d74d/fonttools-4.60.1-py3-none-any.whl", hash = "sha256:906306ac7afe2156fcf0042173d6ebbb05416af70f6b370967b47f8f00103bbb", size = 1143175 },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "frozenlist"
|
|
||||||
version = "1.8.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875 }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409 },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fsspec"
|
name = "fsspec"
|
||||||
version = "2025.10.0"
|
version = "2025.10.0"
|
||||||
|
|
@ -885,18 +635,6 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/f2/24/8d99982f0aa9c1cd82073c6232b54a0dbe6797c7d63c0583a6c68ee3ddf2/litestar_htmx-0.5.0-py3-none-any.whl", hash = "sha256:92833aa47e0d0e868d2a7dbfab75261f124f4b83d4f9ad12b57b9a68f86c50e6", size = 9970 },
|
{ url = "https://files.pythonhosted.org/packages/f2/24/8d99982f0aa9c1cd82073c6232b54a0dbe6797c7d63c0583a6c68ee3ddf2/litestar_htmx-0.5.0-py3-none-any.whl", hash = "sha256:92833aa47e0d0e868d2a7dbfab75261f124f4b83d4f9ad12b57b9a68f86c50e6", size = 9970 },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "mako"
|
|
||||||
version = "1.3.10"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "markupsafe" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/9e/38/bd5b78a920a64d708fe6bc8e0a2c075e1389d53bef8413725c63ba041535/mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28", size = 392474 }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/87/fb/99f81ac72ae23375f22b7afdb7642aba97c00a713c217124420147681a2f/mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", size = 78509 },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "markdown"
|
name = "markdown"
|
||||||
version = "3.10"
|
version = "3.10"
|
||||||
|
|
@ -1459,105 +1197,6 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/11/87/6cd69c43e86bc5863f93dba3c9fb42ee4fda36a0fe5aa3cbd25001fb26f8/polyfactory-2.22.5-py3-none-any.whl", hash = "sha256:822d1af463520153200b4b62b06b0dc73a1d5edc8911e2e63ed0757ae21cc2b3", size = 63934 },
|
{ url = "https://files.pythonhosted.org/packages/11/87/6cd69c43e86bc5863f93dba3c9fb42ee4fda36a0fe5aa3cbd25001fb26f8/polyfactory-2.22.5-py3-none-any.whl", hash = "sha256:822d1af463520153200b4b62b06b0dc73a1d5edc8911e2e63ed0757ae21cc2b3", size = 63934 },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "propcache"
|
|
||||||
version = "0.4.1"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442 }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/bf/df/6d9c1b6ac12b003837dde8a10231a7344512186e87b36e855bef32241942/propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf", size = 77750 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311", size = 44780 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74", size = 46308 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/2d/48/c5ac64dee5262044348d1d78a5f85dd1a57464a60d30daee946699963eb3/propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe", size = 208182 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c6/0c/cd762dd011a9287389a6a3eb43aa30207bde253610cca06824aeabfe9653/propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af", size = 211215 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/30/3e/49861e90233ba36890ae0ca4c660e95df565b2cd15d4a68556ab5865974e/propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c", size = 218112 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f", size = 204442 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/50/a6/4282772fd016a76d3e5c0df58380a5ea64900afd836cec2c2f662d1b9bb3/propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1", size = 199398 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3e/ec/d8a7cd406ee1ddb705db2139f8a10a8a427100347bd698e7014351c7af09/propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24", size = 196920 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f6/6c/f38ab64af3764f431e359f8baf9e0a21013e24329e8b85d2da32e8ed07ca/propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa", size = 203748 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d6/e3/fa846bd70f6534d647886621388f0a265254d30e3ce47e5c8e6e27dbf153/propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61", size = 205877 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e2/39/8163fc6f3133fea7b5f2827e8eba2029a0277ab2c5beee6c1db7b10fc23d/propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66", size = 199437 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/93/89/caa9089970ca49c7c01662bd0eeedfe85494e863e8043565aeb6472ce8fe/propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81", size = 37586 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e", size = 40790 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/59/1b/e71ae98235f8e2ba5004d8cb19765a74877abf189bc53fc0c80d799e56c3/propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1", size = 37158 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/83/ce/a31bbdfc24ee0dcbba458c8175ed26089cf109a55bbe7b7640ed2470cfe9/propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b", size = 81451 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/25/9c/442a45a470a68456e710d96cacd3573ef26a1d0a60067e6a7d5e655621ed/propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566", size = 46374 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f4/bf/b1d5e21dbc3b2e889ea4327044fb16312a736d97640fb8b6aa3f9c7b3b65/propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835", size = 48396 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f4/04/5b4c54a103d480e978d3c8a76073502b18db0c4bc17ab91b3cb5092ad949/propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e", size = 275950 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b4/c1/86f846827fb969c4b78b0af79bba1d1ea2156492e1b83dea8b8a6ae27395/propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859", size = 273856 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/36/1d/fc272a63c8d3bbad6878c336c7a7dea15e8f2d23a544bda43205dfa83ada/propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b", size = 280420 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/07/0c/01f2219d39f7e53d52e5173bcb09c976609ba30209912a0680adfb8c593a/propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0", size = 263254 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/2d/18/cd28081658ce597898f0c4d174d4d0f3c5b6d4dc27ffafeef835c95eb359/propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af", size = 261205 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/7a/71/1f9e22eb8b8316701c2a19fa1f388c8a3185082607da8e406a803c9b954e/propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393", size = 247873 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/4a/65/3d4b61f36af2b4eddba9def857959f1016a51066b4f1ce348e0cf7881f58/propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874", size = 262739 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/2a/42/26746ab087faa77c1c68079b228810436ccd9a5ce9ac85e2b7307195fd06/propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7", size = 263514 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/94/13/630690fe201f5502d2403dd3cfd451ed8858fe3c738ee88d095ad2ff407b/propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1", size = 257781 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/92/f7/1d4ec5841505f423469efbfc381d64b7b467438cd5a4bbcbb063f3b73d27/propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717", size = 41396 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/48/f0/615c30622316496d2cbbc29f5985f7777d3ada70f23370608c1d3e081c1f/propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37", size = 44897 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/fd/ca/6002e46eccbe0e33dcd4069ef32f7f1c9e243736e07adca37ae8c4830ec3/propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a", size = 39789 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/8e/5c/bca52d654a896f831b8256683457ceddd490ec18d9ec50e97dfd8fc726a8/propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12", size = 78152 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/65/9b/03b04e7d82a5f54fb16113d839f5ea1ede58a61e90edf515f6577c66fa8f/propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c", size = 44869 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b2/fa/89a8ef0468d5833a23fff277b143d0573897cf75bd56670a6d28126c7d68/propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded", size = 46596 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/86/bd/47816020d337f4a746edc42fe8d53669965138f39ee117414c7d7a340cfe/propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641", size = 206981 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/df/f6/c5fa1357cc9748510ee55f37173eb31bfde6d94e98ccd9e6f033f2fc06e1/propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4", size = 211490 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/80/1e/e5889652a7c4a3846683401a48f0f2e5083ce0ec1a8a5221d8058fbd1adf/propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44", size = 215371 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b2/f2/889ad4b2408f72fe1a4f6a19491177b30ea7bf1a0fd5f17050ca08cfc882/propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d", size = 201424 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/27/73/033d63069b57b0812c8bd19f311faebeceb6ba31b8f32b73432d12a0b826/propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b", size = 197566 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/dc/89/ce24f3dc182630b4e07aa6d15f0ff4b14ed4b9955fae95a0b54c58d66c05/propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e", size = 193130 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a9/24/ef0d5fd1a811fb5c609278d0209c9f10c35f20581fcc16f818da959fc5b4/propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f", size = 202625 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f5/02/98ec20ff5546f68d673df2f7a69e8c0d076b5abd05ca882dc7ee3a83653d/propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49", size = 204209 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a0/87/492694f76759b15f0467a2a93ab68d32859672b646aa8a04ce4864e7932d/propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144", size = 197797 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ee/36/66367de3575db1d2d3f3d177432bd14ee577a39d3f5d1b3d5df8afe3b6e2/propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f", size = 38140 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/0c/2a/a758b47de253636e1b8aef181c0b4f4f204bf0dd964914fb2af90a95b49b/propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153", size = 41257 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/34/5e/63bd5896c3fec12edcbd6f12508d4890d23c265df28c74b175e1ef9f4f3b/propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992", size = 38097 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/99/85/9ff785d787ccf9bbb3f3106f79884a130951436f58392000231b4c737c80/propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f", size = 81455 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/90/85/2431c10c8e7ddb1445c1f7c4b54d886e8ad20e3c6307e7218f05922cad67/propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393", size = 46372 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/01/20/b0972d902472da9bcb683fa595099911f4d2e86e5683bcc45de60dd05dc3/propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0", size = 48411 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e2/e3/7dc89f4f21e8f99bad3d5ddb3a3389afcf9da4ac69e3deb2dcdc96e74169/propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a", size = 275712 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/20/67/89800c8352489b21a8047c773067644e3897f02ecbbd610f4d46b7f08612/propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be", size = 273557 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e2/a1/b52b055c766a54ce6d9c16d9aca0cad8059acd9637cdf8aa0222f4a026ef/propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc", size = 280015 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/48/c8/33cee30bd890672c63743049f3c9e4be087e6780906bfc3ec58528be59c1/propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a", size = 262880 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/0c/b1/8f08a143b204b418285c88b83d00edbd61afbc2c6415ffafc8905da7038b/propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89", size = 260938 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/cf/12/96e4664c82ca2f31e1c8dff86afb867348979eb78d3cb8546a680287a1e9/propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726", size = 247641 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/18/ed/e7a9cfca28133386ba52278136d42209d3125db08d0a6395f0cba0c0285c/propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367", size = 262510 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f5/76/16d8bf65e8845dd62b4e2b57444ab81f07f40caa5652b8969b87ddcf2ef6/propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36", size = 263161 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e7/70/c99e9edb5d91d5ad8a49fa3c1e8285ba64f1476782fed10ab251ff413ba1/propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455", size = 257393 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/08/02/87b25304249a35c0915d236575bc3574a323f60b47939a2262b77632a3ee/propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85", size = 42546 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/cb/ef/3c6ecf8b317aa982f309835e8f96987466123c6e596646d4e6a1dfcd080f/propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1", size = 46259 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c4/2d/346e946d4951f37eca1e4f55be0f0174c52cd70720f84029b02f296f4a38/propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9", size = 40428 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305 },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "psycopg2-binary"
|
|
||||||
version = "2.9.11"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/ac/6c/8767aaa597ba424643dc87348c6f1754dd9f48e80fdc1b9f7ca5c3a7c213/psycopg2-binary-2.9.11.tar.gz", hash = "sha256:b6aed9e096bf63f9e75edf2581aa9a7e7186d97ab5c177aa6c87797cd591236c", size = 379620 }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ff/a8/a2709681b3ac11b0b1786def10006b8995125ba268c9a54bea6f5ae8bd3e/psycopg2_binary-2.9.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b8fb3db325435d34235b044b199e56cdf9ff41223a4b9752e8576465170bb38c", size = 3756572 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/62/e1/c2b38d256d0dafd32713e9f31982a5b028f4a3651f446be70785f484f472/psycopg2_binary-2.9.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:366df99e710a2acd90efed3764bb1e28df6c675d33a7fb40df9b7281694432ee", size = 3864529 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/11/32/b2ffe8f3853c181e88f0a157c5fb4e383102238d73c52ac6d93a5c8bffe6/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c55b385daa2f92cb64b12ec4536c66954ac53654c7f15a203578da4e78105c0", size = 4411242 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/10/04/6ca7477e6160ae258dc96f67c371157776564679aefd247b66f4661501a2/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c0377174bf1dd416993d16edc15357f6eb17ac998244cca19bc67cdc0e2e5766", size = 4468258 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3c/7e/6a1a38f86412df101435809f225d57c1a021307dd0689f7a5e7fe83588b1/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5c6ff3335ce08c75afaed19e08699e8aacf95d4a260b495a4a8545244fe2ceb3", size = 4166295 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f2/7d/c07374c501b45f3579a9eb761cbf2604ddef3d96ad48679112c2c5aa9c25/psycopg2_binary-2.9.11-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:84011ba3109e06ac412f95399b704d3d6950e386b7994475b231cf61eec2fc1f", size = 3983133 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/82/56/993b7104cb8345ad7d4516538ccf8f0d0ac640b1ebd8c754a7b024e76878/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ba34475ceb08cccbdd98f6b46916917ae6eeb92b5ae111df10b544c3a4621dc4", size = 3652383 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/2d/ac/eaeb6029362fd8d454a27374d84c6866c82c33bfc24587b4face5a8e43ef/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b31e90fdd0f968c2de3b26ab014314fe814225b6c324f770952f7d38abf17e3c", size = 3298168 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/2b/39/50c3facc66bded9ada5cbc0de867499a703dc6bca6be03070b4e3b65da6c/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:d526864e0f67f74937a8fce859bd56c979f5e2ec57ca7c627f5f1071ef7fee60", size = 3044712 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/9c/8e/b7de019a1f562f72ada81081a12823d3c1590bedc48d7d2559410a2763fe/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04195548662fa544626c8ea0f06561eb6203f1984ba5b4562764fbeb4c3d14b1", size = 3347549 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/80/2d/1bb683f64737bbb1f86c82b7359db1eb2be4e2c0c13b947f80efefa7d3e5/psycopg2_binary-2.9.11-cp313-cp313-win_amd64.whl", hash = "sha256:efff12b432179443f54e230fdf60de1f6cc726b6c832db8701227d089310e8aa", size = 2714215 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/64/12/93ef0098590cf51d9732b4f139533732565704f45bdc1ffa741b7c95fb54/psycopg2_binary-2.9.11-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:92e3b669236327083a2e33ccfa0d320dd01b9803b3e14dd986a4fc54aa00f4e1", size = 3756567 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/7c/a9/9d55c614a891288f15ca4b5209b09f0f01e3124056924e17b81b9fa054cc/psycopg2_binary-2.9.11-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e0deeb03da539fa3577fcb0b3f2554a97f7e5477c246098dbb18091a4a01c16f", size = 3864755 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/13/1e/98874ce72fd29cbde93209977b196a2edae03f8490d1bd8158e7f1daf3a0/psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9b52a3f9bb540a3e4ec0f6ba6d31339727b2950c9772850d6545b7eae0b9d7c5", size = 4411646 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/5a/bd/a335ce6645334fb8d758cc358810defca14a1d19ffbc8a10bd38a2328565/psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:db4fd476874ccfdbb630a54426964959e58da4c61c9feba73e6094d51303d7d8", size = 4468701 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/44/d6/c8b4f53f34e295e45709b7568bf9b9407a612ea30387d35eb9fa84f269b4/psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:47f212c1d3be608a12937cc131bd85502954398aaa1320cb4c14421a0ffccf4c", size = 4166293 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/4b/e0/f8cc36eadd1b716ab36bb290618a3292e009867e5c97ce4aba908cb99644/psycopg2_binary-2.9.11-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e35b7abae2b0adab776add56111df1735ccc71406e56203515e228a8dc07089f", size = 3983184 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/53/3e/2a8fe18a4e61cfb3417da67b6318e12691772c0696d79434184a511906dc/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fcf21be3ce5f5659daefd2b3b3b6e4727b028221ddc94e6c1523425579664747", size = 3652650 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/76/36/03801461b31b29fe58d228c24388f999fe814dfc302856e0d17f97d7c54d/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:9bd81e64e8de111237737b29d68039b9c813bdf520156af36d26819c9a979e5f", size = 3298663 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/97/77/21b0ea2e1a73aa5fa9222b2a6b8ba325c43c3a8d54272839c991f2345656/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:32770a4d666fbdafab017086655bcddab791d7cb260a16679cc5a7338b64343b", size = 3044737 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/67/69/f36abe5f118c1dca6d3726ceae164b9356985805480731ac6712a63f24f0/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c3cb3a676873d7506825221045bd70e0427c905b9c8ee8d6acd70cfcbd6e576d", size = 3347643 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e1/36/9c0c326fe3a4227953dfb29f5d0c8ae3b8eb8c1cd2967aa569f50cb3c61f/psycopg2_binary-2.9.11-cp314-cp314-win_amd64.whl", hash = "sha256:4012c9c954dfaccd28f94e84ab9f94e12df76b4afb22331b1f0d3154893a6316", size = 2803913 },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pydantic"
|
name = "pydantic"
|
||||||
version = "2.12.4"
|
version = "2.12.4"
|
||||||
|
|
@ -1699,15 +1338,6 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 },
|
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "python-magic"
|
|
||||||
version = "0.4.27"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/da/db/0b3e28ac047452d079d375ec6798bf76a036a08182dbb39ed38116a49130/python-magic-0.4.27.tar.gz", hash = "sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b", size = 14677 }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/6c/73/9f872cb81fc5c3bb48f7227872c28975f998f3e7c2b1c16e95e6432bbb90/python_magic-0.4.27-py2.py3-none-any.whl", hash = "sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3", size = 13840 },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyyaml"
|
name = "pyyaml"
|
||||||
version = "6.0.3"
|
version = "6.0.3"
|
||||||
|
|
@ -1812,18 +1442,6 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/e5/80/69756670caedcf3b9be597a6e12276a6cf6197076eb62aad0c608f8efce0/ruff-0.14.5-py3-none-win_arm64.whl", hash = "sha256:4b700459d4649e2594b31f20a9de33bc7c19976d4746d8d0798ad959621d64a4", size = 13433331 },
|
{ url = "https://files.pythonhosted.org/packages/e5/80/69756670caedcf3b9be597a6e12276a6cf6197076eb62aad0c608f8efce0/ruff-0.14.5-py3-none-win_arm64.whl", hash = "sha256:4b700459d4649e2594b31f20a9de33bc7c19976d4746d8d0798ad959621d64a4", size = 13433331 },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "s3transfer"
|
|
||||||
version = "0.14.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "botocore" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/62/74/8d69dcb7a9efe8baa2046891735e5dfe433ad558ae23d9e3c14c633d1d58/s3transfer-0.14.0.tar.gz", hash = "sha256:eff12264e7c8b4985074ccce27a3b38a485bb7f7422cc8046fee9be4983e4125", size = 151547 }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/48/f0/ae7ca09223a81a1d890b2557186ea015f6e0502e9b8cb8e1813f1d8cfa4e/s3transfer-0.14.0-py3-none-any.whl", hash = "sha256:ea3b790c7077558ed1f02a3072fb3cb992bbbd253392f4b6e9e8976941c7d456", size = 85712 },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "setuptools"
|
name = "setuptools"
|
||||||
version = "80.9.0"
|
version = "80.9.0"
|
||||||
|
|
@ -2102,120 +1720,3 @@ sdist = { url = "https://files.pythonhosted.org/packages/cb/ce/f06b84e2697fef468
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/ee/d9/d88e73ca598f4f6ff671fb5fde8a32925c2e08a637303a1d12883c7305fa/uvicorn-0.38.0-py3-none-any.whl", hash = "sha256:48c0afd214ceb59340075b4a052ea1ee91c16fbc2a9b1469cca0e54566977b02", size = 68109 },
|
{ url = "https://files.pythonhosted.org/packages/ee/d9/d88e73ca598f4f6ff671fb5fde8a32925c2e08a637303a1d12883c7305fa/uvicorn-0.38.0-py3-none-any.whl", hash = "sha256:48c0afd214ceb59340075b4a052ea1ee91c16fbc2a9b1469cca0e54566977b02", size = 68109 },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wrapt"
|
|
||||||
version = "1.17.3"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/95/8f/aeb76c5b46e273670962298c23e7ddde79916cb74db802131d49a85e4b7d/wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0", size = 55547 }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/fc/f6/759ece88472157acb55fc195e5b116e06730f1b651b5b314c66291729193/wrapt-1.17.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0", size = 54003 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/4f/a9/49940b9dc6d47027dc850c116d79b4155f15c08547d04db0f07121499347/wrapt-1.17.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77", size = 39025 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/45/35/6a08de0f2c96dcdd7fe464d7420ddb9a7655a6561150e5fc4da9356aeaab/wrapt-1.17.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7", size = 39108 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/0c/37/6faf15cfa41bf1f3dba80cd3f5ccc6622dfccb660ab26ed79f0178c7497f/wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277", size = 88072 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/78/f2/efe19ada4a38e4e15b6dff39c3e3f3f73f5decf901f66e6f72fe79623a06/wrapt-1.17.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d", size = 88214 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/40/90/ca86701e9de1622b16e09689fc24b76f69b06bb0150990f6f4e8b0eeb576/wrapt-1.17.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa", size = 87105 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/fd/e0/d10bd257c9a3e15cbf5523025252cc14d77468e8ed644aafb2d6f54cb95d/wrapt-1.17.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050", size = 87766 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e8/cf/7d848740203c7b4b27eb55dbfede11aca974a51c3d894f6cc4b865f42f58/wrapt-1.17.3-cp313-cp313-win32.whl", hash = "sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8", size = 36711 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/57/54/35a84d0a4d23ea675994104e667ceff49227ce473ba6a59ba2c84f250b74/wrapt-1.17.3-cp313-cp313-win_amd64.whl", hash = "sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb", size = 38885 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/01/77/66e54407c59d7b02a3c4e0af3783168fff8e5d61def52cda8728439d86bc/wrapt-1.17.3-cp313-cp313-win_arm64.whl", hash = "sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16", size = 36896 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/02/a2/cd864b2a14f20d14f4c496fab97802001560f9f41554eef6df201cd7f76c/wrapt-1.17.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cf30f6e3c077c8e6a9a7809c94551203c8843e74ba0c960f4a98cd80d4665d39", size = 54132 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d5/46/d011725b0c89e853dc44cceb738a307cde5d240d023d6d40a82d1b4e1182/wrapt-1.17.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e228514a06843cae89621384cfe3a80418f3c04aadf8a3b14e46a7be704e4235", size = 39091 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/2e/9e/3ad852d77c35aae7ddebdbc3b6d35ec8013af7d7dddad0ad911f3d891dae/wrapt-1.17.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5ea5eb3c0c071862997d6f3e02af1d055f381b1d25b286b9d6644b79db77657c", size = 39172 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c3/f7/c983d2762bcce2326c317c26a6a1e7016f7eb039c27cdf5c4e30f4160f31/wrapt-1.17.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:281262213373b6d5e4bb4353bc36d1ba4084e6d6b5d242863721ef2bf2c2930b", size = 87163 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e4/0f/f673f75d489c7f22d17fe0193e84b41540d962f75fce579cf6873167c29b/wrapt-1.17.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4a8d2b25efb6681ecacad42fca8859f88092d8732b170de6a5dddd80a1c8fa", size = 87963 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/df/61/515ad6caca68995da2fac7a6af97faab8f78ebe3bf4f761e1b77efbc47b5/wrapt-1.17.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:373342dd05b1d07d752cecbec0c41817231f29f3a89aa8b8843f7b95992ed0c7", size = 86945 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d3/bd/4e70162ce398462a467bc09e768bee112f1412e563620adc353de9055d33/wrapt-1.17.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d40770d7c0fd5cbed9d84b2c3f2e156431a12c9a37dc6284060fb4bec0b7ffd4", size = 86857 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/2b/b8/da8560695e9284810b8d3df8a19396a6e40e7518059584a1a394a2b35e0a/wrapt-1.17.3-cp314-cp314-win32.whl", hash = "sha256:fbd3c8319de8e1dc79d346929cd71d523622da527cca14e0c1d257e31c2b8b10", size = 37178 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/db/c8/b71eeb192c440d67a5a0449aaee2310a1a1e8eca41676046f99ed2487e9f/wrapt-1.17.3-cp314-cp314-win_amd64.whl", hash = "sha256:e1a4120ae5705f673727d3253de3ed0e016f7cd78dc463db1b31e2463e1f3cf6", size = 39310 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/45/20/2cda20fd4865fa40f86f6c46ed37a2a8356a7a2fde0773269311f2af56c7/wrapt-1.17.3-cp314-cp314-win_arm64.whl", hash = "sha256:507553480670cab08a800b9463bdb881b2edeed77dc677b0a5915e6106e91a58", size = 37266 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/77/ed/dd5cf21aec36c80443c6f900449260b80e2a65cf963668eaef3b9accce36/wrapt-1.17.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ed7c635ae45cfbc1a7371f708727bf74690daedc49b4dba310590ca0bd28aa8a", size = 56544 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/8d/96/450c651cc753877ad100c7949ab4d2e2ecc4d97157e00fa8f45df682456a/wrapt-1.17.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:249f88ed15503f6492a71f01442abddd73856a0032ae860de6d75ca62eed8067", size = 40283 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d1/86/2fcad95994d9b572db57632acb6f900695a648c3e063f2cd344b3f5c5a37/wrapt-1.17.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a03a38adec8066d5a37bea22f2ba6bbf39fcdefbe2d91419ab864c3fb515454", size = 40366 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/64/0e/f4472f2fdde2d4617975144311f8800ef73677a159be7fe61fa50997d6c0/wrapt-1.17.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d4478d72eb61c36e5b446e375bbc49ed002430d17cdec3cecb36993398e1a9e", size = 108571 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/cc/01/9b85a99996b0a97c8a17484684f206cbb6ba73c1ce6890ac668bcf3838fb/wrapt-1.17.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223db574bb38637e8230eb14b185565023ab624474df94d2af18f1cdb625216f", size = 113094 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/25/02/78926c1efddcc7b3aa0bc3d6b33a822f7d898059f7cd9ace8c8318e559ef/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e405adefb53a435f01efa7ccdec012c016b5a1d3f35459990afc39b6be4d5056", size = 110659 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/dc/ee/c414501ad518ac3e6fe184753632fe5e5ecacdcf0effc23f31c1e4f7bfcf/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:88547535b787a6c9ce4086917b6e1d291aa8ed914fdd3a838b3539dc95c12804", size = 106946 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/be/44/a1bd64b723d13bb151d6cc91b986146a1952385e0392a78567e12149c7b4/wrapt-1.17.3-cp314-cp314t-win32.whl", hash = "sha256:41b1d2bc74c2cac6f9074df52b2efbef2b30bdfe5f40cb78f8ca22963bc62977", size = 38717 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/79/d9/7cfd5a312760ac4dd8bf0184a6ee9e43c33e47f3dadc303032ce012b8fa3/wrapt-1.17.3-cp314-cp314t-win_amd64.whl", hash = "sha256:73d496de46cd2cdbdbcce4ae4bcdb4afb6a11234a1df9c085249d55166b95116", size = 41334 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591 },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "yarl"
|
|
||||||
version = "1.22.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "idna" },
|
|
||||||
{ name = "multidict" },
|
|
||||||
{ name = "propcache" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/57/63/0c6ebca57330cd313f6102b16dd57ffaf3ec4c83403dcb45dbd15c6f3ea1/yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71", size = 187169 }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ea/f3/d67de7260456ee105dc1d162d43a019ecad6b91e2f51809d6cddaa56690e/yarl-1.22.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8dee9c25c74997f6a750cd317b8ca63545169c098faee42c84aa5e506c819b53", size = 139980 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/01/88/04d98af0b47e0ef42597b9b28863b9060bb515524da0a65d5f4db160b2d5/yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01e73b85a5434f89fc4fe27dcda2aff08ddf35e4d47bbbea3bdcd25321af538a", size = 93424 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/18/91/3274b215fd8442a03975ce6bee5fe6aa57a8326b29b9d3d56234a1dca244/yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:22965c2af250d20c873cdbee8ff958fb809940aeb2e74ba5f20aaf6b7ac8c70c", size = 93821 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/61/3a/caf4e25036db0f2da4ca22a353dfeb3c9d3c95d2761ebe9b14df8fc16eb0/yarl-1.22.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4f15793aa49793ec8d1c708ab7f9eded1aa72edc5174cae703651555ed1b601", size = 373243 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/6e/9e/51a77ac7516e8e7803b06e01f74e78649c24ee1021eca3d6a739cb6ea49c/yarl-1.22.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5542339dcf2747135c5c85f68680353d5cb9ffd741c0f2e8d832d054d41f35a", size = 342361 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d4/f8/33b92454789dde8407f156c00303e9a891f1f51a0330b0fad7c909f87692/yarl-1.22.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5c401e05ad47a75869c3ab3e35137f8468b846770587e70d71e11de797d113df", size = 387036 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d9/9a/c5db84ea024f76838220280f732970aa4ee154015d7f5c1bfb60a267af6f/yarl-1.22.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:243dda95d901c733f5b59214d28b0120893d91777cb8aa043e6ef059d3cddfe2", size = 397671 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/11/c9/cd8538dc2e7727095e0c1d867bad1e40c98f37763e6d995c1939f5fdc7b1/yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bec03d0d388060058f5d291a813f21c011041938a441c593374da6077fe21b1b", size = 377059 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a1/b9/ab437b261702ced75122ed78a876a6dec0a1b0f5e17a4ac7a9a2482d8abe/yarl-1.22.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0748275abb8c1e1e09301ee3cf90c8a99678a4e92e4373705f2a2570d581273", size = 365356 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b2/9d/8e1ae6d1d008a9567877b08f0ce4077a29974c04c062dabdb923ed98e6fe/yarl-1.22.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:47fdb18187e2a4e18fda2c25c05d8251a9e4a521edaed757fef033e7d8498d9a", size = 361331 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ca/5a/09b7be3905962f145b73beb468cdd53db8aa171cf18c80400a54c5b82846/yarl-1.22.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c7044802eec4524fde550afc28edda0dd5784c4c45f0be151a2d3ba017daca7d", size = 382590 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/aa/7f/59ec509abf90eda5048b0bc3e2d7b5099dffdb3e6b127019895ab9d5ef44/yarl-1.22.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:139718f35149ff544caba20fce6e8a2f71f1e39b92c700d8438a0b1d2a631a02", size = 385316 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e5/84/891158426bc8036bfdfd862fabd0e0fa25df4176ec793e447f4b85cf1be4/yarl-1.22.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e1b51bebd221006d3d2f95fbe124b22b247136647ae5dcc8c7acafba66e5ee67", size = 374431 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/bb/49/03da1580665baa8bef5e8ed34c6df2c2aca0a2f28bf397ed238cc1bbc6f2/yarl-1.22.0-cp313-cp313-win32.whl", hash = "sha256:d3e32536234a95f513bd374e93d717cf6b2231a791758de6c509e3653f234c95", size = 81555 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/9a/ee/450914ae11b419eadd067c6183ae08381cfdfcb9798b90b2b713bbebddda/yarl-1.22.0-cp313-cp313-win_amd64.whl", hash = "sha256:47743b82b76d89a1d20b83e60d5c20314cbd5ba2befc9cda8f28300c4a08ed4d", size = 86965 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/98/4d/264a01eae03b6cf629ad69bae94e3b0e5344741e929073678e84bf7a3e3b/yarl-1.22.0-cp313-cp313-win_arm64.whl", hash = "sha256:5d0fcda9608875f7d052eff120c7a5da474a6796fe4d83e152e0e4d42f6d1a9b", size = 81205 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/88/fc/6908f062a2f77b5f9f6d69cecb1747260831ff206adcbc5b510aff88df91/yarl-1.22.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:719ae08b6972befcba4310e49edb1161a88cdd331e3a694b84466bd938a6ab10", size = 146209 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/65/47/76594ae8eab26210b4867be6f49129861ad33da1f1ebdf7051e98492bf62/yarl-1.22.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:47d8a5c446df1c4db9d21b49619ffdba90e77c89ec6e283f453856c74b50b9e3", size = 95966 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ab/ce/05e9828a49271ba6b5b038b15b3934e996980dd78abdfeb52a04cfb9467e/yarl-1.22.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cfebc0ac8333520d2d0423cbbe43ae43c8838862ddb898f5ca68565e395516e9", size = 97312 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d1/c5/7dffad5e4f2265b29c9d7ec869c369e4223166e4f9206fc2243ee9eea727/yarl-1.22.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4398557cbf484207df000309235979c79c4356518fd5c99158c7d38203c4da4f", size = 361967 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/50/b2/375b933c93a54bff7fc041e1a6ad2c0f6f733ffb0c6e642ce56ee3b39970/yarl-1.22.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2ca6fd72a8cd803be290d42f2dec5cdcd5299eeb93c2d929bf060ad9efaf5de0", size = 323949 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/66/50/bfc2a29a1d78644c5a7220ce2f304f38248dc94124a326794e677634b6cf/yarl-1.22.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca1f59c4e1ab6e72f0a23c13fca5430f889634166be85dbf1013683e49e3278e", size = 361818 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/46/96/f3941a46af7d5d0f0498f86d71275696800ddcdd20426298e572b19b91ff/yarl-1.22.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c5010a52015e7c70f86eb967db0f37f3c8bd503a695a49f8d45700144667708", size = 372626 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c1/42/8b27c83bb875cd89448e42cd627e0fb971fa1675c9ec546393d18826cb50/yarl-1.22.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d7672ecf7557476642c88497c2f8d8542f8e36596e928e9bcba0e42e1e7d71f", size = 341129 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/49/36/99ca3122201b382a3cf7cc937b95235b0ac944f7e9f2d5331d50821ed352/yarl-1.22.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b7c88eeef021579d600e50363e0b6ee4f7f6f728cd3486b9d0f3ee7b946398d", size = 346776 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/85/b4/47328bf996acd01a4c16ef9dcd2f59c969f495073616586f78cd5f2efb99/yarl-1.22.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f4afb5c34f2c6fecdcc182dfcfc6af6cccf1aa923eed4d6a12e9d96904e1a0d8", size = 334879 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c2/ad/b77d7b3f14a4283bffb8e92c6026496f6de49751c2f97d4352242bba3990/yarl-1.22.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:59c189e3e99a59cf8d83cbb31d4db02d66cda5a1a4374e8a012b51255341abf5", size = 350996 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/81/c8/06e1d69295792ba54d556f06686cbd6a7ce39c22307100e3fb4a2c0b0a1d/yarl-1.22.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:5a3bf7f62a289fa90f1990422dc8dff5a458469ea71d1624585ec3a4c8d6960f", size = 356047 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/4b/b8/4c0e9e9f597074b208d18cef227d83aac36184bfbc6eab204ea55783dbc5/yarl-1.22.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:de6b9a04c606978fdfe72666fa216ffcf2d1a9f6a381058d4378f8d7b1e5de62", size = 342947 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e0/e5/11f140a58bf4c6ad7aca69a892bff0ee638c31bea4206748fc0df4ebcb3a/yarl-1.22.0-cp313-cp313t-win32.whl", hash = "sha256:1834bb90991cc2999f10f97f5f01317f99b143284766d197e43cd5b45eb18d03", size = 86943 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/31/74/8b74bae38ed7fe6793d0c15a0c8207bbb819cf287788459e5ed230996cdd/yarl-1.22.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff86011bd159a9d2dfc89c34cfd8aff12875980e3bd6a39ff097887520e60249", size = 93715 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/69/66/991858aa4b5892d57aef7ee1ba6b4d01ec3b7eb3060795d34090a3ca3278/yarl-1.22.0-cp313-cp313t-win_arm64.whl", hash = "sha256:7861058d0582b847bc4e3a4a4c46828a410bca738673f35a29ba3ca5db0b473b", size = 83857 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/46/b3/e20ef504049f1a1c54a814b4b9bed96d1ac0e0610c3b4da178f87209db05/yarl-1.22.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:34b36c2c57124530884d89d50ed2c1478697ad7473efd59cfd479945c95650e4", size = 140520 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e4/04/3532d990fdbab02e5ede063676b5c4260e7f3abea2151099c2aa745acc4c/yarl-1.22.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:0dd9a702591ca2e543631c2a017e4a547e38a5c0f29eece37d9097e04a7ac683", size = 93504 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/11/63/ff458113c5c2dac9a9719ac68ee7c947cb621432bcf28c9972b1c0e83938/yarl-1.22.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:594fcab1032e2d2cc3321bb2e51271e7cd2b516c7d9aee780ece81b07ff8244b", size = 94282 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a7/bc/315a56aca762d44a6aaaf7ad253f04d996cb6b27bad34410f82d76ea8038/yarl-1.22.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3d7a87a78d46a2e3d5b72587ac14b4c16952dd0887dbb051451eceac774411e", size = 372080 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3f/3f/08e9b826ec2e099ea6e7c69a61272f4f6da62cb5b1b63590bb80ca2e4a40/yarl-1.22.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:852863707010316c973162e703bddabec35e8757e67fcb8ad58829de1ebc8590", size = 338696 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e3/9f/90360108e3b32bd76789088e99538febfea24a102380ae73827f62073543/yarl-1.22.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:131a085a53bfe839a477c0845acf21efc77457ba2bcf5899618136d64f3303a2", size = 387121 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/98/92/ab8d4657bd5b46a38094cfaea498f18bb70ce6b63508fd7e909bd1f93066/yarl-1.22.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:078a8aefd263f4d4f923a9677b942b445a2be970ca24548a8102689a3a8ab8da", size = 394080 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f5/e7/d8c5a7752fef68205296201f8ec2bf718f5c805a7a7e9880576c67600658/yarl-1.22.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bca03b91c323036913993ff5c738d0842fc9c60c4648e5c8d98331526df89784", size = 372661 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b6/2e/f4d26183c8db0bb82d491b072f3127fb8c381a6206a3a56332714b79b751/yarl-1.22.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:68986a61557d37bb90d3051a45b91fa3d5c516d177dfc6dd6f2f436a07ff2b6b", size = 364645 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/80/7c/428e5812e6b87cd00ee8e898328a62c95825bf37c7fa87f0b6bb2ad31304/yarl-1.22.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4792b262d585ff0dff6bcb787f8492e40698443ec982a3568c2096433660c694", size = 355361 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ec/2a/249405fd26776f8b13c067378ef4d7dd49c9098d1b6457cdd152a99e96a9/yarl-1.22.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ebd4549b108d732dba1d4ace67614b9545b21ece30937a63a65dd34efa19732d", size = 381451 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/67/a8/fb6b1adbe98cf1e2dd9fad71003d3a63a1bc22459c6e15f5714eb9323b93/yarl-1.22.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f87ac53513d22240c7d59203f25cc3beac1e574c6cd681bbfd321987b69f95fd", size = 383814 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d9/f9/3aa2c0e480fb73e872ae2814c43bc1e734740bb0d54e8cb2a95925f98131/yarl-1.22.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:22b029f2881599e2f1b06f8f1db2ee63bd309e2293ba2d566e008ba12778b8da", size = 370799 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/50/3c/af9dba3b8b5eeb302f36f16f92791f3ea62e3f47763406abf6d5a4a3333b/yarl-1.22.0-cp314-cp314-win32.whl", hash = "sha256:6a635ea45ba4ea8238463b4f7d0e721bad669f80878b7bfd1f89266e2ae63da2", size = 82990 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ac/30/ac3a0c5bdc1d6efd1b41fa24d4897a4329b3b1e98de9449679dd327af4f0/yarl-1.22.0-cp314-cp314-win_amd64.whl", hash = "sha256:0d6e6885777af0f110b0e5d7e5dda8b704efed3894da26220b7f3d887b839a79", size = 88292 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/df/0a/227ab4ff5b998a1b7410abc7b46c9b7a26b0ca9e86c34ba4b8d8bc7c63d5/yarl-1.22.0-cp314-cp314-win_arm64.whl", hash = "sha256:8218f4e98d3c10d683584cb40f0424f4b9fd6e95610232dd75e13743b070ee33", size = 82888 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/06/5e/a15eb13db90abd87dfbefb9760c0f3f257ac42a5cac7e75dbc23bed97a9f/yarl-1.22.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:45c2842ff0e0d1b35a6bf1cd6c690939dacb617a70827f715232b2e0494d55d1", size = 146223 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/18/82/9665c61910d4d84f41a5bf6837597c89e665fa88aa4941080704645932a9/yarl-1.22.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d947071e6ebcf2e2bee8fce76e10faca8f7a14808ca36a910263acaacef08eca", size = 95981 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/5d/9a/2f65743589809af4d0a6d3aa749343c4b5f4c380cc24a8e94a3c6625a808/yarl-1.22.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:334b8721303e61b00019474cc103bdac3d7b1f65e91f0bfedeec2d56dfe74b53", size = 97303 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b0/ab/5b13d3e157505c43c3b43b5a776cbf7b24a02bc4cccc40314771197e3508/yarl-1.22.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e7ce67c34138a058fd092f67d07a72b8e31ff0c9236e751957465a24b28910c", size = 361820 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/fb/76/242a5ef4677615cf95330cfc1b4610e78184400699bdda0acb897ef5e49a/yarl-1.22.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d77e1b2c6d04711478cb1c4ab90db07f1609ccf06a287d5607fcd90dc9863acf", size = 323203 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/8c/96/475509110d3f0153b43d06164cf4195c64d16999e0c7e2d8a099adcd6907/yarl-1.22.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4647674b6150d2cae088fc07de2738a84b8bcedebef29802cf0b0a82ab6face", size = 363173 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c9/66/59db471aecfbd559a1fd48aedd954435558cd98c7d0da8b03cc6c140a32c/yarl-1.22.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efb07073be061c8f79d03d04139a80ba33cbd390ca8f0297aae9cce6411e4c6b", size = 373562 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/03/1f/c5d94abc91557384719da10ff166b916107c1b45e4d0423a88457071dd88/yarl-1.22.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51ac5435758ba97ad69617e13233da53908beccc6cfcd6c34bbed8dcbede486", size = 339828 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/5f/97/aa6a143d3afba17b6465733681c70cf175af89f76ec8d9286e08437a7454/yarl-1.22.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:33e32a0dd0c8205efa8e83d04fc9f19313772b78522d1bdc7d9aed706bfd6138", size = 347551 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/43/3c/45a2b6d80195959239a7b2a8810506d4eea5487dce61c2a3393e7fc3c52e/yarl-1.22.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:bf4a21e58b9cde0e401e683ebd00f6ed30a06d14e93f7c8fd059f8b6e8f87b6a", size = 334512 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/86/a0/c2ab48d74599c7c84cb104ebd799c5813de252bea0f360ffc29d270c2caa/yarl-1.22.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e4b582bab49ac33c8deb97e058cd67c2c50dac0dd134874106d9c774fd272529", size = 352400 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/32/75/f8919b2eafc929567d3d8411f72bdb1a2109c01caaab4ebfa5f8ffadc15b/yarl-1.22.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0b5bcc1a9c4839e7e30b7b30dd47fe5e7e44fb7054ec29b5bb8d526aa1041093", size = 357140 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/cf/72/6a85bba382f22cf78add705d8c3731748397d986e197e53ecc7835e76de7/yarl-1.22.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c0232bce2170103ec23c454e54a57008a9a72b5d1c3105dc2496750da8cfa47c", size = 341473 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/35/18/55e6011f7c044dc80b98893060773cefcfdbf60dfefb8cb2f58b9bacbd83/yarl-1.22.0-cp314-cp314t-win32.whl", hash = "sha256:8009b3173bcd637be650922ac455946197d858b3630b6d8787aa9e5c4564533e", size = 89056 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f9/86/0f0dccb6e59a9e7f122c5afd43568b1d31b8ab7dda5f1b01fb5c7025c9a9/yarl-1.22.0-cp314-cp314t-win_amd64.whl", hash = "sha256:9fb17ea16e972c63d25d4a97f016d235c78dd2344820eb35bc034bc32012ee27", size = 96292 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/48/b7/503c98092fb3b344a179579f55814b613c1fbb1c23b3ec14a7b008a66a6e/yarl-1.22.0-cp314-cp314t-win_arm64.whl", hash = "sha256:9f6d73c1436b934e3f01df1e1b21ff765cd1d28c77dfb9ace207f746d4610ee1", size = 85171 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814 },
|
|
||||||
]
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import requests # type: ignore
|
import requests # type: ignore
|
||||||
|
|
||||||
# Получить токен чтобы:
|
# Получить токен чтобы:
|
||||||
|
|
@ -13,7 +12,9 @@ group_id = 220240483
|
||||||
dir = "../assets/dog"
|
dir = "../assets/dog"
|
||||||
list_labels = [fname for fname in os.listdir(dir)]
|
list_labels = [fname for fname in os.listdir(dir)]
|
||||||
|
|
||||||
r = requests.get(f"{VK_URL}photos.getAll{postfix}&access_token={TOKEN}&owner_id=-{group_id}&count=200")
|
r = requests.get(
|
||||||
|
f"{VK_URL}photos.getAll{postfix}&access_token={TOKEN}&owner_id=-{group_id}&count=200"
|
||||||
|
)
|
||||||
if "error" in r.json():
|
if "error" in r.json():
|
||||||
print("error", r.json())
|
print("error", r.json())
|
||||||
exit()
|
exit()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue