Compare commits
88 Commits
master
...
35-test-ta
| Author | SHA1 | Date | |
|---|---|---|---|
| 81be8abc50 | |||
| a5b19cfe2a | |||
| 4491b1b05a | |||
| a77df06bdd | |||
| 38c9c838ed | |||
| 17f5d81953 | |||
| 468eac45a2 | |||
| c0866ee863 | |||
| 5568c458c2 | |||
| e82586e08c | |||
| 520633f383 | |||
| 491d272304 | |||
| d6e1424d89 | |||
| 9bd3c3e45b | |||
| 7355552366 | |||
| 64cfccd353 | |||
| 97e0270550 | |||
| c4eb8b5568 | |||
| 0b5971c4af | |||
| 80b5a6a64e | |||
| bcf300e0c0 | |||
| 13f5bbc2dd | |||
| ce50a74510 | |||
| f77399b1d2 | |||
| 207a8737ba | |||
| c81ee3c4ec | |||
| d185aaf329 | |||
| fe33d5c7f6 | |||
| 0ee9e87b7f | |||
| cfdd6b72f0 | |||
| d0ffbeef0f | |||
| 8385eff636 | |||
| b0dd2eb5cf | |||
| f7abca1e1b | |||
| e73f57670a | |||
| f4831d5759 | |||
| 17bfb08e43 | |||
| dda9c841fd | |||
| 181a9a5ce9 | |||
| 7d6ce1eaee | |||
| 612d807bc4 | |||
| 25d5b34add | |||
| 0151e61cd6 | |||
| 687dc4bb9b | |||
| 814fefd18b | |||
| afdc7c17b6 | |||
| 3cde36d8a7 | |||
| 83ac7c3a66 | |||
| ad7bff67c4 | |||
| 83a09207d6 | |||
| 7103f3a089 | |||
| 10c012aba2 | |||
| 52df4b54d5 | |||
| 92a9f36acd | |||
| 070a63222c | |||
| 027475e4b3 | |||
| 13e2bff324 | |||
| 731b9d384a | |||
| c8370f96ff | |||
| 9aec75cdd7 | |||
| 38c0b9ba87 | |||
| ac23cc9397 | |||
| acd34f2ca5 | |||
| eb32f27bad | |||
| 60ef0e386d | |||
| 3f1e8c57ac | |||
| 18449382e1 | |||
| c42e39a8d5 | |||
| f8eb591b05 | |||
| d9f5c20557 | |||
| 4440e084b9 | |||
| 2dec1e33c2 | |||
| eb2d630dd0 | |||
| e4a02726e7 | |||
| a0a2248306 | |||
| fadfd8711c | |||
| d01386a4dc | |||
| c82107bad1 | |||
| 976576f8c6 | |||
| c2fecdd87c | |||
| a0f1654cf5 | |||
| 7fb9aae90e | |||
| 1e141ce6fa | |||
| 4da19bb053 | |||
| e7bc75f0d8 | |||
| d03154314c | |||
| 1a668159c7 | |||
| a33711cc49 |
5
deps/test-task-2025-06-30-v1/.dockerignore
vendored
Normal file
5
deps/test-task-2025-06-30-v1/.dockerignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
.venv
|
||||||
|
tmp
|
||||||
|
.git
|
||||||
|
.env
|
||||||
|
build
|
||||||
1
deps/test-task-2025-06-30-v1/.gitattributes
vendored
Normal file
1
deps/test-task-2025-06-30-v1/.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
releases/whl/** filter=lfs diff=lfs merge=lfs -text
|
||||||
6
deps/test-task-2025-06-30-v1/.gitignore
vendored
Normal file
6
deps/test-task-2025-06-30-v1/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
!.tmuxp/
|
||||||
|
!python
|
||||||
|
.env/
|
||||||
|
releases/tar
|
||||||
|
build
|
||||||
|
!releases/whl/**
|
||||||
9
deps/test-task-2025-06-30-v1/.tmuxp/v1.yaml
vendored
Normal file
9
deps/test-task-2025-06-30-v1/.tmuxp/v1.yaml
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
session_name: test-task-2025-06-30-v1
|
||||||
|
start_directory: ${PWD}/deps/test-task-2025-06-30-v1
|
||||||
|
windows:
|
||||||
|
- focus: 'true'
|
||||||
|
layout: 5687,98x12,0,0,18
|
||||||
|
options: {}
|
||||||
|
panes:
|
||||||
|
- pane
|
||||||
|
window_name: zsh
|
||||||
62
deps/test-task-2025-06-30-v1/Makefile
vendored
Normal file
62
deps/test-task-2025-06-30-v1/Makefile
vendored
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
ENV_PATH ?= .venv
|
||||||
|
PYTHON_PATH = $(ENV_PATH)/bin/python3
|
||||||
|
PYTHON_VERSION ?= 3.10
|
||||||
|
UV_ARGS ?= --offline
|
||||||
|
DOCKER ?= podman
|
||||||
|
COMPOSE ?= podman compose
|
||||||
|
|
||||||
|
venv_extract_requirements:
|
||||||
|
$(ENV_PATH)/bin/tomlq \
|
||||||
|
-r '.project.dependencies | join("\n")' \
|
||||||
|
pyproject.toml > requirements.in
|
||||||
|
|
||||||
|
venv_compile:
|
||||||
|
uv pip compile \
|
||||||
|
$(UV_ARGS) \
|
||||||
|
-p $(PYTHON_VERSION) \
|
||||||
|
--generate-hashes \
|
||||||
|
requirements.in > \
|
||||||
|
requirements.txt
|
||||||
|
|
||||||
|
|
||||||
|
venv:
|
||||||
|
uv \
|
||||||
|
venv \
|
||||||
|
-p 3.13 \
|
||||||
|
$(UV_ARGS) \
|
||||||
|
--seed \
|
||||||
|
$(ENV_PATH)
|
||||||
|
uv \
|
||||||
|
pip install \
|
||||||
|
$(UV_ARGS) \
|
||||||
|
-p $(ENV_PATH) \
|
||||||
|
-r requirements.txt
|
||||||
|
|
||||||
|
pyright:
|
||||||
|
$(ENV_PATH)/bin/python3 -m pyright \
|
||||||
|
-p pyproject.toml \
|
||||||
|
--pythonpath $(PYTHON_PATH)
|
||||||
|
|
||||||
|
|
||||||
|
compose_env:
|
||||||
|
cat docker/postgresql/.env .env/postgresql.env > .env/postgresql.patched.env
|
||||||
|
cat docker/web/.env .env/web.env > .env/web.patched.env
|
||||||
|
|
||||||
|
compose_build_web:
|
||||||
|
$(COMPOSE) build web
|
||||||
|
|
||||||
|
git-release:
|
||||||
|
git archive \
|
||||||
|
--format=tar \
|
||||||
|
-o "releases/tar/repo-$$(git describe --tags).tar" \
|
||||||
|
HEAD
|
||||||
|
|
||||||
|
ALEMBIC_CMD ?= --help
|
||||||
|
alembic:
|
||||||
|
$(ENV_PATH)/bin/alembic \
|
||||||
|
-c pyproject.toml \
|
||||||
|
$(ALEMBIC_CMD)
|
||||||
|
|
||||||
|
deploy_wheel:
|
||||||
|
make pyright
|
||||||
|
$(PYTHON_PATH) -m build -o releases/whl -w -n
|
||||||
76
deps/test-task-2025-06-30-v1/docker-compose.yml
vendored
Normal file
76
deps/test-task-2025-06-30-v1/docker-compose.yml
vendored
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
services:
|
||||||
|
redis:
|
||||||
|
image: docker.io/redis:latest-alpine@sha256:e71b4cb00ea461ac21114cff40ff12fb8396914238e1e9ec41520b2d5a4d3423
|
||||||
|
ports:
|
||||||
|
- 127.0.0.1:9004:6379
|
||||||
|
|
||||||
|
web: &web
|
||||||
|
image: online.fxreader.pr34.test_task_2025_06_30_v1:dev
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ./docker/web/Dockerfile
|
||||||
|
target: web
|
||||||
|
env_file: .env/web.env
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: 10m
|
||||||
|
max-file: "3"
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpus: '0.5'
|
||||||
|
memory: 128M
|
||||||
|
|
||||||
|
web-dev:
|
||||||
|
<<: *web
|
||||||
|
volumes:
|
||||||
|
- .:/app:ro
|
||||||
|
- ./tmp/cache:/app/tmp/cache:rw
|
||||||
|
|
||||||
|
emcont_worker:
|
||||||
|
<<: *web
|
||||||
|
image: online.fxreader.pr34.test_task_2025_06_30_v1:dev
|
||||||
|
environment:
|
||||||
|
command:
|
||||||
|
- python3
|
||||||
|
- -m
|
||||||
|
- online.fxreader.pr34.test_task_2025_06_30_v1.async_api.app
|
||||||
|
|
||||||
|
postgresql:
|
||||||
|
image: docker.io/postgres:14.18-bookworm@sha256:c0aab7962b283cf24a0defa5d0d59777f5045a7be59905f21ba81a20b1a110c9
|
||||||
|
# restart: always
|
||||||
|
# set shared memory limit when using docker compose
|
||||||
|
shm_size: 128mb
|
||||||
|
volumes:
|
||||||
|
- postgresql_data:/var/lib/postgresql/data/:rw
|
||||||
|
# or set shared memory limit when deploy via swarm stack
|
||||||
|
#volumes:
|
||||||
|
# - type: tmpfs
|
||||||
|
# target: /dev/shm
|
||||||
|
# tmpfs:
|
||||||
|
# size: 134217728 # 128*2^20 bytes = 128Mb
|
||||||
|
env_file: .env/postgresql.patched.env
|
||||||
|
# environment:
|
||||||
|
# POSTGRES_PASSWORD: example
|
||||||
|
ports:
|
||||||
|
- 127.0.0.1:9002:5432
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: 10m
|
||||||
|
max-file: "3"
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpus: '0.5'
|
||||||
|
memory: 128M
|
||||||
|
|
||||||
|
adminer:
|
||||||
|
image: docker.io/adminer:standalone@sha256:730215fe535daca9a2f378c48321bc615c8f0d88668721e0eff530fa35b6e8f6
|
||||||
|
ports:
|
||||||
|
- 127.0.0.1:9001:8080
|
||||||
|
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgresql_data:
|
||||||
3
deps/test-task-2025-06-30-v1/docker/postgresql/.env
vendored
Normal file
3
deps/test-task-2025-06-30-v1/docker/postgresql/.env
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
PGDATA=/var/lib/postgresql/data/pgdata
|
||||||
|
POSTGRES_USER=tickers
|
||||||
|
POSTGRES_DB=tickers
|
||||||
1
deps/test-task-2025-06-30-v1/docker/web/.env
vendored
Normal file
1
deps/test-task-2025-06-30-v1/docker/web/.env
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
# DB_URL=
|
||||||
50
deps/test-task-2025-06-30-v1/docker/web/Dockerfile
vendored
Normal file
50
deps/test-task-2025-06-30-v1/docker/web/Dockerfile
vendored
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
FROM docker.io/library/python:3.12@sha256:6121c801703ec330726ebf542faab113efcfdf2236378c03df8f49d80e7b4180 AS base
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY docker/web/apt.requirements.txt docker/web/apt.requirements.txt
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y $(cat docker/web/apt.requirements.txt)
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
pip3 install \
|
||||||
|
--break-system-packages uv
|
||||||
|
|
||||||
|
COPY requirements.txt requirements.txt
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
--mount=type=bind,source=releases/whl,target=/app/releases/whl \
|
||||||
|
--mount=type=cache,target=/root/.cache/pip \
|
||||||
|
--mount=type=cache,target=/root/.cache/uv \
|
||||||
|
uv pip \
|
||||||
|
install \
|
||||||
|
--system \
|
||||||
|
--break-system-packages \
|
||||||
|
-f releases/whl \
|
||||||
|
-r requirements.txt
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
RUN apt-get update -yy && apt-get install -yy tini
|
||||||
|
|
||||||
|
FROM base as web
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
--mount=type=bind,source=releases/whl,target=/app/releases/whl \
|
||||||
|
--mount=type=cache,target=/root/.cache/pip \
|
||||||
|
--mount=type=cache,target=/root/.cache/uv \
|
||||||
|
uv pip \
|
||||||
|
install \
|
||||||
|
--system \
|
||||||
|
--break-system-packages \
|
||||||
|
--no-index \
|
||||||
|
-f releases/whl \
|
||||||
|
'online.fxreader.pr34.test_task_2025_06_30_v1==0.1'
|
||||||
|
|
||||||
|
ENTRYPOINT ["tini", "--"]
|
||||||
|
CMD [ \
|
||||||
|
"python3", \
|
||||||
|
"-m", \
|
||||||
|
"online.fxreader.pr34.test_task_2025_06_30_v1.async_api.app" \
|
||||||
|
]
|
||||||
1
deps/test-task-2025-06-30-v1/docker/web/apt.requirements.txt
vendored
Normal file
1
deps/test-task-2025-06-30-v1/docker/web/apt.requirements.txt
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
wget tar git curl
|
||||||
89
deps/test-task-2025-06-30-v1/docs/readme.md
vendored
Normal file
89
deps/test-task-2025-06-30-v1/docs/readme.md
vendored
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
# Requirements
|
||||||
|
|
||||||
|
Tickers of interest:
|
||||||
|
- EURUSD
|
||||||
|
- USDJPY
|
||||||
|
- GBPUSD
|
||||||
|
- AUDUSD
|
||||||
|
- USDCAD
|
||||||
|
|
||||||
|
Rest API - https://rates.emcont.com
|
||||||
|
|
||||||
|
Scrape every second;
|
||||||
|
|
||||||
|
Schema:
|
||||||
|
Ticker:
|
||||||
|
id: foreign_key market
|
||||||
|
timestamp: datetime
|
||||||
|
# (ask + bid) / 2
|
||||||
|
value: decimal
|
||||||
|
|
||||||
|
Store up to 30 minutes of recent tickers;
|
||||||
|
|
||||||
|
Return via websocket up to 30 minutes of recent tickers;
|
||||||
|
|
||||||
|
# AsyncAPI
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
AsyncAPI:
|
||||||
|
Endpoints:
|
||||||
|
subscribe:
|
||||||
|
Request: SubscribeAction
|
||||||
|
Response: AssetHistoryResponse | AssetTickerResponse
|
||||||
|
list:
|
||||||
|
Request: AssetsAction
|
||||||
|
Response: AssetsResponse
|
||||||
|
Schema:
|
||||||
|
SubscribeAction:
|
||||||
|
action: Literal['subscribe']
|
||||||
|
message:
|
||||||
|
assetId: 1
|
||||||
|
AssetHistoryResponse:
|
||||||
|
action: Literal['asset_history']
|
||||||
|
message:
|
||||||
|
points:
|
||||||
|
- assetName: EURUSD
|
||||||
|
time: 1455883484
|
||||||
|
assetId: 1
|
||||||
|
value: 1.110481
|
||||||
|
- assetName: EURUSD
|
||||||
|
time: 1455883485
|
||||||
|
assetId: 1
|
||||||
|
value: 1.110948
|
||||||
|
- assetName: EURUSD
|
||||||
|
time: 1455883486
|
||||||
|
assetId: 1
|
||||||
|
value: 1.111122
|
||||||
|
AssetTickerResponse:
|
||||||
|
action: Literal['point']
|
||||||
|
message:
|
||||||
|
assetName: EURUSD
|
||||||
|
time: 1455883484
|
||||||
|
assetId: 1
|
||||||
|
value: 1.110481
|
||||||
|
AssetsAction:
|
||||||
|
action: Literal['assets']
|
||||||
|
message: {}
|
||||||
|
AssetsResponse:
|
||||||
|
action: Literal['assets']
|
||||||
|
message:
|
||||||
|
assets:
|
||||||
|
- id: 1
|
||||||
|
name: EURUSD
|
||||||
|
- id: 2
|
||||||
|
name: USDJPY
|
||||||
|
- id: 3
|
||||||
|
name: GBPUSD
|
||||||
|
- id: 4
|
||||||
|
name: AUDUSD
|
||||||
|
- id: 5
|
||||||
|
name: USDCAD
|
||||||
|
```
|
||||||
|
|
||||||
|
# Services:
|
||||||
|
|
||||||
|
``` yaml
|
||||||
|
web:
|
||||||
|
ports:
|
||||||
|
- 8080:80
|
||||||
|
```
|
||||||
229
deps/test-task-2025-06-30-v1/pyproject.toml
vendored
Normal file
229
deps/test-task-2025-06-30-v1/pyproject.toml
vendored
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
[project]
|
||||||
|
description = 'test task for websocket with crypto tickers'
|
||||||
|
requires-python = '>= 3.10'
|
||||||
|
maintainers = [
|
||||||
|
{ name = 'Siarhei Siniak', email = 'siarheisiniak@gmail.com' },
|
||||||
|
]
|
||||||
|
classifiers = [
|
||||||
|
'Programming Language :: Python',
|
||||||
|
]
|
||||||
|
|
||||||
|
name = 'online.fxreader.pr34.test_task_2025_06_30_v1'
|
||||||
|
version = '0.1.1'
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
'alembic',
|
||||||
|
'fastapi',
|
||||||
|
'uvicorn',
|
||||||
|
'websockets',
|
||||||
|
'uvloop',
|
||||||
|
'tomlq',
|
||||||
|
'mypy',
|
||||||
|
'marisa-trie',
|
||||||
|
'pydantic',
|
||||||
|
'asyncpg',
|
||||||
|
'pydantic-settings',
|
||||||
|
'tomlkit',
|
||||||
|
'tomlq',
|
||||||
|
'numpy',
|
||||||
|
'cryptography',
|
||||||
|
'mypy',
|
||||||
|
'pyright',
|
||||||
|
'ruff',
|
||||||
|
'ipython',
|
||||||
|
'ipdb',
|
||||||
|
'requests',
|
||||||
|
'types-requests',
|
||||||
|
'aiohttp',
|
||||||
|
'build',
|
||||||
|
'wheel',
|
||||||
|
'setuptools',
|
||||||
|
'setuptools-scm',
|
||||||
|
]
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ['build', 'wheel', 'setuptools', 'setuptools-scm']
|
||||||
|
build-backend = 'setuptools.build_meta'
|
||||||
|
|
||||||
|
[tool.setuptools]
|
||||||
|
include-package-data = false
|
||||||
|
[tool.setuptools.package-dir]
|
||||||
|
'online.fxreader.pr34.test_task_2025_06_30_v1' = 'python/online/fxreader/pr34/test_task_2025_06_30_v1'
|
||||||
|
|
||||||
|
[tool.alembic]
|
||||||
|
script_location = 'python/online/fxreader/pr34/test_task_2025_06_30_v1/tickers/alembic'
|
||||||
|
prepend_sys_path = ['python']
|
||||||
|
|
||||||
|
# sqlalchemy.url = 'asdfasdf:/asdfasdfa'
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
line-length = 160
|
||||||
|
target-version = 'py310'
|
||||||
|
# builtins = ['_', 'I', 'P']
|
||||||
|
include = [
|
||||||
|
# 'follow_the_leader/**/*.py',
|
||||||
|
#'*.py',
|
||||||
|
# '*.recipe',
|
||||||
|
'python/**/*.py',
|
||||||
|
'python/**/*.pyi',
|
||||||
|
]
|
||||||
|
exclude = [
|
||||||
|
'.venv',
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.ruff.format]
|
||||||
|
quote-style = 'single'
|
||||||
|
indent-style = 'tab'
|
||||||
|
skip-magic-trailing-comma = false
|
||||||
|
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
ignore = [
|
||||||
|
'E402', 'E722', 'E741', 'W191', 'E101', 'E501', 'I001', 'F401', 'E714',
|
||||||
|
'E713',
|
||||||
|
# remove lambdas later on
|
||||||
|
'E731',
|
||||||
|
# fix this too
|
||||||
|
'E712',
|
||||||
|
'E703',
|
||||||
|
# remove unused variables, or fix a bug
|
||||||
|
'F841',
|
||||||
|
# fix * imports
|
||||||
|
'F403',
|
||||||
|
# don't care about trailing new lines
|
||||||
|
'W292',
|
||||||
|
|
||||||
|
]
|
||||||
|
select = ['E', 'F', 'I', 'W', 'INT']
|
||||||
|
|
||||||
|
|
||||||
|
[tool.ruff.lint.isort]
|
||||||
|
detect-same-package = true
|
||||||
|
# extra-standard-library = ["aes", "elementmaker", "encodings"]
|
||||||
|
# known-first-party = ["calibre_extensions", "calibre_plugins", "polyglot"]
|
||||||
|
# known-third-party = ["odf", "qt", "templite", "tinycss", "css_selectors"]
|
||||||
|
relative-imports-order = "closest-to-furthest"
|
||||||
|
split-on-trailing-comma = true
|
||||||
|
section-order = [
|
||||||
|
# '__python__',
|
||||||
|
"future",
|
||||||
|
"standard-library", "third-party", "first-party", "local-folder"
|
||||||
|
]
|
||||||
|
force-wrap-aliases = true
|
||||||
|
|
||||||
|
# [tool.ruff.lint.isort.sections]
|
||||||
|
# '__python__' = ['__python__']
|
||||||
|
|
||||||
|
[tool.pylsp-mypy]
|
||||||
|
enabled = false
|
||||||
|
|
||||||
|
[tool.pyright]
|
||||||
|
include = [
|
||||||
|
#'../../../../../follow_the_leader/views2/payments.py',
|
||||||
|
#'../../../../../follow_the_leader/logic/payments.py',
|
||||||
|
#'../../../../../follow_the_leader/logic/paypal.py',
|
||||||
|
'python/**/*.py',
|
||||||
|
'python/**/*.pyi',
|
||||||
|
]
|
||||||
|
# stubPath = '../mypy-stubs'
|
||||||
|
extraPaths = [
|
||||||
|
]
|
||||||
|
#strict = ["src"]
|
||||||
|
|
||||||
|
analyzeUnannotatedFunctions = true
|
||||||
|
disableBytesTypePromotions = true
|
||||||
|
strictParameterNoneValue = true
|
||||||
|
enableTypeIgnoreComments = true
|
||||||
|
enableReachabilityAnalysis = true
|
||||||
|
strictListInference = true
|
||||||
|
strictDictionaryInference = true
|
||||||
|
strictSetInference = true
|
||||||
|
deprecateTypingAliases = false
|
||||||
|
enableExperimentalFeatures = false
|
||||||
|
reportMissingTypeStubs ="error"
|
||||||
|
reportMissingModuleSource = "warning"
|
||||||
|
reportInvalidTypeForm = "error"
|
||||||
|
reportMissingImports = "error"
|
||||||
|
reportUndefinedVariable = "error"
|
||||||
|
reportAssertAlwaysTrue = "error"
|
||||||
|
reportInvalidStringEscapeSequence = "error"
|
||||||
|
reportInvalidTypeVarUse = "error"
|
||||||
|
reportSelfClsParameterName = "error"
|
||||||
|
reportUnsupportedDunderAll = "error"
|
||||||
|
reportUnusedExpression = "error"
|
||||||
|
reportWildcardImportFromLibrary = "error"
|
||||||
|
reportAbstractUsage = "error"
|
||||||
|
reportArgumentType = "error"
|
||||||
|
reportAssertTypeFailure = "error"
|
||||||
|
reportAssignmentType = "error"
|
||||||
|
reportAttributeAccessIssue = "error"
|
||||||
|
reportCallIssue = "error"
|
||||||
|
reportGeneralTypeIssues = "error"
|
||||||
|
reportInconsistentOverload = "error"
|
||||||
|
reportIndexIssue = "error"
|
||||||
|
reportInvalidTypeArguments = "error"
|
||||||
|
reportNoOverloadImplementation = "error"
|
||||||
|
reportOperatorIssue = "error"
|
||||||
|
reportOptionalSubscript = "error"
|
||||||
|
reportOptionalMemberAccess = "error"
|
||||||
|
reportOptionalCall = "error"
|
||||||
|
reportOptionalIterable = "error"
|
||||||
|
reportOptionalContextManager = "error"
|
||||||
|
reportOptionalOperand = "error"
|
||||||
|
reportRedeclaration = "error"
|
||||||
|
reportReturnType = "error"
|
||||||
|
reportTypedDictNotRequiredAccess = "error"
|
||||||
|
reportPrivateImportUsage = "error"
|
||||||
|
reportUnboundVariable = "error"
|
||||||
|
reportUnhashable = "error"
|
||||||
|
reportUnusedCoroutine = "error"
|
||||||
|
reportUnusedExcept = "error"
|
||||||
|
reportFunctionMemberAccess = "error"
|
||||||
|
reportIncompatibleMethodOverride = "error"
|
||||||
|
reportIncompatibleVariableOverride = "error"
|
||||||
|
reportOverlappingOverload = "error"
|
||||||
|
reportPossiblyUnboundVariable = "error"
|
||||||
|
reportConstantRedefinition = "error"
|
||||||
|
#reportDeprecated = "error"
|
||||||
|
reportDeprecated = "warning"
|
||||||
|
reportDuplicateImport = "error"
|
||||||
|
reportIncompleteStub = "error"
|
||||||
|
reportInconsistentConstructor = "error"
|
||||||
|
reportInvalidStubStatement = "error"
|
||||||
|
reportMatchNotExhaustive = "error"
|
||||||
|
reportMissingParameterType = "error"
|
||||||
|
reportMissingTypeArgument = "error"
|
||||||
|
reportPrivateUsage = "error"
|
||||||
|
reportTypeCommentUsage = "error"
|
||||||
|
reportUnknownArgumentType = "error"
|
||||||
|
reportUnknownLambdaType = "error"
|
||||||
|
reportUnknownMemberType = "error"
|
||||||
|
reportUnknownParameterType = "error"
|
||||||
|
reportUnknownVariableType = "error"
|
||||||
|
#reportUnknownVariableType = "warning"
|
||||||
|
reportUnnecessaryCast = "error"
|
||||||
|
reportUnnecessaryComparison = "error"
|
||||||
|
reportUnnecessaryContains = "error"
|
||||||
|
#reportUnnecessaryIsInstance = "error"
|
||||||
|
reportUnnecessaryIsInstance = "warning"
|
||||||
|
reportUnusedClass = "error"
|
||||||
|
#reportUnusedImport = "error"
|
||||||
|
reportUnusedImport = "none"
|
||||||
|
# reportUnusedFunction = "error"
|
||||||
|
reportUnusedFunction = "warning"
|
||||||
|
#reportUnusedVariable = "error"
|
||||||
|
reportUnusedVariable = "warning"
|
||||||
|
reportUntypedBaseClass = "error"
|
||||||
|
reportUntypedClassDecorator = "error"
|
||||||
|
reportUntypedFunctionDecorator = "error"
|
||||||
|
reportUntypedNamedTuple = "error"
|
||||||
|
reportCallInDefaultInitializer = "none"
|
||||||
|
reportImplicitOverride = "none"
|
||||||
|
reportImplicitStringConcatenation = "none"
|
||||||
|
reportImportCycles = "none"
|
||||||
|
reportMissingSuperCall = "none"
|
||||||
|
reportPropertyTypeMismatch = "none"
|
||||||
|
reportShadowedImports = "none"
|
||||||
|
reportUninitializedInstanceVariable = "none"
|
||||||
|
reportUnnecessaryTypeIgnoreComment = "none"
|
||||||
|
reportUnusedCallResult = "none"
|
||||||
0
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/__init__.py
vendored
Normal file
0
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/__init__.py
vendored
Normal file
0
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/async_api/__init__.py
vendored
Normal file
0
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/async_api/__init__.py
vendored
Normal file
68
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/async_api/app.py
vendored
Normal file
68
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/async_api/app.py
vendored
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import asyncio
|
||||||
|
import datetime
|
||||||
|
import logging
|
||||||
|
# import os
|
||||||
|
from ..tickers_retrieval.emcont import Emcont
|
||||||
|
from ..tickers.models import Ticker
|
||||||
|
from ..tickers.logic import ticker_store_multiple, markets_get_by_symbol
|
||||||
|
from .db import create_engine
|
||||||
|
import sqlalchemy.ext.asyncio
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from sqlalchemy.ext.asyncio import async_sessionmaker
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
async def run() -> None:
|
||||||
|
async_session = create_engine()
|
||||||
|
|
||||||
|
async def store_cb(
|
||||||
|
rates: list[Emcont.rates_get_t.data_t.rate_t],
|
||||||
|
timestamp: datetime.datetime,
|
||||||
|
session: 'async_sessionmaker[AsyncSession]',
|
||||||
|
) -> None:
|
||||||
|
logger.info(dict(
|
||||||
|
msg='before markets',
|
||||||
|
))
|
||||||
|
|
||||||
|
markets = await markets_get_by_symbol(
|
||||||
|
session,
|
||||||
|
set([
|
||||||
|
rate.symbol
|
||||||
|
for rate in rates
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(dict(
|
||||||
|
msg='after markets',
|
||||||
|
))
|
||||||
|
|
||||||
|
await ticker_store_multiple(
|
||||||
|
session,
|
||||||
|
[
|
||||||
|
Ticker(
|
||||||
|
id=markets[rate.symbol],
|
||||||
|
timestamp=timestamp,
|
||||||
|
value=rate.value,
|
||||||
|
)
|
||||||
|
for rate in rates
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(dict(
|
||||||
|
rates=rates,
|
||||||
|
timestamp=timestamp.isoformat()
|
||||||
|
))
|
||||||
|
|
||||||
|
await Emcont.worker(
|
||||||
|
only_symbols={'EURUSD', 'USDJPY', 'GBPUSD', 'AUDUSD', 'USDCAD'},
|
||||||
|
session=async_session,
|
||||||
|
store_cb=store_cb,
|
||||||
|
request_timeout=2,
|
||||||
|
store_timeout=0.5,
|
||||||
|
)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
asyncio.run(run())
|
||||||
15
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/async_api/db.py
vendored
Normal file
15
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/async_api/db.py
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
from ..tickers.settings import Settings as ModelsSettings
|
||||||
|
|
||||||
|
import sqlalchemy.ext.asyncio
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from sqlalchemy.ext.asyncio import async_sessionmaker
|
||||||
|
|
||||||
|
def create_engine() -> 'async_sessionmaker[AsyncSession]':
|
||||||
|
engine = sqlalchemy.ext.asyncio.create_async_engine(
|
||||||
|
ModelsSettings.singleton().db_url
|
||||||
|
)
|
||||||
|
async_session = sqlalchemy.ext.asyncio.async_sessionmaker(
|
||||||
|
engine
|
||||||
|
)
|
||||||
|
|
||||||
|
return async_session
|
||||||
71
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/async_api/fastapi.py
vendored
Normal file
71
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/async_api/fastapi.py
vendored
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import fastapi
|
||||||
|
import pydantic
|
||||||
|
import functools
|
||||||
|
import logging
|
||||||
|
import copy
|
||||||
|
import uvicorn
|
||||||
|
import uvicorn.config
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from .settings import Settings as APISettings
|
||||||
|
from .db import create_engine
|
||||||
|
from .websocket_api import WebsocketAPI
|
||||||
|
|
||||||
|
from typing import (Any, Optional, Literal, Annotated,)
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def websocket_tickers(
|
||||||
|
websocket: fastapi.WebSocket,
|
||||||
|
websocket_api: WebsocketAPI,
|
||||||
|
) -> None:
|
||||||
|
try:
|
||||||
|
await websocket_api.connect(websocket)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
msg = await websocket.receive_text()
|
||||||
|
await websocket_api.on_message(websocket, msg)
|
||||||
|
except fastapi.WebSocketDisconnect:
|
||||||
|
pass
|
||||||
|
# websocket_api.disconnect(websocket)
|
||||||
|
except:
|
||||||
|
logger.exception('')
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
await websocket_api.disconnect(websocket)
|
||||||
|
|
||||||
|
def create_app() -> fastapi.FastAPI:
|
||||||
|
async_session = create_engine()
|
||||||
|
|
||||||
|
websocket_api = WebsocketAPI(
|
||||||
|
session=async_session,
|
||||||
|
)
|
||||||
|
|
||||||
|
app = fastapi.FastAPI()
|
||||||
|
|
||||||
|
app.websocket(
|
||||||
|
'/tickers/',
|
||||||
|
)(
|
||||||
|
functools.partial(
|
||||||
|
websocket_tickers,
|
||||||
|
websocket_api=fastapi.Depends(lambda : websocket_api),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return app
|
||||||
|
|
||||||
|
def run(args: list[str]):
|
||||||
|
log_config = copy.deepcopy(uvicorn.config.LOGGING_CONFIG)
|
||||||
|
|
||||||
|
uvicorn.run(
|
||||||
|
create_app(),
|
||||||
|
host=APISettings.singleton().uvicorn_host,
|
||||||
|
port=APISettings.singleton().uvicorn_port,
|
||||||
|
loop='uvloop',
|
||||||
|
log_config=log_config,
|
||||||
|
log_level=logging.INFO,
|
||||||
|
)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
run(sys.argv[1:])
|
||||||
67
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/async_api/schema.py
vendored
Normal file
67
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/async_api/schema.py
vendored
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import pydantic
|
||||||
|
import decimal
|
||||||
|
|
||||||
|
from typing import (Literal, Annotated,)
|
||||||
|
|
||||||
|
class SubscribeAction(pydantic.BaseModel):
|
||||||
|
action: Literal['subscribe']
|
||||||
|
class message_t(pydantic.BaseModel):
|
||||||
|
asset_id: Annotated[
|
||||||
|
int,
|
||||||
|
pydantic.Field(alias='assetId')
|
||||||
|
]
|
||||||
|
|
||||||
|
message: message_t
|
||||||
|
|
||||||
|
class AssetsAction(pydantic.BaseModel):
|
||||||
|
action: Literal['assets']
|
||||||
|
class message_t(pydantic.BaseModel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
message: Annotated[
|
||||||
|
message_t,
|
||||||
|
pydantic.Field(
|
||||||
|
default_factory=message_t,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
Action = pydantic.RootModel[
|
||||||
|
AssetsAction | SubscribeAction
|
||||||
|
]
|
||||||
|
|
||||||
|
class AssetHistoryResponse(pydantic.BaseModel):
|
||||||
|
action: Literal['asset_history'] = 'asset_history'
|
||||||
|
|
||||||
|
class message_t(pydantic.BaseModel):
|
||||||
|
class point_t(pydantic.BaseModel):
|
||||||
|
asset_name : Annotated[
|
||||||
|
str,
|
||||||
|
pydantic.Field(
|
||||||
|
alias='assetName',
|
||||||
|
)
|
||||||
|
]
|
||||||
|
time: int
|
||||||
|
asset_id : Annotated[
|
||||||
|
int,
|
||||||
|
pydantic.Field(alias='assetId')
|
||||||
|
]
|
||||||
|
value: decimal.Decimal
|
||||||
|
|
||||||
|
points: list[point_t]
|
||||||
|
message: message_t
|
||||||
|
|
||||||
|
class AssetTickerResponse(pydantic.BaseModel):
|
||||||
|
action: Literal['point'] = 'point'
|
||||||
|
|
||||||
|
message: 'AssetHistoryResponse.message_t.point_t'
|
||||||
|
|
||||||
|
class AssetsResponse(pydantic.BaseModel):
|
||||||
|
action: Literal['assets'] = 'assets'
|
||||||
|
|
||||||
|
class message_t(pydantic.BaseModel):
|
||||||
|
class asset_t(pydantic.BaseModel):
|
||||||
|
id: int
|
||||||
|
name: str
|
||||||
|
|
||||||
|
assets: list[asset_t]
|
||||||
|
message: message_t
|
||||||
18
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/async_api/settings.py
vendored
Normal file
18
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/async_api/settings.py
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import pydantic
|
||||||
|
import pydantic_settings
|
||||||
|
|
||||||
|
from typing import (ClassVar, Optional,)
|
||||||
|
|
||||||
|
|
||||||
|
class Settings(pydantic_settings.BaseSettings):
|
||||||
|
uvicorn_port : int = 80
|
||||||
|
uvicorn_host : str = '127.0.0.1'
|
||||||
|
|
||||||
|
_singleton : ClassVar[Optional['Settings']] = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def singleton(cls) -> 'Settings':
|
||||||
|
if cls._singleton is None:
|
||||||
|
cls._singleton = Settings.model_validate({})
|
||||||
|
|
||||||
|
return cls._singleton
|
||||||
126
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/async_api/websocket_api.py
vendored
Normal file
126
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/async_api/websocket_api.py
vendored
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
import fastapi
|
||||||
|
import datetime
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from sqlalchemy.ext.asyncio import async_sessionmaker
|
||||||
|
from . import schema
|
||||||
|
from ..tickers.logic import tickers_get_by_period, markets_all
|
||||||
|
|
||||||
|
from typing import (Optional, Literal)
|
||||||
|
|
||||||
|
class WebsocketAPI:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
session: 'async_sessionmaker[AsyncSession]',
|
||||||
|
) -> None:
|
||||||
|
self.connections : set[
|
||||||
|
fastapi.WebSocket,
|
||||||
|
] = set()
|
||||||
|
self.subscriptions_by_asset_id : dict[
|
||||||
|
int, set[fastapi.WebSocket]
|
||||||
|
] = dict()
|
||||||
|
self.subscriptions_by_client : dict[
|
||||||
|
fastapi.WebSocket,
|
||||||
|
int,
|
||||||
|
] = dict()
|
||||||
|
self.session = session
|
||||||
|
|
||||||
|
async def connect(self, client: fastapi.WebSocket) -> None:
|
||||||
|
assert not client in self.connections
|
||||||
|
|
||||||
|
await client.accept()
|
||||||
|
|
||||||
|
self.connections.add(client)
|
||||||
|
|
||||||
|
|
||||||
|
async def subscribe(
|
||||||
|
self,
|
||||||
|
client: fastapi.WebSocket,
|
||||||
|
asset_id: int
|
||||||
|
) -> None:
|
||||||
|
if client in self.subscriptions_by_client:
|
||||||
|
last_asset_id = self.subscriptions_by_client[client]
|
||||||
|
del self.subscriptions_by_asset_id[last_asset_id]
|
||||||
|
del self.subscriptions_by_client[client]
|
||||||
|
|
||||||
|
if not asset_id in self.subscriptions_by_asset_id:
|
||||||
|
self.subscriptions_by_asset_id[asset_id] = set()
|
||||||
|
|
||||||
|
self.subscriptions_by_asset_id[asset_id].add(client)
|
||||||
|
self.subscriptions_by_client[client] = asset_id
|
||||||
|
|
||||||
|
await self.asset_last_period(client, asset_id)
|
||||||
|
|
||||||
|
async def asset_last_period(
|
||||||
|
self,
|
||||||
|
client: fastapi.WebSocket,
|
||||||
|
asset_id: int,
|
||||||
|
) -> None:
|
||||||
|
tickers = await tickers_get_by_period(
|
||||||
|
self.session,
|
||||||
|
period=datetime.timedelta(minutes=30),
|
||||||
|
market_id=asset_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
await client.send_text(
|
||||||
|
schema.AssetHistoryResponse(
|
||||||
|
message=schema.AssetHistoryResponse.message_t(
|
||||||
|
points=[
|
||||||
|
schema.AssetHistoryResponse.message_t.point_t.model_construct(
|
||||||
|
asset_name=o.market.name,
|
||||||
|
asset_id=o.market.id,
|
||||||
|
time=int(o.timestamp.timestamp()),
|
||||||
|
value=o.value,
|
||||||
|
)
|
||||||
|
for o in tickers
|
||||||
|
]
|
||||||
|
)
|
||||||
|
).json(by_alias=True,),
|
||||||
|
)
|
||||||
|
|
||||||
|
async def assets_index(
|
||||||
|
self,
|
||||||
|
client: fastapi.WebSocket,
|
||||||
|
) -> None:
|
||||||
|
markets = await markets_all(
|
||||||
|
self.session,
|
||||||
|
)
|
||||||
|
|
||||||
|
await client.send_text(
|
||||||
|
schema.AssetsResponse(
|
||||||
|
message=schema.AssetsResponse.message_t(
|
||||||
|
assets=[
|
||||||
|
schema.AssetsResponse.message_t.asset_t.model_construct(
|
||||||
|
name=o.name,
|
||||||
|
id=o.id,
|
||||||
|
)
|
||||||
|
for o in markets
|
||||||
|
]
|
||||||
|
)
|
||||||
|
).json(by_alias=True,),
|
||||||
|
)
|
||||||
|
|
||||||
|
async def on_message(
|
||||||
|
self,
|
||||||
|
client: fastapi.WebSocket,
|
||||||
|
msg_raw: str
|
||||||
|
) -> None:
|
||||||
|
msg = schema.Action.model_validate_json(
|
||||||
|
msg_raw
|
||||||
|
).root
|
||||||
|
|
||||||
|
if isinstance(msg, schema.SubscribeAction):
|
||||||
|
await self.subscribe(
|
||||||
|
client,
|
||||||
|
msg.message.asset_id
|
||||||
|
)
|
||||||
|
elif isinstance(msg, schema.AssetsAction):
|
||||||
|
await self.assets_index(
|
||||||
|
client,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
async def disconnect(self, client: fastapi.WebSocket) -> None:
|
||||||
|
assert client in self.connections
|
||||||
|
|
||||||
|
self.connections.remove(client)
|
||||||
0
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/py.typed
vendored
Normal file
0
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/py.typed
vendored
Normal file
0
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/tickers/__init__.py
vendored
Normal file
0
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/tickers/__init__.py
vendored
Normal file
117
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/tickers/alembic/env.py
vendored
Normal file
117
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/tickers/alembic/env.py
vendored
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from logging.config import fileConfig
|
||||||
|
|
||||||
|
from sqlalchemy.ext.asyncio import async_engine_from_config
|
||||||
|
|
||||||
|
from sqlalchemy import engine_from_config
|
||||||
|
from sqlalchemy import pool
|
||||||
|
|
||||||
|
from sqlalchemy.engine.base import Connection
|
||||||
|
|
||||||
|
from alembic import context
|
||||||
|
|
||||||
|
from online.fxreader.pr34.test_task_2025_06_30_v1.tickers.settings import Settings
|
||||||
|
from online.fxreader.pr34.test_task_2025_06_30_v1.tickers.models import (
|
||||||
|
Base,
|
||||||
|
Market,
|
||||||
|
)
|
||||||
|
|
||||||
|
# this is the Alembic Config object, which provides
|
||||||
|
# access to the values within the .ini file in use.
|
||||||
|
config = context.config
|
||||||
|
|
||||||
|
config.set_main_option(
|
||||||
|
'sqlalchemy.url',
|
||||||
|
Settings.singleton().db_url
|
||||||
|
)
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
# else:
|
||||||
|
if True:
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# add your model's MetaData object here
|
||||||
|
# for 'autogenerate' support
|
||||||
|
# from myapp import mymodel
|
||||||
|
# target_metadata = mymodel.Base.metadata
|
||||||
|
# target_metadata = None
|
||||||
|
target_metadata = Base.metadata
|
||||||
|
|
||||||
|
# other values from the config, defined by the needs of env.py,
|
||||||
|
# can be acquired:
|
||||||
|
# my_important_option = config.get_main_option("my_important_option")
|
||||||
|
# ... etc.
|
||||||
|
|
||||||
|
|
||||||
|
def do_run_migrations(
|
||||||
|
connection: Connection,
|
||||||
|
):
|
||||||
|
context.configure(connection=connection, target_metadata=target_metadata)
|
||||||
|
|
||||||
|
with context.begin_transaction():
|
||||||
|
context.run_migrations()
|
||||||
|
|
||||||
|
async def run_async_migrations():
|
||||||
|
"""In this scenario we need to create an Engine
|
||||||
|
and associate a connection with the context.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
logger.info(dict(msg='started'))
|
||||||
|
connectable = async_engine_from_config(
|
||||||
|
config.get_section(config.config_ini_section, {}),
|
||||||
|
prefix="sqlalchemy.",
|
||||||
|
poolclass=pool.NullPool,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async with connectable.connect() as connection:
|
||||||
|
await connection.run_sync(do_run_migrations)
|
||||||
|
|
||||||
|
await connectable.dispose()
|
||||||
|
|
||||||
|
logger.info(dict(msg='done'))
|
||||||
|
|
||||||
|
|
||||||
|
def run_migrations_offline():
|
||||||
|
"""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 = config.get_main_option("sqlalchemy.url")
|
||||||
|
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():
|
||||||
|
"""Run migrations in 'online' mode."""
|
||||||
|
|
||||||
|
asyncio.run(run_async_migrations())
|
||||||
|
|
||||||
|
|
||||||
|
if context.is_offline_mode():
|
||||||
|
raise NotImplementedError
|
||||||
|
# run_migrations_offline()
|
||||||
|
else:
|
||||||
|
run_migrations_online()
|
||||||
28
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/tickers/alembic/script.py.mako
vendored
Normal file
28
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/tickers/alembic/script.py.mako
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
"""${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, Sequence[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:
|
||||||
|
"""Upgrade schema."""
|
||||||
|
${upgrades if upgrades else "pass"}
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
"""Downgrade schema."""
|
||||||
|
${downgrades if downgrades else "pass"}
|
||||||
36
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/tickers/alembic/versions/335b4c4f052c_add_market_table.py
vendored
Normal file
36
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/tickers/alembic/versions/335b4c4f052c_add_market_table.py
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
"""add Market table
|
||||||
|
|
||||||
|
Revision ID: 335b4c4f052c
|
||||||
|
Revises:
|
||||||
|
Create Date: 2025-07-04 11:31:10.983947
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = '335b4c4f052c'
|
||||||
|
down_revision: Union[str, Sequence[str], None] = None
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
"""Upgrade schema."""
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table('tickers_market',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('name', sa.String(length=32), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
"""Downgrade schema."""
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_table('tickers_market')
|
||||||
|
# ### end Alembic commands ###
|
||||||
38
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/tickers/alembic/versions/729afc7194c9_add_timezone.py
vendored
Normal file
38
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/tickers/alembic/versions/729afc7194c9_add_timezone.py
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
"""add timezone
|
||||||
|
|
||||||
|
Revision ID: 729afc7194c9
|
||||||
|
Revises: eb63f793db3a
|
||||||
|
Create Date: 2025-07-11 11:30:06.246152
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import postgresql
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = '729afc7194c9'
|
||||||
|
down_revision: Union[str, Sequence[str], None] = 'eb63f793db3a'
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
"""Upgrade schema."""
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.alter_column('tickers_ticker', 'timestamp',
|
||||||
|
existing_type=postgresql.TIMESTAMP(),
|
||||||
|
type_=sa.DateTime(timezone=True),
|
||||||
|
existing_nullable=False)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
"""Downgrade schema."""
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.alter_column('tickers_ticker', 'timestamp',
|
||||||
|
existing_type=sa.DateTime(timezone=True),
|
||||||
|
type_=postgresql.TIMESTAMP(),
|
||||||
|
existing_nullable=False)
|
||||||
|
# ### end Alembic commands ###
|
||||||
38
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/tickers/alembic/versions/eb63f793db3a_add_ticker_table.py
vendored
Normal file
38
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/tickers/alembic/versions/eb63f793db3a_add_ticker_table.py
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
"""add Ticker table
|
||||||
|
|
||||||
|
Revision ID: eb63f793db3a
|
||||||
|
Revises: 335b4c4f052c
|
||||||
|
Create Date: 2025-07-07 10:32:49.812738
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = 'eb63f793db3a'
|
||||||
|
down_revision: Union[str, Sequence[str], None] = '335b4c4f052c'
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
"""Upgrade schema."""
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table('tickers_ticker',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('timestamp', sa.DateTime(), nullable=False),
|
||||||
|
sa.Column('value', sa.Numeric(precision=32, scale=6), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(['id'], ['tickers_market.id'], ondelete='CASCADE'),
|
||||||
|
sa.UniqueConstraint('id', 'timestamp')
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
"""Downgrade schema."""
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_table('tickers_ticker')
|
||||||
|
# ### end Alembic commands ###
|
||||||
83
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/tickers/logic.py
vendored
Normal file
83
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/tickers/logic.py
vendored
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import datetime
|
||||||
|
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from sqlalchemy.ext.asyncio import async_sessionmaker
|
||||||
|
from sqlalchemy.orm import selectinload, make_transient
|
||||||
|
from sqlalchemy.future import select
|
||||||
|
|
||||||
|
from .models import Ticker, Market
|
||||||
|
from .utils import get_or_create
|
||||||
|
|
||||||
|
async def markets_get_by_symbol(
|
||||||
|
session: 'async_sessionmaker[AsyncSession]',
|
||||||
|
symbols: set[str],
|
||||||
|
) -> dict[str, int]:
|
||||||
|
res : dict[str, int] = dict()
|
||||||
|
|
||||||
|
async with session() as active_session:
|
||||||
|
async with active_session.begin() as transaction:
|
||||||
|
for o in symbols:
|
||||||
|
m = (await get_or_create(
|
||||||
|
active_session,
|
||||||
|
Market,
|
||||||
|
name=o,
|
||||||
|
))[0]
|
||||||
|
res[o] = m.id
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
async def ticker_store_multiple(
|
||||||
|
session: 'async_sessionmaker[AsyncSession]',
|
||||||
|
tickers: list[Ticker],
|
||||||
|
) -> None:
|
||||||
|
async with session() as active_session:
|
||||||
|
async with active_session.begin() as transaction:
|
||||||
|
active_session.add_all(
|
||||||
|
tickers,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def tickers_get_by_period(
|
||||||
|
session: 'async_sessionmaker[AsyncSession]',
|
||||||
|
market_id: int,
|
||||||
|
period: datetime.timedelta,
|
||||||
|
) -> list[Ticker]:
|
||||||
|
async with session() as active_session:
|
||||||
|
async with active_session.begin() as transaction:
|
||||||
|
q = select(
|
||||||
|
Ticker
|
||||||
|
).join(Ticker.market).where(
|
||||||
|
Market.id == market_id,
|
||||||
|
Ticker.timestamp >= datetime.datetime.now(
|
||||||
|
tz=datetime.timezone.utc
|
||||||
|
) - period
|
||||||
|
).order_by(Ticker.timestamp.desc()).options(
|
||||||
|
selectinload(Ticker.market)
|
||||||
|
)
|
||||||
|
|
||||||
|
res = await active_session.execute(q)
|
||||||
|
|
||||||
|
rows = [o[0] for o in res]
|
||||||
|
|
||||||
|
for o in rows:
|
||||||
|
active_session.expunge(o)
|
||||||
|
make_transient(o.market)
|
||||||
|
|
||||||
|
return rows
|
||||||
|
|
||||||
|
async def markets_all(
|
||||||
|
session: 'async_sessionmaker[AsyncSession]',
|
||||||
|
) -> list[Market]:
|
||||||
|
async with session() as active_session:
|
||||||
|
async with active_session.begin() as transaction:
|
||||||
|
q = select(
|
||||||
|
Market
|
||||||
|
)
|
||||||
|
|
||||||
|
res = await active_session.execute(q)
|
||||||
|
|
||||||
|
rows = [o[0] for o in res]
|
||||||
|
|
||||||
|
for o in rows:
|
||||||
|
active_session.expunge(o)
|
||||||
|
|
||||||
|
return rows
|
||||||
63
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/tickers/models.py
vendored
Normal file
63
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/tickers/models.py
vendored
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import datetime
|
||||||
|
import decimal
|
||||||
|
|
||||||
|
from sqlalchemy.orm import (
|
||||||
|
mapped_column,
|
||||||
|
Mapped,
|
||||||
|
DeclarativeBase,
|
||||||
|
relationship,
|
||||||
|
)
|
||||||
|
from sqlalchemy import (
|
||||||
|
String,
|
||||||
|
ForeignKey,
|
||||||
|
Numeric,
|
||||||
|
DateTime,
|
||||||
|
UniqueConstraint,
|
||||||
|
)
|
||||||
|
|
||||||
|
from typing import (Optional,)
|
||||||
|
|
||||||
|
class Base(DeclarativeBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Market(Base):
|
||||||
|
__tablename__ = 'tickers_market'
|
||||||
|
|
||||||
|
id: Mapped[int] = mapped_column(primary_key=True)
|
||||||
|
name: Mapped[str] = mapped_column(String(32))
|
||||||
|
|
||||||
|
tickers: Mapped[list['Ticker']] = relationship(
|
||||||
|
back_populates='market',
|
||||||
|
)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"Market(id={self.id!r}, name={self.name!r})"
|
||||||
|
|
||||||
|
class Ticker(Base):
|
||||||
|
__tablename__ = 'tickers_ticker'
|
||||||
|
|
||||||
|
id: Mapped[int] = mapped_column(ForeignKey(
|
||||||
|
'tickers_market.id',
|
||||||
|
ondelete='CASCADE',
|
||||||
|
))
|
||||||
|
market: Mapped['Market'] = relationship(
|
||||||
|
back_populates='tickers'
|
||||||
|
)
|
||||||
|
|
||||||
|
timestamp: Mapped[datetime.datetime] = mapped_column(
|
||||||
|
DateTime(timezone=True,)
|
||||||
|
)
|
||||||
|
value: Mapped[decimal.Decimal] = mapped_column(Numeric(
|
||||||
|
precision=32, scale=6,
|
||||||
|
))
|
||||||
|
|
||||||
|
__table_args__ = (
|
||||||
|
UniqueConstraint('id', 'timestamp'),
|
||||||
|
)
|
||||||
|
|
||||||
|
__mapper_args__ = dict(
|
||||||
|
primary_key=('id', 'timestamp',)
|
||||||
|
)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"Ticker(id={self.id!r}, timestamp={self.timestamp!r}, value={self.value!r})"
|
||||||
17
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/tickers/settings.py
vendored
Normal file
17
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/tickers/settings.py
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import pydantic
|
||||||
|
import pydantic_settings
|
||||||
|
|
||||||
|
from typing import (ClassVar, Optional,)
|
||||||
|
|
||||||
|
|
||||||
|
class Settings(pydantic_settings.BaseSettings):
|
||||||
|
db_url : str
|
||||||
|
|
||||||
|
_singleton : ClassVar[Optional['Settings']] = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def singleton(cls) -> 'Settings':
|
||||||
|
if cls._singleton is None:
|
||||||
|
cls._singleton = Settings.model_validate({})
|
||||||
|
|
||||||
|
return cls._singleton
|
||||||
50
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/tickers/utils.py
vendored
Normal file
50
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/tickers/utils.py
vendored
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
from typing import (TypeVar, Optional, Any, cast,)
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSessionTransaction, AsyncSession
|
||||||
|
from sqlalchemy.future import select
|
||||||
|
from sqlalchemy.orm import DeclarativeBase
|
||||||
|
from sqlalchemy.exc import NoResultFound, IntegrityError
|
||||||
|
|
||||||
|
M = TypeVar('M', bound='DeclarativeBase')
|
||||||
|
|
||||||
|
async def get_or_create(
|
||||||
|
session: AsyncSession,
|
||||||
|
model: type[M],
|
||||||
|
create_method: Optional[str] = None,
|
||||||
|
create_method_kwargs: Optional[dict[str, Any]] = None,
|
||||||
|
**kwargs: Any
|
||||||
|
) -> tuple[M, bool]:
|
||||||
|
async def select_row() -> M:
|
||||||
|
res = await session.execute(
|
||||||
|
select(model).where(
|
||||||
|
*[
|
||||||
|
getattr(model, k) == v
|
||||||
|
for k, v in kwargs.items()
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
row = res.one()[0]
|
||||||
|
|
||||||
|
assert isinstance(row, model)
|
||||||
|
|
||||||
|
return row
|
||||||
|
|
||||||
|
try:
|
||||||
|
res = await select_row()
|
||||||
|
return res, False
|
||||||
|
except NoResultFound:
|
||||||
|
if create_method_kwargs:
|
||||||
|
kwargs.update(create_method_kwargs)
|
||||||
|
|
||||||
|
if not create_method:
|
||||||
|
created = model(**kwargs)
|
||||||
|
else:
|
||||||
|
created = getattr(model, create_method)(**kwargs)
|
||||||
|
|
||||||
|
try:
|
||||||
|
session.add(created)
|
||||||
|
await session.flush()
|
||||||
|
return created, True
|
||||||
|
except IntegrityError:
|
||||||
|
await session.rollback()
|
||||||
|
return await select_row(), False
|
||||||
0
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/tickers_retrieval/__init__.py
vendored
Normal file
0
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/tickers_retrieval/__init__.py
vendored
Normal file
165
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/tickers_retrieval/emcont.py
vendored
Normal file
165
deps/test-task-2025-06-30-v1/python/online/fxreader/pr34/test_task_2025_06_30_v1/tickers_retrieval/emcont.py
vendored
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
import aiohttp
|
||||||
|
import asyncio
|
||||||
|
import decimal
|
||||||
|
import logging
|
||||||
|
import datetime
|
||||||
|
# import datetime.timezone
|
||||||
|
import pydantic
|
||||||
|
import json
|
||||||
|
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from sqlalchemy.ext.asyncio import async_sessionmaker
|
||||||
|
|
||||||
|
from typing import (
|
||||||
|
Any, Annotated, Optional, Awaitable, Callable,
|
||||||
|
Protocol,
|
||||||
|
)
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class Emcont:
|
||||||
|
class rates_get_t:
|
||||||
|
class data_t(pydantic.BaseModel):
|
||||||
|
class rate_t(pydantic.BaseModel):
|
||||||
|
symbol: Annotated[
|
||||||
|
str,
|
||||||
|
pydantic.Field(
|
||||||
|
alias='Symbol',
|
||||||
|
)
|
||||||
|
]
|
||||||
|
bid: Annotated[
|
||||||
|
decimal.Decimal,
|
||||||
|
pydantic.Field(
|
||||||
|
alias='Bid',
|
||||||
|
)
|
||||||
|
]
|
||||||
|
ask: Annotated[
|
||||||
|
decimal.Decimal,
|
||||||
|
pydantic.Field(
|
||||||
|
alias='Ask',
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
@pydantic.computed_field
|
||||||
|
def value(self) -> decimal.Decimal:
|
||||||
|
return (self.ask + self.bid) / 2
|
||||||
|
|
||||||
|
product_type: Annotated[
|
||||||
|
str,
|
||||||
|
pydantic.Field(
|
||||||
|
alias='ProductType',
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
rates: Annotated[
|
||||||
|
list[rate_t],
|
||||||
|
pydantic.Field(
|
||||||
|
alias='Rates',
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def rates_get(
|
||||||
|
cls,
|
||||||
|
only_symbols: Optional[set[str]] = None,
|
||||||
|
) -> Any:
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.get('https://rates.emcont.com') as response:
|
||||||
|
data_json = await response.text()
|
||||||
|
data = cls.rates_get_t.data_t.model_validate_json(
|
||||||
|
data_json[5:-3],
|
||||||
|
)
|
||||||
|
|
||||||
|
if only_symbols:
|
||||||
|
data.rates = [
|
||||||
|
o
|
||||||
|
for o in data.rates
|
||||||
|
if o.symbol in only_symbols
|
||||||
|
]
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
class store_cb_t(Protocol):
|
||||||
|
async def __call__(
|
||||||
|
self,
|
||||||
|
rates: list['Emcont.rates_get_t.data_t.rate_t'],
|
||||||
|
timestamp: datetime.datetime,
|
||||||
|
session: 'async_sessionmaker[AsyncSession]',
|
||||||
|
) -> None: ...
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def worker(
|
||||||
|
cls,
|
||||||
|
session: 'async_sessionmaker[AsyncSession]',
|
||||||
|
store_cb: 'Emcont.store_cb_t',
|
||||||
|
only_symbols: Optional[set[str]] = None,
|
||||||
|
request_timeout: float | int = 0.5,
|
||||||
|
store_timeout: float | int = 0.5,
|
||||||
|
request_period: float | int = 1,
|
||||||
|
) -> None:
|
||||||
|
last_retrieval = datetime.datetime.now(
|
||||||
|
tz=datetime.timezone.utc,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert request_timeout >= 0
|
||||||
|
assert store_timeout >= 0
|
||||||
|
|
||||||
|
request_period_timedelta = datetime.timedelta(
|
||||||
|
seconds=request_period,
|
||||||
|
)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
logger.info(dict(msg='started'))
|
||||||
|
|
||||||
|
entries : Optional['Emcont.rates_get_t.data_t'] = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
async with asyncio.timeout(request_timeout):
|
||||||
|
entries = await cls.rates_get(
|
||||||
|
only_symbols=only_symbols,
|
||||||
|
)
|
||||||
|
except TimeoutError:
|
||||||
|
logger.exception('request timeout')
|
||||||
|
|
||||||
|
try:
|
||||||
|
async with asyncio.timeout(store_timeout):
|
||||||
|
if entries:
|
||||||
|
await store_cb(
|
||||||
|
rates=entries.rates,
|
||||||
|
timestamp=last_retrieval,
|
||||||
|
session=session,
|
||||||
|
)
|
||||||
|
except TimeoutError:
|
||||||
|
logger.exception('store timeout')
|
||||||
|
except:
|
||||||
|
logger.exception('')
|
||||||
|
|
||||||
|
next_retrieval = last_retrieval
|
||||||
|
|
||||||
|
def wait_interval():
|
||||||
|
nonlocal next_retrieval
|
||||||
|
|
||||||
|
return (
|
||||||
|
next_retrieval - datetime.datetime.now(
|
||||||
|
tz=datetime.timezone.utc,
|
||||||
|
)
|
||||||
|
).total_seconds()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
next_retrieval += request_period_timedelta
|
||||||
|
|
||||||
|
if (
|
||||||
|
wait_interval() > 0 or
|
||||||
|
wait_interval() > -request_period_timedelta.total_seconds() / 4
|
||||||
|
):
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
logger.warning(dict(
|
||||||
|
msg='skip period due to huge lag',
|
||||||
|
))
|
||||||
|
|
||||||
|
if wait_interval() > 0:
|
||||||
|
await asyncio.sleep(wait_interval())
|
||||||
|
|
||||||
|
last_retrieval = next_retrieval
|
||||||
BIN
deps/test-task-2025-06-30-v1/releases/whl/online_fxreader_pr34_test_task_2025_06_30_v1-0.1-py3-none-any.whl
(Stored with Git LFS)
vendored
Normal file
BIN
deps/test-task-2025-06-30-v1/releases/whl/online_fxreader_pr34_test_task_2025_06_30_v1-0.1-py3-none-any.whl
(Stored with Git LFS)
vendored
Normal file
Binary file not shown.
BIN
deps/test-task-2025-06-30-v1/releases/whl/online_fxreader_pr34_test_task_2025_06_30_v1-0.1.1-py3-none-any.whl
(Stored with Git LFS)
vendored
Normal file
BIN
deps/test-task-2025-06-30-v1/releases/whl/online_fxreader_pr34_test_task_2025_06_30_v1-0.1.1-py3-none-any.whl
(Stored with Git LFS)
vendored
Normal file
Binary file not shown.
27
deps/test-task-2025-06-30-v1/requirements.in
vendored
Normal file
27
deps/test-task-2025-06-30-v1/requirements.in
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
alembic
|
||||||
|
fastapi
|
||||||
|
uvicorn
|
||||||
|
websockets
|
||||||
|
uvloop
|
||||||
|
tomlq
|
||||||
|
mypy
|
||||||
|
marisa-trie
|
||||||
|
pydantic
|
||||||
|
asyncpg
|
||||||
|
pydantic-settings
|
||||||
|
tomlkit
|
||||||
|
tomlq
|
||||||
|
numpy
|
||||||
|
cryptography
|
||||||
|
mypy
|
||||||
|
pyright
|
||||||
|
ruff
|
||||||
|
ipython
|
||||||
|
ipdb
|
||||||
|
requests
|
||||||
|
types-requests
|
||||||
|
aiohttp
|
||||||
|
build
|
||||||
|
wheel
|
||||||
|
setuptools
|
||||||
|
setuptools-scm
|
||||||
1723
deps/test-task-2025-06-30-v1/requirements.txt
vendored
Normal file
1723
deps/test-task-2025-06-30-v1/requirements.txt
vendored
Normal file
File diff suppressed because it is too large
Load Diff
5
deps/test-task-2025-07-17-v2/.dockerignore
vendored
Normal file
5
deps/test-task-2025-07-17-v2/.dockerignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
.venv
|
||||||
|
tmp
|
||||||
|
.git
|
||||||
|
.env
|
||||||
|
build
|
||||||
1
deps/test-task-2025-07-17-v2/.gitattributes
vendored
Normal file
1
deps/test-task-2025-07-17-v2/.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
releases/whl/** filter=lfs diff=lfs merge=lfs -text
|
||||||
6
deps/test-task-2025-07-17-v2/.gitignore
vendored
Normal file
6
deps/test-task-2025-07-17-v2/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
!.tmuxp/
|
||||||
|
!python
|
||||||
|
.env/
|
||||||
|
releases/tar
|
||||||
|
build
|
||||||
|
!releases/whl/**
|
||||||
9
deps/test-task-2025-07-17-v2/.tmuxp/v1.yaml
vendored
Normal file
9
deps/test-task-2025-07-17-v2/.tmuxp/v1.yaml
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
session_name: test-task-2025-07-17-v2
|
||||||
|
start_directory: ${PWD}/deps/test-task-2025-07-17-v2
|
||||||
|
windows:
|
||||||
|
- focus: 'true'
|
||||||
|
layout: 5687,98x12,0,0,18
|
||||||
|
options: {}
|
||||||
|
panes:
|
||||||
|
- pane
|
||||||
|
window_name: zsh
|
||||||
99
deps/test-task-2025-07-17-v2/Makefile
vendored
Normal file
99
deps/test-task-2025-07-17-v2/Makefile
vendored
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
ENV_PATH ?= .venv
|
||||||
|
PYTHON_PATH = $(ENV_PATH)/bin/python3
|
||||||
|
PYTHON_VERSION ?= 3.12.9
|
||||||
|
UV_ARGS ?= --offline
|
||||||
|
DOCKER ?= podman
|
||||||
|
COMPOSE ?= podman compose
|
||||||
|
|
||||||
|
venv_extract_requirements:
|
||||||
|
$(ENV_PATH)/bin/tomlq \
|
||||||
|
-r '.project.dependencies | join("\n")' \
|
||||||
|
pyproject.toml > requirements.in
|
||||||
|
|
||||||
|
venv_compile:
|
||||||
|
for requirements_name in requirements requirements.torch; do\
|
||||||
|
uv pip compile \
|
||||||
|
$(UV_ARGS) \
|
||||||
|
-p $(PYTHON_VERSION) \
|
||||||
|
--generate-hashes \
|
||||||
|
$$requirements_name.in > \
|
||||||
|
$$requirements_name.txt; \
|
||||||
|
cat $$requirements_name.in | grep 'index-url' >> $$requirements_name.txt; \
|
||||||
|
done
|
||||||
|
|
||||||
|
|
||||||
|
venv:
|
||||||
|
uv \
|
||||||
|
venv \
|
||||||
|
-p 3.13 \
|
||||||
|
$(UV_ARGS) \
|
||||||
|
--seed \
|
||||||
|
$(ENV_PATH)
|
||||||
|
uv \
|
||||||
|
pip install \
|
||||||
|
$(UV_ARGS) \
|
||||||
|
-p $(ENV_PATH) \
|
||||||
|
-r requirements.txt
|
||||||
|
|
||||||
|
PYRIGHT_ARGS ?= --threads 3
|
||||||
|
|
||||||
|
pyright:
|
||||||
|
$(ENV_PATH)/bin/python3 -m pyright \
|
||||||
|
-p pyproject.toml \
|
||||||
|
--pythonpath $(PYTHON_PATH) \
|
||||||
|
$(PYRIGHT_ARGS)
|
||||||
|
|
||||||
|
pyright_watch:
|
||||||
|
make \
|
||||||
|
PYRIGHT_ARGS=-w \
|
||||||
|
pyright
|
||||||
|
|
||||||
|
ruff_check:
|
||||||
|
$(ENV_PATH)/bin/python3 -m ruff \
|
||||||
|
check
|
||||||
|
|
||||||
|
ruff_format_check:
|
||||||
|
$(ENV_PATH)/bin/python3 -m ruff \
|
||||||
|
format --check
|
||||||
|
|
||||||
|
ruff_format:
|
||||||
|
$(ENV_PATH)/bin/python3 -m ruff \
|
||||||
|
format
|
||||||
|
|
||||||
|
ruff: ruff_format_check ruff_check
|
||||||
|
|
||||||
|
compose_env:
|
||||||
|
cat docker/postgresql/.env .env/postgresql.env > .env/postgresql.patched.env
|
||||||
|
cat docker/web/.env .env/web.env > .env/web.patched.env
|
||||||
|
for app in summarizer payloads; do \
|
||||||
|
cat docker/web/$$app.env .env/$$app.env > .env/$$app.patched.env; \
|
||||||
|
done
|
||||||
|
|
||||||
|
compose_build_web:
|
||||||
|
$(COMPOSE) build web
|
||||||
|
|
||||||
|
compose_build_summarizer:
|
||||||
|
$(COMPOSE) build summarizer
|
||||||
|
|
||||||
|
compose_build_payloads:
|
||||||
|
$(COMPOSE) build payloads
|
||||||
|
|
||||||
|
git-release:
|
||||||
|
mkdir -p releases/tar
|
||||||
|
git archive \
|
||||||
|
--format=tar \
|
||||||
|
-o "releases/tar/repo-$$(git describe --tags).tar" \
|
||||||
|
HEAD
|
||||||
|
|
||||||
|
ALEMBIC_CMD ?= --help
|
||||||
|
alembic:
|
||||||
|
$(ENV_PATH)/bin/alembic \
|
||||||
|
-c pyproject.toml \
|
||||||
|
$(ALEMBIC_CMD)
|
||||||
|
|
||||||
|
deploy_wheel:
|
||||||
|
make pyright
|
||||||
|
make deploy_wheel_unsafe
|
||||||
|
|
||||||
|
deploy_wheel_unsafe:
|
||||||
|
$(PYTHON_PATH) -m build -o releases/whl -w -n
|
||||||
88
deps/test-task-2025-07-17-v2/docker-compose.yml
vendored
Normal file
88
deps/test-task-2025-07-17-v2/docker-compose.yml
vendored
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
services:
|
||||||
|
redis:
|
||||||
|
image: docker.io/redis:latest-alpine@sha256:e71b4cb00ea461ac21114cff40ff12fb8396914238e1e9ec41520b2d5a4d3423
|
||||||
|
ports:
|
||||||
|
- 127.0.0.1:9004:6379
|
||||||
|
|
||||||
|
web: &web
|
||||||
|
image: online.fxreader.pr34.test_task_2025_07_17_v2.web:dev
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ./docker/web/Dockerfile
|
||||||
|
target: web
|
||||||
|
env_file: .env/web.env
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: 10m
|
||||||
|
max-file: "3"
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpus: '0.5'
|
||||||
|
memory: 128M
|
||||||
|
|
||||||
|
web-dev:
|
||||||
|
<<: *web
|
||||||
|
volumes:
|
||||||
|
- .:/app:ro
|
||||||
|
- ./tmp/cache:/app/tmp/cache:rw
|
||||||
|
|
||||||
|
payloads:
|
||||||
|
<<: *web
|
||||||
|
image: online.fxreader.pr34.test_task_2025_07_17_v2.payloads:dev
|
||||||
|
env_file: .env/payloads.patched.env
|
||||||
|
ports:
|
||||||
|
- 127.0.0.1:9003:80
|
||||||
|
|
||||||
|
summarizer:
|
||||||
|
<<: *web
|
||||||
|
image: online.fxreader.pr34.test_task_2025_07_17_v2.summarizer:dev
|
||||||
|
env_file: .env/summarizer.patched.env
|
||||||
|
# ports:
|
||||||
|
# - 127.0.0.1:9003:80
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpus: '4'
|
||||||
|
memory: 1500M
|
||||||
|
volumes:
|
||||||
|
- ~/.cache/huggingface/hub:/root/.cache/huggingface/hub:ro
|
||||||
|
|
||||||
|
postgresql:
|
||||||
|
image: docker.io/postgres:14.18-bookworm@sha256:c0aab7962b283cf24a0defa5d0d59777f5045a7be59905f21ba81a20b1a110c9
|
||||||
|
# restart: always
|
||||||
|
# set shared memory limit when using docker compose
|
||||||
|
shm_size: 128mb
|
||||||
|
volumes:
|
||||||
|
- postgresql_data:/var/lib/postgresql/data/:rw
|
||||||
|
# or set shared memory limit when deploy via swarm stack
|
||||||
|
#volumes:
|
||||||
|
# - type: tmpfs
|
||||||
|
# target: /dev/shm
|
||||||
|
# tmpfs:
|
||||||
|
# size: 134217728 # 128*2^20 bytes = 128Mb
|
||||||
|
env_file: .env/postgresql.patched.env
|
||||||
|
# environment:
|
||||||
|
# POSTGRES_PASSWORD: example
|
||||||
|
ports:
|
||||||
|
- 127.0.0.1:9002:5432
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: 10m
|
||||||
|
max-file: "3"
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpus: '0.5'
|
||||||
|
memory: 128M
|
||||||
|
|
||||||
|
adminer:
|
||||||
|
image: docker.io/adminer:standalone@sha256:730215fe535daca9a2f378c48321bc615c8f0d88668721e0eff530fa35b6e8f6
|
||||||
|
ports:
|
||||||
|
- 127.0.0.1:9001:8080
|
||||||
|
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgresql_data:
|
||||||
3
deps/test-task-2025-07-17-v2/docker/postgresql/.env
vendored
Normal file
3
deps/test-task-2025-07-17-v2/docker/postgresql/.env
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
PGDATA=/var/lib/postgresql/data/pgdata
|
||||||
|
POSTGRES_USER=payloads
|
||||||
|
POSTGRES_DB=payloads
|
||||||
1
deps/test-task-2025-07-17-v2/docker/web/.env
vendored
Normal file
1
deps/test-task-2025-07-17-v2/docker/web/.env
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
# DB_URL=
|
||||||
55
deps/test-task-2025-07-17-v2/docker/web/Dockerfile
vendored
Normal file
55
deps/test-task-2025-07-17-v2/docker/web/Dockerfile
vendored
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
FROM docker.io/library/python:3.12@sha256:6121c801703ec330726ebf542faab113efcfdf2236378c03df8f49d80e7b4180 AS base
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY docker/web/apt.requirements.txt docker/web/apt.requirements.txt
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y $(cat docker/web/apt.requirements.txt)
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
pip3 install \
|
||||||
|
--break-system-packages uv
|
||||||
|
|
||||||
|
RUN apt-get update -yy && apt-get install -yy tini
|
||||||
|
|
||||||
|
COPY requirements.txt .
|
||||||
|
COPY requirements.torch.txt .
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
# --mount=type=bind,source=releases/whl,target=/app/releases/whl \
|
||||||
|
--mount=type=cache,target=/root/.cache/pip \
|
||||||
|
--mount=type=cache,target=/root/.cache/uv \
|
||||||
|
( \
|
||||||
|
for requirements in requirements*.txt; do \
|
||||||
|
uv pip \
|
||||||
|
install \
|
||||||
|
--system \
|
||||||
|
--break-system-packages \
|
||||||
|
-r $requirements; \
|
||||||
|
done; \
|
||||||
|
)
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
FROM base as web
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
--mount=type=bind,source=releases/whl,target=/app/releases/whl \
|
||||||
|
--mount=type=cache,target=/root/.cache/pip \
|
||||||
|
--mount=type=cache,target=/root/.cache/uv \
|
||||||
|
uv pip \
|
||||||
|
install \
|
||||||
|
--system \
|
||||||
|
--break-system-packages \
|
||||||
|
--no-index \
|
||||||
|
-f releases/whl \
|
||||||
|
'online.fxreader.pr34.test_task_2025_07_17_v2==0.1.15'
|
||||||
|
|
||||||
|
ENTRYPOINT ["tini", "--"]
|
||||||
|
CMD [ \
|
||||||
|
"python3", \
|
||||||
|
"-m", \
|
||||||
|
"online.fxreader.pr34.test_task_2025_07_17_v2.async_api.fastapi" \
|
||||||
|
]
|
||||||
1
deps/test-task-2025-07-17-v2/docker/web/apt.requirements.txt
vendored
Normal file
1
deps/test-task-2025-07-17-v2/docker/web/apt.requirements.txt
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
wget tar git curl
|
||||||
5
deps/test-task-2025-07-17-v2/docker/web/payloads.env
vendored
Normal file
5
deps/test-task-2025-07-17-v2/docker/web/payloads.env
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
APPS=["online.fxreader.pr34.test_task_2025_07_17_v2.payloads.app:get_app_router:"]
|
||||||
|
UVICORN_HOST=0.0.0.0
|
||||||
|
UVICORN_PORT=80
|
||||||
|
SUMMARIZER_DOMAIN=summarizer
|
||||||
|
SUMMARIZER_PROTOCOL=http
|
||||||
5
deps/test-task-2025-07-17-v2/docker/web/summarizer.env
vendored
Normal file
5
deps/test-task-2025-07-17-v2/docker/web/summarizer.env
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
APPS=["online.fxreader.pr34.test_task_2025_07_17_v2.transform.app:get_app_router:"]
|
||||||
|
UVICORN_HOST=0.0.0.0
|
||||||
|
UVICORN_PORT=80
|
||||||
|
SUMMARIZER_DOMAIN=summarizer
|
||||||
|
SUMMARIZER_PROTOCOL=http
|
||||||
34
deps/test-task-2025-07-17-v2/docs/readme.md
vendored
Normal file
34
deps/test-task-2025-07-17-v2/docs/readme.md
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# Requirements
|
||||||
|
|
||||||
|
1. FastAPI Microservice // Caching service
|
||||||
|
|
||||||
|
1.1. endpoints
|
||||||
|
1.1.1 POST:/payload `payload_create`
|
||||||
|
1.1.2. GET:/payload/<payload_id> `payload_read`
|
||||||
|
1.2. tech specs
|
||||||
|
1.2.1. fastapi for rest api
|
||||||
|
1.2.2. sqlite/postgresql for DB that caches LLM replies;
|
||||||
|
1.2.3. LLM transform can be stubbed (idk, maybe try to find something simple);
|
||||||
|
1.2.4. docker-compose for services
|
||||||
|
1.2.5. add pytest based tests;
|
||||||
|
1.2.6. add some linters for code style, and type checking;
|
||||||
|
|
||||||
|
# Schemas
|
||||||
|
```yaml
|
||||||
|
endpoints:
|
||||||
|
payload:
|
||||||
|
create:
|
||||||
|
request:
|
||||||
|
list_1: list[str]
|
||||||
|
list_2: list[str]
|
||||||
|
response:
|
||||||
|
payload:
|
||||||
|
id: int
|
||||||
|
output: list[str]
|
||||||
|
read:
|
||||||
|
request:
|
||||||
|
id: int
|
||||||
|
response:
|
||||||
|
id: int
|
||||||
|
output: list[str]
|
||||||
|
```
|
||||||
230
deps/test-task-2025-07-17-v2/pyproject.toml
vendored
Normal file
230
deps/test-task-2025-07-17-v2/pyproject.toml
vendored
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
[project]
|
||||||
|
description = 'test task for LLM replies caching'
|
||||||
|
requires-python = '>= 3.10'
|
||||||
|
maintainers = [
|
||||||
|
{ name = 'Siarhei Siniak', email = 'siarheisiniak@gmail.com' },
|
||||||
|
]
|
||||||
|
classifiers = [
|
||||||
|
'Programming Language :: Python',
|
||||||
|
]
|
||||||
|
|
||||||
|
name = 'online.fxreader.pr34.test_task_2025_07_17_v2'
|
||||||
|
version = '0.1.15'
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
'alembic',
|
||||||
|
'fastapi',
|
||||||
|
'uvicorn',
|
||||||
|
'websockets',
|
||||||
|
'uvloop',
|
||||||
|
'tomlq',
|
||||||
|
'mypy',
|
||||||
|
'marisa-trie',
|
||||||
|
'pydantic',
|
||||||
|
'asyncpg',
|
||||||
|
'pydantic-settings',
|
||||||
|
'tomlkit',
|
||||||
|
'tomlq',
|
||||||
|
'numpy',
|
||||||
|
'cryptography',
|
||||||
|
'mypy',
|
||||||
|
'pyright',
|
||||||
|
'ruff',
|
||||||
|
'ipython',
|
||||||
|
'ipdb',
|
||||||
|
'requests',
|
||||||
|
'types-requests',
|
||||||
|
'aiohttp',
|
||||||
|
'build',
|
||||||
|
'wheel',
|
||||||
|
'setuptools',
|
||||||
|
'setuptools-scm',
|
||||||
|
'transformers',
|
||||||
|
]
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ['build', 'wheel', 'setuptools', 'setuptools-scm']
|
||||||
|
build-backend = 'setuptools.build_meta'
|
||||||
|
|
||||||
|
[tool.setuptools]
|
||||||
|
include-package-data = false
|
||||||
|
[tool.setuptools.package-dir]
|
||||||
|
'online.fxreader.pr34.test_task_2025_07_17_v2' = 'python/online/fxreader/pr34/test_task_2025_07_17_v2'
|
||||||
|
|
||||||
|
[tool.alembic]
|
||||||
|
script_location = 'python/online/fxreader/pr34/test_task_2025_07_17_v2/payloads/alembic'
|
||||||
|
prepend_sys_path = ['python']
|
||||||
|
|
||||||
|
# sqlalchemy.url = 'asdfasdf:/asdfasdfa'
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
line-length = 160
|
||||||
|
target-version = 'py310'
|
||||||
|
# builtins = ['_', 'I', 'P']
|
||||||
|
include = [
|
||||||
|
# 'follow_the_leader/**/*.py',
|
||||||
|
#'*.py',
|
||||||
|
# '*.recipe',
|
||||||
|
'python/**/*.py',
|
||||||
|
'python/**/*.pyi',
|
||||||
|
]
|
||||||
|
exclude = [
|
||||||
|
'.venv',
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.ruff.format]
|
||||||
|
quote-style = 'single'
|
||||||
|
indent-style = 'tab'
|
||||||
|
skip-magic-trailing-comma = false
|
||||||
|
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
ignore = [
|
||||||
|
'E402', 'E722', 'E741', 'W191', 'E101', 'E501', 'I001', 'F401', 'E714',
|
||||||
|
'E713',
|
||||||
|
# remove lambdas later on
|
||||||
|
'E731',
|
||||||
|
# fix this too
|
||||||
|
'E712',
|
||||||
|
'E703',
|
||||||
|
# remove unused variables, or fix a bug
|
||||||
|
'F841',
|
||||||
|
# fix * imports
|
||||||
|
'F403',
|
||||||
|
# don't care about trailing new lines
|
||||||
|
'W292',
|
||||||
|
|
||||||
|
]
|
||||||
|
select = ['E', 'F', 'I', 'W', 'INT']
|
||||||
|
|
||||||
|
|
||||||
|
[tool.ruff.lint.isort]
|
||||||
|
detect-same-package = true
|
||||||
|
# extra-standard-library = ["aes", "elementmaker", "encodings"]
|
||||||
|
# known-first-party = ["calibre_extensions", "calibre_plugins", "polyglot"]
|
||||||
|
# known-third-party = ["odf", "qt", "templite", "tinycss", "css_selectors"]
|
||||||
|
relative-imports-order = "closest-to-furthest"
|
||||||
|
split-on-trailing-comma = true
|
||||||
|
section-order = [
|
||||||
|
# '__python__',
|
||||||
|
"future",
|
||||||
|
"standard-library", "third-party", "first-party", "local-folder"
|
||||||
|
]
|
||||||
|
force-wrap-aliases = true
|
||||||
|
|
||||||
|
# [tool.ruff.lint.isort.sections]
|
||||||
|
# '__python__' = ['__python__']
|
||||||
|
|
||||||
|
[tool.pylsp-mypy]
|
||||||
|
enabled = false
|
||||||
|
|
||||||
|
[tool.pyright]
|
||||||
|
include = [
|
||||||
|
#'../../../../../follow_the_leader/views2/payments.py',
|
||||||
|
#'../../../../../follow_the_leader/logic/payments.py',
|
||||||
|
#'../../../../../follow_the_leader/logic/paypal.py',
|
||||||
|
'python/**/*.py',
|
||||||
|
'python/**/*.pyi',
|
||||||
|
]
|
||||||
|
# stubPath = '../mypy-stubs'
|
||||||
|
extraPaths = [
|
||||||
|
]
|
||||||
|
#strict = ["src"]
|
||||||
|
|
||||||
|
analyzeUnannotatedFunctions = true
|
||||||
|
disableBytesTypePromotions = true
|
||||||
|
strictParameterNoneValue = true
|
||||||
|
enableTypeIgnoreComments = true
|
||||||
|
enableReachabilityAnalysis = true
|
||||||
|
strictListInference = true
|
||||||
|
strictDictionaryInference = true
|
||||||
|
strictSetInference = true
|
||||||
|
deprecateTypingAliases = false
|
||||||
|
enableExperimentalFeatures = false
|
||||||
|
reportMissingTypeStubs ="error"
|
||||||
|
reportMissingModuleSource = "warning"
|
||||||
|
reportInvalidTypeForm = "error"
|
||||||
|
reportMissingImports = "error"
|
||||||
|
reportUndefinedVariable = "error"
|
||||||
|
reportAssertAlwaysTrue = "error"
|
||||||
|
reportInvalidStringEscapeSequence = "error"
|
||||||
|
reportInvalidTypeVarUse = "error"
|
||||||
|
reportSelfClsParameterName = "error"
|
||||||
|
reportUnsupportedDunderAll = "error"
|
||||||
|
reportUnusedExpression = "error"
|
||||||
|
reportWildcardImportFromLibrary = "error"
|
||||||
|
reportAbstractUsage = "error"
|
||||||
|
reportArgumentType = "error"
|
||||||
|
reportAssertTypeFailure = "error"
|
||||||
|
reportAssignmentType = "error"
|
||||||
|
reportAttributeAccessIssue = "error"
|
||||||
|
reportCallIssue = "error"
|
||||||
|
reportGeneralTypeIssues = "error"
|
||||||
|
reportInconsistentOverload = "error"
|
||||||
|
reportIndexIssue = "error"
|
||||||
|
reportInvalidTypeArguments = "error"
|
||||||
|
reportNoOverloadImplementation = "error"
|
||||||
|
reportOperatorIssue = "error"
|
||||||
|
reportOptionalSubscript = "error"
|
||||||
|
reportOptionalMemberAccess = "error"
|
||||||
|
reportOptionalCall = "error"
|
||||||
|
reportOptionalIterable = "error"
|
||||||
|
reportOptionalContextManager = "error"
|
||||||
|
reportOptionalOperand = "error"
|
||||||
|
reportRedeclaration = "error"
|
||||||
|
reportReturnType = "error"
|
||||||
|
reportTypedDictNotRequiredAccess = "error"
|
||||||
|
reportPrivateImportUsage = "error"
|
||||||
|
reportUnboundVariable = "error"
|
||||||
|
reportUnhashable = "error"
|
||||||
|
reportUnusedCoroutine = "error"
|
||||||
|
reportUnusedExcept = "error"
|
||||||
|
reportFunctionMemberAccess = "error"
|
||||||
|
reportIncompatibleMethodOverride = "error"
|
||||||
|
reportIncompatibleVariableOverride = "error"
|
||||||
|
reportOverlappingOverload = "error"
|
||||||
|
reportPossiblyUnboundVariable = "error"
|
||||||
|
reportConstantRedefinition = "error"
|
||||||
|
#reportDeprecated = "error"
|
||||||
|
reportDeprecated = "warning"
|
||||||
|
reportDuplicateImport = "error"
|
||||||
|
reportIncompleteStub = "error"
|
||||||
|
reportInconsistentConstructor = "error"
|
||||||
|
reportInvalidStubStatement = "error"
|
||||||
|
reportMatchNotExhaustive = "error"
|
||||||
|
reportMissingParameterType = "error"
|
||||||
|
reportMissingTypeArgument = "error"
|
||||||
|
reportPrivateUsage = "error"
|
||||||
|
reportTypeCommentUsage = "error"
|
||||||
|
reportUnknownArgumentType = "error"
|
||||||
|
reportUnknownLambdaType = "error"
|
||||||
|
reportUnknownMemberType = "error"
|
||||||
|
reportUnknownParameterType = "error"
|
||||||
|
reportUnknownVariableType = "error"
|
||||||
|
#reportUnknownVariableType = "warning"
|
||||||
|
reportUnnecessaryCast = "error"
|
||||||
|
reportUnnecessaryComparison = "error"
|
||||||
|
reportUnnecessaryContains = "error"
|
||||||
|
#reportUnnecessaryIsInstance = "error"
|
||||||
|
reportUnnecessaryIsInstance = "warning"
|
||||||
|
reportUnusedClass = "error"
|
||||||
|
#reportUnusedImport = "error"
|
||||||
|
reportUnusedImport = "none"
|
||||||
|
# reportUnusedFunction = "error"
|
||||||
|
reportUnusedFunction = "warning"
|
||||||
|
#reportUnusedVariable = "error"
|
||||||
|
reportUnusedVariable = "warning"
|
||||||
|
reportUntypedBaseClass = "error"
|
||||||
|
reportUntypedClassDecorator = "error"
|
||||||
|
reportUntypedFunctionDecorator = "error"
|
||||||
|
reportUntypedNamedTuple = "error"
|
||||||
|
reportCallInDefaultInitializer = "none"
|
||||||
|
reportImplicitOverride = "none"
|
||||||
|
reportImplicitStringConcatenation = "none"
|
||||||
|
reportImportCycles = "none"
|
||||||
|
reportMissingSuperCall = "none"
|
||||||
|
reportPropertyTypeMismatch = "none"
|
||||||
|
reportShadowedImports = "none"
|
||||||
|
reportUninitializedInstanceVariable = "none"
|
||||||
|
reportUnnecessaryTypeIgnoreComment = "none"
|
||||||
|
reportUnusedCallResult = "none"
|
||||||
0
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/__init__.py
vendored
Normal file
0
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/__init__.py
vendored
Normal file
0
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/async_api/__init__.py
vendored
Normal file
0
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/async_api/__init__.py
vendored
Normal file
25
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/async_api/db.py
vendored
Normal file
25
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/async_api/db.py
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import fastapi
|
||||||
|
|
||||||
|
from ..payloads.settings import Settings as ModelsSettings
|
||||||
|
|
||||||
|
import sqlalchemy.ext.asyncio
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from sqlalchemy.ext.asyncio import async_sessionmaker
|
||||||
|
|
||||||
|
from typing import (Annotated, Generator,)
|
||||||
|
|
||||||
|
|
||||||
|
def create_engine() -> Generator[
|
||||||
|
'async_sessionmaker[AsyncSession]',
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
]:
|
||||||
|
engine = sqlalchemy.ext.asyncio.create_async_engine(ModelsSettings.singleton().db_url)
|
||||||
|
async_session = sqlalchemy.ext.asyncio.async_sessionmaker(engine)
|
||||||
|
|
||||||
|
yield async_session
|
||||||
|
|
||||||
|
AsyncSessionDep = Annotated[
|
||||||
|
'async_sessionmaker[AsyncSession]',
|
||||||
|
fastapi.Depends(create_engine)
|
||||||
|
]
|
||||||
113
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/async_api/fastapi.py
vendored
Normal file
113
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/async_api/fastapi.py
vendored
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import fastapi
|
||||||
|
import importlib
|
||||||
|
import pydantic
|
||||||
|
import functools
|
||||||
|
import logging
|
||||||
|
import copy
|
||||||
|
import uvicorn
|
||||||
|
import uvicorn.config
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from .settings import Settings as APISettings
|
||||||
|
# from .db import create_engine, async_session
|
||||||
|
from ..payloads.views import router as payloads_router
|
||||||
|
# from .websocket_api import WebsocketAPI
|
||||||
|
|
||||||
|
from typing import (
|
||||||
|
Any,
|
||||||
|
Optional,
|
||||||
|
Literal,
|
||||||
|
Annotated,
|
||||||
|
cast,
|
||||||
|
Callable,
|
||||||
|
)
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# async def websocket_tickers(
|
||||||
|
# websocket: fastapi.WebSocket,
|
||||||
|
# websocket_api: WebsocketAPI,
|
||||||
|
# ) -> None:
|
||||||
|
# try:
|
||||||
|
# await websocket_api.connect(websocket)
|
||||||
|
#
|
||||||
|
# while True:
|
||||||
|
# msg = await websocket.receive_text()
|
||||||
|
# await websocket_api.on_message(websocket, msg)
|
||||||
|
# except fastapi.WebSocketDisconnect:
|
||||||
|
# pass
|
||||||
|
# # websocket_api.disconnect(websocket)
|
||||||
|
# except:
|
||||||
|
# logger.exception('')
|
||||||
|
# raise
|
||||||
|
# finally:
|
||||||
|
# await websocket_api.disconnect(websocket)
|
||||||
|
|
||||||
|
|
||||||
|
def create_app() -> fastapi.FastAPI:
|
||||||
|
# async_session = create_engine()
|
||||||
|
|
||||||
|
# websocket_api = WebsocketAPI(
|
||||||
|
# session=async_session,
|
||||||
|
# )
|
||||||
|
|
||||||
|
app = fastapi.FastAPI(
|
||||||
|
# dependencies=[
|
||||||
|
# fastapi.Depends(async_session),
|
||||||
|
# ]
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(dict(msg='started loading apps'))
|
||||||
|
for app_config in APISettings.singleton().apps:
|
||||||
|
logger.info(dict(msg='start loading app = {}'.format(app_config)))
|
||||||
|
app_module, app_method, app_prefix = app_config.split(':')
|
||||||
|
|
||||||
|
app_router = cast(
|
||||||
|
Callable[[], Any],
|
||||||
|
getattr(
|
||||||
|
importlib.import_module(app_module),
|
||||||
|
app_method
|
||||||
|
)
|
||||||
|
)()
|
||||||
|
|
||||||
|
assert isinstance(app_router, fastapi.APIRouter)
|
||||||
|
|
||||||
|
app.include_router(
|
||||||
|
app_router,
|
||||||
|
prefix=app_prefix,
|
||||||
|
# prefix='/',
|
||||||
|
)
|
||||||
|
logger.info(dict(msg='done loading app = {}'.format(app_config)))
|
||||||
|
logger.info(dict(msg='done loading apps'))
|
||||||
|
|
||||||
|
# app.websocket(
|
||||||
|
# '/tickers/',
|
||||||
|
# )(
|
||||||
|
# functools.partial(
|
||||||
|
# websocket_tickers,
|
||||||
|
# websocket_api=fastapi.Depends(lambda : websocket_api),
|
||||||
|
# )
|
||||||
|
# )
|
||||||
|
|
||||||
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
def run(args: list[str]):
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
log_config = copy.deepcopy(uvicorn.config.LOGGING_CONFIG)
|
||||||
|
|
||||||
|
uvicorn.run(
|
||||||
|
# create_app(),
|
||||||
|
create_app,
|
||||||
|
host=APISettings.singleton().uvicorn_host,
|
||||||
|
port=APISettings.singleton().uvicorn_port,
|
||||||
|
loop='uvloop',
|
||||||
|
log_config=log_config,
|
||||||
|
log_level=logging.INFO,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
run(sys.argv[1:])
|
||||||
71
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/async_api/schema.py
vendored
Normal file
71
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/async_api/schema.py
vendored
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import pydantic
|
||||||
|
import decimal
|
||||||
|
|
||||||
|
from typing import (
|
||||||
|
Literal,
|
||||||
|
Annotated,
|
||||||
|
)
|
||||||
|
|
||||||
|
# class SubscribeAction(pydantic.BaseModel):
|
||||||
|
# action: Literal['subscribe']
|
||||||
|
# class message_t(pydantic.BaseModel):
|
||||||
|
# asset_id: Annotated[
|
||||||
|
# int,
|
||||||
|
# pydantic.Field(alias='assetId')
|
||||||
|
# ]
|
||||||
|
#
|
||||||
|
# message: message_t
|
||||||
|
#
|
||||||
|
# class AssetsAction(pydantic.BaseModel):
|
||||||
|
# action: Literal['assets']
|
||||||
|
# class message_t(pydantic.BaseModel):
|
||||||
|
# pass
|
||||||
|
#
|
||||||
|
# message: Annotated[
|
||||||
|
# message_t,
|
||||||
|
# pydantic.Field(
|
||||||
|
# default_factory=message_t,
|
||||||
|
# )
|
||||||
|
# ]
|
||||||
|
#
|
||||||
|
# Action = pydantic.RootModel[
|
||||||
|
# AssetsAction | SubscribeAction
|
||||||
|
# ]
|
||||||
|
#
|
||||||
|
# class AssetHistoryResponse(pydantic.BaseModel):
|
||||||
|
# action: Literal['asset_history'] = 'asset_history'
|
||||||
|
#
|
||||||
|
# class message_t(pydantic.BaseModel):
|
||||||
|
# class point_t(pydantic.BaseModel):
|
||||||
|
# asset_name : Annotated[
|
||||||
|
# str,
|
||||||
|
# pydantic.Field(
|
||||||
|
# alias='assetName',
|
||||||
|
# )
|
||||||
|
# ]
|
||||||
|
# time: int
|
||||||
|
# asset_id : Annotated[
|
||||||
|
# int,
|
||||||
|
# pydantic.Field(alias='assetId')
|
||||||
|
# ]
|
||||||
|
# value: decimal.Decimal
|
||||||
|
#
|
||||||
|
# points: list[point_t]
|
||||||
|
# message: message_t
|
||||||
|
#
|
||||||
|
# class AssetTickerResponse(pydantic.BaseModel):
|
||||||
|
# action: Literal['point'] = 'point'
|
||||||
|
#
|
||||||
|
# message: 'AssetHistoryResponse.message_t.point_t'
|
||||||
|
#
|
||||||
|
# class AssetsResponse(pydantic.BaseModel):
|
||||||
|
# action: Literal['assets'] = 'assets'
|
||||||
|
#
|
||||||
|
# class message_t(pydantic.BaseModel):
|
||||||
|
# class asset_t(pydantic.BaseModel):
|
||||||
|
# id: int
|
||||||
|
# name: str
|
||||||
|
#
|
||||||
|
# assets: list[asset_t]
|
||||||
|
# message: message_t
|
||||||
|
#
|
||||||
29
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/async_api/settings.py
vendored
Normal file
29
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/async_api/settings.py
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import pydantic
|
||||||
|
import pydantic_settings
|
||||||
|
|
||||||
|
from typing import (
|
||||||
|
ClassVar,
|
||||||
|
Optional,
|
||||||
|
Annotated,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Settings(pydantic_settings.BaseSettings):
|
||||||
|
apps: Annotated[
|
||||||
|
list[str],
|
||||||
|
pydantic.Field(
|
||||||
|
default_factory=list,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
uvicorn_port: int = 80
|
||||||
|
uvicorn_host: str = '127.0.0.1'
|
||||||
|
|
||||||
|
_singleton: ClassVar[Optional['Settings']] = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def singleton(cls) -> 'Settings':
|
||||||
|
if cls._singleton is None:
|
||||||
|
cls._singleton = Settings.model_validate({})
|
||||||
|
|
||||||
|
return cls._singleton
|
||||||
0
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/payloads/__init__.py
vendored
Normal file
0
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/payloads/__init__.py
vendored
Normal file
114
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/payloads/alembic/env.py
vendored
Normal file
114
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/payloads/alembic/env.py
vendored
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from logging.config import fileConfig
|
||||||
|
|
||||||
|
from sqlalchemy.ext.asyncio import async_engine_from_config
|
||||||
|
|
||||||
|
from sqlalchemy import engine_from_config
|
||||||
|
from sqlalchemy import pool
|
||||||
|
|
||||||
|
from sqlalchemy.engine.base import Connection
|
||||||
|
|
||||||
|
from alembic import context
|
||||||
|
|
||||||
|
from online.fxreader.pr34.test_task_2025_07_17_v2.payloads.settings import Settings
|
||||||
|
from online.fxreader.pr34.test_task_2025_07_17_v2.payloads.models import (
|
||||||
|
Base,
|
||||||
|
# Market,
|
||||||
|
)
|
||||||
|
|
||||||
|
# this is the Alembic Config object, which provides
|
||||||
|
# access to the values within the .ini file in use.
|
||||||
|
config = context.config
|
||||||
|
|
||||||
|
config.set_main_option('sqlalchemy.url', Settings.singleton().db_url)
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
# else:
|
||||||
|
if True:
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# add your model's MetaData object here
|
||||||
|
# for 'autogenerate' support
|
||||||
|
# from myapp import mymodel
|
||||||
|
# target_metadata = mymodel.Base.metadata
|
||||||
|
# target_metadata = None
|
||||||
|
target_metadata = Base.metadata
|
||||||
|
|
||||||
|
# other values from the config, defined by the needs of env.py,
|
||||||
|
# can be acquired:
|
||||||
|
# my_important_option = config.get_main_option("my_important_option")
|
||||||
|
# ... etc.
|
||||||
|
|
||||||
|
|
||||||
|
def do_run_migrations(
|
||||||
|
connection: Connection,
|
||||||
|
):
|
||||||
|
context.configure(connection=connection, target_metadata=target_metadata)
|
||||||
|
|
||||||
|
with context.begin_transaction():
|
||||||
|
context.run_migrations()
|
||||||
|
|
||||||
|
|
||||||
|
async def run_async_migrations():
|
||||||
|
"""In this scenario we need to create an Engine
|
||||||
|
and associate a connection with the context.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
logger.info(dict(msg='started'))
|
||||||
|
connectable = async_engine_from_config(
|
||||||
|
config.get_section(config.config_ini_section, {}),
|
||||||
|
prefix='sqlalchemy.',
|
||||||
|
poolclass=pool.NullPool,
|
||||||
|
)
|
||||||
|
|
||||||
|
async with connectable.connect() as connection:
|
||||||
|
await connection.run_sync(do_run_migrations)
|
||||||
|
|
||||||
|
await connectable.dispose()
|
||||||
|
|
||||||
|
logger.info(dict(msg='done'))
|
||||||
|
|
||||||
|
|
||||||
|
def run_migrations_offline():
|
||||||
|
"""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 = config.get_main_option('sqlalchemy.url')
|
||||||
|
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():
|
||||||
|
"""Run migrations in 'online' mode."""
|
||||||
|
|
||||||
|
asyncio.run(run_async_migrations())
|
||||||
|
|
||||||
|
|
||||||
|
if context.is_offline_mode():
|
||||||
|
raise NotImplementedError
|
||||||
|
# run_migrations_offline()
|
||||||
|
else:
|
||||||
|
run_migrations_online()
|
||||||
28
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/payloads/alembic/script.py.mako
vendored
Normal file
28
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/payloads/alembic/script.py.mako
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
"""${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, Sequence[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:
|
||||||
|
"""Upgrade schema."""
|
||||||
|
${upgrades if upgrades else "pass"}
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
"""Downgrade schema."""
|
||||||
|
${downgrades if downgrades else "pass"}
|
||||||
42
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/payloads/alembic/versions/f7fa90d3339d_add_payloads_models.py
vendored
Normal file
42
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/payloads/alembic/versions/f7fa90d3339d_add_payloads_models.py
vendored
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
"""add payloads models
|
||||||
|
|
||||||
|
Revision ID: f7fa90d3339d
|
||||||
|
Revises:
|
||||||
|
Create Date: 2025-07-18 09:58:54.099010
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = 'f7fa90d3339d'
|
||||||
|
down_revision: Union[str, Sequence[str], None] = None
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
"""Upgrade schema."""
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table(
|
||||||
|
'payloads_payload',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('output', sa.JSON(), nullable=False),
|
||||||
|
sa.Column('list_1', sa.JSON(), nullable=False),
|
||||||
|
sa.Column('list_2', sa.JSON(), nullable=False),
|
||||||
|
sa.Column('input_hash', sa.String(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('id'),
|
||||||
|
sa.UniqueConstraint('input_hash'),
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
"""Downgrade schema."""
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_table('payloads_payload')
|
||||||
|
# ### end Alembic commands ###
|
||||||
11
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/payloads/app.py
vendored
Normal file
11
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/payloads/app.py
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import logging
|
||||||
|
import fastapi
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
from typing import (Annotated,)
|
||||||
|
|
||||||
|
def get_app_router() -> fastapi.APIRouter:
|
||||||
|
return views.router
|
||||||
112
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/payloads/logic.py
vendored
Normal file
112
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/payloads/logic.py
vendored
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import datetime
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from sqlalchemy.ext.asyncio import async_sessionmaker
|
||||||
|
from sqlalchemy.orm import selectinload, make_transient
|
||||||
|
from sqlalchemy.future import select
|
||||||
|
|
||||||
|
from .models import Payload
|
||||||
|
from .utils import get_or_create
|
||||||
|
|
||||||
|
from typing import (Optional, Any,)
|
||||||
|
|
||||||
|
async def payload_get_or_create(
|
||||||
|
session: AsyncSession,
|
||||||
|
output: list[str],
|
||||||
|
list_1: list[str],
|
||||||
|
list_2: list[str],
|
||||||
|
input_hash: Optional[str] = None,
|
||||||
|
):
|
||||||
|
if input_hash is None:
|
||||||
|
input_hash = hashlib.sha256(json.dumps(dict(
|
||||||
|
list_1=list_1,
|
||||||
|
list_2=list_2,
|
||||||
|
)).encode('utf-8')).digest().hex()
|
||||||
|
|
||||||
|
return await get_or_create(
|
||||||
|
session,
|
||||||
|
Payload,
|
||||||
|
create_method_kwargs=dict(
|
||||||
|
output=output,
|
||||||
|
list_1=list_1,
|
||||||
|
list_2=list_2,
|
||||||
|
),
|
||||||
|
input_hash=input_hash,
|
||||||
|
)
|
||||||
|
|
||||||
|
# async def markets_get_by_symbol(
|
||||||
|
# session: 'async_sessionmaker[AsyncSession]',
|
||||||
|
# symbols: set[str],
|
||||||
|
# ) -> dict[str, int]:
|
||||||
|
# res : dict[str, int] = dict()
|
||||||
|
#
|
||||||
|
# async with session() as active_session:
|
||||||
|
# async with active_session.begin() as transaction:
|
||||||
|
# for o in symbols:
|
||||||
|
# m = (await get_or_create(
|
||||||
|
# active_session,
|
||||||
|
# Market,
|
||||||
|
# name=o,
|
||||||
|
# ))[0]
|
||||||
|
# res[o] = m.id
|
||||||
|
#
|
||||||
|
# return res
|
||||||
|
#
|
||||||
|
# async def ticker_store_multiple(
|
||||||
|
# session: 'async_sessionmaker[AsyncSession]',
|
||||||
|
# tickers: list[Ticker],
|
||||||
|
# ) -> None:
|
||||||
|
# async with session() as active_session:
|
||||||
|
# async with active_session.begin() as transaction:
|
||||||
|
# active_session.add_all(
|
||||||
|
# tickers,
|
||||||
|
# )
|
||||||
|
#
|
||||||
|
# async def tickers_get_by_period(
|
||||||
|
# session: 'async_sessionmaker[AsyncSession]',
|
||||||
|
# market_id: int,
|
||||||
|
# period: datetime.timedelta,
|
||||||
|
# ) -> list[Ticker]:
|
||||||
|
# async with session() as active_session:
|
||||||
|
# async with active_session.begin() as transaction:
|
||||||
|
# q = select(
|
||||||
|
# Ticker
|
||||||
|
# ).join(Ticker.market).where(
|
||||||
|
# Market.id == market_id,
|
||||||
|
# Ticker.timestamp >= datetime.datetime.now(
|
||||||
|
# tz=datetime.timezone.utc
|
||||||
|
# ) - period
|
||||||
|
# ).order_by(Ticker.timestamp.desc()).options(
|
||||||
|
# selectinload(Ticker.market)
|
||||||
|
# )
|
||||||
|
#
|
||||||
|
# res = await active_session.execute(q)
|
||||||
|
#
|
||||||
|
# rows = [o[0] for o in res]
|
||||||
|
#
|
||||||
|
# for o in rows:
|
||||||
|
# active_session.expunge(o)
|
||||||
|
# make_transient(o.market)
|
||||||
|
#
|
||||||
|
# return rows
|
||||||
|
#
|
||||||
|
# async def markets_all(
|
||||||
|
# session: 'async_sessionmaker[AsyncSession]',
|
||||||
|
# ) -> list[Market]:
|
||||||
|
# async with session() as active_session:
|
||||||
|
# async with active_session.begin() as transaction:
|
||||||
|
# q = select(
|
||||||
|
# Market
|
||||||
|
# )
|
||||||
|
#
|
||||||
|
# res = await active_session.execute(q)
|
||||||
|
#
|
||||||
|
# rows = [o[0] for o in res]
|
||||||
|
#
|
||||||
|
# for o in rows:
|
||||||
|
# active_session.expunge(o)
|
||||||
|
#
|
||||||
|
# return rows
|
||||||
|
#
|
||||||
50
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/payloads/models.py
vendored
Normal file
50
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/payloads/models.py
vendored
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import datetime
|
||||||
|
import decimal
|
||||||
|
import json
|
||||||
|
|
||||||
|
from sqlalchemy.orm import (
|
||||||
|
mapped_column,
|
||||||
|
Mapped,
|
||||||
|
DeclarativeBase,
|
||||||
|
relationship,
|
||||||
|
)
|
||||||
|
from sqlalchemy import (
|
||||||
|
String,
|
||||||
|
ForeignKey,
|
||||||
|
Numeric,
|
||||||
|
DateTime,
|
||||||
|
UniqueConstraint,
|
||||||
|
JSON,
|
||||||
|
)
|
||||||
|
|
||||||
|
from typing import (
|
||||||
|
Optional,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Base(DeclarativeBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Payload(Base):
|
||||||
|
__tablename__ = 'payloads_payload'
|
||||||
|
|
||||||
|
id: Mapped[int] = mapped_column(primary_key=True)
|
||||||
|
output: Mapped[list[str]] = mapped_column(JSON())
|
||||||
|
list_1: Mapped[list[str]] = mapped_column(JSON())
|
||||||
|
list_2: Mapped[list[str]] = mapped_column(JSON())
|
||||||
|
input_hash: Mapped[str] = mapped_column()
|
||||||
|
|
||||||
|
__table_args__ = (UniqueConstraint('input_hash'),)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return json.dumps(
|
||||||
|
dict(
|
||||||
|
model=str(type(self)),
|
||||||
|
id=self.id,
|
||||||
|
output=self.output,
|
||||||
|
list_1=self.list_1,
|
||||||
|
list_2=self.list_2,
|
||||||
|
input_hash=self.input_hash,
|
||||||
|
)
|
||||||
|
)
|
||||||
5
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/payloads/schema.py
vendored
Normal file
5
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/payloads/schema.py
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import pydantic
|
||||||
|
|
||||||
|
class Payload(pydantic.BaseModel):
|
||||||
|
id: int
|
||||||
|
output: list[str]
|
||||||
24
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/payloads/settings.py
vendored
Normal file
24
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/payloads/settings.py
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import pydantic
|
||||||
|
import pydantic_settings
|
||||||
|
|
||||||
|
from typing import (
|
||||||
|
ClassVar,
|
||||||
|
Optional,
|
||||||
|
Literal,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Settings(pydantic_settings.BaseSettings):
|
||||||
|
db_url: str
|
||||||
|
|
||||||
|
_singleton: ClassVar[Optional['Settings']] = None
|
||||||
|
|
||||||
|
summarizer_domain: str
|
||||||
|
summarizer_protocol: Literal['http', 'https']
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def singleton(cls) -> 'Settings':
|
||||||
|
if cls._singleton is None:
|
||||||
|
cls._singleton = Settings.model_validate({})
|
||||||
|
|
||||||
|
return cls._singleton
|
||||||
18
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/payloads/summarizer.py
vendored
Normal file
18
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/payloads/summarizer.py
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
from ..transform import rest_client as summarizer_rest_client
|
||||||
|
from ..transform.rest_client import SummaryRequest
|
||||||
|
from .settings import Settings as ModelSettings
|
||||||
|
|
||||||
|
from typing import (ClassVar, Optional,)
|
||||||
|
|
||||||
|
class SummarizerClient:
|
||||||
|
_summarizer_client : ClassVar[Optional[summarizer_rest_client.SummarizerClient]] = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def singleton(cls) -> summarizer_rest_client.SummarizerClient:
|
||||||
|
if cls._summarizer_client is None:
|
||||||
|
cls._summarizer_client = summarizer_rest_client.SummarizerClient(
|
||||||
|
domain=ModelSettings.singleton().summarizer_domain,
|
||||||
|
protocol=ModelSettings.singleton().summarizer_protocol,
|
||||||
|
)
|
||||||
|
|
||||||
|
return cls._summarizer_client
|
||||||
45
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/payloads/utils.py
vendored
Normal file
45
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/payloads/utils.py
vendored
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
from typing import (
|
||||||
|
TypeVar,
|
||||||
|
Optional,
|
||||||
|
Any,
|
||||||
|
cast,
|
||||||
|
)
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSessionTransaction, AsyncSession
|
||||||
|
from sqlalchemy.future import select
|
||||||
|
from sqlalchemy.orm import DeclarativeBase
|
||||||
|
from sqlalchemy.exc import NoResultFound, IntegrityError
|
||||||
|
|
||||||
|
M = TypeVar('M', bound='DeclarativeBase')
|
||||||
|
|
||||||
|
|
||||||
|
async def get_or_create(
|
||||||
|
session: AsyncSession, model: type[M], create_method: Optional[str] = None, create_method_kwargs: Optional[dict[str, Any]] = None, **kwargs: Any
|
||||||
|
) -> tuple[M, bool]:
|
||||||
|
async def select_row() -> M:
|
||||||
|
res = await session.execute(select(model).where(*[getattr(model, k) == v for k, v in kwargs.items()]))
|
||||||
|
|
||||||
|
row = res.one()[0]
|
||||||
|
|
||||||
|
assert isinstance(row, model)
|
||||||
|
|
||||||
|
return row
|
||||||
|
|
||||||
|
try:
|
||||||
|
res = await select_row()
|
||||||
|
return res, False
|
||||||
|
except NoResultFound:
|
||||||
|
if create_method_kwargs:
|
||||||
|
kwargs.update(create_method_kwargs)
|
||||||
|
|
||||||
|
if not create_method:
|
||||||
|
created = model(**kwargs)
|
||||||
|
else:
|
||||||
|
created = getattr(model, create_method)(**kwargs)
|
||||||
|
|
||||||
|
try:
|
||||||
|
session.add(created)
|
||||||
|
await session.flush()
|
||||||
|
return created, True
|
||||||
|
except IntegrityError:
|
||||||
|
await session.rollback()
|
||||||
|
return await select_row(), False
|
||||||
70
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/payloads/views.py
vendored
Normal file
70
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/payloads/views.py
vendored
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import fastapi
|
||||||
|
import itertools
|
||||||
|
|
||||||
|
from typing import (Annotated, Any, cast, Optional,)
|
||||||
|
from . import schema
|
||||||
|
from .summarizer import SummarizerClient, SummaryRequest
|
||||||
|
from ..async_api.db import AsyncSessionDep
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from sqlalchemy.ext.asyncio import async_sessionmaker
|
||||||
|
from . import logic
|
||||||
|
|
||||||
|
router = fastapi.APIRouter()
|
||||||
|
|
||||||
|
@router.post('/payload')
|
||||||
|
async def payload_create(
|
||||||
|
list_1: Annotated[
|
||||||
|
list[str],
|
||||||
|
fastapi.Body(),
|
||||||
|
],
|
||||||
|
list_2: Annotated[
|
||||||
|
list[str],
|
||||||
|
fastapi.Body(),
|
||||||
|
],
|
||||||
|
session: AsyncSessionDep
|
||||||
|
) -> schema.Payload:
|
||||||
|
data_1 = (await SummarizerClient.singleton().summarize_post(
|
||||||
|
request=SummaryRequest(
|
||||||
|
data=list_1
|
||||||
|
)
|
||||||
|
)).data
|
||||||
|
|
||||||
|
data_2 = (await SummarizerClient.singleton().summarize_post(
|
||||||
|
request=SummaryRequest(
|
||||||
|
data=list_2
|
||||||
|
)
|
||||||
|
)).data
|
||||||
|
|
||||||
|
output_len = max(len(data_1), len(data_2))
|
||||||
|
|
||||||
|
def filter_none(o: Any) -> bool:
|
||||||
|
return o is not None
|
||||||
|
|
||||||
|
output = list(
|
||||||
|
filter(
|
||||||
|
filter_none,
|
||||||
|
sum(
|
||||||
|
list(
|
||||||
|
itertools.zip_longest(data_1, data_2),
|
||||||
|
),
|
||||||
|
cast(tuple[str], tuple()),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
async with session() as active_session:
|
||||||
|
async with active_session.begin() as transaction:
|
||||||
|
payload, is_created = await logic.payload_get_or_create(
|
||||||
|
active_session,
|
||||||
|
list_1=list_1,
|
||||||
|
list_2=list_2,
|
||||||
|
output=output,
|
||||||
|
)
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@router.get('/payload/{paylaod_id}')
|
||||||
|
async def payload_read(
|
||||||
|
payload_id: int,
|
||||||
|
) -> schema.Payload:
|
||||||
|
raise NotImplementedError
|
||||||
0
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/py.typed
vendored
Normal file
0
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/py.typed
vendored
Normal file
0
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/transform/__init__.py
vendored
Normal file
0
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/transform/__init__.py
vendored
Normal file
27
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/transform/app.py
vendored
Normal file
27
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/transform/app.py
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import logging
|
||||||
|
import fastapi
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
from .dependencies import summarizer_dependency
|
||||||
|
|
||||||
|
from typing import (Annotated,)
|
||||||
|
|
||||||
|
def get_app_router() -> fastapi.APIRouter:
|
||||||
|
logger.info(dict(msg='started'))
|
||||||
|
router = fastapi.APIRouter(
|
||||||
|
# dependencies=[
|
||||||
|
# fastapi.Depends(summarizer_dependency,)
|
||||||
|
# ]
|
||||||
|
)
|
||||||
|
|
||||||
|
router.include_router(
|
||||||
|
views.router,
|
||||||
|
prefix='',
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(dict(msg='done'))
|
||||||
|
|
||||||
|
return router
|
||||||
22
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/transform/dependencies.py
vendored
Normal file
22
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/transform/dependencies.py
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import logging
|
||||||
|
import fastapi
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
from .worker import Summarizer
|
||||||
|
|
||||||
|
from typing import (Annotated,)
|
||||||
|
|
||||||
|
async def create_summarizer(
|
||||||
|
) -> Summarizer:
|
||||||
|
# return Summarizer()
|
||||||
|
return Summarizer.singleton()
|
||||||
|
|
||||||
|
AnnotatedSummarizer = Annotated[
|
||||||
|
Summarizer, fastapi.Depends(create_summarizer)
|
||||||
|
]
|
||||||
|
|
||||||
|
async def summarizer_dependency(
|
||||||
|
summarizer: AnnotatedSummarizer
|
||||||
|
) -> None:
|
||||||
|
pass
|
||||||
12
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/transform/logic.py
vendored
Normal file
12
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/transform/logic.py
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import asyncio
|
||||||
|
|
||||||
|
async def transform(
|
||||||
|
list_1: list[str],
|
||||||
|
list_2: list[str]
|
||||||
|
) -> list[str]:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
async def transform_api_post(
|
||||||
|
data: list[str],
|
||||||
|
) -> list[str]:
|
||||||
|
raise NotImplementedError
|
||||||
36
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/transform/rest_client.py
vendored
Normal file
36
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/transform/rest_client.py
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import aiohttp
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from .schema import Summary, SummaryRequest
|
||||||
|
|
||||||
|
from typing import (Literal,)
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class SummarizerClient:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
domain: str,
|
||||||
|
protocol: Literal['http', 'https'] = 'http',
|
||||||
|
) -> None:
|
||||||
|
self.domain = domain
|
||||||
|
self.protocol = protocol
|
||||||
|
|
||||||
|
async def summarize_post(
|
||||||
|
self,
|
||||||
|
request: SummaryRequest,
|
||||||
|
) -> Summary:
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.post(
|
||||||
|
'{}://{}/summarize'.format(self.protocol, self.domain),
|
||||||
|
json=json.loads(request.json()),
|
||||||
|
) as response:
|
||||||
|
data_json = await response.text()
|
||||||
|
data = Summary.model_validate_json(
|
||||||
|
data_json,
|
||||||
|
)
|
||||||
|
|
||||||
|
return data
|
||||||
7
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/transform/schema.py
vendored
Normal file
7
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/transform/schema.py
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import pydantic
|
||||||
|
|
||||||
|
class Summary(pydantic.BaseModel):
|
||||||
|
data: list[str]
|
||||||
|
|
||||||
|
class SummaryRequest(pydantic.BaseModel):
|
||||||
|
data: list[str]
|
||||||
25
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/transform/views.py
vendored
Normal file
25
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/transform/views.py
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import fastapi
|
||||||
|
|
||||||
|
from typing import (Annotated, Any,)
|
||||||
|
from . import schema
|
||||||
|
# from .worker import Summarizer
|
||||||
|
from .dependencies import AnnotatedSummarizer
|
||||||
|
|
||||||
|
router = fastapi.APIRouter()
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
'/summarize',
|
||||||
|
# response_model=schema.Summary,
|
||||||
|
)
|
||||||
|
async def summarize(
|
||||||
|
request: Annotated[
|
||||||
|
schema.SummaryRequest,
|
||||||
|
fastapi.Body(),
|
||||||
|
],
|
||||||
|
summarizer: AnnotatedSummarizer
|
||||||
|
) -> schema.Summary:
|
||||||
|
return schema.Summary(
|
||||||
|
data=summarizer.summarize(
|
||||||
|
request.data,
|
||||||
|
)
|
||||||
|
)
|
||||||
80
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/transform/worker.py
vendored
Normal file
80
deps/test-task-2025-07-17-v2/python/online/fxreader/pr34/test_task_2025_07_17_v2/transform/worker.py
vendored
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import transformers
|
||||||
|
import transformers.pipelines
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
from typing import (
|
||||||
|
Any, cast, Callable, Protocol, Literal, TypedDict,
|
||||||
|
TypeAlias, ClassVar, Optional,
|
||||||
|
)
|
||||||
|
|
||||||
|
class SummarizerPipeline(Protocol):
|
||||||
|
class predict_t:
|
||||||
|
class output_t(TypedDict):
|
||||||
|
summary_text: str
|
||||||
|
|
||||||
|
res_t : TypeAlias = list[output_t]
|
||||||
|
|
||||||
|
def predict(self, data: str) -> predict_t.res_t: ...
|
||||||
|
|
||||||
|
class Pipeline(Protocol):
|
||||||
|
def __call__(
|
||||||
|
self,
|
||||||
|
task: Literal['summarization'],
|
||||||
|
model: Any,
|
||||||
|
tokenizer: Any,
|
||||||
|
) -> 'SummarizerPipeline': ...
|
||||||
|
|
||||||
|
|
||||||
|
class Summarizer:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
logger.info(dict(msg='started loading bart'))
|
||||||
|
|
||||||
|
self.tokenizer = cast(
|
||||||
|
Callable[[str], Any],
|
||||||
|
getattr(transformers.AutoTokenizer, 'from_pretrained')
|
||||||
|
)(
|
||||||
|
'sshleifer/distilbart-cnn-12-6',
|
||||||
|
)
|
||||||
|
self.model = cast(
|
||||||
|
Callable[[str], Any],
|
||||||
|
getattr(transformers.AutoModelForSeq2SeqLM, 'from_pretrained')
|
||||||
|
)(
|
||||||
|
'sshleifer/distilbart-cnn-12-6',
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(dict(msg='done loading bart'))
|
||||||
|
|
||||||
|
self.summarizer = cast(
|
||||||
|
Pipeline,
|
||||||
|
# getattr(transformers.pipelines, 'pipeline')
|
||||||
|
getattr(transformers, 'pipeline')
|
||||||
|
)(
|
||||||
|
'summarization',
|
||||||
|
model=self.model,
|
||||||
|
tokenizer=self.tokenizer,
|
||||||
|
# framework='pt',
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(dict(msg='created pipeline'))
|
||||||
|
|
||||||
|
def summarize(
|
||||||
|
self,
|
||||||
|
data: list[str]
|
||||||
|
) -> list[str]:
|
||||||
|
res = self.summarizer.predict(
|
||||||
|
' '.join(data)
|
||||||
|
)
|
||||||
|
assert len(res) == 1
|
||||||
|
|
||||||
|
return res[0]['summary_text'].split()
|
||||||
|
|
||||||
|
_singleton: ClassVar[Optional['Summarizer']] = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def singleton(cls) -> 'Summarizer':
|
||||||
|
if cls._singleton is None:
|
||||||
|
cls._singleton = Summarizer()
|
||||||
|
|
||||||
|
return cls._singleton
|
||||||
BIN
deps/test-task-2025-07-17-v2/releases/whl/online_fxreader_pr34_test_task_2025_07_17_v2-0.1.1-py3-none-any.whl
(Stored with Git LFS)
vendored
Normal file
BIN
deps/test-task-2025-07-17-v2/releases/whl/online_fxreader_pr34_test_task_2025_07_17_v2-0.1.1-py3-none-any.whl
(Stored with Git LFS)
vendored
Normal file
Binary file not shown.
BIN
deps/test-task-2025-07-17-v2/releases/whl/online_fxreader_pr34_test_task_2025_07_17_v2-0.1.10-py3-none-any.whl
(Stored with Git LFS)
vendored
Normal file
BIN
deps/test-task-2025-07-17-v2/releases/whl/online_fxreader_pr34_test_task_2025_07_17_v2-0.1.10-py3-none-any.whl
(Stored with Git LFS)
vendored
Normal file
Binary file not shown.
BIN
deps/test-task-2025-07-17-v2/releases/whl/online_fxreader_pr34_test_task_2025_07_17_v2-0.1.11-py3-none-any.whl
(Stored with Git LFS)
vendored
Normal file
BIN
deps/test-task-2025-07-17-v2/releases/whl/online_fxreader_pr34_test_task_2025_07_17_v2-0.1.11-py3-none-any.whl
(Stored with Git LFS)
vendored
Normal file
Binary file not shown.
BIN
deps/test-task-2025-07-17-v2/releases/whl/online_fxreader_pr34_test_task_2025_07_17_v2-0.1.12-py3-none-any.whl
(Stored with Git LFS)
vendored
Normal file
BIN
deps/test-task-2025-07-17-v2/releases/whl/online_fxreader_pr34_test_task_2025_07_17_v2-0.1.12-py3-none-any.whl
(Stored with Git LFS)
vendored
Normal file
Binary file not shown.
BIN
deps/test-task-2025-07-17-v2/releases/whl/online_fxreader_pr34_test_task_2025_07_17_v2-0.1.13-py3-none-any.whl
(Stored with Git LFS)
vendored
Normal file
BIN
deps/test-task-2025-07-17-v2/releases/whl/online_fxreader_pr34_test_task_2025_07_17_v2-0.1.13-py3-none-any.whl
(Stored with Git LFS)
vendored
Normal file
Binary file not shown.
BIN
deps/test-task-2025-07-17-v2/releases/whl/online_fxreader_pr34_test_task_2025_07_17_v2-0.1.14-py3-none-any.whl
(Stored with Git LFS)
vendored
Normal file
BIN
deps/test-task-2025-07-17-v2/releases/whl/online_fxreader_pr34_test_task_2025_07_17_v2-0.1.14-py3-none-any.whl
(Stored with Git LFS)
vendored
Normal file
Binary file not shown.
BIN
deps/test-task-2025-07-17-v2/releases/whl/online_fxreader_pr34_test_task_2025_07_17_v2-0.1.15-py3-none-any.whl
(Stored with Git LFS)
vendored
Normal file
BIN
deps/test-task-2025-07-17-v2/releases/whl/online_fxreader_pr34_test_task_2025_07_17_v2-0.1.15-py3-none-any.whl
(Stored with Git LFS)
vendored
Normal file
Binary file not shown.
BIN
deps/test-task-2025-07-17-v2/releases/whl/online_fxreader_pr34_test_task_2025_07_17_v2-0.1.2-py3-none-any.whl
(Stored with Git LFS)
vendored
Normal file
BIN
deps/test-task-2025-07-17-v2/releases/whl/online_fxreader_pr34_test_task_2025_07_17_v2-0.1.2-py3-none-any.whl
(Stored with Git LFS)
vendored
Normal file
Binary file not shown.
BIN
deps/test-task-2025-07-17-v2/releases/whl/online_fxreader_pr34_test_task_2025_07_17_v2-0.1.3-py3-none-any.whl
(Stored with Git LFS)
vendored
Normal file
BIN
deps/test-task-2025-07-17-v2/releases/whl/online_fxreader_pr34_test_task_2025_07_17_v2-0.1.3-py3-none-any.whl
(Stored with Git LFS)
vendored
Normal file
Binary file not shown.
BIN
deps/test-task-2025-07-17-v2/releases/whl/online_fxreader_pr34_test_task_2025_07_17_v2-0.1.4-py3-none-any.whl
(Stored with Git LFS)
vendored
Normal file
BIN
deps/test-task-2025-07-17-v2/releases/whl/online_fxreader_pr34_test_task_2025_07_17_v2-0.1.4-py3-none-any.whl
(Stored with Git LFS)
vendored
Normal file
Binary file not shown.
BIN
deps/test-task-2025-07-17-v2/releases/whl/online_fxreader_pr34_test_task_2025_07_17_v2-0.1.5-py3-none-any.whl
(Stored with Git LFS)
vendored
Normal file
BIN
deps/test-task-2025-07-17-v2/releases/whl/online_fxreader_pr34_test_task_2025_07_17_v2-0.1.5-py3-none-any.whl
(Stored with Git LFS)
vendored
Normal file
Binary file not shown.
BIN
deps/test-task-2025-07-17-v2/releases/whl/online_fxreader_pr34_test_task_2025_07_17_v2-0.1.6-py3-none-any.whl
(Stored with Git LFS)
vendored
Normal file
BIN
deps/test-task-2025-07-17-v2/releases/whl/online_fxreader_pr34_test_task_2025_07_17_v2-0.1.6-py3-none-any.whl
(Stored with Git LFS)
vendored
Normal file
Binary file not shown.
BIN
deps/test-task-2025-07-17-v2/releases/whl/online_fxreader_pr34_test_task_2025_07_17_v2-0.1.7-py3-none-any.whl
(Stored with Git LFS)
vendored
Normal file
BIN
deps/test-task-2025-07-17-v2/releases/whl/online_fxreader_pr34_test_task_2025_07_17_v2-0.1.7-py3-none-any.whl
(Stored with Git LFS)
vendored
Normal file
Binary file not shown.
BIN
deps/test-task-2025-07-17-v2/releases/whl/online_fxreader_pr34_test_task_2025_07_17_v2-0.1.8-py3-none-any.whl
(Stored with Git LFS)
vendored
Normal file
BIN
deps/test-task-2025-07-17-v2/releases/whl/online_fxreader_pr34_test_task_2025_07_17_v2-0.1.8-py3-none-any.whl
(Stored with Git LFS)
vendored
Normal file
Binary file not shown.
BIN
deps/test-task-2025-07-17-v2/releases/whl/online_fxreader_pr34_test_task_2025_07_17_v2-0.1.9-py3-none-any.whl
(Stored with Git LFS)
vendored
Normal file
BIN
deps/test-task-2025-07-17-v2/releases/whl/online_fxreader_pr34_test_task_2025_07_17_v2-0.1.9-py3-none-any.whl
(Stored with Git LFS)
vendored
Normal file
Binary file not shown.
28
deps/test-task-2025-07-17-v2/requirements.in
vendored
Normal file
28
deps/test-task-2025-07-17-v2/requirements.in
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
alembic
|
||||||
|
fastapi
|
||||||
|
uvicorn
|
||||||
|
websockets
|
||||||
|
uvloop
|
||||||
|
tomlq
|
||||||
|
mypy
|
||||||
|
marisa-trie
|
||||||
|
pydantic
|
||||||
|
asyncpg
|
||||||
|
pydantic-settings
|
||||||
|
tomlkit
|
||||||
|
tomlq
|
||||||
|
numpy
|
||||||
|
cryptography
|
||||||
|
mypy
|
||||||
|
pyright
|
||||||
|
ruff
|
||||||
|
ipython
|
||||||
|
ipdb
|
||||||
|
requests
|
||||||
|
types-requests
|
||||||
|
aiohttp
|
||||||
|
build
|
||||||
|
wheel
|
||||||
|
setuptools
|
||||||
|
setuptools-scm
|
||||||
|
transformers
|
||||||
2
deps/test-task-2025-07-17-v2/requirements.torch.in
vendored
Normal file
2
deps/test-task-2025-07-17-v2/requirements.torch.in
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
--extra-index-url https://download.pytorch.org/whl/cpu
|
||||||
|
torch
|
||||||
86
deps/test-task-2025-07-17-v2/requirements.torch.txt
vendored
Normal file
86
deps/test-task-2025-07-17-v2/requirements.torch.txt
vendored
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
# This file was autogenerated by uv via the following command:
|
||||||
|
# uv pip compile -p 3.12.9 --generate-hashes requirements.torch.in
|
||||||
|
filelock==3.13.1 \
|
||||||
|
--hash=sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c
|
||||||
|
# via torch
|
||||||
|
fsspec==2024.6.1 \
|
||||||
|
--hash=sha256:3cb443f8bcd2efb31295a5b9fdb02aee81d8452c80d28f97a6d0959e6cee101e
|
||||||
|
# via torch
|
||||||
|
jinja2==3.1.4 \
|
||||||
|
--hash=sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d
|
||||||
|
# via torch
|
||||||
|
markupsafe==2.1.5 \
|
||||||
|
--hash=sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3 \
|
||||||
|
--hash=sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532 \
|
||||||
|
--hash=sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f \
|
||||||
|
--hash=sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617 \
|
||||||
|
--hash=sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4 \
|
||||||
|
--hash=sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8 \
|
||||||
|
--hash=sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2 \
|
||||||
|
--hash=sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465 \
|
||||||
|
--hash=sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52 \
|
||||||
|
--hash=sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2 \
|
||||||
|
--hash=sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029 \
|
||||||
|
--hash=sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f \
|
||||||
|
--hash=sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a \
|
||||||
|
--hash=sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced \
|
||||||
|
--hash=sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5 \
|
||||||
|
--hash=sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c \
|
||||||
|
--hash=sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf \
|
||||||
|
--hash=sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb \
|
||||||
|
--hash=sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1 \
|
||||||
|
--hash=sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46 \
|
||||||
|
--hash=sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc \
|
||||||
|
--hash=sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a \
|
||||||
|
--hash=sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee \
|
||||||
|
--hash=sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900 \
|
||||||
|
--hash=sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5 \
|
||||||
|
--hash=sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5 \
|
||||||
|
--hash=sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f \
|
||||||
|
--hash=sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b \
|
||||||
|
--hash=sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2 \
|
||||||
|
--hash=sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46 \
|
||||||
|
--hash=sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b \
|
||||||
|
--hash=sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf \
|
||||||
|
--hash=sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5 \
|
||||||
|
--hash=sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5 \
|
||||||
|
--hash=sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab \
|
||||||
|
--hash=sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68
|
||||||
|
# via jinja2
|
||||||
|
mpmath==1.3.0 \
|
||||||
|
--hash=sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c
|
||||||
|
# via sympy
|
||||||
|
networkx==3.3 \
|
||||||
|
--hash=sha256:28575580c6ebdaf4505b22c6256a2b9de86b316dc63ba9e93abde3d78dfdbcf2
|
||||||
|
# via torch
|
||||||
|
setuptools==70.2.0 \
|
||||||
|
--hash=sha256:b8b8060bb426838fbe942479c90296ce976249451118ef566a5a0b7d8b78fb05
|
||||||
|
# via torch
|
||||||
|
sympy==1.13.3 \
|
||||||
|
--hash=sha256:54612cf55a62755ee71824ce692986f23c88ffa77207b30c1368eda4a7060f73
|
||||||
|
# via torch
|
||||||
|
torch==2.7.1+cpu \
|
||||||
|
--hash=sha256:0bc887068772233f532b51a3e8c8cfc682ae62bef74bf4e0c53526c8b9e4138f \
|
||||||
|
--hash=sha256:1f04a373a3f643821f721da9898ef77dce73b5b6bfc64486f0976f7fb5f90e83 \
|
||||||
|
--hash=sha256:355614185a2aea7155f9c88a20bfd49de5f3063866f3cf9b2f21b6e9e59e31e0 \
|
||||||
|
--hash=sha256:3bf2db5adf77b433844f080887ade049c4705ddf9fe1a32023ff84ff735aa5ad \
|
||||||
|
--hash=sha256:464bca1bc9452f2ccd676514688896e66b9488f2a0268ecd3ac497cf09c5aac1 \
|
||||||
|
--hash=sha256:56136a2aca6707df3c8811e46ea2d379eaafd18e656e2fd51e8e4d0ca995651b \
|
||||||
|
--hash=sha256:5fe6045b8f426bf2d0426e4fe009f1667a954ec2aeb82f1bd0bf60c6d7a85445 \
|
||||||
|
--hash=sha256:7b977eccbc85ae2bd19d6998de7b1f1f4bd3c04eaffd3015deb7934389783399 \
|
||||||
|
--hash=sha256:84ea1f6a1d15663037d01b121d6e33bb9da3c90af8e069e5072c30f413455a57 \
|
||||||
|
--hash=sha256:8f8b3cfc53010a4b4a3c7ecb88c212e9decc4f5eeb6af75c3c803937d2d60947 \
|
||||||
|
--hash=sha256:a1684793e352f03fa14f78857e55d65de4ada8405ded1da2bf4f452179c4b779 \
|
||||||
|
--hash=sha256:a2618775f32eb4126c5b2050686da52001a08cffa331637d9cf51c8250931e00 \
|
||||||
|
--hash=sha256:a4551cb97b83df5f93fc0d7538332535828581e1db2f179afc287027afbdd6e8 \
|
||||||
|
--hash=sha256:b4cc706973655151f198d027ed34c92ab31a3db55676b44251194e1280631426 \
|
||||||
|
--hash=sha256:b66f77f6f67317344ee083aa7ac4751a14395fcb38060d564bf513978d267153 \
|
||||||
|
--hash=sha256:c0df17cee97653d09a4e84488a33d21217f9b24208583c55cf28f0045aab0766 \
|
||||||
|
--hash=sha256:d205cac087d60bc176bdc0b63a1d00dc7a4ee5ac76fd20a2ca318ac65674167e \
|
||||||
|
--hash=sha256:d25435bdc4780d3cb512aad55142aca9584ae1fe8f8691cda6d32f19faf5d58e \
|
||||||
|
--hash=sha256:eb17646792ac4374ffc87e42369f45d21eff17c790868963b90483ef0b6db4ef
|
||||||
|
# via -r requirements.torch.in
|
||||||
|
typing-extensions==4.12.2 \
|
||||||
|
--hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d
|
||||||
|
# via torch
|
||||||
|
--extra-index-url https://download.pytorch.org/whl/cpu
|
||||||
1862
deps/test-task-2025-07-17-v2/requirements.txt
vendored
Normal file
1862
deps/test-task-2025-07-17-v2/requirements.txt
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user