From ba3fe1284c1a68e2296b12dc8ae1d7cc89e2c6c0 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Thu, 31 Jul 2025 16:50:04 +0300 Subject: [PATCH 01/70] [+] add check_type, add .whl --- python/meson.build | 2 +- python/online/fxreader/pr34/commands_typed/cli_bootstrap.py | 6 ++++++ releases/whl/online_fxreader_pr34-0.1.5.18-py3-none-any.whl | 3 +++ 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.18-py3-none-any.whl diff --git a/python/meson.build b/python/meson.build index 0c7aaa4..ca7fdee 100644 --- a/python/meson.build +++ b/python/meson.build @@ -5,7 +5,7 @@ project( ).stdout().strip('\n'), # 'online.fxreader.uv', # ['c', 'cpp'], - version: '0.1.5.17+27.23', + version: '0.1.5.18', # default_options: [ # 'cpp_std=c++23', # # 'prefer_static=true', diff --git a/python/online/fxreader/pr34/commands_typed/cli_bootstrap.py b/python/online/fxreader/pr34/commands_typed/cli_bootstrap.py index bbe275f..40e9f45 100644 --- a/python/online/fxreader/pr34/commands_typed/cli_bootstrap.py +++ b/python/online/fxreader/pr34/commands_typed/cli_bootstrap.py @@ -172,6 +172,12 @@ def check_list( ) +def check_type(value: Any, VT: Type[Value]) -> Value: + assert isinstance(value, VT) + + return value + + def pyproject_load( d: pathlib.Path, ) -> PyProject: diff --git a/releases/whl/online_fxreader_pr34-0.1.5.18-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.18-py3-none-any.whl new file mode 100644 index 0000000..eac0045 --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.18-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4da989c2c7216dc258fe74ca28a5e80413f410d8660502da81370293e8bc56d3 +size 72213 From 3a01fc9e87fc5795ac75aca9238d1cbdc09a9a42 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Fri, 1 Aug 2025 12:43:55 +0300 Subject: [PATCH 02/70] [+] improve check_type --- python/meson.build | 2 +- .../fxreader/pr34/commands_typed/cli_bootstrap.py | 15 ++++++++++++--- ...online_fxreader_pr34-0.1.5.19-py3-none-any.whl | 3 +++ 3 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.19-py3-none-any.whl diff --git a/python/meson.build b/python/meson.build index ca7fdee..cff2a3b 100644 --- a/python/meson.build +++ b/python/meson.build @@ -5,7 +5,7 @@ project( ).stdout().strip('\n'), # 'online.fxreader.uv', # ['c', 'cpp'], - version: '0.1.5.18', + version: '0.1.5.19', # default_options: [ # 'cpp_std=c++23', # # 'prefer_static=true', diff --git a/python/online/fxreader/pr34/commands_typed/cli_bootstrap.py b/python/online/fxreader/pr34/commands_typed/cli_bootstrap.py index 40e9f45..ca9cc64 100644 --- a/python/online/fxreader/pr34/commands_typed/cli_bootstrap.py +++ b/python/online/fxreader/pr34/commands_typed/cli_bootstrap.py @@ -172,10 +172,19 @@ def check_list( ) -def check_type(value: Any, VT: Type[Value]) -> Value: - assert isinstance(value, VT) +def check_type( + value: Any, + VT: Type[Value], + attribute_name: Optional[str] = None, +) -> Value: + if attribute_name: + attribute_value = getattr(value, attribute_name) + assert isinstance(attribute_value, VT) + return attribute_value + else: + assert isinstance(value, VT) - return value + return value def pyproject_load( diff --git a/releases/whl/online_fxreader_pr34-0.1.5.19-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.19-py3-none-any.whl new file mode 100644 index 0000000..07d5c9b --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.19-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c41fac5f3f04295d11aff5a55783580c39a2bbf783c24434af9eab82ecaf7830 +size 72256 From f87de4d9bf9c66976a0dd7686c27e6adabedc48a Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Mon, 4 Aug 2025 09:52:36 +0300 Subject: [PATCH 03/70] [+] improve meson:setup 1. allow to pass mode; --- python/meson.build | 2 +- python/online/fxreader/pr34/commands_typed/cli.py | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/python/meson.build b/python/meson.build index cff2a3b..0230458 100644 --- a/python/meson.build +++ b/python/meson.build @@ -5,7 +5,7 @@ project( ).stdout().strip('\n'), # 'online.fxreader.uv', # ['c', 'cpp'], - version: '0.1.5.19', + version: '0.1.5.20', # default_options: [ # 'cpp_std=c++23', # # 'prefer_static=true', diff --git a/python/online/fxreader/pr34/commands_typed/cli.py b/python/online/fxreader/pr34/commands_typed/cli.py index b5a664c..ccbe3f4 100644 --- a/python/online/fxreader/pr34/commands_typed/cli.py +++ b/python/online/fxreader/pr34/commands_typed/cli.py @@ -648,11 +648,15 @@ class CLI(abc.ABC): force: bool, argv: Optional[list[str]] = None, env: Optional[dict[str, str]] = None, + mode: Optional[Literal['meson', 'pyproject']] = None, # third_party_roots: Optional[list[pathlib.Path]] = None, ) -> None: from . import cli_bootstrap from .os import shutil_rmtree + if mode is None: + mode = 'meson' + project = self.projects[project_name] if argv is None: @@ -668,9 +672,9 @@ class CLI(abc.ABC): logger.info(dict(env=env)) if force: - if (project.build_dir / 'meson').exists(): - logger.info(dict(action='removing build dir', path=project.build_dir / 'meson')) - shutil.rmtree(project.build_dir / 'meson') + if (project.build_dir / mode).exists(): + logger.info(dict(action='removing build dir', path=project.build_dir / mode)) + shutil.rmtree(project.build_dir / mode) extra_args: list[str] = [] @@ -692,8 +696,8 @@ class CLI(abc.ABC): 'mesonbuild.mesonmain', 'setup', str(project.source_dir), - str(project.build_dir / 'meson'), - '-Dmodes=["meson"]', + str(project.build_dir / mode), + '-Dmodes=["{}"]'.format(mode), *extra_args, # '-Dpkgconfig.relocatable=true', '-Dprefix=/', From 41d90d5dcf3471631c9b28691a6c28732b5772de Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Mon, 4 Aug 2025 10:06:40 +0300 Subject: [PATCH 04/70] [+] improve mode usage 1. update ninja command; 2. add .whl for .20, .21 versions; --- python/meson.build | 2 +- python/online/fxreader/pr34/commands_typed/cli.py | 6 +++++- releases/whl/online_fxreader_pr34-0.1.5.20-py3-none-any.whl | 3 +++ releases/whl/online_fxreader_pr34-0.1.5.21-py3-none-any.whl | 3 +++ 4 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.20-py3-none-any.whl create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.21-py3-none-any.whl diff --git a/python/meson.build b/python/meson.build index 0230458..9fca143 100644 --- a/python/meson.build +++ b/python/meson.build @@ -5,7 +5,7 @@ project( ).stdout().strip('\n'), # 'online.fxreader.uv', # ['c', 'cpp'], - version: '0.1.5.20', + version: '0.1.5.21', # default_options: [ # 'cpp_std=c++23', # # 'prefer_static=true', diff --git a/python/online/fxreader/pr34/commands_typed/cli.py b/python/online/fxreader/pr34/commands_typed/cli.py index ccbe3f4..b629a2a 100644 --- a/python/online/fxreader/pr34/commands_typed/cli.py +++ b/python/online/fxreader/pr34/commands_typed/cli.py @@ -484,9 +484,13 @@ class CLI(abc.ABC): project_name: str, argv: Optional[list[str]] = None, env: Optional[dict[str, str]] = None, + mode: Optional[Literal['meson', 'pyproject']] = None, ) -> None: project = self.projects[project_name] + if mode is None: + mode = 'meson' + if argv is None: argv = [] @@ -499,7 +503,7 @@ class CLI(abc.ABC): [ shutil_which('ninja', True), '-C', - str(project.build_dir / 'meson'), + str(project.build_dir / mode), *argv, ], env=dict(list(os.environ.items())) | env, diff --git a/releases/whl/online_fxreader_pr34-0.1.5.20-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.20-py3-none-any.whl new file mode 100644 index 0000000..14cad4f --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.20-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0a04a4880e3887208da31969c5b5158a3a51f83627a752393ebd8b72684ec1a1 +size 72294 diff --git a/releases/whl/online_fxreader_pr34-0.1.5.21-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.21-py3-none-any.whl new file mode 100644 index 0000000..5f3183d --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.21-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9b2f7670ef865966367e81427c1e97d2eb538962b77b1cfc207356f6e91e65c1 +size 72296 From 9d2d48f6ede35e0969caeabfaf6f18efdc32ada5 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Tue, 5 Aug 2025 16:56:52 +0300 Subject: [PATCH 05/70] [+] improve meson wrapper 1. provide properly custom pkg_config_path folders for meson; 1.1. check that pyproject mode correctly discovers library built and installed by meson mode; --- python/meson.build | 2 +- .../fxreader/pr34/commands_typed/cli.py | 29 +++++++++++++------ ...ne_fxreader_pr34-0.1.5.22-py3-none-any.whl | 3 ++ ...ne_fxreader_pr34-0.1.5.23-py3-none-any.whl | 3 ++ 4 files changed, 27 insertions(+), 10 deletions(-) create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.22-py3-none-any.whl create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.23-py3-none-any.whl diff --git a/python/meson.build b/python/meson.build index 9fca143..e0c9bc1 100644 --- a/python/meson.build +++ b/python/meson.build @@ -5,7 +5,7 @@ project( ).stdout().strip('\n'), # 'online.fxreader.uv', # ['c', 'cpp'], - version: '0.1.5.21', + version: '0.1.5.23', # default_options: [ # 'cpp_std=c++23', # # 'prefer_static=true', diff --git a/python/online/fxreader/pr34/commands_typed/cli.py b/python/online/fxreader/pr34/commands_typed/cli.py index b629a2a..c718b4f 100644 --- a/python/online/fxreader/pr34/commands_typed/cli.py +++ b/python/online/fxreader/pr34/commands_typed/cli.py @@ -281,17 +281,27 @@ class CLI(abc.ABC): ] ) - @property + # @property def pkg_config_path( self, - ) -> set[pathlib.Path]: - return { - pathlib.Path(o) - for o in glob.glob( - str(self.dist_settings.env_path / 'lib' / 'python*' / '**' / 'pkgconfig'), - recursive=True, - ) - } + project: Optional[str] = None, + ) -> list[pathlib.Path]: + res: list[pathlib.Path] = [] + + if project: + res.append(self.projects[project].dest_dir / 'lib' / 'pkgconfig') + + res.extend( + [ + pathlib.Path(o) + for o in glob.glob( + str(self.dist_settings.env_path / 'lib' / 'python*' / '**' / 'pkgconfig'), + recursive=True, + ) + ] + ) + + return res def deploy_wheel( self, @@ -701,6 +711,7 @@ class CLI(abc.ABC): 'setup', str(project.source_dir), str(project.build_dir / mode), + '--pkg-config-path={}'.format(json.dumps([str(o) for o in self.pkg_config_path(project_name)])), '-Dmodes=["{}"]'.format(mode), *extra_args, # '-Dpkgconfig.relocatable=true', diff --git a/releases/whl/online_fxreader_pr34-0.1.5.22-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.22-py3-none-any.whl new file mode 100644 index 0000000..ddd7cd1 --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.22-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:58bebb4c696cb36c24ec89e848785bcea89ec3b261f97c54ce8b350626785b98 +size 72324 diff --git a/releases/whl/online_fxreader_pr34-0.1.5.23-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.23-py3-none-any.whl new file mode 100644 index 0000000..7141429 --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.23-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:584d16efd888caace6884bed15449514d48ca670d46d16dff0675ac54642d429 +size 72346 From 671e093726bb7d19545fd90aeaa767e57f1923ee Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Tue, 19 Aug 2025 10:31:10 +0300 Subject: [PATCH 06/70] [+] update nginx 1. fix websocket proxying; 2. add prometheus metrics for nginx ssl; --- .env.examples | 1 + .gitignore | 1 + d1/nginx_config.py | 18 ++++++++++++++++++ docker-compose.yml | 12 ++++++++++++ 4 files changed, 32 insertions(+) create mode 100644 .env.examples diff --git a/.env.examples b/.env.examples new file mode 100644 index 0000000..5afa6b8 --- /dev/null +++ b/.env.examples @@ -0,0 +1 @@ +NGINX_EXPORTER_PORTS=127.0.0.1:9113 diff --git a/.gitignore b/.gitignore index fba53aa..3b2e3fa 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ d2/book1/books python/build .*.kate-swp !releases/whl/*.whl +.env diff --git a/d1/nginx_config.py b/d1/nginx_config.py index 3572036..2b9fc17 100644 --- a/d1/nginx_config.py +++ b/d1/nginx_config.py @@ -84,6 +84,7 @@ def forward( location_body_get = lambda target_endpoint: \ r''' proxy_set_header Host $http_host; + proxy_http_version 1.1; proxy_set_header X-Forwarded-For $t1; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Upgrade $http_upgrade; @@ -292,6 +293,22 @@ stream { servers.append( r''' +server { + server_name _; + listen 80 default_server; + + location = /_metrics { + stub_status; + access_log off; + allow 172.0.0.0/8; # allow only local exporter + deny all; + } + + location ~ { + deny all; + } +} + server { set $t1 $remote_addr; if ($http_x_forwarded_for) @@ -370,6 +387,7 @@ server { proxy_set_header Connection $connection_upgrade; proxy_redirect off; proxy_buffering off; + proxy_http_version 1.1; proxy_pass http://app:80; } } diff --git a/docker-compose.yml b/docker-compose.yml index a8dc8f8..1312c52 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,6 +8,18 @@ services: - ./d1/:/app/d1/:ro - ./tmp/cache/:/app/tmp/cache/:ro restart: on-failure + nginx-exporter: + image: docker.io/nginx/nginx-prometheus-exporter@sha256:6edfb73afd11f2d83ea4e8007f5068c3ffaa38078a6b0ad1339e5bd2f637aacd + #profiles: + # - podman + #env_file: + # .envs/nginx-exporter.env + environment: + SCRAPE_URI: http://ssl-app:80/_status + # LISTEN_ADDRESS: 0.0.0.0:9113 + ports: + - ${NGINX_EXPORTER_PORTS:-"127.0.0.1:9113"}:9113 + ssl-app: build: context: . From a7cb247a4e8ccc7116817d133eee1ad99bffd318 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Tue, 19 Aug 2025 16:32:56 +0300 Subject: [PATCH 07/70] [+] fix docker compose --- d1/nginx_config.py | 3 ++- docker-compose.yml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/d1/nginx_config.py b/d1/nginx_config.py index 2b9fc17..17d2172 100644 --- a/d1/nginx_config.py +++ b/d1/nginx_config.py @@ -300,7 +300,8 @@ server { location = /_metrics { stub_status; access_log off; - allow 172.0.0.0/8; # allow only local exporter + allow 172.0.0.0/8; + allow 127.0.0.1; deny all; } diff --git a/docker-compose.yml b/docker-compose.yml index 1312c52..fd447a8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,7 +15,7 @@ services: #env_file: # .envs/nginx-exporter.env environment: - SCRAPE_URI: http://ssl-app:80/_status + SCRAPE_URI: http://ssl-app:80/_metrics # LISTEN_ADDRESS: 0.0.0.0:9113 ports: - ${NGINX_EXPORTER_PORTS:-"127.0.0.1:9113"}:9113 From 4152024ce7f2c030707ce080110f6ca2ea86ebea Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Thu, 21 Aug 2025 16:55:13 +0300 Subject: [PATCH 08/70] [+] restrict _metrics ips 1. resolve provided host name as ip address and use it allow clients for _metrics endpoint; 1.1. fixed _metrics being exposed from outside, with -H 'Host: blah'; --- d1/nginx_config.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/d1/nginx_config.py b/d1/nginx_config.py index 17d2172..f722554 100644 --- a/d1/nginx_config.py +++ b/d1/nginx_config.py @@ -1,4 +1,5 @@ import json +import socket import os import io import sys @@ -291,6 +292,11 @@ stream { if 'default_server' in ssl_nginx: server = ssl_nginx['default_server'] + if 'metrics_allowed' in server: + metrics_allowed_ip = socket.gethostbyname(server['metrics_allowed']) + else: + metrics_allowed_ip = '127.0.0.1' + servers.append( r''' server { @@ -300,8 +306,9 @@ server { location = /_metrics { stub_status; access_log off; - allow 172.0.0.0/8; - allow 127.0.0.1; + # allow 172.0.0.0/8; + allow {metrics_allowed_ip}; + # allow 127.0.0.1; deny all; } @@ -335,6 +342,8 @@ server { '{domain_key}', server['domain_key'], ).replace( '{ssl_port}', '%d' % ssl_port, + ).replace( + '{metrics_allowed_ip}', metrics_allowed_ip ) ) From e4d38eb53d0b2703d16d947ea33fee239834b382 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Mon, 25 Aug 2025 12:41:38 +0300 Subject: [PATCH 09/70] [+] add checks service --- .dockerignore | 1 + .envs/checks.env | 4 ++++ docker-compose.yml | 9 +++++++++ docker/checks/Dockerfile | 8 ++++++++ docker/checks/rest.py | 5 +++++ 5 files changed, 27 insertions(+) create mode 100644 .envs/checks.env create mode 100644 docker/checks/Dockerfile create mode 100644 docker/checks/rest.py diff --git a/.dockerignore b/.dockerignore index a030649..885f28e 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,4 @@ * .* !d1/blank-app-nginx.conf +!docker/checks diff --git a/.envs/checks.env b/.envs/checks.env new file mode 100644 index 0000000..237cc4a --- /dev/null +++ b/.envs/checks.env @@ -0,0 +1,4 @@ +# UVICORN_HOST=127.0.0.1 +# UVICORN_PORT=80 +# HTTP_AUTH_USERNAME=test +# HTTP_AUTH_PASSWORD=blah diff --git a/docker-compose.yml b/docker-compose.yml index fd447a8..f3a6be0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -30,6 +30,14 @@ services: - ./tmp/d1/letsencrypt:/etc/letsencrypt:rw restart: on-failure + checks: + build: + context: . + dockerfile: ./docker/checks/Dockerfile + init: true + env_file: + .envs/checks.patched.env + cpanel: build: context: . @@ -40,6 +48,7 @@ services: - ./d1/:/app/d1:ro - ./tmp/d1/:/app/tmp/d1/:ro restart: on-failure + dynu: build: context: . diff --git a/docker/checks/Dockerfile b/docker/checks/Dockerfile new file mode 100644 index 0000000..2af1673 --- /dev/null +++ b/docker/checks/Dockerfile @@ -0,0 +1,8 @@ +FROM alpine@sha256:56fa17d2a7e7f168a043a2712e63aed1f8543aeafdcee47c58dcffe38ed51099 + + +WORKDIR /app + +COPY ./docker/checks/rest.py ./docker/checks/rest.py + +CMD ["python3", "docker/checks/rest.py"] diff --git a/docker/checks/rest.py b/docker/checks/rest.py new file mode 100644 index 0000000..6a88ba7 --- /dev/null +++ b/docker/checks/rest.py @@ -0,0 +1,5 @@ +def main() -> None: + raise NotImplementedError + +if __name__ == '__main__': + main() From fa46ce22a2d2e8a50707075f69e1b8ea0da79273 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Mon, 25 Aug 2025 12:44:27 +0300 Subject: [PATCH 10/70] [+] update compose envs --- .gitignore | 2 ++ Makefile | 5 +++++ .envs/checks.env => docker/checks/.env | 0 3 files changed, 7 insertions(+) rename .envs/checks.env => docker/checks/.env (100%) diff --git a/.gitignore b/.gitignore index 3b2e3fa..d44bf86 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ python/build .*.kate-swp !releases/whl/*.whl .env +!docker/*/.env +.envs diff --git a/Makefile b/Makefile index 0de9813..f0cf983 100644 --- a/Makefile +++ b/Makefile @@ -133,6 +133,11 @@ venv_compile: uv pip compile --generate-hashes \ requirements.in > requirements.txt +compose_env: + for s in checks; do \ + cat docker/$$s/.env .envs/$$s.env > .envs/$$s.patched.env; \ + done + MYPY_SOURCES ?= \ d1/cpanel.py mypy: diff --git a/.envs/checks.env b/docker/checks/.env similarity index 100% rename from .envs/checks.env rename to docker/checks/.env From 308625525bff6e3190234e7996379357369a9472 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Mon, 25 Aug 2025 12:45:37 +0300 Subject: [PATCH 11/70] [+] fix checks dockerfile --- docker/checks/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/checks/Dockerfile b/docker/checks/Dockerfile index 2af1673..3f558fd 100644 --- a/docker/checks/Dockerfile +++ b/docker/checks/Dockerfile @@ -1,5 +1,6 @@ FROM alpine@sha256:56fa17d2a7e7f168a043a2712e63aed1f8543aeafdcee47c58dcffe38ed51099 +RUN apk add --no-cache python3 WORKDIR /app From 99a0013f421f6a473ef6e4ad11af576e8dd0e163 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Mon, 25 Aug 2025 18:54:16 +0300 Subject: [PATCH 12/70] [+] add metrics for prometheus, django --- Makefile | 9 - .../fxreader/pr34/commands_typed/metrics.py | 104 +++ python/pyproject.toml | 5 + python/requirements.txt | 644 ++++++++++-------- requirements.in | 6 - requirements.txt | 350 ---------- 6 files changed, 452 insertions(+), 666 deletions(-) create mode 100644 python/online/fxreader/pr34/commands_typed/metrics.py delete mode 100644 requirements.in delete mode 100644 requirements.txt diff --git a/Makefile b/Makefile index f0cf983..5cc8827 100644 --- a/Makefile +++ b/Makefile @@ -124,15 +124,6 @@ systemd: done sudo systemctl daemon-reload -venv: - uv venv - uv pip install -p .venv \ - -r requirements.txt - -venv_compile: - uv pip compile --generate-hashes \ - requirements.in > requirements.txt - compose_env: for s in checks; do \ cat docker/$$s/.env .envs/$$s.env > .envs/$$s.patched.env; \ diff --git a/python/online/fxreader/pr34/commands_typed/metrics.py b/python/online/fxreader/pr34/commands_typed/metrics.py new file mode 100644 index 0000000..770ccb5 --- /dev/null +++ b/python/online/fxreader/pr34/commands_typed/metrics.py @@ -0,0 +1,104 @@ +import pydantic +import json +import logging +import datetime + +import django.http + +from typing import ( + Literal, Any, Optional, Annotated, cast, + TypeVar, Protocol, Generic, Callable, +) + + +logger = logging.getLogger(__name__) + +class Metric(pydantic.BaseModel): + name: str + type: Literal['gauge', 'counter'] + help: Optional[str] = None + + class Sample(pydantic.BaseModel): + value: str + parameters: dict[str, str] + timestamp: Optional[datetime.datetime] = None + + samples: list[Sample] = pydantic.Field( + default_factory=lambda: [], + ) + + @classmethod + def sample_serialize( + cls, + o: 'Metric', + s: 'Metric.Sample', + ) -> str: + samples: list[Metric.Sample] = [s,] + + if o.type == 'gauge': + samples.append( + Metric.Sample( + parameters=s.parameters, + value='NaN', + timestamp=( + s.timestamp + datetime.timedelta(seconds=15) + if s.timestamp + else None + ) + ) + ) + + return ''.join([ + '{metric}{{{parameters}}} {value} {timestamp}\n'.format( + metric=o.name, + parameters=','.join([ + '%s=%s' % ( + k, + json.dumps(v), + ) + for k, v in s2.parameters.items() + ]), + value=s2.value, + timestamp=( + '%.f' % (s2.timestamp.timestamp() * 1000,) + if s2.timestamp + else '' + ), + ) + for s2 in samples + ]) + +def serialize( + metrics: list[Metric], +): + return django.http.HttpResponse( + ''.join([ + '{help}{type}{samples}'.format( + #help='# HELP %s some metric' % o.name, + #type='# TYPE %s counter' % o.name, + help=( + '# HELP {0} {1}\n'.format( + o.name, + o.help + ) + if o.help + else '' + ), + type=( + '# TYPE {0} {1}\n'.format( + o.name, + o.type + ) + if o.type + else '' + ), + samples=''.join([ + Metric.sample_serialize(o, s) + for s in o.samples + ]), + ) + for o in metrics + if len(o.samples) > 0 + ]), + content_type='text/plain; version=0.0.4; charset=utf-8', + ) diff --git a/python/pyproject.toml b/python/pyproject.toml index 597c06a..cf87c01 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -28,6 +28,10 @@ crypto = [ 'cryptography', ] +django = [ + 'django', +] + early = [ 'numpy', 'cryptography', @@ -38,6 +42,7 @@ lint = [ 'tomli', # 'tomllib', 'mypy', + 'django-stubs', 'pyright', 'ruff', # 'tomlkit', diff --git a/python/requirements.txt b/python/requirements.txt index aafd36c..f6bd3b2 100644 --- a/python/requirements.txt +++ b/python/requirements.txt @@ -1,13 +1,17 @@ # This file was autogenerated by uv via the following command: -# uv pip compile --generate-hashes -o /home/nartes/Documents/current/freelance-project-34-marketing-blog/python/requirements.txt /tmp/requirementsx8idgxd2.in +# uv pip compile --generate-hashes -o /home/nartes/Documents/current/freelance-project-34-marketing-blog/python/requirements.txt /tmp/requirements7xt3_2_h.in annotated-types==0.7.0 \ --hash=sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53 \ --hash=sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89 # via pydantic -build==1.2.2.post1 \ - --hash=sha256:1d61c0887fa860c01971625baae8bdd338e517b836a2f70dd1f7aa3a6b2fc5b5 \ - --hash=sha256:b36993e92ca9375a219c99e606a122ff365a760a2d4bba0caa09bd5278b608b7 - # via -r /tmp/requirementsx8idgxd2.in +asgiref==3.9.1 \ + --hash=sha256:a5ab6582236218e5ef1648f242fd9f10626cfd4de8dc377db215d5d5098e3142 \ + --hash=sha256:f3bba7092a48005b5f5bacd747d36ee4a5a61f4a269a6df590b43144355ebd2c + # via django +build==1.3.0 \ + --hash=sha256:698edd0ea270bde950f53aed21f3a0135672206f3911e0176261a31e0e07b397 \ + --hash=sha256:7145f0b5061ba90a1500d60bd1b13ca0a8a4cebdd0cc16ed8adf1c0e739f43b4 + # via -r /tmp/requirements7xt3_2_h.in cffi==1.17.1 \ --hash=sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8 \ --hash=sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2 \ @@ -77,165 +81,174 @@ cffi==1.17.1 \ --hash=sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87 \ --hash=sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b # via cryptography -cryptography==45.0.2 \ - --hash=sha256:057723b79752a142efbc609e90b0dff27b0361ccbee3bd48312d70f5cdf53b78 \ - --hash=sha256:05c2385b1f5c89a17df19900cfb1345115a77168f5ed44bdf6fd3de1ce5cc65b \ - --hash=sha256:08281de408e7eb71ba3cd5098709a356bfdf65eebd7ee7633c3610f0aa80d79b \ - --hash=sha256:10d68763892a7b19c22508ab57799c4423c7c8cd61d7eee4c5a6a55a46511949 \ - --hash=sha256:1655d3a76e3dedb683c982a6c3a2cbfae2d08f47a48ec5a3d58db52b3d29ea6f \ - --hash=sha256:18f8084b7ca3ce1b8d38bdfe33c48116edf9a08b4d056ef4a96dceaa36d8d965 \ - --hash=sha256:2cb03a944a1a412724d15a7c051d50e63a868031f26b6a312f2016965b661942 \ - --hash=sha256:4142e20c29224cec63e9e32eb1e6014fb285fe39b7be66b3564ca978a3a8afe9 \ - --hash=sha256:463096533acd5097f8751115bc600b0b64620c4aafcac10c6d0041e6e68f88fe \ - --hash=sha256:48caa55c528617fa6db1a9c3bf2e37ccb31b73e098ac2b71408d1f2db551dde4 \ - --hash=sha256:49af56491473231159c98c2c26f1a8f3799a60e5cf0e872d00745b858ddac9d2 \ - --hash=sha256:4cc31c66411e14dd70e2f384a9204a859dc25b05e1f303df0f5326691061b839 \ - --hash=sha256:501de1296b2041dccf2115e3c7d4947430585601b251b140970ce255c5cfb985 \ - --hash=sha256:59c0c8f043dd376bbd9d4f636223836aed50431af4c5a467ed9bf61520294627 \ - --hash=sha256:614bca7c6ed0d8ad1dce683a6289afae1f880675b4090878a0136c3da16bc693 \ - --hash=sha256:61a8b1bbddd9332917485b2453d1de49f142e6334ce1d97b7916d5a85d179c84 \ - --hash=sha256:7429936146063bd1b2cfc54f0e04016b90ee9b1c908a7bed0800049cbace70eb \ - --hash=sha256:7c73968fbb7698a4c5d6160859db560d3aac160edde89c751edd5a8bc6560c88 \ - --hash=sha256:80303ee6a02ef38c4253160446cbeb5c400c07e01d4ddbd4ff722a89b736d95a \ - --hash=sha256:965611880c3fa8e504b7458484c0697e00ae6e937279cd6734fdaa2bc954dc49 \ - --hash=sha256:9a900036b42f7324df7c7ad9569eb92ba0b613cf699160dd9c2154b24fd02f8e \ - --hash=sha256:9cfd1399064b13043082c660ddd97a0358e41c8b0dc7b77c1243e013d305c344 \ - --hash=sha256:a8ec324711596fbf21837d3a5db543937dd84597d364769b46e0102250023f77 \ - --hash=sha256:a9727a21957d3327cf6b7eb5ffc9e4b663909a25fea158e3fcbc49d4cdd7881b \ - --hash=sha256:b19f4b28dd2ef2e6d600307fee656c00825a2980c4356a7080bd758d633c3a6f \ - --hash=sha256:b2de529027579e43b6dc1f805f467b102fb7d13c1e54c334f1403ee2b37d0059 \ - --hash=sha256:c0c000c1a09f069632d8a9eb3b610ac029fcc682f1d69b758e625d6ee713f4ed \ - --hash=sha256:cdafb86eb673c3211accffbffdb3cdffa3aaafacd14819e0898d23696d18e4d3 \ - --hash=sha256:d2a90ce2f0f5b695e4785ac07c19a58244092f3c85d57db6d8eb1a2b26d2aad6 \ - --hash=sha256:d784d57b958ffd07e9e226d17272f9af0c41572557604ca7554214def32c26bf \ - --hash=sha256:d891942592789fa0ab71b502550bbadb12f540d7413d7d7c4cef4b02af0f5bc6 \ - --hash=sha256:dc7693573f16535428183de8fd27f0ca1ca37a51baa0b41dc5ed7b3d68fe80e2 \ - --hash=sha256:ddb8d01aa900b741d6b7cc585a97aff787175f160ab975e21f880e89d810781a \ - --hash=sha256:e328357b6bbf79928363dbf13f4635b7aac0306afb7e5ad24d21d0c5761c3253 \ - --hash=sha256:e86c8d54cd19a13e9081898b3c24351683fd39d726ecf8e774aaa9d8d96f5f3a \ - --hash=sha256:e9e4bdcd70216b08801e267c0b563316b787f957a46e215249921f99288456f9 \ - --hash=sha256:f169469d04a23282de9d0be349499cb6683b6ff1b68901210faacac9b0c24b7d - # via -r /tmp/requirementsx8idgxd2.in -marisa-trie==1.2.1 \ - --hash=sha256:06b099dd743676dbcd8abd8465ceac8f6d97d8bfaabe2c83b965495523b4cef2 \ - --hash=sha256:0ee6cf6a16d9c3d1c94e21c8e63c93d8b34bede170ca4e937e16e1c0700d399f \ - --hash=sha256:0fe69fb9ffb2767746181f7b3b29bbd3454d1d24717b5958e030494f3d3cddf3 \ - --hash=sha256:1db3213b451bf058d558f6e619bceff09d1d130214448a207c55e1526e2773a1 \ - --hash=sha256:20948e40ab2038e62b7000ca6b4a913bc16c91a2c2e6da501bd1f917eeb28d51 \ - --hash=sha256:2428b495003c189695fb91ceeb499f9fcced3a2dce853e17fa475519433c67ff \ - --hash=sha256:24a81aa7566e4ec96fc4d934581fe26d62eac47fc02b35fa443a0bb718b471e8 \ - --hash=sha256:25688f34cac3bec01b4f655ffdd6c599a01f0bd596b4a79cf56c6f01a7df3560 \ - --hash=sha256:36aa4401a1180615f74d575571a6550081d84fc6461e9aefc0bb7b2427af098e \ - --hash=sha256:3a27c408e2aefc03e0f1d25b2ff2afb85aac3568f6fa2ae2a53b57a2e87ce29d \ - --hash=sha256:3ad356442c2fea4c2a6f514738ddf213d23930f942299a2b2c05df464a00848a \ - --hash=sha256:429858a0452a7bedcf67bc7bb34383d00f666c980cb75a31bcd31285fbdd4403 \ - --hash=sha256:436f62d27714970b9cdd3b3c41bdad046f260e62ebb0daa38125ef70536fc73b \ - --hash=sha256:46e528ee71808c961baf8c3ce1c46a8337ec7a96cc55389d11baafe5b632f8e9 \ - --hash=sha256:4728ed3ae372d1ea2cdbd5eaa27b8f20a10e415d1f9d153314831e67d963f281 \ - --hash=sha256:536ea19ce6a2ce61c57fed4123ecd10d18d77a0db45cd2741afff2b8b68f15b3 \ - --hash=sha256:5685a14b3099b1422c4f59fa38b0bf4b5342ee6cc38ae57df9666a0b28eeaad3 \ - --hash=sha256:594f98491a96c7f1ffe13ce292cef1b4e63c028f0707effdea0f113364c1ae6c \ - --hash=sha256:5bd39a4e1cc839a88acca2889d17ebc3f202a5039cd6059a13148ce75c8a6244 \ - --hash=sha256:5e43891a37b0d7f618819fea14bd951289a0a8e3dd0da50c596139ca83ebb9b1 \ - --hash=sha256:5e649f3dc8ab5476732094f2828cc90cac3be7c79bc0c8318b6fda0c1d248db4 \ - --hash=sha256:5fe5a286f997848a410eebe1c28657506adaeb405220ee1e16cfcfd10deb37f2 \ - --hash=sha256:638506eacf20ca503fff72221a7e66a6eadbf28d6a4a6f949fcf5b1701bb05ec \ - --hash=sha256:6532615111eec2c79e711965ece0bc95adac1ff547a7fff5ffca525463116deb \ - --hash=sha256:66b23e5b35dd547f85bf98db7c749bc0ffc57916ade2534a6bbc32db9a4abc44 \ - --hash=sha256:6704adf0247d2dda42e876b793be40775dff46624309ad99bc7537098bee106d \ - --hash=sha256:67f0c2ec82c20a02c16fc9ba81dee2586ef20270127c470cb1054767aa8ba310 \ - --hash=sha256:6946100a43f933fad6bc458c502a59926d80b321d5ac1ed2ff9c56605360496f \ - --hash=sha256:6c50c861faad0a5c091bd763e0729f958c316e678dfa065d3984fbb9e4eacbcd \ - --hash=sha256:735c363d9aaac82eaf516a28f7c6b95084c2e176d8231c87328dc80e112a9afa \ - --hash=sha256:746a7c60a17fccd3cfcfd4326926f02ea4fcdfc25d513411a0c4fc8e4a1ca51f \ - --hash=sha256:7ac170d20b97beb75059ba65d1ccad6b434d777c8992ab41ffabdade3b06dd74 \ - --hash=sha256:7cca7f96236ffdbf49be4b2e42c132e3df05968ac424544034767650913524de \ - --hash=sha256:7e7b1786e852e014d03e5f32dbd991f9a9eb223dd3fa9a2564108b807e4b7e1c \ - --hash=sha256:852d7bcf14b0c63404de26e7c4c8d5d65ecaeca935e93794331bc4e2f213660b \ - --hash=sha256:875a6248e60fbb48d947b574ffa4170f34981f9e579bde960d0f9a49ea393ecc \ - --hash=sha256:8951e7ce5d3167fbd085703b4cbb3f47948ed66826bef9a2173c379508776cf5 \ - --hash=sha256:8cf4f25cf895692b232f49aa5397af6aba78bb679fb917a05fce8d3cb1ee446d \ - --hash=sha256:952af3a5859c3b20b15a00748c36e9eb8316eb2c70bd353ae1646da216322908 \ - --hash=sha256:98042040d1d6085792e8d0f74004fc0f5f9ca6091c298f593dd81a22a4643854 \ - --hash=sha256:9c9b32b14651a6dcf9e8857d2df5d29d322a1ea8c0be5c8ffb88f9841c4ec62b \ - --hash=sha256:9e956e6a46f604b17d570901e66f5214fb6f658c21e5e7665deace236793cef6 \ - --hash=sha256:9f627f4e41be710b6cb6ed54b0128b229ac9d50e2054d9cde3af0fef277c23cf \ - --hash=sha256:a2eb41d2f9114d8b7bd66772c237111e00d2bae2260824560eaa0a1e291ce9e8 \ - --hash=sha256:a3c98613180cf1730e221933ff74b454008161b1a82597e41054127719964188 \ - --hash=sha256:a4177dc0bd1374e82be9b2ba4d0c2733b0a85b9d154ceeea83a5bee8c1e62fbf \ - --hash=sha256:a8443d116c612cfd1961fbf76769faf0561a46d8e317315dd13f9d9639ad500c \ - --hash=sha256:aa7cd17e1c690ce96c538b2f4aae003d9a498e65067dd433c52dd069009951d4 \ - --hash=sha256:ad548117744b2bcf0e3d97374608be0a92d18c2af13d98b728d37cd06248e571 \ - --hash=sha256:aefe0973cc4698e0907289dc0517ab0c7cdb13d588201932ff567d08a50b0e2e \ - --hash=sha256:b0ef26733d3c836be79e812071e1a431ce1f807955a27a981ebb7993d95f842b \ - --hash=sha256:b1ce340da608530500ab4f963f12d6bfc8d8680900919a60dbdc9b78c02060a4 \ - --hash=sha256:b1ec93f0d1ee6d7ab680a6d8ea1a08bf264636358e92692072170032dda652ba \ - --hash=sha256:b2a7d00f53f4945320b551bccb826b3fb26948bde1a10d50bb9802fabb611b10 \ - --hash=sha256:b2eacb84446543082ec50f2fb563f1a94c96804d4057b7da8ed815958d0cdfbe \ - --hash=sha256:b5ea16e69bfda0ac028c921b58de1a4aaf83d43934892977368579cd3c0a2554 \ - --hash=sha256:bd45142501300e7538b2e544905580918b67b1c82abed1275fe4c682c95635fa \ - --hash=sha256:c0fe2ace0cb1806badbd1c551a8ec2f8d4cf97bf044313c082ef1acfe631ddca \ - --hash=sha256:c484410911182457a8a1a0249d0c09c01e2071b78a0a8538cd5f7fa45589b13a \ - --hash=sha256:ce37d8ca462bb64cc13f529b9ed92f7b21fe8d1f1679b62e29f9cb7d0e888b49 \ - --hash=sha256:ce59bcd2cda9bb52b0e90cc7f36413cd86c3d0ce7224143447424aafb9f4aa48 \ - --hash=sha256:d2a82eb21afdaf22b50d9b996472305c05ca67fc4ff5a026a220320c9c961db6 \ - --hash=sha256:d5648c6dcc5dc9200297fb779b1663b8a4467bda034a3c69bd9c32d8afb33b1d \ - --hash=sha256:d659fda873d8dcb2c14c2c331de1dee21f5a902d7f2de7978b62c6431a8850ef \ - --hash=sha256:d7eb20bf0e8b55a58d2a9b518aabc4c18278787bdba476c551dd1c1ed109e509 \ - --hash=sha256:da4e4facb79614cc4653cfd859f398e4db4ca9ab26270ff12610e50ed7f1f6c6 \ - --hash=sha256:de1665eaafefa48a308e4753786519888021740501a15461c77bdfd57638e6b4 \ - --hash=sha256:e2699255d7ac610dee26d4ae7bda5951d05c7d9123a22e1f7c6a6f1964e0a4e4 \ - --hash=sha256:e58788004adda24c401d1751331618ed20c507ffc23bfd28d7c0661a1cf0ad16 \ - --hash=sha256:e70869737cc0e5bd903f620667da6c330d6737048d1f44db792a6af68a1d35be \ - --hash=sha256:eba6ca45500ca1a042466a0684aacc9838e7f20fe2605521ee19f2853062798f \ - --hash=sha256:ed3fb4ed7f2084597e862bcd56c56c5529e773729a426c083238682dba540e98 \ - --hash=sha256:f2806f75817392cedcacb24ac5d80b0350dde8d3861d67d045c1d9b109764114 \ - --hash=sha256:f35c2603a6be168088ed1db6ad1704b078aa8f39974c60888fbbced95dcadad4 \ - --hash=sha256:f4cd800704a5fc57e53c39c3a6b0c9b1519ebdbcb644ede3ee67a06eb542697d \ - --hash=sha256:f713af9b8aa66a34cd3a78c7d150a560a75734713abe818a69021fd269e927fa - # via -r /tmp/requirementsx8idgxd2.in -meson==1.8.0 \ - --hash=sha256:0a9b23311271519bd03dca12d7d8b0eab582c3a2c5da433d465b6e519dc88e2f \ - --hash=sha256:472b7b25da286447333d32872b82d1c6f1a34024fb8ee017d7308056c25fec1f +cryptography==45.0.6 \ + --hash=sha256:00e8724bdad672d75e6f069b27970883179bd472cd24a63f6e620ca7e41cc0c5 \ + --hash=sha256:048e7ad9e08cf4c0ab07ff7f36cc3115924e22e2266e034450a890d9e312dd74 \ + --hash=sha256:0d9ef57b6768d9fa58e92f4947cea96ade1233c0e236db22ba44748ffedca394 \ + --hash=sha256:18f878a34b90d688982e43f4b700408b478102dd58b3e39de21b5ebf6509c301 \ + --hash=sha256:1b7fa6a1c1188c7ee32e47590d16a5a0646270921f8020efc9a511648e1b2e08 \ + --hash=sha256:20ae4906a13716139d6d762ceb3e0e7e110f7955f3bc3876e3a07f5daadec5f3 \ + --hash=sha256:20d15aed3ee522faac1a39fbfdfee25d17b1284bafd808e1640a74846d7c4d1b \ + --hash=sha256:2384f2ab18d9be88a6e4f8972923405e2dbb8d3e16c6b43f15ca491d7831bd18 \ + --hash=sha256:275ba5cc0d9e320cd70f8e7b96d9e59903c815ca579ab96c1e37278d231fc402 \ + --hash=sha256:2dac5ec199038b8e131365e2324c03d20e97fe214af051d20c49db129844e8b3 \ + --hash=sha256:31a2b9a10530a1cb04ffd6aa1cd4d3be9ed49f7d77a4dafe198f3b382f41545c \ + --hash=sha256:3436128a60a5e5490603ab2adbabc8763613f638513ffa7d311c900a8349a2a0 \ + --hash=sha256:3b5bf5267e98661b9b888a9250d05b063220dfa917a8203744454573c7eb79db \ + --hash=sha256:3de77e4df42ac8d4e4d6cdb342d989803ad37707cf8f3fbf7b088c9cbdd46427 \ + --hash=sha256:44647c5d796f5fc042bbc6d61307d04bf29bccb74d188f18051b635f20a9c75f \ + --hash=sha256:550ae02148206beb722cfe4ef0933f9352bab26b087af00e48fdfb9ade35c5b3 \ + --hash=sha256:599c8d7df950aa68baa7e98f7b73f4f414c9f02d0e8104a30c0182a07732638b \ + --hash=sha256:5b64e668fc3528e77efa51ca70fadcd6610e8ab231e3e06ae2bab3b31c2b8ed9 \ + --hash=sha256:5bd6020c80c5b2b2242d6c48487d7b85700f5e0038e67b29d706f98440d66eb5 \ + --hash=sha256:5c966c732cf6e4a276ce83b6e4c729edda2df6929083a952cc7da973c539c719 \ + --hash=sha256:629127cfdcdc6806dfe234734d7cb8ac54edaf572148274fa377a7d3405b0043 \ + --hash=sha256:705bb7c7ecc3d79a50f236adda12ca331c8e7ecfbea51edd931ce5a7a7c4f012 \ + --hash=sha256:780c40fb751c7d2b0c6786ceee6b6f871e86e8718a8ff4bc35073ac353c7cd02 \ + --hash=sha256:7a3085d1b319d35296176af31c90338eeb2ddac8104661df79f80e1d9787b8b2 \ + --hash=sha256:826b46dae41a1155a0c0e66fafba43d0ede1dc16570b95e40c4d83bfcf0a451d \ + --hash=sha256:833dc32dfc1e39b7376a87b9a6a4288a10aae234631268486558920029b086ec \ + --hash=sha256:cc4d66f5dc4dc37b89cfef1bd5044387f7a1f6f0abb490815628501909332d5d \ + --hash=sha256:d063341378d7ee9c91f9d23b431a3502fc8bfacd54ef0a27baa72a0843b29159 \ + --hash=sha256:e2a21a8eda2d86bb604934b6b37691585bd095c1f788530c1fcefc53a82b3453 \ + --hash=sha256:e40b80ecf35ec265c452eea0ba94c9587ca763e739b8e559c128d23bff7ebbbf \ + --hash=sha256:e5b3dda1b00fb41da3af4c5ef3f922a200e33ee5ba0f0bc9ecf0b0c173958385 \ + --hash=sha256:ea3c42f2016a5bbf71825537c2ad753f2870191134933196bee408aac397b3d9 \ + --hash=sha256:eccddbd986e43014263eda489abbddfbc287af5cddfd690477993dbb31e31016 \ + --hash=sha256:ee411a1b977f40bd075392c80c10b58025ee5c6b47a822a33c1198598a7a5f05 \ + --hash=sha256:f4028f29a9f38a2025abedb2e409973709c660d44319c61762202206ed577c42 \ + --hash=sha256:f68f833a9d445cc49f01097d95c83a850795921b3f7cc6488731e69bde3288da \ + --hash=sha256:fc022c1fa5acff6def2fc6d7819bbbd31ccddfe67d075331a65d9cfb28a20983 + # via -r /tmp/requirements7xt3_2_h.in +django==5.2.5 \ + --hash=sha256:0745b25681b129a77aae3d4f6549b62d3913d74407831abaa0d9021a03954bae \ + --hash=sha256:2b2ada0ee8a5ff743a40e2b9820d1f8e24c11bac9ae6469cd548f0057ea6ddcd + # via + # django-stubs + # django-stubs-ext +django-stubs==5.2.2 \ + --hash=sha256:2a04b510c7a812f88223fd7e6d87fb4ea98717f19c8e5c8b59691d83ad40a8a6 \ + --hash=sha256:79bd0fdbc78958a8f63e0b062bd9d03f1de539664476c0be62ade5f063c9e41e + # via -r /tmp/requirements7xt3_2_h.in +django-stubs-ext==5.2.2 \ + --hash=sha256:8833bbe32405a2a0ce168d3f75a87168f61bd16939caf0e8bf173bccbd8a44c5 \ + --hash=sha256:d9d151b919fe2438760f5bd938f03e1cb08c84d0651f9e5917f1313907e42683 + # via django-stubs +marisa-trie==1.3.0 \ + --hash=sha256:0111d6067c5a52141585a9213e073aa0d0438ba1c6febc40f827c5cadd3aa5d8 \ + --hash=sha256:034e483bd35ab6d136d8a91f43088dc78549394cf3787fdeebca144e2e4c82df \ + --hash=sha256:04bf4a128d8ec1881477364269034df620ebcec0ab0fd54bf2c5ee4779df10fe \ + --hash=sha256:05ba1011626d8845643a29449e1de5faed01e9e2b261825ac67a9675ce7f7426 \ + --hash=sha256:06ad6722d6d3f3be1f1a9b2b61afe8836e37d9f7ac4d23ebeb4b1acb043b2559 \ + --hash=sha256:0ec9d7fa8e16eb2399b9ab5677bca5fcca3dbc58f0b285f158c2da5fb79080d4 \ + --hash=sha256:10767b992ab20d24d8e97b54f89c5b0149e979d10bf88bb0151bee99f0f996a3 \ + --hash=sha256:10dce1641ef253eec9db7c5931763643b81d39e9d9e45c537d4739b6a09856f9 \ + --hash=sha256:10e4722fdb7b87ccf9ca279c7f7d8a2ed5b64934b9cd36cbcd5cdca81365db4d \ + --hash=sha256:1a56cc700b1405cc75fde9197f9d2fed66ecbbaee7bdf1f28728494f119dc7f3 \ + --hash=sha256:22a9140ffc7a82855bb41d6140e77c658d6a2abbf613b227adb1b786f53962ec \ + --hash=sha256:2379030b1339a38110509cd1f4d8ecbe6647c5df85eccc7f2133bcdc55855082 \ + --hash=sha256:284354853d5292b722abe4bfb9fbfff8015e9edd9462b097072875ed8c99e0d6 \ + --hash=sha256:28bfd6fada6c87cb31d300bbed5de1bfd338f8c98d1b834cf810a06ce019a020 \ + --hash=sha256:2e598970f95c9bb7f4f5a27d5e11ec2babfac1f737910395009a1753283f15dd \ + --hash=sha256:31c891ebce899f35936d4ab9f332b69ab762513d5944b0f43f61427e53671d42 \ + --hash=sha256:31ca1258ec765f47e4df6b46cdb562caff762a9126ab72276415bca1b34d1a16 \ + --hash=sha256:324ca8b80f76016fc459e1c2b6cab8df12e4fd43830700c7290650651f71f662 \ + --hash=sha256:39af3060b4ab41a3cce18b1808338db8bf50b6ec4b81be3cc452558aaad95581 \ + --hash=sha256:3b3a3a8b5b2ee26fa72e6c92a7b31731f79c1f81e7c0a2041e8e6b5d19497bac \ + --hash=sha256:3bd0af8668d0858f174085fcac5062d38a44ee35a230fb211e7164d791ac07c3 \ + --hash=sha256:4570850d9b6e6a099797f731652dbe764dfd6dd7eff2934318a7018ba1a82cf1 \ + --hash=sha256:548b9b020a6c5ed210e13f706b9fb1d097cfc510c1a02e757ea0d61bdcf17c80 \ + --hash=sha256:58f1b70501c2462583bce5639a65af5516e9785ae6b3158533ddeecde70f0675 \ + --hash=sha256:5b37b55dd120b6dad14ee4cdab5f57dafb1a937decf148f67d13df3392e421a9 \ + --hash=sha256:5c6f0c01c3853c3cc65f7b7db1c1ce3181f7479a2cc4de145fae53db3cc5193b \ + --hash=sha256:5d72ffde56fb1515bcb03539803d42d0a119f6782c5812bf2b7313eddc691735 \ + --hash=sha256:5e5acc03e489201b26a98251d0e8eedca43a32ab2bc1840a6cd5e8b918e193a3 \ + --hash=sha256:608d965d47f40b8cd402215b95d85db899268d277ae5b8ebe87b7acdd3e2a0bb \ + --hash=sha256:644e64763617b346bb66bdaa7a286bedc888cd2afa8f3b0219de62f996c701bc \ + --hash=sha256:6482ab865261164b6577c5016b3d8a14ba1baf966945e203d78d7994702d45e4 \ + --hash=sha256:6a1f0781bccd854184a9c59b095ed09adf16627460eb8df4a91dc3f87e882352 \ + --hash=sha256:6e8e2f1394eecfb780a25950849d64a799b79f538d17945e42b1652da4e0cae4 \ + --hash=sha256:714dabb0ddd4be72841c962d0559d5a80613964dc2a5db72651ae3b2ae3408fc \ + --hash=sha256:80bf10d0d2a19bdbc1fe1174a2887dcdaaba857218d3d627adea9045a54f5a17 \ + --hash=sha256:80f158464e05d6e063abaebfb8811f48333e2337605d852ae9065d442b637dd0 \ + --hash=sha256:8b39a7314f6ad141c9c24acff0a71f4fdae1eab5ea827468c40afafc0662cab3 \ + --hash=sha256:8fc98a5362a25c27c1372af68253ba19ec0b27f1423fce307516257458bcf778 \ + --hash=sha256:9079d9d88921e46de1b65214d28608974dfcac2b49ee74f03807dc03e9d0da20 \ + --hash=sha256:9210446587d3daa40c2fe808b966a80e03995eeb6688c475b77276200524f0a0 \ + --hash=sha256:932b0101cf39d20afc07d71726b709376cbaf06316e4ce5008e2c1c21c9a925d \ + --hash=sha256:938e6e9ed7675a0a2c520926897c02126749e12a6cb6c2e7c910e7ea83aa40f3 \ + --hash=sha256:938f618d2cece8358899c688591d94db6652d9e1076c15a7efdfcfdc64a96cdb \ + --hash=sha256:989ba916e7747817b6fd2c46f2d40371ab3adaf026c1e6b4cded251ce1768ae4 \ + --hash=sha256:9a6a18176b283950c7f6c4c0952c3bb8b4430e5b38d645a0d96f12ff8c650a73 \ + --hash=sha256:9c7631f8442a4407b72a150089b6b804fbc06c4494ff45c96c4469e44aaf0003 \ + --hash=sha256:a1b34336cd3a7bc84d29ca6da4f38e6845b83cb18b38362f967b0a3096847ec2 \ + --hash=sha256:a22e8e3b82533fc71fa34d28e3563e72e7863810c786a8e3c350ede0fe3f4ad7 \ + --hash=sha256:a6e9b4cec99935cbc339d3896852c045605dd65910e8c534998d751113a0f767 \ + --hash=sha256:b71462677dc6c119589755394086cffbcf4d4d42f906fefb325c982c679406d6 \ + --hash=sha256:bd53e6b99008ff3dab6455791800af405351d98fbf01c4f474642afb1499236d \ + --hash=sha256:c27bde381c46574f3f534b4a62c42485e80e0e26c127899f83a391dd2c2bf078 \ + --hash=sha256:cba78321fae9b825f2bfcb2c3f66f60ab773777a8d2fcb34468daac657e0fc48 \ + --hash=sha256:cc6ea03831be59a50dbe7afc3691fa3cc8f0c6a1af48e98eccb749cbe03a5414 \ + --hash=sha256:d33818e5ece65da895d2262519abd752b3ef96245ae977ebe970f5a0631bcb83 \ + --hash=sha256:d6bb4a231d12b4e58d4f7250a8491f529ca41ef2171d3fa15fba13dce3c2efff \ + --hash=sha256:d85a0484f8ecd3a6c843c1b10b42953f14278b35ce30d94bc7cb6305604a6109 \ + --hash=sha256:dc6a1cca4ad5bead99efde0079605bc059f856b00be9b58b0f5978665ece7bb9 \ + --hash=sha256:e2dd0868d3695c742166b7922608f9c5bbf89f536c2144743ca5a62a24290a08 \ + --hash=sha256:e79b517386135eb84c3459805047bfb173df2763b1aa322a66864f13d620bd83 \ + --hash=sha256:e945c78652b01720d419051cf37642165878abb182d555f99390c7d36cec6152 \ + --hash=sha256:ea63c74aa88d0dc24464bc356bc31625318e58b5dd20169d98e696baa3f91ffd \ + --hash=sha256:ee193c1f26d9a10bbc56b9bd1e3b16c79ed0e0e44387275f8054d4cf853804d1 \ + --hash=sha256:f03cea2fabebf4f1429ccb87c4037dacd828050e8829cacb233f0865bda4244e \ + --hash=sha256:f44e0c0c339fe44dd3e7fcbab91cc1a5888c12c35a8bf2811b3eb85236570b29 + # via -r /tmp/requirements7xt3_2_h.in +meson==1.9.0 \ + --hash=sha256:45e51ddc41e37d961582d06e78c48e0f9039011587f3495c4d6b0781dad92357 \ + --hash=sha256:cd27277649b5ed50d19875031de516e270b22e890d9db65ed9af57d18ebc498d # via meson-python meson-python==0.18.0 \ --hash=sha256:3b0fe051551cc238f5febb873247c0949cd60ded556efa130aa57021804868e2 \ --hash=sha256:c56a99ec9df669a40662fe46960321af6e4b14106c14db228709c1628e23848d - # via -r /tmp/requirementsx8idgxd2.in -mypy==1.15.0 \ - --hash=sha256:1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e \ - --hash=sha256:171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22 \ - --hash=sha256:1905f494bfd7d85a23a88c5d97840888a7bd516545fc5aaedff0267e0bb54e2f \ - --hash=sha256:1fbb8da62dc352133d7d7ca90ed2fb0e9d42bb1a32724c287d3c76c58cbaa9c2 \ - --hash=sha256:2922d42e16d6de288022e5ca321cd0618b238cfc5570e0263e5ba0a77dbef56f \ - --hash=sha256:2e2c2e6d3593f6451b18588848e66260ff62ccca522dd231cd4dd59b0160668b \ - --hash=sha256:2ee2d57e01a7c35de00f4634ba1bbf015185b219e4dc5909e281016df43f5ee5 \ - --hash=sha256:2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f \ - --hash=sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43 \ - --hash=sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e \ - --hash=sha256:5a95fb17c13e29d2d5195869262f8125dfdb5c134dc8d9a9d0aecf7525b10c2c \ - --hash=sha256:6983aae8b2f653e098edb77f893f7b6aca69f6cffb19b2cc7443f23cce5f4828 \ - --hash=sha256:712e962a6357634fef20412699a3655c610110e01cdaa6180acec7fc9f8513ba \ - --hash=sha256:8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee \ - --hash=sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d \ - --hash=sha256:8f8722560a14cde92fdb1e31597760dc35f9f5524cce17836c0d22841830fd5b \ - --hash=sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445 \ - --hash=sha256:973500e0774b85d9689715feeffcc980193086551110fd678ebe1f4342fb7c5e \ - --hash=sha256:979e4e1a006511dacf628e36fadfecbcc0160a8af6ca7dad2f5025529e082c13 \ - --hash=sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5 \ - --hash=sha256:aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd \ - --hash=sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf \ - --hash=sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357 \ - --hash=sha256:be68172e9fd9ad8fb876c6389f16d1c1b5f100ffa779f77b1fb2176fcc9ab95b \ - --hash=sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036 \ - --hash=sha256:c4bb0e1bd29f7d34efcccd71cf733580191e9a264a2202b0239da95984c5b559 \ - --hash=sha256:c7be1e46525adfa0d97681432ee9fcd61a3964c2446795714699a998d193f1a3 \ - --hash=sha256:c9817fa23833ff189db061e6d2eff49b2f3b6ed9856b4a0a73046e41932d744f \ - --hash=sha256:ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464 \ - --hash=sha256:d10d994b41fb3497719bbf866f227b3489048ea4bbbb5015357db306249f7980 \ - --hash=sha256:e601a7fa172c2131bff456bb3ee08a88360760d0d2f8cbd7a75a65497e2df078 \ - --hash=sha256:f95579473af29ab73a10bada2f9722856792a36ec5af5399b653aa28360290a5 - # via -r /tmp/requirementsx8idgxd2.in + # via -r /tmp/requirements7xt3_2_h.in +mypy==1.17.1 \ + --hash=sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341 \ + --hash=sha256:064e2ff508e5464b4bd807a7c1625bc5047c5022b85c70f030680e18f37273a5 \ + --hash=sha256:099b9a5da47de9e2cb5165e581f158e854d9e19d2e96b6698c0d64de911dd849 \ + --hash=sha256:15a83369400454c41ed3a118e0cc58bd8123921a602f385cb6d6ea5df050c733 \ + --hash=sha256:15d54056f7fe7a826d897789f53dd6377ec2ea8ba6f776dc83c2902b899fee81 \ + --hash=sha256:1b16708a66d38abb1e6b5702f5c2c87e133289da36f6a1d15f6a5221085c6403 \ + --hash=sha256:209a58fed9987eccc20f2ca94afe7257a8f46eb5df1fb69958650973230f91e6 \ + --hash=sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01 \ + --hash=sha256:397fba5d7616a5bc60b45c7ed204717eaddc38f826e3645402c426057ead9a91 \ + --hash=sha256:3fbe6d5555bf608c47203baa3e72dbc6ec9965b3d7c318aa9a4ca76f465bd972 \ + --hash=sha256:43808d9476c36b927fbcd0b0255ce75efe1b68a080154a38ae68a7e62de8f0f8 \ + --hash=sha256:55b918670f692fc9fba55c3298d8a3beae295c5cded0a55dccdc5bbead814acd \ + --hash=sha256:5d1092694f166a7e56c805caaf794e0585cabdbf1df36911c414e4e9abb62ae9 \ + --hash=sha256:62761474061feef6f720149d7ba876122007ddc64adff5ba6f374fda35a018a0 \ + --hash=sha256:665afab0963a4b39dff7c1fa563cc8b11ecff7910206db4b2e64dd1ba25aed19 \ + --hash=sha256:69e83ea6553a3ba79c08c6e15dbd9bfa912ec1e493bf75489ef93beb65209aeb \ + --hash=sha256:70401bbabd2fa1aa7c43bb358f54037baf0586f41e83b0ae67dd0534fc64edfd \ + --hash=sha256:79d44f9bfb004941ebb0abe8eff6504223a9c1ac51ef967d1263c6572bbebc99 \ + --hash=sha256:80ef5c058b7bce08c83cac668158cb7edea692e458d21098c7d3bce35a5d43e7 \ + --hash=sha256:89e972c0035e9e05823907ad5398c5a73b9f47a002b22359b177d40bdaee7056 \ + --hash=sha256:93378d3203a5c0800c6b6d850ad2f19f7a3cdf1a3701d3416dbf128805c6a6a7 \ + --hash=sha256:9a2b7d9180aed171f033c9f2fc6c204c1245cf60b0cb61cf2e7acc24eea78e0a \ + --hash=sha256:9d6b20b97d373f41617bd0708fd46aa656059af57f2ef72aa8c7d6a2b73b74ed \ + --hash=sha256:a76906f26bd8d51ea9504966a9c25419f2e668f012e0bdf3da4ea1526c534d94 \ + --hash=sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9 \ + --hash=sha256:ad37544be07c5d7fba814eb370e006df58fed8ad1ef33ed1649cb1889ba6ff58 \ + --hash=sha256:b01586eed696ec905e61bd2568f48740f7ac4a45b3a468e6423a03d3788a51a8 \ + --hash=sha256:c1fdf4abb29ed1cb091cf432979e162c208a5ac676ce35010373ff29247bcad5 \ + --hash=sha256:c49562d3d908fd49ed0938e5423daed8d407774a479b595b143a3d7f87cdae6a \ + --hash=sha256:c4a580f8a70c69e4a75587bd925d298434057fe2a428faaf927ffe6e4b9a98df \ + --hash=sha256:c837b896b37cd103570d776bda106eabb8737aa6dd4f248451aecf53030cdbeb \ + --hash=sha256:d7598cf74c3e16539d4e2f0b8d8c318e00041553d83d4861f87c7a72e95ac24d \ + --hash=sha256:dd86bb649299f09d987a2eebb4d52d10603224500792e1bee18303bbcc1ce390 \ + --hash=sha256:e79311f2d904ccb59787477b7bd5d26f3347789c06fcd7656fa500875290264b \ + --hash=sha256:e92bdc656b7757c438660f775f872a669b8ff374edc4d18277d86b63edba6b8b \ + --hash=sha256:fa6ffadfbe6994d724c5a1bb6123a7d27dd68fc9c059561cd33b664a79578e14 \ + --hash=sha256:feb8cc32d319edd5859da2cc084493b3e2ce5e49a946377663cc90f6c15fb259 \ + --hash=sha256:ff2933428516ab63f961644bc49bc4cbe42bbffb2cd3b71cc7277c07d16b1a8b + # via -r /tmp/requirements7xt3_2_h.in mypy-extensions==1.1.0 \ --hash=sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505 \ --hash=sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558 @@ -244,63 +257,82 @@ nodeenv==1.9.1 \ --hash=sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f \ --hash=sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9 # via pyright -numpy==2.2.6 \ - --hash=sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff \ - --hash=sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47 \ - --hash=sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84 \ - --hash=sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d \ - --hash=sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6 \ - --hash=sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f \ - --hash=sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b \ - --hash=sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49 \ - --hash=sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163 \ - --hash=sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571 \ - --hash=sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42 \ - --hash=sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff \ - --hash=sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491 \ - --hash=sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4 \ - --hash=sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566 \ - --hash=sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf \ - --hash=sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40 \ - --hash=sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd \ - --hash=sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06 \ - --hash=sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282 \ - --hash=sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680 \ - --hash=sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db \ - --hash=sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3 \ - --hash=sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90 \ - --hash=sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1 \ - --hash=sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289 \ - --hash=sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab \ - --hash=sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c \ - --hash=sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d \ - --hash=sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb \ - --hash=sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d \ - --hash=sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a \ - --hash=sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf \ - --hash=sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1 \ - --hash=sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2 \ - --hash=sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a \ - --hash=sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543 \ - --hash=sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00 \ - --hash=sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c \ - --hash=sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f \ - --hash=sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd \ - --hash=sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868 \ - --hash=sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303 \ - --hash=sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83 \ - --hash=sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3 \ - --hash=sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d \ - --hash=sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87 \ - --hash=sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa \ - --hash=sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f \ - --hash=sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae \ - --hash=sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda \ - --hash=sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915 \ - --hash=sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249 \ - --hash=sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de \ - --hash=sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8 - # via -r /tmp/requirementsx8idgxd2.in +numpy==2.3.2 \ + --hash=sha256:07b62978075b67eee4065b166d000d457c82a1efe726cce608b9db9dd66a73a5 \ + --hash=sha256:087ffc25890d89a43536f75c5fe8770922008758e8eeeef61733957041ed2f9b \ + --hash=sha256:092aeb3449833ea9c0bf0089d70c29ae480685dd2377ec9cdbbb620257f84631 \ + --hash=sha256:095737ed986e00393ec18ec0b21b47c22889ae4b0cd2d5e88342e08b01141f58 \ + --hash=sha256:0a4f2021a6da53a0d580d6ef5db29947025ae8b35b3250141805ea9a32bbe86b \ + --hash=sha256:103ea7063fa624af04a791c39f97070bf93b96d7af7eb23530cd087dc8dbe9dc \ + --hash=sha256:11e58218c0c46c80509186e460d79fbdc9ca1eb8d8aee39d8f2dc768eb781089 \ + --hash=sha256:122bf5ed9a0221b3419672493878ba4967121514b1d7d4656a7580cd11dddcbf \ + --hash=sha256:14a91ebac98813a49bc6aa1a0dfc09513dcec1d97eaf31ca21a87221a1cdcb15 \ + --hash=sha256:1f91e5c028504660d606340a084db4b216567ded1056ea2b4be4f9d10b67197f \ + --hash=sha256:20b8200721840f5621b7bd03f8dcd78de33ec522fc40dc2641aa09537df010c3 \ + --hash=sha256:240259d6564f1c65424bcd10f435145a7644a65a6811cfc3201c4a429ba79170 \ + --hash=sha256:2738534837c6a1d0c39340a190177d7d66fdf432894f469728da901f8f6dc910 \ + --hash=sha256:27c9f90e7481275c7800dc9c24b7cc40ace3fdb970ae4d21eaff983a32f70c91 \ + --hash=sha256:293b2192c6bcce487dbc6326de5853787f870aeb6c43f8f9c6496db5b1781e45 \ + --hash=sha256:2c3271cc4097beb5a60f010bcc1cc204b300bb3eafb4399376418a83a1c6373c \ + --hash=sha256:2f4f0215edb189048a3c03bd5b19345bdfa7b45a7a6f72ae5945d2a28272727f \ + --hash=sha256:3dcf02866b977a38ba3ec10215220609ab9667378a9e2150615673f3ffd6c73b \ + --hash=sha256:4209f874d45f921bde2cff1ffcd8a3695f545ad2ffbef6d3d3c6768162efab89 \ + --hash=sha256:448a66d052d0cf14ce9865d159bfc403282c9bc7bb2a31b03cc18b651eca8b1a \ + --hash=sha256:4ae6863868aaee2f57503c7a5052b3a2807cf7a3914475e637a0ecd366ced220 \ + --hash=sha256:4d002ecf7c9b53240be3bb69d80f86ddbd34078bae04d87be81c1f58466f264e \ + --hash=sha256:4e6ecfeddfa83b02318f4d84acf15fbdbf9ded18e46989a15a8b6995dfbf85ab \ + --hash=sha256:508b0eada3eded10a3b55725b40806a4b855961040180028f52580c4729916a2 \ + --hash=sha256:546aaf78e81b4081b2eba1d105c3b34064783027a06b3ab20b6eba21fb64132b \ + --hash=sha256:572d5512df5470f50ada8d1972c5f1082d9a0b7aa5944db8084077570cf98370 \ + --hash=sha256:5ad4ebcb683a1f99f4f392cc522ee20a18b2bb12a2c1c42c3d48d5a1adc9d3d2 \ + --hash=sha256:66459dccc65d8ec98cc7df61307b64bf9e08101f9598755d42d8ae65d9a7a6ee \ + --hash=sha256:6936aff90dda378c09bea075af0d9c675fe3a977a9d2402f95a87f440f59f619 \ + --hash=sha256:69779198d9caee6e547adb933941ed7520f896fd9656834c300bdf4dd8642712 \ + --hash=sha256:6f1ae3dcb840edccc45af496f312528c15b1f79ac318169d094e85e4bb35fdf1 \ + --hash=sha256:71669b5daae692189540cffc4c439468d35a3f84f0c88b078ecd94337f6cb0ec \ + --hash=sha256:72c6df2267e926a6d5286b0a6d556ebe49eae261062059317837fda12ddf0c1a \ + --hash=sha256:72dbebb2dcc8305c431b2836bcc66af967df91be793d63a24e3d9b741374c450 \ + --hash=sha256:754d6755d9a7588bdc6ac47dc4ee97867271b17cee39cb87aef079574366db0a \ + --hash=sha256:76c3e9501ceb50b2ff3824c3589d5d1ab4ac857b0ee3f8f49629d0de55ecf7c2 \ + --hash=sha256:7a0e27186e781a69959d0230dd9909b5e26024f8da10683bd6344baea1885168 \ + --hash=sha256:7d6e390423cc1f76e1b8108c9b6889d20a7a1f59d9a60cac4a050fa734d6c1e2 \ + --hash=sha256:8145dd6d10df13c559d1e4314df29695613575183fa2e2d11fac4c208c8a1f73 \ + --hash=sha256:8446acd11fe3dc1830568c941d44449fd5cb83068e5c70bd5a470d323d448296 \ + --hash=sha256:852ae5bed3478b92f093e30f785c98e0cb62fa0a939ed057c31716e18a7a22b9 \ + --hash=sha256:87c930d52f45df092f7578889711a0768094debf73cfcde105e2d66954358125 \ + --hash=sha256:8b1224a734cd509f70816455c3cffe13a4f599b1bf7130f913ba0e2c0b2006c0 \ + --hash=sha256:8dc082ea901a62edb8f59713c6a7e28a85daddcb67454c839de57656478f5b19 \ + --hash=sha256:906a30249315f9c8e17b085cc5f87d3f369b35fedd0051d4a84686967bdbbd0b \ + --hash=sha256:938065908d1d869c7d75d8ec45f735a034771c6ea07088867f713d1cd3bbbe4f \ + --hash=sha256:9c144440db4bf3bb6372d2c3e49834cc0ff7bb4c24975ab33e01199e645416f2 \ + --hash=sha256:9e196ade2400c0c737d93465327d1ae7c06c7cb8a1756121ebf54b06ca183c7f \ + --hash=sha256:a3ef07ec8cbc8fc9e369c8dcd52019510c12da4de81367d8b20bc692aa07573a \ + --hash=sha256:a7af9ed2aa9ec5950daf05bb11abc4076a108bd3c7db9aa7251d5f107079b6a6 \ + --hash=sha256:a9f66e7d2b2d7712410d3bc5684149040ef5f19856f20277cd17ea83e5006286 \ + --hash=sha256:aa098a5ab53fa407fded5870865c6275a5cd4101cfdef8d6fafc48286a96e981 \ + --hash=sha256:af58de8745f7fa9ca1c0c7c943616c6fe28e75d0c81f5c295810e3c83b5be92f \ + --hash=sha256:b05a89f2fb84d21235f93de47129dd4f11c16f64c87c33f5e284e6a3a54e43f2 \ + --hash=sha256:b5e40e80299607f597e1a8a247ff8d71d79c5b52baa11cc1cce30aa92d2da6e0 \ + --hash=sha256:b9d0878b21e3918d76d2209c924ebb272340da1fb51abc00f986c258cd5e957b \ + --hash=sha256:bc3186bea41fae9d8e90c2b4fb5f0a1f5a690682da79b92574d63f56b529080b \ + --hash=sha256:c63d95dc9d67b676e9108fe0d2182987ccb0f11933c1e8959f42fa0da8d4fa56 \ + --hash=sha256:c771cfac34a4f2c0de8e8c97312d07d64fd8f8ed45bc9f5726a7e947270152b5 \ + --hash=sha256:c8d9727f5316a256425892b043736d63e89ed15bbfe6556c5ff4d9d4448ff3b3 \ + --hash=sha256:cbc95b3813920145032412f7e33d12080f11dc776262df1712e1638207dde9e8 \ + --hash=sha256:cefc2219baa48e468e3db7e706305fcd0c095534a192a08f31e98d83a7d45fb0 \ + --hash=sha256:d95f59afe7f808c103be692175008bab926b59309ade3e6d25009e9a171f7036 \ + --hash=sha256:dd937f088a2df683cbb79dda9a772b62a3e5a8a7e76690612c2737f38c6ef1b6 \ + --hash=sha256:de6ea4e5a65d5a90c7d286ddff2b87f3f4ad61faa3db8dabe936b34c2275b6f8 \ + --hash=sha256:e0486a11ec30cdecb53f184d496d1c6a20786c81e55e41640270130056f8ee48 \ + --hash=sha256:ee807923782faaf60d0d7331f5e86da7d5e3079e28b291973c545476c2b00d07 \ + --hash=sha256:efc81393f25f14d11c9d161e46e6ee348637c0a1e8a54bf9dedc472a3fae993b \ + --hash=sha256:f0a1a8476ad77a228e41619af2fa9505cf69df928e9aaa165746584ea17fed2b \ + --hash=sha256:f75018be4980a7324edc5930fe39aa391d5734531b1926968605416ff58c332d \ + --hash=sha256:f92d6c2a8535dc4fe4419562294ff957f83a16ebdec66df0805e473ffaad8bd0 \ + --hash=sha256:fb1752a3bb9a3ad2d6b090b88a9a0ae1cd6f004ef95f75825e2f382c183b2097 \ + --hash=sha256:fc927d7f289d14f5e037be917539620603294454130b6de200091e23d27dc9be \ + --hash=sha256:fed5527c4cf10f16c6d0b6bee1f89958bccb0ad2522c8cadc2efd318bcd545f5 + # via -r /tmp/requirements7xt3_2_h.in packaging==25.0 \ --hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \ --hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f @@ -308,23 +340,27 @@ packaging==25.0 \ # build # meson-python # pyproject-metadata -pip==25.1.1 \ - --hash=sha256:2913a38a2abf4ea6b64ab507bd9e967f3b53dc1ede74b01b0931e1ce548751af \ - --hash=sha256:3de45d411d308d5054c2168185d8da7f9a2cd753dbac8acbfa88a8909ecd9077 - # via -r /tmp/requirementsx8idgxd2.in -pybind11==2.13.6 \ - --hash=sha256:237c41e29157b962835d356b370ededd57594a26d5894a795960f0047cb5caf5 \ - --hash=sha256:ba6af10348c12b24e92fa086b39cfba0eff619b61ac77c406167d813b096d39a - # via -r /tmp/requirementsx8idgxd2.in +pathspec==0.12.1 \ + --hash=sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08 \ + --hash=sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712 + # via mypy +pip==25.2 \ + --hash=sha256:578283f006390f85bb6282dffb876454593d637f5d1be494b5202ce4877e71f2 \ + --hash=sha256:6d67a2b4e7f14d8b31b8b52648866fa717f45a1eb70e83002f4331d07e953717 + # via -r /tmp/requirements7xt3_2_h.in +pybind11==3.0.1 \ + --hash=sha256:9c0f40056a016da59bab516efb523089139fcc6f2ba7e4930854c61efb932051 \ + --hash=sha256:aa8f0aa6e0a94d3b64adfc38f560f33f15e589be2175e103c0a33c6bce55ee89 + # via -r /tmp/requirements7xt3_2_h.in pycparser==2.22 \ --hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \ --hash=sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc # via cffi -pydantic==2.11.4 \ - --hash=sha256:32738d19d63a226a52eed76645a98ee07c1f410ee41d93b4afbfa85ed8111c2d \ - --hash=sha256:d9615eaa9ac5a063471da949c8fc16376a84afb5024688b3ff885693506764eb +pydantic==2.11.7 \ + --hash=sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db \ + --hash=sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b # via - # -r /tmp/requirementsx8idgxd2.in + # -r /tmp/requirements7xt3_2_h.in # pydantic-settings pydantic-core==2.33.2 \ --hash=sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d \ @@ -427,10 +463,10 @@ pydantic-core==2.33.2 \ --hash=sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6 \ --hash=sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d # via pydantic -pydantic-settings==2.9.1 \ - --hash=sha256:59b4f431b1defb26fe620c71a7d3968a710d719f5f4cdbbdb7926edeb770f6ef \ - --hash=sha256:c509bf79d27563add44e8446233359004ed85066cd096d8b510f715e6ef5d268 - # via -r /tmp/requirementsx8idgxd2.in +pydantic-settings==2.10.1 \ + --hash=sha256:06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee \ + --hash=sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796 + # via -r /tmp/requirements7xt3_2_h.in pyproject-hooks==1.2.0 \ --hash=sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8 \ --hash=sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913 @@ -439,40 +475,43 @@ pyproject-metadata==0.9.1 \ --hash=sha256:b8b2253dd1b7062b78cf949a115f02ba7fa4114aabe63fa10528e9e1a954a816 \ --hash=sha256:ee5efde548c3ed9b75a354fc319d5afd25e9585fa918a34f62f904cc731973ad # via meson-python -pyright==1.1.400 \ - --hash=sha256:b8a3ba40481aa47ba08ffb3228e821d22f7d391f83609211335858bf05686bdb \ - --hash=sha256:c80d04f98b5a4358ad3a35e241dbf2a408eee33a40779df365644f8054d2517e - # via -r /tmp/requirementsx8idgxd2.in -python-dotenv==1.1.0 \ - --hash=sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5 \ - --hash=sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d +pyright==1.1.404 \ + --hash=sha256:455e881a558ca6be9ecca0b30ce08aa78343ecc031d37a198ffa9a7a1abeb63e \ + --hash=sha256:c7b7ff1fdb7219c643079e4c3e7d4125f0dafcc19d253b47e898d130ea426419 + # via -r /tmp/requirements7xt3_2_h.in +python-dotenv==1.1.1 \ + --hash=sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc \ + --hash=sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab # via pydantic-settings -ruff==0.11.10 \ - --hash=sha256:1067245bad978e7aa7b22f67113ecc6eb241dca0d9b696144256c3a879663bca \ - --hash=sha256:2f071b0deed7e9245d5820dac235cbdd4ef99d7b12ff04c330a241ad3534319f \ - --hash=sha256:3afead355f1d16d95630df28d4ba17fb2cb9c8dfac8d21ced14984121f639bad \ - --hash=sha256:4a60e3a0a617eafba1f2e4186d827759d65348fa53708ca547e384db28406a0b \ - --hash=sha256:5a94acf798a82db188f6f36575d80609072b032105d114b0f98661e1679c9125 \ - --hash=sha256:5b6a9cc5b62c03cc1fea0044ed8576379dbaf751d5503d718c973d5418483641 \ - --hash=sha256:5cc725fbb4d25b0f185cb42df07ab6b76c4489b4bfb740a175f3a59c70e8a224 \ - --hash=sha256:607ecbb6f03e44c9e0a93aedacb17b4eb4f3563d00e8b474298a201622677947 \ - --hash=sha256:7b3a522fa389402cd2137df9ddefe848f727250535c70dafa840badffb56b7a4 \ - --hash=sha256:859a7bfa7bc8888abbea31ef8a2b411714e6a80f0d173c2a82f9041ed6b50f58 \ - --hash=sha256:8b4564e9f99168c0f9195a0fd5fa5928004b33b377137f978055e40008a082c5 \ - --hash=sha256:968220a57e09ea5e4fd48ed1c646419961a0570727c7e069842edd018ee8afed \ - --hash=sha256:d522fb204b4959909ecac47da02830daec102eeb100fb50ea9554818d47a5fa6 \ - --hash=sha256:da8ec977eaa4b7bf75470fb575bea2cb41a0e07c7ea9d5a0a97d13dbca697bf2 \ - --hash=sha256:dc061a98d32a97211af7e7f3fa1d4ca2fcf919fb96c28f39551f35fc55bdbc19 \ - --hash=sha256:ddf8967e08227d1bd95cc0851ef80d2ad9c7c0c5aab1eba31db49cf0a7b99523 \ - --hash=sha256:ef69637b35fb8b210743926778d0e45e1bffa850a7c61e428c6b971549b5f5d1 \ - --hash=sha256:f4854fd09c7aed5b1590e996a81aeff0c9ff51378b084eb5a0b9cd9518e6cff2 - # via -r /tmp/requirementsx8idgxd2.in -setuptools==80.7.1 \ - --hash=sha256:ca5cc1069b85dc23070a6628e6bcecb3292acac802399c7f8edc0100619f9009 \ - --hash=sha256:f6ffc5f0142b1bd8d0ca94ee91b30c0ca862ffd50826da1ea85258a06fd94552 - # via - # -r /tmp/requirementsx8idgxd2.in - # marisa-trie +ruff==0.12.10 \ + --hash=sha256:059e863ea3a9ade41407ad71c1de2badfbe01539117f38f763ba42a1206f7559 \ + --hash=sha256:141ce3d88803c625257b8a6debf4a0473eb6eed9643a6189b68838b43e78165a \ + --hash=sha256:189ab65149d11ea69a2d775343adf5f49bb2426fc4780f65ee33b423ad2e47f9 \ + --hash=sha256:1bef6161e297c68908b7218fa6e0e93e99a286e5ed9653d4be71e687dff101cf \ + --hash=sha256:1f68433c4fbc63efbfa3ba5db31727db229fa4e61000f452c540474b03de52a9 \ + --hash=sha256:2c6f4064c69d2542029b2a61d39920c85240c39837599d7f2e32e80d36401d6e \ + --hash=sha256:37b4a64f4062a50c75019c61c7017ff598cb444984b638511f48539d3a1c98db \ + --hash=sha256:4f1345fbf8fb0531cd722285b5f15af49b2932742fc96b633e883da8d841896b \ + --hash=sha256:7837eca8787f076f67aba2ca559cefd9c5cbc3a9852fd66186f4201b87c1563e \ + --hash=sha256:7d1a4e0bdfafcd2e3e235ecf50bf0176f74dd37902f241588ae1f6c827a36c56 \ + --hash=sha256:822d9677b560f1fdeab69b89d1f444bf5459da4aa04e06e766cf0121771ab844 \ + --hash=sha256:8b593cb0fb55cc8692dac7b06deb29afda78c721c7ccfed22db941201b7b8f7b \ + --hash=sha256:9de785e95dc2f09846c5e6e1d3a3d32ecd0b283a979898ad427a9be7be22b266 \ + --hash=sha256:ae479e1a18b439c59138f066ae79cc0f3ee250712a873d00dbafadaad9481e5b \ + --hash=sha256:cc138cc06ed9d4bfa9d667a65af7172b47840e1a98b02ce7011c391e54635ffc \ + --hash=sha256:d59e58586829f8e4a9920788f6efba97a13d1fa320b047814e8afede381c6839 \ + --hash=sha256:e67d96827854f50b9e3e8327b031647e7bcc090dbe7bb11101a81a3a2cbf1cc9 \ + --hash=sha256:ebb7333a45d56efc7c110a46a69a1b32365d5c5161e7244aaf3aa20ce62399c1 \ + --hash=sha256:f3fc21178cd44c98142ae7590f42ddcb587b8e09a3b849cbc84edb62ee95de60 + # via -r /tmp/requirements7xt3_2_h.in +setuptools==80.9.0 \ + --hash=sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922 \ + --hash=sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c + # via -r /tmp/requirements7xt3_2_h.in +sqlparse==0.5.3 \ + --hash=sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272 \ + --hash=sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca + # via django tomli==2.2.1 \ --hash=sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6 \ --hash=sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd \ @@ -506,47 +545,50 @@ tomli==2.2.1 \ --hash=sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272 \ --hash=sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a \ --hash=sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7 + # via -r /tmp/requirements7xt3_2_h.in +tomlkit==0.13.3 \ + --hash=sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1 \ + --hash=sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0 + # via -r /tmp/requirements7xt3_2_h.in +types-pyyaml==6.0.12.20250822 \ + --hash=sha256:1fe1a5e146aa315483592d292b72a172b65b946a6d98aa6ddd8e4aa838ab7098 \ + --hash=sha256:259f1d93079d335730a9db7cff2bcaf65d7e04b4a56b5927d49a612199b59413 + # via django-stubs +typing-extensions==4.15.0 \ + --hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \ + --hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548 # via - # -r /tmp/requirementsx8idgxd2.in - # build - # meson-python - # mypy -tomlkit==0.13.2 \ - --hash=sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde \ - --hash=sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79 - # via -r /tmp/requirementsx8idgxd2.in -typing-extensions==4.13.2 \ - --hash=sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c \ - --hash=sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef - # via + # django-stubs + # django-stubs-ext # mypy # pydantic # pydantic-core # pyright # typing-inspection -typing-inspection==0.4.0 \ - --hash=sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f \ - --hash=sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122 +typing-inspection==0.4.1 \ + --hash=sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51 \ + --hash=sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28 # via # pydantic # pydantic-settings -uv==0.7.6 \ - --hash=sha256:0bad870f797971423d7f654423cf3ccd3bbd3688f88aee3f84e79af008c6abae \ - --hash=sha256:17c79eec35c65bbd25180203be7266dd7d43381e02e28a8f2cb6ee809d008837 \ - --hash=sha256:1f46cfd2de04dd261cc75158c293de64f99cc907ab0d395f3a0f97c94e7f076a \ - --hash=sha256:310e488493d03a843b838e9301af1731b02bc93b14bcaa38c62d448cebbdca3c \ - --hash=sha256:32aecfd27bd724d8ca8bafa811a69d436fcd403d589b025fbbd2e967eb154b46 \ - --hash=sha256:4026513441dc01326f8bc04517956385442523ed1d40400e14723d8fb3d9c321 \ - --hash=sha256:434f1820a8fbf54494c53d8ebb2b6509d98a2792876a2d990f90ac70afc9a11a \ - --hash=sha256:4cd32743d2c0c0b40ffbde48163ae2835353d319472aadabd71e9dcf98152e8b \ - --hash=sha256:5e283166816f129f29023a4bfdf49fdb33e1e2bcb4e555e9d6996122867a44af \ - --hash=sha256:72e9337db681a16a7203abe112fedc249f01fe4cadd6d65d23c85031183dcf23 \ - --hash=sha256:832d7741117c41455ff43569b88892ec0a81938750a8bc4307e1160b70c91f3c \ - --hash=sha256:8a86cfefd0b9cd3b8a8577e79a0e61d52ade23a7876ed5b5312cc1f05baa140b \ - --hash=sha256:ad79d71d2bb4cc1cb22d09771a23f70190e3b5fa41668da208e694b50b900178 \ - --hash=sha256:bd188ac9d9902f1652130837ede39768d7c8f72b0a68fd484ba884d88e963b66 \ - --hash=sha256:c18b2437e254906b1f48710e1fc1b313052e2ee7261ff104d58b25ef2d347d98 \ - --hash=sha256:c44311ed1a32e397d81e346e7b868e4ae22f2df2e5ba601e055683fa4cc68323 \ - --hash=sha256:e15ac957e0a319dba40c897b9408c93e603d2317807384ec8f7d47a9e17c0d85 \ - --hash=sha256:e3fb41bd4bf88ab21df773b642465fffc469e173645eb986d000db38d7bb8e3c - # via -r /tmp/requirementsx8idgxd2.in +uv==0.8.13 \ + --hash=sha256:18a502328545af511039c7b7c602a0aa89eeff23b1221a1f56d99b3a3fecfddd \ + --hash=sha256:20862f612de38f6dea55d40467a29f3cb621b256a4b5891ae55debbbdf1db2b4 \ + --hash=sha256:2113cd877974b68ea2af64a2f2cc23708ba97066046e78efb72ba94e5fef617a \ + --hash=sha256:28c8d4560c673ff5c798f2f4422281840728f46ebf1946345b65d065f8344c03 \ + --hash=sha256:2945c32b8fcf23807ef1f74c390795e2b00371c53b94c015cc6e7b0cfbab9d94 \ + --hash=sha256:3735a452cdc3168932d128891d7e8866b4a2d052283c6da5ccfe0b038d1cf8bd \ + --hash=sha256:3b5c6e44238007ec1d25212cafe1b37a8506d425d1dd74a267cb9072a61930f9 \ + --hash=sha256:3bac51ea503d97f371222f23e845fc4ab95465ac3e958c7589d6743c75445b71 \ + --hash=sha256:404ca19b2d860ab661e1d78633f594e994f8422af8772ad237d763fe353da2ab \ + --hash=sha256:4a6d37547947fcae57244b4d1f3b62fba55f4a85d3e45e7284a93b6cd5bedca4 \ + --hash=sha256:4c2c5e5962239ecaff6444d5bc22422a9bd2da25a80adc6ab14cb42e4461b1cf \ + --hash=sha256:73459fe1403b1089853071db6770450dc03e4058848f7146d88cff5f1c352743 \ + --hash=sha256:854c4e75024a4894477bf61684b2872b83c77ca87d1bad62692bcc31200619c3 \ + --hash=sha256:8a3739540f8b0b5258869b1671185d55daacfa4609eaffd235573ac938ec01a6 \ + --hash=sha256:a4438eca3d301183c52994a6d2baff70fd1840421a83446f3cabb1d0d0b50aff \ + --hash=sha256:cf3ce98404ddc1e11cd2c2604668f8f81219cf00bb1227b792fdf5dbb4faf31a \ + --hash=sha256:d22fa55580b224779279b98e0b23cbc45e51837e1fac616d7c5d03aff668a998 \ + --hash=sha256:eb90089624d92d57b8582f708973db8988e09dba6bae83991dba20731d82eb6a \ + --hash=sha256:f6c508aa9c5210577008e1919b532e38356fe68712179399f00462b3e78fd845 + # via -r /tmp/requirements7xt3_2_h.in diff --git a/requirements.in b/requirements.in deleted file mode 100644 index b022ae5..0000000 --- a/requirements.in +++ /dev/null @@ -1,6 +0,0 @@ -mypy -ruff -numpy -pydantic -requests -types-requests diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 87a4bad..0000000 --- a/requirements.txt +++ /dev/null @@ -1,350 +0,0 @@ -# This file was autogenerated by uv via the following command: -# uv pip compile --generate-hashes requirements.in -annotated-types==0.7.0 \ - --hash=sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53 \ - --hash=sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89 - # via pydantic -certifi==2025.1.31 \ - --hash=sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651 \ - --hash=sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe - # via requests -charset-normalizer==3.4.1 \ - --hash=sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537 \ - --hash=sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa \ - --hash=sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a \ - --hash=sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294 \ - --hash=sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b \ - --hash=sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd \ - --hash=sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601 \ - --hash=sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd \ - --hash=sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4 \ - --hash=sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d \ - --hash=sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2 \ - --hash=sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313 \ - --hash=sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd \ - --hash=sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa \ - --hash=sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8 \ - --hash=sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1 \ - --hash=sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2 \ - --hash=sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496 \ - --hash=sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d \ - --hash=sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b \ - --hash=sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e \ - --hash=sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a \ - --hash=sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4 \ - --hash=sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca \ - --hash=sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78 \ - --hash=sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408 \ - --hash=sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5 \ - --hash=sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3 \ - --hash=sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f \ - --hash=sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a \ - --hash=sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765 \ - --hash=sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6 \ - --hash=sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146 \ - --hash=sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6 \ - --hash=sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9 \ - --hash=sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd \ - --hash=sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c \ - --hash=sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f \ - --hash=sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545 \ - --hash=sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176 \ - --hash=sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770 \ - --hash=sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824 \ - --hash=sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f \ - --hash=sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf \ - --hash=sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487 \ - --hash=sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d \ - --hash=sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd \ - --hash=sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b \ - --hash=sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534 \ - --hash=sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f \ - --hash=sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b \ - --hash=sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9 \ - --hash=sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd \ - --hash=sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125 \ - --hash=sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9 \ - --hash=sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de \ - --hash=sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11 \ - --hash=sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d \ - --hash=sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35 \ - --hash=sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f \ - --hash=sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda \ - --hash=sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7 \ - --hash=sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a \ - --hash=sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971 \ - --hash=sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8 \ - --hash=sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41 \ - --hash=sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d \ - --hash=sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f \ - --hash=sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757 \ - --hash=sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a \ - --hash=sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886 \ - --hash=sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77 \ - --hash=sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76 \ - --hash=sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247 \ - --hash=sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85 \ - --hash=sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb \ - --hash=sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7 \ - --hash=sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e \ - --hash=sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6 \ - --hash=sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037 \ - --hash=sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1 \ - --hash=sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e \ - --hash=sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807 \ - --hash=sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407 \ - --hash=sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c \ - --hash=sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12 \ - --hash=sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3 \ - --hash=sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089 \ - --hash=sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd \ - --hash=sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e \ - --hash=sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00 \ - --hash=sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616 - # via requests -idna==3.10 \ - --hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \ - --hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3 - # via requests -mypy==1.15.0 \ - --hash=sha256:1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e \ - --hash=sha256:171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22 \ - --hash=sha256:1905f494bfd7d85a23a88c5d97840888a7bd516545fc5aaedff0267e0bb54e2f \ - --hash=sha256:1fbb8da62dc352133d7d7ca90ed2fb0e9d42bb1a32724c287d3c76c58cbaa9c2 \ - --hash=sha256:2922d42e16d6de288022e5ca321cd0618b238cfc5570e0263e5ba0a77dbef56f \ - --hash=sha256:2e2c2e6d3593f6451b18588848e66260ff62ccca522dd231cd4dd59b0160668b \ - --hash=sha256:2ee2d57e01a7c35de00f4634ba1bbf015185b219e4dc5909e281016df43f5ee5 \ - --hash=sha256:2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f \ - --hash=sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43 \ - --hash=sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e \ - --hash=sha256:5a95fb17c13e29d2d5195869262f8125dfdb5c134dc8d9a9d0aecf7525b10c2c \ - --hash=sha256:6983aae8b2f653e098edb77f893f7b6aca69f6cffb19b2cc7443f23cce5f4828 \ - --hash=sha256:712e962a6357634fef20412699a3655c610110e01cdaa6180acec7fc9f8513ba \ - --hash=sha256:8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee \ - --hash=sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d \ - --hash=sha256:8f8722560a14cde92fdb1e31597760dc35f9f5524cce17836c0d22841830fd5b \ - --hash=sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445 \ - --hash=sha256:973500e0774b85d9689715feeffcc980193086551110fd678ebe1f4342fb7c5e \ - --hash=sha256:979e4e1a006511dacf628e36fadfecbcc0160a8af6ca7dad2f5025529e082c13 \ - --hash=sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5 \ - --hash=sha256:aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd \ - --hash=sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf \ - --hash=sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357 \ - --hash=sha256:be68172e9fd9ad8fb876c6389f16d1c1b5f100ffa779f77b1fb2176fcc9ab95b \ - --hash=sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036 \ - --hash=sha256:c4bb0e1bd29f7d34efcccd71cf733580191e9a264a2202b0239da95984c5b559 \ - --hash=sha256:c7be1e46525adfa0d97681432ee9fcd61a3964c2446795714699a998d193f1a3 \ - --hash=sha256:c9817fa23833ff189db061e6d2eff49b2f3b6ed9856b4a0a73046e41932d744f \ - --hash=sha256:ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464 \ - --hash=sha256:d10d994b41fb3497719bbf866f227b3489048ea4bbbb5015357db306249f7980 \ - --hash=sha256:e601a7fa172c2131bff456bb3ee08a88360760d0d2f8cbd7a75a65497e2df078 \ - --hash=sha256:f95579473af29ab73a10bada2f9722856792a36ec5af5399b653aa28360290a5 - # via -r requirements.in -mypy-extensions==1.0.0 \ - --hash=sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d \ - --hash=sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782 - # via mypy -numpy==2.2.4 \ - --hash=sha256:05c076d531e9998e7e694c36e8b349969c56eadd2cdcd07242958489d79a7286 \ - --hash=sha256:0d54974f9cf14acf49c60f0f7f4084b6579d24d439453d5fc5805d46a165b542 \ - --hash=sha256:11c43995255eb4127115956495f43e9343736edb7fcdb0d973defd9de14cd84f \ - --hash=sha256:188dcbca89834cc2e14eb2f106c96d6d46f200fe0200310fc29089657379c58d \ - --hash=sha256:1974afec0b479e50438fc3648974268f972e2d908ddb6d7fb634598cdb8260a0 \ - --hash=sha256:1cf4e5c6a278d620dee9ddeb487dc6a860f9b199eadeecc567f777daace1e9e7 \ - --hash=sha256:207a2b8441cc8b6a2a78c9ddc64d00d20c303d79fba08c577752f080c4007ee3 \ - --hash=sha256:218f061d2faa73621fa23d6359442b0fc658d5b9a70801373625d958259eaca3 \ - --hash=sha256:2aad3c17ed2ff455b8eaafe06bcdae0062a1db77cb99f4b9cbb5f4ecb13c5146 \ - --hash=sha256:2fa8fa7697ad1646b5c93de1719965844e004fcad23c91228aca1cf0800044a1 \ - --hash=sha256:31504f970f563d99f71a3512d0c01a645b692b12a63630d6aafa0939e52361e6 \ - --hash=sha256:3387dd7232804b341165cedcb90694565a6015433ee076c6754775e85d86f1fc \ - --hash=sha256:4ba5054787e89c59c593a4169830ab362ac2bee8a969249dc56e5d7d20ff8df9 \ - --hash=sha256:4f92084defa704deadd4e0a5ab1dc52d8ac9e8a8ef617f3fbb853e79b0ea3592 \ - --hash=sha256:65ef3468b53269eb5fdb3a5c09508c032b793da03251d5f8722b1194f1790c00 \ - --hash=sha256:6f527d8fdb0286fd2fd97a2a96c6be17ba4232da346931d967a0630050dfd298 \ - --hash=sha256:7051ee569db5fbac144335e0f3b9c2337e0c8d5c9fee015f259a5bd70772b7e8 \ - --hash=sha256:7716e4a9b7af82c06a2543c53ca476fa0b57e4d760481273e09da04b74ee6ee2 \ - --hash=sha256:79bd5f0a02aa16808fcbc79a9a376a147cc1045f7dfe44c6e7d53fa8b8a79392 \ - --hash=sha256:7a4e84a6283b36632e2a5b56e121961f6542ab886bc9e12f8f9818b3c266bfbb \ - --hash=sha256:8120575cb4882318c791f839a4fd66161a6fa46f3f0a5e613071aae35b5dd8f8 \ - --hash=sha256:81413336ef121a6ba746892fad881a83351ee3e1e4011f52e97fba79233611fd \ - --hash=sha256:8146f3550d627252269ac42ae660281d673eb6f8b32f113538e0cc2a9aed42b9 \ - --hash=sha256:879cf3a9a2b53a4672a168c21375166171bc3932b7e21f622201811c43cdd3b0 \ - --hash=sha256:892c10d6a73e0f14935c31229e03325a7b3093fafd6ce0af704be7f894d95687 \ - --hash=sha256:92bda934a791c01d6d9d8e038363c50918ef7c40601552a58ac84c9613a665bc \ - --hash=sha256:9ba03692a45d3eef66559efe1d1096c4b9b75c0986b5dff5530c378fb8331d4f \ - --hash=sha256:9eeea959168ea555e556b8188da5fa7831e21d91ce031e95ce23747b7609f8a4 \ - --hash=sha256:a0258ad1f44f138b791327961caedffbf9612bfa504ab9597157806faa95194a \ - --hash=sha256:a761ba0fa886a7bb33c6c8f6f20213735cb19642c580a931c625ee377ee8bd39 \ - --hash=sha256:a7b9084668aa0f64e64bd00d27ba5146ef1c3a8835f3bd912e7a9e01326804c4 \ - --hash=sha256:a84eda42bd12edc36eb5b53bbcc9b406820d3353f1994b6cfe453a33ff101775 \ - --hash=sha256:ab2939cd5bec30a7430cbdb2287b63151b77cf9624de0532d629c9a1c59b1d5c \ - --hash=sha256:ac0280f1ba4a4bfff363a99a6aceed4f8e123f8a9b234c89140f5e894e452ecd \ - --hash=sha256:adf8c1d66f432ce577d0197dceaac2ac00c0759f573f28516246351c58a85020 \ - --hash=sha256:b4adfbbc64014976d2f91084915ca4e626fbf2057fb81af209c1a6d776d23e3d \ - --hash=sha256:bb649f8b207ab07caebba230d851b579a3c8711a851d29efe15008e31bb4de24 \ - --hash=sha256:bce43e386c16898b91e162e5baaad90c4b06f9dcbe36282490032cec98dc8ae7 \ - --hash=sha256:bd3ad3b0a40e713fc68f99ecfd07124195333f1e689387c180813f0e94309d6f \ - --hash=sha256:c3f7ac96b16955634e223b579a3e5798df59007ca43e8d451a0e6a50f6bfdfba \ - --hash=sha256:cf28633d64294969c019c6df4ff37f5698e8326db68cc2b66576a51fad634880 \ - --hash=sha256:d0f35b19894a9e08639fd60a1ec1978cb7f5f7f1eace62f38dd36be8aecdef4d \ - --hash=sha256:db1f1c22173ac1c58db249ae48aa7ead29f534b9a948bc56828337aa84a32ed6 \ - --hash=sha256:dbe512c511956b893d2dacd007d955a3f03d555ae05cfa3ff1c1ff6df8851854 \ - --hash=sha256:df2f57871a96bbc1b69733cd4c51dc33bea66146b8c63cacbfed73eec0883017 \ - --hash=sha256:e2f085ce2e813a50dfd0e01fbfc0c12bbe5d2063d99f8b29da30e544fb6483b8 \ - --hash=sha256:e642d86b8f956098b564a45e6f6ce68a22c2c97a04f5acd3f221f57b8cb850ae \ - --hash=sha256:e9e0a277bb2eb5d8a7407e14688b85fd8ad628ee4e0c7930415687b6564207a4 \ - --hash=sha256:ea2bb7e2ae9e37d96835b3576a4fa4b3a97592fbea8ef7c3587078b0068b8f09 \ - --hash=sha256:ee4d528022f4c5ff67332469e10efe06a267e32f4067dc76bb7e2cddf3cd25ff \ - --hash=sha256:f05d4198c1bacc9124018109c5fba2f3201dbe7ab6e92ff100494f236209c960 \ - --hash=sha256:f34dc300df798742b3d06515aa2a0aee20941c13579d7a2f2e10af01ae4901ee \ - --hash=sha256:f4162988a360a29af158aeb4a2f4f09ffed6a969c9776f8f3bdee9b06a8ab7e5 \ - --hash=sha256:f486038e44caa08dbd97275a9a35a283a8f1d2f0ee60ac260a1790e76660833c \ - --hash=sha256:f7de08cbe5551911886d1ab60de58448c6df0f67d9feb7d1fb21e9875ef95e91 - # via -r requirements.in -pydantic==2.10.6 \ - --hash=sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584 \ - --hash=sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236 - # via -r requirements.in -pydantic-core==2.27.2 \ - --hash=sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278 \ - --hash=sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50 \ - --hash=sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9 \ - --hash=sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f \ - --hash=sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6 \ - --hash=sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc \ - --hash=sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54 \ - --hash=sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630 \ - --hash=sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9 \ - --hash=sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236 \ - --hash=sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7 \ - --hash=sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee \ - --hash=sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b \ - --hash=sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048 \ - --hash=sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc \ - --hash=sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130 \ - --hash=sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4 \ - --hash=sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd \ - --hash=sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4 \ - --hash=sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7 \ - --hash=sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7 \ - --hash=sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4 \ - --hash=sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e \ - --hash=sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa \ - --hash=sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6 \ - --hash=sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962 \ - --hash=sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b \ - --hash=sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f \ - --hash=sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474 \ - --hash=sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5 \ - --hash=sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459 \ - --hash=sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf \ - --hash=sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a \ - --hash=sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c \ - --hash=sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76 \ - --hash=sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362 \ - --hash=sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4 \ - --hash=sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934 \ - --hash=sha256:521eb9b7f036c9b6187f0b47318ab0d7ca14bd87f776240b90b21c1f4f149320 \ - --hash=sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118 \ - --hash=sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96 \ - --hash=sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306 \ - --hash=sha256:669e193c1c576a58f132e3158f9dfa9662969edb1a250c54d8fa52590045f046 \ - --hash=sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3 \ - --hash=sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2 \ - --hash=sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af \ - --hash=sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9 \ - --hash=sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67 \ - --hash=sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a \ - --hash=sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27 \ - --hash=sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35 \ - --hash=sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b \ - --hash=sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151 \ - --hash=sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b \ - --hash=sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154 \ - --hash=sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133 \ - --hash=sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef \ - --hash=sha256:85210c4d99a0114f5a9481b44560d7d1e35e32cc5634c656bc48e590b669b145 \ - --hash=sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15 \ - --hash=sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4 \ - --hash=sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc \ - --hash=sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee \ - --hash=sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c \ - --hash=sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0 \ - --hash=sha256:9fdbe7629b996647b99c01b37f11170a57ae675375b14b8c13b8518b8320ced5 \ - --hash=sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57 \ - --hash=sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b \ - --hash=sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8 \ - --hash=sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1 \ - --hash=sha256:bca101c00bff0adb45a833f8451b9105d9df18accb8743b08107d7ada14bd7da \ - --hash=sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e \ - --hash=sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc \ - --hash=sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993 \ - --hash=sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656 \ - --hash=sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4 \ - --hash=sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c \ - --hash=sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb \ - --hash=sha256:cabb9bcb7e0d97f74df8646f34fc76fbf793b7f6dc2438517d7a9e50eee4f14d \ - --hash=sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9 \ - --hash=sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e \ - --hash=sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1 \ - --hash=sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc \ - --hash=sha256:d262606bf386a5ba0b0af3b97f37c83d7011439e3dc1a9298f21efb292e42f1a \ - --hash=sha256:d2d63f1215638d28221f664596b1ccb3944f6e25dd18cd3b86b0a4c408d5ebb9 \ - --hash=sha256:d3e8d504bdd3f10835468f29008d72fc8359d95c9c415ce6e767203db6127506 \ - --hash=sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b \ - --hash=sha256:d716e2e30c6f140d7560ef1538953a5cd1a87264c737643d481f2779fc247fe1 \ - --hash=sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d \ - --hash=sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99 \ - --hash=sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3 \ - --hash=sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31 \ - --hash=sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c \ - --hash=sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39 \ - --hash=sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a \ - --hash=sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308 \ - --hash=sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2 \ - --hash=sha256:f66d89ba397d92f840f8654756196d93804278457b5fbede59598a1f9f90b228 \ - --hash=sha256:f6f8e111843bbb0dee4cb6594cdc73e79b3329b526037ec242a3e49012495b3b \ - --hash=sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9 \ - --hash=sha256:fd1aea04935a508f62e0d0ef1f5ae968774a32afc306fb8545e06f5ff5cdf3ad - # via pydantic -requests==2.32.3 \ - --hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \ - --hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6 - # via -r requirements.in -ruff==0.11.1 \ - --hash=sha256:111dbad1706d8200a7138237b4766b45ba7ee45cc8299c02102f4327624f86a2 \ - --hash=sha256:1f2b03d504516d6b22065ce7fac2564dac15d79a6a776452dabfdd7673a45b07 \ - --hash=sha256:28e2d89e7ba8a1525cdb50bc86c07aba35e7bbeef86dad93781b14ad94dc732c \ - --hash=sha256:429a2e533e3a0dba2ba7e0608a736e728150aa9b6d179245aa11a1339baa968b \ - --hash=sha256:441f94c44fe250691c92382ef84f40acef290766fb3e819a9035e83eadd4dbbe \ - --hash=sha256:52b95a9071f5ad8552af890bd814c6a04daf5b27297ac1054e3667016f3ab739 \ - --hash=sha256:62882a4cc7c0a48c2f34189bd4c7ba45f3d0efb990e02413eeb180aa042a39ca \ - --hash=sha256:6bbcc2984a4d5cbc0f7b10409e74a00a043be45d813e5e81eb58e707455df7f1 \ - --hash=sha256:7aa939fa57ef6770d18bd5cf0d6de77198dd762a559bd0d4a8763bdae4c8cc16 \ - --hash=sha256:88d9c283ebc88faa5bc23fa33f399b6d47a93f5980c92edcddf1f2127aa376b3 \ - --hash=sha256:9c833671aaefcbe280aa54da387264402ffbb1e513ff3420c9c7265ea56d6c5c \ - --hash=sha256:a5a57cd457764228c73066b832040728b02a3837c53c8a781a960b68129c4e0b \ - --hash=sha256:caa872941b876f7ad73abc60144f9a7f6efb575e4f91c4fc1517f0339bcea01e \ - --hash=sha256:da91da0d42e70cd8bda8e6687fab2afd28513a3cc9434539f4149610e63baf8f \ - --hash=sha256:e17b85919d461583aa7e0171bb4f419a6545b261ca080984db49b1f8dced1d4b \ - --hash=sha256:e2df41763d7a9fd438b6b7bde7b75eb3a92ef2f4682ed2d8e4b997b5f0c76ca9 \ - --hash=sha256:e76be5a98dc6c29d85dfa72eb419e8d9276ee96ccf5c33f2b6828001907dcb17 \ - --hash=sha256:f2e209a283c9fa423e268cad015ec4fb249178608f755fb67491ff175ecbffbf - # via -r requirements.in -types-requests==2.32.0.20250306 \ - --hash=sha256:0962352694ec5b2f95fda877ee60a159abdf84a0fc6fdace599f20acb41a03d1 \ - --hash=sha256:25f2cbb5c8710b2022f8bbee7b2b66f319ef14aeea2f35d80f18c9dbf3b60a0b - # via -r requirements.in -typing-extensions==4.12.2 \ - --hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \ - --hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8 - # via - # mypy - # pydantic - # pydantic-core -urllib3==2.3.0 \ - --hash=sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df \ - --hash=sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d - # via - # requests - # types-requests From b3c0dd2703e303eb9dbf0335116e246f35def692 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Mon, 25 Aug 2025 19:02:09 +0300 Subject: [PATCH 13/70] [+] improve deploy:wheel 1. format with ruff; 2. use tomlq from venv; --- python/meson.build | 2 +- .../fxreader/pr34/commands_typed/metrics.py | 153 ++++++++---------- 2 files changed, 72 insertions(+), 83 deletions(-) diff --git a/python/meson.build b/python/meson.build index e0c9bc1..768c7c4 100644 --- a/python/meson.build +++ b/python/meson.build @@ -1,6 +1,6 @@ project( run_command( - 'tomlq', '-r', '.project.name', 'pyproject.toml', + '.venv/bin/tomlq', '-r', '.project.name', 'pyproject.toml', check: true ).stdout().strip('\n'), # 'online.fxreader.uv', diff --git a/python/online/fxreader/pr34/commands_typed/metrics.py b/python/online/fxreader/pr34/commands_typed/metrics.py index 770ccb5..0409346 100644 --- a/python/online/fxreader/pr34/commands_typed/metrics.py +++ b/python/online/fxreader/pr34/commands_typed/metrics.py @@ -6,99 +6,88 @@ import datetime import django.http from typing import ( - Literal, Any, Optional, Annotated, cast, - TypeVar, Protocol, Generic, Callable, + Literal, + Any, + Optional, + Annotated, + cast, + TypeVar, + Protocol, + Generic, + Callable, ) logger = logging.getLogger(__name__) + class Metric(pydantic.BaseModel): - name: str - type: Literal['gauge', 'counter'] - help: Optional[str] = None + name: str + type: Literal['gauge', 'counter'] + help: Optional[str] = None - class Sample(pydantic.BaseModel): - value: str - parameters: dict[str, str] - timestamp: Optional[datetime.datetime] = None + class Sample(pydantic.BaseModel): + value: str + parameters: dict[str, str] + timestamp: Optional[datetime.datetime] = None - samples: list[Sample] = pydantic.Field( - default_factory=lambda: [], - ) + samples: list[Sample] = pydantic.Field( + default_factory=lambda: [], + ) - @classmethod - def sample_serialize( - cls, - o: 'Metric', - s: 'Metric.Sample', - ) -> str: - samples: list[Metric.Sample] = [s,] + @classmethod + def sample_serialize( + cls, + o: 'Metric', + s: 'Metric.Sample', + ) -> str: + samples: list[Metric.Sample] = [ + s, + ] - if o.type == 'gauge': - samples.append( - Metric.Sample( - parameters=s.parameters, - value='NaN', - timestamp=( - s.timestamp + datetime.timedelta(seconds=15) - if s.timestamp - else None - ) - ) - ) + if o.type == 'gauge': + samples.append( + Metric.Sample(parameters=s.parameters, value='NaN', timestamp=(s.timestamp + datetime.timedelta(seconds=15) if s.timestamp else None)) + ) + + return ''.join( + [ + '{metric}{{{parameters}}} {value} {timestamp}\n'.format( + metric=o.name, + parameters=','.join( + [ + '%s=%s' + % ( + k, + json.dumps(v), + ) + for k, v in s2.parameters.items() + ] + ), + value=s2.value, + timestamp=('%.f' % (s2.timestamp.timestamp() * 1000,) if s2.timestamp else ''), + ) + for s2 in samples + ] + ) - return ''.join([ - '{metric}{{{parameters}}} {value} {timestamp}\n'.format( - metric=o.name, - parameters=','.join([ - '%s=%s' % ( - k, - json.dumps(v), - ) - for k, v in s2.parameters.items() - ]), - value=s2.value, - timestamp=( - '%.f' % (s2.timestamp.timestamp() * 1000,) - if s2.timestamp - else '' - ), - ) - for s2 in samples - ]) def serialize( - metrics: list[Metric], + metrics: list[Metric], ): - return django.http.HttpResponse( - ''.join([ - '{help}{type}{samples}'.format( - #help='# HELP %s some metric' % o.name, - #type='# TYPE %s counter' % o.name, - help=( - '# HELP {0} {1}\n'.format( - o.name, - o.help - ) - if o.help - else '' - ), - type=( - '# TYPE {0} {1}\n'.format( - o.name, - o.type - ) - if o.type - else '' - ), - samples=''.join([ - Metric.sample_serialize(o, s) - for s in o.samples - ]), - ) - for o in metrics - if len(o.samples) > 0 - ]), - content_type='text/plain; version=0.0.4; charset=utf-8', - ) + return django.http.HttpResponse( + ''.join( + [ + '{help}{type}{samples}'.format( + # help='# HELP %s some metric' % o.name, + # type='# TYPE %s counter' % o.name, + help=('# HELP {0} {1}\n'.format(o.name, o.help) if o.help else ''), + type=('# TYPE {0} {1}\n'.format(o.name, o.type) if o.type else ''), + samples=''.join([Metric.sample_serialize(o, s) for s in o.samples]), + ) + for o in metrics + if len(o.samples) > 0 + ] + ), + content_type='text/plain; version=0.0.4; charset=utf-8', + ) From 5df328aec2a4d6266e4bce5bb7be7ced15f080d7 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Mon, 25 Aug 2025 19:05:07 +0300 Subject: [PATCH 14/70] [+] update lock 1. fix pip==25.1; otherwise custom extensions crash on typing checks; 2. add tomlq, since meson.build requires it; --- python/pyproject.toml | 3 ++ python/requirements.txt | 115 ++++++++++++++++++++++++++++++++-------- 2 files changed, 97 insertions(+), 21 deletions(-) diff --git a/python/pyproject.toml b/python/pyproject.toml index cf87c01..02c5ebc 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -21,6 +21,8 @@ dependencies = [ 'pydantic', 'pydantic-settings', 'tomlkit', + 'tomlq', + 'pip==25.1', ] [project.optional-dependencies] @@ -45,6 +47,7 @@ lint = [ 'django-stubs', 'pyright', 'ruff', + 'pip==25.1', # 'tomlkit', ] diff --git a/python/requirements.txt b/python/requirements.txt index f6bd3b2..7724b90 100644 --- a/python/requirements.txt +++ b/python/requirements.txt @@ -1,9 +1,13 @@ # This file was autogenerated by uv via the following command: -# uv pip compile --generate-hashes -o /home/nartes/Documents/current/freelance-project-34-marketing-blog/python/requirements.txt /tmp/requirements7xt3_2_h.in +# uv pip compile --generate-hashes -o /home/nartes/Documents/current/freelance-project-34-marketing-blog/python/requirements.txt /tmp/requirementsb22u9wh4.in annotated-types==0.7.0 \ --hash=sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53 \ --hash=sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89 # via pydantic +argcomplete==3.6.2 \ + --hash=sha256:65b3133a29ad53fb42c48cf5114752c7ab66c1c38544fdf6460f450c09b42591 \ + --hash=sha256:d0519b1bc867f5f4f4713c41ad0aba73a4a5f007449716b16f385f2166dc6adf + # via yq asgiref==3.9.1 \ --hash=sha256:a5ab6582236218e5ef1648f242fd9f10626cfd4de8dc377db215d5d5098e3142 \ --hash=sha256:f3bba7092a48005b5f5bacd747d36ee4a5a61f4a269a6df590b43144355ebd2c @@ -11,7 +15,7 @@ asgiref==3.9.1 \ build==1.3.0 \ --hash=sha256:698edd0ea270bde950f53aed21f3a0135672206f3911e0176261a31e0e07b397 \ --hash=sha256:7145f0b5061ba90a1500d60bd1b13ca0a8a4cebdd0cc16ed8adf1c0e739f43b4 - # via -r /tmp/requirements7xt3_2_h.in + # via -r /tmp/requirementsb22u9wh4.in cffi==1.17.1 \ --hash=sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8 \ --hash=sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2 \ @@ -119,7 +123,7 @@ cryptography==45.0.6 \ --hash=sha256:f4028f29a9f38a2025abedb2e409973709c660d44319c61762202206ed577c42 \ --hash=sha256:f68f833a9d445cc49f01097d95c83a850795921b3f7cc6488731e69bde3288da \ --hash=sha256:fc022c1fa5acff6def2fc6d7819bbbd31ccddfe67d075331a65d9cfb28a20983 - # via -r /tmp/requirements7xt3_2_h.in + # via -r /tmp/requirementsb22u9wh4.in django==5.2.5 \ --hash=sha256:0745b25681b129a77aae3d4f6549b62d3913d74407831abaa0d9021a03954bae \ --hash=sha256:2b2ada0ee8a5ff743a40e2b9820d1f8e24c11bac9ae6469cd548f0057ea6ddcd @@ -129,7 +133,7 @@ django==5.2.5 \ django-stubs==5.2.2 \ --hash=sha256:2a04b510c7a812f88223fd7e6d87fb4ea98717f19c8e5c8b59691d83ad40a8a6 \ --hash=sha256:79bd0fdbc78958a8f63e0b062bd9d03f1de539664476c0be62ade5f063c9e41e - # via -r /tmp/requirements7xt3_2_h.in + # via -r /tmp/requirementsb22u9wh4.in django-stubs-ext==5.2.2 \ --hash=sha256:8833bbe32405a2a0ce168d3f75a87168f61bd16939caf0e8bf173bccbd8a44c5 \ --hash=sha256:d9d151b919fe2438760f5bd938f03e1cb08c84d0651f9e5917f1313907e42683 @@ -200,7 +204,7 @@ marisa-trie==1.3.0 \ --hash=sha256:ee193c1f26d9a10bbc56b9bd1e3b16c79ed0e0e44387275f8054d4cf853804d1 \ --hash=sha256:f03cea2fabebf4f1429ccb87c4037dacd828050e8829cacb233f0865bda4244e \ --hash=sha256:f44e0c0c339fe44dd3e7fcbab91cc1a5888c12c35a8bf2811b3eb85236570b29 - # via -r /tmp/requirements7xt3_2_h.in + # via -r /tmp/requirementsb22u9wh4.in meson==1.9.0 \ --hash=sha256:45e51ddc41e37d961582d06e78c48e0f9039011587f3495c4d6b0781dad92357 \ --hash=sha256:cd27277649b5ed50d19875031de516e270b22e890d9db65ed9af57d18ebc498d @@ -208,7 +212,7 @@ meson==1.9.0 \ meson-python==0.18.0 \ --hash=sha256:3b0fe051551cc238f5febb873247c0949cd60ded556efa130aa57021804868e2 \ --hash=sha256:c56a99ec9df669a40662fe46960321af6e4b14106c14db228709c1628e23848d - # via -r /tmp/requirements7xt3_2_h.in + # via -r /tmp/requirementsb22u9wh4.in mypy==1.17.1 \ --hash=sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341 \ --hash=sha256:064e2ff508e5464b4bd807a7c1625bc5047c5022b85c70f030680e18f37273a5 \ @@ -248,7 +252,7 @@ mypy==1.17.1 \ --hash=sha256:fa6ffadfbe6994d724c5a1bb6123a7d27dd68fc9c059561cd33b664a79578e14 \ --hash=sha256:feb8cc32d319edd5859da2cc084493b3e2ce5e49a946377663cc90f6c15fb259 \ --hash=sha256:ff2933428516ab63f961644bc49bc4cbe42bbffb2cd3b71cc7277c07d16b1a8b - # via -r /tmp/requirements7xt3_2_h.in + # via -r /tmp/requirementsb22u9wh4.in mypy-extensions==1.1.0 \ --hash=sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505 \ --hash=sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558 @@ -332,7 +336,7 @@ numpy==2.3.2 \ --hash=sha256:fb1752a3bb9a3ad2d6b090b88a9a0ae1cd6f004ef95f75825e2f382c183b2097 \ --hash=sha256:fc927d7f289d14f5e037be917539620603294454130b6de200091e23d27dc9be \ --hash=sha256:fed5527c4cf10f16c6d0b6bee1f89958bccb0ad2522c8cadc2efd318bcd545f5 - # via -r /tmp/requirements7xt3_2_h.in + # via -r /tmp/requirementsb22u9wh4.in packaging==25.0 \ --hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \ --hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f @@ -344,14 +348,14 @@ pathspec==0.12.1 \ --hash=sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08 \ --hash=sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712 # via mypy -pip==25.2 \ - --hash=sha256:578283f006390f85bb6282dffb876454593d637f5d1be494b5202ce4877e71f2 \ - --hash=sha256:6d67a2b4e7f14d8b31b8b52648866fa717f45a1eb70e83002f4331d07e953717 - # via -r /tmp/requirements7xt3_2_h.in +pip==25.1 \ + --hash=sha256:13b4aa0aaad055020a11bec8a1c2a70a2b2d080e12d89b962266029fff0a16ba \ + --hash=sha256:272bdd1289f80165e9070a4f881e8f9e1001bbb50378561d1af20e49bf5a2200 + # via -r /tmp/requirementsb22u9wh4.in pybind11==3.0.1 \ --hash=sha256:9c0f40056a016da59bab516efb523089139fcc6f2ba7e4930854c61efb932051 \ --hash=sha256:aa8f0aa6e0a94d3b64adfc38f560f33f15e589be2175e103c0a33c6bce55ee89 - # via -r /tmp/requirements7xt3_2_h.in + # via -r /tmp/requirementsb22u9wh4.in pycparser==2.22 \ --hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \ --hash=sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc @@ -360,7 +364,7 @@ pydantic==2.11.7 \ --hash=sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db \ --hash=sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b # via - # -r /tmp/requirements7xt3_2_h.in + # -r /tmp/requirementsb22u9wh4.in # pydantic-settings pydantic-core==2.33.2 \ --hash=sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d \ @@ -466,7 +470,7 @@ pydantic-core==2.33.2 \ pydantic-settings==2.10.1 \ --hash=sha256:06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee \ --hash=sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796 - # via -r /tmp/requirements7xt3_2_h.in + # via -r /tmp/requirementsb22u9wh4.in pyproject-hooks==1.2.0 \ --hash=sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8 \ --hash=sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913 @@ -478,11 +482,66 @@ pyproject-metadata==0.9.1 \ pyright==1.1.404 \ --hash=sha256:455e881a558ca6be9ecca0b30ce08aa78343ecc031d37a198ffa9a7a1abeb63e \ --hash=sha256:c7b7ff1fdb7219c643079e4c3e7d4125f0dafcc19d253b47e898d130ea426419 - # via -r /tmp/requirements7xt3_2_h.in + # via -r /tmp/requirementsb22u9wh4.in python-dotenv==1.1.1 \ --hash=sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc \ --hash=sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab # via pydantic-settings +pyyaml==6.0.2 \ + --hash=sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff \ + --hash=sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48 \ + --hash=sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086 \ + --hash=sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e \ + --hash=sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133 \ + --hash=sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5 \ + --hash=sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484 \ + --hash=sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee \ + --hash=sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5 \ + --hash=sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68 \ + --hash=sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a \ + --hash=sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf \ + --hash=sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99 \ + --hash=sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8 \ + --hash=sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85 \ + --hash=sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19 \ + --hash=sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc \ + --hash=sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a \ + --hash=sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1 \ + --hash=sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317 \ + --hash=sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c \ + --hash=sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631 \ + --hash=sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d \ + --hash=sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652 \ + --hash=sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5 \ + --hash=sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e \ + --hash=sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b \ + --hash=sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8 \ + --hash=sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476 \ + --hash=sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706 \ + --hash=sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563 \ + --hash=sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237 \ + --hash=sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b \ + --hash=sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083 \ + --hash=sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180 \ + --hash=sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425 \ + --hash=sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e \ + --hash=sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f \ + --hash=sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725 \ + --hash=sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183 \ + --hash=sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab \ + --hash=sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774 \ + --hash=sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725 \ + --hash=sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e \ + --hash=sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5 \ + --hash=sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d \ + --hash=sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290 \ + --hash=sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44 \ + --hash=sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed \ + --hash=sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4 \ + --hash=sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba \ + --hash=sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12 \ + --hash=sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4 + # via yq ruff==0.12.10 \ --hash=sha256:059e863ea3a9ade41407ad71c1de2badfbe01539117f38f763ba42a1206f7559 \ --hash=sha256:141ce3d88803c625257b8a6debf4a0473eb6eed9643a6189b68838b43e78165a \ @@ -503,11 +562,11 @@ ruff==0.12.10 \ --hash=sha256:e67d96827854f50b9e3e8327b031647e7bcc090dbe7bb11101a81a3a2cbf1cc9 \ --hash=sha256:ebb7333a45d56efc7c110a46a69a1b32365d5c5161e7244aaf3aa20ce62399c1 \ --hash=sha256:f3fc21178cd44c98142ae7590f42ddcb587b8e09a3b849cbc84edb62ee95de60 - # via -r /tmp/requirements7xt3_2_h.in + # via -r /tmp/requirementsb22u9wh4.in setuptools==80.9.0 \ --hash=sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922 \ --hash=sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c - # via -r /tmp/requirements7xt3_2_h.in + # via -r /tmp/requirementsb22u9wh4.in sqlparse==0.5.3 \ --hash=sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272 \ --hash=sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca @@ -545,11 +604,17 @@ tomli==2.2.1 \ --hash=sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272 \ --hash=sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a \ --hash=sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7 - # via -r /tmp/requirements7xt3_2_h.in + # via -r /tmp/requirementsb22u9wh4.in tomlkit==0.13.3 \ --hash=sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1 \ --hash=sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0 - # via -r /tmp/requirements7xt3_2_h.in + # via + # -r /tmp/requirementsb22u9wh4.in + # yq +tomlq==0.1.0 \ + --hash=sha256:4b966fd999ed2bf69081b7c7f5caadbc4c9542d0ed5fcf2e9b7b4d8d7ada3c82 \ + --hash=sha256:e775720e90da3e405142b9fe476145e71c0389f787b1ff9933f92a1704d8c6e7 + # via -r /tmp/requirementsb22u9wh4.in types-pyyaml==6.0.12.20250822 \ --hash=sha256:1fe1a5e146aa315483592d292b72a172b65b946a6d98aa6ddd8e4aa838ab7098 \ --hash=sha256:259f1d93079d335730a9db7cff2bcaf65d7e04b4a56b5927d49a612199b59413 @@ -591,4 +656,12 @@ uv==0.8.13 \ --hash=sha256:d22fa55580b224779279b98e0b23cbc45e51837e1fac616d7c5d03aff668a998 \ --hash=sha256:eb90089624d92d57b8582f708973db8988e09dba6bae83991dba20731d82eb6a \ --hash=sha256:f6c508aa9c5210577008e1919b532e38356fe68712179399f00462b3e78fd845 - # via -r /tmp/requirements7xt3_2_h.in + # via -r /tmp/requirementsb22u9wh4.in +xmltodict==0.14.2 \ + --hash=sha256:201e7c28bb210e374999d1dde6382923ab0ed1a8a5faeece48ab525b7810a553 \ + --hash=sha256:20cc7d723ed729276e808f26fb6b3599f786cbc37e06c65e192ba77c40f20aac + # via yq +yq==3.4.3 \ + --hash=sha256:547e34bc3caacce83665fd3429bf7c85f8e8b6b9aaee3f953db1ad716ff3434d \ + --hash=sha256:ba586a1a6f30cf705b2f92206712df2281cd320280210e7b7b80adcb8f256e3b + # via tomlq From c6301b634466c584cfb45f102a911191ba7ebfe0 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Mon, 25 Aug 2025 19:07:55 +0300 Subject: [PATCH 15/70] [+] add .whl 0.1.5.24 --- python/meson.build | 2 +- releases/whl/online_fxreader_pr34-0.1.5.24-py3-none-any.whl | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.24-py3-none-any.whl diff --git a/python/meson.build b/python/meson.build index 768c7c4..2ccb7e7 100644 --- a/python/meson.build +++ b/python/meson.build @@ -5,7 +5,7 @@ project( ).stdout().strip('\n'), # 'online.fxreader.uv', # ['c', 'cpp'], - version: '0.1.5.23', + version: '0.1.5.24', # default_options: [ # 'cpp_std=c++23', # # 'prefer_static=true', diff --git a/releases/whl/online_fxreader_pr34-0.1.5.24-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.24-py3-none-any.whl new file mode 100644 index 0000000..45b043b --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.24-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5e6ad1193bbdb859c2f0b1b407ccf944880212fd339139beede1eb31b4b2ce8a +size 73447 From 83c9d864da822b305581ab27422f22fff9d846ff Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Wed, 27 Aug 2025 11:36:31 +0300 Subject: [PATCH 16/70] [+] improve checks app 1. partially add simple module for fastapi apps; 2. update checks rest.py; --- .gitattributes | 1 + .gitignore | 1 + docker-compose.yml | 4 +- docker/checks/Dockerfile | 23 +- docker/checks/Makefile | 7 + ...ne_fxreader_pr34-0.1.5.24-py3-none-any.whl | 3 + docker/checks/requirements.in | 3 + docker/checks/requirements.txt | 382 ++++++++++++++++++ docker/checks/rest.py | 2 + .../pr34/commands_typed/async_api/fastapi.py | 71 ++++ 10 files changed, 492 insertions(+), 5 deletions(-) create mode 100644 docker/checks/Makefile create mode 100644 docker/checks/deps/whl/online_fxreader_pr34-0.1.5.24-py3-none-any.whl create mode 100644 docker/checks/requirements.in create mode 100644 docker/checks/requirements.txt create mode 100644 python/online/fxreader/pr34/commands_typed/async_api/fastapi.py diff --git a/.gitattributes b/.gitattributes index a7c9697..3daee15 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ releases/tar/** filter=lfs diff=lfs merge=lfs -text releases/whl/** filter=lfs diff=lfs merge=lfs -text +docker/*/deps/whl/** filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore index d44bf86..9aff2d6 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ python/build .env !docker/*/.env .envs +!docker/*/deps/whl/** diff --git a/docker-compose.yml b/docker-compose.yml index f3a6be0..68e91a1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -32,8 +32,8 @@ services: checks: build: - context: . - dockerfile: ./docker/checks/Dockerfile + context: ./docker/checks + dockerfile: ./Dockerfile init: true env_file: .envs/checks.patched.env diff --git a/docker/checks/Dockerfile b/docker/checks/Dockerfile index 3f558fd..5a1d132 100644 --- a/docker/checks/Dockerfile +++ b/docker/checks/Dockerfile @@ -1,9 +1,26 @@ FROM alpine@sha256:56fa17d2a7e7f168a043a2712e63aed1f8543aeafdcee47c58dcffe38ed51099 -RUN apk add --no-cache python3 +RUN apk add --no-cache python3 py3-pip + +RUN \ + --mount=type=cache,target=/root/.cache/pip \ + pip install \ + --break-system-packages \ + uv WORKDIR /app -COPY ./docker/checks/rest.py ./docker/checks/rest.py +COPY requirements.txt requirements.txt -CMD ["python3", "docker/checks/rest.py"] +RUN \ + --mount=type=cache,target=/root/.cache/pip \ + --mount=type=cache,target=/root/.cache/uv \ + --mount=type=bind,source=./deps/whl,target=/app/deps/whl \ + uv pip install \ + -f deps/whl \ + -r requirements.txt \ + --break-system-packages --system + +COPY ./rest.py ./rest.py + +CMD ["python3", "rest.py"] diff --git a/docker/checks/Makefile b/docker/checks/Makefile new file mode 100644 index 0000000..da4dee0 --- /dev/null +++ b/docker/checks/Makefile @@ -0,0 +1,7 @@ +venv_compile: + uv pip compile \ + -p 3.12 \ + --generate-hashes \ + -f deps/whl \ + requirements.in > \ + requirements.txt diff --git a/docker/checks/deps/whl/online_fxreader_pr34-0.1.5.24-py3-none-any.whl b/docker/checks/deps/whl/online_fxreader_pr34-0.1.5.24-py3-none-any.whl new file mode 100644 index 0000000..45b043b --- /dev/null +++ b/docker/checks/deps/whl/online_fxreader_pr34-0.1.5.24-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5e6ad1193bbdb859c2f0b1b407ccf944880212fd339139beede1eb31b4b2ce8a +size 73447 diff --git a/docker/checks/requirements.in b/docker/checks/requirements.in new file mode 100644 index 0000000..23b90c1 --- /dev/null +++ b/docker/checks/requirements.in @@ -0,0 +1,3 @@ +online.fxreader.pr34[django]>=0.1.5.24 +fastapi +uvicorn diff --git a/docker/checks/requirements.txt b/docker/checks/requirements.txt new file mode 100644 index 0000000..48f95c3 --- /dev/null +++ b/docker/checks/requirements.txt @@ -0,0 +1,382 @@ +# This file was autogenerated by uv via the following command: +# uv pip compile -p 3.12 --generate-hashes requirements.in +annotated-types==0.7.0 \ + --hash=sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53 \ + --hash=sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89 + # via pydantic +anyio==4.10.0 \ + --hash=sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6 \ + --hash=sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1 + # via starlette +argcomplete==3.6.2 \ + --hash=sha256:65b3133a29ad53fb42c48cf5114752c7ab66c1c38544fdf6460f450c09b42591 \ + --hash=sha256:d0519b1bc867f5f4f4713c41ad0aba73a4a5f007449716b16f385f2166dc6adf + # via yq +asgiref==3.9.1 \ + --hash=sha256:a5ab6582236218e5ef1648f242fd9f10626cfd4de8dc377db215d5d5098e3142 \ + --hash=sha256:f3bba7092a48005b5f5bacd747d36ee4a5a61f4a269a6df590b43144355ebd2c + # via django +click==8.2.1 \ + --hash=sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202 \ + --hash=sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b + # via uvicorn +django==5.2.5 \ + --hash=sha256:0745b25681b129a77aae3d4f6549b62d3913d74407831abaa0d9021a03954bae \ + --hash=sha256:2b2ada0ee8a5ff743a40e2b9820d1f8e24c11bac9ae6469cd548f0057ea6ddcd + # via online-fxreader-pr34 +fastapi==0.116.1 \ + --hash=sha256:c46ac7c312df840f0c9e220f7964bada936781bc4e2e6eb71f1c4d7553786565 \ + --hash=sha256:ed52cbf946abfd70c5a0dccb24673f0670deeb517a88b3544d03c2a6bf283143 + # via -r requirements.in +h11==0.16.0 \ + --hash=sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1 \ + --hash=sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86 + # via uvicorn +idna==3.10 \ + --hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \ + --hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3 + # via anyio +marisa-trie==1.3.1 \ + --hash=sha256:076731f79f8603cb3216cb6e5bbbc56536c89f63f175ad47014219ecb01e5996 \ + --hash=sha256:0b9816ab993001a7854b02a7daec228892f35bd5ab0ac493bacbd1b80baec9f1 \ + --hash=sha256:0c2bc6bee737f4d47fce48c5b03a7bd3214ef2d83eb5c9f84210091370a5f195 \ + --hash=sha256:0dcd42774e367ceb423c211a4fc8e7ce586acfaf0929c9c06d98002112075239 \ + --hash=sha256:0e6f3b45def6ff23e254eeaa9079267004f0069d0a34eba30a620780caa4f2cb \ + --hash=sha256:137010598d8cebc53dbfb7caf59bde96c33a6af555e3e1bdbf30269b6a157e1e \ + --hash=sha256:2f7c10f69cbc3e6c7d715ec9cb0c270182ea2496063bebeda873f4aa83fd9910 \ + --hash=sha256:3715d779561699471edde70975e07b1de7dddb2816735d40ed16be4b32054188 \ + --hash=sha256:3834304fdeaa1c9b73596ad5a6c01a44fc19c13c115194704b85f7fbdf0a7b8e \ + --hash=sha256:389721481c14a92fa042e4b91ae065bff13e2bc567c85a10aa9d9de80aaa8622 \ + --hash=sha256:3a96ef3e461ecc85ec7d2233ddc449ff5a3fbdc520caea752bc5bc8faa975231 \ + --hash=sha256:3e2a0e1be95237981bd375a388f44b33d69ea5669a2f79fea038e45fff326595 \ + --hash=sha256:3e431f9c80ee1850b2a406770acf52c058b97a27968a0ed6aca45c2614d64c9f \ + --hash=sha256:47631614c5243ed7d15ae0af8245fcc0599f5b7921fae2a4ae992afb27c9afbb \ + --hash=sha256:52d1764906befef91886e3bff374d8090c9716822bd56b70e07aa697188090b7 \ + --hash=sha256:5370f9ef6c008e502537cc1ff518c80ddf749367ce90179efa0e7f6275903a76 \ + --hash=sha256:56043cf908ddf3d7364498085dbc2855d4ea8969aff3bf2439a79482a79e68e2 \ + --hash=sha256:5a6abc9573a6a45d09548fde136dbcd4260b8c56f8dff443eaa565352d7cca59 \ + --hash=sha256:5b7c1e7fa6c3b855e8cfbabf38454d7decbaba1c567d0cd58880d033c6b363bd \ + --hash=sha256:5ef045f694ef66079b4e00c4c9063a00183d6af7d1ff643de6ea5c3b0d9af01b \ + --hash=sha256:68678816818efcd4a1787b557af81f215b989ec88680a86c85c34c914d413690 \ + --hash=sha256:6cac19952e0e258ded765737d1fb11704fe81bf4f27526638a5d44496f329235 \ + --hash=sha256:70b4c96f9119cfeb4dc6a0cf4afc9f92f0b002cde225bcd910915d976c78e66a \ + --hash=sha256:7e957aa4251a8e70b9fe02a16b2d190f18787902da563cb7ba865508b8e8fb04 \ + --hash=sha256:82de2de90488d0fbbf74cf9f20e1afd62e320693b88f5e9565fc80b28f5bbad3 \ + --hash=sha256:83a3748088d117a9b15d8981c947df9e4f56eb2e4b5456ae34fe1f83666c9185 \ + --hash=sha256:83efc045fc58ca04c91a96c9b894d8a19ac6553677a76f96df01ff9f0405f53d \ + --hash=sha256:8c8b2386d2d22c57880ed20a913ceca86363765623175671137484a7d223f07a \ + --hash=sha256:8f81344d212cb41992340b0b8a67e375f44da90590b884204fd3fa5e02107df2 \ + --hash=sha256:954fef9185f8a79441b4e433695116636bf66402945cfee404f8983bafa59788 \ + --hash=sha256:9651daa1fdc471df5a5fa6a4833d3b01e76ac512eea141a5995681aebac5555f \ + --hash=sha256:9688c7b45f744366a4ef661e399f24636ebe440d315ab35d768676c59c613186 \ + --hash=sha256:97107fd12f30e4f8fea97790343a2d2d9a79d93697fe14e1b6f6363c984ff85b \ + --hash=sha256:9868b7a8e0f648d09ffe25ac29511e6e208cc5fb0d156c295385f9d5dc2a138e \ + --hash=sha256:986eaf35a7f63c878280609ecd37edf8a074f7601c199acfec81d03f1ee9a39a \ + --hash=sha256:99a00cab4cf9643a87977c87a5c8961aa44fff8d5dd46e00250135f686e7dedf \ + --hash=sha256:9c56001badaf1779afae5c24b7ab85938644ab8ef3c5fd438ab5d49621b84482 \ + --hash=sha256:9dc61fb8f8993589544f6df268229c6cf0a56ad4ed3e8585a9cd23c5ad79527b \ + --hash=sha256:9de573d933db4753a50af891bcb3ffbfe14e200406214c223aa5dfe2163f316d \ + --hash=sha256:9e467e13971c64db6aed8afe4c2a131c3f73f048bec3f788a6141216acda598d \ + --hash=sha256:9e6496bbad3068e3bbbb934b1e1307bf1a9cb4609f9ec47b57e8ea37f1b5ee40 \ + --hash=sha256:9f92d3577c72d5a97af5c8e3d98247b79c8ccfb64ebf611311dcf631b11e5604 \ + --hash=sha256:a1c6990961d1177f6d8fdf7b610fa2e7c0c02743a090d173f6dfa9dc9231c73c \ + --hash=sha256:a5a0a58ffe2a7eb3f870214c6df8f9a43ce768bd8fed883e6ba8c77645666b63 \ + --hash=sha256:a7416f1a084eb889c5792c57317875aeaa86abfe0bdc6f167712cebcec1d36ee \ + --hash=sha256:a83f5f7ae3494e0cc25211296252b1b86901c788ed82c83adda19d0c98f828d6 \ + --hash=sha256:a850b151bd1e3a5d9afef113adc22727d696603659d575d7e84f994bd8d04bf1 \ + --hash=sha256:ad82ab8a58562cf69e6b786debcc7638b28df12f9f1c7bcffb07efb5c1f09cbd \ + --hash=sha256:b173ec46d521308f7c97d96d6e05cf2088e0548f82544ec9a8656af65593304d \ + --hash=sha256:bf9f2b97fcfd5e2dbb0090d0664023872dcde990df0b545eca8d0ce95795a409 \ + --hash=sha256:c12b44c190deb0d67655021da1f2d0a7d61a257bf844101cf982e68ed344f28d \ + --hash=sha256:c6571462417cda2239b1ade86ceaf3852da9b52c6286046e87d404afc6da20a7 \ + --hash=sha256:c785fd6dae9daa6825734b7b494cdac972f958be1f9cb3fb1f32be8598d2b936 \ + --hash=sha256:c7a33506d0451112911c69f38d55da3e0e050f2be0ea4e5176865cf03baf26a9 \ + --hash=sha256:c89df75aefe1ad7e613340790130f1badc5926bcfa66a6b3c9471071002956a5 \ + --hash=sha256:ca644534f15f85bba14c412afc17de07531e79a766ce85b8dbf3f8b6e7758f20 \ + --hash=sha256:cbd28f95d5f30d9a7af6130869568e75bfd7ef2e0adfb1480f1f44480f5d3603 \ + --hash=sha256:d0f87bdf660f01e88ab3a507955697b2e3284065afa0b94fc9e77d6ad153ed5e \ + --hash=sha256:d4bd41a6e73c0d0adafe4de449b6d35530a4ce6a836a6ee839baf117785ecfd7 \ + --hash=sha256:d8d5e686db0ae758837ed29b3b742afb994d1a01ce10977eabd3490f16b5c9f9 \ + --hash=sha256:e5888b269e790356ce4525f3e8df1fe866d1497b7d7fb7548cfec883cb985288 \ + --hash=sha256:ec633e108f277f2b7f4671d933a909f39bba549910bf103e2940b87a14da2783 \ + --hash=sha256:ecdb19d33b26738a32602ef432b06cc6deeca4b498ce67ba8e5e39c8a7c19745 \ + --hash=sha256:ee428575377e29c636f2b4b3b0488875dcea310c6c5b3412ec4ef997f7bb37cc \ + --hash=sha256:f4bae4f920f2a1082eaf766c1883df7da84abdf333bafa15b8717c10416a615e + # via online-fxreader-pr34 +mypy==1.17.1 \ + --hash=sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341 \ + --hash=sha256:064e2ff508e5464b4bd807a7c1625bc5047c5022b85c70f030680e18f37273a5 \ + --hash=sha256:099b9a5da47de9e2cb5165e581f158e854d9e19d2e96b6698c0d64de911dd849 \ + --hash=sha256:15a83369400454c41ed3a118e0cc58bd8123921a602f385cb6d6ea5df050c733 \ + --hash=sha256:15d54056f7fe7a826d897789f53dd6377ec2ea8ba6f776dc83c2902b899fee81 \ + --hash=sha256:1b16708a66d38abb1e6b5702f5c2c87e133289da36f6a1d15f6a5221085c6403 \ + --hash=sha256:209a58fed9987eccc20f2ca94afe7257a8f46eb5df1fb69958650973230f91e6 \ + --hash=sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01 \ + --hash=sha256:397fba5d7616a5bc60b45c7ed204717eaddc38f826e3645402c426057ead9a91 \ + --hash=sha256:3fbe6d5555bf608c47203baa3e72dbc6ec9965b3d7c318aa9a4ca76f465bd972 \ + --hash=sha256:43808d9476c36b927fbcd0b0255ce75efe1b68a080154a38ae68a7e62de8f0f8 \ + --hash=sha256:55b918670f692fc9fba55c3298d8a3beae295c5cded0a55dccdc5bbead814acd \ + --hash=sha256:5d1092694f166a7e56c805caaf794e0585cabdbf1df36911c414e4e9abb62ae9 \ + --hash=sha256:62761474061feef6f720149d7ba876122007ddc64adff5ba6f374fda35a018a0 \ + --hash=sha256:665afab0963a4b39dff7c1fa563cc8b11ecff7910206db4b2e64dd1ba25aed19 \ + --hash=sha256:69e83ea6553a3ba79c08c6e15dbd9bfa912ec1e493bf75489ef93beb65209aeb \ + --hash=sha256:70401bbabd2fa1aa7c43bb358f54037baf0586f41e83b0ae67dd0534fc64edfd \ + --hash=sha256:79d44f9bfb004941ebb0abe8eff6504223a9c1ac51ef967d1263c6572bbebc99 \ + --hash=sha256:80ef5c058b7bce08c83cac668158cb7edea692e458d21098c7d3bce35a5d43e7 \ + --hash=sha256:89e972c0035e9e05823907ad5398c5a73b9f47a002b22359b177d40bdaee7056 \ + --hash=sha256:93378d3203a5c0800c6b6d850ad2f19f7a3cdf1a3701d3416dbf128805c6a6a7 \ + --hash=sha256:9a2b7d9180aed171f033c9f2fc6c204c1245cf60b0cb61cf2e7acc24eea78e0a \ + --hash=sha256:9d6b20b97d373f41617bd0708fd46aa656059af57f2ef72aa8c7d6a2b73b74ed \ + --hash=sha256:a76906f26bd8d51ea9504966a9c25419f2e668f012e0bdf3da4ea1526c534d94 \ + --hash=sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9 \ + --hash=sha256:ad37544be07c5d7fba814eb370e006df58fed8ad1ef33ed1649cb1889ba6ff58 \ + --hash=sha256:b01586eed696ec905e61bd2568f48740f7ac4a45b3a468e6423a03d3788a51a8 \ + --hash=sha256:c1fdf4abb29ed1cb091cf432979e162c208a5ac676ce35010373ff29247bcad5 \ + --hash=sha256:c49562d3d908fd49ed0938e5423daed8d407774a479b595b143a3d7f87cdae6a \ + --hash=sha256:c4a580f8a70c69e4a75587bd925d298434057fe2a428faaf927ffe6e4b9a98df \ + --hash=sha256:c837b896b37cd103570d776bda106eabb8737aa6dd4f248451aecf53030cdbeb \ + --hash=sha256:d7598cf74c3e16539d4e2f0b8d8c318e00041553d83d4861f87c7a72e95ac24d \ + --hash=sha256:dd86bb649299f09d987a2eebb4d52d10603224500792e1bee18303bbcc1ce390 \ + --hash=sha256:e79311f2d904ccb59787477b7bd5d26f3347789c06fcd7656fa500875290264b \ + --hash=sha256:e92bdc656b7757c438660f775f872a669b8ff374edc4d18277d86b63edba6b8b \ + --hash=sha256:fa6ffadfbe6994d724c5a1bb6123a7d27dd68fc9c059561cd33b664a79578e14 \ + --hash=sha256:feb8cc32d319edd5859da2cc084493b3e2ce5e49a946377663cc90f6c15fb259 \ + --hash=sha256:ff2933428516ab63f961644bc49bc4cbe42bbffb2cd3b71cc7277c07d16b1a8b + # via online-fxreader-pr34 +mypy-extensions==1.1.0 \ + --hash=sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505 \ + --hash=sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558 + # via mypy +online-fxreader-pr34==0.1.5.24 \ + --hash=sha256:5e6ad1193bbdb859c2f0b1b407ccf944880212fd339139beede1eb31b4b2ce8a + # via -r requirements.in +pathspec==0.12.1 \ + --hash=sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08 \ + --hash=sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712 + # via mypy +pip==25.1 \ + --hash=sha256:13b4aa0aaad055020a11bec8a1c2a70a2b2d080e12d89b962266029fff0a16ba \ + --hash=sha256:272bdd1289f80165e9070a4f881e8f9e1001bbb50378561d1af20e49bf5a2200 + # via online-fxreader-pr34 +pydantic==2.11.7 \ + --hash=sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db \ + --hash=sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b + # via + # fastapi + # online-fxreader-pr34 + # pydantic-settings +pydantic-core==2.33.2 \ + --hash=sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d \ + --hash=sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac \ + --hash=sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02 \ + --hash=sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56 \ + --hash=sha256:09fb9dd6571aacd023fe6aaca316bd01cf60ab27240d7eb39ebd66a3a15293b4 \ + --hash=sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22 \ + --hash=sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef \ + --hash=sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec \ + --hash=sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d \ + --hash=sha256:0e6116757f7959a712db11f3e9c0a99ade00a5bbedae83cb801985aa154f071b \ + --hash=sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a \ + --hash=sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f \ + --hash=sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052 \ + --hash=sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab \ + --hash=sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916 \ + --hash=sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c \ + --hash=sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf \ + --hash=sha256:2807668ba86cb38c6817ad9bc66215ab8584d1d304030ce4f0887336f28a5e27 \ + --hash=sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a \ + --hash=sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8 \ + --hash=sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7 \ + --hash=sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612 \ + --hash=sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1 \ + --hash=sha256:3a1c81334778f9e3af2f8aeb7a960736e5cab1dfebfb26aabca09afd2906c039 \ + --hash=sha256:3abcd9392a36025e3bd55f9bd38d908bd17962cc49bc6da8e7e96285336e2bca \ + --hash=sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7 \ + --hash=sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a \ + --hash=sha256:3eb3fe62804e8f859c49ed20a8451342de53ed764150cb14ca71357c765dc2a6 \ + --hash=sha256:44857c3227d3fb5e753d5fe4a3420d6376fa594b07b621e220cd93703fe21782 \ + --hash=sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b \ + --hash=sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7 \ + --hash=sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025 \ + --hash=sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849 \ + --hash=sha256:53a57d2ed685940a504248187d5685e49eb5eef0f696853647bf37c418c538f7 \ + --hash=sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b \ + --hash=sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa \ + --hash=sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e \ + --hash=sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea \ + --hash=sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac \ + --hash=sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51 \ + --hash=sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e \ + --hash=sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162 \ + --hash=sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65 \ + --hash=sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2 \ + --hash=sha256:6fa6dfc3e4d1f734a34710f391ae822e0a8eb8559a85c6979e14e65ee6ba2954 \ + --hash=sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b \ + --hash=sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de \ + --hash=sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc \ + --hash=sha256:7f92c15cd1e97d4b12acd1cc9004fa092578acfa57b67ad5e43a197175d01a64 \ + --hash=sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb \ + --hash=sha256:83aa99b1285bc8f038941ddf598501a86f1536789740991d7d8756e34f1e74d9 \ + --hash=sha256:87acbfcf8e90ca885206e98359d7dca4bcbb35abdc0ff66672a293e1d7a19101 \ + --hash=sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d \ + --hash=sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef \ + --hash=sha256:8d55ab81c57b8ff8548c3e4947f119551253f4e3787a7bbc0b6b3ca47498a9d3 \ + --hash=sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1 \ + --hash=sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5 \ + --hash=sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88 \ + --hash=sha256:970919794d126ba8645f3837ab6046fb4e72bbc057b3709144066204c19a455d \ + --hash=sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290 \ + --hash=sha256:9fcd347d2cc5c23b06de6d3b7b8275be558a0c90549495c699e379a80bf8379e \ + --hash=sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d \ + --hash=sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808 \ + --hash=sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc \ + --hash=sha256:a2b911a5b90e0374d03813674bf0a5fbbb7741570dcd4b4e85a2e48d17def29d \ + --hash=sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc \ + --hash=sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e \ + --hash=sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640 \ + --hash=sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30 \ + --hash=sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e \ + --hash=sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9 \ + --hash=sha256:c20c462aa4434b33a2661701b861604913f912254e441ab8d78d30485736115a \ + --hash=sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9 \ + --hash=sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f \ + --hash=sha256:c54c939ee22dc8e2d545da79fc5381f1c020d6d3141d3bd747eab59164dc89fb \ + --hash=sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5 \ + --hash=sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab \ + --hash=sha256:d3f26877a748dc4251cfcfda9dfb5f13fcb034f5308388066bcfe9031b63ae7d \ + --hash=sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572 \ + --hash=sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593 \ + --hash=sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29 \ + --hash=sha256:dac89aea9af8cd672fa7b510e7b8c33b0bba9a43186680550ccf23020f32d535 \ + --hash=sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1 \ + --hash=sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f \ + --hash=sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8 \ + --hash=sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf \ + --hash=sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246 \ + --hash=sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9 \ + --hash=sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011 \ + --hash=sha256:eb9b459ca4df0e5c87deb59d37377461a538852765293f9e6ee834f0435a93b9 \ + --hash=sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a \ + --hash=sha256:f481959862f57f29601ccced557cc2e817bce7533ab8e01a797a48b49c9692b3 \ + --hash=sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6 \ + --hash=sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8 \ + --hash=sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a \ + --hash=sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2 \ + --hash=sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c \ + --hash=sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6 \ + --hash=sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d + # via pydantic +pydantic-settings==2.10.1 \ + --hash=sha256:06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee \ + --hash=sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796 + # via online-fxreader-pr34 +python-dotenv==1.1.1 \ + --hash=sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc \ + --hash=sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab + # via pydantic-settings +pyyaml==6.0.2 \ + --hash=sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff \ + --hash=sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48 \ + --hash=sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086 \ + --hash=sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e \ + --hash=sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133 \ + --hash=sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5 \ + --hash=sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484 \ + --hash=sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee \ + --hash=sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5 \ + --hash=sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68 \ + --hash=sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a \ + --hash=sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf \ + --hash=sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99 \ + --hash=sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8 \ + --hash=sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85 \ + --hash=sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19 \ + --hash=sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc \ + --hash=sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a \ + --hash=sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1 \ + --hash=sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317 \ + --hash=sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c \ + --hash=sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631 \ + --hash=sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d \ + --hash=sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652 \ + --hash=sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5 \ + --hash=sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e \ + --hash=sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b \ + --hash=sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8 \ + --hash=sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476 \ + --hash=sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706 \ + --hash=sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563 \ + --hash=sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237 \ + --hash=sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b \ + --hash=sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083 \ + --hash=sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180 \ + --hash=sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425 \ + --hash=sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e \ + --hash=sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f \ + --hash=sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725 \ + --hash=sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183 \ + --hash=sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab \ + --hash=sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774 \ + --hash=sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725 \ + --hash=sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e \ + --hash=sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5 \ + --hash=sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d \ + --hash=sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290 \ + --hash=sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44 \ + --hash=sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed \ + --hash=sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4 \ + --hash=sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba \ + --hash=sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12 \ + --hash=sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4 + # via yq +sniffio==1.3.1 \ + --hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \ + --hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc + # via anyio +sqlparse==0.5.3 \ + --hash=sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272 \ + --hash=sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca + # via django +starlette==0.47.3 \ + --hash=sha256:6bc94f839cc176c4858894f1f8908f0ab79dfec1a6b8402f6da9be26ebea52e9 \ + --hash=sha256:89c0778ca62a76b826101e7c709e70680a1699ca7da6b44d38eb0a7e61fe4b51 + # via fastapi +tomlkit==0.13.3 \ + --hash=sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1 \ + --hash=sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0 + # via + # online-fxreader-pr34 + # yq +tomlq==0.1.0 \ + --hash=sha256:4b966fd999ed2bf69081b7c7f5caadbc4c9542d0ed5fcf2e9b7b4d8d7ada3c82 \ + --hash=sha256:e775720e90da3e405142b9fe476145e71c0389f787b1ff9933f92a1704d8c6e7 + # via online-fxreader-pr34 +typing-extensions==4.15.0 \ + --hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \ + --hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548 + # via + # anyio + # fastapi + # mypy + # pydantic + # pydantic-core + # starlette + # typing-inspection +typing-inspection==0.4.1 \ + --hash=sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51 \ + --hash=sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28 + # via + # pydantic + # pydantic-settings +uvicorn==0.35.0 \ + --hash=sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a \ + --hash=sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01 + # via -r requirements.in +xmltodict==0.14.2 \ + --hash=sha256:201e7c28bb210e374999d1dde6382923ab0ed1a8a5faeece48ab525b7810a553 \ + --hash=sha256:20cc7d723ed729276e808f26fb6b3599f786cbc37e06c65e192ba77c40f20aac + # via yq +yq==3.4.3 \ + --hash=sha256:547e34bc3caacce83665fd3429bf7c85f8e8b6b9aaee3f953db1ad716ff3434d \ + --hash=sha256:ba586a1a6f30cf705b2f92206712df2281cd320280210e7b7b80adcb8f256e3b + # via tomlq diff --git a/docker/checks/rest.py b/docker/checks/rest.py index 6a88ba7..f851293 100644 --- a/docker/checks/rest.py +++ b/docker/checks/rest.py @@ -1,3 +1,5 @@ +from online.fxreader.pr34.commands_typed import metrics + def main() -> None: raise NotImplementedError diff --git a/python/online/fxreader/pr34/commands_typed/async_api/fastapi.py b/python/online/fxreader/pr34/commands_typed/async_api/fastapi.py new file mode 100644 index 0000000..4d16bd2 --- /dev/null +++ b/python/online/fxreader/pr34/commands_typed/async_api/fastapi.py @@ -0,0 +1,71 @@ +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 typing import ( + Any, + Optional, + Literal, + Annotated, + cast, + Callable, +) + +logger = logging.getLogger(__name__) + + +def create_app() -> fastapi.FastAPI: + app = fastapi.FastAPI() + + 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')) + + return app + + +def run(args: list[str]): + logging.basicConfig(level=logging.INFO) + + 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:]) From aa927da5562c5892e59b3f68ba20c17615a7c4e6 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Wed, 27 Aug 2025 16:41:44 +0300 Subject: [PATCH 17/70] [+] improve checks service --- docker-compose.yml | 4 + docker/checks/.env | 1 + docker/checks/Dockerfile | 2 +- ...ne_fxreader_pr34-0.1.5.25-py3-none-any.whl | 3 + ...ne_fxreader_pr34-0.1.5.26-py3-none-any.whl | 3 + docker/checks/requirements.in | 2 +- docker/checks/requirements.txt | 51 ++++- docker/checks/rest.py | 6 + python/meson.build | 2 +- .../pr34/commands_typed/async_api/fastapi.py | 8 +- .../pr34/commands_typed/async_api/settings.py | 29 +++ python/pyproject.toml | 12 +- python/requirements.txt | 204 ++++++++++-------- ...ne_fxreader_pr34-0.1.5.25-py3-none-any.whl | 3 + ...ne_fxreader_pr34-0.1.5.26-py3-none-any.whl | 3 + 15 files changed, 232 insertions(+), 101 deletions(-) create mode 100644 docker/checks/deps/whl/online_fxreader_pr34-0.1.5.25-py3-none-any.whl create mode 100644 docker/checks/deps/whl/online_fxreader_pr34-0.1.5.26-py3-none-any.whl create mode 100644 python/online/fxreader/pr34/commands_typed/async_api/settings.py create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.25-py3-none-any.whl create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.26-py3-none-any.whl diff --git a/docker-compose.yml b/docker-compose.yml index 68e91a1..c118626 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -37,6 +37,10 @@ services: init: true env_file: .envs/checks.patched.env + command: + - python3 + - -m + - online.fxreader.pr34.commands_typed.async_api.fastapi cpanel: build: diff --git a/docker/checks/.env b/docker/checks/.env index 237cc4a..28218e9 100644 --- a/docker/checks/.env +++ b/docker/checks/.env @@ -2,3 +2,4 @@ # UVICORN_PORT=80 # HTTP_AUTH_USERNAME=test # HTTP_AUTH_PASSWORD=blah +APPS='["rest:get_router:"]' diff --git a/docker/checks/Dockerfile b/docker/checks/Dockerfile index 5a1d132..cf5b2ec 100644 --- a/docker/checks/Dockerfile +++ b/docker/checks/Dockerfile @@ -23,4 +23,4 @@ RUN \ COPY ./rest.py ./rest.py -CMD ["python3", "rest.py"] +# CMD ["python3", "rest.py"] diff --git a/docker/checks/deps/whl/online_fxreader_pr34-0.1.5.25-py3-none-any.whl b/docker/checks/deps/whl/online_fxreader_pr34-0.1.5.25-py3-none-any.whl new file mode 100644 index 0000000..a1d2890 --- /dev/null +++ b/docker/checks/deps/whl/online_fxreader_pr34-0.1.5.25-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9b70a3fb2fe6652c1ff8c14e464a5baf80a5d9da9e39204eeba40490bf13deca +size 74822 diff --git a/docker/checks/deps/whl/online_fxreader_pr34-0.1.5.26-py3-none-any.whl b/docker/checks/deps/whl/online_fxreader_pr34-0.1.5.26-py3-none-any.whl new file mode 100644 index 0000000..58b307f --- /dev/null +++ b/docker/checks/deps/whl/online_fxreader_pr34-0.1.5.26-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3066558ef4a5dd2dfda85c3d9fb7d50014774d500187da86c6cc0795fca3be10 +size 74831 diff --git a/docker/checks/requirements.in b/docker/checks/requirements.in index 23b90c1..ef12ab4 100644 --- a/docker/checks/requirements.in +++ b/docker/checks/requirements.in @@ -1,3 +1,3 @@ -online.fxreader.pr34[django]>=0.1.5.24 +online.fxreader.pr34[django,fastapi]>=0.1.5.24 fastapi uvicorn diff --git a/docker/checks/requirements.txt b/docker/checks/requirements.txt index 48f95c3..2b5eb1f 100644 --- a/docker/checks/requirements.txt +++ b/docker/checks/requirements.txt @@ -27,7 +27,9 @@ django==5.2.5 \ fastapi==0.116.1 \ --hash=sha256:c46ac7c312df840f0c9e220f7964bada936781bc4e2e6eb71f1c4d7553786565 \ --hash=sha256:ed52cbf946abfd70c5a0dccb24673f0670deeb517a88b3544d03c2a6bf283143 - # via -r requirements.in + # via + # -r requirements.in + # online-fxreader-pr34 h11==0.16.0 \ --hash=sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1 \ --hash=sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86 @@ -147,8 +149,8 @@ mypy-extensions==1.1.0 \ --hash=sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505 \ --hash=sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558 # via mypy -online-fxreader-pr34==0.1.5.24 \ - --hash=sha256:5e6ad1193bbdb859c2f0b1b407ccf944880212fd339139beede1eb31b4b2ce8a +online-fxreader-pr34==0.1.5.26 \ + --hash=sha256:3066558ef4a5dd2dfda85c3d9fb7d50014774d500187da86c6cc0795fca3be10 # via -r requirements.in pathspec==0.12.1 \ --hash=sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08 \ @@ -371,7 +373,48 @@ typing-inspection==0.4.1 \ uvicorn==0.35.0 \ --hash=sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a \ --hash=sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01 - # via -r requirements.in + # via + # -r requirements.in + # online-fxreader-pr34 +uvloop==0.21.0 \ + --hash=sha256:0878c2640cf341b269b7e128b1a5fed890adc4455513ca710d77d5e93aa6d6a0 \ + --hash=sha256:10d66943def5fcb6e7b37310eb6b5639fd2ccbc38df1177262b0640c3ca68c1f \ + --hash=sha256:10da8046cc4a8f12c91a1c39d1dd1585c41162a15caaef165c2174db9ef18bdc \ + --hash=sha256:17df489689befc72c39a08359efac29bbee8eee5209650d4b9f34df73d22e414 \ + --hash=sha256:183aef7c8730e54c9a3ee3227464daed66e37ba13040bb3f350bc2ddc040f22f \ + --hash=sha256:196274f2adb9689a289ad7d65700d37df0c0930fd8e4e743fa4834e850d7719d \ + --hash=sha256:221f4f2a1f46032b403bf3be628011caf75428ee3cc204a22addf96f586b19fd \ + --hash=sha256:2d1f581393673ce119355d56da84fe1dd9d2bb8b3d13ce792524e1607139feff \ + --hash=sha256:359ec2c888397b9e592a889c4d72ba3d6befba8b2bb01743f72fffbde663b59c \ + --hash=sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3 \ + --hash=sha256:4509360fcc4c3bd2c70d87573ad472de40c13387f5fda8cb58350a1d7475e58d \ + --hash=sha256:460def4412e473896ef179a1671b40c039c7012184b627898eea5072ef6f017a \ + --hash=sha256:461d9ae6660fbbafedd07559c6a2e57cd553b34b0065b6550685f6653a98c1cb \ + --hash=sha256:46923b0b5ee7fc0020bef24afe7836cb068f5050ca04caf6b487c513dc1a20b2 \ + --hash=sha256:53e420a3afe22cdcf2a0f4846e377d16e718bc70103d7088a4f7623567ba5fb0 \ + --hash=sha256:5ee4d4ef48036ff6e5cfffb09dd192c7a5027153948d85b8da7ff705065bacc6 \ + --hash=sha256:67dd654b8ca23aed0a8e99010b4c34aca62f4b7fce88f39d452ed7622c94845c \ + --hash=sha256:787ae31ad8a2856fc4e7c095341cccc7209bd657d0e71ad0dc2ea83c4a6fa8af \ + --hash=sha256:86975dca1c773a2c9864f4c52c5a55631038e387b47eaf56210f873887b6c8dc \ + --hash=sha256:87c43e0f13022b998eb9b973b5e97200c8b90823454d4bc06ab33829e09fb9bb \ + --hash=sha256:88cb67cdbc0e483da00af0b2c3cdad4b7c61ceb1ee0f33fe00e09c81e3a6cb75 \ + --hash=sha256:8a375441696e2eda1c43c44ccb66e04d61ceeffcd76e4929e527b7fa401b90fb \ + --hash=sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553 \ + --hash=sha256:b9fb766bb57b7388745d8bcc53a359b116b8a04c83a2288069809d2b3466c37e \ + --hash=sha256:baa0e6291d91649c6ba4ed4b2f982f9fa165b5bbd50a9e203c416a2797bab3c6 \ + --hash=sha256:baa4dcdbd9ae0a372f2167a207cd98c9f9a1ea1188a8a526431eef2f8116cc8d \ + --hash=sha256:bc09f0ff191e61c2d592a752423c767b4ebb2986daa9ed62908e2b1b9a9ae206 \ + --hash=sha256:bd53ecc9a0f3d87ab847503c2e1552b690362e005ab54e8a48ba97da3924c0dc \ + --hash=sha256:bfd55dfcc2a512316e65f16e503e9e450cab148ef11df4e4e679b5e8253a5281 \ + --hash=sha256:c097078b8031190c934ed0ebfee8cc5f9ba9642e6eb88322b9958b649750f72b \ + --hash=sha256:c0f3fa6200b3108919f8bdabb9a7f87f20e7097ea3c543754cabc7d717d95cf8 \ + --hash=sha256:e678ad6fe52af2c58d2ae3c73dc85524ba8abe637f134bf3564ed07f555c5e79 \ + --hash=sha256:ec7e6b09a6fdded42403182ab6b832b71f4edaf7f37a9a0e371a01db5f0cb45f \ + --hash=sha256:f0ce1b49560b1d2d8a2977e3ba4afb2414fb46b86a1b64056bc4ab929efdafbe \ + --hash=sha256:f38b2e090258d051d68a5b14d1da7203a3c3677321cf32a95a6f4db4dd8b6f26 \ + --hash=sha256:f3df876acd7ec037a3d005b3ab85a7e4110422e4d9c1571d4fc89b0fc41b6816 \ + --hash=sha256:f7089d2dc73179ce5ac255bdf37c236a9f914b264825fdaacaded6990a7fb4c2 + # via online-fxreader-pr34 xmltodict==0.14.2 \ --hash=sha256:201e7c28bb210e374999d1dde6382923ab0ed1a8a5faeece48ab525b7810a553 \ --hash=sha256:20cc7d723ed729276e808f26fb6b3599f786cbc37e06c65e192ba77c40f20aac diff --git a/docker/checks/rest.py b/docker/checks/rest.py index f851293..2151cc5 100644 --- a/docker/checks/rest.py +++ b/docker/checks/rest.py @@ -1,7 +1,13 @@ +import fastapi from online.fxreader.pr34.commands_typed import metrics def main() -> None: raise NotImplementedError +def get_router() -> fastapi.APIRouter: + router = fastapi.APIRouter() + + return router + if __name__ == '__main__': main() diff --git a/python/meson.build b/python/meson.build index 2ccb7e7..592472b 100644 --- a/python/meson.build +++ b/python/meson.build @@ -5,7 +5,7 @@ project( ).stdout().strip('\n'), # 'online.fxreader.uv', # ['c', 'cpp'], - version: '0.1.5.24', + version: '0.1.5.26', # default_options: [ # 'cpp_std=c++23', # # 'prefer_static=true', diff --git a/python/online/fxreader/pr34/commands_typed/async_api/fastapi.py b/python/online/fxreader/pr34/commands_typed/async_api/fastapi.py index 4d16bd2..b75cc47 100644 --- a/python/online/fxreader/pr34/commands_typed/async_api/fastapi.py +++ b/python/online/fxreader/pr34/commands_typed/async_api/fastapi.py @@ -31,13 +31,7 @@ def create_app() -> fastapi.FastAPI: 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 - ) - )() + app_router = cast(Callable[[], Any], getattr(importlib.import_module(app_module), app_method))() assert isinstance(app_router, fastapi.APIRouter) diff --git a/python/online/fxreader/pr34/commands_typed/async_api/settings.py b/python/online/fxreader/pr34/commands_typed/async_api/settings.py new file mode 100644 index 0000000..d806260 --- /dev/null +++ b/python/online/fxreader/pr34/commands_typed/async_api/settings.py @@ -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 diff --git a/python/pyproject.toml b/python/pyproject.toml index 02c5ebc..fdeff17 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -27,11 +27,17 @@ dependencies = [ [project.optional-dependencies] crypto = [ - 'cryptography', + 'cryptography', ] django = [ - 'django', + 'django', +] + +fastapi = [ + 'uvicorn', + 'fastapi', + 'uvloop', ] early = [ @@ -47,6 +53,8 @@ lint = [ 'django-stubs', 'pyright', 'ruff', + 'fastapi', + 'uvicorn', 'pip==25.1', # 'tomlkit', ] diff --git a/python/requirements.txt b/python/requirements.txt index 7724b90..254c85a 100644 --- a/python/requirements.txt +++ b/python/requirements.txt @@ -1,9 +1,13 @@ # This file was autogenerated by uv via the following command: -# uv pip compile --generate-hashes -o /home/nartes/Documents/current/freelance-project-34-marketing-blog/python/requirements.txt /tmp/requirementsb22u9wh4.in +# uv pip compile --generate-hashes -o /home/nartes/Documents/current/freelance-project-34-marketing-blog/python/requirements.txt /tmp/requirements6816cy66.in annotated-types==0.7.0 \ --hash=sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53 \ --hash=sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89 # via pydantic +anyio==4.10.0 \ + --hash=sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6 \ + --hash=sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1 + # via starlette argcomplete==3.6.2 \ --hash=sha256:65b3133a29ad53fb42c48cf5114752c7ab66c1c38544fdf6460f450c09b42591 \ --hash=sha256:d0519b1bc867f5f4f4713c41ad0aba73a4a5f007449716b16f385f2166dc6adf @@ -15,7 +19,7 @@ asgiref==3.9.1 \ build==1.3.0 \ --hash=sha256:698edd0ea270bde950f53aed21f3a0135672206f3911e0176261a31e0e07b397 \ --hash=sha256:7145f0b5061ba90a1500d60bd1b13ca0a8a4cebdd0cc16ed8adf1c0e739f43b4 - # via -r /tmp/requirementsb22u9wh4.in + # via -r /tmp/requirements6816cy66.in cffi==1.17.1 \ --hash=sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8 \ --hash=sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2 \ @@ -85,6 +89,10 @@ cffi==1.17.1 \ --hash=sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87 \ --hash=sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b # via cryptography +click==8.2.1 \ + --hash=sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202 \ + --hash=sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b + # via uvicorn cryptography==45.0.6 \ --hash=sha256:00e8724bdad672d75e6f069b27970883179bd472cd24a63f6e620ca7e41cc0c5 \ --hash=sha256:048e7ad9e08cf4c0ab07ff7f36cc3115924e22e2266e034450a890d9e312dd74 \ @@ -123,7 +131,7 @@ cryptography==45.0.6 \ --hash=sha256:f4028f29a9f38a2025abedb2e409973709c660d44319c61762202206ed577c42 \ --hash=sha256:f68f833a9d445cc49f01097d95c83a850795921b3f7cc6488731e69bde3288da \ --hash=sha256:fc022c1fa5acff6def2fc6d7819bbbd31ccddfe67d075331a65d9cfb28a20983 - # via -r /tmp/requirementsb22u9wh4.in + # via -r /tmp/requirements6816cy66.in django==5.2.5 \ --hash=sha256:0745b25681b129a77aae3d4f6549b62d3913d74407831abaa0d9021a03954bae \ --hash=sha256:2b2ada0ee8a5ff743a40e2b9820d1f8e24c11bac9ae6469cd548f0057ea6ddcd @@ -133,78 +141,90 @@ django==5.2.5 \ django-stubs==5.2.2 \ --hash=sha256:2a04b510c7a812f88223fd7e6d87fb4ea98717f19c8e5c8b59691d83ad40a8a6 \ --hash=sha256:79bd0fdbc78958a8f63e0b062bd9d03f1de539664476c0be62ade5f063c9e41e - # via -r /tmp/requirementsb22u9wh4.in + # via -r /tmp/requirements6816cy66.in django-stubs-ext==5.2.2 \ --hash=sha256:8833bbe32405a2a0ce168d3f75a87168f61bd16939caf0e8bf173bccbd8a44c5 \ --hash=sha256:d9d151b919fe2438760f5bd938f03e1cb08c84d0651f9e5917f1313907e42683 # via django-stubs -marisa-trie==1.3.0 \ - --hash=sha256:0111d6067c5a52141585a9213e073aa0d0438ba1c6febc40f827c5cadd3aa5d8 \ - --hash=sha256:034e483bd35ab6d136d8a91f43088dc78549394cf3787fdeebca144e2e4c82df \ - --hash=sha256:04bf4a128d8ec1881477364269034df620ebcec0ab0fd54bf2c5ee4779df10fe \ - --hash=sha256:05ba1011626d8845643a29449e1de5faed01e9e2b261825ac67a9675ce7f7426 \ - --hash=sha256:06ad6722d6d3f3be1f1a9b2b61afe8836e37d9f7ac4d23ebeb4b1acb043b2559 \ - --hash=sha256:0ec9d7fa8e16eb2399b9ab5677bca5fcca3dbc58f0b285f158c2da5fb79080d4 \ - --hash=sha256:10767b992ab20d24d8e97b54f89c5b0149e979d10bf88bb0151bee99f0f996a3 \ - --hash=sha256:10dce1641ef253eec9db7c5931763643b81d39e9d9e45c537d4739b6a09856f9 \ - --hash=sha256:10e4722fdb7b87ccf9ca279c7f7d8a2ed5b64934b9cd36cbcd5cdca81365db4d \ - --hash=sha256:1a56cc700b1405cc75fde9197f9d2fed66ecbbaee7bdf1f28728494f119dc7f3 \ - --hash=sha256:22a9140ffc7a82855bb41d6140e77c658d6a2abbf613b227adb1b786f53962ec \ - --hash=sha256:2379030b1339a38110509cd1f4d8ecbe6647c5df85eccc7f2133bcdc55855082 \ - --hash=sha256:284354853d5292b722abe4bfb9fbfff8015e9edd9462b097072875ed8c99e0d6 \ - --hash=sha256:28bfd6fada6c87cb31d300bbed5de1bfd338f8c98d1b834cf810a06ce019a020 \ - --hash=sha256:2e598970f95c9bb7f4f5a27d5e11ec2babfac1f737910395009a1753283f15dd \ - --hash=sha256:31c891ebce899f35936d4ab9f332b69ab762513d5944b0f43f61427e53671d42 \ - --hash=sha256:31ca1258ec765f47e4df6b46cdb562caff762a9126ab72276415bca1b34d1a16 \ - --hash=sha256:324ca8b80f76016fc459e1c2b6cab8df12e4fd43830700c7290650651f71f662 \ - --hash=sha256:39af3060b4ab41a3cce18b1808338db8bf50b6ec4b81be3cc452558aaad95581 \ - --hash=sha256:3b3a3a8b5b2ee26fa72e6c92a7b31731f79c1f81e7c0a2041e8e6b5d19497bac \ - --hash=sha256:3bd0af8668d0858f174085fcac5062d38a44ee35a230fb211e7164d791ac07c3 \ - --hash=sha256:4570850d9b6e6a099797f731652dbe764dfd6dd7eff2934318a7018ba1a82cf1 \ - --hash=sha256:548b9b020a6c5ed210e13f706b9fb1d097cfc510c1a02e757ea0d61bdcf17c80 \ - --hash=sha256:58f1b70501c2462583bce5639a65af5516e9785ae6b3158533ddeecde70f0675 \ - --hash=sha256:5b37b55dd120b6dad14ee4cdab5f57dafb1a937decf148f67d13df3392e421a9 \ - --hash=sha256:5c6f0c01c3853c3cc65f7b7db1c1ce3181f7479a2cc4de145fae53db3cc5193b \ - --hash=sha256:5d72ffde56fb1515bcb03539803d42d0a119f6782c5812bf2b7313eddc691735 \ - --hash=sha256:5e5acc03e489201b26a98251d0e8eedca43a32ab2bc1840a6cd5e8b918e193a3 \ - --hash=sha256:608d965d47f40b8cd402215b95d85db899268d277ae5b8ebe87b7acdd3e2a0bb \ - --hash=sha256:644e64763617b346bb66bdaa7a286bedc888cd2afa8f3b0219de62f996c701bc \ - --hash=sha256:6482ab865261164b6577c5016b3d8a14ba1baf966945e203d78d7994702d45e4 \ - --hash=sha256:6a1f0781bccd854184a9c59b095ed09adf16627460eb8df4a91dc3f87e882352 \ - --hash=sha256:6e8e2f1394eecfb780a25950849d64a799b79f538d17945e42b1652da4e0cae4 \ - --hash=sha256:714dabb0ddd4be72841c962d0559d5a80613964dc2a5db72651ae3b2ae3408fc \ - --hash=sha256:80bf10d0d2a19bdbc1fe1174a2887dcdaaba857218d3d627adea9045a54f5a17 \ - --hash=sha256:80f158464e05d6e063abaebfb8811f48333e2337605d852ae9065d442b637dd0 \ - --hash=sha256:8b39a7314f6ad141c9c24acff0a71f4fdae1eab5ea827468c40afafc0662cab3 \ - --hash=sha256:8fc98a5362a25c27c1372af68253ba19ec0b27f1423fce307516257458bcf778 \ - --hash=sha256:9079d9d88921e46de1b65214d28608974dfcac2b49ee74f03807dc03e9d0da20 \ - --hash=sha256:9210446587d3daa40c2fe808b966a80e03995eeb6688c475b77276200524f0a0 \ - --hash=sha256:932b0101cf39d20afc07d71726b709376cbaf06316e4ce5008e2c1c21c9a925d \ - --hash=sha256:938e6e9ed7675a0a2c520926897c02126749e12a6cb6c2e7c910e7ea83aa40f3 \ - --hash=sha256:938f618d2cece8358899c688591d94db6652d9e1076c15a7efdfcfdc64a96cdb \ - --hash=sha256:989ba916e7747817b6fd2c46f2d40371ab3adaf026c1e6b4cded251ce1768ae4 \ - --hash=sha256:9a6a18176b283950c7f6c4c0952c3bb8b4430e5b38d645a0d96f12ff8c650a73 \ - --hash=sha256:9c7631f8442a4407b72a150089b6b804fbc06c4494ff45c96c4469e44aaf0003 \ - --hash=sha256:a1b34336cd3a7bc84d29ca6da4f38e6845b83cb18b38362f967b0a3096847ec2 \ - --hash=sha256:a22e8e3b82533fc71fa34d28e3563e72e7863810c786a8e3c350ede0fe3f4ad7 \ - --hash=sha256:a6e9b4cec99935cbc339d3896852c045605dd65910e8c534998d751113a0f767 \ - --hash=sha256:b71462677dc6c119589755394086cffbcf4d4d42f906fefb325c982c679406d6 \ - --hash=sha256:bd53e6b99008ff3dab6455791800af405351d98fbf01c4f474642afb1499236d \ - --hash=sha256:c27bde381c46574f3f534b4a62c42485e80e0e26c127899f83a391dd2c2bf078 \ - --hash=sha256:cba78321fae9b825f2bfcb2c3f66f60ab773777a8d2fcb34468daac657e0fc48 \ - --hash=sha256:cc6ea03831be59a50dbe7afc3691fa3cc8f0c6a1af48e98eccb749cbe03a5414 \ - --hash=sha256:d33818e5ece65da895d2262519abd752b3ef96245ae977ebe970f5a0631bcb83 \ - --hash=sha256:d6bb4a231d12b4e58d4f7250a8491f529ca41ef2171d3fa15fba13dce3c2efff \ - --hash=sha256:d85a0484f8ecd3a6c843c1b10b42953f14278b35ce30d94bc7cb6305604a6109 \ - --hash=sha256:dc6a1cca4ad5bead99efde0079605bc059f856b00be9b58b0f5978665ece7bb9 \ - --hash=sha256:e2dd0868d3695c742166b7922608f9c5bbf89f536c2144743ca5a62a24290a08 \ - --hash=sha256:e79b517386135eb84c3459805047bfb173df2763b1aa322a66864f13d620bd83 \ - --hash=sha256:e945c78652b01720d419051cf37642165878abb182d555f99390c7d36cec6152 \ - --hash=sha256:ea63c74aa88d0dc24464bc356bc31625318e58b5dd20169d98e696baa3f91ffd \ - --hash=sha256:ee193c1f26d9a10bbc56b9bd1e3b16c79ed0e0e44387275f8054d4cf853804d1 \ - --hash=sha256:f03cea2fabebf4f1429ccb87c4037dacd828050e8829cacb233f0865bda4244e \ - --hash=sha256:f44e0c0c339fe44dd3e7fcbab91cc1a5888c12c35a8bf2811b3eb85236570b29 - # via -r /tmp/requirementsb22u9wh4.in +fastapi==0.116.1 \ + --hash=sha256:c46ac7c312df840f0c9e220f7964bada936781bc4e2e6eb71f1c4d7553786565 \ + --hash=sha256:ed52cbf946abfd70c5a0dccb24673f0670deeb517a88b3544d03c2a6bf283143 + # via -r /tmp/requirements6816cy66.in +h11==0.16.0 \ + --hash=sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1 \ + --hash=sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86 + # via uvicorn +idna==3.10 \ + --hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \ + --hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3 + # via anyio +marisa-trie==1.3.1 \ + --hash=sha256:076731f79f8603cb3216cb6e5bbbc56536c89f63f175ad47014219ecb01e5996 \ + --hash=sha256:0b9816ab993001a7854b02a7daec228892f35bd5ab0ac493bacbd1b80baec9f1 \ + --hash=sha256:0c2bc6bee737f4d47fce48c5b03a7bd3214ef2d83eb5c9f84210091370a5f195 \ + --hash=sha256:0dcd42774e367ceb423c211a4fc8e7ce586acfaf0929c9c06d98002112075239 \ + --hash=sha256:0e6f3b45def6ff23e254eeaa9079267004f0069d0a34eba30a620780caa4f2cb \ + --hash=sha256:137010598d8cebc53dbfb7caf59bde96c33a6af555e3e1bdbf30269b6a157e1e \ + --hash=sha256:2f7c10f69cbc3e6c7d715ec9cb0c270182ea2496063bebeda873f4aa83fd9910 \ + --hash=sha256:3715d779561699471edde70975e07b1de7dddb2816735d40ed16be4b32054188 \ + --hash=sha256:3834304fdeaa1c9b73596ad5a6c01a44fc19c13c115194704b85f7fbdf0a7b8e \ + --hash=sha256:389721481c14a92fa042e4b91ae065bff13e2bc567c85a10aa9d9de80aaa8622 \ + --hash=sha256:3a96ef3e461ecc85ec7d2233ddc449ff5a3fbdc520caea752bc5bc8faa975231 \ + --hash=sha256:3e2a0e1be95237981bd375a388f44b33d69ea5669a2f79fea038e45fff326595 \ + --hash=sha256:3e431f9c80ee1850b2a406770acf52c058b97a27968a0ed6aca45c2614d64c9f \ + --hash=sha256:47631614c5243ed7d15ae0af8245fcc0599f5b7921fae2a4ae992afb27c9afbb \ + --hash=sha256:52d1764906befef91886e3bff374d8090c9716822bd56b70e07aa697188090b7 \ + --hash=sha256:5370f9ef6c008e502537cc1ff518c80ddf749367ce90179efa0e7f6275903a76 \ + --hash=sha256:56043cf908ddf3d7364498085dbc2855d4ea8969aff3bf2439a79482a79e68e2 \ + --hash=sha256:5a6abc9573a6a45d09548fde136dbcd4260b8c56f8dff443eaa565352d7cca59 \ + --hash=sha256:5b7c1e7fa6c3b855e8cfbabf38454d7decbaba1c567d0cd58880d033c6b363bd \ + --hash=sha256:5ef045f694ef66079b4e00c4c9063a00183d6af7d1ff643de6ea5c3b0d9af01b \ + --hash=sha256:68678816818efcd4a1787b557af81f215b989ec88680a86c85c34c914d413690 \ + --hash=sha256:6cac19952e0e258ded765737d1fb11704fe81bf4f27526638a5d44496f329235 \ + --hash=sha256:70b4c96f9119cfeb4dc6a0cf4afc9f92f0b002cde225bcd910915d976c78e66a \ + --hash=sha256:7e957aa4251a8e70b9fe02a16b2d190f18787902da563cb7ba865508b8e8fb04 \ + --hash=sha256:82de2de90488d0fbbf74cf9f20e1afd62e320693b88f5e9565fc80b28f5bbad3 \ + --hash=sha256:83a3748088d117a9b15d8981c947df9e4f56eb2e4b5456ae34fe1f83666c9185 \ + --hash=sha256:83efc045fc58ca04c91a96c9b894d8a19ac6553677a76f96df01ff9f0405f53d \ + --hash=sha256:8c8b2386d2d22c57880ed20a913ceca86363765623175671137484a7d223f07a \ + --hash=sha256:8f81344d212cb41992340b0b8a67e375f44da90590b884204fd3fa5e02107df2 \ + --hash=sha256:954fef9185f8a79441b4e433695116636bf66402945cfee404f8983bafa59788 \ + --hash=sha256:9651daa1fdc471df5a5fa6a4833d3b01e76ac512eea141a5995681aebac5555f \ + --hash=sha256:9688c7b45f744366a4ef661e399f24636ebe440d315ab35d768676c59c613186 \ + --hash=sha256:97107fd12f30e4f8fea97790343a2d2d9a79d93697fe14e1b6f6363c984ff85b \ + --hash=sha256:9868b7a8e0f648d09ffe25ac29511e6e208cc5fb0d156c295385f9d5dc2a138e \ + --hash=sha256:986eaf35a7f63c878280609ecd37edf8a074f7601c199acfec81d03f1ee9a39a \ + --hash=sha256:99a00cab4cf9643a87977c87a5c8961aa44fff8d5dd46e00250135f686e7dedf \ + --hash=sha256:9c56001badaf1779afae5c24b7ab85938644ab8ef3c5fd438ab5d49621b84482 \ + --hash=sha256:9dc61fb8f8993589544f6df268229c6cf0a56ad4ed3e8585a9cd23c5ad79527b \ + --hash=sha256:9de573d933db4753a50af891bcb3ffbfe14e200406214c223aa5dfe2163f316d \ + --hash=sha256:9e467e13971c64db6aed8afe4c2a131c3f73f048bec3f788a6141216acda598d \ + --hash=sha256:9e6496bbad3068e3bbbb934b1e1307bf1a9cb4609f9ec47b57e8ea37f1b5ee40 \ + --hash=sha256:9f92d3577c72d5a97af5c8e3d98247b79c8ccfb64ebf611311dcf631b11e5604 \ + --hash=sha256:a1c6990961d1177f6d8fdf7b610fa2e7c0c02743a090d173f6dfa9dc9231c73c \ + --hash=sha256:a5a0a58ffe2a7eb3f870214c6df8f9a43ce768bd8fed883e6ba8c77645666b63 \ + --hash=sha256:a7416f1a084eb889c5792c57317875aeaa86abfe0bdc6f167712cebcec1d36ee \ + --hash=sha256:a83f5f7ae3494e0cc25211296252b1b86901c788ed82c83adda19d0c98f828d6 \ + --hash=sha256:a850b151bd1e3a5d9afef113adc22727d696603659d575d7e84f994bd8d04bf1 \ + --hash=sha256:ad82ab8a58562cf69e6b786debcc7638b28df12f9f1c7bcffb07efb5c1f09cbd \ + --hash=sha256:b173ec46d521308f7c97d96d6e05cf2088e0548f82544ec9a8656af65593304d \ + --hash=sha256:bf9f2b97fcfd5e2dbb0090d0664023872dcde990df0b545eca8d0ce95795a409 \ + --hash=sha256:c12b44c190deb0d67655021da1f2d0a7d61a257bf844101cf982e68ed344f28d \ + --hash=sha256:c6571462417cda2239b1ade86ceaf3852da9b52c6286046e87d404afc6da20a7 \ + --hash=sha256:c785fd6dae9daa6825734b7b494cdac972f958be1f9cb3fb1f32be8598d2b936 \ + --hash=sha256:c7a33506d0451112911c69f38d55da3e0e050f2be0ea4e5176865cf03baf26a9 \ + --hash=sha256:c89df75aefe1ad7e613340790130f1badc5926bcfa66a6b3c9471071002956a5 \ + --hash=sha256:ca644534f15f85bba14c412afc17de07531e79a766ce85b8dbf3f8b6e7758f20 \ + --hash=sha256:cbd28f95d5f30d9a7af6130869568e75bfd7ef2e0adfb1480f1f44480f5d3603 \ + --hash=sha256:d0f87bdf660f01e88ab3a507955697b2e3284065afa0b94fc9e77d6ad153ed5e \ + --hash=sha256:d4bd41a6e73c0d0adafe4de449b6d35530a4ce6a836a6ee839baf117785ecfd7 \ + --hash=sha256:d8d5e686db0ae758837ed29b3b742afb994d1a01ce10977eabd3490f16b5c9f9 \ + --hash=sha256:e5888b269e790356ce4525f3e8df1fe866d1497b7d7fb7548cfec883cb985288 \ + --hash=sha256:ec633e108f277f2b7f4671d933a909f39bba549910bf103e2940b87a14da2783 \ + --hash=sha256:ecdb19d33b26738a32602ef432b06cc6deeca4b498ce67ba8e5e39c8a7c19745 \ + --hash=sha256:ee428575377e29c636f2b4b3b0488875dcea310c6c5b3412ec4ef997f7bb37cc \ + --hash=sha256:f4bae4f920f2a1082eaf766c1883df7da84abdf333bafa15b8717c10416a615e + # via -r /tmp/requirements6816cy66.in meson==1.9.0 \ --hash=sha256:45e51ddc41e37d961582d06e78c48e0f9039011587f3495c4d6b0781dad92357 \ --hash=sha256:cd27277649b5ed50d19875031de516e270b22e890d9db65ed9af57d18ebc498d @@ -212,7 +232,7 @@ meson==1.9.0 \ meson-python==0.18.0 \ --hash=sha256:3b0fe051551cc238f5febb873247c0949cd60ded556efa130aa57021804868e2 \ --hash=sha256:c56a99ec9df669a40662fe46960321af6e4b14106c14db228709c1628e23848d - # via -r /tmp/requirementsb22u9wh4.in + # via -r /tmp/requirements6816cy66.in mypy==1.17.1 \ --hash=sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341 \ --hash=sha256:064e2ff508e5464b4bd807a7c1625bc5047c5022b85c70f030680e18f37273a5 \ @@ -252,7 +272,7 @@ mypy==1.17.1 \ --hash=sha256:fa6ffadfbe6994d724c5a1bb6123a7d27dd68fc9c059561cd33b664a79578e14 \ --hash=sha256:feb8cc32d319edd5859da2cc084493b3e2ce5e49a946377663cc90f6c15fb259 \ --hash=sha256:ff2933428516ab63f961644bc49bc4cbe42bbffb2cd3b71cc7277c07d16b1a8b - # via -r /tmp/requirementsb22u9wh4.in + # via -r /tmp/requirements6816cy66.in mypy-extensions==1.1.0 \ --hash=sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505 \ --hash=sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558 @@ -336,7 +356,7 @@ numpy==2.3.2 \ --hash=sha256:fb1752a3bb9a3ad2d6b090b88a9a0ae1cd6f004ef95f75825e2f382c183b2097 \ --hash=sha256:fc927d7f289d14f5e037be917539620603294454130b6de200091e23d27dc9be \ --hash=sha256:fed5527c4cf10f16c6d0b6bee1f89958bccb0ad2522c8cadc2efd318bcd545f5 - # via -r /tmp/requirementsb22u9wh4.in + # via -r /tmp/requirements6816cy66.in packaging==25.0 \ --hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \ --hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f @@ -351,11 +371,11 @@ pathspec==0.12.1 \ pip==25.1 \ --hash=sha256:13b4aa0aaad055020a11bec8a1c2a70a2b2d080e12d89b962266029fff0a16ba \ --hash=sha256:272bdd1289f80165e9070a4f881e8f9e1001bbb50378561d1af20e49bf5a2200 - # via -r /tmp/requirementsb22u9wh4.in + # via -r /tmp/requirements6816cy66.in pybind11==3.0.1 \ --hash=sha256:9c0f40056a016da59bab516efb523089139fcc6f2ba7e4930854c61efb932051 \ --hash=sha256:aa8f0aa6e0a94d3b64adfc38f560f33f15e589be2175e103c0a33c6bce55ee89 - # via -r /tmp/requirementsb22u9wh4.in + # via -r /tmp/requirements6816cy66.in pycparser==2.22 \ --hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \ --hash=sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc @@ -364,7 +384,8 @@ pydantic==2.11.7 \ --hash=sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db \ --hash=sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b # via - # -r /tmp/requirementsb22u9wh4.in + # -r /tmp/requirements6816cy66.in + # fastapi # pydantic-settings pydantic-core==2.33.2 \ --hash=sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d \ @@ -470,7 +491,7 @@ pydantic-core==2.33.2 \ pydantic-settings==2.10.1 \ --hash=sha256:06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee \ --hash=sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796 - # via -r /tmp/requirementsb22u9wh4.in + # via -r /tmp/requirements6816cy66.in pyproject-hooks==1.2.0 \ --hash=sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8 \ --hash=sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913 @@ -482,7 +503,7 @@ pyproject-metadata==0.9.1 \ pyright==1.1.404 \ --hash=sha256:455e881a558ca6be9ecca0b30ce08aa78343ecc031d37a198ffa9a7a1abeb63e \ --hash=sha256:c7b7ff1fdb7219c643079e4c3e7d4125f0dafcc19d253b47e898d130ea426419 - # via -r /tmp/requirementsb22u9wh4.in + # via -r /tmp/requirements6816cy66.in python-dotenv==1.1.1 \ --hash=sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc \ --hash=sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab @@ -562,15 +583,23 @@ ruff==0.12.10 \ --hash=sha256:e67d96827854f50b9e3e8327b031647e7bcc090dbe7bb11101a81a3a2cbf1cc9 \ --hash=sha256:ebb7333a45d56efc7c110a46a69a1b32365d5c5161e7244aaf3aa20ce62399c1 \ --hash=sha256:f3fc21178cd44c98142ae7590f42ddcb587b8e09a3b849cbc84edb62ee95de60 - # via -r /tmp/requirementsb22u9wh4.in + # via -r /tmp/requirements6816cy66.in setuptools==80.9.0 \ --hash=sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922 \ --hash=sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c - # via -r /tmp/requirementsb22u9wh4.in + # via -r /tmp/requirements6816cy66.in +sniffio==1.3.1 \ + --hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \ + --hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc + # via anyio sqlparse==0.5.3 \ --hash=sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272 \ --hash=sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca # via django +starlette==0.47.3 \ + --hash=sha256:6bc94f839cc176c4858894f1f8908f0ab79dfec1a6b8402f6da9be26ebea52e9 \ + --hash=sha256:89c0778ca62a76b826101e7c709e70680a1699ca7da6b44d38eb0a7e61fe4b51 + # via fastapi tomli==2.2.1 \ --hash=sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6 \ --hash=sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd \ @@ -604,17 +633,17 @@ tomli==2.2.1 \ --hash=sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272 \ --hash=sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a \ --hash=sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7 - # via -r /tmp/requirementsb22u9wh4.in + # via -r /tmp/requirements6816cy66.in tomlkit==0.13.3 \ --hash=sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1 \ --hash=sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0 # via - # -r /tmp/requirementsb22u9wh4.in + # -r /tmp/requirements6816cy66.in # yq tomlq==0.1.0 \ --hash=sha256:4b966fd999ed2bf69081b7c7f5caadbc4c9542d0ed5fcf2e9b7b4d8d7ada3c82 \ --hash=sha256:e775720e90da3e405142b9fe476145e71c0389f787b1ff9933f92a1704d8c6e7 - # via -r /tmp/requirementsb22u9wh4.in + # via -r /tmp/requirements6816cy66.in types-pyyaml==6.0.12.20250822 \ --hash=sha256:1fe1a5e146aa315483592d292b72a172b65b946a6d98aa6ddd8e4aa838ab7098 \ --hash=sha256:259f1d93079d335730a9db7cff2bcaf65d7e04b4a56b5927d49a612199b59413 @@ -625,6 +654,7 @@ typing-extensions==4.15.0 \ # via # django-stubs # django-stubs-ext + # fastapi # mypy # pydantic # pydantic-core @@ -656,7 +686,11 @@ uv==0.8.13 \ --hash=sha256:d22fa55580b224779279b98e0b23cbc45e51837e1fac616d7c5d03aff668a998 \ --hash=sha256:eb90089624d92d57b8582f708973db8988e09dba6bae83991dba20731d82eb6a \ --hash=sha256:f6c508aa9c5210577008e1919b532e38356fe68712179399f00462b3e78fd845 - # via -r /tmp/requirementsb22u9wh4.in + # via -r /tmp/requirements6816cy66.in +uvicorn==0.35.0 \ + --hash=sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a \ + --hash=sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01 + # via -r /tmp/requirements6816cy66.in xmltodict==0.14.2 \ --hash=sha256:201e7c28bb210e374999d1dde6382923ab0ed1a8a5faeece48ab525b7810a553 \ --hash=sha256:20cc7d723ed729276e808f26fb6b3599f786cbc37e06c65e192ba77c40f20aac diff --git a/releases/whl/online_fxreader_pr34-0.1.5.25-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.25-py3-none-any.whl new file mode 100644 index 0000000..a1d2890 --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.25-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9b70a3fb2fe6652c1ff8c14e464a5baf80a5d9da9e39204eeba40490bf13deca +size 74822 diff --git a/releases/whl/online_fxreader_pr34-0.1.5.26-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.26-py3-none-any.whl new file mode 100644 index 0000000..58b307f --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.26-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3066558ef4a5dd2dfda85c3d9fb7d50014774d500187da86c6cc0795fca3be10 +size 74831 From 7f2f0cbda3b57e8c04f7b46c1afdacc538ad53cc Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Fri, 29 Aug 2025 11:45:23 +0300 Subject: [PATCH 18/70] [+] update checks service 1. make metrics in commands_typed be agnostic of fastapi, django; 2. implement ping wrapper for rest.py in checks; 3. use env settings to specify hosts to ping; 4. add pyright, ruff into Makefile; 5. test in production; --- docker-compose.yml | 2 + docker/checks/Makefile | 21 +++ ...ne_fxreader_pr34-0.1.5.27-py3-none-any.whl | 3 + docker/checks/pyproject.toml | 173 ++++++++++++++++++ docker/checks/requirements.in | 3 +- docker/checks/requirements.txt | 161 +++++++++++++++- docker/checks/rest.py | 94 +++++++++- python/meson.build | 2 +- .../fxreader/pr34/commands_typed/metrics.py | 12 +- ...ne_fxreader_pr34-0.1.5.27-py3-none-any.whl | 3 + 10 files changed, 461 insertions(+), 13 deletions(-) create mode 100644 docker/checks/deps/whl/online_fxreader_pr34-0.1.5.27-py3-none-any.whl create mode 100644 docker/checks/pyproject.toml create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.27-py3-none-any.whl diff --git a/docker-compose.yml b/docker-compose.yml index c118626..648732d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -41,6 +41,8 @@ services: - python3 - -m - online.fxreader.pr34.commands_typed.async_api.fastapi + ports: + - ${CHECKS_PORTS:-"127.0.0.1:80"}:80 cpanel: build: diff --git a/docker/checks/Makefile b/docker/checks/Makefile index da4dee0..75d3cbe 100644 --- a/docker/checks/Makefile +++ b/docker/checks/Makefile @@ -5,3 +5,24 @@ venv_compile: -f deps/whl \ requirements.in > \ requirements.txt + +venv: + uv venv -p 3.12 .venv + uv pip install \ + -p .venv/bin/python3 \ + -f deps/whl \ + -r requirements.txt + +PYRIGHT_CMD ?= --threads 3 + +pyright: + .venv/bin/python3 \ + -m pyright \ + --pythonpath .venv/bin/python3 \ + -p pyproject.toml \ + $(PYRIGHT_CMD) \ + . + +RUFF_CMD ?= format +ruff: + .venv/bin/python3 -m ruff --config pyproject.toml $(RUFF_CMD) . diff --git a/docker/checks/deps/whl/online_fxreader_pr34-0.1.5.27-py3-none-any.whl b/docker/checks/deps/whl/online_fxreader_pr34-0.1.5.27-py3-none-any.whl new file mode 100644 index 0000000..beaf03f --- /dev/null +++ b/docker/checks/deps/whl/online_fxreader_pr34-0.1.5.27-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d081758fdb91fb460da5c55d3a38257122de096363a8d956ba2f91a234566010 +size 74850 diff --git a/docker/checks/pyproject.toml b/docker/checks/pyproject.toml new file mode 100644 index 0000000..b45babb --- /dev/null +++ b/docker/checks/pyproject.toml @@ -0,0 +1,173 @@ +[project] +description = 'checks service' +requires-python = '>= 3.10' +maintainers = [ + { name = 'Siarhei Siniak', email = 'siarheisiniak@gmail.com' }, +] +classifiers = [ + 'Programming Language :: Python', +] + +name = 'online.fxreader.pr34.checks' + +[tool.ruff] +line-length = 160 +target-version = 'py310' +include = [ + '*.py', + '*/**/*.py', + '*/**/*.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 +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 = [ + '*/**/*.py', +] +extraPaths = [ + '.', +] + +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" + diff --git a/docker/checks/requirements.in b/docker/checks/requirements.in index ef12ab4..3d0e939 100644 --- a/docker/checks/requirements.in +++ b/docker/checks/requirements.in @@ -1,3 +1,4 @@ -online.fxreader.pr34[django,fastapi]>=0.1.5.24 +online.fxreader.pr34[django,fastapi,lint]>=0.1.5.24 fastapi uvicorn +numpy diff --git a/docker/checks/requirements.txt b/docker/checks/requirements.txt index 2b5eb1f..f6f82a9 100644 --- a/docker/checks/requirements.txt +++ b/docker/checks/requirements.txt @@ -23,7 +23,18 @@ click==8.2.1 \ django==5.2.5 \ --hash=sha256:0745b25681b129a77aae3d4f6549b62d3913d74407831abaa0d9021a03954bae \ --hash=sha256:2b2ada0ee8a5ff743a40e2b9820d1f8e24c11bac9ae6469cd548f0057ea6ddcd + # via + # django-stubs + # django-stubs-ext + # online-fxreader-pr34 +django-stubs==5.2.2 \ + --hash=sha256:2a04b510c7a812f88223fd7e6d87fb4ea98717f19c8e5c8b59691d83ad40a8a6 \ + --hash=sha256:79bd0fdbc78958a8f63e0b062bd9d03f1de539664476c0be62ade5f063c9e41e # via online-fxreader-pr34 +django-stubs-ext==5.2.2 \ + --hash=sha256:8833bbe32405a2a0ce168d3f75a87168f61bd16939caf0e8bf173bccbd8a44c5 \ + --hash=sha256:d9d151b919fe2438760f5bd938f03e1cb08c84d0651f9e5917f1313907e42683 + # via django-stubs fastapi==0.116.1 \ --hash=sha256:c46ac7c312df840f0c9e220f7964bada936781bc4e2e6eb71f1c4d7553786565 \ --hash=sha256:ed52cbf946abfd70c5a0dccb24673f0670deeb517a88b3544d03c2a6bf283143 @@ -149,8 +160,88 @@ mypy-extensions==1.1.0 \ --hash=sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505 \ --hash=sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558 # via mypy -online-fxreader-pr34==0.1.5.26 \ - --hash=sha256:3066558ef4a5dd2dfda85c3d9fb7d50014774d500187da86c6cc0795fca3be10 +nodeenv==1.9.1 \ + --hash=sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f \ + --hash=sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9 + # via pyright +numpy==2.3.2 \ + --hash=sha256:07b62978075b67eee4065b166d000d457c82a1efe726cce608b9db9dd66a73a5 \ + --hash=sha256:087ffc25890d89a43536f75c5fe8770922008758e8eeeef61733957041ed2f9b \ + --hash=sha256:092aeb3449833ea9c0bf0089d70c29ae480685dd2377ec9cdbbb620257f84631 \ + --hash=sha256:095737ed986e00393ec18ec0b21b47c22889ae4b0cd2d5e88342e08b01141f58 \ + --hash=sha256:0a4f2021a6da53a0d580d6ef5db29947025ae8b35b3250141805ea9a32bbe86b \ + --hash=sha256:103ea7063fa624af04a791c39f97070bf93b96d7af7eb23530cd087dc8dbe9dc \ + --hash=sha256:11e58218c0c46c80509186e460d79fbdc9ca1eb8d8aee39d8f2dc768eb781089 \ + --hash=sha256:122bf5ed9a0221b3419672493878ba4967121514b1d7d4656a7580cd11dddcbf \ + --hash=sha256:14a91ebac98813a49bc6aa1a0dfc09513dcec1d97eaf31ca21a87221a1cdcb15 \ + --hash=sha256:1f91e5c028504660d606340a084db4b216567ded1056ea2b4be4f9d10b67197f \ + --hash=sha256:20b8200721840f5621b7bd03f8dcd78de33ec522fc40dc2641aa09537df010c3 \ + --hash=sha256:240259d6564f1c65424bcd10f435145a7644a65a6811cfc3201c4a429ba79170 \ + --hash=sha256:2738534837c6a1d0c39340a190177d7d66fdf432894f469728da901f8f6dc910 \ + --hash=sha256:27c9f90e7481275c7800dc9c24b7cc40ace3fdb970ae4d21eaff983a32f70c91 \ + --hash=sha256:293b2192c6bcce487dbc6326de5853787f870aeb6c43f8f9c6496db5b1781e45 \ + --hash=sha256:2c3271cc4097beb5a60f010bcc1cc204b300bb3eafb4399376418a83a1c6373c \ + --hash=sha256:2f4f0215edb189048a3c03bd5b19345bdfa7b45a7a6f72ae5945d2a28272727f \ + --hash=sha256:3dcf02866b977a38ba3ec10215220609ab9667378a9e2150615673f3ffd6c73b \ + --hash=sha256:4209f874d45f921bde2cff1ffcd8a3695f545ad2ffbef6d3d3c6768162efab89 \ + --hash=sha256:448a66d052d0cf14ce9865d159bfc403282c9bc7bb2a31b03cc18b651eca8b1a \ + --hash=sha256:4ae6863868aaee2f57503c7a5052b3a2807cf7a3914475e637a0ecd366ced220 \ + --hash=sha256:4d002ecf7c9b53240be3bb69d80f86ddbd34078bae04d87be81c1f58466f264e \ + --hash=sha256:4e6ecfeddfa83b02318f4d84acf15fbdbf9ded18e46989a15a8b6995dfbf85ab \ + --hash=sha256:508b0eada3eded10a3b55725b40806a4b855961040180028f52580c4729916a2 \ + --hash=sha256:546aaf78e81b4081b2eba1d105c3b34064783027a06b3ab20b6eba21fb64132b \ + --hash=sha256:572d5512df5470f50ada8d1972c5f1082d9a0b7aa5944db8084077570cf98370 \ + --hash=sha256:5ad4ebcb683a1f99f4f392cc522ee20a18b2bb12a2c1c42c3d48d5a1adc9d3d2 \ + --hash=sha256:66459dccc65d8ec98cc7df61307b64bf9e08101f9598755d42d8ae65d9a7a6ee \ + --hash=sha256:6936aff90dda378c09bea075af0d9c675fe3a977a9d2402f95a87f440f59f619 \ + --hash=sha256:69779198d9caee6e547adb933941ed7520f896fd9656834c300bdf4dd8642712 \ + --hash=sha256:6f1ae3dcb840edccc45af496f312528c15b1f79ac318169d094e85e4bb35fdf1 \ + --hash=sha256:71669b5daae692189540cffc4c439468d35a3f84f0c88b078ecd94337f6cb0ec \ + --hash=sha256:72c6df2267e926a6d5286b0a6d556ebe49eae261062059317837fda12ddf0c1a \ + --hash=sha256:72dbebb2dcc8305c431b2836bcc66af967df91be793d63a24e3d9b741374c450 \ + --hash=sha256:754d6755d9a7588bdc6ac47dc4ee97867271b17cee39cb87aef079574366db0a \ + --hash=sha256:76c3e9501ceb50b2ff3824c3589d5d1ab4ac857b0ee3f8f49629d0de55ecf7c2 \ + --hash=sha256:7a0e27186e781a69959d0230dd9909b5e26024f8da10683bd6344baea1885168 \ + --hash=sha256:7d6e390423cc1f76e1b8108c9b6889d20a7a1f59d9a60cac4a050fa734d6c1e2 \ + --hash=sha256:8145dd6d10df13c559d1e4314df29695613575183fa2e2d11fac4c208c8a1f73 \ + --hash=sha256:8446acd11fe3dc1830568c941d44449fd5cb83068e5c70bd5a470d323d448296 \ + --hash=sha256:852ae5bed3478b92f093e30f785c98e0cb62fa0a939ed057c31716e18a7a22b9 \ + --hash=sha256:87c930d52f45df092f7578889711a0768094debf73cfcde105e2d66954358125 \ + --hash=sha256:8b1224a734cd509f70816455c3cffe13a4f599b1bf7130f913ba0e2c0b2006c0 \ + --hash=sha256:8dc082ea901a62edb8f59713c6a7e28a85daddcb67454c839de57656478f5b19 \ + --hash=sha256:906a30249315f9c8e17b085cc5f87d3f369b35fedd0051d4a84686967bdbbd0b \ + --hash=sha256:938065908d1d869c7d75d8ec45f735a034771c6ea07088867f713d1cd3bbbe4f \ + --hash=sha256:9c144440db4bf3bb6372d2c3e49834cc0ff7bb4c24975ab33e01199e645416f2 \ + --hash=sha256:9e196ade2400c0c737d93465327d1ae7c06c7cb8a1756121ebf54b06ca183c7f \ + --hash=sha256:a3ef07ec8cbc8fc9e369c8dcd52019510c12da4de81367d8b20bc692aa07573a \ + --hash=sha256:a7af9ed2aa9ec5950daf05bb11abc4076a108bd3c7db9aa7251d5f107079b6a6 \ + --hash=sha256:a9f66e7d2b2d7712410d3bc5684149040ef5f19856f20277cd17ea83e5006286 \ + --hash=sha256:aa098a5ab53fa407fded5870865c6275a5cd4101cfdef8d6fafc48286a96e981 \ + --hash=sha256:af58de8745f7fa9ca1c0c7c943616c6fe28e75d0c81f5c295810e3c83b5be92f \ + --hash=sha256:b05a89f2fb84d21235f93de47129dd4f11c16f64c87c33f5e284e6a3a54e43f2 \ + --hash=sha256:b5e40e80299607f597e1a8a247ff8d71d79c5b52baa11cc1cce30aa92d2da6e0 \ + --hash=sha256:b9d0878b21e3918d76d2209c924ebb272340da1fb51abc00f986c258cd5e957b \ + --hash=sha256:bc3186bea41fae9d8e90c2b4fb5f0a1f5a690682da79b92574d63f56b529080b \ + --hash=sha256:c63d95dc9d67b676e9108fe0d2182987ccb0f11933c1e8959f42fa0da8d4fa56 \ + --hash=sha256:c771cfac34a4f2c0de8e8c97312d07d64fd8f8ed45bc9f5726a7e947270152b5 \ + --hash=sha256:c8d9727f5316a256425892b043736d63e89ed15bbfe6556c5ff4d9d4448ff3b3 \ + --hash=sha256:cbc95b3813920145032412f7e33d12080f11dc776262df1712e1638207dde9e8 \ + --hash=sha256:cefc2219baa48e468e3db7e706305fcd0c095534a192a08f31e98d83a7d45fb0 \ + --hash=sha256:d95f59afe7f808c103be692175008bab926b59309ade3e6d25009e9a171f7036 \ + --hash=sha256:dd937f088a2df683cbb79dda9a772b62a3e5a8a7e76690612c2737f38c6ef1b6 \ + --hash=sha256:de6ea4e5a65d5a90c7d286ddff2b87f3f4ad61faa3db8dabe936b34c2275b6f8 \ + --hash=sha256:e0486a11ec30cdecb53f184d496d1c6a20786c81e55e41640270130056f8ee48 \ + --hash=sha256:ee807923782faaf60d0d7331f5e86da7d5e3079e28b291973c545476c2b00d07 \ + --hash=sha256:efc81393f25f14d11c9d161e46e6ee348637c0a1e8a54bf9dedc472a3fae993b \ + --hash=sha256:f0a1a8476ad77a228e41619af2fa9505cf69df928e9aaa165746584ea17fed2b \ + --hash=sha256:f75018be4980a7324edc5930fe39aa391d5734531b1926968605416ff58c332d \ + --hash=sha256:f92d6c2a8535dc4fe4419562294ff957f83a16ebdec66df0805e473ffaad8bd0 \ + --hash=sha256:fb1752a3bb9a3ad2d6b090b88a9a0ae1cd6f004ef95f75825e2f382c183b2097 \ + --hash=sha256:fc927d7f289d14f5e037be917539620603294454130b6de200091e23d27dc9be \ + --hash=sha256:fed5527c4cf10f16c6d0b6bee1f89958bccb0ad2522c8cadc2efd318bcd545f5 + # via -r requirements.in +online-fxreader-pr34==0.1.5.27 \ + --hash=sha256:d081758fdb91fb460da5c55d3a38257122de096363a8d956ba2f91a234566010 # via -r requirements.in pathspec==0.12.1 \ --hash=sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08 \ @@ -272,6 +363,10 @@ pydantic-settings==2.10.1 \ --hash=sha256:06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee \ --hash=sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796 # via online-fxreader-pr34 +pyright==1.1.404 \ + --hash=sha256:455e881a558ca6be9ecca0b30ce08aa78343ecc031d37a198ffa9a7a1abeb63e \ + --hash=sha256:c7b7ff1fdb7219c643079e4c3e7d4125f0dafcc19d253b47e898d130ea426419 + # via online-fxreader-pr34 python-dotenv==1.1.1 \ --hash=sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc \ --hash=sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab @@ -331,6 +426,27 @@ pyyaml==6.0.2 \ --hash=sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12 \ --hash=sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4 # via yq +ruff==0.12.11 \ + --hash=sha256:0d737b4059d66295c3ea5720e6efc152623bb83fde5444209b69cd33a53e2000 \ + --hash=sha256:411954eca8464595077a93e580e2918d0a01a19317af0a72132283e28ae21bee \ + --hash=sha256:4d1df0098124006f6a66ecf3581a7f7e754c4df7644b2e6704cd7ca80ff95211 \ + --hash=sha256:4dc75533039d0ed04cd33fb8ca9ac9620b99672fe7ff1533b6402206901c34ee \ + --hash=sha256:4fc58f9266d62c6eccc75261a665f26b4ef64840887fc6cbc552ce5b29f96cc8 \ + --hash=sha256:5a0113bd6eafd545146440225fe60b4e9489f59eb5f5f107acd715ba5f0b3d2f \ + --hash=sha256:5a8dd5f230efc99a24ace3b77e3555d3fbc0343aeed3fc84c8d89e75ab2ff793 \ + --hash=sha256:6a2c0a2e1a450f387bf2c6237c727dd22191ae8c00e448e0672d624b2bbd7fb0 \ + --hash=sha256:8ca4c3a7f937725fd2413c0e884b5248a19369ab9bdd850b5781348ba283f644 \ + --hash=sha256:916fc5defee32dbc1fc1650b576a8fed68f5e8256e2180d4d9855aea43d6aab2 \ + --hash=sha256:93fce71e1cac3a8bf9200e63a38ac5c078f3b6baebffb74ba5274fb2ab276065 \ + --hash=sha256:a3283325960307915b6deb3576b96919ee89432ebd9c48771ca12ee8afe4a0fd \ + --hash=sha256:b8e33ac7b28c772440afa80cebb972ffd823621ded90404f29e5ab6d1e2d4b93 \ + --hash=sha256:bae4d6e6a2676f8fb0f98b74594a048bae1b944aab17e9f5d504062303c6dbea \ + --hash=sha256:c6b09ae8426a65bbee5425b9d0b82796dbb07cb1af045743c79bfb163001165d \ + --hash=sha256:c792e8f597c9c756e9bcd4d87cf407a00b60af77078c96f7b6366ea2ce9ba9d3 \ + --hash=sha256:c984f07d7adb42d3ded5be894fb4007f30f82c87559438b4879fe7aa08c62b39 \ + --hash=sha256:d69fb9d4937aa19adb2e9f058bc4fbfe986c2040acb1a4a9747734834eaa0bfd \ + --hash=sha256:e07fbb89f2e9249f219d88331c833860489b49cdf4b032b8e4432e9b13e8a4b9 + # via online-fxreader-pr34 sniffio==1.3.1 \ --hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \ --hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc @@ -343,6 +459,40 @@ starlette==0.47.3 \ --hash=sha256:6bc94f839cc176c4858894f1f8908f0ab79dfec1a6b8402f6da9be26ebea52e9 \ --hash=sha256:89c0778ca62a76b826101e7c709e70680a1699ca7da6b44d38eb0a7e61fe4b51 # via fastapi +tomli==2.2.1 \ + --hash=sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6 \ + --hash=sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd \ + --hash=sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c \ + --hash=sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b \ + --hash=sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8 \ + --hash=sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6 \ + --hash=sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77 \ + --hash=sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff \ + --hash=sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea \ + --hash=sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192 \ + --hash=sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249 \ + --hash=sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee \ + --hash=sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4 \ + --hash=sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98 \ + --hash=sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8 \ + --hash=sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4 \ + --hash=sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281 \ + --hash=sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744 \ + --hash=sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69 \ + --hash=sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13 \ + --hash=sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140 \ + --hash=sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e \ + --hash=sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e \ + --hash=sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc \ + --hash=sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff \ + --hash=sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec \ + --hash=sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2 \ + --hash=sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222 \ + --hash=sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106 \ + --hash=sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272 \ + --hash=sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a \ + --hash=sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7 + # via online-fxreader-pr34 tomlkit==0.13.3 \ --hash=sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1 \ --hash=sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0 @@ -353,15 +503,22 @@ tomlq==0.1.0 \ --hash=sha256:4b966fd999ed2bf69081b7c7f5caadbc4c9542d0ed5fcf2e9b7b4d8d7ada3c82 \ --hash=sha256:e775720e90da3e405142b9fe476145e71c0389f787b1ff9933f92a1704d8c6e7 # via online-fxreader-pr34 +types-pyyaml==6.0.12.20250822 \ + --hash=sha256:1fe1a5e146aa315483592d292b72a172b65b946a6d98aa6ddd8e4aa838ab7098 \ + --hash=sha256:259f1d93079d335730a9db7cff2bcaf65d7e04b4a56b5927d49a612199b59413 + # via django-stubs typing-extensions==4.15.0 \ --hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \ --hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548 # via # anyio + # django-stubs + # django-stubs-ext # fastapi # mypy # pydantic # pydantic-core + # pyright # starlette # typing-inspection typing-inspection==0.4.1 \ diff --git a/docker/checks/rest.py b/docker/checks/rest.py index 2151cc5..f3cbfa5 100644 --- a/docker/checks/rest.py +++ b/docker/checks/rest.py @@ -1,13 +1,95 @@ import fastapi -from online.fxreader.pr34.commands_typed import metrics +import re +import numpy +import subprocess +import fastapi.responses +import pydantic_settings +import logging + +logger = logging.getLogger(__name__) + +from online.fxreader.pr34.commands_typed import metrics as pr34_metrics + +from typing import Optional, Any, ClassVar + + +class Settings(pydantic_settings.BaseSettings): + checks_hosts: list[str] + + _singleton: ClassVar[Optional['Settings']] = None + + @classmethod + def singleton(cls) -> 'Settings': + if cls._singleton is None: + cls._singleton = Settings.model_validate({}) + + return cls._singleton + + +def ping_stats(host: str) -> Optional[float]: + try: + ping_output = subprocess.check_output( + [ + 'ping', + '-i', + '0.1', + '-c', + '3', + '-w', + '1', + host, + ] + ).decode('utf-8') + except: + logger.exception('') + + ping_output = '' + + r1 = re.compile(r'time=(\d+\.\d+)\sms') + + spend_time = [float(o[1]) for o in r1.finditer(ping_output)] + + if len(spend_time) == 0: + return None + else: + return float(numpy.mean(spend_time)) + + +async def metrics_get() -> fastapi.responses.Response: + ping_res = {h: ping_stats(h) for h in Settings.singleton().checks_hosts} + + metrics = [ + pr34_metrics.Metric.model_validate( + dict( + name='ping_mean', + type='gauge', + help='ping to host, 3 counts, up to 1 second', + samples=[ + dict( + value=str(v), + parameters=dict( + host=k, + ), + ) + ], + ) + ) + for k, v in ping_res.items() + if not v is None + ] + serialize_res = pr34_metrics.serialize(metrics) + + return fastapi.responses.Response( + content=serialize_res.json2, + headers={ + 'Content-Type': serialize_res.content_type, + }, + ) -def main() -> None: - raise NotImplementedError def get_router() -> fastapi.APIRouter: router = fastapi.APIRouter() - return router + router.get('/metrics')(metrics_get) -if __name__ == '__main__': - main() + return router diff --git a/python/meson.build b/python/meson.build index 592472b..8dcb557 100644 --- a/python/meson.build +++ b/python/meson.build @@ -5,7 +5,7 @@ project( ).stdout().strip('\n'), # 'online.fxreader.uv', # ['c', 'cpp'], - version: '0.1.5.26', + version: '0.1.5.27', # default_options: [ # 'cpp_std=c++23', # # 'prefer_static=true', diff --git a/python/online/fxreader/pr34/commands_typed/metrics.py b/python/online/fxreader/pr34/commands_typed/metrics.py index 0409346..7c6881e 100644 --- a/python/online/fxreader/pr34/commands_typed/metrics.py +++ b/python/online/fxreader/pr34/commands_typed/metrics.py @@ -3,7 +3,7 @@ import json import logging import datetime -import django.http +# import django.http from typing import ( Literal, @@ -72,11 +72,17 @@ class Metric(pydantic.BaseModel): ) +class serialize_t: + class res_t(pydantic.BaseModel): + json2: str + content_type: str + + def serialize( metrics: list[Metric], ): - return django.http.HttpResponse( - ''.join( + return serialize_t.res_t( + json2=''.join( [ '{help}{type}{samples}'.format( # help='# HELP %s some metric' % o.name, diff --git a/releases/whl/online_fxreader_pr34-0.1.5.27-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.27-py3-none-any.whl new file mode 100644 index 0000000..beaf03f --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.27-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d081758fdb91fb460da5c55d3a38257122de096363a8d956ba2f91a234566010 +size 74850 From a666658e0b537ede4348c52fc7c4e77944d5900c Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Wed, 3 Sep 2025 13:44:24 +0300 Subject: [PATCH 19/70] [+] update nginx config 1. allow http only for servers with a flag set; 1.1. by default revert to redirecting to https; --- Makefile | 25 +++++++++++++++++++++++++ d1/nginx_config.py | 35 +++++++++++++++++++++++++++++------ 2 files changed, 54 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 5cc8827..e34a7fb 100644 --- a/Makefile +++ b/Makefile @@ -135,3 +135,28 @@ mypy: . .venv/bin/activate && \ mypy --strict --follow-imports silent \ $(MYPY_SOURCES) + +COMPOSE ?= sudo docker-compose + +nginx_config_http: + $(COMPOSE) exec app \ + python3 \ + d1/nginx_config.py \ + tmp/cache/forward.nginx.json \ + /etc/nginx/nginx.conf + +nginx_config_https: + $(COMPOSE) exec ssl-app \ + python3 \ + d1/nginx_config.py ssl \ + tmp/d1/ssl.nginx.json \ + /etc/nginx/nginx.conf + +nginx_config: nginx_config_https nginx_config_http + +nginx_reload_common: + $(COMPOSE) exec $(NGINX_SERVICE) nginx -s reload + +nginx_reload: + make nginx_reload_common NGINX_SERVICE=ssl-app + make nginx_reload_common NGINX_SERVICE=app diff --git a/d1/nginx_config.py b/d1/nginx_config.py index f722554..3c44990 100644 --- a/d1/nginx_config.py +++ b/d1/nginx_config.py @@ -348,10 +348,34 @@ server { ) for server in ssl_nginx['servers']: + location_proxy_app = r''' + location ^~ / { + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_redirect off; + proxy_buffering off; + proxy_http_version 1.1; + proxy_pass http://app:80; + } + ''' + + location_forward_ssl = r''' + location ~ { + #return 444; + return 301 https://$host$request_uri; + } + ''' + + if server.get('allow_http') in [True]: + http_location = location_proxy_app + else: + http_location = location_forward_ssl + servers.append( r''' - - server { set $t1 $remote_addr; if ($http_x_forwarded_for) @@ -368,10 +392,7 @@ server { try_files $uri =404; } - location ~ { - #return 444; - return 301 https://$host$request_uri; - } + {http_location} } server { @@ -411,6 +432,8 @@ server { '{domain_key}', server['domain_key'], ).replace( '{ssl_port}', '%d' % ssl_port, + ).replace( + '{http_location}', http_location ) ) From f4f579b8f1dff72a8de1773676ad28e640e63c34 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Thu, 11 Sep 2025 13:43:45 +0300 Subject: [PATCH 20/70] [+] update Battery service --- python/meson.build | 2 +- python/online/fxreader/pr34/commands.py | 267 +++++++++++++----------- 2 files changed, 147 insertions(+), 122 deletions(-) diff --git a/python/meson.build b/python/meson.build index 8dcb557..7bb0407 100644 --- a/python/meson.build +++ b/python/meson.build @@ -5,7 +5,7 @@ project( ).stdout().strip('\n'), # 'online.fxreader.uv', # ['c', 'cpp'], - version: '0.1.5.27', + version: '0.1.5.28', # default_options: [ # 'cpp_std=c++23', # # 'prefer_static=true', diff --git a/python/online/fxreader/pr34/commands.py b/python/online/fxreader/pr34/commands.py index 280aafa..9bd2af5 100644 --- a/python/online/fxreader/pr34/commands.py +++ b/python/online/fxreader/pr34/commands.py @@ -2047,6 +2047,152 @@ def loginctl(argv: list[str]) -> None: raise NotImplementedError +class Battery: + def __init__( + self, + should_start=None, + ): + if should_start is None: + should_start = False + + assert isinstance(should_start, bool) + + self.last_check = None + self.period = 10 + self.is_running = should_start + + def check_is_needed(self): + now = datetime.datetime.now(tz=datetime.timezone.utc) + + is_needed = None + + if self.last_check is None: + is_needed = True + else: + if (now - self.last_check).total_seconds() >= self.period: + is_needed = True + else: + is_needed = False + + if is_needed: + self.last_check = now + + return is_needed + + def run(self): + while True: + self.check() + + time.sleep(self.period) + + def terminate(self): + self.is_running = False + + def wait(self, *args, **kwargs): + if self.is_running: + raise NotImplementedError + + def poll(self): + if self.is_running: + return None + else: + return 0 + + @property + def percentage_low(self) -> int: + try: + return int( + subprocess.check_output( + r""" + cat /etc/UPower/UPower.conf | grep -Po '^PercentageLow=\d+' + """, + shell=True, + ) + .decode('utf-8') + .strip() + .split('=')[1] + ) + except: + logger.exception('') + return 15 + + @property + def percentage_critical(self) -> int: + try: + return int( + subprocess.check_output( + r""" + cat /etc/UPower/UPower.conf | grep -Po '^PercentageCritical=\d+' + """, + shell=True, + ) + .decode('utf-8') + .strip() + .split('=')[1] + ) + except: + logger.exception('') + return 10 + + def check(self): + try: + if not self.check_is_needed(): + return + + t1 = subprocess.check_output( + ['upower', '-d'], + timeout=1, + ).decode('utf-8') + t2 = [o for o in t1.splitlines() if 'percentage' in o.lower()] + t4 = [o for o in t1.splitlines() if 'state' in o.lower()] + t3 = float(t2[0].split(':')[1].strip()[:-1]) + # t5 = any(['discharging' in o.lower() for o in t4]) + state = [[o2.strip() for o2 in o.lower().split()][1] for o in t4][1] + + t5 = state != 'charging' + # t5 = True + if t3 < self.percentage_critical: + logging.error( + json.dumps( + dict( + msg='too low', + t3=t3, + t5=t5, + state=state, + ) + ) + ) + if t5: + subprocess.check_call(['systemctl', 'suspend']) + elif t3 < self.percentage_low: + msg = 'battery near low' + logging.error( + json.dumps( + dict( + msg=msg, + t3=t3, + t5=t5, + state=state, + ) + ) + ) + if t5: + subprocess.check_call( + [ + 'notify-send', + '-t', + '%d' % (5 * 1000), + msg, + '% 5.2f' % t3, + ] + ) + else: + pass + print('\r%s % 5.2f%% %s' % (datetime.datetime.now().isoformat(), t3, str(t5)), end='') + except Exception: + logging.error(traceback.format_exc()) + + def desktop_services(argv): parser = optparse.OptionParser() parser.add_option( @@ -2185,127 +2331,6 @@ def desktop_services(argv): return len(t3) > 0 and t4 - class Battery: - def __init__( - self, - should_start=None, - ): - if should_start is None: - should_start = False - - assert isinstance(should_start, bool) - - self.last_check = None - self.period = 10 - self.is_running = should_start - - def check_is_needed(self): - now = datetime.datetime.now(tz=datetime.timezone.utc) - - is_needed = None - - if self.last_check is None: - is_needed = True - else: - if (now - self.last_check).total_seconds() >= self.period: - is_needed = True - else: - is_needed = False - - if is_needed: - self.last_check = now - - return is_needed - - def run(self): - while True: - self.check() - - time.sleep(self.period) - - def terminate(self): - self.is_running = False - - def wait(self, *args, **kwargs): - if self.is_running: - raise NotImplementedError - - def poll(self): - if self.is_running: - return None - else: - return 0 - - @property - def percentage_low(self) -> int: - try: - return int( - subprocess.check_output( - r""" - cat /etc/UPower/UPower.conf | grep -Po '^PercentageLow=\d+' - """, - shell=True, - ) - .decode('utf-8') - .strip() - .split('=')[1] - ) - except: - logger.exception('') - return 15 - - @property - def percentage_critical(self) -> int: - try: - return int( - subprocess.check_output( - r""" - cat /etc/UPower/UPower.conf | grep -Po '^PercentageCritical=\d+' - """, - shell=True, - ) - .decode('utf-8') - .strip() - .split('=')[1] - ) - except: - logger.exception('') - return 10 - - def check(self): - try: - if not self.check_is_needed(): - return - - t1 = subprocess.check_output( - ['upower', '-d'], - timeout=1, - ).decode('utf-8') - t2 = [o for o in t1.splitlines() if 'percentage' in o.lower()] - t4 = [o for o in t1.splitlines() if 'state' in o.lower()] - t3 = float(t2[0].split(':')[1].strip()[:-1]) - t5 = any(['discharging' in o.lower() for o in t4]) - if t3 < self.percentage_critical and t5: - logging.error(json.dumps(dict(msg='too low', t3=t3, t5=t5))) - subprocess.check_call(['systemctl', 'suspend']) - elif t3 < self.percentage_low and t5: - msg = 'battery near low' - logging.error(json.dumps(dict(msg=msg, t3=t3, t5=t5))) - subprocess.check_call( - [ - 'notify-send', - '-t', - '%d' % (5 * 1000), - msg, - '% 5.2f' % t3, - ] - ) - else: - pass - print('\r%s % 5.2f%% %s' % (datetime.datetime.now().isoformat(), t3, str(t5)), end='') - except Exception: - logging.error(traceback.format_exc()) - class Backlight: class Direction(enum.Enum): increase = 'increase' From aa6b407fe72692cd00041f4d49f53a3e6128d191 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Thu, 11 Sep 2025 13:51:35 +0300 Subject: [PATCH 21/70] [+] update pr34 1. add -U to UV_ARGS, ignore it in venv; 2. generate .whl; 3. update m.py for pr34; --- python/m.py | 361 +++++++++++++++--- .../pr34/commands_typed/cli_bootstrap.py | 4 +- ...ne_fxreader_pr34-0.1.5.28-py3-none-any.whl | 3 + 3 files changed, 317 insertions(+), 51 deletions(-) create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.28-py3-none-any.whl diff --git a/python/m.py b/python/m.py index 99da03a..a98e0c8 100755 --- a/python/m.py +++ b/python/m.py @@ -1,5 +1,7 @@ #!/usr/bin/env python3 import glob +import importlib +import json import io import tempfile import dataclasses @@ -8,15 +10,21 @@ import sys import subprocess import os import logging +import typing from typing import ( Optional, Any, + cast, + Type, + TypeVar, + Callable, ) from typing_extensions import ( Self, BinaryIO, + overload, ) logger = logging.getLogger(__name__) @@ -24,17 +32,23 @@ logger = logging.getLogger(__name__) def toml_load(f: BinaryIO) -> Any: try: - import tomllib + tomllib = importlib.import_module('tomllib') - return tomllib.load(f) - except: + return cast( + Callable[[Any], Any], + getattr( + tomllib, + 'load', + ), + )(f) + except ModuleNotFoundError: pass try: import tomli return tomli.load(f) - except: + except ModuleNotFoundError: pass raise NotImplementedError @@ -42,14 +56,136 @@ def toml_load(f: BinaryIO) -> Any: @dataclasses.dataclass class PyProject: + @dataclasses.dataclass + class Module: + name: str + meson: Optional[pathlib.Path] = None + tool: dict[str, Any] = dataclasses.field(default_factory=lambda: dict()) + path: pathlib.Path dependencies: dict[str, list[str]] early_features: Optional[list[str]] = None pip_find_links: Optional[list[pathlib.Path]] = None runtime_libdirs: Optional[list[pathlib.Path]] = None runtime_preload: Optional[list[pathlib.Path]] = None + + @dataclasses.dataclass + class ThirdPartyRoot: + package: Optional[str] = None + module_root: Optional[str] = None + path: Optional[str] = None + + third_party_roots: list[ThirdPartyRoot] = dataclasses.field( + default_factory=lambda: [], + ) requirements: dict[str, pathlib.Path] = dataclasses.field(default_factory=lambda: dict()) + modules: list[Module] = dataclasses.field( + default_factory=lambda: [], + ) + + tool: dict[str, Any] = dataclasses.field( + default_factory=lambda: dict(), + ) + + +Key = TypeVar('Key') +Value = TypeVar('Value') + + +@overload +def check_dict( + value: Any, + KT: Type[Key], + VT: Type[Value], +) -> dict[Key, Value]: ... + + +@overload +def check_dict( + value: Any, + KT: Type[Key], +) -> dict[Key, Any]: ... + + +def check_dict( + value: Any, + KT: Type[Key], + VT: Optional[Type[Value]] = None, +) -> dict[Key, Value]: + assert isinstance(value, dict) + value2 = cast(dict[Any, Any], value) + + VT_class: Optional[type[Any]] = None + + if not VT is None: + if not typing.get_origin(VT) is None: + VT_class = cast(type[Any], typing.get_origin(VT)) + else: + VT_class = VT + + assert all([isinstance(k, KT) and (VT_class is None or isinstance(v, VT_class)) for k, v in value2.items()]) + + if VT is None: + return cast( + dict[Key, Any], + value, + ) + else: + return cast( + dict[Key, Value], + value, + ) + + +@overload +def check_list( + value: Any, + VT: Type[Value], +) -> list[Value]: ... + + +@overload +def check_list( + value: Any, +) -> list[Any]: ... + + +def check_list( + value: Any, + VT: Optional[Type[Value]] = None, +) -> list[Value] | list[Any]: + assert isinstance(value, list) + value2 = cast(list[Any], value) + + assert all([(VT is None or isinstance(o, VT)) for o in value2]) + + if VT is None: + return cast( + list[Any], + value, + ) + else: + return cast( + list[Value], + value, + ) + + +def check_type( + value: Any, + VT: Type[Value], + attribute_name: Optional[str] = None, +) -> Value: + if attribute_name: + attribute_value = getattr(value, attribute_name) + assert isinstance(attribute_value, VT) + return attribute_value + else: + assert isinstance(value, VT) + + return value + def pyproject_load( d: pathlib.Path, @@ -66,9 +202,21 @@ def pyproject_load( if 'optional-dependencies' in content['project']: assert isinstance(content['project']['optional-dependencies'], dict) - for k, v in content['project']['optional-dependencies'].items(): - assert isinstance(v, list) - assert isinstance(k, str) + for k, v in check_dict( + check_dict( + check_dict( + content, + str, + # Any, + )['project'], + str, + # Any, + )['optional-dependencies'], + str, + list[Any], + ).items(): + # assert isinstance(v, list) + # assert isinstance(k, str) dependencies[k] = v @@ -79,36 +227,88 @@ def pyproject_load( tool_name = 'online.fxreader.pr34'.replace('.', '-') + if 'tool' in content: + res.tool = check_dict( + content['tool'], + str, + ) + if 'tool' in content and isinstance(content['tool'], dict) and tool_name in content['tool'] and isinstance(content['tool'][tool_name], dict): - if 'early_features' in content['tool'][tool_name]: - res.early_features = content['tool'][tool_name]['early_features'] + pr34_tool = check_dict( + check_dict( + content['tool'], + str, + )[tool_name], + str, + ) - if 'pip_find_links' in content['tool'][tool_name]: - res.pip_find_links = [d.parent / pathlib.Path(o) for o in content['tool'][tool_name]['pip_find_links']] + if 'early_features' in pr34_tool: + res.early_features = pr34_tool['early_features'] - if 'runtime_libdirs' in content['tool'][tool_name]: + if 'pip_find_links' in pr34_tool: + res.pip_find_links = [d.parent / pathlib.Path(o) for o in pr34_tool['pip_find_links']] + + if 'runtime_libdirs' in pr34_tool: res.runtime_libdirs = [ d.parent / pathlib.Path(o) # pathlib.Path(o) - for o in content['tool'][tool_name]['runtime_libdirs'] + for o in check_list(pr34_tool['runtime_libdirs'], str) ] - if 'runtime_preload' in content['tool'][tool_name]: + if 'runtime_preload' in pr34_tool: res.runtime_preload = [ d.parent / pathlib.Path(o) # pathlib.Path(o) - for o in content['tool'][tool_name]['runtime_preload'] + for o in check_list(pr34_tool['runtime_preload'], str) ] - if 'requirements' in content['tool'][tool_name]: - assert isinstance(content['tool'][tool_name]['requirements'], dict) + if 'third_party_roots' in pr34_tool: + for o in check_list(pr34_tool['third_party_roots']): + o2 = check_dict(o, str, str) + assert all([k in {'package', 'module_root', 'path'} for k in o2]) + res.third_party_roots.append( + PyProject.ThirdPartyRoot( + package=o2.get('package'), + module_root=o2.get('module_root'), + path=o2.get('path'), + ) + ) + + if 'requirements' in pr34_tool: res.requirements = { k: d.parent / pathlib.Path(v) # pathlib.Path(o) - for k, v in content['tool'][tool_name]['requirements'].items() + for k, v in check_dict(pr34_tool['requirements'], str, str).items() } + if 'modules' in pr34_tool: + modules = check_list(pr34_tool['modules']) + # res.modules = [] + + for o in modules: + assert isinstance(o, dict) + assert 'name' in o and isinstance(o['name'], str) + + module = PyProject.Module( + name=o['name'], + ) + + if 'meson' in o: + assert 'meson' in o and isinstance(o['meson'], str) + + module.meson = pathlib.Path(o['meson']) + + if 'tool' in o: + module.tool.update( + check_dict( + o['tool'], + str, + ) + ) + + res.modules.append(module) + return res @@ -127,10 +327,13 @@ class BootstrapSettings: ), ).strip() ) + pip_check_conflicts: Optional[bool] = dataclasses.field( + default_factory=lambda: os.environ.get('PIP_CHECK_CONFLICTS', json.dumps(True)) in [json.dumps(True)], + ) uv_args: list[str] = dataclasses.field( default_factory=lambda: os.environ.get( 'UV_ARGS', - '--offline', + '--offline -U', ).split(), ) @@ -142,7 +345,12 @@ class BootstrapSettings: if base_dir is None: base_dir = pathlib.Path.cwd() - env_path = base_dir / '.venv' + env_path: Optional[pathlib.Path] = None + if 'ENV_PATH' in os.environ: + env_path = pathlib.Path(os.environ['ENV_PATH']) + else: + env_path = base_dir / '.venv' + python_path = env_path / 'bin' / 'python3' return cls( @@ -152,6 +360,47 @@ class BootstrapSettings: ) +class requirements_name_get_t: + @dataclasses.dataclass + class res_t: + not_compiled: pathlib.Path + compiled: pathlib.Path + name: str + + +def requirements_name_get( + source_dir: pathlib.Path, + python_version: Optional[str], + features: list[str], + requirements: dict[str, pathlib.Path], +) -> requirements_name_get_t.res_t: + requirements_python_version: Optional[str] = None + if not python_version is None: + requirements_python_version = python_version.replace('.', '_') + + requirements_name = '_'.join(sorted(features)) + + if requirements_python_version: + requirements_name += '_' + requirements_python_version + + requirements_path: Optional[pathlib.Path] = None + + if requirements_name in requirements: + requirements_path = requirements[requirements_name] + else: + requirements_path = source_dir / 'requirements.txt' + + requirements_path_in = requirements_path.parent / (requirements_path.stem + '.in') + + requirements_in: list[str] = [] + + return requirements_name_get_t.res_t( + not_compiled=requirements_path_in, + compiled=requirements_path, + name=requirements_name, + ) + + def env_bootstrap( bootstrap_settings: BootstrapSettings, pyproject: PyProject, @@ -169,7 +418,7 @@ def env_bootstrap( ] for o in pip_find_links ], - [], + cast(list[str], []), ) features: list[str] = [] @@ -177,31 +426,24 @@ def env_bootstrap( if pyproject.early_features: features.extend(pyproject.early_features) - requirements_python_version: Optional[str] = None - if not bootstrap_settings.python_version is None: - requirements_python_version = bootstrap_settings.python_version.replace('.', '_') - - requirements_name = '_'.join(sorted(features)) - - if requirements_python_version: - requirements_name += '_' + requirements_python_version - - requirements_path: Optional[pathlib.Path] = None - - if requirements_name in pyproject.requirements: - requirements_path = pyproject.requirements[requirements_name] - else: - requirements_path = pyproject.path.parent / 'requirements.txt' + requirements_name_get_res = requirements_name_get( + python_version=bootstrap_settings.python_version, + features=features, + requirements=pyproject.requirements, + source_dir=pyproject.path.parent, + ) + requirements_path = requirements_name_get_res.compiled requirements_in: list[str] = [] requirements_in.extend(['uv', 'pip', 'build', 'setuptools', 'meson-python', 'pybind11']) if pyproject.early_features: - early_dependencies = sum([pyproject.dependencies[o] for o in pyproject.early_features], []) + early_dependencies = sum([pyproject.dependencies[o] for o in pyproject.early_features], cast(list[str], [])) logger.info( dict( + requirements_name_get_res=requirements_name_get_res, early_dependencies=early_dependencies, ) ) @@ -218,6 +460,25 @@ def env_bootstrap( # *early_dependencies, # ]) + uv_python_version: list[str] = [] + venv_python_version: list[str] = [] + + if not bootstrap_settings.python_version is None: + uv_python_version.extend( + [ + # '-p', + '--python-version', + bootstrap_settings.python_version, + ] + ) + venv_python_version.extend( + [ + '-p', + # '--python-version', + bootstrap_settings.python_version, + ] + ) + if not requirements_path.exists(): with tempfile.NamedTemporaryFile( mode='w', @@ -232,6 +493,7 @@ def env_bootstrap( 'uv', 'pip', 'compile', + *uv_python_version, '--generate-hashes', *pip_find_links_args, # '-p', @@ -243,24 +505,14 @@ def env_bootstrap( ] ) - uv_python_version: list[str] = [] - - if not bootstrap_settings.python_version is None: - uv_python_version.extend( - [ - '-p', - bootstrap_settings.python_version, - ] - ) - subprocess.check_call( [ 'uv', + *[o for o in bootstrap_settings.uv_args if not o in ['-U', '--upgrade']], 'venv', - *uv_python_version, + *venv_python_version, *pip_find_links_args, # '--seed', - *bootstrap_settings.uv_args, str(bootstrap_settings.env_path), ] ) @@ -270,6 +522,7 @@ def env_bootstrap( 'uv', 'pip', 'install', + *uv_python_version, *pip_find_links_args, '-p', bootstrap_settings.python_path, @@ -280,6 +533,16 @@ def env_bootstrap( ] ) + if bootstrap_settings.pip_check_conflicts: + subprocess.check_call( + [ + bootstrap_settings.python_path, + '-m', + 'online.fxreader.pr34.commands', + 'pip_check_conflicts', + ] + ) + def paths_equal(a: pathlib.Path | str, b: pathlib.Path | str) -> bool: return os.path.abspath(str(a)) == os.path.abspath(str(b)) diff --git a/python/online/fxreader/pr34/commands_typed/cli_bootstrap.py b/python/online/fxreader/pr34/commands_typed/cli_bootstrap.py index ca9cc64..7a95032 100644 --- a/python/online/fxreader/pr34/commands_typed/cli_bootstrap.py +++ b/python/online/fxreader/pr34/commands_typed/cli_bootstrap.py @@ -333,7 +333,7 @@ class BootstrapSettings: uv_args: list[str] = dataclasses.field( default_factory=lambda: os.environ.get( 'UV_ARGS', - '--offline', + '--offline -U', ).split(), ) @@ -508,11 +508,11 @@ def env_bootstrap( subprocess.check_call( [ 'uv', + *[o for o in bootstrap_settings.uv_args if not o in ['-U', '--upgrade']], 'venv', *venv_python_version, *pip_find_links_args, # '--seed', - *bootstrap_settings.uv_args, str(bootstrap_settings.env_path), ] ) diff --git a/releases/whl/online_fxreader_pr34-0.1.5.28-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.28-py3-none-any.whl new file mode 100644 index 0000000..e1397f2 --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.28-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dedd1d53da278e01078df915f3b574aa81bd5af39bad5f815cd2b332bd2cfe75 +size 74933 From ef32d2ae3886388e5fc3c07b5ff1cfa0a9495c84 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Wed, 17 Sep 2025 12:46:42 +0300 Subject: [PATCH 22/70] [+] update nginx config 1. add drop_by_user_agent section for ssl servers; 1.1. ban traffic from openai.com/gptbot; --- d1/nginx_config.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/d1/nginx_config.py b/d1/nginx_config.py index 3c44990..07e4627 100644 --- a/d1/nginx_config.py +++ b/d1/nginx_config.py @@ -1,4 +1,5 @@ import json +import re import socket import os import io @@ -312,6 +313,11 @@ server { deny all; } + location ~ ^/.well-known/acme-challenge/ { + alias /var/www/; + try_files $uri =404; + } + location ~ { deny all; } @@ -374,6 +380,23 @@ server { else: http_location = location_forward_ssl + drop_by_user_agent = '' + + if not server.get('drop_by_user_agent') is None: + r = re.compile('^([a-zA-Z0-9\s\.\,\(\)]+)$') + user_agent_list = [ + r.match(o)[1] + for o in server.get('drop_by_user_agent') + ] + drop_by_user_agent = r''' + if ( $http_user_agent ~ ({user_agent_list}) ) { + return 444; + } + '''.replace( + '{user_agent_list}', + '|'.join(user_agent_list) + ) + servers.append( r''' server { @@ -410,6 +433,8 @@ server { ssl_certificate {signed_chain_cert}; ssl_certificate_key {domain_key}; + {drop_by_user_agent} + location ^~ / { proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -430,6 +455,8 @@ server { '{client_max_body_size}', server['client_max_body_size'], ).replace( '{domain_key}', server['domain_key'], + ).replace( + '{drop_by_user_agent}', drop_by_user_agent, ).replace( '{ssl_port}', '%d' % ssl_port, ).replace( From f4ac4c8ff95b435ac9e67957c64873345e59c55d Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Thu, 18 Sep 2025 09:42:18 +0300 Subject: [PATCH 23/70] [+] update nginx config 1. add ssh, web limits on upload/download speed; --- d1/nginx_config.py | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/d1/nginx_config.py b/d1/nginx_config.py index 07e4627..fd9dff5 100644 --- a/d1/nginx_config.py +++ b/d1/nginx_config.py @@ -220,6 +220,23 @@ def ssl(input_json, output_conf): upstream_servers = [] server_names = [] + ssh_proxy_download_rate = ssl_nginx['stream_server'].get( + 'ssh_proxy_download_rate', + 128 * 1024, + ) + ssh_proxy_upload_rate = ssl_nginx['stream_server'].get( + 'ssh_proxy_upload_rate', + 128 * 1024, + ) + web_proxy_download_rate = ssl_nginx['stream_server'].get( + 'web_proxy_download_rate', + 128 * 1024 * 1024, + ) + web_proxy_upload_rate = ssl_nginx['stream_server'].get( + 'web_proxy_upload_rate', + 128 * 1024 * 1024, + ) + if 'by_server_name' in ssl_nginx['stream_server']: for k, v in ssl_nginx['stream_server']['by_server_name'].items(): upstream_servers.append( @@ -260,6 +277,15 @@ stream { "TLSv1.3" $upstream_server_name; } + map $upstream_protocol $proxy_download_rate { + web {web_proxy_download_rate}; + ssh {ssh_proxy_download_rate}; + } + map $upstream_protocol $proxy_upload_rate { + web {web_proxy_upload_rate}; + ssh {ssh_proxy_upload_rate}; + } + map $ssl_preread_server_name $upstream_server_name { default web; {server_names} @@ -270,7 +296,12 @@ stream { listen 443; ssl_preread on; + proxy_pass $upstream_protocol; + + proxy_download_rate $proxy_download_rate; + proxy_upload_rate $proxy_upload_rate; + # proxy_upload_rate 10k; } } '''.replace( @@ -280,6 +311,14 @@ stream { ]), ).replace( '{ssh_section}', ssh_section, + ).replace( + '{web_proxy_download_rate}', '%d' % web_proxy_download_rate, + ).replace( + '{ssh_proxy_download_rate}', '%d' % ssh_proxy_download_rate, + ).replace( + '{web_proxy_upload_rate}', '%d' % web_proxy_upload_rate, + ).replace( + '{ssh_proxy_upload_rate}', '%d' % ssh_proxy_upload_rate, ).replace( '{server_names}', ''.join([ ' ' + o + '\n' From b9f791fc3d5a8c331c5b4073bcac628cf198fe6d Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Thu, 25 Sep 2025 13:24:10 +0300 Subject: [PATCH 24/70] [+] update cpanel 1. add netcat-openbsd, for socks5 proxy of ssh connections; --- docker-compose.yml | 1 + docker/cpanel/Dockerfile | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 648732d..6d22ac8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -45,6 +45,7 @@ services: - ${CHECKS_PORTS:-"127.0.0.1:80"}:80 cpanel: + image: online.fxreader.pr34.cpanel:dev build: context: . dockerfile: ./docker/cpanel/Dockerfile diff --git a/docker/cpanel/Dockerfile b/docker/cpanel/Dockerfile index be88b41..ba1b460 100644 --- a/docker/cpanel/Dockerfile +++ b/docker/cpanel/Dockerfile @@ -1,9 +1,11 @@ -FROM alpine:latest +# FROM alpine:latest +FROM alpine@sha256:56fa17d2a7e7f168a043a2712e63aed1f8543aeafdcee47c58dcffe38ed51099 RUN apk add openssh RUN apk add python3 RUN apk add tini RUN apk add bash curl RUN apk add py3-pip +RUN apk add netcat-openbsd RUN pip3 install --break-system-packages requests WORKDIR /app From 01e98958a67762d9614041061aef581cba697ae9 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Tue, 30 Sep 2025 07:30:38 +0300 Subject: [PATCH 25/70] [+] update docker-compose 1. use PartOf for proper dependency management; 2. add hard coded subnet, with specific ip for app; to use in ufw rules; --- .env.examples | 2 ++ d1/fxreader.online-certbot.service | 3 +++ d1/fxreader.online-gateway.service | 3 ++- docker-compose.yml | 26 ++++++++++++++++++++++++++ 4 files changed, 33 insertions(+), 1 deletion(-) diff --git a/.env.examples b/.env.examples index 5afa6b8..ea15557 100644 --- a/.env.examples +++ b/.env.examples @@ -1 +1,3 @@ NGINX_EXPORTER_PORTS=127.0.0.1:9113 +CHECKS_PORTS=127.0.0.1:9097 +SUBNET=172.31.0 diff --git a/d1/fxreader.online-certbot.service b/d1/fxreader.online-certbot.service index f249bf4..8a3179c 100644 --- a/d1/fxreader.online-certbot.service +++ b/d1/fxreader.online-certbot.service @@ -1,5 +1,8 @@ [Unit] Description=fxreader.online-certbot +Requires=fxreader.online-gateway +After=fxreader.online-gateway +PartOf=fxreader.online-gateway [Service] Type=oneshot diff --git a/d1/fxreader.online-gateway.service b/d1/fxreader.online-gateway.service index 2d8b33a..1161802 100644 --- a/d1/fxreader.online-gateway.service +++ b/d1/fxreader.online-gateway.service @@ -2,10 +2,11 @@ Description=fxreader.online-service Requires=docker.service After=docker.service +PartOf=docker.service [Service] #Type=oneshot -ExecStart=/usr/bin/docker compose up --force-recreate --remove-orphans +ExecStart=/usr/bin/docker compose up ExecStop=/usr/bin/docker compose down WorkingDirectory={{PROJECT_ROOT}} StandardOutput=null diff --git a/docker-compose.yml b/docker-compose.yml index 6d22ac8..afb83ee 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,6 +8,10 @@ services: - ./d1/:/app/d1/:ro - ./tmp/cache/:/app/tmp/cache/:ro restart: on-failure + networks: + network: + ipv4_address: ${SUBNET}.2 + nginx-exporter: image: docker.io/nginx/nginx-prometheus-exporter@sha256:6edfb73afd11f2d83ea4e8007f5068c3ffaa38078a6b0ad1339e5bd2f637aacd #profiles: @@ -19,6 +23,8 @@ services: # LISTEN_ADDRESS: 0.0.0.0:9113 ports: - ${NGINX_EXPORTER_PORTS:-"127.0.0.1:9113"}:9113 + networks: + network: ssl-app: build: @@ -29,6 +35,8 @@ services: - ./tmp/d1/:/app/tmp/d1/:ro - ./tmp/d1/letsencrypt:/etc/letsencrypt:rw restart: on-failure + networks: + network: checks: build: @@ -43,6 +51,8 @@ services: - online.fxreader.pr34.commands_typed.async_api.fastapi ports: - ${CHECKS_PORTS:-"127.0.0.1:80"}:80 + networks: + network: cpanel: image: online.fxreader.pr34.cpanel:dev @@ -55,6 +65,8 @@ services: - ./d1/:/app/d1:ro - ./tmp/d1/:/app/tmp/d1/:ro restart: on-failure + networks: + network: dynu: build: @@ -68,6 +80,8 @@ services: restart: on-failure # links: # - ngrok + networks: + network: ngrok: image: wernight/ngrok #links: @@ -78,6 +92,8 @@ services: volumes: - ./tmp/cache/ngrok.yml:/home/ngrok/.ngrok2/ngrok.yml:ro restart: on-failure + networks: + network: #forward: # build: # context: . @@ -86,3 +102,13 @@ services: # - ./d1/forward.py:/app/d1/forward.py:ro # - ./tmp/cache/forward_data:/app/tmp/cache/forward_data:ro # restart: always +networks: + network: + driver: bridge + # driver_opts: + # com.docker.network.bridge.name: br-mynet # stable bridge name (optional) + ipam: + config: + - subnet: ${SUBNET}.0/24 + gateway: "${SUBNET}.1" + ip_range: "${SUBNET}.128/25" # optional: pool for containers From 81f3fc494ab5c6992301e5baf92408d12b0b7151 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Fri, 10 Oct 2025 13:26:32 +0300 Subject: [PATCH 26/70] [+] update vim config 1. add dynamic modeline application based on .editorconfig, vim_modeline key; with validation for security; --- .editorconfig | 2 + Makefile | 7 +++- dotfiles/.py3.vimrc | 99 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f514c1d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,2 @@ +[**/*.py] +vim_modeline = set noet ts=2 sts=2 sw=2 ai ci diff --git a/Makefile b/Makefile index e34a7fb..6e4f1fb 100644 --- a/Makefile +++ b/Makefile @@ -69,7 +69,6 @@ dotfiles_put: cp dotfiles/.vimrc ~/.vimrc cp dotfiles/.tmux.conf ~/.tmux.conf cp dotfiles/.py3.vimrc ~/.py3.vimrc - cp dotfiles/.py3.vimrc ~/.py3.vimrc cp dotfiles/.gitconfig ~/.gitconfig cp -rp \ dotfiles/.ipython/profile_default/ipython_config.py \ @@ -82,6 +81,12 @@ dotfiles_put: done #commands install -f -p dotfiles -s dotfiles/ -t ~/.config/ +dotfiles_vim_put: + mkdir -p $(INSTALL_ROOT) + + cp dotfiles/.vimrc ~/.vimrc + cp dotfiles/.py3.vimrc ~/.py3.vimrc + PLATFORM ?= macbook_air_2012 PLATFORM_TMP ?= tmp/platform_dotfiles/$(PLATFORM) diff --git a/dotfiles/.py3.vimrc b/dotfiles/.py3.vimrc index d453fab..b798dd3 100644 --- a/dotfiles/.py3.vimrc +++ b/dotfiles/.py3.vimrc @@ -1,4 +1,15 @@ py3 << EOF +from typing import (Optional, ClassVar, Self,) +import configparser +import re +import pathlib +import logging +import fnmatch + +logger = logging.getLogger(__name__) + +logging.basicConfig(level=logging.WARNING) + def f1(): t1 = vim.current.window t2 = t1.width @@ -122,8 +133,96 @@ def f5_1(pattern, flags, info): #return [{'name': 'blah', 'filename': 'docker-compose.yml', 'cmd': '23'}] return t2 + +class EditorConfigModeline: + _instance : ClassVar[Optional['EditorConfigModeline']] = None + + def __init__(self) -> None: + self.configs : dict[ + pathlib.Path, + dict[str, str], + ] = dict() + + @classmethod + def singleton(cls) -> Self: + if cls._instance is None: + cls._instance = cls() + + return cls._instance + + def load_config(self) -> Optional[dict[str, str]]: + cwd = pathlib.Path.cwd() + + if not cwd in self.configs: + config_path = cwd / '.editorconfig' + + if not config_path.exists(): + return None + + parser = configparser.ConfigParser() + parser.optionxform = str # keep case + parser.read(str(config_path)) + + config : dict[str, str] = dict() + + for section in parser.sections(): + logger.info(dict(section=section)) + + if len(section) > 0: + # pattern = section[1:-1] + pattern = section + if not parser[section].get('vim_modeline') is None: + config[pattern] = parser[section].get('vim_modeline') + self.validate_modeline(config[pattern]) + + self.configs[cwd] = config + + return self.configs[cwd] + + @classmethod + def validate_modeline(cls, modeline: str) -> None: + pattern = re.compile(r'^set(\s+(noet|sts|ts|et|ai|ci|noai|noci|sw)(\=\w)?)+$') + assert pattern.match(modeline), 'invalid modeline %s' % modeline + + @classmethod + def find_entry( + cls, + config: dict[str, str], + file_path: pathlib.Path + ) -> Optional[str]: + rel_path = file_path.relative_to(pathlib.Path.cwd()) + + for pattern, modeline in config.items(): + if fnmatch.fnmatch(str(rel_path), pattern): + return modeline + + return None + + def on_buffer(self) -> None: + config = self.load_config() + + logger.info(dict(config=config)) + + buf_name = vim.current.buffer.name + file_path = pathlib.Path(buf_name).resolve() + + entry = self.find_entry(config, file_path) + + logger.info(dict(modeline=entry)) + + vim.command('silent! {}'.format(entry)) + + # vim.command("echo '{}'".format('applied %s' % entry)) + + # raise NotImplementedError + EOF +augroup EditorConfigModeline + autocmd! + autocmd BufEnter * python3 EditorConfigModeline.singleton().on_buffer() +augroup END + function! F5(pattern, flags, info) let res = py3eval( \'f5_1( From 31cd7c35c96500ea0c5adba714669c8a8fc75326 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Sat, 11 Oct 2025 16:34:14 +0300 Subject: [PATCH 27/70] [+] fix relative_to error, vim config --- dotfiles/.py3.vimrc | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/dotfiles/.py3.vimrc b/dotfiles/.py3.vimrc index b798dd3..033685e 100644 --- a/dotfiles/.py3.vimrc +++ b/dotfiles/.py3.vimrc @@ -187,10 +187,18 @@ class EditorConfigModeline: @classmethod def find_entry( cls, - config: dict[str, str], - file_path: pathlib.Path + file_path: pathlib.Path, + config: Optional[dict[str, str]] = None, ) -> Optional[str]: - rel_path = file_path.relative_to(pathlib.Path.cwd()) + if config is None: + return None + + project_root = pathlib.Path.cwd() + + if file_path.is_relative_to(project_root): + rel_path = file_path.relative_to(pathlib.Path.cwd()) + else: + rel_path = file_path for pattern, modeline in config.items(): if fnmatch.fnmatch(str(rel_path), pattern): @@ -206,7 +214,7 @@ class EditorConfigModeline: buf_name = vim.current.buffer.name file_path = pathlib.Path(buf_name).resolve() - entry = self.find_entry(config, file_path) + entry = self.find_entry(file_path, config=config) logger.info(dict(modeline=entry)) From cc4a703bcb8418ed97d12d982441a445fc8e8d56 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Sat, 11 Oct 2025 17:07:45 +0300 Subject: [PATCH 28/70] [+] update pydantic, validate_params 1. extend to support sync/async methods; --- .editorconfig | 3 ++ python/meson.build | 2 +- .../fxreader/pr34/commands_typed/pydantic.py | 44 +++++++++++++++---- ...ne_fxreader_pr34-0.1.5.29-py3-none-any.whl | 3 ++ 4 files changed, 43 insertions(+), 9 deletions(-) create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.29-py3-none-any.whl diff --git a/.editorconfig b/.editorconfig index f514c1d..732af22 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,2 +1,5 @@ [**/*.py] vim_modeline = set noet ts=2 sts=2 sw=2 ai ci + +[**/meson.build] +vim_modeline = set noet ts=2 sts=2 sw=2 ai ci diff --git a/python/meson.build b/python/meson.build index 7bb0407..119c0fb 100644 --- a/python/meson.build +++ b/python/meson.build @@ -5,7 +5,7 @@ project( ).stdout().strip('\n'), # 'online.fxreader.uv', # ['c', 'cpp'], - version: '0.1.5.28', + version: '0.1.5.29', # default_options: [ # 'cpp_std=c++23', # # 'prefer_static=true', diff --git a/python/online/fxreader/pr34/commands_typed/pydantic.py b/python/online/fxreader/pr34/commands_typed/pydantic.py index 07e1d79..67cc0b5 100644 --- a/python/online/fxreader/pr34/commands_typed/pydantic.py +++ b/python/online/fxreader/pr34/commands_typed/pydantic.py @@ -1,4 +1,7 @@ import pydantic +import functools + +# import asgiref.sync import inspect import collections @@ -9,13 +12,23 @@ from typing import ( Optional, Mapping, cast, + Awaitable, + overload, ) P = TypeVar('P') R = TypeVar('R') -def validate_params(view: Callable[..., R]) -> Callable[..., R]: +@overload +def validate_params(view: Callable[..., Awaitable[R]]) -> Callable[..., Awaitable[R]]: ... + + +@overload +def validate_params(view: Callable[..., R]) -> Callable[..., R]: ... + + +def validate_params(view: Callable[..., Awaitable[R]] | Callable[..., R]) -> Any: class Parameter: kind: Any annotation: Any @@ -52,7 +65,10 @@ def validate_params(view: Callable[..., R]) -> Callable[..., R]: ), ) - def wrapper(*args: Any, **kwargs: Any) -> R: + sync_view: Optional[Callable[..., R]] = None + async_view: Optional[Callable[..., Awaitable[R]]] = None + + def validate_params(*args: Any, **kwargs: Any) -> None: # data = model.model_validate( kwargs_to_check: dict[str, Any] = {k: v for k, v in kwargs.items()} @@ -71,10 +87,22 @@ def validate_params(view: Callable[..., R]) -> Callable[..., R]: ) # ).dict() - return view( - # **data, - *args, - **kwargs, - ) + if inspect.iscoroutinefunction(view): + async_view = cast(Callable[..., Awaitable[R]], view) + raise NotImplementedError - return wrapper + @functools.wraps(async_view) + async def wrapper(*args: Any, **kwargs: Any) -> R: + validate_params(*args, **kwargs) + + return await async_view(*args, **kwargs) + else: + sync_view = cast(Callable[..., R], view) + + @functools.wraps(sync_view) + def wrapper(*args: Any, **kwargs: Any) -> R: + validate_params(*args, **kwargs) + + return sync_view(*args, **kwargs) + + return wrapper diff --git a/releases/whl/online_fxreader_pr34-0.1.5.29-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.29-py3-none-any.whl new file mode 100644 index 0000000..c691957 --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.29-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a03f843dc71d1aac335daa628e8ef4aff46aa0f31b224bbf5762bd1456e799c7 +size 75106 From cf2476ec28e2dd1d409d79c2e591cf60cc0e6074 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Sun, 12 Oct 2025 00:29:01 +0300 Subject: [PATCH 29/70] [+] update vim, pydantic 1. refactor python vim module; 1.1. experiment with fast select based on popup in vim, and multi threaded app in python; 1.2. TODO, figure out some thread safe way to call vim.command from python side threads; 1.3. update pydantic validate params; --- Makefile | 1 + dotfiles/.beta.vimrc.py | 495 ++++++++++++++++++ dotfiles/.module.vimrc.py | 243 +++++++++ dotfiles/.py3.vimrc | 228 +------- python/meson.build | 2 +- .../fxreader/pr34/commands_typed/pydantic.py | 7 +- python/pyproject.toml | 3 +- ...ne_fxreader_pr34-0.1.5.30-py3-none-any.whl | 3 + ...ne_fxreader_pr34-0.1.5.31-py3-none-any.whl | 3 + 9 files changed, 753 insertions(+), 232 deletions(-) create mode 100644 dotfiles/.beta.vimrc.py create mode 100644 dotfiles/.module.vimrc.py create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.30-py3-none-any.whl create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.31-py3-none-any.whl diff --git a/Makefile b/Makefile index 6e4f1fb..101cd4e 100644 --- a/Makefile +++ b/Makefile @@ -86,6 +86,7 @@ dotfiles_vim_put: cp dotfiles/.vimrc ~/.vimrc cp dotfiles/.py3.vimrc ~/.py3.vimrc + cp dotfiles/.module.vimrc.py ~/.module.vimrc.py PLATFORM ?= macbook_air_2012 PLATFORM_TMP ?= tmp/platform_dotfiles/$(PLATFORM) diff --git a/dotfiles/.beta.vimrc.py b/dotfiles/.beta.vimrc.py new file mode 100644 index 0000000..d0c47aa --- /dev/null +++ b/dotfiles/.beta.vimrc.py @@ -0,0 +1,495 @@ +import functools +import configparser +import collections +import asyncio +import threading +import re +import inspect +import pathlib +import logging +import fnmatch +import vim + +from typing import ( + Optional, + ClassVar, + Self, + Any, + Callable, +) + +logger = logging.getLogger(__name__) + +logging.basicConfig(level=logging.WARNING) + + +MODULE_NAME = 'online_fxreader_pr34_vim' + +def f1(): + t1 = vim.current.window + t2 = t1.width + vim.command('vnew') + t3 = t2 // 3 + vim.command('vertical resize %d' % t3) + vim.current.window = t1 + + +def f2(): + context = {k: vim.options['splitright'] for k in ['splitright']} + try: + current_window = vim.current.window + vim.options['splitright'] = True + vim.command('vnew') + vim.command('r! tmux show-buffer') + vim.current.window = current_window + finally: + for k, v in context.items(): + vim.options[k] = v + + +def f5_1(pattern, flags, info): + import subprocess + import io + import re + import tempfile + import traceback + import logging + + # print([pattern, flags, info]) + completed_process = None + + options = dict( + recursive=False, + ext=[], + ) + + # print('fuck') + if b'r' in flags: + while True: + ext_m = re.compile(r'^.([^\,]+),(.*)$').match(pattern) + + if pattern[:3] in [r'\r,']: + options['recursive'] = True + pattern = pattern[3:] + elif not ext_m is None: + options['ext'].append(ext_m[1]) + pattern = ext_m[2] + else: + break + + print( + [ + flags, + pattern, + options, + ] + ) + + try: + git_cmd = [ + 'git', + 'grep', + '-n', + ] + + if options['recursive']: + git_cmd.append('--recurse-submodules') + + git_cmd.extend(['-P', pattern]) + + if len(options['ext']) > 0: + git_cmd.extend(['--', *['**/*%s' % o for o in options['ext']]]) + + completed_process = subprocess.run( + git_cmd, + capture_output=True, + ) + assert completed_process.returncode == 0 or ( + completed_process.stdout == b'' + # completed_process.stdout == b'' and + # completed_process.stderr == b'' + ) + t1 = completed_process.stdout + except: + logging.error( + ''.join( + [ + traceback.format_exc(), + getattr(completed_process, 'stdout', b'').decode('utf-8'), + getattr(completed_process, 'stderr', b'').decode('utf-8'), + ] + ) + ) + t1 = b'' + + def watch(data): + with tempfile.NamedTemporaryFile(suffix='.txt') as f: + with io.open(f.name, 'wb') as f2: + f2.write(data) + vim.command('!less %s' % f.name) + + # watch(t1) + + t2 = [] + for o in t1.splitlines(): + try: + # watch(o.encode('utf-8')) + t3 = o.decode('utf-8') + t4 = re.compile(r'^([^\:\=]+)[\:\=](\d+)[\:\=](.*)$').match(t3) + if not t4 is None: + t2.append( + dict( + name=t4[3].strip(), + filename=t4[1], + cmd=t4[2], + ) + ) + except: + pass + # print(t2) + + # return [{'name': 'blah', 'filename': 'docker-compose.yml', 'cmd': '23'}] + return t2 + + +class EditorConfigModeline: + _instance: ClassVar[Optional['EditorConfigModeline']] = None + + def __init__(self) -> None: + self.configs: dict[ + pathlib.Path, + dict[str, str], + ] = dict() + + @classmethod + def singleton(cls) -> Self: + if cls._instance is None: + cls._instance = cls() + + return cls._instance + + def load_config(self) -> Optional[dict[str, str]]: + cwd = pathlib.Path.cwd() + + if not cwd in self.configs: + config_path = cwd / '.editorconfig' + + if not config_path.exists(): + return None + + parser = configparser.ConfigParser() + parser.optionxform = str # keep case + parser.read(str(config_path)) + + config: dict[str, str] = dict() + + for section in parser.sections(): + logger.info(dict(section=section)) + + if len(section) > 0: + # pattern = section[1:-1] + pattern = section + if not parser[section].get('vim_modeline') is None: + config[pattern] = parser[section].get('vim_modeline') + self.validate_modeline(config[pattern]) + + self.configs[cwd] = config + + return self.configs[cwd] + + @classmethod + def validate_modeline(cls, modeline: str) -> None: + pattern = re.compile(r'^set(\s+(noet|sts|ts|et|ai|ci|noai|noci|sw)(\=\w)?)+$') + assert pattern.match(modeline), 'invalid modeline %s' % modeline + + @classmethod + def find_entry( + cls, + file_path: pathlib.Path, + config: Optional[dict[str, str]] = None, + ) -> Optional[str]: + if config is None: + return None + + project_root = pathlib.Path.cwd() + + if file_path.is_relative_to(project_root): + rel_path = file_path.relative_to(pathlib.Path.cwd()) + else: + rel_path = file_path + + for pattern, modeline in config.items(): + if fnmatch.fnmatch(str(rel_path), pattern): + return modeline + + return None + + def on_buffer(self) -> None: + config = self.load_config() + + logger.info(dict(config=config)) + + buf_name = vim.current.buffer.name + file_path = pathlib.Path(buf_name).resolve() + + entry = self.find_entry(file_path, config=config) + + logger.info(dict(modeline=entry)) + + vim.command('silent! {}'.format(entry)) + + # vim.command("echo '{}'".format('applied %s' % entry)) + + # raise NotImplementedError + + +class _Vim: + @classmethod + def run_command(cls, cmd) -> list[str]: + logger.info(dict(cmd=cmd)) + + output: list[str] = [] + for line in cmd.splitlines(): + if line.strip() == '': + continue + output.append( + vim.command(line) + ) + + return output + +def future_dump_exception(future: Any) -> None: + try: + future.result() + except: + logger.exception('') + +class FastSelect: + _instance: ClassVar[Optional['FastSelect']] = None + + def __init__(self) -> None: + self.loop = asyncio.new_event_loop() + + self.thread = threading.Thread( + target=self.loop.run_forever, + ) + + self._queue : collections.deque[Callable[[], None]] = collections.deque() + self._lock = threading.Lock() + + self.thread.start() + self._option_id : asyncio.Future[Optional[int]] = None + self._options: list[str] = None + + auto_group = '{}_{}_{}'.format( + MODULE_NAME, + type(self).__name__.lower(), + 'close', + ).capitalize() + + vim.command(r''' + func! UIThread(timer_id) + python3 FastSelect.singleton().ui_thread() + endfunc + ''') + _Vim.run_command(r''' + call timer_start(1000, 'UIThread', {'repeat': -1}) + ''') + _Vim.run_command(r''' +augroup {auto_group} + autocmd! + autocmd VimLeavePre * python3 FastSelect.singleton().close() +augroup END + '''.format( + auto_group=auto_group, + )) + + + def __del__(self) -> None: + self.close() + + def close(self) -> None: + logger.info(dict(msg='close started')) + self.loop.call_soon_threadsafe(self.loop.stop) + self.thread.join() + + logger.info(dict(msg='close done')) + + @classmethod + def singleton(cls) -> Self: + if cls._instance is None: + cls._instance = cls() + + return cls._instance + + def pick_option_put_id(self, option_id: int) -> None: + self.loop.call_soon_threadsafe( + lambda: self._option_id.set_result(option_id) + ) + + async def _switch_buffer(self) -> None: + buffers_future : asyncio.Future[list[tuple[str, int]]] = asyncio.Future() + + def get_buffers() -> list[tuple[str, int]]: + res = [ + (o.name, o.number) + for o in vim.buffers + ] + + self.loop.call_soon_threadsafe( + lambda: buffers_future.set_result(res) + ) + + with self._lock: + self._queue.append(get_buffers) + + buffers = await buffers_future + + logger.info(dict(buffers=buffers[:3])) + + selected_id = await self._pick_option_from_popup( + [o[0] for o in buffers] + ) + + logger.info(dict(selected_id=selected_id)) + + def ui_switch_buffer(): + nonlocal selected_id + nonlocal buffers + + logger.warning(dict(buffers=list(vim.buffers), id=selected_id)) + # print(vim.buffers, selected_id) + if selected_id >= 0: + vim.current.buffer = vim.buffers[buffers[selected_id][1]] + + with self._lock: + self._queue.append(ui_switch_buffer) + + def switch_buffer(self) -> None: + logger.info(dict(msg='before switch_buffer started')) + result = asyncio.run_coroutine_threadsafe( + self._switch_buffer(), + self.loop + ) + + result.add_done_callback(future_dump_exception) + + logger.info(dict(msg='after switch_buffer started')) + + + def pick_file_from_popup( + self, + paths: list[pathlib.Path], + ) -> None: + + _Vim.run_command(r''' + call popup_menu(['asdasdfasdfasdfasdfasdfasdfasdff', 'adfadf'], {'title': '!!!!sdfasdf'}) + ''') + return + + logger.info(dict(msg='before pick started')) + result = asyncio.run_coroutine_threadsafe( + self._pick_option_from_popup( + options=[str(o) for o in paths] + ), + self.loop + ) + + result.add_done_callback(future_dump_exception) + + logger.info(dict(msg='after pick started')) + + async def _pick_option_from_popup( + self, + options: list[str], + ) -> Optional[int]: + logger.info(dict(msg='started')) + + self._options = options + + self._option_id = asyncio.Future[int]() + + await self._pick_option_start_popup() + + option_id = await self._option_id + + logger.info(dict(option_id=option_id)) + + self._options = None + self._option_id = None + + logger.info(dict(msg='done')) + + if option_id >= 0: + return option_id + else: + return None + + def ui_thread(self): + with self._lock: + while len(self._queue) > 0: + cmd = self._queue.pop(); + logger.warning(dict(msg='start command', cmd=inspect.getsource(cmd))) + try: + cmd() + except: + logger.exception('') + # self._result.append( + # vim.command(cmd) + # ) + + async def _pick_option_start_popup( + self, + ): + callback_name = '{}_{}_{}'.format( + MODULE_NAME, + type(self).__name__.lower(), + 'popup_callback', + ).capitalize() + + if int(vim.eval('exists("{}")'.format(callback_name))) == 1: + logger.warning(dict(msg='callback already defined, %s' % callback_name)) + + vim.command(r""" + function! {callback_name}(id, result) + if a:result > 0 + call py3eval('FastSelect.singleton().pick_option_put_id(' . (a:result - 1). ')') + else + call py3eval('FastSelect.singleton().pick_option_put_id(-1)') + endif + endfunction + """.format( + callback_name=callback_name, + )) + + logger.info(dict(msg='before popup')) + + popup_menu = vim.Function('popup_menu') + + with self._lock: + self._queue.append( + lambda : popup_menu( + self._options, + { + 'title': 'Select a file', + 'callback': callback_name + } + ) + #lambda : vim.command( + # "call popup_menu({options}, {'title': '{title}', 'callback': '{callback}'})".replace( + # '{options}', '[%s]' % ','.join([ + # '\'%s\'' % o.replace('\'', '\\\'') + # for o in self._options + # ]), + # ).replace( + # '{title}', 'Select a file', + # ).replace( + # '{callback}', + # callback_name + # ) + #) + ) + + # logger.info(dict(popup_id=popup_id)) + + # logger.info(dict(msg='after popup')) diff --git a/dotfiles/.module.vimrc.py b/dotfiles/.module.vimrc.py new file mode 100644 index 0000000..62edfd2 --- /dev/null +++ b/dotfiles/.module.vimrc.py @@ -0,0 +1,243 @@ +import functools +import configparser +import collections +import asyncio +import threading +import re +import inspect +import pathlib +import logging +import fnmatch +import vim + +from typing import ( + Optional, + ClassVar, + Self, + Any, + Callable, +) + +logger = logging.getLogger(__name__) + +logging.basicConfig(level=logging.WARNING) + + +MODULE_NAME = 'online_fxreader_pr34_vim' + +def f1(): + t1 = vim.current.window + t2 = t1.width + vim.command('vnew') + t3 = t2 // 3 + vim.command('vertical resize %d' % t3) + vim.current.window = t1 + + +def f2(): + context = {k: vim.options['splitright'] for k in ['splitright']} + try: + current_window = vim.current.window + vim.options['splitright'] = True + vim.command('vnew') + vim.command('r! tmux show-buffer') + vim.current.window = current_window + finally: + for k, v in context.items(): + vim.options[k] = v + + +def f5_1(pattern, flags, info): + import subprocess + import io + import re + import tempfile + import traceback + import logging + + # print([pattern, flags, info]) + completed_process = None + + options = dict( + recursive=False, + ext=[], + ) + + # print('fuck') + if b'r' in flags: + while True: + ext_m = re.compile(r'^.([^\,]+),(.*)$').match(pattern) + + if pattern[:3] in [r'\r,']: + options['recursive'] = True + pattern = pattern[3:] + elif not ext_m is None: + options['ext'].append(ext_m[1]) + pattern = ext_m[2] + else: + break + + print( + [ + flags, + pattern, + options, + ] + ) + + try: + git_cmd = [ + 'git', + 'grep', + '-n', + ] + + if options['recursive']: + git_cmd.append('--recurse-submodules') + + git_cmd.extend(['-P', pattern]) + + if len(options['ext']) > 0: + git_cmd.extend(['--', *['**/*%s' % o for o in options['ext']]]) + + completed_process = subprocess.run( + git_cmd, + capture_output=True, + ) + assert completed_process.returncode == 0 or ( + completed_process.stdout == b'' + # completed_process.stdout == b'' and + # completed_process.stderr == b'' + ) + t1 = completed_process.stdout + except: + logging.error( + ''.join( + [ + traceback.format_exc(), + getattr(completed_process, 'stdout', b'').decode('utf-8'), + getattr(completed_process, 'stderr', b'').decode('utf-8'), + ] + ) + ) + t1 = b'' + + def watch(data): + with tempfile.NamedTemporaryFile(suffix='.txt') as f: + with io.open(f.name, 'wb') as f2: + f2.write(data) + vim.command('!less %s' % f.name) + + # watch(t1) + + t2 = [] + for o in t1.splitlines(): + try: + # watch(o.encode('utf-8')) + t3 = o.decode('utf-8') + t4 = re.compile(r'^([^\:\=]+)[\:\=](\d+)[\:\=](.*)$').match(t3) + if not t4 is None: + t2.append( + dict( + name=t4[3].strip(), + filename=t4[1], + cmd=t4[2], + ) + ) + except: + pass + # print(t2) + + # return [{'name': 'blah', 'filename': 'docker-compose.yml', 'cmd': '23'}] + return t2 + + +class EditorConfigModeline: + _instance: ClassVar[Optional['EditorConfigModeline']] = None + + def __init__(self) -> None: + self.configs: dict[ + pathlib.Path, + dict[str, str], + ] = dict() + + @classmethod + def singleton(cls) -> Self: + if cls._instance is None: + cls._instance = cls() + + return cls._instance + + def load_config(self) -> Optional[dict[str, str]]: + cwd = pathlib.Path.cwd() + + if not cwd in self.configs: + config_path = cwd / '.editorconfig' + + if not config_path.exists(): + return None + + parser = configparser.ConfigParser() + parser.optionxform = str # keep case + parser.read(str(config_path)) + + config: dict[str, str] = dict() + + for section in parser.sections(): + logger.info(dict(section=section)) + + if len(section) > 0: + # pattern = section[1:-1] + pattern = section + if not parser[section].get('vim_modeline') is None: + config[pattern] = parser[section].get('vim_modeline') + self.validate_modeline(config[pattern]) + + self.configs[cwd] = config + + return self.configs[cwd] + + @classmethod + def validate_modeline(cls, modeline: str) -> None: + pattern = re.compile(r'^set(\s+(noet|sts|ts|et|ai|ci|noai|noci|sw)(\=\w)?)+$') + assert pattern.match(modeline), 'invalid modeline %s' % modeline + + @classmethod + def find_entry( + cls, + file_path: pathlib.Path, + config: Optional[dict[str, str]] = None, + ) -> Optional[str]: + if config is None: + return None + + project_root = pathlib.Path.cwd() + + if file_path.is_relative_to(project_root): + rel_path = file_path.relative_to(pathlib.Path.cwd()) + else: + rel_path = file_path + + for pattern, modeline in config.items(): + if fnmatch.fnmatch(str(rel_path), pattern): + return modeline + + return None + + def on_buffer(self) -> None: + config = self.load_config() + + logger.info(dict(config=config)) + + buf_name = vim.current.buffer.name + file_path = pathlib.Path(buf_name).resolve() + + entry = self.find_entry(file_path, config=config) + + logger.info(dict(modeline=entry)) + + vim.command('silent! {}'.format(entry)) + + # vim.command("echo '{}'".format('applied %s' % entry)) + + # raise NotImplementedError diff --git a/dotfiles/.py3.vimrc b/dotfiles/.py3.vimrc index 033685e..81d3366 100644 --- a/dotfiles/.py3.vimrc +++ b/dotfiles/.py3.vimrc @@ -1,230 +1,4 @@ -py3 << EOF -from typing import (Optional, ClassVar, Self,) -import configparser -import re -import pathlib -import logging -import fnmatch - -logger = logging.getLogger(__name__) - -logging.basicConfig(level=logging.WARNING) - -def f1(): - t1 = vim.current.window - t2 = t1.width - vim.command('vnew') - t3 = t2 // 3 - vim.command('vertical resize %d' % t3) - vim.current.window = t1 - -def f2(): - context = { - k : vim.options['splitright'] - for k in ['splitright'] - } - try: - current_window = vim.current.window - vim.options['splitright'] = True - vim.command('vnew') - vim.command('r! tmux show-buffer') - vim.current.window = current_window - finally: - for k, v in context.items(): - vim.options[k] = v - -def f5_1(pattern, flags, info): - import subprocess - import io - import re - import tempfile - import traceback - import logging - - #print([pattern, flags, info]) - completed_process = None - - options = dict( - recursive=False, - ext=[], - ) - - #print('fuck') - if b'r' in flags: - while True: - ext_m = re.compile(r'^.([^\,]+),(.*)$').match(pattern) - - if pattern[:3] in [r'\r,']: - options['recursive'] = True - pattern = pattern[3:] - elif not ext_m is None: - options['ext'].append( - ext_m[1] - ) - pattern = ext_m[2] - else: - break - - print([flags, pattern, options,]) - - try: - git_cmd = [ - 'git', 'grep', - '-n', - ] - - if options['recursive']: - git_cmd.append('--recurse-submodules') - - git_cmd.extend(['-P', pattern]) - - if len(options['ext']) > 0: - git_cmd.extend(['--', *[ - '**/*%s' % o - for o in options['ext'] - ]]) - - completed_process = subprocess.run( - git_cmd, - capture_output=True, - ) - assert ( - completed_process.returncode == 0 or - ( - completed_process.stdout == b'' - #completed_process.stdout == b'' and - #completed_process.stderr == b'' - ) - ) - t1 = completed_process.stdout - except: - logging.error(''.join([ - traceback.format_exc(), - getattr(completed_process, 'stdout', b'').decode('utf-8'), - getattr(completed_process, 'stderr', b'').decode('utf-8'), - ])) - t1 = b'' - - def watch(data): - with tempfile.NamedTemporaryFile(suffix='.txt') as f: - with io.open(f.name, 'wb') as f2: - f2.write(data) - vim.command('!less %s' % f.name) - - #watch(t1) - - t2 = [] - for o in t1.splitlines(): - try: - #watch(o.encode('utf-8')) - t3 = o.decode('utf-8') - t4 = re.compile(r'^([^\:\=]+)[\:\=](\d+)[\:\=](.*)$').match(t3) - if not t4 is None: - t2.append( - dict( - name=t4[3].strip(), - filename=t4[1], - cmd=t4[2], - ) - ) - except: - pass - #print(t2) - - #return [{'name': 'blah', 'filename': 'docker-compose.yml', 'cmd': '23'}] - return t2 - -class EditorConfigModeline: - _instance : ClassVar[Optional['EditorConfigModeline']] = None - - def __init__(self) -> None: - self.configs : dict[ - pathlib.Path, - dict[str, str], - ] = dict() - - @classmethod - def singleton(cls) -> Self: - if cls._instance is None: - cls._instance = cls() - - return cls._instance - - def load_config(self) -> Optional[dict[str, str]]: - cwd = pathlib.Path.cwd() - - if not cwd in self.configs: - config_path = cwd / '.editorconfig' - - if not config_path.exists(): - return None - - parser = configparser.ConfigParser() - parser.optionxform = str # keep case - parser.read(str(config_path)) - - config : dict[str, str] = dict() - - for section in parser.sections(): - logger.info(dict(section=section)) - - if len(section) > 0: - # pattern = section[1:-1] - pattern = section - if not parser[section].get('vim_modeline') is None: - config[pattern] = parser[section].get('vim_modeline') - self.validate_modeline(config[pattern]) - - self.configs[cwd] = config - - return self.configs[cwd] - - @classmethod - def validate_modeline(cls, modeline: str) -> None: - pattern = re.compile(r'^set(\s+(noet|sts|ts|et|ai|ci|noai|noci|sw)(\=\w)?)+$') - assert pattern.match(modeline), 'invalid modeline %s' % modeline - - @classmethod - def find_entry( - cls, - file_path: pathlib.Path, - config: Optional[dict[str, str]] = None, - ) -> Optional[str]: - if config is None: - return None - - project_root = pathlib.Path.cwd() - - if file_path.is_relative_to(project_root): - rel_path = file_path.relative_to(pathlib.Path.cwd()) - else: - rel_path = file_path - - for pattern, modeline in config.items(): - if fnmatch.fnmatch(str(rel_path), pattern): - return modeline - - return None - - def on_buffer(self) -> None: - config = self.load_config() - - logger.info(dict(config=config)) - - buf_name = vim.current.buffer.name - file_path = pathlib.Path(buf_name).resolve() - - entry = self.find_entry(file_path, config=config) - - logger.info(dict(modeline=entry)) - - vim.command('silent! {}'.format(entry)) - - # vim.command("echo '{}'".format('applied %s' % entry)) - - # raise NotImplementedError - -EOF +py3file ~/.module.vimrc.py augroup EditorConfigModeline autocmd! diff --git a/python/meson.build b/python/meson.build index 119c0fb..4141ad4 100644 --- a/python/meson.build +++ b/python/meson.build @@ -5,7 +5,7 @@ project( ).stdout().strip('\n'), # 'online.fxreader.uv', # ['c', 'cpp'], - version: '0.1.5.29', + version: '0.1.5.31', # default_options: [ # 'cpp_std=c++23', # # 'prefer_static=true', diff --git a/python/online/fxreader/pr34/commands_typed/pydantic.py b/python/online/fxreader/pr34/commands_typed/pydantic.py index 67cc0b5..c2f4291 100644 --- a/python/online/fxreader/pr34/commands_typed/pydantic.py +++ b/python/online/fxreader/pr34/commands_typed/pydantic.py @@ -28,7 +28,7 @@ def validate_params(view: Callable[..., Awaitable[R]]) -> Callable[..., Awaitabl def validate_params(view: Callable[..., R]) -> Callable[..., R]: ... -def validate_params(view: Callable[..., Awaitable[R]] | Callable[..., R]) -> Any: +def validate_params(view: Callable[..., Awaitable[R]] | Callable[..., R]) -> Callable[..., Awaitable[R]] | Callable[..., R]: class Parameter: kind: Any annotation: Any @@ -89,13 +89,14 @@ def validate_params(view: Callable[..., Awaitable[R]] | Callable[..., R]) -> Any if inspect.iscoroutinefunction(view): async_view = cast(Callable[..., Awaitable[R]], view) - raise NotImplementedError @functools.wraps(async_view) - async def wrapper(*args: Any, **kwargs: Any) -> R: + async def async_wrapper(*args: Any, **kwargs: Any) -> R: validate_params(*args, **kwargs) return await async_view(*args, **kwargs) + + return async_wrapper else: sync_view = cast(Callable[..., R], view) diff --git a/python/pyproject.toml b/python/pyproject.toml index fdeff17..c7eb166 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -78,9 +78,10 @@ include = [ # 'follow_the_leader/**/*.py', #'*.py', # '*.recipe', - '*.py', + './*.py', 'online/**/*.py', 'online/**/*.pyi', + '../dotfiles/.module.vimrc.py', ] exclude = [ '.venv', diff --git a/releases/whl/online_fxreader_pr34-0.1.5.30-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.30-py3-none-any.whl new file mode 100644 index 0000000..aa31e53 --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.30-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:106beb7b687f45404e77c3d05de892d8d51e54e8471cd6a00e714b73fb1010f2 +size 75109 diff --git a/releases/whl/online_fxreader_pr34-0.1.5.31-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.31-py3-none-any.whl new file mode 100644 index 0000000..8478376 --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.31-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d995e0a026bce305e30d458eee51725c6209d44c5a859ee4e5dbaf19b25be3be +size 75089 From 3a76eab761d3556a90fddf371a0bb0bd5de2506f Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Mon, 13 Oct 2025 18:39:44 +0300 Subject: [PATCH 30/70] [+] improve vim py3 modules 1. switch to file logging with rotation; 2. test that at 100ms callback execution UI looks fast; 3. test that hotkey makes the switcher popu; 3.1. TODO, select some better switching UI component; should support a list of more buffers than fit into the screen; should support some real time filtering via regex; --- Makefile | 1 + dotfiles/.beta.vimrc.py | 15 +++++++++------ dotfiles/.module.vimrc.py | 2 +- dotfiles/.py3.vimrc | 39 ++++++++++++++++++++++++++++++++++++++- dotfiles/.vimrc | 2 ++ 5 files changed, 51 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 101cd4e..95a6e7b 100644 --- a/Makefile +++ b/Makefile @@ -87,6 +87,7 @@ dotfiles_vim_put: cp dotfiles/.vimrc ~/.vimrc cp dotfiles/.py3.vimrc ~/.py3.vimrc cp dotfiles/.module.vimrc.py ~/.module.vimrc.py + cp dotfiles/.beta.vimrc.py ~/.beta.vimrc.py PLATFORM ?= macbook_air_2012 PLATFORM_TMP ?= tmp/platform_dotfiles/$(PLATFORM) diff --git a/dotfiles/.beta.vimrc.py b/dotfiles/.beta.vimrc.py index d0c47aa..e1effb6 100644 --- a/dotfiles/.beta.vimrc.py +++ b/dotfiles/.beta.vimrc.py @@ -1,5 +1,6 @@ import functools import configparser +import datetime import collections import asyncio import threading @@ -20,9 +21,6 @@ from typing import ( logger = logging.getLogger(__name__) -logging.basicConfig(level=logging.WARNING) - - MODULE_NAME = 'online_fxreader_pr34_vim' def f1(): @@ -246,7 +244,7 @@ class EditorConfigModeline: class _Vim: @classmethod def run_command(cls, cmd) -> list[str]: - logger.info(dict(cmd=cmd)) + # logger.info(dict(cmd=cmd)) output: list[str] = [] for line in cmd.splitlines(): @@ -293,7 +291,7 @@ class FastSelect: endfunc ''') _Vim.run_command(r''' - call timer_start(1000, 'UIThread', {'repeat': -1}) + call timer_start(100, 'UIThread', {'repeat': -1}) ''') _Vim.run_command(r''' augroup {auto_group} @@ -359,7 +357,7 @@ augroup END logger.warning(dict(buffers=list(vim.buffers), id=selected_id)) # print(vim.buffers, selected_id) - if selected_id >= 0: + if selected_id: vim.current.buffer = vim.buffers[buffers[selected_id][1]] with self._lock: @@ -427,6 +425,11 @@ augroup END def ui_thread(self): with self._lock: + #_Vim.run_command(r''' + # set laststatus=2 + # set statusline={} + #'''.format(datetime.datetime.now().isoformat())) + while len(self._queue) > 0: cmd = self._queue.pop(); logger.warning(dict(msg='start command', cmd=inspect.getsource(cmd))) diff --git a/dotfiles/.module.vimrc.py b/dotfiles/.module.vimrc.py index 62edfd2..3188be7 100644 --- a/dotfiles/.module.vimrc.py +++ b/dotfiles/.module.vimrc.py @@ -20,7 +20,7 @@ from typing import ( logger = logging.getLogger(__name__) -logging.basicConfig(level=logging.WARNING) +# logging.basicConfig(level=logging.WARNING) MODULE_NAME = 'online_fxreader_pr34_vim' diff --git a/dotfiles/.py3.vimrc b/dotfiles/.py3.vimrc index 81d3366..7721a17 100644 --- a/dotfiles/.py3.vimrc +++ b/dotfiles/.py3.vimrc @@ -1,4 +1,41 @@ -py3file ~/.module.vimrc.py +py3 <i5 :set sw=2 sts=2 ts=2 noet ai ci set foldmethod=indent set nofoldenable map e :e # + +map :python3 FastSelect.singleton().switch_buffer() From ecab00f4aa0065c8ec1912bb2d0bd36e5276f825 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Tue, 14 Oct 2025 18:13:20 +0300 Subject: [PATCH 31/70] [+] update vim modules 1. use python modules for separate logic; 2. update popup_menu parameters for fast select; --- .gitignore | 2 + Makefile | 4 +- dotfiles/.py3.vimrc | 28 ++++---- .../.vim/online_fxreader_pr34_vim/__init__.py | 0 .../online_fxreader_pr34_vim/beta.py} | 66 ++++++------------- .../online_fxreader_pr34_vim/main.py} | 20 +++++- .../.vim/online_fxreader_pr34_vim/utils.py | 16 +++++ dotfiles/.vimrc | 2 +- 8 files changed, 76 insertions(+), 62 deletions(-) create mode 100644 dotfiles/.vim/online_fxreader_pr34_vim/__init__.py rename dotfiles/{.beta.vimrc.py => .vim/online_fxreader_pr34_vim/beta.py} (89%) rename dotfiles/{.module.vimrc.py => .vim/online_fxreader_pr34_vim/main.py} (91%) create mode 100644 dotfiles/.vim/online_fxreader_pr34_vim/utils.py diff --git a/.gitignore b/.gitignore index 9aff2d6..f85e4cc 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,5 @@ python/build !docker/*/.env .envs !docker/*/deps/whl/** + +!dotfiles/.vim diff --git a/Makefile b/Makefile index 95a6e7b..a47f1ae 100644 --- a/Makefile +++ b/Makefile @@ -83,11 +83,11 @@ dotfiles_put: dotfiles_vim_put: mkdir -p $(INSTALL_ROOT) + mkdir -p $(INSTALL_ROOT)/.vim cp dotfiles/.vimrc ~/.vimrc cp dotfiles/.py3.vimrc ~/.py3.vimrc - cp dotfiles/.module.vimrc.py ~/.module.vimrc.py - cp dotfiles/.beta.vimrc.py ~/.beta.vimrc.py + cp -rp dotfiles/.vim/online_fxreader_pr34_vim ~/.vim/ PLATFORM ?= macbook_air_2012 PLATFORM_TMP ?= tmp/platform_dotfiles/$(PLATFORM) diff --git a/dotfiles/.py3.vimrc b/dotfiles/.py3.vimrc index 7721a17..ba11c90 100644 --- a/dotfiles/.py3.vimrc +++ b/dotfiles/.py3.vimrc @@ -1,12 +1,18 @@ py3 < list[str]: - # logger.info(dict(cmd=cmd)) - - output: list[str] = [] - for line in cmd.splitlines(): - if line.strip() == '': - continue - output.append( - vim.command(line) - ) - - return output - def future_dump_exception(future: Any) -> None: try: future.result() @@ -287,16 +274,16 @@ class FastSelect: vim.command(r''' func! UIThread(timer_id) - python3 FastSelect.singleton().ui_thread() + python3 online_fxreader_pr34_vim.beta.FastSelect.singleton().ui_thread() endfunc ''') - _Vim.run_command(r''' + Vim.run_command(r''' call timer_start(100, 'UIThread', {'repeat': -1}) ''') - _Vim.run_command(r''' + Vim.run_command(r''' augroup {auto_group} autocmd! - autocmd VimLeavePre * python3 FastSelect.singleton().close() + autocmd VimLeavePre * python3 online_fxreader_pr34_vim.beta.FastSelect.singleton().close() augroup END '''.format( auto_group=auto_group, @@ -357,7 +344,7 @@ augroup END logger.warning(dict(buffers=list(vim.buffers), id=selected_id)) # print(vim.buffers, selected_id) - if selected_id: + if not selected_id is None: vim.current.buffer = vim.buffers[buffers[selected_id][1]] with self._lock: @@ -375,28 +362,6 @@ augroup END logger.info(dict(msg='after switch_buffer started')) - def pick_file_from_popup( - self, - paths: list[pathlib.Path], - ) -> None: - - _Vim.run_command(r''' - call popup_menu(['asdasdfasdfasdfasdfasdfasdfasdff', 'adfadf'], {'title': '!!!!sdfasdf'}) - ''') - return - - logger.info(dict(msg='before pick started')) - result = asyncio.run_coroutine_threadsafe( - self._pick_option_from_popup( - options=[str(o) for o in paths] - ), - self.loop - ) - - result.add_done_callback(future_dump_exception) - - logger.info(dict(msg='after pick started')) - async def _pick_option_from_popup( self, options: list[str], @@ -425,7 +390,7 @@ augroup END def ui_thread(self): with self._lock: - #_Vim.run_command(r''' + #Vim.run_command(r''' # set laststatus=2 # set statusline={} #'''.format(datetime.datetime.now().isoformat())) @@ -456,9 +421,9 @@ augroup END vim.command(r""" function! {callback_name}(id, result) if a:result > 0 - call py3eval('FastSelect.singleton().pick_option_put_id(' . (a:result - 1). ')') + call py3eval('online_fxreader_pr34_vim.beta.FastSelect.singleton().pick_option_put_id(' . (a:result - 1). ')') else - call py3eval('FastSelect.singleton().pick_option_put_id(-1)') + call py3eval('online_fxreader_pr34_vim.beta.FastSelect.singleton().pick_option_put_id(-1)') endif endfunction """.format( @@ -475,7 +440,13 @@ augroup END self._options, { 'title': 'Select a file', - 'callback': callback_name + 'callback': callback_name, + 'wrap': 1, + 'maxwidth': 80, + 'close': 'button', + 'resize': 1, + 'drag': 1, + 'maxheight': '16', } ) #lambda : vim.command( @@ -496,3 +467,6 @@ augroup END # logger.info(dict(popup_id=popup_id)) # logger.info(dict(msg='after popup')) + +def init(): + EditorConfigModeline.singleton() diff --git a/dotfiles/.module.vimrc.py b/dotfiles/.vim/online_fxreader_pr34_vim/main.py similarity index 91% rename from dotfiles/.module.vimrc.py rename to dotfiles/.vim/online_fxreader_pr34_vim/main.py index 3188be7..d64708f 100644 --- a/dotfiles/.module.vimrc.py +++ b/dotfiles/.vim/online_fxreader_pr34_vim/main.py @@ -20,6 +20,8 @@ from typing import ( logger = logging.getLogger(__name__) +from .utils import Vim + # logging.basicConfig(level=logging.WARNING) @@ -161,6 +163,13 @@ class EditorConfigModeline: dict[str, str], ] = dict() + Vim.run_command(r''' +augroup EditorConfigModeline + autocmd! + autocmd BufEnter * python3 import online_fxreader_pr34_vim.main; online_fxreader_pr34_vim.main.EditorConfigModeline.singleton().on_buffer() +augroup END + ''') + @classmethod def singleton(cls) -> Self: if cls._instance is None: @@ -179,7 +188,11 @@ class EditorConfigModeline: parser = configparser.ConfigParser() parser.optionxform = str # keep case - parser.read(str(config_path)) + try: + parser.read(str(config_path)) + except: + logger.exception('') + return None config: dict[str, str] = dict() @@ -241,3 +254,8 @@ class EditorConfigModeline: # vim.command("echo '{}'".format('applied %s' % entry)) # raise NotImplementedError + +# EditorConfigModeline.singleton() + +def init(): + EditorConfigModeline.singleton() diff --git a/dotfiles/.vim/online_fxreader_pr34_vim/utils.py b/dotfiles/.vim/online_fxreader_pr34_vim/utils.py new file mode 100644 index 0000000..ebe86fb --- /dev/null +++ b/dotfiles/.vim/online_fxreader_pr34_vim/utils.py @@ -0,0 +1,16 @@ +import vim + +class Vim: + @classmethod + def run_command(cls, cmd) -> list[str]: + # logger.info(dict(cmd=cmd)) + + output: list[str] = [] + for line in cmd.splitlines(): + if line.strip() == '': + continue + output.append( + vim.command(line) + ) + + return output diff --git a/dotfiles/.vimrc b/dotfiles/.vimrc index c10f2e0..3dc7618 100644 --- a/dotfiles/.vimrc +++ b/dotfiles/.vimrc @@ -88,4 +88,4 @@ set foldmethod=indent set nofoldenable map e :e # -map :python3 FastSelect.singleton().switch_buffer() +map :python3 import online_fxreader_pr34_vim.beta; online_fxreader_pr34_vim.beta.FastSelect.singleton().switch_buffer() From 8516fbfcad22d28f08fd46a3c3367d129197f9d5 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Wed, 15 Oct 2025 21:06:01 +0300 Subject: [PATCH 32/70] [+] update beta vim module 1. remove duplicate logic from beta, that is present in main; 2. fix init for FastSelect; 3. implement last recently used ordering for buffers selection; --- .../.vim/online_fxreader_pr34_vim/beta.py | 246 ++---------------- 1 file changed, 26 insertions(+), 220 deletions(-) diff --git a/dotfiles/.vim/online_fxreader_pr34_vim/beta.py b/dotfiles/.vim/online_fxreader_pr34_vim/beta.py index ef21214..9526ab3 100644 --- a/dotfiles/.vim/online_fxreader_pr34_vim/beta.py +++ b/dotfiles/.vim/online_fxreader_pr34_vim/beta.py @@ -25,224 +25,6 @@ logger = logging.getLogger(__name__) MODULE_NAME = 'online_fxreader_pr34_vim' -def f1(): - t1 = vim.current.window - t2 = t1.width - vim.command('vnew') - t3 = t2 // 3 - vim.command('vertical resize %d' % t3) - vim.current.window = t1 - - -def f2(): - context = {k: vim.options['splitright'] for k in ['splitright']} - try: - current_window = vim.current.window - vim.options['splitright'] = True - vim.command('vnew') - vim.command('r! tmux show-buffer') - vim.current.window = current_window - finally: - for k, v in context.items(): - vim.options[k] = v - - -def f5_1(pattern, flags, info): - import subprocess - import io - import re - import tempfile - import traceback - import logging - - # print([pattern, flags, info]) - completed_process = None - - options = dict( - recursive=False, - ext=[], - ) - - # print('fuck') - if b'r' in flags: - while True: - ext_m = re.compile(r'^.([^\,]+),(.*)$').match(pattern) - - if pattern[:3] in [r'\r,']: - options['recursive'] = True - pattern = pattern[3:] - elif not ext_m is None: - options['ext'].append(ext_m[1]) - pattern = ext_m[2] - else: - break - - print( - [ - flags, - pattern, - options, - ] - ) - - try: - git_cmd = [ - 'git', - 'grep', - '-n', - ] - - if options['recursive']: - git_cmd.append('--recurse-submodules') - - git_cmd.extend(['-P', pattern]) - - if len(options['ext']) > 0: - git_cmd.extend(['--', *['**/*%s' % o for o in options['ext']]]) - - completed_process = subprocess.run( - git_cmd, - capture_output=True, - ) - assert completed_process.returncode == 0 or ( - completed_process.stdout == b'' - # completed_process.stdout == b'' and - # completed_process.stderr == b'' - ) - t1 = completed_process.stdout - except: - logging.error( - ''.join( - [ - traceback.format_exc(), - getattr(completed_process, 'stdout', b'').decode('utf-8'), - getattr(completed_process, 'stderr', b'').decode('utf-8'), - ] - ) - ) - t1 = b'' - - def watch(data): - with tempfile.NamedTemporaryFile(suffix='.txt') as f: - with io.open(f.name, 'wb') as f2: - f2.write(data) - vim.command('!less %s' % f.name) - - # watch(t1) - - t2 = [] - for o in t1.splitlines(): - try: - # watch(o.encode('utf-8')) - t3 = o.decode('utf-8') - t4 = re.compile(r'^([^\:\=]+)[\:\=](\d+)[\:\=](.*)$').match(t3) - if not t4 is None: - t2.append( - dict( - name=t4[3].strip(), - filename=t4[1], - cmd=t4[2], - ) - ) - except: - pass - # print(t2) - - # return [{'name': 'blah', 'filename': 'docker-compose.yml', 'cmd': '23'}] - return t2 - - -class EditorConfigModeline: - _instance: ClassVar[Optional['EditorConfigModeline']] = None - - def __init__(self) -> None: - self.configs: dict[ - pathlib.Path, - dict[str, str], - ] = dict() - - @classmethod - def singleton(cls) -> Self: - if cls._instance is None: - cls._instance = cls() - - return cls._instance - - def load_config(self) -> Optional[dict[str, str]]: - cwd = pathlib.Path.cwd() - - if not cwd in self.configs: - config_path = cwd / '.editorconfig' - - if not config_path.exists(): - return None - - parser = configparser.ConfigParser() - parser.optionxform = str # keep case - parser.read(str(config_path)) - - config: dict[str, str] = dict() - - for section in parser.sections(): - logger.info(dict(section=section)) - - if len(section) > 0: - # pattern = section[1:-1] - pattern = section - if not parser[section].get('vim_modeline') is None: - config[pattern] = parser[section].get('vim_modeline') - self.validate_modeline(config[pattern]) - - self.configs[cwd] = config - - return self.configs[cwd] - - @classmethod - def validate_modeline(cls, modeline: str) -> None: - pattern = re.compile(r'^set(\s+(noet|sts|ts|et|ai|ci|noai|noci|sw)(\=\w)?)+$') - assert pattern.match(modeline), 'invalid modeline %s' % modeline - - @classmethod - def find_entry( - cls, - file_path: pathlib.Path, - config: Optional[dict[str, str]] = None, - ) -> Optional[str]: - if config is None: - return None - - project_root = pathlib.Path.cwd() - - if file_path.is_relative_to(project_root): - rel_path = file_path.relative_to(pathlib.Path.cwd()) - else: - rel_path = file_path - - for pattern, modeline in config.items(): - if fnmatch.fnmatch(str(rel_path), pattern): - return modeline - - return None - - def on_buffer(self) -> None: - config = self.load_config() - - logger.info(dict(config=config)) - - buf_name = vim.current.buffer.name - file_path = pathlib.Path(buf_name).resolve() - - entry = self.find_entry(file_path, config=config) - - logger.info(dict(modeline=entry)) - - vim.command('silent! {}'.format(entry)) - - # vim.command("echo '{}'".format('applied %s' % entry)) - - # raise NotImplementedError - - def future_dump_exception(future: Any) -> None: try: future.result() @@ -259,6 +41,8 @@ class FastSelect: target=self.loop.run_forever, ) + self._buffer_frequency : dict[int, int] = dict() + self._queue : collections.deque[Callable[[], None]] = collections.deque() self._lock = threading.Lock() @@ -284,6 +68,7 @@ class FastSelect: augroup {auto_group} autocmd! autocmd VimLeavePre * python3 online_fxreader_pr34_vim.beta.FastSelect.singleton().close() + autocmd BufEnter * python3 online_fxreader_pr34_vim.beta.FastSelect.singleton().on_buf_enter() augroup END '''.format( auto_group=auto_group, @@ -321,8 +106,13 @@ augroup END for o in vim.buffers ] + res_sorted = sorted( + res, + key=lambda x: -self._buffer_frequency.get(x[1], 0) + ) + self.loop.call_soon_threadsafe( - lambda: buffers_future.set_result(res) + lambda: buffers_future.set_result(res_sorted) ) with self._lock: @@ -406,6 +196,22 @@ augroup END # vim.command(cmd) # ) + def on_buf_enter(self) -> None: + with self._lock: + buf_number = vim.current.buffer.number + + if not buf_number in self._buffer_frequency: + self._buffer_frequency[buf_number] = 0 + + self._buffer_frequency[buf_number] += 1 + + logger.info(dict( + msg='updated', + buf_path=vim.current.buffer.name, + frequency=self._buffer_frequency[buf_number], + buf_number=buf_number, + )) + async def _pick_option_start_popup( self, ): @@ -469,4 +275,4 @@ augroup END # logger.info(dict(msg='after popup')) def init(): - EditorConfigModeline.singleton() + FastSelect.singleton() From 656464fe3844505cf4119d23585029798911d772 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Wed, 15 Oct 2025 21:21:41 +0300 Subject: [PATCH 33/70] [+] update FastSelect 1. sort buffers based on last access time; --- .../.vim/online_fxreader_pr34_vim/beta.py | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/dotfiles/.vim/online_fxreader_pr34_vim/beta.py b/dotfiles/.vim/online_fxreader_pr34_vim/beta.py index 9526ab3..974a61d 100644 --- a/dotfiles/.vim/online_fxreader_pr34_vim/beta.py +++ b/dotfiles/.vim/online_fxreader_pr34_vim/beta.py @@ -42,6 +42,7 @@ class FastSelect: ) self._buffer_frequency : dict[int, int] = dict() + self._buffer_last_used : dict[int, int] = dict() self._queue : collections.deque[Callable[[], None]] = collections.deque() self._lock = threading.Lock() @@ -108,7 +109,8 @@ augroup END res_sorted = sorted( res, - key=lambda x: -self._buffer_frequency.get(x[1], 0) + # key=lambda x: -self._buffer_frequency.get(x[1], 0) + key=lambda x: -self._buffer_last_used.get(x[1], 0) ) self.loop.call_soon_threadsafe( @@ -197,17 +199,36 @@ augroup END # ) def on_buf_enter(self) -> None: + result = asyncio.run_coroutine_threadsafe( + self._on_buf_enter( + buf_number=vim.current.buffer.number, + buf_name=pathlib.Path(vim.current.buffer.name), + ), + self.loop + ) + + result.add_done_callback(future_dump_exception) + + async def _on_buf_enter( + self, + buf_number: int, + buf_name: pathlib.Path, + ) -> None: + # logger.info(dict(msg='waiting')) + with self._lock: - buf_number = vim.current.buffer.number + # buf_number = vim.current.buffer.number if not buf_number in self._buffer_frequency: self._buffer_frequency[buf_number] = 0 self._buffer_frequency[buf_number] += 1 + self._buffer_last_used[buf_number] = datetime.datetime.now().timestamp() + logger.info(dict( msg='updated', - buf_path=vim.current.buffer.name, + buf_path=str(buf_name), frequency=self._buffer_frequency[buf_number], buf_number=buf_number, )) From f59e0bbe6c134145d2d67631a080294e83b1804d Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Wed, 15 Oct 2025 21:48:30 +0300 Subject: [PATCH 34/70] [+] improve vim, beta 1. add filter callback; 1.1. partially added recording of pressed keys; 1.2. TODO, handle esape to close the window; handle to select the entry; handle hjkl, arrows to change the selection; handle pressed keys otherwise, ideally after i has been pressed, or handle hjkl after escape has been presed; to restrict entries to the matching pattern visualize current filter, and ins/normal mode in the popup title; --- .../.vim/online_fxreader_pr34_vim/beta.py | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/dotfiles/.vim/online_fxreader_pr34_vim/beta.py b/dotfiles/.vim/online_fxreader_pr34_vim/beta.py index 974a61d..266d2a3 100644 --- a/dotfiles/.vim/online_fxreader_pr34_vim/beta.py +++ b/dotfiles/.vim/online_fxreader_pr34_vim/beta.py @@ -44,6 +44,8 @@ class FastSelect: self._buffer_frequency : dict[int, int] = dict() self._buffer_last_used : dict[int, int] = dict() + self._filter_pattern : Optional[str] = None + self._queue : collections.deque[Callable[[], None]] = collections.deque() self._lock = threading.Lock() @@ -160,6 +162,8 @@ augroup END ) -> Optional[int]: logger.info(dict(msg='started')) + self._filter_pattern = '' + self._options = options self._option_id = asyncio.Future[int]() @@ -209,6 +213,22 @@ augroup END result.add_done_callback(future_dump_exception) + def on_filter_key(self, key: str) -> None: + logger.info(dict(msg='got key', key=key)) + + try: + key_str = key.decode('utf-8') + except: + return 0 + + if not key_str.isprintable(): + return 0 + + with self._lock: + self._filter_pattern += key_str + + return 1 + async def _on_buf_enter( self, buf_number: int, @@ -242,6 +262,12 @@ augroup END 'popup_callback', ).capitalize() + filter_name = '{}_{}_{}'.format( + MODULE_NAME, + type(self).__name__.lower(), + 'popup_filter', + ).capitalize() + if int(vim.eval('exists("{}")'.format(callback_name))) == 1: logger.warning(dict(msg='callback already defined, %s' % callback_name)) @@ -257,6 +283,14 @@ augroup END callback_name=callback_name, )) + vim.command(r""" + function! {filter_name}(win_id, key) + return py3eval('online_fxreader_pr34_vim.beta.FastSelect.singleton().on_filter_key(key)', #{key: a:key}) + endfunction + """.replace( + '{filter_name}', filter_name, + )) + logger.info(dict(msg='before popup')) popup_menu = vim.Function('popup_menu') @@ -268,6 +302,7 @@ augroup END { 'title': 'Select a file', 'callback': callback_name, + 'filter': filter_name, 'wrap': 1, 'maxwidth': 80, 'close': 'button', From f657d63522928358ce4435dc24c133d0efce6386 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Thu, 16 Oct 2025 17:13:50 +0300 Subject: [PATCH 35/70] [+] update vim, python3 plugin 1. handle escape character; 2. handle items filtering based on entered letters; 3. redraw UI when the filter changes; 4. TODO handle selection changes via arrows, hjkl; 5. TODO handle selection after Enter has been pressed; --- .../.vim/online_fxreader_pr34_vim/beta.py | 218 +++++++++++------- .../.vim/online_fxreader_pr34_vim/main.py | 7 +- .../.vim/online_fxreader_pr34_vim/utils.py | 5 +- python/pyproject.toml | 2 +- 4 files changed, 139 insertions(+), 93 deletions(-) diff --git a/dotfiles/.vim/online_fxreader_pr34_vim/beta.py b/dotfiles/.vim/online_fxreader_pr34_vim/beta.py index 266d2a3..604be1f 100644 --- a/dotfiles/.vim/online_fxreader_pr34_vim/beta.py +++ b/dotfiles/.vim/online_fxreader_pr34_vim/beta.py @@ -25,12 +25,14 @@ logger = logging.getLogger(__name__) MODULE_NAME = 'online_fxreader_pr34_vim' + def future_dump_exception(future: Any) -> None: try: future.result() except: logger.exception('') + class FastSelect: _instance: ClassVar[Optional['FastSelect']] = None @@ -41,16 +43,20 @@ class FastSelect: target=self.loop.run_forever, ) - self._buffer_frequency : dict[int, int] = dict() - self._buffer_last_used : dict[int, int] = dict() + self._buffer_frequency: dict[int, int] = dict() + self._buffer_last_used: dict[int, int] = dict() - self._filter_pattern : Optional[str] = None + self._filter_pattern: Optional[str] = None + self._items: Optional[list[tuple[str, int]]] = None + self._filtered_ids: Optional[set[int]] = None - self._queue : collections.deque[Callable[[], None]] = collections.deque() + self._queue: collections.deque[Callable[[], None]] = collections.deque() self._lock = threading.Lock() + self.popup_id: Optional[int] = None + self.thread.start() - self._option_id : asyncio.Future[Optional[int]] = None + self._option_id: asyncio.Future[Optional[int]] = None self._options: list[str] = None auto_group = '{}_{}_{}'.format( @@ -59,24 +65,25 @@ class FastSelect: 'close', ).capitalize() - vim.command(r''' + vim.command(r""" func! UIThread(timer_id) python3 online_fxreader_pr34_vim.beta.FastSelect.singleton().ui_thread() endfunc - ''') - Vim.run_command(r''' + """) + Vim.run_command(r""" call timer_start(100, 'UIThread', {'repeat': -1}) - ''') - Vim.run_command(r''' + """) + Vim.run_command( + r""" augroup {auto_group} autocmd! autocmd VimLeavePre * python3 online_fxreader_pr34_vim.beta.FastSelect.singleton().close() autocmd BufEnter * python3 online_fxreader_pr34_vim.beta.FastSelect.singleton().on_buf_enter() augroup END - '''.format( - auto_group=auto_group, - )) - + """.format( + auto_group=auto_group, + ) + ) def __del__(self) -> None: self.close() @@ -96,28 +103,21 @@ augroup END return cls._instance def pick_option_put_id(self, option_id: int) -> None: - self.loop.call_soon_threadsafe( - lambda: self._option_id.set_result(option_id) - ) + self.loop.call_soon_threadsafe(lambda: self._option_id.set_result(option_id)) async def _switch_buffer(self) -> None: - buffers_future : asyncio.Future[list[tuple[str, int]]] = asyncio.Future() + buffers_future: asyncio.Future[list[tuple[str, int]]] = asyncio.Future() def get_buffers() -> list[tuple[str, int]]: - res = [ - (o.name, o.number) - for o in vim.buffers - ] + res = [(o.name, o.number) for o in vim.buffers] res_sorted = sorted( res, # key=lambda x: -self._buffer_frequency.get(x[1], 0) - key=lambda x: -self._buffer_last_used.get(x[1], 0) + key=lambda x: -self._buffer_last_used.get(x[1], 0), ) - self.loop.call_soon_threadsafe( - lambda: buffers_future.set_result(res_sorted) - ) + self.loop.call_soon_threadsafe(lambda: buffers_future.set_result(res_sorted)) with self._lock: self._queue.append(get_buffers) @@ -126,8 +126,13 @@ augroup END logger.info(dict(buffers=buffers[:3])) + self._items = buffers + + with self._lock: + self._set_filter_pattern('') + selected_id = await self._pick_option_from_popup( - [o[0] for o in buffers] + # [o[0] for o in buffers] ) logger.info(dict(selected_id=selected_id)) @@ -146,25 +151,22 @@ augroup END def switch_buffer(self) -> None: logger.info(dict(msg='before switch_buffer started')) - result = asyncio.run_coroutine_threadsafe( - self._switch_buffer(), - self.loop - ) + result = asyncio.run_coroutine_threadsafe(self._switch_buffer(), self.loop) result.add_done_callback(future_dump_exception) logger.info(dict(msg='after switch_buffer started')) - async def _pick_option_from_popup( self, - options: list[str], + # options: list[str], ) -> Optional[int]: logger.info(dict(msg='started')) self._filter_pattern = '' + self._popup_id = None - self._options = options + # self._options = options self._option_id = asyncio.Future[int]() @@ -186,13 +188,13 @@ augroup END def ui_thread(self): with self._lock: - #Vim.run_command(r''' - # set laststatus=2 - # set statusline={} + # Vim.run_command(r''' + # set laststatus=2 + # set statusline={} #'''.format(datetime.datetime.now().isoformat())) while len(self._queue) > 0: - cmd = self._queue.pop(); + cmd = self._queue.pop() logger.warning(dict(msg='start command', cmd=inspect.getsource(cmd))) try: cmd() @@ -208,7 +210,7 @@ augroup END buf_number=vim.current.buffer.number, buf_name=pathlib.Path(vim.current.buffer.name), ), - self.loop + self.loop, ) result.add_done_callback(future_dump_exception) @@ -216,19 +218,50 @@ augroup END def on_filter_key(self, key: str) -> None: logger.info(dict(msg='got key', key=key)) - try: - key_str = key.decode('utf-8') - except: - return 0 + if key == bytes([27]): + logger.info(dict(msg='closing popup')) - if not key_str.isprintable(): - return 0 + vim.Function('popup_close')(self._popup_id) + return 1 - with self._lock: - self._filter_pattern += key_str + if key == b'\x80kb': + logger.info(dict(msg='backspace')) + + with self._lock: + self._set_filter_pattern(self._filter_pattern[:-1]) + else: + try: + key_str = key.decode('utf-8') + except: + return 0 + + if not key_str.isprintable(): + return 0 + else: + + with self._lock: + self._set_filter_pattern(self._filter_pattern + key_str) + + self._update_popup() return 1 + def _set_filter_pattern(self, filter_pattern: str) -> None: + self._filter_pattern = filter_pattern + + pattern = re.compile(self._filter_pattern) + + self._filtered_ids = [i for i, o in enumerate(self._items) if not pattern.search(o[0]) is None] + + self._options = [self._items[o][0] for o in self._filtered_ids] + + def _update_popup(self) -> None: + vim.Function('popup_settext')( + self._popup_id, + self._options, + ) + vim.Function('popup_setoptions')(self._popup_id, {'title': 'Select a file, [%s]' % self._filter_pattern}) + async def _on_buf_enter( self, buf_number: int, @@ -246,12 +279,14 @@ augroup END self._buffer_last_used[buf_number] = datetime.datetime.now().timestamp() - logger.info(dict( - msg='updated', - buf_path=str(buf_name), - frequency=self._buffer_frequency[buf_number], - buf_number=buf_number, - )) + logger.info( + dict( + msg='updated', + buf_path=str(buf_name), + frequency=self._buffer_frequency[buf_number], + buf_number=buf_number, + ) + ) async def _pick_option_start_popup( self, @@ -271,7 +306,8 @@ augroup END if int(vim.eval('exists("{}")'.format(callback_name))) == 1: logger.warning(dict(msg='callback already defined, %s' % callback_name)) - vim.command(r""" + vim.command( + r""" function! {callback_name}(id, result) if a:result > 0 call py3eval('online_fxreader_pr34_vim.beta.FastSelect.singleton().pick_option_put_id(' . (a:result - 1). ')') @@ -280,55 +316,63 @@ augroup END endif endfunction """.format( - callback_name=callback_name, - )) + callback_name=callback_name, + ) + ) - vim.command(r""" + vim.command( + r""" function! {filter_name}(win_id, key) return py3eval('online_fxreader_pr34_vim.beta.FastSelect.singleton().on_filter_key(key)', #{key: a:key}) endfunction """.replace( - '{filter_name}', filter_name, - )) + '{filter_name}', + filter_name, + ) + ) logger.info(dict(msg='before popup')) popup_menu = vim.Function('popup_menu') + def create_popup(): + self._popup_id = popup_menu( + self._options, + { + 'title': 'Select a file', + 'callback': callback_name, + 'filter': filter_name, + 'wrap': 1, + 'maxwidth': 80, + 'close': 'button', + 'resize': 1, + 'drag': 1, + 'maxheight': '16', + }, + ) + with self._lock: self._queue.append( - lambda : popup_menu( - self._options, - { - 'title': 'Select a file', - 'callback': callback_name, - 'filter': filter_name, - 'wrap': 1, - 'maxwidth': 80, - 'close': 'button', - 'resize': 1, - 'drag': 1, - 'maxheight': '16', - } - ) - #lambda : vim.command( - # "call popup_menu({options}, {'title': '{title}', 'callback': '{callback}'})".replace( - # '{options}', '[%s]' % ','.join([ - # '\'%s\'' % o.replace('\'', '\\\'') - # for o in self._options - # ]), - # ).replace( - # '{title}', 'Select a file', - # ).replace( - # '{callback}', - # callback_name - # ) - #) + create_popup, + # lambda : vim.command( + # "call popup_menu({options}, {'title': '{title}', 'callback': '{callback}'})".replace( + # '{options}', '[%s]' % ','.join([ + # '\'%s\'' % o.replace('\'', '\\\'') + # for o in self._options + # ]), + # ).replace( + # '{title}', 'Select a file', + # ).replace( + # '{callback}', + # callback_name + # ) + # ) ) # logger.info(dict(popup_id=popup_id)) # logger.info(dict(msg='after popup')) + def init(): - FastSelect.singleton() + FastSelect.singleton() diff --git a/dotfiles/.vim/online_fxreader_pr34_vim/main.py b/dotfiles/.vim/online_fxreader_pr34_vim/main.py index d64708f..ceaa21a 100644 --- a/dotfiles/.vim/online_fxreader_pr34_vim/main.py +++ b/dotfiles/.vim/online_fxreader_pr34_vim/main.py @@ -27,6 +27,7 @@ from .utils import Vim MODULE_NAME = 'online_fxreader_pr34_vim' + def f1(): t1 = vim.current.window t2 = t1.width @@ -163,12 +164,12 @@ class EditorConfigModeline: dict[str, str], ] = dict() - Vim.run_command(r''' + Vim.run_command(r""" augroup EditorConfigModeline autocmd! autocmd BufEnter * python3 import online_fxreader_pr34_vim.main; online_fxreader_pr34_vim.main.EditorConfigModeline.singleton().on_buffer() augroup END - ''') + """) @classmethod def singleton(cls) -> Self: @@ -255,7 +256,9 @@ augroup END # raise NotImplementedError + # EditorConfigModeline.singleton() + def init(): EditorConfigModeline.singleton() diff --git a/dotfiles/.vim/online_fxreader_pr34_vim/utils.py b/dotfiles/.vim/online_fxreader_pr34_vim/utils.py index ebe86fb..ca4a712 100644 --- a/dotfiles/.vim/online_fxreader_pr34_vim/utils.py +++ b/dotfiles/.vim/online_fxreader_pr34_vim/utils.py @@ -1,5 +1,6 @@ import vim + class Vim: @classmethod def run_command(cls, cmd) -> list[str]: @@ -9,8 +10,6 @@ class Vim: for line in cmd.splitlines(): if line.strip() == '': continue - output.append( - vim.command(line) - ) + output.append(vim.command(line)) return output diff --git a/python/pyproject.toml b/python/pyproject.toml index c7eb166..8b0e03a 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -81,7 +81,7 @@ include = [ './*.py', 'online/**/*.py', 'online/**/*.pyi', - '../dotfiles/.module.vimrc.py', + '../dotfiles/.vim/**/*.py', ] exclude = [ '.venv', From 2eadc88afccdbd50b53db36a2de0e04757cc96c8 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Thu, 16 Oct 2025 17:20:21 +0300 Subject: [PATCH 36/70] [+] update vim, python plugin 1. fallback on_filter_key to popup_filter_menu; the default callback; --- dotfiles/.vim/online_fxreader_pr34_vim/beta.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/dotfiles/.vim/online_fxreader_pr34_vim/beta.py b/dotfiles/.vim/online_fxreader_pr34_vim/beta.py index 604be1f..2e046e4 100644 --- a/dotfiles/.vim/online_fxreader_pr34_vim/beta.py +++ b/dotfiles/.vim/online_fxreader_pr34_vim/beta.py @@ -233,12 +233,18 @@ augroup END try: key_str = key.decode('utf-8') except: - return 0 + return vim.Function('popup_filter_menu')( + self._popup_id, key + ) + # return 0 if not key_str.isprintable(): - return 0 - else: + return vim.Function('popup_filter_menu')( + self._popup_id, key + ) + # return 0 + else: with self._lock: self._set_filter_pattern(self._filter_pattern + key_str) From 6b589c099f75e1ea7b03c177705f6d3b4029de00 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Thu, 16 Oct 2025 17:26:22 +0300 Subject: [PATCH 37/70] [+] update vim, python plugin 1. handle remapping after filtering; --- dotfiles/.vim/online_fxreader_pr34_vim/beta.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotfiles/.vim/online_fxreader_pr34_vim/beta.py b/dotfiles/.vim/online_fxreader_pr34_vim/beta.py index 2e046e4..475d626 100644 --- a/dotfiles/.vim/online_fxreader_pr34_vim/beta.py +++ b/dotfiles/.vim/online_fxreader_pr34_vim/beta.py @@ -182,7 +182,7 @@ augroup END logger.info(dict(msg='done')) if option_id >= 0: - return option_id + return self._filtered_ids[option_id] else: return None From 067638315b36925ab065f215d44638c508f8e35b Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Fri, 17 Oct 2025 16:04:13 +0300 Subject: [PATCH 38/70] [+] update vim, python plugin 1. fix autosuggest function calling; --- dotfiles/.py3.vimrc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dotfiles/.py3.vimrc b/dotfiles/.py3.vimrc index ba11c90..a80ca9c 100644 --- a/dotfiles/.py3.vimrc +++ b/dotfiles/.py3.vimrc @@ -47,8 +47,10 @@ EOF python3 load() function! F5(pattern, flags, info) + python3 import online_fxreader_pr34_vim.main; + let res = py3eval( - \'f5_1( + \'online_fxreader_pr34_vim.main.f5_1( \vim.bindeval("a:pattern").decode("utf-8"), \vim.bindeval("a:flags"), \vim.bindeval("a:info") From c3ce3979bbe0f52a4d504e52d788343a0d660591 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Sat, 18 Oct 2025 09:58:33 +0300 Subject: [PATCH 39/70] [+] improve tmux status line --- dotfiles/.tmux.conf | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dotfiles/.tmux.conf b/dotfiles/.tmux.conf index e53dba3..c3ee646 100644 --- a/dotfiles/.tmux.conf +++ b/dotfiles/.tmux.conf @@ -37,7 +37,8 @@ set-option -g pane-active-border-style "bg=#33dd44 fg=#ffffff" bind space display "Fuck!" set-option -g set-titles on set-option -g set-titles-string "#S / #W" -set -g status-right "#H %H:%M:%S %Y-%m-%d %Z" +# set -g status-right "#H %H:%M:%S %Y-%m-%d %Z" +set -g status-right "#{=-16:pane_current_path} #{pane_index} #H %H:%M:%S %Y-%m-%d %Z" set -g status-interval 1 -set -g status-right-length 60 +set -g status-right-length 64 set -g mouse on From 68d1de72ecd2dc07568c0ed86308614dcfdfd488 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Sat, 18 Oct 2025 11:41:01 +0300 Subject: [PATCH 40/70] [+] improve vim, python plugin 1. add files selection from git ls-files; --- .../.vim/online_fxreader_pr34_vim/beta.py | 175 +++++++++++++++--- .../.vim/online_fxreader_pr34_vim/utils.py | 4 +- 2 files changed, 153 insertions(+), 26 deletions(-) diff --git a/dotfiles/.vim/online_fxreader_pr34_vim/beta.py b/dotfiles/.vim/online_fxreader_pr34_vim/beta.py index 475d626..3053c4a 100644 --- a/dotfiles/.vim/online_fxreader_pr34_vim/beta.py +++ b/dotfiles/.vim/online_fxreader_pr34_vim/beta.py @@ -1,5 +1,8 @@ import functools import configparser +import subprocess +import dataclasses +import json import datetime import collections import asyncio @@ -47,9 +50,13 @@ class FastSelect: self._buffer_last_used: dict[int, int] = dict() self._filter_pattern: Optional[str] = None - self._items: Optional[list[tuple[str, int]]] = None + self._include_git : Optional[bool] = False self._filtered_ids: Optional[set[int]] = None + self._items: Optional[list['self.entry_t']] = None + self._buffers: Optional[list['self.entry_t']] = None + self._tracked_files: Optional[list['self.entry_t']] = None + self._queue: collections.deque[Callable[[], None]] = collections.deque() self._lock = threading.Lock() @@ -105,28 +112,37 @@ augroup END def pick_option_put_id(self, option_id: int) -> None: self.loop.call_soon_threadsafe(lambda: self._option_id.set_result(option_id)) - async def _switch_buffer(self) -> None: - buffers_future: asyncio.Future[list[tuple[str, int]]] = asyncio.Future() + @dataclasses.dataclass + class entry_t: + path: pathlib.Path + buf_number: Optional[int] = None - def get_buffers() -> list[tuple[str, int]]: - res = [(o.name, o.number) for o in vim.buffers] - - res_sorted = sorted( - res, - # key=lambda x: -self._buffer_frequency.get(x[1], 0) - key=lambda x: -self._buffer_last_used.get(x[1], 0), + def _get_buffers( + self, + res_future: Optional[asyncio.Future[ + list[entry_t] + ]] = None, + ) -> list[entry_t]: + res = [ + self.entry_t( + buf_number=o.number, + path=pathlib.Path(o.name).absolute(), ) + for o in vim.buffers + ] - self.loop.call_soon_threadsafe(lambda: buffers_future.set_result(res_sorted)) + if res_future: + self.loop.call_soon_threadsafe(lambda: res_future.set_result(res)) + return res + + async def _switch_buffer(self) -> None: with self._lock: - self._queue.append(get_buffers) + self._reset_items() - buffers = await buffers_future + await self._sync_task(self._update_items) - logger.info(dict(buffers=buffers[:3])) - - self._items = buffers + # self._items = buffers with self._lock: self._set_filter_pattern('') @@ -139,12 +155,22 @@ augroup END def ui_switch_buffer(): nonlocal selected_id - nonlocal buffers + # nonlocal buffers + + logger.warning(dict( + buffers=self._items[:3], + id=selected_id, + )) - logger.warning(dict(buffers=list(vim.buffers), id=selected_id)) # print(vim.buffers, selected_id) + if not selected_id is None: - vim.current.buffer = vim.buffers[buffers[selected_id][1]] + selected_item = self._items[selected_id] + if selected_item.buf_number is None: + Vim.run_command('badd %s' % json.dumps(str(selected_item.path))[1:-1]) + Vim.run_command('e %s' % json.dumps(str(selected_item.path))[1:-1]) + else: + vim.current.buffer = vim.buffers[selected_item.buf_number] with self._lock: self._queue.append(ui_switch_buffer) @@ -195,8 +221,15 @@ augroup END while len(self._queue) > 0: cmd = self._queue.pop() - logger.warning(dict(msg='start command', cmd=inspect.getsource(cmd))) + try: + cmd_str = inspect.getsource(cmd) + except: + cmd_str = str(cmd) + + try: + logger.warning(dict(msg='start command', cmd=cmd_str)) + cmd() except: logger.exception('') @@ -229,6 +262,14 @@ augroup END with self._lock: self._set_filter_pattern(self._filter_pattern[:-1]) + + # C-g + elif key == b'\x07': + with self._lock: + self._include_git = not self._include_git + self._update_items() + self._update_filtered() + # self._update_popup() else: try: key_str = key.decode('utf-8') @@ -252,14 +293,98 @@ augroup END return 1 + async def _sync_task( + self, + cb: Callable[[], None], + # future: asyncio.Future[bool] + ) -> None: + res_future: asyncio.Future[bool] = asyncio.Future() + + def wrapper(): + res : bool = True + + try: + cb() + except: + logger.exception('') + res = False + + self.loop.call_soon_threadsafe(lambda: res_future.set_result(res)) + + with self._lock: + self._queue.append(wrapper) + + return await res_future + + def _update_items(self) -> None: + known_files: dict[str, int] = dict() + + if self._buffers is None: + self._buffers = self._get_buffers() + + logger.info(dict(buffers=self._buffers[:3])) + + if self._include_git: + if self._tracked_files is None: + for o in self._buffers: + assert o.buf_number + + known_files[str(o.path)] = o.buf_number + + ls_files_output = [ + o.strip() + for o in subprocess.check_output( + ['git', 'ls-files'] + ).decode('utf-8').splitlines() + ] + + self._tracked_files = [] + + for o in ls_files_output: + path = pathlib.Path( + o, + ).absolute() + + entry = self.entry_t( + path=path, + buf_number=known_files.get(str(path)), + ) + + if entry.buf_number: + continue + + self._tracked_files.append(entry) + + logger.info(dict(tracked_files=self._tracked_files[:3])) + + self._items = self._buffers + self._tracked_files + else: + self._items = self._buffers + + self._items = sorted( + self._items, + # key=lambda x: -self._buffer_frequency.get(x[1], 0) + key=lambda x: -self._buffer_last_used.get(x.buf_number, 0), + ) + + def _reset_items(self) -> None: + self._buffers = None + self._tracked_files = None + self._items = None + + def _update_filtered(self) -> None: + pattern = re.compile(self._filter_pattern) + + self._filtered_ids = [ + i for i, o in enumerate(self._items) if not pattern.search(str(o.path)) is None + ] + + self._options = [str(self._items[o].path) for o in self._filtered_ids] + def _set_filter_pattern(self, filter_pattern: str) -> None: self._filter_pattern = filter_pattern - pattern = re.compile(self._filter_pattern) - - self._filtered_ids = [i for i, o in enumerate(self._items) if not pattern.search(o[0]) is None] - - self._options = [self._items[o][0] for o in self._filtered_ids] + self._update_filtered() def _update_popup(self) -> None: vim.Function('popup_settext')( diff --git a/dotfiles/.vim/online_fxreader_pr34_vim/utils.py b/dotfiles/.vim/online_fxreader_pr34_vim/utils.py index ca4a712..02c49f5 100644 --- a/dotfiles/.vim/online_fxreader_pr34_vim/utils.py +++ b/dotfiles/.vim/online_fxreader_pr34_vim/utils.py @@ -1,10 +1,12 @@ import vim +import logging +logger = logging.getLogger(__name__) class Vim: @classmethod def run_command(cls, cmd) -> list[str]: - # logger.info(dict(cmd=cmd)) + logger.info(dict(cmd=cmd)) output: list[str] = [] for line in cmd.splitlines(): From 680671e34b0c49332f0ff7ee51a675e9151d2637 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Mon, 27 Oct 2025 10:49:00 +0300 Subject: [PATCH 41/70] [+] update tmux config --- Makefile | 5 +++++ dotfiles/.tmux.conf | 1 + 2 files changed, 6 insertions(+) diff --git a/Makefile b/Makefile index a47f1ae..25348a0 100644 --- a/Makefile +++ b/Makefile @@ -89,6 +89,11 @@ dotfiles_vim_put: cp dotfiles/.py3.vimrc ~/.py3.vimrc cp -rp dotfiles/.vim/online_fxreader_pr34_vim ~/.vim/ +dotfiles_tmux_put: + mkdir -p $(INSTALL_ROOT) + + cp dotfiles/.tmux.conf ~/.tmux.conf + PLATFORM ?= macbook_air_2012 PLATFORM_TMP ?= tmp/platform_dotfiles/$(PLATFORM) diff --git a/dotfiles/.tmux.conf b/dotfiles/.tmux.conf index c3ee646..cfd9436 100644 --- a/dotfiles/.tmux.conf +++ b/dotfiles/.tmux.conf @@ -30,6 +30,7 @@ bind -n M-[ copy-mode bind -n M-m set -g mouse set -g default-terminal "screen-256color" +set-option -g status-style "bg=#00aa00,fg=#ffffff" #set-option -ga terminal-overrides ",screen-256color:Tc" set-option -g pane-active-border-style "bg=#33dd44 fg=#ffffff" From 14f0a66c675fd2699d79bbc92fe7d1eaf481de07 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Wed, 29 Oct 2025 14:57:47 +0300 Subject: [PATCH 42/70] [+] update cli.py, fix assert --- python/meson.build | 2 +- python/online/fxreader/pr34/commands_typed/cli.py | 3 ++- releases/whl/online_fxreader_pr34-0.1.5.32-py3-none-any.whl | 3 +++ 3 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.32-py3-none-any.whl diff --git a/python/meson.build b/python/meson.build index 4141ad4..657a9fd 100644 --- a/python/meson.build +++ b/python/meson.build @@ -5,7 +5,7 @@ project( ).stdout().strip('\n'), # 'online.fxreader.uv', # ['c', 'cpp'], - version: '0.1.5.31', + version: '0.1.5.32', # default_options: [ # 'cpp_std=c++23', # # 'prefer_static=true', diff --git a/python/online/fxreader/pr34/commands_typed/cli.py b/python/online/fxreader/pr34/commands_typed/cli.py index c718b4f..c8ec3a1 100644 --- a/python/online/fxreader/pr34/commands_typed/cli.py +++ b/python/online/fxreader/pr34/commands_typed/cli.py @@ -889,7 +889,8 @@ class CLI(abc.ABC): 'w', ) as f: p = pyproject2['project'] - assert isinstance(p, tomlkit.items.Table) + # assert isinstance(p, tomlkit.items.Table) + assert isinstance(p, MutableMapping) p['name'] = module.name if not pyproject2['tool']: diff --git a/releases/whl/online_fxreader_pr34-0.1.5.32-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.32-py3-none-any.whl new file mode 100644 index 0000000..6bfe450 --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.32-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:15752fc356bb4c6b4a92a71b7f615e0c0c2b42df1efb82f8613aa0951a74ae5e +size 75107 From 169ed5cebc1e0978d1352a7a5ce9e87081b49a5f Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Sat, 1 Nov 2025 16:25:08 +0300 Subject: [PATCH 43/70] [+] update vim plugins 1. make sure sessions uses the directory vim has been opened at; 2. update git ls-files to apply --recurse-submodules for choosing a buffer by name; --- .../.vim/online_fxreader_pr34_vim/beta.py | 7 ++++++- dotfiles/.vimrc | 19 ++++++++++++++++--- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/dotfiles/.vim/online_fxreader_pr34_vim/beta.py b/dotfiles/.vim/online_fxreader_pr34_vim/beta.py index 3053c4a..e1caac2 100644 --- a/dotfiles/.vim/online_fxreader_pr34_vim/beta.py +++ b/dotfiles/.vim/online_fxreader_pr34_vim/beta.py @@ -316,6 +316,11 @@ augroup END return await res_future + def _cwd(cls) -> pathlib.Path: + return pathlib.Path( + vim.Function('getcwd')().decode('utf-8') + ) + def _update_items(self) -> None: known_files: dict[str, int] = dict() @@ -334,7 +339,7 @@ augroup END ls_files_output = [ o.strip() for o in subprocess.check_output( - ['git', 'ls-files'] + ['git', 'ls-files', '--recurse-submodules',], cwd=self._cwd(), ).decode('utf-8').splitlines() ] diff --git a/dotfiles/.vimrc b/dotfiles/.vimrc index 3dc7618..194efee 100644 --- a/dotfiles/.vimrc +++ b/dotfiles/.vimrc @@ -30,7 +30,14 @@ colorscheme morning hi MatchParen guifg=white guibg=black gui=NONE ctermfg=1 ctermbg=0 function! MakeSession() - let b:sessiondir = '.vim/' + " let b:sessiondir = '.vim/' + if exists('g:sessiondir') + let b:sessiondir = g:sessiondir + else + let b:sessiondir = getcwd() . '/' . '.vim/' + let g:sessiondir = b:sessiondir + endif + if exists('g:session_name') let b:session_name = g:session_name else @@ -43,11 +50,17 @@ function! MakeSession() endif let b:filename = b:sessiondir . '/' . b:session_name . '.vim' exe "mksession! " . b:filename - echo 'saved ' . b:session_name + echo 'saved ' . b:sessiondir . ' ' . b:session_name endfunction function! LoadSession() - let b:sessiondir = '.vim/' + if exists('g:sessiondir') + let b:sessiondir = g:sessiondir + else + let b:sessiondir = getcwd() . '/' . '.vim/' + let g:sessiondir = b:sessiondir + endif + if exists('g:session_name') let b:session_name = g:session_name else From 8a7de59e7340c28e107dac69a078a8156e06ee73 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Sat, 1 Nov 2025 21:29:35 +0300 Subject: [PATCH 44/70] [+] update pr34 1. add dark/light mode changing via gsettings for GTK, wayland; --- Makefile | 8 ++ deps/com.github.aiortc.aiortc | 2 +- deps/online.fxreader.nartes.books | 2 +- python/meson.build | 2 +- python/online/fxreader/pr34/commands.py | 5 + .../pr34/commands_typed/color_scheme.py | 93 +++++++++++++++++++ ...ne_fxreader_pr34-0.1.5.33-py3-none-any.whl | 3 + ...ne_fxreader_pr34-0.1.5.34-py3-none-any.whl | 3 + ...ne_fxreader_pr34-0.1.5.35-py3-none-any.whl | 3 + ...ne_fxreader_pr34-0.1.5.36-py3-none-any.whl | 3 + ...ne_fxreader_pr34-0.1.5.37-py3-none-any.whl | 3 + ...ne_fxreader_pr34-0.1.5.38-py3-none-any.whl | 3 + 12 files changed, 127 insertions(+), 3 deletions(-) create mode 100644 python/online/fxreader/pr34/commands_typed/color_scheme.py create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.33-py3-none-any.whl create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.34-py3-none-any.whl create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.35-py3-none-any.whl create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.36-py3-none-any.whl create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.37-py3-none-any.whl create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.38-py3-none-any.whl diff --git a/Makefile b/Makefile index 25348a0..3b407de 100644 --- a/Makefile +++ b/Makefile @@ -51,6 +51,14 @@ python_put_dist: done ln -sf $(INSTALL_ROOT)/env3/bin/online-fxreader-pr34-commands $(INSTALL_ROOT)/commands +python_put_pr34: + $(INSTALL_ROOT)/env3/bin/python3 -m uv pip install $(UV_ARGS) \ + -f releases/whl \ + -U \ + online.fxreader.pr34 + ln -sf $(INSTALL_ROOT)/env3/bin/online-fxreader-pr34-commands $(INSTALL_ROOT)/commands + + PYTHON_PROJECTS_NAMES ?= online.fxreader.pr34 python_whl: for f in $(PYTHON_PROJECTS_NAMES); do \ diff --git a/deps/com.github.aiortc.aiortc b/deps/com.github.aiortc.aiortc index adef10a..4c187fc 160000 --- a/deps/com.github.aiortc.aiortc +++ b/deps/com.github.aiortc.aiortc @@ -1 +1 @@ -Subproject commit adef10a8c41f5c550622879370a40f8a9e545574 +Subproject commit 4c187fc7dd17c52fb8e4f992d3985eb609eefe6a diff --git a/deps/online.fxreader.nartes.books b/deps/online.fxreader.nartes.books index 3c691ef..f2366f3 160000 --- a/deps/online.fxreader.nartes.books +++ b/deps/online.fxreader.nartes.books @@ -1 +1 @@ -Subproject commit 3c691ef68d8899edf328d5b06135c0d3b02e7940 +Subproject commit f2366f328fb8129fa6ae26d00b421025d2f090c7 diff --git a/python/meson.build b/python/meson.build index 657a9fd..2c9d59f 100644 --- a/python/meson.build +++ b/python/meson.build @@ -5,7 +5,7 @@ project( ).stdout().strip('\n'), # 'online.fxreader.uv', # ['c', 'cpp'], - version: '0.1.5.32', + version: '0.1.5.38', # default_options: [ # 'cpp_std=c++23', # # 'prefer_static=true', diff --git a/python/online/fxreader/pr34/commands.py b/python/online/fxreader/pr34/commands.py index 9bd2af5..ac1e317 100644 --- a/python/online/fxreader/pr34/commands.py +++ b/python/online/fxreader/pr34/commands.py @@ -3979,6 +3979,7 @@ class Command(enum.Enum): backup = 'backup' pip_resolve = 'pip_resolve' pip_check_conflicts = 'pip_check_conflicts' + color_scheme = 'color_scheme' def pip_check_conflicts( @@ -4148,6 +4149,10 @@ def commands_cli(argv: Optional[list[str]] = None) -> int: backup(args) elif options.command is Command.scrap_yt_music: scrap_yt_music(args) + elif options.command is Command.color_scheme: + from .commands_typed.color_scheme import run as color_scheme + + color_scheme(args) elif options.command is Command.vpn: vpn(args) else: diff --git a/python/online/fxreader/pr34/commands_typed/color_scheme.py b/python/online/fxreader/pr34/commands_typed/color_scheme.py new file mode 100644 index 0000000..a71f612 --- /dev/null +++ b/python/online/fxreader/pr34/commands_typed/color_scheme.py @@ -0,0 +1,93 @@ +import subprocess +import sys +import json +import logging +from typing import ( + Literal, + Optional, +) + +import argparse + +logger = logging.getLogger(__name__) + + +def run(argv: list[str]) -> None: + parser = argparse.ArgumentParser() + parser.add_argument( + 'action', + choices=[ + 'toggle', + 'dark', + 'light', + 'get', + ], + # required=True, + type=str, + help='action', + ) + from .argparse import parse_args as pr34_parse_args + + options, args = pr34_parse_args(parser, argv) + assert len(args) == 0 + + def get_theme() -> Literal['light', 'dark', 'default']: + res = ( + subprocess.check_output( + [ + 'gsettings', + 'get', + 'org.gnome.desktop.interface', + 'color-scheme', + ] + ) + .decode('utf-8') + .strip() + ) + + if res == "'prefer-dark'": + return 'dark' + elif res == "'prefer-light'": + return 'light' + elif res == "'default'": + return 'default' + else: + logger.error(dict(res=res, msg='unknown theme')) + + raise NotImplementedError + + def set_theme(theme: Literal['light', 'dark', 'default']) -> None: + if theme == 'light': + subprocess.check_call(['gsettings', 'set', 'org.gnome.desktop.interface', 'color-scheme', 'prefer-light']) + elif theme == 'dark': + subprocess.check_call(['gsettings', 'set', 'org.gnome.desktop.interface', 'color-scheme', 'prefer-dark']) + elif theme == 'default': + subprocess.check_call( + [ + 'gsettings', + 'reset', + 'org.gnome.desktop.interface', + 'color-scheme', + ] + ) + else: + raise NotImplementedError + + def toggle() -> None: + theme = get_theme() + if theme in ('light', 'default'): + set_theme('dark') + else: + set_theme('light') + + if options.action == 'toggle': + toggle() + elif options.action == 'dark': + set_theme('dark') + elif options.action == 'light': + set_theme('light') + elif options.action == 'get': + sys.stdout.write(json.dumps(get_theme())) + sys.stdout.flush() + else: + raise NotImplementedError diff --git a/releases/whl/online_fxreader_pr34-0.1.5.33-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.33-py3-none-any.whl new file mode 100644 index 0000000..039f555 --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.33-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8fbaa95dcd0d0c0564fb6b7c161f486b54c4fb591593c769b6ad4fdf08b869f1 +size 75933 diff --git a/releases/whl/online_fxreader_pr34-0.1.5.34-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.34-py3-none-any.whl new file mode 100644 index 0000000..591533d --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.34-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b02999a55ca5687040b6947dd1f66aaafb33b1f5fd28cbd630c2e614690a3b8c +size 75937 diff --git a/releases/whl/online_fxreader_pr34-0.1.5.35-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.35-py3-none-any.whl new file mode 100644 index 0000000..58bebc4 --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.35-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6f8b32e3c34e253e7cab74bb3d4e772e69b8378e3698e3266204df54d6b1545a +size 75992 diff --git a/releases/whl/online_fxreader_pr34-0.1.5.36-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.36-py3-none-any.whl new file mode 100644 index 0000000..472ebb8 --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.36-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6670bd5dea3c54fca9766690b40c8416d7f5ec9d0c0abf9aa5309ec6779caad9 +size 75999 diff --git a/releases/whl/online_fxreader_pr34-0.1.5.37-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.37-py3-none-any.whl new file mode 100644 index 0000000..23b4ecb --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.37-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f92cb068a9a3145aad3ed9e6f66069e7560df1c60982d1ff6fb87d5ce08563c7 +size 76038 diff --git a/releases/whl/online_fxreader_pr34-0.1.5.38-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.38-py3-none-any.whl new file mode 100644 index 0000000..ffe375f --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.38-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a3748856fb9d1388a8b419ffa2b3dd523db371a9771dc50539ceb6eee2d395f7 +size 76042 From e8760c05f98bea6c42f89f3ff18391898bc1d699 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Sat, 1 Nov 2025 21:34:41 +0300 Subject: [PATCH 45/70] [+] update sway 1. add $mod + m to toggle light/dark mode; --- Makefile | 4 ++++ dotfiles/.sway/config | 2 ++ 2 files changed, 6 insertions(+) diff --git a/Makefile b/Makefile index 3b407de..573ad8c 100644 --- a/Makefile +++ b/Makefile @@ -116,6 +116,10 @@ dotfiles_put_platform: sudo udevadm control --reload sudo systemctl daemon-reload +dotfiles_sway_put: + mkdir -p ~/.sway + cp dotfiles/.sway/config ~/.sway/config + dotfiles_fetch: commands install -f -p ~ -s ~/.config/katerc -t dotfiles commands install -f -p ~ -s ~/.mime.types -t dotfiles diff --git a/dotfiles/.sway/config b/dotfiles/.sway/config index ff80706..b3f60e1 100644 --- a/dotfiles/.sway/config +++ b/dotfiles/.sway/config @@ -122,6 +122,8 @@ bindsym --locked XF86AudioMute exec zsh -c "commands media-toggle-volume" bindsym --locked XF86AudioNext exec zsh -c "commands media-next" bindsym --locked XF86AudioPrev exec zsh -c "commands media-prev" +bindsym $mod+m exec zsh -c "commands color_scheme toggle" + # Start a terminal bindsym $mod+t exec $term From 8336a8095d787186cb302ad043d24e25c02bc386 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Mon, 3 Nov 2025 08:56:45 +0300 Subject: [PATCH 46/70] [+] update sway config 1. use shift+mod+q to act more like OSX; --- dotfiles/.sway/config | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dotfiles/.sway/config b/dotfiles/.sway/config index b3f60e1..028b6f3 100644 --- a/dotfiles/.sway/config +++ b/dotfiles/.sway/config @@ -79,7 +79,7 @@ bindgesture swipe:4:up exec $lock_cmd # # Basics: # -bindsym Shift+$mod+l exec $lock_cmd +bindsym Shift+$mod+q exec $lock_cmd bindsym --locked Shift+mod1+1 \ exec ~/.local/bin/commands \ @@ -128,8 +128,8 @@ bindsym $mod+m exec zsh -c "commands color_scheme toggle" # Start a terminal bindsym $mod+t exec $term -# Kill focused window -bindsym $mod+Shift+q kill +## Kill focused window +#bindsym $mod+Shift+q kill # Start your launcher bindsym $mod+Return exec $menu From 0462559cc6b338ef55f667a9424188c686e39e21 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Thu, 6 Nov 2025 15:43:42 +0300 Subject: [PATCH 47/70] [+] update pr34 1. backport fixed timestamp for zip, .whl; when calling deploy:wheel; --- python/meson.build | 2 +- python/online/fxreader/pr34/commands_typed/cli.py | 7 +++++++ .../whl/online_fxreader_pr34-0.1.5.39-py3-none-any.whl | 3 +++ 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.39-py3-none-any.whl diff --git a/python/meson.build b/python/meson.build index 2c9d59f..e45208e 100644 --- a/python/meson.build +++ b/python/meson.build @@ -5,7 +5,7 @@ project( ).stdout().strip('\n'), # 'online.fxreader.uv', # ['c', 'cpp'], - version: '0.1.5.38', + version: '0.1.5.39', # default_options: [ # 'cpp_std=c++23', # # 'prefer_static=true', diff --git a/python/online/fxreader/pr34/commands_typed/cli.py b/python/online/fxreader/pr34/commands_typed/cli.py index c8ec3a1..2e96846 100644 --- a/python/online/fxreader/pr34/commands_typed/cli.py +++ b/python/online/fxreader/pr34/commands_typed/cli.py @@ -679,6 +679,13 @@ class CLI(abc.ABC): if env is None: env = dict() + env = ( + dict( + # to generate zip for .whl with a reproducible checksum + SOURCE_DATE_EPOCH='0', + ) + | env + ) pyproject = cli_bootstrap.pyproject_load(project.source_dir / 'pyproject.toml') pyproject_tool = pydantic.RootModel[PyProject.Tool].model_validate(pyproject.tool).root diff --git a/releases/whl/online_fxreader_pr34-0.1.5.39-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.39-py3-none-any.whl new file mode 100644 index 0000000..18d5537 --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.39-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:174f4cb51997b50fb6e7e4cca475077a8d91029759417251e312487d7ee70c28 +size 76117 From 938e5a94e629d9a9a04558b5937b83fc18516faf Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Thu, 6 Nov 2025 15:53:03 +0300 Subject: [PATCH 48/70] [+] update pr34 1. make .whl reproducible; --- .gitattributes | 1 + .gitignore | 1 + ...ne_fxreader_pr34-0.1.5.39-py3-none-any.whl | 3 + .../fxreader/pr34/commands_typed/cli.py | 15 +- python/pyproject.toml | 8 +- python/requirements.txt | 544 ++++++++++-------- ...ne_fxreader_pr34-0.1.5.39-py3-none-any.whl | 4 +- 7 files changed, 314 insertions(+), 262 deletions(-) create mode 100644 python/deps/whl/online_fxreader_pr34-0.1.5.39-py3-none-any.whl diff --git a/.gitattributes b/.gitattributes index 3daee15..fb1135e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,4 @@ releases/tar/** filter=lfs diff=lfs merge=lfs -text releases/whl/** filter=lfs diff=lfs merge=lfs -text +python/deps/whl/** filter=lfs diff=lfs merge=lfs -text docker/*/deps/whl/** filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore index f85e4cc..f92cefb 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ d2/book1/books *.tar.gz .vscode/* !.vscode/launch.json +!python/deps/whl/**/*.whl python/build .*.kate-swp !releases/whl/*.whl diff --git a/python/deps/whl/online_fxreader_pr34-0.1.5.39-py3-none-any.whl b/python/deps/whl/online_fxreader_pr34-0.1.5.39-py3-none-any.whl new file mode 100644 index 0000000..6de61f6 --- /dev/null +++ b/python/deps/whl/online_fxreader_pr34-0.1.5.39-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b73f4caecb1ce6b94b4b3a87299e779b843ceb8d7a581da8d27abe622a6a37db +size 76119 diff --git a/python/online/fxreader/pr34/commands_typed/cli.py b/python/online/fxreader/pr34/commands_typed/cli.py index 2e96846..912be7e 100644 --- a/python/online/fxreader/pr34/commands_typed/cli.py +++ b/python/online/fxreader/pr34/commands_typed/cli.py @@ -366,6 +366,14 @@ class CLI(abc.ABC): if env is None: env = dict() + env = ( + dict( + # to generate zip for .whl with a reproducible checksum + SOURCE_DATE_EPOCH='0', + ) + | env + ) + extra_args: list[str] = [] pyproject_build_dir = project.build_dir / 'pyproject' @@ -679,13 +687,6 @@ class CLI(abc.ABC): if env is None: env = dict() - env = ( - dict( - # to generate zip for .whl with a reproducible checksum - SOURCE_DATE_EPOCH='0', - ) - | env - ) pyproject = cli_bootstrap.pyproject_load(project.source_dir / 'pyproject.toml') pyproject_tool = pydantic.RootModel[PyProject.Tool].model_validate(pyproject.tool).root diff --git a/python/pyproject.toml b/python/pyproject.toml index 8b0e03a..00045e9 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -41,8 +41,9 @@ fastapi = [ ] early = [ - 'numpy', - 'cryptography', + 'numpy', + 'cryptography', + 'online.fxreader.pr34', # 'tomlkit', ] @@ -61,6 +62,9 @@ lint = [ [tool.online-fxreader-pr34] early_features = ['default', 'early', 'lint',] +pip_find_links = [ + 'deps/whl', +] [build-system] requires = ["meson-python", "pybind11"] diff --git a/python/requirements.txt b/python/requirements.txt index 254c85a..4c2fc60 100644 --- a/python/requirements.txt +++ b/python/requirements.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile --generate-hashes -o /home/nartes/Documents/current/freelance-project-34-marketing-blog/python/requirements.txt /tmp/requirements6816cy66.in +# uv pip compile --python-version 3.13 --generate-hashes --offline -o /home/nartes/Documents/current/freelance-project-34-marketing-blog/python/requirements.txt /tmp/requirementscnex08p2.in annotated-types==0.7.0 \ --hash=sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53 \ --hash=sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89 @@ -19,7 +19,7 @@ asgiref==3.9.1 \ build==1.3.0 \ --hash=sha256:698edd0ea270bde950f53aed21f3a0135672206f3911e0176261a31e0e07b397 \ --hash=sha256:7145f0b5061ba90a1500d60bd1b13ca0a8a4cebdd0cc16ed8adf1c0e739f43b4 - # via -r /tmp/requirements6816cy66.in + # via -r /tmp/requirementscnex08p2.in cffi==1.17.1 \ --hash=sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8 \ --hash=sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2 \ @@ -131,7 +131,7 @@ cryptography==45.0.6 \ --hash=sha256:f4028f29a9f38a2025abedb2e409973709c660d44319c61762202206ed577c42 \ --hash=sha256:f68f833a9d445cc49f01097d95c83a850795921b3f7cc6488731e69bde3288da \ --hash=sha256:fc022c1fa5acff6def2fc6d7819bbbd31ccddfe67d075331a65d9cfb28a20983 - # via -r /tmp/requirements6816cy66.in + # via -r /tmp/requirementscnex08p2.in django==5.2.5 \ --hash=sha256:0745b25681b129a77aae3d4f6549b62d3913d74407831abaa0d9021a03954bae \ --hash=sha256:2b2ada0ee8a5ff743a40e2b9820d1f8e24c11bac9ae6469cd548f0057ea6ddcd @@ -141,7 +141,7 @@ django==5.2.5 \ django-stubs==5.2.2 \ --hash=sha256:2a04b510c7a812f88223fd7e6d87fb4ea98717f19c8e5c8b59691d83ad40a8a6 \ --hash=sha256:79bd0fdbc78958a8f63e0b062bd9d03f1de539664476c0be62ade5f063c9e41e - # via -r /tmp/requirements6816cy66.in + # via -r /tmp/requirementscnex08p2.in django-stubs-ext==5.2.2 \ --hash=sha256:8833bbe32405a2a0ce168d3f75a87168f61bd16939caf0e8bf173bccbd8a44c5 \ --hash=sha256:d9d151b919fe2438760f5bd938f03e1cb08c84d0651f9e5917f1313907e42683 @@ -149,7 +149,7 @@ django-stubs-ext==5.2.2 \ fastapi==0.116.1 \ --hash=sha256:c46ac7c312df840f0c9e220f7964bada936781bc4e2e6eb71f1c4d7553786565 \ --hash=sha256:ed52cbf946abfd70c5a0dccb24673f0670deeb517a88b3544d03c2a6bf283143 - # via -r /tmp/requirements6816cy66.in + # via -r /tmp/requirementscnex08p2.in h11==0.16.0 \ --hash=sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1 \ --hash=sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86 @@ -224,55 +224,58 @@ marisa-trie==1.3.1 \ --hash=sha256:ecdb19d33b26738a32602ef432b06cc6deeca4b498ce67ba8e5e39c8a7c19745 \ --hash=sha256:ee428575377e29c636f2b4b3b0488875dcea310c6c5b3412ec4ef997f7bb37cc \ --hash=sha256:f4bae4f920f2a1082eaf766c1883df7da84abdf333bafa15b8717c10416a615e - # via -r /tmp/requirements6816cy66.in -meson==1.9.0 \ - --hash=sha256:45e51ddc41e37d961582d06e78c48e0f9039011587f3495c4d6b0781dad92357 \ - --hash=sha256:cd27277649b5ed50d19875031de516e270b22e890d9db65ed9af57d18ebc498d + # via + # -r /tmp/requirementscnex08p2.in + # online-fxreader-pr34 +meson==1.9.1 \ + --hash=sha256:f824ab770c041a202f532f69e114c971918ed2daff7ea56583d80642564598d0 # via meson-python meson-python==0.18.0 \ --hash=sha256:3b0fe051551cc238f5febb873247c0949cd60ded556efa130aa57021804868e2 \ --hash=sha256:c56a99ec9df669a40662fe46960321af6e4b14106c14db228709c1628e23848d - # via -r /tmp/requirements6816cy66.in -mypy==1.17.1 \ - --hash=sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341 \ - --hash=sha256:064e2ff508e5464b4bd807a7c1625bc5047c5022b85c70f030680e18f37273a5 \ - --hash=sha256:099b9a5da47de9e2cb5165e581f158e854d9e19d2e96b6698c0d64de911dd849 \ - --hash=sha256:15a83369400454c41ed3a118e0cc58bd8123921a602f385cb6d6ea5df050c733 \ - --hash=sha256:15d54056f7fe7a826d897789f53dd6377ec2ea8ba6f776dc83c2902b899fee81 \ - --hash=sha256:1b16708a66d38abb1e6b5702f5c2c87e133289da36f6a1d15f6a5221085c6403 \ - --hash=sha256:209a58fed9987eccc20f2ca94afe7257a8f46eb5df1fb69958650973230f91e6 \ - --hash=sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01 \ - --hash=sha256:397fba5d7616a5bc60b45c7ed204717eaddc38f826e3645402c426057ead9a91 \ - --hash=sha256:3fbe6d5555bf608c47203baa3e72dbc6ec9965b3d7c318aa9a4ca76f465bd972 \ - --hash=sha256:43808d9476c36b927fbcd0b0255ce75efe1b68a080154a38ae68a7e62de8f0f8 \ - --hash=sha256:55b918670f692fc9fba55c3298d8a3beae295c5cded0a55dccdc5bbead814acd \ - --hash=sha256:5d1092694f166a7e56c805caaf794e0585cabdbf1df36911c414e4e9abb62ae9 \ - --hash=sha256:62761474061feef6f720149d7ba876122007ddc64adff5ba6f374fda35a018a0 \ - --hash=sha256:665afab0963a4b39dff7c1fa563cc8b11ecff7910206db4b2e64dd1ba25aed19 \ - --hash=sha256:69e83ea6553a3ba79c08c6e15dbd9bfa912ec1e493bf75489ef93beb65209aeb \ - --hash=sha256:70401bbabd2fa1aa7c43bb358f54037baf0586f41e83b0ae67dd0534fc64edfd \ - --hash=sha256:79d44f9bfb004941ebb0abe8eff6504223a9c1ac51ef967d1263c6572bbebc99 \ - --hash=sha256:80ef5c058b7bce08c83cac668158cb7edea692e458d21098c7d3bce35a5d43e7 \ - --hash=sha256:89e972c0035e9e05823907ad5398c5a73b9f47a002b22359b177d40bdaee7056 \ - --hash=sha256:93378d3203a5c0800c6b6d850ad2f19f7a3cdf1a3701d3416dbf128805c6a6a7 \ - --hash=sha256:9a2b7d9180aed171f033c9f2fc6c204c1245cf60b0cb61cf2e7acc24eea78e0a \ - --hash=sha256:9d6b20b97d373f41617bd0708fd46aa656059af57f2ef72aa8c7d6a2b73b74ed \ - --hash=sha256:a76906f26bd8d51ea9504966a9c25419f2e668f012e0bdf3da4ea1526c534d94 \ - --hash=sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9 \ - --hash=sha256:ad37544be07c5d7fba814eb370e006df58fed8ad1ef33ed1649cb1889ba6ff58 \ - --hash=sha256:b01586eed696ec905e61bd2568f48740f7ac4a45b3a468e6423a03d3788a51a8 \ - --hash=sha256:c1fdf4abb29ed1cb091cf432979e162c208a5ac676ce35010373ff29247bcad5 \ - --hash=sha256:c49562d3d908fd49ed0938e5423daed8d407774a479b595b143a3d7f87cdae6a \ - --hash=sha256:c4a580f8a70c69e4a75587bd925d298434057fe2a428faaf927ffe6e4b9a98df \ - --hash=sha256:c837b896b37cd103570d776bda106eabb8737aa6dd4f248451aecf53030cdbeb \ - --hash=sha256:d7598cf74c3e16539d4e2f0b8d8c318e00041553d83d4861f87c7a72e95ac24d \ - --hash=sha256:dd86bb649299f09d987a2eebb4d52d10603224500792e1bee18303bbcc1ce390 \ - --hash=sha256:e79311f2d904ccb59787477b7bd5d26f3347789c06fcd7656fa500875290264b \ - --hash=sha256:e92bdc656b7757c438660f775f872a669b8ff374edc4d18277d86b63edba6b8b \ - --hash=sha256:fa6ffadfbe6994d724c5a1bb6123a7d27dd68fc9c059561cd33b664a79578e14 \ - --hash=sha256:feb8cc32d319edd5859da2cc084493b3e2ce5e49a946377663cc90f6c15fb259 \ - --hash=sha256:ff2933428516ab63f961644bc49bc4cbe42bbffb2cd3b71cc7277c07d16b1a8b - # via -r /tmp/requirements6816cy66.in + # via -r /tmp/requirementscnex08p2.in +mypy==1.18.2 \ + --hash=sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914 \ + --hash=sha256:030c52d0ea8144e721e49b1f68391e39553d7451f0c3f8a7565b59e19fcb608b \ + --hash=sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b \ + --hash=sha256:07b8b0f580ca6d289e69209ec9d3911b4a26e5abfde32228a288eb79df129fcc \ + --hash=sha256:0e2785a84b34a72ba55fb5daf079a1003a34c05b22238da94fcae2bbe46f3544 \ + --hash=sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86 \ + --hash=sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d \ + --hash=sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075 \ + --hash=sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e \ + --hash=sha256:22f27105f1525ec024b5c630c0b9f36d5c1cc4d447d61fe51ff4bd60633f47ac \ + --hash=sha256:25a9c8fb67b00599f839cf472713f54249a62efd53a54b565eb61956a7e3296b \ + --hash=sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34 \ + --hash=sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37 \ + --hash=sha256:448acd386266989ef11662ce3c8011fd2a7b632e0ec7d61a98edd8e27472225b \ + --hash=sha256:592ec214750bc00741af1f80cbf96b5013d81486b7bb24cb052382c19e40b428 \ + --hash=sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893 \ + --hash=sha256:62f0e1e988ad41c2a110edde6c398383a889d95b36b3e60bcf155f5164c4fdce \ + --hash=sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8 \ + --hash=sha256:6ca1e64b24a700ab5ce10133f7ccd956a04715463d30498e64ea8715236f9c9c \ + --hash=sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf \ + --hash=sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341 \ + --hash=sha256:7a780ca61fc239e4865968ebc5240bb3bf610ef59ac398de9a7421b54e4a207e \ + --hash=sha256:7ab28cc197f1dd77a67e1c6f35cd1f8e8b73ed2217e4fc005f9e6a504e46e7ba \ + --hash=sha256:7fb95f97199ea11769ebe3638c29b550b5221e997c63b14ef93d2e971606ebed \ + --hash=sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f \ + --hash=sha256:8795a039bab805ff0c1dfdb8cd3344642c2b99b8e439d057aba30850b8d3423d \ + --hash=sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8 \ + --hash=sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764 \ + --hash=sha256:a431a6f1ef14cf8c144c6b14793a23ec4eae3db28277c358136e79d7d062f62d \ + --hash=sha256:aa5e07ac1a60a253445797e42b8b2963c9675563a94f11291ab40718b016a7a0 \ + --hash=sha256:c1eab0cf6294dafe397c261a75f96dc2c31bffe3b944faa24db5def4e2b0f77c \ + --hash=sha256:c2b9c7e284ee20e7598d6f42e13ca40b4928e6957ed6813d1ab6348aa3f47133 \ + --hash=sha256:c3ad2afadd1e9fea5cf99a45a822346971ede8685cc581ed9cd4d42eaf940986 \ + --hash=sha256:d6985ed057513e344e43a26cc1cd815c7a94602fb6a3130a34798625bc2f07b6 \ + --hash=sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074 \ + --hash=sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb \ + --hash=sha256:ed4482847168439651d3feee5833ccedbf6657e964572706a2adb1f7fa4dfe2e \ + --hash=sha256:f9e171c465ad3901dc652643ee4bffa8e9fef4d7d0eece23b428908c77a76a66 + # via + # -r /tmp/requirementscnex08p2.in + # online-fxreader-pr34 mypy-extensions==1.1.0 \ --hash=sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505 \ --hash=sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558 @@ -356,7 +359,10 @@ numpy==2.3.2 \ --hash=sha256:fb1752a3bb9a3ad2d6b090b88a9a0ae1cd6f004ef95f75825e2f382c183b2097 \ --hash=sha256:fc927d7f289d14f5e037be917539620603294454130b6de200091e23d27dc9be \ --hash=sha256:fed5527c4cf10f16c6d0b6bee1f89958bccb0ad2522c8cadc2efd318bcd545f5 - # via -r /tmp/requirements6816cy66.in + # via -r /tmp/requirementscnex08p2.in +online-fxreader-pr34==0.1.5.39 \ + --hash=sha256:b73f4caecb1ce6b94b4b3a87299e779b843ceb8d7a581da8d27abe622a6a37db + # via -r /tmp/requirementscnex08p2.in packaging==25.0 \ --hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \ --hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f @@ -371,127 +377,150 @@ pathspec==0.12.1 \ pip==25.1 \ --hash=sha256:13b4aa0aaad055020a11bec8a1c2a70a2b2d080e12d89b962266029fff0a16ba \ --hash=sha256:272bdd1289f80165e9070a4f881e8f9e1001bbb50378561d1af20e49bf5a2200 - # via -r /tmp/requirements6816cy66.in + # via + # -r /tmp/requirementscnex08p2.in + # online-fxreader-pr34 pybind11==3.0.1 \ --hash=sha256:9c0f40056a016da59bab516efb523089139fcc6f2ba7e4930854c61efb932051 \ --hash=sha256:aa8f0aa6e0a94d3b64adfc38f560f33f15e589be2175e103c0a33c6bce55ee89 - # via -r /tmp/requirements6816cy66.in + # via -r /tmp/requirementscnex08p2.in pycparser==2.22 \ --hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \ --hash=sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc # via cffi -pydantic==2.11.7 \ - --hash=sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db \ - --hash=sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b +pydantic==2.12.3 \ + --hash=sha256:1da1c82b0fc140bb0103bc1441ffe062154c8d38491189751ee00fd8ca65ce74 \ + --hash=sha256:6986454a854bc3bc6e5443e1369e06a3a456af9d339eda45510f517d9ea5c6bf # via - # -r /tmp/requirements6816cy66.in + # -r /tmp/requirementscnex08p2.in # fastapi + # online-fxreader-pr34 # pydantic-settings -pydantic-core==2.33.2 \ - --hash=sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d \ - --hash=sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac \ - --hash=sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02 \ - --hash=sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56 \ - --hash=sha256:09fb9dd6571aacd023fe6aaca316bd01cf60ab27240d7eb39ebd66a3a15293b4 \ - --hash=sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22 \ - --hash=sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef \ - --hash=sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec \ - --hash=sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d \ - --hash=sha256:0e6116757f7959a712db11f3e9c0a99ade00a5bbedae83cb801985aa154f071b \ - --hash=sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a \ - --hash=sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f \ - --hash=sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052 \ - --hash=sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab \ - --hash=sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916 \ - --hash=sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c \ - --hash=sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf \ - --hash=sha256:2807668ba86cb38c6817ad9bc66215ab8584d1d304030ce4f0887336f28a5e27 \ - --hash=sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a \ - --hash=sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8 \ - --hash=sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7 \ - --hash=sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612 \ - --hash=sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1 \ - --hash=sha256:3a1c81334778f9e3af2f8aeb7a960736e5cab1dfebfb26aabca09afd2906c039 \ - --hash=sha256:3abcd9392a36025e3bd55f9bd38d908bd17962cc49bc6da8e7e96285336e2bca \ - --hash=sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7 \ - --hash=sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a \ - --hash=sha256:3eb3fe62804e8f859c49ed20a8451342de53ed764150cb14ca71357c765dc2a6 \ - --hash=sha256:44857c3227d3fb5e753d5fe4a3420d6376fa594b07b621e220cd93703fe21782 \ - --hash=sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b \ - --hash=sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7 \ - --hash=sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025 \ - --hash=sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849 \ - --hash=sha256:53a57d2ed685940a504248187d5685e49eb5eef0f696853647bf37c418c538f7 \ - --hash=sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b \ - --hash=sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa \ - --hash=sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e \ - --hash=sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea \ - --hash=sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac \ - --hash=sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51 \ - --hash=sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e \ - --hash=sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162 \ - --hash=sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65 \ - --hash=sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2 \ - --hash=sha256:6fa6dfc3e4d1f734a34710f391ae822e0a8eb8559a85c6979e14e65ee6ba2954 \ - --hash=sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b \ - --hash=sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de \ - --hash=sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc \ - --hash=sha256:7f92c15cd1e97d4b12acd1cc9004fa092578acfa57b67ad5e43a197175d01a64 \ - --hash=sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb \ - --hash=sha256:83aa99b1285bc8f038941ddf598501a86f1536789740991d7d8756e34f1e74d9 \ - --hash=sha256:87acbfcf8e90ca885206e98359d7dca4bcbb35abdc0ff66672a293e1d7a19101 \ - --hash=sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d \ - --hash=sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef \ - --hash=sha256:8d55ab81c57b8ff8548c3e4947f119551253f4e3787a7bbc0b6b3ca47498a9d3 \ - --hash=sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1 \ - --hash=sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5 \ - --hash=sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88 \ - --hash=sha256:970919794d126ba8645f3837ab6046fb4e72bbc057b3709144066204c19a455d \ - --hash=sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290 \ - --hash=sha256:9fcd347d2cc5c23b06de6d3b7b8275be558a0c90549495c699e379a80bf8379e \ - --hash=sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d \ - --hash=sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808 \ - --hash=sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc \ - --hash=sha256:a2b911a5b90e0374d03813674bf0a5fbbb7741570dcd4b4e85a2e48d17def29d \ - --hash=sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc \ - --hash=sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e \ - --hash=sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640 \ - --hash=sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30 \ - --hash=sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e \ - --hash=sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9 \ - --hash=sha256:c20c462aa4434b33a2661701b861604913f912254e441ab8d78d30485736115a \ - --hash=sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9 \ - --hash=sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f \ - --hash=sha256:c54c939ee22dc8e2d545da79fc5381f1c020d6d3141d3bd747eab59164dc89fb \ - --hash=sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5 \ - --hash=sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab \ - --hash=sha256:d3f26877a748dc4251cfcfda9dfb5f13fcb034f5308388066bcfe9031b63ae7d \ - --hash=sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572 \ - --hash=sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593 \ - --hash=sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29 \ - --hash=sha256:dac89aea9af8cd672fa7b510e7b8c33b0bba9a43186680550ccf23020f32d535 \ - --hash=sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1 \ - --hash=sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f \ - --hash=sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8 \ - --hash=sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf \ - --hash=sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246 \ - --hash=sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9 \ - --hash=sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011 \ - --hash=sha256:eb9b459ca4df0e5c87deb59d37377461a538852765293f9e6ee834f0435a93b9 \ - --hash=sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a \ - --hash=sha256:f481959862f57f29601ccced557cc2e817bce7533ab8e01a797a48b49c9692b3 \ - --hash=sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6 \ - --hash=sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8 \ - --hash=sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a \ - --hash=sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2 \ - --hash=sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c \ - --hash=sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6 \ - --hash=sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d +pydantic-core==2.41.4 \ + --hash=sha256:025ba34a4cf4fb32f917d5d188ab5e702223d3ba603be4d8aca2f82bede432a4 \ + --hash=sha256:09c2a60e55b357284b5f31f5ab275ba9f7f70b7525e18a132ec1f9160b4f1f03 \ + --hash=sha256:0c19cb355224037c83642429b8ce261ae108e1c5fbf5c028bac63c77b0f8646e \ + --hash=sha256:0cf2a1f599efe57fa0051312774280ee0f650e11152325e41dfd3018ef2c1b57 \ + --hash=sha256:0f184d657fa4947ae5ec9c47bd7e917730fa1cbb78195037e32dcbab50aca5ee \ + --hash=sha256:15dd504af121caaf2c95cb90c0ebf71603c53de98305621b94da0f967e572def \ + --hash=sha256:170ee6835f6c71081d031ef1c3b4dc4a12b9efa6a9540f93f95b82f3c7571ae8 \ + --hash=sha256:19f3684868309db5263a11bace3c45d93f6f24afa2ffe75a647583df22a2ff89 \ + --hash=sha256:1affa4798520b148d7182da0615d648e752de4ab1a9566b7471bc803d88a062d \ + --hash=sha256:1b65077a4693a98b90ec5ad8f203ad65802a1b9b6d4a7e48066925a7e1606706 \ + --hash=sha256:1cae8851e174c83633f0833e90636832857297900133705ee158cf79d40f03e6 \ + --hash=sha256:1e5ab4fc177dd41536b3c32b2ea11380dd3d4619a385860621478ac2d25ceb00 \ + --hash=sha256:1ed810568aeffed3edc78910af32af911c835cc39ebbfacd1f0ab5dd53028e5c \ + --hash=sha256:2442d9a4d38f3411f22eb9dd0912b7cbf4b7d5b6c92c4173b75d3e1ccd84e36e \ + --hash=sha256:26895a4268ae5a2849269f4991cdc97236e4b9c010e51137becf25182daac405 \ + --hash=sha256:285b643d75c0e30abda9dc1077395624f314a37e3c09ca402d4015ef5979f1a2 \ + --hash=sha256:28ff11666443a1a8cf2a044d6a545ebffa8382b5f7973f22c36109205e65dc80 \ + --hash=sha256:2dfe3aa529c8f501babf6e502936b9e8d4698502b2cfab41e17a028d91b1ac7b \ + --hash=sha256:304c54176af2c143bd181d82e77c15c41cbacea8872a2225dd37e6544dce9999 \ + --hash=sha256:30a9876226dda131a741afeab2702e2d127209bde3c65a2b8133f428bc5d006b \ + --hash=sha256:31a41030b1d9ca497634092b46481b937ff9397a86f9f51bd41c4767b6fc04af \ + --hash=sha256:3619320641fd212aaf5997b6ca505e97540b7e16418f4a241f44cdf108ffb50d \ + --hash=sha256:37e516bca9264cbf29612539801ca3cd5d1be465f940417b002905e6ed79d38a \ + --hash=sha256:3a926768ea49a8af4d36abd6a8968b8790f7f76dd7cbd5a4c180db2b4ac9a3a2 \ + --hash=sha256:3a95d4590b1f1a43bf33ca6d647b990a88f4a3824a8c4572c708f0b45a5290ed \ + --hash=sha256:3adf61415efa6ce977041ba9745183c0e1f637ca849773afa93833e04b163feb \ + --hash=sha256:3d88d0054d3fa11ce936184896bed3c1c5441d6fa483b498fac6a5d0dd6f64a9 \ + --hash=sha256:3f1ea6f48a045745d0d9f325989d8abd3f1eaf47dd00485912d1a3a63c623a8d \ + --hash=sha256:44e7625332683b6c1c8b980461475cde9595eff94447500e80716db89b0da005 \ + --hash=sha256:491535d45cd7ad7e4a2af4a5169b0d07bebf1adfd164b0368da8aa41e19907a5 \ + --hash=sha256:4a9ab037b71927babc6d9e7fc01aea9e66dc2a4a34dff06ef0724a4049629f94 \ + --hash=sha256:4c973add636efc61de22530b2ef83a65f39b6d6f656df97f678720e20de26caa \ + --hash=sha256:4f5d640aeebb438517150fdeec097739614421900e4a08db4a3ef38898798537 \ + --hash=sha256:523e7da4d43b113bf8e7b49fa4ec0c35bf4fe66b2230bfc5c13cc498f12c6c3e \ + --hash=sha256:54d86c0cada6aba4ec4c047d0e348cbad7063b87ae0f005d9f8c9ad04d4a92a2 \ + --hash=sha256:557a0aab88664cc552285316809cab897716a372afaf8efdbef756f8b890e894 \ + --hash=sha256:5729225de81fb65b70fdb1907fcf08c75d498f4a6f15af005aabb1fdadc19dfa \ + --hash=sha256:5a28fcedd762349519276c36634e71853b4541079cab4acaaac60c4421827308 \ + --hash=sha256:5b66584e549e2e32a1398df11da2e0a7eff45d5c2d9db9d5667c5e6ac764d77e \ + --hash=sha256:5cf90535979089df02e6f17ffd076f07237efa55b7343d98760bde8743c4b265 \ + --hash=sha256:61760c3925d4633290292bad462e0f737b840508b4f722247d8729684f6539ae \ + --hash=sha256:62637c769dee16eddb7686bf421be48dfc2fae93832c25e25bc7242e698361ba \ + --hash=sha256:6273ea2c8ffdac7b7fda2653c49682db815aebf4a89243a6feccf5e36c18c347 \ + --hash=sha256:646e76293345954acea6966149683047b7b2ace793011922208c8e9da12b0062 \ + --hash=sha256:664b3199193262277b8b3cd1e754fb07f2c6023289c815a1e1e8fb415cb247b1 \ + --hash=sha256:66c529f862fdba70558061bb936fe00ddbaaa0c647fd26e4a4356ef1d6561891 \ + --hash=sha256:6916b9b7d134bff5440098a4deb80e4cb623e68974a87883299de9124126c2a8 \ + --hash=sha256:692c622c8f859a17c156492783902d8370ac7e121a611bd6fe92cc71acf9ee8d \ + --hash=sha256:6c1fe4c5404c448b13188dd8bd2ebc2bdd7e6727fa61ff481bcc2cca894018da \ + --hash=sha256:6c9024169becccf0cb470ada03ee578d7348c119a0d42af3dcf9eda96e3a247c \ + --hash=sha256:6cb9cf7e761f4f8a8589a45e49ed3c0d92d1d696a45a6feaee8c904b26efc2db \ + --hash=sha256:6d55fb8b1e8929b341cc313a81a26e0d48aa3b519c1dbaadec3a6a2b4fcad025 \ + --hash=sha256:6e0fc40d84448f941df9b3334c4b78fe42f36e3bf631ad54c3047a0cdddc2514 \ + --hash=sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5 \ + --hash=sha256:711156b6afb5cb1cb7c14a2cc2c4a8b4c717b69046f13c6b332d8a0a8f41ca3e \ + --hash=sha256:7533c76fa647fade2d7ec75ac5cc079ab3f34879626dae5689b27790a6cf5a5c \ + --hash=sha256:7b2a054a8725f05b4b6503357e0ac1c4e8234ad3b0c2ac130d6ffc66f0e170e2 \ + --hash=sha256:7b74e18052fea4aa8dea2fb7dbc23d15439695da6cbe6cfc1b694af1115df09d \ + --hash=sha256:82df1f432b37d832709fbcc0e24394bba04a01b6ecf1ee87578145c19cde12ac \ + --hash=sha256:833eebfd75a26d17470b58768c1834dfc90141b7afc6eb0429c21fc5a21dcfb8 \ + --hash=sha256:84d8854db5f55fead3b579f04bda9a36461dab0730c5d570e1526483e7bb8431 \ + --hash=sha256:85e050ad9e5f6fe1004eec65c914332e52f429bc0ae12d6fa2092407a462c746 \ + --hash=sha256:94dab0940b0d1fb28bcab847adf887c66a27a40291eedf0b473be58761c9799a \ + --hash=sha256:98f348cbb44fae6e9653c1055db7e29de67ea6a9ca03a5fa2c2e11a47cff0e47 \ + --hash=sha256:9be1c01adb2ecc4e464392c36d17f97e9110fbbc906bcbe1c943b5b87a74aabd \ + --hash=sha256:a1351f5bbdbbabc689727cb91649a00cb9ee7203e0a6e54e9f5ba9e22e384b84 \ + --hash=sha256:a1b2cfec3879afb742a7b0bcfa53e4f22ba96571c9e54d6a3afe1052d17d843b \ + --hash=sha256:a238dd3feee263eeaeb7dc44aea4ba1364682c4f9f9467e6af5596ba322c2332 \ + --hash=sha256:a26d950449aae348afe1ac8be5525a00ae4235309b729ad4d3399623125b43c9 \ + --hash=sha256:a44ac1738591472c3d020f61c6df1e4015180d6262ebd39bf2aeb52571b60f12 \ + --hash=sha256:a870c307bf1ee91fc58a9a61338ff780d01bfae45922624816878dce784095d2 \ + --hash=sha256:a8c2e340d7e454dc3340d3d2e8f23558ebe78c98aa8f68851b04dcb7bc37abdc \ + --hash=sha256:ab06d77e053d660a6faaf04894446df7b0a7e7aba70c2797465a0a1af00fc887 \ + --hash=sha256:b0d9db5a161c99375a0c68c058e227bee1d89303300802601d76a3d01f74e258 \ + --hash=sha256:b1eb1754fce47c63d2ff57fdb88c351a6c0150995890088b33767a10218eaa4e \ + --hash=sha256:b568af94267729d76e6ee5ececda4e283d07bbb28e8148bb17adad93d025d25a \ + --hash=sha256:b69d1973354758007f46cf2d44a4f3d0933f10b6dc9bf15cf1356e037f6f731a \ + --hash=sha256:b9f5f30c402ed58f90c70e12eff65547d3ab74685ffe8283c719e6bead8ef53f \ + --hash=sha256:bd8a5028425820731d8c6c098ab642d7b8b999758e24acae03ed38a66eca8335 \ + --hash=sha256:c173ddcd86afd2535e2b695217e82191580663a1d1928239f877f5a1649ef39f \ + --hash=sha256:c4d1e854aaf044487d31143f541f7aafe7b482ae72a022c664b2de2e466ed0ad \ + --hash=sha256:c53ff33e603a9c1179a9364b0a24694f183717b2e0da2b5ad43c316c956901b2 \ + --hash=sha256:ca2322da745bf2eeb581fc9ea3bbb31147702163ccbcbf12a3bb630e4bf05e1d \ + --hash=sha256:ca4df25762cf71308c446e33c9b1fdca2923a3f13de616e2a949f38bf21ff5a8 \ + --hash=sha256:cc8e85a63085a137d286e2791037f5fdfff0aabb8b899483ca9c496dd5797338 \ + --hash=sha256:d081a1f3800f05409ed868ebb2d74ac39dd0c1ff6c035b5162356d76030736d4 \ + --hash=sha256:d175600d975b7c244af6eb9c9041f10059f20b8bbffec9e33fdd5ee3f67cdc42 \ + --hash=sha256:d1e2906efb1031a532600679b424ef1d95d9f9fb507f813951f23320903adbd7 \ + --hash=sha256:d25e97bc1f5f8f7985bdc2335ef9e73843bb561eb1fa6831fdfc295c1c2061cf \ + --hash=sha256:d34f950ae05a83e0ede899c595f312ca976023ea1db100cd5aa188f7005e3ab0 \ + --hash=sha256:d405d14bea042f166512add3091c1af40437c2e7f86988f3915fabd27b1e9cd2 \ + --hash=sha256:d55bbac04711e2980645af68b97d445cdbcce70e5216de444a6c4b6943ebcccd \ + --hash=sha256:d682cf1d22bab22a5be08539dca3d1593488a99998f9f412137bc323179067ff \ + --hash=sha256:d72f2b5e6e82ab8f94ea7d0d42f83c487dc159c5240d8f83beae684472864e2d \ + --hash=sha256:d95b253b88f7d308b1c0b417c4624f44553ba4762816f94e6986819b9c273fb2 \ + --hash=sha256:dd96e5d15385d301733113bcaa324c8bcf111275b7675a9c6e88bfb19fc05e3b \ + --hash=sha256:de2cfbb09e88f0f795fd90cf955858fc2c691df65b1f21f0aa00b99f3fbc661d \ + --hash=sha256:de7c42f897e689ee6f9e93c4bec72b99ae3b32a2ade1c7e4798e690ff5246e02 \ + --hash=sha256:df649916b81822543d1c8e0e1d079235f68acdc7d270c911e8425045a8cfc57e \ + --hash=sha256:e04e2f7f8916ad3ddd417a7abdd295276a0bf216993d9318a5d61cc058209166 \ + --hash=sha256:e1d778fb7849a42d0ee5927ab0f7453bf9f85eef8887a546ec87db5ddb178945 \ + --hash=sha256:e4dab9484ec605c3016df9ad4fd4f9a390bc5d816a3b10c6550f8424bb80b18c \ + --hash=sha256:e6ab5ab30ef325b443f379ddb575a34969c333004fca5a1daa0133a6ffaad616 \ + --hash=sha256:e7393f1d64792763a48924ba31d1e44c2cfbc05e3b1c2c9abb4ceeadd912cced \ + --hash=sha256:e8cd3577c796be7231dcf80badcf2e0835a46665eaafd8ace124d886bab4d700 \ + --hash=sha256:e9205d97ed08a82ebb9a307e92914bb30e18cdf6f6b12ca4bedadb1588a0bfe1 \ + --hash=sha256:eae547b7315d055b0de2ec3965643b0ab82ad0106a7ffd29615ee9f266a02827 \ + --hash=sha256:ec22626a2d14620a83ca583c6f5a4080fa3155282718b6055c2ea48d3ef35970 \ + --hash=sha256:eca1124aced216b2500dc2609eade086d718e8249cb9696660ab447d50a758bd \ + --hash=sha256:ecde6dedd6fff127c273c76821bb754d793be1024bc33314a120f83a3c69460c \ + --hash=sha256:ed97fd56a561f5eb5706cebe94f1ad7c13b84d98312a05546f2ad036bafe87f4 \ + --hash=sha256:ef9ee5471edd58d1fcce1c80ffc8783a650e3e3a193fe90d52e43bb4d87bff1f \ + --hash=sha256:f52679ff4218d713b3b33f88c89ccbf3a5c2c12ba665fb80ccc4192b4608dbab \ + --hash=sha256:f8e49c9c364a7edcbe2a310f12733aad95b022495ef2a8d653f645e5d20c1564 \ + --hash=sha256:f9672ab4d398e1b602feadcffcdd3af44d5f5e6ddc15bc7d15d376d47e8e19f8 \ + --hash=sha256:fc3b4c5a1fd3a311563ed866c2c9b62da06cb6398bee186484ce95c820db71cb \ + --hash=sha256:fc3b4cc4539e055cfa39a3763c939f9d409eb40e85813257dcd761985a108554 # via pydantic -pydantic-settings==2.10.1 \ - --hash=sha256:06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee \ - --hash=sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796 - # via -r /tmp/requirements6816cy66.in +pydantic-settings==2.11.0 \ + --hash=sha256:d0e87a1c7d33593beb7194adb8470fc426e95ba02af83a0f23474a04c9a08180 \ + --hash=sha256:fe2cea3413b9530d10f3a5875adffb17ada5c1e1bab0b2885546d7310415207c + # via + # -r /tmp/requirementscnex08p2.in + # online-fxreader-pr34 pyproject-hooks==1.2.0 \ --hash=sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8 \ --hash=sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913 @@ -500,13 +529,13 @@ pyproject-metadata==0.9.1 \ --hash=sha256:b8b2253dd1b7062b78cf949a115f02ba7fa4114aabe63fa10528e9e1a954a816 \ --hash=sha256:ee5efde548c3ed9b75a354fc319d5afd25e9585fa918a34f62f904cc731973ad # via meson-python -pyright==1.1.404 \ - --hash=sha256:455e881a558ca6be9ecca0b30ce08aa78343ecc031d37a198ffa9a7a1abeb63e \ - --hash=sha256:c7b7ff1fdb7219c643079e4c3e7d4125f0dafcc19d253b47e898d130ea426419 - # via -r /tmp/requirements6816cy66.in -python-dotenv==1.1.1 \ - --hash=sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc \ - --hash=sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab +pyright==1.1.407 \ + --hash=sha256:099674dba5c10489832d4a4b2d302636152a9a42d317986c38474c76fe562262 \ + --hash=sha256:6dd419f54fcc13f03b52285796d65e639786373f433e243f8b94cf93a7444d21 + # via -r /tmp/requirementscnex08p2.in +python-dotenv==1.2.1 \ + --hash=sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6 \ + --hash=sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61 # via pydantic-settings pyyaml==6.0.2 \ --hash=sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff \ @@ -563,31 +592,31 @@ pyyaml==6.0.2 \ --hash=sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12 \ --hash=sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4 # via yq -ruff==0.12.10 \ - --hash=sha256:059e863ea3a9ade41407ad71c1de2badfbe01539117f38f763ba42a1206f7559 \ - --hash=sha256:141ce3d88803c625257b8a6debf4a0473eb6eed9643a6189b68838b43e78165a \ - --hash=sha256:189ab65149d11ea69a2d775343adf5f49bb2426fc4780f65ee33b423ad2e47f9 \ - --hash=sha256:1bef6161e297c68908b7218fa6e0e93e99a286e5ed9653d4be71e687dff101cf \ - --hash=sha256:1f68433c4fbc63efbfa3ba5db31727db229fa4e61000f452c540474b03de52a9 \ - --hash=sha256:2c6f4064c69d2542029b2a61d39920c85240c39837599d7f2e32e80d36401d6e \ - --hash=sha256:37b4a64f4062a50c75019c61c7017ff598cb444984b638511f48539d3a1c98db \ - --hash=sha256:4f1345fbf8fb0531cd722285b5f15af49b2932742fc96b633e883da8d841896b \ - --hash=sha256:7837eca8787f076f67aba2ca559cefd9c5cbc3a9852fd66186f4201b87c1563e \ - --hash=sha256:7d1a4e0bdfafcd2e3e235ecf50bf0176f74dd37902f241588ae1f6c827a36c56 \ - --hash=sha256:822d9677b560f1fdeab69b89d1f444bf5459da4aa04e06e766cf0121771ab844 \ - --hash=sha256:8b593cb0fb55cc8692dac7b06deb29afda78c721c7ccfed22db941201b7b8f7b \ - --hash=sha256:9de785e95dc2f09846c5e6e1d3a3d32ecd0b283a979898ad427a9be7be22b266 \ - --hash=sha256:ae479e1a18b439c59138f066ae79cc0f3ee250712a873d00dbafadaad9481e5b \ - --hash=sha256:cc138cc06ed9d4bfa9d667a65af7172b47840e1a98b02ce7011c391e54635ffc \ - --hash=sha256:d59e58586829f8e4a9920788f6efba97a13d1fa320b047814e8afede381c6839 \ - --hash=sha256:e67d96827854f50b9e3e8327b031647e7bcc090dbe7bb11101a81a3a2cbf1cc9 \ - --hash=sha256:ebb7333a45d56efc7c110a46a69a1b32365d5c5161e7244aaf3aa20ce62399c1 \ - --hash=sha256:f3fc21178cd44c98142ae7590f42ddcb587b8e09a3b849cbc84edb62ee95de60 - # via -r /tmp/requirements6816cy66.in +ruff==0.14.3 \ + --hash=sha256:0e2f8a0bbcffcfd895df39c9a4ecd59bb80dca03dc43f7fb63e647ed176b741e \ + --hash=sha256:1ec1ac071e7e37e0221d2f2dbaf90897a988c531a8592a6a5959f0603a1ecf5e \ + --hash=sha256:26eb477ede6d399d898791d01961e16b86f02bc2486d0d1a7a9bb2379d055dc1 \ + --hash=sha256:3d6bc90307c469cb9d28b7cfad90aaa600b10d67c6e22026869f585e1e8a2db0 \ + --hash=sha256:469e35872a09c0e45fecf48dd960bfbce056b5db2d5e6b50eca329b4f853ae20 \ + --hash=sha256:4ff876d2ab2b161b6de0aa1f5bd714e8e9b4033dc122ee006925fbacc4f62153 \ + --hash=sha256:678fdd7c7d2d94851597c23ee6336d25f9930b460b55f8598e011b57c74fd8c5 \ + --hash=sha256:71ff6edca490c308f083156938c0c1a66907151263c4abdcb588602c6e696a14 \ + --hash=sha256:786ee3ce6139772ff9272aaf43296d975c0217ee1b97538a98171bf0d21f87ed \ + --hash=sha256:7bfc42f81862749a7136267a343990f865e71fe2f99cf8d2958f684d23ce3dfa \ + --hash=sha256:876b21e6c824f519446715c1342b8e60f97f93264012de9d8d10314f8a79c371 \ + --hash=sha256:a497ec0c3d2c88561b6d90f9c29f5ae68221ac00d471f306fa21fa4264ce5fcd \ + --hash=sha256:a65e448cfd7e9c59fae8cf37f9221585d3354febaad9a07f29158af1528e165f \ + --hash=sha256:afcdc4b5335ef440d19e7df9e8ae2ad9f749352190e96d481dc501b753f0733e \ + --hash=sha256:b6fd8c79b457bedd2abf2702b9b472147cd860ed7855c73a5247fa55c9117654 \ + --hash=sha256:cd6291d0061811c52b8e392f946889916757610d45d004e41140d81fb6cd5ddc \ + --hash=sha256:d7b7006ac0756306db212fd37116cce2bd307e1e109375e1c6c106002df0ae5f \ + --hash=sha256:e231e1be58fc568950a04fbe6887c8e4b85310e7889727e2b81db205c45059eb \ + --hash=sha256:f3d91857d023ba93e14ed2d462ab62c3428f9bbf2b4fbac50a03ca66d31991f7 + # via -r /tmp/requirementscnex08p2.in setuptools==80.9.0 \ --hash=sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922 \ --hash=sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c - # via -r /tmp/requirements6816cy66.in + # via -r /tmp/requirementscnex08p2.in sniffio==1.3.1 \ --hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \ --hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc @@ -600,50 +629,63 @@ starlette==0.47.3 \ --hash=sha256:6bc94f839cc176c4858894f1f8908f0ab79dfec1a6b8402f6da9be26ebea52e9 \ --hash=sha256:89c0778ca62a76b826101e7c709e70680a1699ca7da6b44d38eb0a7e61fe4b51 # via fastapi -tomli==2.2.1 \ - --hash=sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6 \ - --hash=sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd \ - --hash=sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c \ - --hash=sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b \ - --hash=sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8 \ - --hash=sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6 \ - --hash=sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77 \ - --hash=sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff \ - --hash=sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea \ - --hash=sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192 \ - --hash=sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249 \ - --hash=sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee \ - --hash=sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4 \ - --hash=sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98 \ - --hash=sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8 \ - --hash=sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4 \ - --hash=sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281 \ - --hash=sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744 \ - --hash=sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69 \ - --hash=sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13 \ - --hash=sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140 \ - --hash=sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e \ - --hash=sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e \ - --hash=sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc \ - --hash=sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff \ - --hash=sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec \ - --hash=sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2 \ - --hash=sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222 \ - --hash=sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106 \ - --hash=sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272 \ - --hash=sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a \ - --hash=sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7 - # via -r /tmp/requirements6816cy66.in +tomli==2.3.0 \ + --hash=sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456 \ + --hash=sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845 \ + --hash=sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999 \ + --hash=sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0 \ + --hash=sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878 \ + --hash=sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf \ + --hash=sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3 \ + --hash=sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be \ + --hash=sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52 \ + --hash=sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b \ + --hash=sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67 \ + --hash=sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549 \ + --hash=sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba \ + --hash=sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22 \ + --hash=sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c \ + --hash=sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f \ + --hash=sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6 \ + --hash=sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba \ + --hash=sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45 \ + --hash=sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f \ + --hash=sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77 \ + --hash=sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606 \ + --hash=sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441 \ + --hash=sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0 \ + --hash=sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f \ + --hash=sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530 \ + --hash=sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05 \ + --hash=sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8 \ + --hash=sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005 \ + --hash=sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879 \ + --hash=sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae \ + --hash=sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc \ + --hash=sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b \ + --hash=sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b \ + --hash=sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e \ + --hash=sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf \ + --hash=sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac \ + --hash=sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8 \ + --hash=sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b \ + --hash=sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf \ + --hash=sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463 \ + --hash=sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876 + # via -r /tmp/requirementscnex08p2.in tomlkit==0.13.3 \ --hash=sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1 \ --hash=sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0 # via - # -r /tmp/requirements6816cy66.in + # -r /tmp/requirementscnex08p2.in + # online-fxreader-pr34 # yq tomlq==0.1.0 \ --hash=sha256:4b966fd999ed2bf69081b7c7f5caadbc4c9542d0ed5fcf2e9b7b4d8d7ada3c82 \ --hash=sha256:e775720e90da3e405142b9fe476145e71c0389f787b1ff9933f92a1704d8c6e7 - # via -r /tmp/requirements6816cy66.in + # via + # -r /tmp/requirementscnex08p2.in + # online-fxreader-pr34 types-pyyaml==6.0.12.20250822 \ --hash=sha256:1fe1a5e146aa315483592d292b72a172b65b946a6d98aa6ddd8e4aa838ab7098 \ --hash=sha256:259f1d93079d335730a9db7cff2bcaf65d7e04b4a56b5927d49a612199b59413 @@ -660,37 +702,37 @@ typing-extensions==4.15.0 \ # pydantic-core # pyright # typing-inspection -typing-inspection==0.4.1 \ - --hash=sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51 \ - --hash=sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28 +typing-inspection==0.4.2 \ + --hash=sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7 \ + --hash=sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464 # via # pydantic # pydantic-settings -uv==0.8.13 \ - --hash=sha256:18a502328545af511039c7b7c602a0aa89eeff23b1221a1f56d99b3a3fecfddd \ - --hash=sha256:20862f612de38f6dea55d40467a29f3cb621b256a4b5891ae55debbbdf1db2b4 \ - --hash=sha256:2113cd877974b68ea2af64a2f2cc23708ba97066046e78efb72ba94e5fef617a \ - --hash=sha256:28c8d4560c673ff5c798f2f4422281840728f46ebf1946345b65d065f8344c03 \ - --hash=sha256:2945c32b8fcf23807ef1f74c390795e2b00371c53b94c015cc6e7b0cfbab9d94 \ - --hash=sha256:3735a452cdc3168932d128891d7e8866b4a2d052283c6da5ccfe0b038d1cf8bd \ - --hash=sha256:3b5c6e44238007ec1d25212cafe1b37a8506d425d1dd74a267cb9072a61930f9 \ - --hash=sha256:3bac51ea503d97f371222f23e845fc4ab95465ac3e958c7589d6743c75445b71 \ - --hash=sha256:404ca19b2d860ab661e1d78633f594e994f8422af8772ad237d763fe353da2ab \ - --hash=sha256:4a6d37547947fcae57244b4d1f3b62fba55f4a85d3e45e7284a93b6cd5bedca4 \ - --hash=sha256:4c2c5e5962239ecaff6444d5bc22422a9bd2da25a80adc6ab14cb42e4461b1cf \ - --hash=sha256:73459fe1403b1089853071db6770450dc03e4058848f7146d88cff5f1c352743 \ - --hash=sha256:854c4e75024a4894477bf61684b2872b83c77ca87d1bad62692bcc31200619c3 \ - --hash=sha256:8a3739540f8b0b5258869b1671185d55daacfa4609eaffd235573ac938ec01a6 \ - --hash=sha256:a4438eca3d301183c52994a6d2baff70fd1840421a83446f3cabb1d0d0b50aff \ - --hash=sha256:cf3ce98404ddc1e11cd2c2604668f8f81219cf00bb1227b792fdf5dbb4faf31a \ - --hash=sha256:d22fa55580b224779279b98e0b23cbc45e51837e1fac616d7c5d03aff668a998 \ - --hash=sha256:eb90089624d92d57b8582f708973db8988e09dba6bae83991dba20731d82eb6a \ - --hash=sha256:f6c508aa9c5210577008e1919b532e38356fe68712179399f00462b3e78fd845 - # via -r /tmp/requirements6816cy66.in +uv==0.9.7 \ + --hash=sha256:0fdbfad5b367e7a3968264af6da5bbfffd4944a90319042f166e8df1a2d9de09 \ + --hash=sha256:134e0daac56f9e399ccdfc9e4635bc0a13c234cad9224994c67bae462e07399a \ + --hash=sha256:1aaf79b4234400e9e2fbf5b50b091726ccbb0b6d4d032edd3dfd4c9673d89dca \ + --hash=sha256:34fe0af83fcafb9e2b786f4bd633a06c878d548a7c479594ffb5607db8778471 \ + --hash=sha256:555ee72146b8782c73d755e4a21c9885c6bfc81db0ffca2220d52dddae007eb7 \ + --hash=sha256:56a440ccde7624a7bc070e1c2492b358c67aea9b8f17bc243ea27c5871c8d02c \ + --hash=sha256:62b315f62669899076a1953fba6baf50bd2b57f66f656280491331dcedd7e6c6 \ + --hash=sha256:635e82c2d0d8b001618af82e4f2724350f15814f6462a71b3ebd44adec21f03c \ + --hash=sha256:7019f4416925f4091b9d28c1cf3e8444cf910c4ede76bdf1f6b9a56ca5f97985 \ + --hash=sha256:777bb1de174319245a35e4f805d3b4484d006ebedae71d3546f95e7c28a5f436 \ + --hash=sha256:89697fa0d7384ba047daf75df844ee7800235105e41d08e0c876861a2b4aa90e \ + --hash=sha256:8cf6bc2482d1293cc630f66b862b494c09acda9b7faff7307ef52667a2b3ad49 \ + --hash=sha256:b5f1fb8203a77853db176000e8f30d5815ab175dc46199db059f97a72fc51110 \ + --hash=sha256:bb8bfcc2897f7653522abc2cae80233af756ad857bfbbbbe176f79460cbba417 \ + --hash=sha256:bcf878528bd079fe8ae15928b5dfa232fac8b0e1854a2102da6ae1a833c31276 \ + --hash=sha256:c9810ee8173dce129c49b338d5e97f3d7c7e9435f73e0b9b26c2f37743d3bb9e \ + --hash=sha256:d13da6521d4e841b1e0a9fda82e793dcf8458a323a9e8955f50903479d0bfa97 \ + --hash=sha256:d6e5fe28ca05a4b576c0e8da5f69251dc187a67054829cfc4afb2bfa1767114b \ + --hash=sha256:edd768f6730bba06aa10fdbd80ee064569f7236806f636bf65b68136a430aad0 + # via -r /tmp/requirementscnex08p2.in uvicorn==0.35.0 \ --hash=sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a \ --hash=sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01 - # via -r /tmp/requirements6816cy66.in + # via -r /tmp/requirementscnex08p2.in xmltodict==0.14.2 \ --hash=sha256:201e7c28bb210e374999d1dde6382923ab0ed1a8a5faeece48ab525b7810a553 \ --hash=sha256:20cc7d723ed729276e808f26fb6b3599f786cbc37e06c65e192ba77c40f20aac diff --git a/releases/whl/online_fxreader_pr34-0.1.5.39-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.39-py3-none-any.whl index 18d5537..b68f2ae 100644 --- a/releases/whl/online_fxreader_pr34-0.1.5.39-py3-none-any.whl +++ b/releases/whl/online_fxreader_pr34-0.1.5.39-py3-none-any.whl @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:174f4cb51997b50fb6e7e4cca475077a8d91029759417251e312487d7ee70c28 -size 76117 +oid sha256:f3f2973ec7d90c44bad04cbfca37cfe065ba2533f4b0320f82153fb439a96739 +size 76124 From 7805bad8e7c8d8afc754ca394a89e45ed81e6f2d Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Fri, 7 Nov 2025 13:46:40 +0300 Subject: [PATCH 49/70] [+] update makefile 1. make sure vim put respects INSTALL_ROOT variable; --- Makefile | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 573ad8c..345e374 100644 --- a/Makefile +++ b/Makefile @@ -90,12 +90,13 @@ dotfiles_put: #commands install -f -p dotfiles -s dotfiles/ -t ~/.config/ dotfiles_vim_put: + @echo INSTALL_ROOT=$(INSTALL_ROOT) mkdir -p $(INSTALL_ROOT) mkdir -p $(INSTALL_ROOT)/.vim - cp dotfiles/.vimrc ~/.vimrc - cp dotfiles/.py3.vimrc ~/.py3.vimrc - cp -rp dotfiles/.vim/online_fxreader_pr34_vim ~/.vim/ + cp dotfiles/.vimrc $(INSTALL_ROOT)/.vimrc + cp dotfiles/.py3.vimrc $(INSTALL_ROOT)/.py3.vimrc + cp -rp dotfiles/.vim/online_fxreader_pr34_vim $(INSTALL_ROOT)/.vim/ dotfiles_tmux_put: mkdir -p $(INSTALL_ROOT) From 1377fdd9ff05eb0bc86305d29831b36ed5f29ac0 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Sat, 8 Nov 2025 09:56:53 +0300 Subject: [PATCH 50/70] [+] update pr34 1. update cli_bootstrap to explicitly fetch whl-cache; --- python/meson.build | 2 +- .../pr34/commands_typed/cli_bootstrap.py | 29 +++++++++++++++++-- ...ne_fxreader_pr34-0.1.5.40-py3-none-any.whl | 3 ++ 3 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.40-py3-none-any.whl diff --git a/python/meson.build b/python/meson.build index e45208e..8618f44 100644 --- a/python/meson.build +++ b/python/meson.build @@ -5,7 +5,7 @@ project( ).stdout().strip('\n'), # 'online.fxreader.uv', # ['c', 'cpp'], - version: '0.1.5.39', + version: '0.1.5.40', # default_options: [ # 'cpp_std=c++23', # # 'prefer_static=true', diff --git a/python/online/fxreader/pr34/commands_typed/cli_bootstrap.py b/python/online/fxreader/pr34/commands_typed/cli_bootstrap.py index 7a95032..c1b4374 100644 --- a/python/online/fxreader/pr34/commands_typed/cli_bootstrap.py +++ b/python/online/fxreader/pr34/commands_typed/cli_bootstrap.py @@ -315,6 +315,7 @@ def pyproject_load( @dataclasses.dataclass class BootstrapSettings: env_path: pathlib.Path + whl_cache_path: pathlib.Path python_path: pathlib.Path base_dir: pathlib.Path python_version: Optional[str] = dataclasses.field( @@ -351,11 +352,14 @@ class BootstrapSettings: else: env_path = base_dir / '.venv' + whl_cache_path = env_path.parent / '.venv-whl-cache' + python_path = env_path / 'bin' / 'python3' return cls( base_dir=base_dir, env_path=env_path, + whl_cache_path=whl_cache_path, python_path=python_path, ) @@ -505,13 +509,33 @@ def env_bootstrap( ] ) + if not bootstrap_settings.env_path.exists(): + subprocess.check_call( + [ + 'pip', + 'download', + '--only-binary=:all:', + *uv_python_version, + *pip_find_links_args, + '-r', + str(requirements_path), + '-d', + str(bootstrap_settings.whl_cache_path), + ] + ) + + cache_find_links_args = ( + '-f', + str(bootstrap_settings.whl_cache_path), + ) + subprocess.check_call( [ 'uv', *[o for o in bootstrap_settings.uv_args if not o in ['-U', '--upgrade']], 'venv', *venv_python_version, - *pip_find_links_args, + *cache_find_links_args, # '--seed', str(bootstrap_settings.env_path), ] @@ -523,7 +547,8 @@ def env_bootstrap( 'pip', 'install', *uv_python_version, - *pip_find_links_args, + *cache_find_links_args, + # *pip_find_links_args, '-p', bootstrap_settings.python_path, '--require-hashes', diff --git a/releases/whl/online_fxreader_pr34-0.1.5.40-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.40-py3-none-any.whl new file mode 100644 index 0000000..7aeca1e --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.40-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a43f4743a00411371d52197d82d7573e9eb34b8a54a343398f9af1b267d8a215 +size 76224 From b50468154f91a86114fa7bc05af776762af8aef3 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Sun, 30 Nov 2025 21:51:48 +0300 Subject: [PATCH 51/70] [+] update config 1. update pr34 cpufreq action 1.1. add support for amd_pstate cpus; 1.2. add pr34 .whl release; 2. fetch configs for new platform; 3. fetch sensitive configs and store gpg encrypted data; --- .gitattributes | 1 + Makefile | 19 +++ .../etc/udev/rules.d/10-tun0.rules | 1 + .../etc/udev/rules.d/40-leds.rules | 11 ++ .../etc/udev/rules.d/40-scsi-power.rules | 1 + .../usr/local/bin/online-fxreader-pr34-udev | 116 ++++++++++++++++++ .../local/bin/online.fxreader.pr34-on-resume | 73 +++++++++++ ...tive-configs-2025-11-30T21:48:40+03:00.gpg | 3 + ...tive-configs-2025-11-30T21:50:49+03:00.gpg | 3 + python/meson.build | 2 +- python/online/fxreader/pr34/commands.py | 22 +++- ...ne_fxreader_pr34-0.1.5.41-py3-none-any.whl | 3 + 12 files changed, 253 insertions(+), 2 deletions(-) create mode 100644 platform_dotfiles/ideapad_slim_3_15arp10/etc/udev/rules.d/10-tun0.rules create mode 100644 platform_dotfiles/ideapad_slim_3_15arp10/etc/udev/rules.d/40-leds.rules create mode 100644 platform_dotfiles/ideapad_slim_3_15arp10/etc/udev/rules.d/40-scsi-power.rules create mode 100755 platform_dotfiles/ideapad_slim_3_15arp10/usr/local/bin/online-fxreader-pr34-udev create mode 100755 platform_dotfiles/ideapad_slim_3_15arp10/usr/local/bin/online.fxreader.pr34-on-resume create mode 100644 platform_dotfiles_gpg/ideapad_slim_3_15arp10/sensitive-configs-2025-11-30T21:48:40+03:00.gpg create mode 100644 platform_dotfiles_gpg/ideapad_slim_3_15arp10/sensitive-configs-2025-11-30T21:50:49+03:00.gpg create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.41-py3-none-any.whl diff --git a/.gitattributes b/.gitattributes index fb1135e..990f6b5 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,3 +2,4 @@ releases/tar/** filter=lfs diff=lfs merge=lfs -text releases/whl/** filter=lfs diff=lfs merge=lfs -text python/deps/whl/** filter=lfs diff=lfs merge=lfs -text docker/*/deps/whl/** filter=lfs diff=lfs merge=lfs -text +**/*.gpg filter=lfs diff=lfs merge=lfs -text diff --git a/Makefile b/Makefile index 345e374..2f57fca 100644 --- a/Makefile +++ b/Makefile @@ -117,6 +117,25 @@ dotfiles_put_platform: sudo udevadm control --reload sudo systemctl daemon-reload +GPG_RECIPIENTS_ARGS ?= -r 891382BEBFEFFC6729837400DA0B6C15FBB70FC9 +dotfiles_fetch_platform: + mkdir -p platform_dotfiles/$(PLATFORM) + mkdir -p platform_dotfiles_gpg/$(PLATFORM) + tar -cvf - \ + /etc/udev/rules.d/ \ + /usr/local/bin \ + | tar -xvf - -C platform_dotfiles/$(PLATFORM) + tar -h -cvf - \ + ~/.sway/config.d \ + ~/.config/commands-status.json \ + /etc/fstab \ + | gpg -e $(GPG_RECIPIENTS_ARGS) \ + > platform_dotfiles_gpg/$(PLATFORM)/sensitive-configs-$$(date -Iseconds).gpg + +dotfiles_fetch_platform_ideapad_slim_3_15arp10: + make dotfiles_fetch_platform \ + PLATFORM=ideapad_slim_3_15arp10 + dotfiles_sway_put: mkdir -p ~/.sway cp dotfiles/.sway/config ~/.sway/config diff --git a/platform_dotfiles/ideapad_slim_3_15arp10/etc/udev/rules.d/10-tun0.rules b/platform_dotfiles/ideapad_slim_3_15arp10/etc/udev/rules.d/10-tun0.rules new file mode 100644 index 0000000..b7272d5 --- /dev/null +++ b/platform_dotfiles/ideapad_slim_3_15arp10/etc/udev/rules.d/10-tun0.rules @@ -0,0 +1 @@ +ACTION=="add", SUBSYSTEM=="net", KERNEL=="tun0", TAG+="systemd" diff --git a/platform_dotfiles/ideapad_slim_3_15arp10/etc/udev/rules.d/40-leds.rules b/platform_dotfiles/ideapad_slim_3_15arp10/etc/udev/rules.d/40-leds.rules new file mode 100644 index 0000000..f4ffc49 --- /dev/null +++ b/platform_dotfiles/ideapad_slim_3_15arp10/etc/udev/rules.d/40-leds.rules @@ -0,0 +1,11 @@ +ACTION=="add|change", SUBSYSTEM=="leds", DEVPATH=="/devices/pci0000:00/0000:00:1b.0/hdaudioC0D0/leds/hda::mute", RUN{program}+="/usr/bin/chmod 666 /sys$devpath/brightness" +ACTION=="add|change", SUBSYSTEM=="leds", DEVPATH=="/devices/platform/applesmc.768/leds/smc::kbd_backlight", RUN{program}+="/usr/bin/chmod 666 /sys$devpath/brightness" +# udevadm info --attribute-walk --path=/sys/devices/platform/applesmc.768/ +# udevadm trigger --action=add --verbose --parent-match /devices/platform/applesmc.768/ +#ACTION=="add|change", KERNEL=="applesmc.768", SUBSYSTEM=="platform", DRIVER=="applesmc", RUN{program}+="ls -allh /sys$devpath/", OPTIONS="log_level=debug" +#ACTION=="add|change", KERNEL=="applesmc.768", SUBSYSTEM=="platform", DRIVER=="applesmc", RUN{program}+="/usr/bin/ls -allh /sys$devpath/", OPTIONS="log_level=debug" +ACTION=="add|change", KERNEL=="applesmc.768", SUBSYSTEM=="platform", DRIVER=="applesmc", TAG+="systemd", ENV{SYSTEMD_WANTS}="online.fxreader.pr34.udev@$devnode.service", OPTIONS="log_level=debug" +#KERNEL=="applesmc.768", SUBSYSTEM=="platform", DRIVER=="applesmc", MODE="0660", TAG+="uaccess", OPTIONS="log_level=debug", OPTIONS+="watch" +ACTION=="add|change", DEVPATH=="/class/backlight/intel_backlight", RUN{program}+="/usr/bin/chmod 666 /sys$devpath/brightness" +ACTION=="add|change", KERNEL=="cpu1", SUBSYSTEM=="cpu", TAG+="systemd", ENV{SYSTEMD_WANTS}="online.fxreader.pr34.udev@$devnode.service", OPTIONS="log_level=debug" +#ACTION=="add|change", KERNEL=="cpu[0-9]", SUBSYSTEM=="cpu", TAG+="systemd", ENV{SYSTEMD_WANTS}="online.fxreader.pr34.udev@$devnode.service", OPTIONS="log_level=debug" diff --git a/platform_dotfiles/ideapad_slim_3_15arp10/etc/udev/rules.d/40-scsi-power.rules b/platform_dotfiles/ideapad_slim_3_15arp10/etc/udev/rules.d/40-scsi-power.rules new file mode 100644 index 0000000..cbca5a7 --- /dev/null +++ b/platform_dotfiles/ideapad_slim_3_15arp10/etc/udev/rules.d/40-scsi-power.rules @@ -0,0 +1 @@ +ACTION=="add", SUBSYSTEM=="scsi_host", KERNEL=="host*", ATTR{link_power_management_policy}="max_performance", OPTIONS="log_level=debug" diff --git a/platform_dotfiles/ideapad_slim_3_15arp10/usr/local/bin/online-fxreader-pr34-udev b/platform_dotfiles/ideapad_slim_3_15arp10/usr/local/bin/online-fxreader-pr34-udev new file mode 100755 index 0000000..9d426c6 --- /dev/null +++ b/platform_dotfiles/ideapad_slim_3_15arp10/usr/local/bin/online-fxreader-pr34-udev @@ -0,0 +1,116 @@ +#!/usr/bin/python3 + +# vi: filetype=python + +import re +import sys +import os +import time +import subprocess +import argparse +import logging + +from typing import (Any,) + +logger = logging.getLogger(__name__) + + +def run() -> None: + logging.basicConfig(level=logging.INFO) + + parser = argparse.ArgumentParser() + parser.add_argument( + '--device', + ) + + options = parser.parse_args() + + DEVICES : dict[str, Any] = dict( + applesmc=dict( + devpath='sys/devices/platform/applesmc.768', + node='/sys/devices/platform/applesmc.768/fan1_manual', + cmd=r''' + chown root:fan /sys/devices/platform/applesmc.768/fan1_* + chmod g+w /sys/devices/platform/applesmc.768/fan1_* + ''', + ), + intel_pstate=dict( + devpath=r'/?sys/devices/system/cpu/cpu0', + node='/sys/devices/system/cpu/intel_pstate/no_turbo', + cmd=r''' + chown root:fan /sys/devices/system/cpu/intel_pstate/no_turbo + chown root:fan /sys/devices/system/cpu/intel_pstate/max_perf_pct + #chown root:fan /sys/devices/system/cpu/intel_pstate/status + chmod g+w /sys/devices/system/cpu/intel_pstate/no_turbo + chmod g+w /sys/devices/system/cpu/intel_pstate/max_perf_pct + #chmod g+w /sys/devices/system/cpu/intel_pstate/status + echo passive > /sys/devices/system/cpu/intel_pstate/status + chown root:fan /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor + chown root:fan /sys/devices/system/cpu/cpu*/cpufreq/scaling_max_freq + chmod g+w /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor + chmod g+w /sys/devices/system/cpu/cpu*/cpufreq/scaling_max_freq + ''', + ), + amd_pstate=dict( + devpath=r'/?sys/devices/system/cpu/cpu1', + node='/sys/devices/system/cpu/amd_pstate/status', + cmd=r''' + chown root:fan /sys/devices/system/cpu/cpu*/cpufreq/boost + chown root:fan /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor + chown root:fan /sys/devices/system/cpu/cpu*/cpufreq/scaling_max_freq + chmod g+w /sys/devices/system/cpu/cpu*/cpufreq/boost + chmod g+w /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor + chmod g+w /sys/devices/system/cpu/cpu*/cpufreq/scaling_max_freq + ''', + ), + #governor=dict( + # devpath=r'/?sys/devices/system/cpu/cpu(\d+)', + # node=r'/sys/devices/system/cpu/cpu{0}/cpufreq/scaling_governor', + # cmd=r''' + # chown root:fan /sys/devices/system/cpu/cpu{0}/cpufreq/scaling_governor + # chown root:fan /sys/devices/system/cpu/cpu{0}/cpufreq/scaling_max_freq + # chmod g+w /sys/devices/system/cpu/cpu{0}/cpufreq/scaling_governor + # chmod g+w /sys/devices/system/cpu/cpu{0}/cpufreq/scaling_max_freq + # ''', + #), + ) + + processed : int = 0 + + logger.info(dict(device=options.device)) + + for k, v in DEVICES.items(): + devpath = re.compile(v['devpath']) + + devpath_m = devpath.match(options.device) + + if devpath_m is None: + continue + + node_2 = v['node'].format(*devpath_m.groups()) + + # logger.info(dict(devpath_m=devpath_m, node=node_2)) + + while not os.path.exists(node_2): + #continue + time.sleep(1) + + cmd_2 = v['cmd'].format(*devpath_m.groups()) + + subprocess.check_call(cmd_2, shell=True) + + logger.info(dict( + devpath_m=devpath_m, + node_2=node_2, + cmd_2=cmd_2, + msg='processed', + label=k, + )) + + processed += 1 + + if processed == 0: + raise NotImplementedError + +if __name__ == '__main__': + run() diff --git a/platform_dotfiles/ideapad_slim_3_15arp10/usr/local/bin/online.fxreader.pr34-on-resume b/platform_dotfiles/ideapad_slim_3_15arp10/usr/local/bin/online.fxreader.pr34-on-resume new file mode 100755 index 0000000..95a97ea --- /dev/null +++ b/platform_dotfiles/ideapad_slim_3_15arp10/usr/local/bin/online.fxreader.pr34-on-resume @@ -0,0 +1,73 @@ +#!/usr/bin/python3 +#vi syntax=python +import subprocess +import sys +import logging + +from typing import (Optional,) + +logger = logging.getLogger(__name__) + +logging.basicConfig(level=logging.INFO) + +if sys.argv[1] == 'before-suspend': + logger.info('before-suspend started') + subprocess.check_call(['nmcli', 'radio', 'wifi', 'off']) + #subprocess.check_call(['modprobe', '-r', 'atkbd',]) + subprocess.check_call(['modprobe', '-r', 'ideapad_laptop',]) + subprocess.check_call(['modprobe', '-r', 'i8042',]) + logger.info('before-suspend done') +elif sys.argv[1] == 'after-suspend': + logger.info('after-suspend started') + subprocess.check_call(['modprobe', 'i8042',]) + subprocess.check_call(['modprobe', 'ideapad_laptop',]) + #subprocess.check_call(['modprobe', 'atkbd',]) + subprocess.check_call(['nmcli', 'radio', 'wifi', 'on']) + subprocess.check_call(['rfkill', 'unblock', '109']) + #subprocess.check_call(r''' + # # systemctl restart wg-quick@siarhei-hp.service + #''', shell=True,) + logger.info('after-suspend done') +elif sys.argv[1] == 'lid-switch': + import evdev + import time + import io + + lid = evdev.UInput({5 : [0]}, name="virtual-lid-switch") + + last_state : Optional[bool] = None + + try: + while True: + try: + with io.open('/proc/acpi/button/lid/LID0/state', 'r') as f: + value = f.read() + except: + logger.exception('') + value = None + time.sleep(1) + continue + + if not value is None: + if 'open' in value: + is_opened = True + else: + is_opened = False + + if last_state != is_opened: + if is_opened: + logger.info(dict(msg='lid opened')) + lid.write(5, 0, 0) + lid.write(0, 0, 0) + else: + logger.info(dict(msg='lid closed')) + lid.write(5, 0, 1) + lid.write(0, 0, 0) + + last_state = is_opened + + time.sleep(0.1) + finally: + lid.close() +else: + raise NotImplementedError diff --git a/platform_dotfiles_gpg/ideapad_slim_3_15arp10/sensitive-configs-2025-11-30T21:48:40+03:00.gpg b/platform_dotfiles_gpg/ideapad_slim_3_15arp10/sensitive-configs-2025-11-30T21:48:40+03:00.gpg new file mode 100644 index 0000000..3b87013 --- /dev/null +++ b/platform_dotfiles_gpg/ideapad_slim_3_15arp10/sensitive-configs-2025-11-30T21:48:40+03:00.gpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:669462268da0a51921d882ff5869072e4a04723a0002112bf16bd2e9f387ed4c +size 1257 diff --git a/platform_dotfiles_gpg/ideapad_slim_3_15arp10/sensitive-configs-2025-11-30T21:50:49+03:00.gpg b/platform_dotfiles_gpg/ideapad_slim_3_15arp10/sensitive-configs-2025-11-30T21:50:49+03:00.gpg new file mode 100644 index 0000000..c7f78dc --- /dev/null +++ b/platform_dotfiles_gpg/ideapad_slim_3_15arp10/sensitive-configs-2025-11-30T21:50:49+03:00.gpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8e8a26795fe289518464fa5de23898531af3dcb0a06d292c7bbd3fb86a473a1d +size 1666 diff --git a/python/meson.build b/python/meson.build index 8618f44..5191121 100644 --- a/python/meson.build +++ b/python/meson.build @@ -5,7 +5,7 @@ project( ).stdout().strip('\n'), # 'online.fxreader.uv', # ['c', 'cpp'], - version: '0.1.5.40', + version: '0.1.5.41', # default_options: [ # 'cpp_std=c++23', # # 'prefer_static=true', diff --git a/python/online/fxreader/pr34/commands.py b/python/online/fxreader/pr34/commands.py index ac1e317..3399ae0 100644 --- a/python/online/fxreader/pr34/commands.py +++ b/python/online/fxreader/pr34/commands.py @@ -2605,8 +2605,10 @@ def desktop_services(argv): ]: if os.path.exists('/sys/bus/platform/devices/applesmc.768'): return 'applesmc.768' - if os.path.exists('/sys/devices/system/cpu/intel_pstate/no_turbo'): + elif os.path.exists('/sys/devices/system/cpu/intel_pstate/no_turbo'): return 'intel_pstate' + elif os.path.exists('/sys/devices/system/cpu/amd_pstate'): + return 'amd_pstate' else: raise NotImplementedError @@ -2637,6 +2639,15 @@ echo performance | tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; echo 40 > /sys/devices/system/cpu/intel_pstate/max_perf_pct; echo 900000 | tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_max_freq; echo schedutil | tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; + """, + shell=True, + ) + elif cls.profile() == 'amd_pstate': + subprocess.check_call( + r""" +echo powersave | tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; +echo 800000 | tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_max_freq; +echo 0 | tee /sys/devices/system/cpu/cpu*/cpufreq/boost """, shell=True, ) @@ -2668,6 +2679,15 @@ echo powersave | tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; echo 60 > /sys/devices/system/cpu/intel_pstate/max_perf_pct; echo 1200000 | tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_max_freq; echo schedutil | tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; + """, + shell=True, + ) + elif cls.profile() == 'amd_pstate': + subprocess.check_call( + r""" +echo performance | tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; +cat /sys/devices/system/cpu/cpu0/cpufreq/amd_pstate_max_freq | tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_max_freq; +echo 1 | tee /sys/devices/system/cpu/cpu*/cpufreq/boost """, shell=True, ) diff --git a/releases/whl/online_fxreader_pr34-0.1.5.41-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.41-py3-none-any.whl new file mode 100644 index 0000000..a6087f6 --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.41-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5b302323f35f1ed0f351e171be3d54257762a7a816644652eb4925b96bc8007c +size 76304 From 7d5868345b7065397016352624c3c849cfdf6c1b Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Mon, 1 Dec 2025 13:56:57 +0300 Subject: [PATCH 52/70] [+] update platform 1. add few random scripts; 1.1. systemd_gtk, oom_firefox are vibe coded initially; oom_firefox is useful, but migrated to ps subprocess manually; since psutils is very inefficient, puts too much load on CPU; --- Makefile | 7 +- .../.local/bin/gnome-shortcuts-macbook-air | 13 + .../home/nartes/.local/bin/oom_firefox | 414 ++++++++++++++++++ .../home/nartes/.local/bin/systemd_gtk | 330 ++++++++++++++ 4 files changed, 763 insertions(+), 1 deletion(-) create mode 100755 platform_dotfiles/ideapad_slim_3_15arp10/home/nartes/.local/bin/gnome-shortcuts-macbook-air create mode 100755 platform_dotfiles/ideapad_slim_3_15arp10/home/nartes/.local/bin/oom_firefox create mode 100755 platform_dotfiles/ideapad_slim_3_15arp10/home/nartes/.local/bin/systemd_gtk diff --git a/Makefile b/Makefile index 2f57fca..b1fbf02 100644 --- a/Makefile +++ b/Makefile @@ -120,11 +120,16 @@ dotfiles_put_platform: GPG_RECIPIENTS_ARGS ?= -r 891382BEBFEFFC6729837400DA0B6C15FBB70FC9 dotfiles_fetch_platform: mkdir -p platform_dotfiles/$(PLATFORM) - mkdir -p platform_dotfiles_gpg/$(PLATFORM) tar -cvf - \ /etc/udev/rules.d/ \ + ~/.local/bin/oom_firefox \ + ~/.local/bin/systemd_gtk \ + ~/.local/bin/gnome-shortcuts-macbook-air \ /usr/local/bin \ | tar -xvf - -C platform_dotfiles/$(PLATFORM) + +dotfiles_fetch_platform_gpg: + mkdir -p platform_dotfiles_gpg/$(PLATFORM) tar -h -cvf - \ ~/.sway/config.d \ ~/.config/commands-status.json \ diff --git a/platform_dotfiles/ideapad_slim_3_15arp10/home/nartes/.local/bin/gnome-shortcuts-macbook-air b/platform_dotfiles/ideapad_slim_3_15arp10/home/nartes/.local/bin/gnome-shortcuts-macbook-air new file mode 100755 index 0000000..838b40f --- /dev/null +++ b/platform_dotfiles/ideapad_slim_3_15arp10/home/nartes/.local/bin/gnome-shortcuts-macbook-air @@ -0,0 +1,13 @@ +#!/usr/bin/bash + +commands gnome-shortcuts \ + -a \ + 'powersave' \ + 'commands desktop-services --cpufreq-action powersave' \ + '1' + +commands gnome-shortcuts \ + -a \ + 'performance' \ + 'commands desktop-services --cpufreq-action performance' \ + '2' diff --git a/platform_dotfiles/ideapad_slim_3_15arp10/home/nartes/.local/bin/oom_firefox b/platform_dotfiles/ideapad_slim_3_15arp10/home/nartes/.local/bin/oom_firefox new file mode 100755 index 0000000..ae5400d --- /dev/null +++ b/platform_dotfiles/ideapad_slim_3_15arp10/home/nartes/.local/bin/oom_firefox @@ -0,0 +1,414 @@ +#!/usr/bin/env python3 +import psutil +import logging +import os +import signal +import subprocess +import threading +import time +import argparse +import re +import sys + +from prompt_toolkit.application import Application +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.layout import Layout, HSplit, FloatContainer, Float +from prompt_toolkit.layout.containers import Window +from prompt_toolkit.layout.controls import FormattedTextControl +from prompt_toolkit.widgets import TextArea, Frame, Dialog, Button, Label +from prompt_toolkit.styles import Style + +from typing import (TypedDict,) + +logger = logging.getLogger(__name__) + +__version__ = "0.6.1" +__created__ = "2025-11-21" + +# — Helper for cgroup / slice matching — + +def get_cgroup_path(pid): + try: + with open(f"/proc/{pid}/cgroup", "r") as f: + for line in f: + parts = line.strip().split(":", 2) + if len(parts) == 3: + return parts[2] + except Exception: + logger.exception('') + return None + return None + +def slice_matches(cpath, target_slice): + if not cpath or not target_slice: + return False + comps = cpath.strip("/").split("/") + tgt = target_slice.lower() + for comp in comps: + name = comp.lower() + if name.endswith(".slice"): + name = name[:-6] + if tgt == name: + return True + return False + +# — Memory‑management logic — + +class get_firefox_procs_ps_t: + class res_t: + class entry_t(TypedDict): + rss: int + pid: int + ppid: int + cgroup: str + cmd: str + +def get_firefox_procs_ps(slice_name=None) -> list[get_firefox_procs_ps_t.res_t.entry_t]: + lines = subprocess.check_output([ + 'ps', '-ax', '-o', 'rss,pid,ppid,cgroup,cmd' + ]).decode('utf-8').splitlines()[1:] + + entries : list[dict[str, str]] = [] + + for line in lines: + r = re.compile(r'^\s*(\d+)\s+(\d+)\s+(\d+)\s+([^\s]+)\s+(.*)$') + # print([r, line]) + match = r.match(line) + + assert match + + entry = dict( + rss=int(match[1]), + pid=int(match[2]), + ppid=int(match[3]), + cgroup=match[4], + cmd=match[5], + ) + + if not slice_name is None: + if not slice_name in entry['cgroup']: + continue + + entries.append(entry) + + return entries + +def get_firefox_procs(slice_name=None): + procs = [] + for p in psutil.process_iter(['pid', 'name', 'cmdline', 'memory_info']): + try: + name = p.info['name'] + cmd = p.info['cmdline'] + if not cmd: + continue + if 'firefox' not in name and not (cmd and 'firefox' in cmd[0]): + continue + if slice_name: + cpath = get_cgroup_path(p.pid) + if not slice_matches(cpath, slice_name): + continue + procs.append(p) + except (psutil.NoSuchProcess, psutil.AccessDenied): + continue + return procs + +def total_rss_mb(procs: list['get_firefox_procs_ps_t.res_t.entry_t']): + total = 0 + for p in procs: + try: + # total += p.memory_info().rss + total += p['rss'] + except Exception: + logger.exception('') + pass + return total / (1024 * 1024) + +def is_main_firefox(p): + try: + # for arg in p.cmdline(): + for arg in p['cmd'].split(): + if "contentproc" in arg: + return False + return True + except Exception: + logger.exception('') + return False + +def kill_prioritized( + procs: list['get_firefox_procs_ps_t.res_t.entry_t'], + to_free_mb, + low_priority_pids +): + candidates = [] + for p in procs: + if is_main_firefox(p): + continue + try: + # rss_mb = p.memory_info().rss / (1024 * 1024) + rss_mb = p['rss'] / (1024 * 1024) + candidates.append((p, rss_mb)) + except Exception: + logger.exception('') + continue + + candidates.sort(key=lambda x: ((x[0].pid in low_priority_pids), -x[1])) + + freed = 0.0 + killed = [] + for p, rss in candidates: + if freed >= to_free_mb: + break + try: + os.kill(p.pid, signal.SIGTERM) + killed.append(p.pid) + freed += rss + except Exception as e: + logger.exception(f"Error killing pid {p.pid}") + # print(f"Error killing pid {p.pid}: {e}", file=sys.stderr) + return killed, freed + +# — systemd-run logic — + +def launch_firefox_with_limits(base_cmd, memory_high, swap_max, extra_args, unit_name): + cmd = [ + "systemd-run", + "--user", + "--scope", + "-p", f"MemoryHigh={int(memory_high)}M", + ] + if swap_max is not None: + cmd += ["-p", f"MemorySwapMax={int(swap_max)}M"] + if unit_name: + cmd += ["--unit", unit_name] + + cmd += base_cmd + cmd += extra_args + + devnull = subprocess.DEVNULL + proc = subprocess.Popen(cmd, stdin=devnull, stdout=devnull, stderr=devnull) + print("Launched Firefox via systemd-run, PID:", proc.pid, file=sys.stderr) + return proc + +# — Main + TUI + Monitoring — + +def main(): + logging.basicConfig(level=logging.INFO) + + parser = argparse.ArgumentParser(description="Firefox memory manager with slice + graceful shutdown") + parser.add_argument("--max-mb", type=float, required=True, + help="Memory threshold in MB (used for killing logic & MemoryHigh)") + parser.add_argument("--kill-percent", type=float, default=70.0, + help="If over max, kill until usage ≤ this percent of max") + parser.add_argument("--swap-max-mb", type=float, default=None, + help="MemorySwapMax (MB) for the systemd scope") + parser.add_argument("--interval", type=float, default=1.0, + help="Monitoring interval in seconds") + parser.add_argument("--slice", type=str, default=None, + help="Only monitor Firefox processes in this systemd slice") + parser.add_argument("--unit-name", type=str, default="firefox-limited", + help="Name for systemd transient unit") + parser.add_argument("--firefox-extra", action="append", default=[], + help="Extra CLI args to pass to Firefox (can repeat)") + parser.add_argument("firefox_cmd", nargs=argparse.REMAINDER, + help="Firefox command + args (if launching it)") + + args = parser.parse_args() + + low_priority_pids = set() + body = TextArea(focusable=False, scrollbar=True) + + terminate_flag = threading.Event() + + lock = threading.Lock() + + firefox_proc = None + + def terminate(): + terminate_flag.set() + + def stop(): + with lock: + if firefox_proc: + try: + firefox_proc.terminate() + firefox_proc.wait(timeout=5) + except Exception: + logger.exception('') + try: + firefox_proc.kill() + except Exception: + logger.exception('') + pass + # app.exit() + + # signal.signal(signal.SIGINT, lambda s, f: terminate()) + # signal.signal(signal.SIGTERM, lambda s, f: terminate()) + + def refresh_body(): + nonlocal firefox_proc + + with lock: + procs = get_firefox_procs_ps(slice_name=args.slice) + total = total_rss_mb(procs) + limit = args.max_mb + kill_to = args.kill_percent / 100.0 * limit + + lines = [ + f"Firefox RSS (slice={args.slice}): {total:.1f} MB", + f"Threshold (max): {limit:.1f} MB", + f"Kill‑to target: {kill_to:.1f} MB ({args.kill_percent}%)", + f"Low‑priority PIDs: {sorted(low_priority_pids)}" + ] + + if total > limit: + to_free = total - kill_to + killed, freed = kill_prioritized(procs, to_free, low_priority_pids) + lines.append(f"Killed: {killed}") + lines.append(f"Freed ≈ {freed:.1f} MB") + else: + lines.append("Within limit — no kill") + + if firefox_proc and firefox_proc.poll() is not None: + print("Firefox died — restarting …", file=sys.stderr) + firefox_proc = launch_firefox_with_limits( + args.firefox_cmd, + memory_high=args.max_mb, + swap_max=args.swap_max_mb, + extra_args=args.firefox_extra, + unit_name=args.unit_name + ) + + body.text = "\n".join(lines) + + dialog_float = [None] + root_floats = [] + + def open_pid_dialog(): + ta = TextArea(text="", multiline=True, scrollbar=True) + + def on_ok(): + txt = ta.text + for m in re.finditer(r"\((\d+)\)", txt): + low_priority_pids.add(int(m.group(1))) + close_dialog() + refresh_body() + + def on_cancel(): + close_dialog() + + dialog = Dialog( + title="Enter low‑priority PIDs", + body=ta, + buttons=[Button(text="OK", handler=on_ok), Button(text="Cancel", handler=on_cancel)], + width=60, + modal=True + ) + f = Float(content=dialog, left=2, top=2) + dialog_float[0] = f + root_floats.append(f) + app.layout.focus(ta) + + def open_message(title, message): + def on_close(): + close_dialog() + + dialog = Dialog( + title=title, + body=Label(text=message), + buttons=[Button(text="Close", handler=on_close)], + width=50, + modal=True + ) + f = Float(content=dialog, left=4, top=4) + dialog_float[0] = f + root_floats.append(f) + app.layout.focus(dialog) + + def close_dialog(): + f = dialog_float[0] + if f in root_floats: + root_floats.remove(f) + dialog_float[0] = None + app.layout.focus(body) + + kb = KeyBindings() + + @kb.add("q") + def _(event): + terminate() + + @kb.add("m") + def _(event): + open_pid_dialog() + + @kb.add("h") + def _(event): + open_message("Help", "Keys: m=add PIDs, s=settings, a=about, q=quit") + + @kb.add("s") + def _(event): + open_message("Settings", + f"max_mb = {args.max_mb}\n" + f"kill_percent = {args.kill_percent}\n" + f"slice = {args.slice}\n" + f"swap_max_mb = {args.swap_max_mb}\n" + f"extra firefox args = {args.firefox_extra}") + + @kb.add("a") + def _(event): + open_message("About", f"Version {__version__}\nCreated {__created__}") + + root = FloatContainer( + content=HSplit([ + Frame(body, title="Firefox Memory Manager"), + Window(height=1, content=FormattedTextControl("q=quit, m=PID, h=help, s=setting, a=about")) + ]), + floats=root_floats, + modal=True + ) + + style = Style.from_dict({ + "frame.border": "ansicyan", + "dialog.body": "bg:#444444", + "dialog": "bg:#888888", + }) + + app = Application( + layout=Layout(root), + key_bindings=kb, + style=style, + full_screen=True, + refresh_interval=args.interval, + ) + + if args.firefox_cmd: + firefox_proc = launch_firefox_with_limits( + args.firefox_cmd, + memory_high=args.max_mb, + swap_max=args.swap_max_mb, # **fixed here** + extra_args=args.firefox_extra, + unit_name=args.unit_name + ) + + def monitor_loop(): + nonlocal firefox_proc + while not terminate_flag.is_set(): + refresh_body() + + time.sleep(args.interval) + + # stop() + + terminate_flag = threading.Event() + t = threading.Thread(target=monitor_loop, daemon=True) + t.start() + + # refresh_body() + app.run(handle_sigint=True) # from prompt‑toolkit API :contentReference[oaicite:0]{index=0} + + t.join() + + stop() + +if __name__ == "__main__": + main() diff --git a/platform_dotfiles/ideapad_slim_3_15arp10/home/nartes/.local/bin/systemd_gtk b/platform_dotfiles/ideapad_slim_3_15arp10/home/nartes/.local/bin/systemd_gtk new file mode 100755 index 0000000..e564a3a --- /dev/null +++ b/platform_dotfiles/ideapad_slim_3_15arp10/home/nartes/.local/bin/systemd_gtk @@ -0,0 +1,330 @@ +#!/usr/bin/env python3 + +# vi: filetype=python + +import gi +gi.require_version("Gtk", "4.0") +from gi.repository import Gtk, Gio, GLib, GObject + +import subprocess +import shlex +import threading +import uuid +import argparse +import logging + +# CLI / Logging +parser = argparse.ArgumentParser(description="Systemd Scope Manager (GTK4 ColumnView)") +parser.add_argument("--log-level", "-l", choices=["DEBUG","INFO","WARNING","ERROR","CRITICAL"], default="INFO") +parser.add_argument("--values-mode", "-m", choices=["raw","human"], default="human", + help="Display memory / CPU values raw or human‑readable") +args = parser.parse_args() + +logging.basicConfig(level=getattr(logging, args.log_level.upper()), + format="%(asctime)s %(name)s %(levelname)s: %(message)s") +logger = logging.getLogger(__name__) + +# Helpers +def human_bytes(value: int) -> str: + for unit in ("B","KB","MB","GB","TB"): + if value < 1024: + return f"{value:.1f}{unit}" + value /= 1024 + return f"{value:.1f}PB" + +def human_time(nsec: int) -> str: + sec = nsec / 1_000_000_000 + if sec < 60: + return f"{sec:.1f}s" + minutes = sec / 60 + if minutes < 60: + return f"{minutes:.1f}m" + return f"{minutes/60:.1f}h" + +def run_systemctl_show(unit: str) -> dict: + cmd = [ + "systemctl", "--user", "show", unit, + "--property=MemoryCurrent,MemorySwapCurrent,CPUUsageNSec,ActiveState,Restart" + ] + try: + res = subprocess.run(cmd, capture_output=True, text=True, check=True) + props = {} + for line in res.stdout.splitlines(): + if "=" in line: + k, v = line.split("=", 1) + props[k.strip()] = v.strip() + return props + except Exception: + logger.exception("systemctl show failed for %s", unit) + return {} + +# Data row +class ScopeRow(GObject.Object): + __gtype_name__ = "ScopeRow" + unit = GObject.Property(type=str) + cli = GObject.Property(type=str) + mem = GObject.Property(type=str) + swap = GObject.Property(type=str) + cpu = GObject.Property(type=str) + state= GObject.Property(type=str) + + def __init__(self, unit, cli, mem, swap, cpu, state): + super().__init__() + self.unit = unit + self.cli = cli + self.mem = mem + self.swap = swap + self.cpu = cpu + self.state = state + +# Main Window +class ScopeManagerWindow(Gtk.ApplicationWindow): + def __init__(self, app): + super().__init__(application=app, title="Systemd Scope Manager") + self.set_default_size(1000, 500) + self.values_mode = args.values_mode + self.scopes = {} + + self.model = Gio.ListStore(item_type=ScopeRow) + self.sel = Gtk.SingleSelection.new(self.model) + self.view = Gtk.ColumnView.new(self.sel) + self.view.set_reorderable(True) + self.view.set_show_row_separators(True) + + cols = [ + ("Unit", "unit"), + ("CLI", "cli"), + ("Memory", "mem"), + ("Swap", "swap"), + ("CPU", "cpu"), + ("State", "state"), + ] + for title, prop_name in cols: + factory = Gtk.SignalListItemFactory() + factory.connect("setup", self._factory_setup_label) + factory.connect("bind", self._make_factory_bind(prop_name)) + col = Gtk.ColumnViewColumn() + col.set_title(title) + col.set_factory(factory) + col.set_resizable(True) + col.set_expand(True) + self.view.append_column(col) + + # Actions column + action_factory = Gtk.SignalListItemFactory() + action_factory.connect("setup", self._factory_setup_actions) + action_factory.connect("bind", self._factory_bind_actions) + act_col = Gtk.ColumnViewColumn() + act_col.set_title("⋮") + act_col.set_factory(action_factory) + act_col.set_resizable(False) + act_col.set_expand(False) + self.view.append_column(act_col) + + # Input area + self.cmd_entry = Gtk.Entry() + self.cmd_entry.set_placeholder_text("Command to run in new scope") + run_btn = Gtk.Button(label="Run") + run_btn.connect("clicked", self.on_run_clicked) + prop_btn = Gtk.Button(label="Edit Property") + prop_btn.connect("clicked", self.on_edit_property) + + input_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6) + input_box.append(self.cmd_entry) + input_box.append(run_btn) + input_box.append(prop_btn) + + scrolled = Gtk.ScrolledWindow() + scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) + scrolled.set_propagate_natural_width(True) + scrolled.set_propagate_natural_height(True) + scrolled.set_child(self.view) + + vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) + vbox.set_vexpand(True) + vbox.append(input_box) + vbox.append(scrolled) + self.set_child(vbox) + + GLib.timeout_add_seconds(2, self.refresh_scopes) + + def _factory_setup_label(self, factory, list_item): + lbl = Gtk.Label() + list_item.set_child(lbl) + + def _make_factory_bind(self, prop_name): + def bind(factory, list_item): + row = list_item.get_item() + lbl = list_item.get_child() + lbl.set_text(getattr(row, prop_name)) + row.connect(f"notify::{prop_name}", lambda obj, pspec: lbl.set_text(getattr(obj, prop_name))) + return bind + + def _factory_setup_actions(self, factory, list_item): + btn = Gtk.MenuButton() + btn.set_icon_name("open-menu-symbolic") + # style class if needed: + btn.get_style_context().add_class("flat") + list_item.set_child(btn) + + def _factory_bind_actions(self, factory, list_item): + row = list_item.get_item() + btn = list_item.get_child() + + menu = Gio.Menu() + menu.append("Stop", f"app.stop_{row.unit}") + menu.append("Restart", f"app.restart_{row.unit}") + menu.append("Toggle Auto‑Restart", f"app.toggle_{row.unit}") + + btn.set_menu_model(menu) + + # create actions + self._ensure_row_actions(row) + + def _ensure_row_actions(self, row): + unit = row.unit + # Stop + act_stop = Gio.SimpleAction.new(f"stop_{unit}", None) + act_stop.connect("activate", lambda a, v, r=row: self.menu_action("Stop", r)) + self.add_action(act_stop) + # Restart + act_restart = Gio.SimpleAction.new(f"restart_{unit}", None) + act_restart.connect("activate", lambda a, v, r=row: self.menu_action("Restart", r)) + self.add_action(act_restart) + # Toggle + act_toggle = Gio.SimpleAction.new(f"toggle_{unit}", None) + act_toggle.connect("activate", lambda a, v, r=row: self.menu_action("Toggle Auto‑Restart", r)) + self.add_action(act_toggle) + + def on_run_clicked(self, button): + cmd = self.cmd_entry.get_text().strip() + if not cmd: + return + unit = f"app-{uuid.uuid4().int >> 64}.scope" + argv = ["systemd-run", "--user", "--scope", "--unit", unit, + "-p", "MemoryAccounting=yes", "-p", "CPUAccounting=yes"] + shlex.split(cmd) + logger.info("Starting scope: %s", " ".join(argv)) + + def worker(): + try: + p = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + out, err = p.communicate() + if out: + logger.debug("systemd-run stdout [%s]: %s", unit, out.strip()) + if err: + logger.debug("systemd-run stderr [%s]: %s", unit, err.strip()) + except Exception: + logger.exception("Failed to run systemd-run for %s", unit) + + threading.Thread(target=worker, daemon=True).start() + self.scopes[unit] = cmd + self.cmd_entry.set_text("") + + def on_edit_property(self, button): + idx = self.sel.get_selected() + if idx < 0: + return + row = self.model.get_item(idx) + + dialog = Gtk.Dialog(transient_for=self, modal=True, title="Set systemd property") + content = dialog.get_content_area() + grid = Gtk.Grid(row_spacing=6, column_spacing=6, margin=10) + content.append(grid) + + lbl1 = Gtk.Label(label="Property (e.g. MemoryMax):") + entry1 = Gtk.Entry() + lbl2 = Gtk.Label(label="Value (e.g. 512M):") + entry2 = Gtk.Entry() + runtime_chk = Gtk.CheckButton(label="Runtime only") + + grid.attach(lbl1, 0,0,1,1) + grid.attach(entry1, 1,0,1,1) + grid.attach(lbl2, 0,1,1,1) + grid.attach(entry2, 1,1,1,1) + grid.attach(runtime_chk,0,2,2,1) + + dialog.add_button("OK", Gtk.ResponseType.OK) + dialog.add_button("Cancel", Gtk.ResponseType.CANCEL) + dialog.show() + + resp = dialog.run() + if resp == Gtk.ResponseType.OK: + prop = entry1.get_text().strip() + val = entry2.get_text().strip() + rt = runtime_chk.get_active() + dialog.destroy() + self.set_property(row.unit, prop, val, rt) + else: + dialog.destroy() + + def set_property(self, unit, prop, val, runtime_flag): + cmd = ["systemctl", "--user", "set-property"] + if runtime_flag: + cmd.append("--runtime") + cmd += [unit, f"{prop}={val}"] + logger.info("Setting %s=%s on %s (runtime=%s)", prop, val, unit, runtime_flag) + try: + res = subprocess.run(cmd, capture_output=True, text=True) + if res.stdout: + logger.debug("set-property stdout: %s", res.stdout.strip()) + if res.stderr: + logger.debug("set-property stderr: %s", res.stderr.strip()) + except Exception: + logger.exception("Failed set-property for %s", unit) + + def menu_action(self, label, row): + unit = row.unit + try: + if label == "Stop": + subprocess.run(["systemctl","--user","stop",unit]) + elif label == "Restart": + subprocess.run(["systemctl","--user","restart",unit]) + elif label == "Toggle Auto‑Restart": + props = run_systemctl_show(unit) + current = props.get("Restart","no") + new = "no" if current != "no" else "always" + subprocess.run(["systemctl","--user","set-property",unit,f"Restart={new}"]) + except Exception: + logger.exception("Action %s failed on %s", label, unit) + + def refresh_scopes(self): + self.model.remove_all() + for unit, cli in self.scopes.items(): + props = run_systemctl_show(unit) + mem = int(props.get("MemoryCurrent", "0")) + swap = int(props.get("MemorySwapCurrent","0")) + cpu = int(props.get("CPUUsageNSec", "0")) + state = props.get("ActiveState","unknown") + + if self.values_mode == "human": + mem_s = human_bytes(mem) + swap_s = human_bytes(swap) + cpu_s = human_time(cpu) + else: + mem_s = str(mem) + swap_s = str(swap) + cpu_s = str(cpu) + + row = ScopeRow(unit, cli, mem_s, swap_s, cpu_s, state) + self.model.append(row) + + logger.debug("Refreshed %d scopes", self.model.get_n_items()) + return True + +# Application +class ScopeManagerApp(Gtk.Application): + def __init__(self): + super().__init__(application_id="org.systemd.ScopeManager") + self.connect("activate", self.on_activate) + + def on_activate(self, app): + win = ScopeManagerWindow(self) + win.present() + +def main(): + app = ScopeManagerApp() + return app.run() + +if __name__ == "__main__": + import sys + sys.exit(main()) From a8d0f64419379ef73b3803b484671c1035671865 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Mon, 1 Dec 2025 15:59:36 +0300 Subject: [PATCH 53/70] [+] update oom_firefox 1. fix rss being in KiB; 2. get rid of --slice, use --unit-name; 3. fix columns strings being cut, use two calls to ps, to get full cmd and cgroup; 4. tested manually that it works; --- .../home/nartes/.local/bin/oom_firefox | 81 ++++++++++++------- 1 file changed, 54 insertions(+), 27 deletions(-) diff --git a/platform_dotfiles/ideapad_slim_3_15arp10/home/nartes/.local/bin/oom_firefox b/platform_dotfiles/ideapad_slim_3_15arp10/home/nartes/.local/bin/oom_firefox index ae5400d..864538e 100755 --- a/platform_dotfiles/ideapad_slim_3_15arp10/home/nartes/.local/bin/oom_firefox +++ b/platform_dotfiles/ideapad_slim_3_15arp10/home/nartes/.local/bin/oom_firefox @@ -19,6 +19,7 @@ from prompt_toolkit.widgets import TextArea, Frame, Dialog, Button, Label from prompt_toolkit.styles import Style from typing import (TypedDict,) +from collections import OrderedDict logger = logging.getLogger(__name__) @@ -64,34 +65,59 @@ class get_firefox_procs_ps_t: cmd: str def get_firefox_procs_ps(slice_name=None) -> list[get_firefox_procs_ps_t.res_t.entry_t]: - lines = subprocess.check_output([ - 'ps', '-ax', '-o', 'rss,pid,ppid,cgroup,cmd' - ]).decode('utf-8').splitlines()[1:] + entries : dict[int, dict[str, Any]] = dict() - entries : list[dict[str, str]] = [] + for regex, columns in [ + ( + re.compile(r'^\s*(\d+)\s+(\d+)\s+(\d+)\s+(.*)$'), + OrderedDict( + pid=lambda x: int(x[1]), + rss=lambda x: int(x[2]) * 1024, + ppid=lambda x: int(x[3]), + cmd=lambda x: x[4], + ), + ), + ( + re.compile(r'^\s*(\d+)\s+(.*)$'), + OrderedDict( + pid=lambda x: int(x[1]), + cgroup=lambda x: x[2], + ), + ), + ]: + lines = subprocess.check_output([ + 'ps', '-ax', '-o', ','.join(columns.keys()), + ]).decode('utf-8').splitlines()[1:] - for line in lines: - r = re.compile(r'^\s*(\d+)\s+(\d+)\s+(\d+)\s+([^\s]+)\s+(.*)$') - # print([r, line]) - match = r.match(line) + for line in lines: + r = re.compile(regex) + # print([r, line]) + match = r.match(line) - assert match + assert match - entry = dict( - rss=int(match[1]), - pid=int(match[2]), - ppid=int(match[3]), - cgroup=match[4], - cmd=match[5], - ) + entry = { + k : v(match) + for k, v in columns.items() + } + + if not entry['pid'] in entries: + entries[entry['pid']] = dict() + + entries[entry['pid']].update(entry) + + filtered_entries : list[dict[str, Any]] = [] + + for entry in entries.values(): + if not 'cgroup' in entry or not 'rss' in entry: + continue if not slice_name is None: if not slice_name in entry['cgroup']: continue + filtered_entries.append(entry) - entries.append(entry) - - return entries + return filtered_entries def get_firefox_procs(slice_name=None): procs = [] @@ -126,9 +152,11 @@ def total_rss_mb(procs: list['get_firefox_procs_ps_t.res_t.entry_t']): def is_main_firefox(p): try: # for arg in p.cmdline(): - for arg in p['cmd'].split(): - if "contentproc" in arg: - return False + #for arg in p['cmd'].split(): + # if "contentproc" in arg: + # return False + if 'contentproc' in p['cmd']: + return False return True except Exception: logger.exception('') @@ -203,8 +231,6 @@ def main(): help="MemorySwapMax (MB) for the systemd scope") parser.add_argument("--interval", type=float, default=1.0, help="Monitoring interval in seconds") - parser.add_argument("--slice", type=str, default=None, - help="Only monitor Firefox processes in this systemd slice") parser.add_argument("--unit-name", type=str, default="firefox-limited", help="Name for systemd transient unit") parser.add_argument("--firefox-extra", action="append", default=[], @@ -225,6 +251,7 @@ def main(): def terminate(): terminate_flag.set() + app.exit() def stop(): with lock: @@ -248,13 +275,13 @@ def main(): nonlocal firefox_proc with lock: - procs = get_firefox_procs_ps(slice_name=args.slice) + procs = get_firefox_procs_ps(slice_name=args.unit_name) total = total_rss_mb(procs) limit = args.max_mb kill_to = args.kill_percent / 100.0 * limit lines = [ - f"Firefox RSS (slice={args.slice}): {total:.1f} MB", + f"Firefox RSS (slice={args.unit_name}): {total:.1f} MB", f"Threshold (max): {limit:.1f} MB", f"Kill‑to target: {kill_to:.1f} MB ({args.kill_percent}%)", f"Low‑priority PIDs: {sorted(low_priority_pids)}" @@ -350,7 +377,7 @@ def main(): open_message("Settings", f"max_mb = {args.max_mb}\n" f"kill_percent = {args.kill_percent}\n" - f"slice = {args.slice}\n" + f"slice = {args.unit_name}\n" f"swap_max_mb = {args.swap_max_mb}\n" f"extra firefox args = {args.firefox_extra}") From c568d8d9a742a8e37935f4764c51fd0f92be10e4 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Wed, 3 Dec 2025 17:38:12 +0300 Subject: [PATCH 54/70] [+] update oom_firefox 1. fix .pid attributes for dict; 2. reformat; 3. migrate into pr34 repo; 4. update Makefile for fetch, deploy; --- Makefile | 2 +- .../home/nartes/.local/bin/oom_firefox | 441 ----------------- python/meson.build | 2 +- python/online/fxreader/pr34/oom_firefox.py | 468 ++++++++++++++++++ python/pyproject.toml | 1 + ...ne_fxreader_pr34-0.1.5.42-py3-none-any.whl | 3 + 6 files changed, 474 insertions(+), 443 deletions(-) delete mode 100755 platform_dotfiles/ideapad_slim_3_15arp10/home/nartes/.local/bin/oom_firefox create mode 100644 python/online/fxreader/pr34/oom_firefox.py create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.42-py3-none-any.whl diff --git a/Makefile b/Makefile index b1fbf02..386da80 100644 --- a/Makefile +++ b/Makefile @@ -57,6 +57,7 @@ python_put_pr34: -U \ online.fxreader.pr34 ln -sf $(INSTALL_ROOT)/env3/bin/online-fxreader-pr34-commands $(INSTALL_ROOT)/commands + ln -sf $(INSTALL_ROOT)/env3/bin/oom_firefox $(INSTALL_ROOT)/oom_firefox PYTHON_PROJECTS_NAMES ?= online.fxreader.pr34 @@ -122,7 +123,6 @@ dotfiles_fetch_platform: mkdir -p platform_dotfiles/$(PLATFORM) tar -cvf - \ /etc/udev/rules.d/ \ - ~/.local/bin/oom_firefox \ ~/.local/bin/systemd_gtk \ ~/.local/bin/gnome-shortcuts-macbook-air \ /usr/local/bin \ diff --git a/platform_dotfiles/ideapad_slim_3_15arp10/home/nartes/.local/bin/oom_firefox b/platform_dotfiles/ideapad_slim_3_15arp10/home/nartes/.local/bin/oom_firefox deleted file mode 100755 index 864538e..0000000 --- a/platform_dotfiles/ideapad_slim_3_15arp10/home/nartes/.local/bin/oom_firefox +++ /dev/null @@ -1,441 +0,0 @@ -#!/usr/bin/env python3 -import psutil -import logging -import os -import signal -import subprocess -import threading -import time -import argparse -import re -import sys - -from prompt_toolkit.application import Application -from prompt_toolkit.key_binding import KeyBindings -from prompt_toolkit.layout import Layout, HSplit, FloatContainer, Float -from prompt_toolkit.layout.containers import Window -from prompt_toolkit.layout.controls import FormattedTextControl -from prompt_toolkit.widgets import TextArea, Frame, Dialog, Button, Label -from prompt_toolkit.styles import Style - -from typing import (TypedDict,) -from collections import OrderedDict - -logger = logging.getLogger(__name__) - -__version__ = "0.6.1" -__created__ = "2025-11-21" - -# — Helper for cgroup / slice matching — - -def get_cgroup_path(pid): - try: - with open(f"/proc/{pid}/cgroup", "r") as f: - for line in f: - parts = line.strip().split(":", 2) - if len(parts) == 3: - return parts[2] - except Exception: - logger.exception('') - return None - return None - -def slice_matches(cpath, target_slice): - if not cpath or not target_slice: - return False - comps = cpath.strip("/").split("/") - tgt = target_slice.lower() - for comp in comps: - name = comp.lower() - if name.endswith(".slice"): - name = name[:-6] - if tgt == name: - return True - return False - -# — Memory‑management logic — - -class get_firefox_procs_ps_t: - class res_t: - class entry_t(TypedDict): - rss: int - pid: int - ppid: int - cgroup: str - cmd: str - -def get_firefox_procs_ps(slice_name=None) -> list[get_firefox_procs_ps_t.res_t.entry_t]: - entries : dict[int, dict[str, Any]] = dict() - - for regex, columns in [ - ( - re.compile(r'^\s*(\d+)\s+(\d+)\s+(\d+)\s+(.*)$'), - OrderedDict( - pid=lambda x: int(x[1]), - rss=lambda x: int(x[2]) * 1024, - ppid=lambda x: int(x[3]), - cmd=lambda x: x[4], - ), - ), - ( - re.compile(r'^\s*(\d+)\s+(.*)$'), - OrderedDict( - pid=lambda x: int(x[1]), - cgroup=lambda x: x[2], - ), - ), - ]: - lines = subprocess.check_output([ - 'ps', '-ax', '-o', ','.join(columns.keys()), - ]).decode('utf-8').splitlines()[1:] - - for line in lines: - r = re.compile(regex) - # print([r, line]) - match = r.match(line) - - assert match - - entry = { - k : v(match) - for k, v in columns.items() - } - - if not entry['pid'] in entries: - entries[entry['pid']] = dict() - - entries[entry['pid']].update(entry) - - filtered_entries : list[dict[str, Any]] = [] - - for entry in entries.values(): - if not 'cgroup' in entry or not 'rss' in entry: - continue - - if not slice_name is None: - if not slice_name in entry['cgroup']: - continue - filtered_entries.append(entry) - - return filtered_entries - -def get_firefox_procs(slice_name=None): - procs = [] - for p in psutil.process_iter(['pid', 'name', 'cmdline', 'memory_info']): - try: - name = p.info['name'] - cmd = p.info['cmdline'] - if not cmd: - continue - if 'firefox' not in name and not (cmd and 'firefox' in cmd[0]): - continue - if slice_name: - cpath = get_cgroup_path(p.pid) - if not slice_matches(cpath, slice_name): - continue - procs.append(p) - except (psutil.NoSuchProcess, psutil.AccessDenied): - continue - return procs - -def total_rss_mb(procs: list['get_firefox_procs_ps_t.res_t.entry_t']): - total = 0 - for p in procs: - try: - # total += p.memory_info().rss - total += p['rss'] - except Exception: - logger.exception('') - pass - return total / (1024 * 1024) - -def is_main_firefox(p): - try: - # for arg in p.cmdline(): - #for arg in p['cmd'].split(): - # if "contentproc" in arg: - # return False - if 'contentproc' in p['cmd']: - return False - return True - except Exception: - logger.exception('') - return False - -def kill_prioritized( - procs: list['get_firefox_procs_ps_t.res_t.entry_t'], - to_free_mb, - low_priority_pids -): - candidates = [] - for p in procs: - if is_main_firefox(p): - continue - try: - # rss_mb = p.memory_info().rss / (1024 * 1024) - rss_mb = p['rss'] / (1024 * 1024) - candidates.append((p, rss_mb)) - except Exception: - logger.exception('') - continue - - candidates.sort(key=lambda x: ((x[0].pid in low_priority_pids), -x[1])) - - freed = 0.0 - killed = [] - for p, rss in candidates: - if freed >= to_free_mb: - break - try: - os.kill(p.pid, signal.SIGTERM) - killed.append(p.pid) - freed += rss - except Exception as e: - logger.exception(f"Error killing pid {p.pid}") - # print(f"Error killing pid {p.pid}: {e}", file=sys.stderr) - return killed, freed - -# — systemd-run logic — - -def launch_firefox_with_limits(base_cmd, memory_high, swap_max, extra_args, unit_name): - cmd = [ - "systemd-run", - "--user", - "--scope", - "-p", f"MemoryHigh={int(memory_high)}M", - ] - if swap_max is not None: - cmd += ["-p", f"MemorySwapMax={int(swap_max)}M"] - if unit_name: - cmd += ["--unit", unit_name] - - cmd += base_cmd - cmd += extra_args - - devnull = subprocess.DEVNULL - proc = subprocess.Popen(cmd, stdin=devnull, stdout=devnull, stderr=devnull) - print("Launched Firefox via systemd-run, PID:", proc.pid, file=sys.stderr) - return proc - -# — Main + TUI + Monitoring — - -def main(): - logging.basicConfig(level=logging.INFO) - - parser = argparse.ArgumentParser(description="Firefox memory manager with slice + graceful shutdown") - parser.add_argument("--max-mb", type=float, required=True, - help="Memory threshold in MB (used for killing logic & MemoryHigh)") - parser.add_argument("--kill-percent", type=float, default=70.0, - help="If over max, kill until usage ≤ this percent of max") - parser.add_argument("--swap-max-mb", type=float, default=None, - help="MemorySwapMax (MB) for the systemd scope") - parser.add_argument("--interval", type=float, default=1.0, - help="Monitoring interval in seconds") - parser.add_argument("--unit-name", type=str, default="firefox-limited", - help="Name for systemd transient unit") - parser.add_argument("--firefox-extra", action="append", default=[], - help="Extra CLI args to pass to Firefox (can repeat)") - parser.add_argument("firefox_cmd", nargs=argparse.REMAINDER, - help="Firefox command + args (if launching it)") - - args = parser.parse_args() - - low_priority_pids = set() - body = TextArea(focusable=False, scrollbar=True) - - terminate_flag = threading.Event() - - lock = threading.Lock() - - firefox_proc = None - - def terminate(): - terminate_flag.set() - app.exit() - - def stop(): - with lock: - if firefox_proc: - try: - firefox_proc.terminate() - firefox_proc.wait(timeout=5) - except Exception: - logger.exception('') - try: - firefox_proc.kill() - except Exception: - logger.exception('') - pass - # app.exit() - - # signal.signal(signal.SIGINT, lambda s, f: terminate()) - # signal.signal(signal.SIGTERM, lambda s, f: terminate()) - - def refresh_body(): - nonlocal firefox_proc - - with lock: - procs = get_firefox_procs_ps(slice_name=args.unit_name) - total = total_rss_mb(procs) - limit = args.max_mb - kill_to = args.kill_percent / 100.0 * limit - - lines = [ - f"Firefox RSS (slice={args.unit_name}): {total:.1f} MB", - f"Threshold (max): {limit:.1f} MB", - f"Kill‑to target: {kill_to:.1f} MB ({args.kill_percent}%)", - f"Low‑priority PIDs: {sorted(low_priority_pids)}" - ] - - if total > limit: - to_free = total - kill_to - killed, freed = kill_prioritized(procs, to_free, low_priority_pids) - lines.append(f"Killed: {killed}") - lines.append(f"Freed ≈ {freed:.1f} MB") - else: - lines.append("Within limit — no kill") - - if firefox_proc and firefox_proc.poll() is not None: - print("Firefox died — restarting …", file=sys.stderr) - firefox_proc = launch_firefox_with_limits( - args.firefox_cmd, - memory_high=args.max_mb, - swap_max=args.swap_max_mb, - extra_args=args.firefox_extra, - unit_name=args.unit_name - ) - - body.text = "\n".join(lines) - - dialog_float = [None] - root_floats = [] - - def open_pid_dialog(): - ta = TextArea(text="", multiline=True, scrollbar=True) - - def on_ok(): - txt = ta.text - for m in re.finditer(r"\((\d+)\)", txt): - low_priority_pids.add(int(m.group(1))) - close_dialog() - refresh_body() - - def on_cancel(): - close_dialog() - - dialog = Dialog( - title="Enter low‑priority PIDs", - body=ta, - buttons=[Button(text="OK", handler=on_ok), Button(text="Cancel", handler=on_cancel)], - width=60, - modal=True - ) - f = Float(content=dialog, left=2, top=2) - dialog_float[0] = f - root_floats.append(f) - app.layout.focus(ta) - - def open_message(title, message): - def on_close(): - close_dialog() - - dialog = Dialog( - title=title, - body=Label(text=message), - buttons=[Button(text="Close", handler=on_close)], - width=50, - modal=True - ) - f = Float(content=dialog, left=4, top=4) - dialog_float[0] = f - root_floats.append(f) - app.layout.focus(dialog) - - def close_dialog(): - f = dialog_float[0] - if f in root_floats: - root_floats.remove(f) - dialog_float[0] = None - app.layout.focus(body) - - kb = KeyBindings() - - @kb.add("q") - def _(event): - terminate() - - @kb.add("m") - def _(event): - open_pid_dialog() - - @kb.add("h") - def _(event): - open_message("Help", "Keys: m=add PIDs, s=settings, a=about, q=quit") - - @kb.add("s") - def _(event): - open_message("Settings", - f"max_mb = {args.max_mb}\n" - f"kill_percent = {args.kill_percent}\n" - f"slice = {args.unit_name}\n" - f"swap_max_mb = {args.swap_max_mb}\n" - f"extra firefox args = {args.firefox_extra}") - - @kb.add("a") - def _(event): - open_message("About", f"Version {__version__}\nCreated {__created__}") - - root = FloatContainer( - content=HSplit([ - Frame(body, title="Firefox Memory Manager"), - Window(height=1, content=FormattedTextControl("q=quit, m=PID, h=help, s=setting, a=about")) - ]), - floats=root_floats, - modal=True - ) - - style = Style.from_dict({ - "frame.border": "ansicyan", - "dialog.body": "bg:#444444", - "dialog": "bg:#888888", - }) - - app = Application( - layout=Layout(root), - key_bindings=kb, - style=style, - full_screen=True, - refresh_interval=args.interval, - ) - - if args.firefox_cmd: - firefox_proc = launch_firefox_with_limits( - args.firefox_cmd, - memory_high=args.max_mb, - swap_max=args.swap_max_mb, # **fixed here** - extra_args=args.firefox_extra, - unit_name=args.unit_name - ) - - def monitor_loop(): - nonlocal firefox_proc - while not terminate_flag.is_set(): - refresh_body() - - time.sleep(args.interval) - - # stop() - - terminate_flag = threading.Event() - t = threading.Thread(target=monitor_loop, daemon=True) - t.start() - - # refresh_body() - app.run(handle_sigint=True) # from prompt‑toolkit API :contentReference[oaicite:0]{index=0} - - t.join() - - stop() - -if __name__ == "__main__": - main() diff --git a/python/meson.build b/python/meson.build index 5191121..805b2e4 100644 --- a/python/meson.build +++ b/python/meson.build @@ -5,7 +5,7 @@ project( ).stdout().strip('\n'), # 'online.fxreader.uv', # ['c', 'cpp'], - version: '0.1.5.41', + version: '0.1.5.42', # default_options: [ # 'cpp_std=c++23', # # 'prefer_static=true', diff --git a/python/online/fxreader/pr34/oom_firefox.py b/python/online/fxreader/pr34/oom_firefox.py new file mode 100644 index 0000000..d50eca6 --- /dev/null +++ b/python/online/fxreader/pr34/oom_firefox.py @@ -0,0 +1,468 @@ +#!/usr/bin/env python3 +import psutil +import pathlib +import logging +import logging.handlers +import os +import signal +import subprocess +import threading +import time +import argparse +import re +import sys + +from prompt_toolkit.application import Application +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.layout import Layout, HSplit, FloatContainer, Float +from prompt_toolkit.layout.containers import Window +from prompt_toolkit.layout.controls import FormattedTextControl +from prompt_toolkit.widgets import TextArea, Frame, Dialog, Button, Label +from prompt_toolkit.styles import Style + +from typing import ( + TypedDict, + Any, +) +from collections import OrderedDict + +logger = logging.getLogger(__name__) + +__version__ = '0.6.1' +__created__ = '2025-11-21' + +# — Helper for cgroup / slice matching — + + +def get_cgroup_path(pid): + try: + with open(f'/proc/{pid}/cgroup', 'r') as f: + for line in f: + parts = line.strip().split(':', 2) + if len(parts) == 3: + return parts[2] + except Exception: + logger.exception('') + return None + return None + + +def slice_matches(cpath, target_slice): + if not cpath or not target_slice: + return False + comps = cpath.strip('/').split('/') + tgt = target_slice.lower() + for comp in comps: + name = comp.lower() + if name.endswith('.slice'): + name = name[:-6] + if tgt == name: + return True + return False + + +# — Memory‑management logic — + + +class get_firefox_procs_ps_t: + class res_t: + class entry_t(TypedDict): + rss: int + pid: int + ppid: int + cgroup: str + cmd: str + + +def get_firefox_procs_ps(slice_name=None) -> list[get_firefox_procs_ps_t.res_t.entry_t]: + entries: dict[int, dict[str, Any]] = dict() + + for regex, columns in [ + ( + re.compile(r'^\s*(\d+)\s+(\d+)\s+(\d+)\s+(.*)$'), + OrderedDict( + pid=lambda x: int(x[1]), + rss=lambda x: int(x[2]) * 1024, + ppid=lambda x: int(x[3]), + cmd=lambda x: x[4], + ), + ), + ( + re.compile(r'^\s*(\d+)\s+(.*)$'), + OrderedDict( + pid=lambda x: int(x[1]), + cgroup=lambda x: x[2], + ), + ), + ]: + lines = ( + subprocess.check_output( + [ + 'ps', + '-ax', + '-o', + ','.join(columns.keys()), + ] + ) + .decode('utf-8') + .splitlines()[1:] + ) + + for line in lines: + r = re.compile(regex) + # print([r, line]) + match = r.match(line) + + assert match + + entry = {k: v(match) for k, v in columns.items()} + + if not entry['pid'] in entries: + entries[entry['pid']] = dict() + + entries[entry['pid']].update(entry) + + filtered_entries: list[dict[str, Any]] = [] + + for entry in entries.values(): + if not 'cgroup' in entry or not 'rss' in entry: + continue + + if not slice_name is None: + if not slice_name in entry['cgroup']: + continue + filtered_entries.append(entry) + + return filtered_entries + + +def get_firefox_procs(slice_name=None): + procs = [] + for p in psutil.process_iter(['pid', 'name', 'cmdline', 'memory_info']): + try: + name = p.info['name'] + cmd = p.info['cmdline'] + if not cmd: + continue + if 'firefox' not in name and not (cmd and 'firefox' in cmd[0]): + continue + if slice_name: + cpath = get_cgroup_path(p.pid) + if not slice_matches(cpath, slice_name): + continue + procs.append(p) + except (psutil.NoSuchProcess, psutil.AccessDenied): + continue + return procs + + +def total_rss_mb(procs: list['get_firefox_procs_ps_t.res_t.entry_t']): + total = 0 + for p in procs: + try: + # total += p.memory_info().rss + total += p['rss'] + except Exception: + logger.exception('') + pass + return total / (1024 * 1024) + + +def is_main_firefox(p): + try: + # for arg in p.cmdline(): + # for arg in p['cmd'].split(): + # if "contentproc" in arg: + # return False + if 'contentproc' in p['cmd']: + return False + return True + except Exception: + logger.exception('') + return False + + +def kill_prioritized(procs: list['get_firefox_procs_ps_t.res_t.entry_t'], to_free_mb, low_priority_pids): + candidates = [] + for p in procs: + if is_main_firefox(p): + continue + try: + # rss_mb = p.memory_info().rss / (1024 * 1024) + rss_mb = p['rss'] / (1024 * 1024) + candidates.append((p, rss_mb)) + except Exception: + logger.exception('') + continue + + candidates.sort(key=lambda x: ((x[0]['pid'] in low_priority_pids), -x[1])) + + freed = 0.0 + killed = [] + for p, rss in candidates: + if freed >= to_free_mb: + break + + logger.info( + dict( + p=p, + action='kill', + msg='started', + ) + ) + + try: + os.kill(p['pid'], signal.SIGTERM) + killed.append(p['pid']) + freed += rss + except Exception as e: + logger.exception(f'Error killing pid {p["pid"]}') + # print(f"Error killing pid {p.pid}: {e}", file=sys.stderr) + return killed, freed + + +# — systemd-run logic — + + +def launch_firefox_with_limits(base_cmd, memory_high, swap_max, extra_args, unit_name): + cmd = [ + 'systemd-run', + '--user', + '--scope', + '-p', + f'MemoryHigh={int(memory_high)}M', + ] + if swap_max is not None: + cmd += ['-p', f'MemorySwapMax={int(swap_max)}M'] + if unit_name: + cmd += ['--unit', unit_name] + + cmd += base_cmd + cmd += extra_args + + devnull = subprocess.DEVNULL + proc = subprocess.Popen(cmd, stdin=devnull, stdout=devnull, stderr=devnull) + print('Launched Firefox via systemd-run, PID:', proc.pid, file=sys.stderr) + return proc + + +# — Main + TUI + Monitoring — + + +def main(): + os.makedirs(pathlib.Path('~/.cache/oom_firefox/').expanduser(), exist_ok=True) + + logging.basicConfig( + level=logging.INFO, + handlers=[ + logging.handlers.RotatingFileHandler( + pathlib.Path('~/.cache/oom_firefox/log').expanduser(), + maxBytes=128 * 1024, + backupCount=3, + ) + ], + ) + + parser = argparse.ArgumentParser(description='Firefox memory manager with slice + graceful shutdown') + parser.add_argument('--max-mb', type=float, required=True, help='Memory threshold in MB (used for killing logic & MemoryHigh)') + parser.add_argument('--kill-percent', type=float, default=70.0, help='If over max, kill until usage ≤ this percent of max') + parser.add_argument('--swap-max-mb', type=float, default=None, help='MemorySwapMax (MB) for the systemd scope') + parser.add_argument('--interval', type=float, default=1.0, help='Monitoring interval in seconds') + parser.add_argument('--unit-name', type=str, default='firefox-limited', help='Name for systemd transient unit') + parser.add_argument('--firefox-extra', action='append', default=[], help='Extra CLI args to pass to Firefox (can repeat)') + parser.add_argument('firefox_cmd', nargs=argparse.REMAINDER, help='Firefox command + args (if launching it)') + + args = parser.parse_args() + + low_priority_pids = set() + body = TextArea(focusable=False, scrollbar=True) + + terminate_flag = threading.Event() + + lock = threading.Lock() + + firefox_proc = None + + def terminate(): + terminate_flag.set() + app.exit() + + def stop(): + with lock: + if firefox_proc: + try: + firefox_proc.terminate() + firefox_proc.wait(timeout=5) + except Exception: + logger.exception('') + try: + firefox_proc.kill() + except Exception: + logger.exception('') + pass + # app.exit() + + # signal.signal(signal.SIGINT, lambda s, f: terminate()) + # signal.signal(signal.SIGTERM, lambda s, f: terminate()) + + def refresh_body(): + nonlocal firefox_proc + + with lock: + procs = get_firefox_procs_ps(slice_name=args.unit_name) + total = total_rss_mb(procs) + limit = args.max_mb + kill_to = args.kill_percent / 100.0 * limit + + lines = [ + f'Firefox RSS (slice={args.unit_name}): {total:.1f} MB', + f'Threshold (max): {limit:.1f} MB', + f'Kill‑to target: {kill_to:.1f} MB ({args.kill_percent}%)', + f'Low‑priority PIDs: {sorted(low_priority_pids)}', + ] + + if total > limit: + to_free = total - kill_to + killed, freed = kill_prioritized(procs, to_free, low_priority_pids) + lines.append(f'Killed: {killed}') + lines.append(f'Freed ≈ {freed:.1f} MB') + else: + lines.append('Within limit — no kill') + + if firefox_proc and firefox_proc.poll() is not None: + print('Firefox died — restarting …', file=sys.stderr) + firefox_proc = launch_firefox_with_limits( + args.firefox_cmd, memory_high=args.max_mb, swap_max=args.swap_max_mb, extra_args=args.firefox_extra, unit_name=args.unit_name + ) + + body.text = '\n'.join(lines) + + dialog_float = [None] + root_floats = [] + + def open_pid_dialog(): + ta = TextArea(text='', multiline=True, scrollbar=True) + + def on_ok(): + txt = ta.text + for m in re.finditer(r'\((\d+)\)', txt): + low_priority_pids.add(int(m.group(1))) + close_dialog() + refresh_body() + + def on_cancel(): + close_dialog() + + dialog = Dialog( + title='Enter low‑priority PIDs', body=ta, buttons=[Button(text='OK', handler=on_ok), Button(text='Cancel', handler=on_cancel)], width=60, modal=True + ) + f = Float(content=dialog, left=2, top=2) + dialog_float[0] = f + root_floats.append(f) + app.layout.focus(ta) + + def open_message(title, message): + def on_close(): + close_dialog() + + dialog = Dialog(title=title, body=Label(text=message), buttons=[Button(text='Close', handler=on_close)], width=50, modal=True) + f = Float(content=dialog, left=4, top=4) + dialog_float[0] = f + root_floats.append(f) + app.layout.focus(dialog) + + def close_dialog(): + f = dialog_float[0] + if f in root_floats: + root_floats.remove(f) + dialog_float[0] = None + app.layout.focus(body) + + kb = KeyBindings() + + @kb.add('q') + def _(event): + terminate() + + @kb.add('m') + def _(event): + open_pid_dialog() + + @kb.add('h') + def _(event): + open_message('Help', 'Keys: m=add PIDs, s=settings, a=about, q=quit') + + @kb.add('s') + def _(event): + open_message( + 'Settings', + f'max_mb = {args.max_mb}\n' + f'kill_percent = {args.kill_percent}\n' + f'slice = {args.unit_name}\n' + f'swap_max_mb = {args.swap_max_mb}\n' + f'extra firefox args = {args.firefox_extra}', + ) + + @kb.add('a') + def _(event): + open_message('About', f'Version {__version__}\nCreated {__created__}') + + root = FloatContainer( + content=HSplit( + [Frame(body, title='Firefox Memory Manager'), Window(height=1, content=FormattedTextControl('q=quit, m=PID, h=help, s=setting, a=about'))] + ), + floats=root_floats, + modal=True, + ) + + style = Style.from_dict( + { + 'frame.border': 'ansicyan', + 'dialog.body': 'bg:#444444', + 'dialog': 'bg:#888888', + } + ) + + app = Application( + layout=Layout(root), + key_bindings=kb, + style=style, + full_screen=True, + refresh_interval=args.interval, + ) + + if args.firefox_cmd: + firefox_proc = launch_firefox_with_limits( + args.firefox_cmd, + memory_high=args.max_mb, + swap_max=args.swap_max_mb, # **fixed here** + extra_args=args.firefox_extra, + unit_name=args.unit_name, + ) + + def monitor_loop(): + nonlocal firefox_proc + while not terminate_flag.is_set(): + try: + refresh_body() + except: + logger.exception('') + + time.sleep(args.interval) + + # stop() + + terminate_flag = threading.Event() + t = threading.Thread(target=monitor_loop, daemon=True) + t.start() + + # refresh_body() + app.run(handle_sigint=True) # from prompt‑toolkit API :contentReference[oaicite:0]{index=0} + + t.join() + + stop() + + +if __name__ == '__main__': + main() diff --git a/python/pyproject.toml b/python/pyproject.toml index 00045e9..3a18b73 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -72,6 +72,7 @@ build-backend = "mesonpy" [project.scripts] online-fxreader-pr34-commands = 'online.fxreader.pr34.commands:commands_cli' +oom_firefox = 'online.fxreader.pr34.oom_firefox:main' [tool.ruff] diff --git a/releases/whl/online_fxreader_pr34-0.1.5.42-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.42-py3-none-any.whl new file mode 100644 index 0000000..97296d9 --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.42-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0404b4c65982bc74364d2f07d3bfccbe46bc2fbc64ff10820a383f96c39420a1 +size 80608 From 85139fae81513079e5b761e0a9d6ed9dd96cbc9b Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Thu, 4 Dec 2025 10:11:44 +0300 Subject: [PATCH 55/70] [+] update config 1. configure for_window in sway config to inhibit-idle when they are fullscreen; tried idlehack-git, but not sure whether it's bugger, stick to for_window for now; --- Makefile | 9 +++ .../etc/systemd/logind.conf | 57 +++++++++++++++++++ ...tive-configs-2025-12-04T10:10:51+03:00.gpg | 3 + 3 files changed, 69 insertions(+) create mode 100644 platform_dotfiles/ideapad_slim_3_15arp10/etc/systemd/logind.conf create mode 100644 platform_dotfiles_gpg/ideapad_slim_3_15arp10/sensitive-configs-2025-12-04T10:10:51+03:00.gpg diff --git a/Makefile b/Makefile index 386da80..294002e 100644 --- a/Makefile +++ b/Makefile @@ -123,6 +123,7 @@ dotfiles_fetch_platform: mkdir -p platform_dotfiles/$(PLATFORM) tar -cvf - \ /etc/udev/rules.d/ \ + /etc/systemd/logind.conf \ ~/.local/bin/systemd_gtk \ ~/.local/bin/gnome-shortcuts-macbook-air \ /usr/local/bin \ @@ -130,17 +131,25 @@ dotfiles_fetch_platform: dotfiles_fetch_platform_gpg: mkdir -p platform_dotfiles_gpg/$(PLATFORM) + yay -Q > /tmp/pacman-packages.txt; tar -h -cvf - \ + /tmp/pacman-packages.txt \ ~/.sway/config.d \ + ~/.sway/config \ ~/.config/commands-status.json \ /etc/fstab \ | gpg -e $(GPG_RECIPIENTS_ARGS) \ > platform_dotfiles_gpg/$(PLATFORM)/sensitive-configs-$$(date -Iseconds).gpg + rm /tmp/pacman-packages.txt; dotfiles_fetch_platform_ideapad_slim_3_15arp10: make dotfiles_fetch_platform \ PLATFORM=ideapad_slim_3_15arp10 +dotfiles_fetch_platform_gpg_ideapad_slim_3_15arp10: + make dotfiles_fetch_platform_gpg \ + PLATFORM=ideapad_slim_3_15arp10 + dotfiles_sway_put: mkdir -p ~/.sway cp dotfiles/.sway/config ~/.sway/config diff --git a/platform_dotfiles/ideapad_slim_3_15arp10/etc/systemd/logind.conf b/platform_dotfiles/ideapad_slim_3_15arp10/etc/systemd/logind.conf new file mode 100644 index 0000000..f440a34 --- /dev/null +++ b/platform_dotfiles/ideapad_slim_3_15arp10/etc/systemd/logind.conf @@ -0,0 +1,57 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Entries in this file show the compile time defaults. Local configuration +# should be created by either modifying this file (or a copy of it placed in +# /etc/ if the original file is shipped in /usr/), or by creating "drop-ins" in +# the /etc/systemd/logind.conf.d/ directory. The latter is generally +# recommended. Defaults can be restored by simply deleting the main +# configuration file and all drop-ins located in /etc/. +# +# Use 'systemd-analyze cat-config systemd/logind.conf' to display the full config. +# +# See logind.conf(5) for details. + +[Login] +#NAutoVTs=6 +#ReserveVT=6 +#KillUserProcesses=no +#KillOnlyUsers= +#KillExcludeUsers=root +#InhibitDelayMaxSec=5 +#UserStopDelaySec=10 +#HandlePowerKey=hibernate +HandlePowerKey=suspend +#HandlePowerKeyLongPress=ignore +#HandleRebootKey=reboot +#HandleRebootKeyLongPress=poweroff +#HandleSuspendKey=suspend +#HandleSuspendKeyLongPress=hibernate +#HandleHibernateKey=hibernate +#HandleHibernateKeyLongPress=ignore + + +#HandleLidSwitchExternalPower=suspend +#HandleLidSwitchDocked=ignore +#PowerKeyIgnoreInhibited=no +#SuspendKeyIgnoreInhibited=no +#HibernateKeyIgnoreInhibited=no +#LidSwitchIgnoreInhibited=yes +#RebootKeyIgnoreInhibited=no +#HoldoffTimeoutSec=30s +#IdleAction=ignore +#IdleActionSec=30min +#RuntimeDirectorySize=10% +#RuntimeDirectoryInodesMax= +#RemoveIPC=yes +#InhibitorsMax=8192 +#SessionsMax=8192 +#StopIdleSessionSec=infinity + +HandleLidSwitch=suspend +# for sway +#HandleLidSwitch=none diff --git a/platform_dotfiles_gpg/ideapad_slim_3_15arp10/sensitive-configs-2025-12-04T10:10:51+03:00.gpg b/platform_dotfiles_gpg/ideapad_slim_3_15arp10/sensitive-configs-2025-12-04T10:10:51+03:00.gpg new file mode 100644 index 0000000..fc35651 --- /dev/null +++ b/platform_dotfiles_gpg/ideapad_slim_3_15arp10/sensitive-configs-2025-12-04T10:10:51+03:00.gpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6d9e5fd66267dada45cef3617136fb8bef43f61afc3bacf18ff7d65a5c64137 +size 19015 From 314426c67420103b5502ea1a99bcea18fb1cf5b9 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Thu, 4 Dec 2025 11:25:24 +0300 Subject: [PATCH 56/70] [+] update status 1. move to typed commands; 2. partially fixed monkey patch for format of help; --- python/meson.build | 2 +- python/online/fxreader/pr34/commands.py | 115 +------------ .../fxreader/pr34/commands_typed/status.py | 155 ++++++++++++++++++ python/pyproject.toml | 2 +- 4 files changed, 159 insertions(+), 115 deletions(-) create mode 100644 python/online/fxreader/pr34/commands_typed/status.py diff --git a/python/meson.build b/python/meson.build index 805b2e4..a5b3dad 100644 --- a/python/meson.build +++ b/python/meson.build @@ -5,7 +5,7 @@ project( ).stdout().strip('\n'), # 'online.fxreader.uv', # ['c', 'cpp'], - version: '0.1.5.42', + version: '0.1.5.43', # default_options: [ # 'cpp_std=c++23', # # 'prefer_static=true', diff --git a/python/online/fxreader/pr34/commands.py b/python/online/fxreader/pr34/commands.py index 3399ae0..3cfdb49 100644 --- a/python/online/fxreader/pr34/commands.py +++ b/python/online/fxreader/pr34/commands.py @@ -3552,119 +3552,6 @@ def share_wifi(argv): time.sleep(1) -def status(argv): - import inspect - import textwrap - - assert isinstance(argv, list) and all([isinstance(o, str) for o in argv]) - - class c1(optparse.IndentedHelpFormatter): - def format_option(self, *args, **kwargs): - f1 = lambda text, width: '\n'.join([textwrap.fill('\t' + o, width, replace_whitespace=False) for o in text.splitlines()]).splitlines() - t1 = inspect.getsource(optparse.IndentedHelpFormatter.format_option) - t2 = ( - '\n'.join([o[4:] for o in t1.splitlines()[:]]) - .replace( - 'textwrap.wrap', - 'f1', - ) - .replace('format_option', 'f2') - ) - exec(t2, dict(f1=f1), locals()) - return locals()['f2'](self, *args, **kwargs) - - parser = optparse.OptionParser( - formatter=c1( - width=None, - ), - ) - parser.add_option( - '--sh', - dest='sh', - default=[], - action='append', - type=str, - ) - parser.add_option( - '--timeout', - dest='timeout', - default=None, - type=float, - ) - parser.add_option( - '--config', - dest='config', - default=None, - type=str, - help=''.join( - [ - '.json file with array of strings, each is a shell command ', - 'that outputs a separate status text value, ', - 'like\n', - r""" -ping -w 1 -i 0.02 -c 3 | tail -n 2| head -n 1 | grep -Po $'time\\s+.*$' -sensors -j | jq -r '.\"coretemp-isa-0000\".\"Package id 0\".temp1_input|tostring + \" C\"' -printf '%d RPM' $(cat /sys/devices/platform/applesmc.768/fan1_input) -printf '% 3.0f%%' $(upower -d | grep -Po 'percentage:\\s+\\d+(\\.\\d+)?%' | grep -Po '\\d+(\\.\\d+)?' | head -n 1) - """.strip(), - ] - ), - ) - options, args = parser.parse_args(argv) - - if options.timeout is None: - options.timeout = 0.5 - - timeout2 = max(options.timeout, 0.0) - - assert timeout2 >= 0.0 and timeout2 <= 4 - - config = dict() - try: - if not options.config is None: - with io.open(options.config, 'r') as f: - config.update(json.load(f)) - except Exception: - logging.error(traceback.format_exc()) - pass - - options.sh.extend(config.get('sh', [])) - - t1 = [] - - for sh_index, o in enumerate( - [ - *options.sh, - *[ - r""" - A=$(free -h | grep -P Mem: | grep -Po '[\w\.\d]+'); - echo -n $A | awk '{print $2, $7}'; - """, - r""" - date +'%Y-%m-%d %l:%M:%S %p'; - """, - ], - ] - ): - try: - t1.append( - subprocess.check_output( - o, - shell=True, - timeout=timeout2, - ) - .decode('utf-8') - .strip() - ) - except Exception: - t1.append('fail %d' % sh_index) - - t3 = ' | '.join(t1).replace('\n\r', '') - - sys.stdout.write(t3) - sys.stdout.flush() - - def custom_translate( current_string, check, @@ -4107,6 +3994,8 @@ def commands_cli(argv: Optional[list[str]] = None) -> int: options.command = Command(options._command) if options.command is Command.status: + from .commands_typed.status import run as status + status(args) elif options.command is Command.http_server: http_server(args) diff --git a/python/online/fxreader/pr34/commands_typed/status.py b/python/online/fxreader/pr34/commands_typed/status.py new file mode 100644 index 0000000..3ae2246 --- /dev/null +++ b/python/online/fxreader/pr34/commands_typed/status.py @@ -0,0 +1,155 @@ +import sys +import io +import json +import subprocess +import logging +import inspect +import textwrap +import optparse +import traceback + +from typing import ( + Any, + Optional, +) + +logger = logging.getLogger(__name__) + + +def run(argv: list[str]): + assert isinstance(argv, list) and all([isinstance(o, str) for o in argv]) + + class c1(optparse.IndentedHelpFormatter): + def format_option(self, *args: Any, **kwargs: Any) -> Any: + def f1(text: str, width: Optional[int]) -> list[str]: + width = None + return '\n'.join([textwrap.fill('\t' + o, width, replace_whitespace=False) for o in text.splitlines()]).splitlines() + + t1 = inspect.getsource(optparse.IndentedHelpFormatter.format_option) + t2 = ( + '\n'.join([o[4:] for o in t1.splitlines()[:]]) + .replace( + 'textwrap.wrap', + 'f1', + ) + .replace('format_option', 'f2') + ) + ns: dict[str, Any] = dict() + exec(t2, dict(f1=f1), ns) + return ns['f2'](self, *args, **kwargs) + + parser = optparse.OptionParser( + formatter=c1( + width=None, + ), + ) + + def add_option( + p: optparse.OptionParser, + option_name: str, + dest: str, + default: Optional[Any] = None, + action: Optional[str] = None, + **kwargs: Any, + ) -> None: + getattr(p, 'add_option')( + option_name, + dest=dest, + default=default, + action=action, + **kwargs, + ) + + add_option( + parser, + '--sh', + dest='sh', + default=[], + action='append', + type=str, + ) + add_option( + parser, + '--timeout', + dest='timeout', + default=None, + type=float, + ) + add_option( + parser, + '--config', + dest='config', + default=None, + type=str, + help=''.join( + [ + '.json file with array of strings, each is a shell command ', + 'that outputs a separate status text value, ', + 'like\n', + r""" +ping -w 1 -i 0.02 -c 3 | tail -n 2| head -n 1 | grep -Po $'time\\s+.*$' +sensors -j | jq -r '.\"coretemp-isa-0000\".\"Package id 0\".temp1_input|tostring + \" C\"' +printf '%d RPM' $(cat /sys/devices/platform/applesmc.768/fan1_input) +printf '% 3.0f%%' $(upower -d | grep -Po 'percentage:\\s+\\d+(\\.\\d+)?%' | grep -Po '\\d+(\\.\\d+)?' | head -n 1) + """.strip(), + ] + ), + ) + options, args = parser.parse_args(argv) + + if options.timeout is None: + options.timeout = 0.5 + + timeout2 = max(options.timeout, 0.0) + + assert timeout2 >= 0.0 and timeout2 <= 4 + + config: dict[str, Any] = dict() + + try: + if not options.config is None: + with io.open(options.config, 'r') as f: + config.update(json.load(f)) + except Exception: + logging.error(traceback.format_exc()) + pass + + options.sh.extend(config.get('sh', [])) + + t1: list[str] = [] + + for sh_index, o in enumerate( + [ + *options.sh, + *[ + r""" + A=$(free -h | grep -P Mem: | grep -Po '[\w\.\d]+'); + echo -n $A | awk '{print $2, $7}'; + """, + r""" + date +'%Y-%m-%d %l:%M:%S %p'; + """, + ], + ] + ): + try: + t1.append( + subprocess.check_output( + o, + shell=True, + timeout=timeout2, + ) + .decode('utf-8') + .strip() + ) + except Exception: + t1.append('fail %d' % sh_index) + + t3 = ' | '.join(t1).replace('\n\r', '') + + sys.stdout.write(t3) + sys.stdout.flush() + + +if __name__ == '__main__': + run(sys.argv[1:]) diff --git a/python/pyproject.toml b/python/pyproject.toml index 3a18b73..badcd38 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -76,7 +76,7 @@ oom_firefox = 'online.fxreader.pr34.oom_firefox:main' [tool.ruff] -line-length = 160 +line-length = 80 target-version = 'py310' # builtins = ['_', 'I', 'P'] include = [ From 67fcefbce0adba0b4124d8605120ccb509f36f55 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Thu, 4 Dec 2025 11:25:58 +0300 Subject: [PATCH 57/70] [+] reformat ruff 1. column width 80; --- python/_m.py | 19 +- python/cli.py | 18 +- python/m.py | 54 +- python/online/fxreader/pr34/commands.py | 554 ++++++++++++++---- .../pr34/commands_typed/async_api/fastapi.py | 5 +- .../fxreader/pr34/commands_typed/cli.py | 148 ++++- .../pr34/commands_typed/cli_bootstrap.py | 54 +- .../pr34/commands_typed/color_scheme.py | 20 +- .../fxreader/pr34/commands_typed/crypto.py | 4 +- .../fxreader/pr34/commands_typed/logging.py | 4 +- .../fxreader/pr34/commands_typed/metrics.py | 32 +- .../fxreader/pr34/commands_typed/mypy.py | 10 +- .../online/fxreader/pr34/commands_typed/os.py | 27 +- .../fxreader/pr34/commands_typed/pip.py | 69 ++- .../fxreader/pr34/commands_typed/pydantic.py | 30 +- .../fxreader/pr34/commands_typed/status.py | 7 +- python/online/fxreader/pr34/oom_firefox.py | 111 +++- python/online/fxreader/pr34/tasks/ble.py | 20 +- python/online/fxreader/pr34/tasks/cython.py | 24 +- .../fxreader/pr34/tasks/jigsaw_toxic.py | 66 ++- .../online/fxreader/pr34/tasks/mlb_player.py | 444 +++++++++++--- 21 files changed, 1405 insertions(+), 315 deletions(-) diff --git a/python/_m.py b/python/_m.py index b63426e..7f327ab 100644 --- a/python/_m.py +++ b/python/_m.py @@ -53,7 +53,10 @@ def js(argv: list[str]) -> int: '--project-directory', Settings.settings().project_root, '-f', - Settings.settings().project_root / 'docker' / 'js' / 'docker-compose.yml', + Settings.settings().project_root + / 'docker' + / 'js' + / 'docker-compose.yml', *argv, ] ) @@ -67,7 +70,15 @@ def env( env_path = Settings.settings().env_path if not env_path.exists(): - subprocess.check_call([sys.executable, '-m', 'venv', '--system-site-packages', str(env_path)]) + subprocess.check_call( + [ + sys.executable, + '-m', + 'venv', + '--system-site-packages', + str(env_path), + ] + ) subprocess.check_call( [ @@ -233,7 +244,9 @@ Command: TypeAlias = Literal[ def run(argv: Optional[list[str]] = None) -> None: logging.basicConfig( level=logging.INFO, - format=('%(levelname)s:%(name)s:%(message)s:%(process)d:%(asctime)s:%(pathname)s:%(funcName)s:%(lineno)s'), + format=( + '%(levelname)s:%(name)s:%(message)s:%(process)d:%(asctime)s:%(pathname)s:%(funcName)s:%(lineno)s' + ), ) if argv is None: diff --git a/python/cli.py b/python/cli.py index 9a41347..81e16b5 100644 --- a/python/cli.py +++ b/python/cli.py @@ -56,8 +56,18 @@ class CLI(_cli.CLI): self._projects: dict[str, _cli.Project] = { 'online.fxreader.pr34': _cli.Project( source_dir=self.settings.base_dir / 'python', - build_dir=self.settings.base_dir / 'tmp' / 'online' / 'fxreader' / 'pr34' / 'build', - dest_dir=self.settings.base_dir / 'tmp' / 'online' / 'fxreader' / 'pr34' / 'install', + build_dir=self.settings.base_dir + / 'tmp' + / 'online' + / 'fxreader' + / 'pr34' + / 'build', + dest_dir=self.settings.base_dir + / 'tmp' + / 'online' + / 'fxreader' + / 'pr34' + / 'install', meson_path=self.settings.base_dir / 'python' / 'meson.build', ) } @@ -117,7 +127,9 @@ class CLI(_cli.CLI): parser = argparse.ArgumentParser() parser.add_argument('command', choices=[o.value for o in Command]) - parser.add_argument('-p', '--project', choices=[o for o in self.projects]) + parser.add_argument( + '-p', '--project', choices=[o for o in self.projects] + ) parser.add_argument( '-o', '--output_dir', diff --git a/python/m.py b/python/m.py index a98e0c8..82a9d0b 100755 --- a/python/m.py +++ b/python/m.py @@ -78,7 +78,9 @@ class PyProject: third_party_roots: list[ThirdPartyRoot] = dataclasses.field( default_factory=lambda: [], ) - requirements: dict[str, pathlib.Path] = dataclasses.field(default_factory=lambda: dict()) + requirements: dict[str, pathlib.Path] = dataclasses.field( + default_factory=lambda: dict() + ) modules: list[Module] = dataclasses.field( default_factory=lambda: [], @@ -124,7 +126,12 @@ def check_dict( else: VT_class = VT - assert all([isinstance(k, KT) and (VT_class is None or isinstance(v, VT_class)) for k, v in value2.items()]) + assert all( + [ + isinstance(k, KT) and (VT_class is None or isinstance(v, VT_class)) + for k, v in value2.items() + ] + ) if VT is None: return cast( @@ -233,7 +240,12 @@ def pyproject_load( str, ) - if 'tool' in content and isinstance(content['tool'], dict) and tool_name in content['tool'] and isinstance(content['tool'][tool_name], dict): + if ( + 'tool' in content + and isinstance(content['tool'], dict) + and tool_name in content['tool'] + and isinstance(content['tool'][tool_name], dict) + ): pr34_tool = check_dict( check_dict( content['tool'], @@ -246,7 +258,9 @@ def pyproject_load( res.early_features = pr34_tool['early_features'] if 'pip_find_links' in pr34_tool: - res.pip_find_links = [d.parent / pathlib.Path(o) for o in pr34_tool['pip_find_links']] + res.pip_find_links = [ + d.parent / pathlib.Path(o) for o in pr34_tool['pip_find_links'] + ] if 'runtime_libdirs' in pr34_tool: res.runtime_libdirs = [ @@ -265,7 +279,9 @@ def pyproject_load( if 'third_party_roots' in pr34_tool: for o in check_list(pr34_tool['third_party_roots']): o2 = check_dict(o, str, str) - assert all([k in {'package', 'module_root', 'path'} for k in o2]) + assert all( + [k in {'package', 'module_root', 'path'} for k in o2] + ) res.third_party_roots.append( PyProject.ThirdPartyRoot( @@ -279,7 +295,9 @@ def pyproject_load( res.requirements = { k: d.parent / pathlib.Path(v) # pathlib.Path(o) - for k, v in check_dict(pr34_tool['requirements'], str, str).items() + for k, v in check_dict( + pr34_tool['requirements'], str, str + ).items() } if 'modules' in pr34_tool: @@ -328,7 +346,10 @@ class BootstrapSettings: ).strip() ) pip_check_conflicts: Optional[bool] = dataclasses.field( - default_factory=lambda: os.environ.get('PIP_CHECK_CONFLICTS', json.dumps(True)) in [json.dumps(True)], + default_factory=lambda: os.environ.get( + 'PIP_CHECK_CONFLICTS', json.dumps(True) + ) + in [json.dumps(True)], ) uv_args: list[str] = dataclasses.field( default_factory=lambda: os.environ.get( @@ -390,7 +411,9 @@ def requirements_name_get( else: requirements_path = source_dir / 'requirements.txt' - requirements_path_in = requirements_path.parent / (requirements_path.stem + '.in') + requirements_path_in = requirements_path.parent / ( + requirements_path.stem + '.in' + ) requirements_in: list[str] = [] @@ -436,10 +459,15 @@ def env_bootstrap( requirements_in: list[str] = [] - requirements_in.extend(['uv', 'pip', 'build', 'setuptools', 'meson-python', 'pybind11']) + requirements_in.extend( + ['uv', 'pip', 'build', 'setuptools', 'meson-python', 'pybind11'] + ) if pyproject.early_features: - early_dependencies = sum([pyproject.dependencies[o] for o in pyproject.early_features], cast(list[str], [])) + early_dependencies = sum( + [pyproject.dependencies[o] for o in pyproject.early_features], + cast(list[str], []), + ) logger.info( dict( @@ -508,7 +536,11 @@ def env_bootstrap( subprocess.check_call( [ 'uv', - *[o for o in bootstrap_settings.uv_args if not o in ['-U', '--upgrade']], + *[ + o + for o in bootstrap_settings.uv_args + if not o in ['-U', '--upgrade'] + ], 'venv', *venv_python_version, *pip_find_links_args, diff --git a/python/online/fxreader/pr34/commands.py b/python/online/fxreader/pr34/commands.py index 3cfdb49..8b0d354 100644 --- a/python/online/fxreader/pr34/commands.py +++ b/python/online/fxreader/pr34/commands.py @@ -60,7 +60,10 @@ def custom_notify( if sys.platform == 'darwin': osascript_translate = functools.partial( custom_translate, - check=lambda a, b: not re.compile(r'^[a-zA-Z0-9\<\>\/\(\)\s\.\,\:]*$').match(b) is None, + check=lambda a, b: not re.compile( + r'^[a-zA-Z0-9\<\>\/\(\)\s\.\,\:]*$' + ).match(b) + is None, ) subprocess.check_call( @@ -75,7 +78,9 @@ def custom_notify( ] ) else: - subprocess.check_call(['notify-send', '-t', '%d' % timeout2, title, msg[-128:]]) + subprocess.check_call( + ['notify-send', '-t', '%d' % timeout2, title, msg[-128:]] + ) class intercept_output_t: @@ -130,7 +135,11 @@ def intercept_output( last_data = None while not (not current_subprocess.poll() is None and not last_data is None): - if not timeout is None and (datetime.datetime.now() - start_timestamp).total_seconds() > timeout: + if ( + not timeout is None + and (datetime.datetime.now() - start_timestamp).total_seconds() + > timeout + ): break t2 = t1.poll(100) @@ -199,7 +208,12 @@ def intercept_output( def player_metadata() -> Optional[str]: for k in range(20): try: - metadata = {k: subprocess.check_output(['playerctl', 'metadata', k]).decode('utf-8').strip() for k in ['artist', 'title']} + metadata = { + k: subprocess.check_output(['playerctl', 'metadata', k]) + .decode('utf-8') + .strip() + for k in ['artist', 'title'] + } return '%s - %s' % (metadata['artist'], metadata['title']) time.sleep(1.0) except Exception: @@ -219,7 +233,9 @@ def memory_stats() -> memory_stats_t.res_t: with io.BytesIO(subprocess.check_output('free', shell=True)) as f: t1 = f.read().decode('utf-8').splitlines() mem_total = int(t1[1].strip().split()[1]) - mem_used = int(t1[1].strip().split()[2]) + int(t1[1].strip().split()[4]) + mem_used = int(t1[1].strip().split()[2]) + int( + t1[1].strip().split()[4] + ) return dict( mem_total=mem_total, @@ -241,7 +257,9 @@ def memory_stats() -> memory_stats_t.res_t: t1 = subprocess.check_output('vm_stat').decode('utf-8') t2 = [o.split(':') for o in t1.splitlines() if ':' in o] t3 = { - o[0].replace(' ', '_').replace('-', '_').lower(): int(o[1].strip().rstrip('.')) + o[0].replace(' ', '_').replace('-', '_').lower(): int( + o[1].strip().rstrip('.') + ) for o in t2 if len(o) == 2 and len(o[0]) > 0 @@ -382,7 +400,9 @@ def eternal_oom(argv: list[str]) -> None: p = config[app] try: - t1 = subprocess.check_output(['pgrep', '-a', '-f', p[0]]).decode('utf-8') + t1 = subprocess.check_output( + ['pgrep', '-a', '-f', p[0]] + ).decode('utf-8') except Exception: continue t2 = t1.splitlines() @@ -429,9 +449,17 @@ def eternal_oom(argv: list[str]) -> None: if isinstance(options.memory_limit, float): options.memory_limit = int(options.memory_limit) - assert isinstance(options.memory_limit, int) and options.memory_limit < memory_stats()['mem_total'] * 0.95 and options.memory_limit > 512 * 1024 + assert ( + isinstance(options.memory_limit, int) + and options.memory_limit < memory_stats()['mem_total'] * 0.95 + and options.memory_limit > 512 * 1024 + ) - assert isinstance(options.cpu_limit, float) and options.cpu_limit > 0.2 * cpu_count and options.cpu_limit < cpu_count * 0.95 + assert ( + isinstance(options.cpu_limit, float) + and options.cpu_limit > 0.2 * cpu_count + and options.cpu_limit < cpu_count * 0.95 + ) assert options.period >= 1 @@ -459,7 +487,10 @@ def eternal_oom(argv: list[str]) -> None: for value, column in zip(row, header): columns[column].append(value) for column, transformation in extra_columns.items(): - columns[column] = [transformation({k: v[index] for k, v in columns.items()}) for index in range(len(rows))] + columns[column] = [ + transformation({k: v[index] for k, v in columns.items()}) + for index in range(len(rows)) + ] return columns @@ -488,14 +519,19 @@ def eternal_oom(argv: list[str]) -> None: columns: dict[str, list[Any]] merged_data_frame: MergedDataFrame = dict( - header=[column + '_x' for column in left] + [column + '_y' for column in right], + header=[column + '_x' for column in left] + + [column + '_y' for column in right], columns={}, ) for column in merged_data_frame['header']: merged_data_frame['columns'][column] = [] - common_values: set[Any] = {left_value for left_value in index['left'] if left_value in index['right']} + common_values: set[Any] = { + left_value + for left_value in index['left'] + if left_value in index['right'] + } class RowMatch(TypedDict): left_row_index: int @@ -518,8 +554,14 @@ def eternal_oom(argv: list[str]) -> None: values[ common_row[ cast( - Literal['left_row_index' | 'right_row_index'], - 'left_row_index' if index_name == 'left' else 'right_row_index' if index_name == 'right' else raise_not_implemented(), + Literal[ + 'left_row_index' | 'right_row_index' + ], + 'left_row_index' + if index_name == 'left' + else 'right_row_index' + if index_name == 'right' + else raise_not_implemented(), ) ] ] @@ -539,9 +581,18 @@ def eternal_oom(argv: list[str]) -> None: assert ascending is False t1 = [ o['row_index'] - for o in sorted([dict(row_index=row_index, value=value) for row_index, value in enumerate(data_frame[by[0]])], key=lambda x: x['value'])[::-1] + for o in sorted( + [ + dict(row_index=row_index, value=value) + for row_index, value in enumerate(data_frame[by[0]]) + ], + key=lambda x: x['value'], + )[::-1] ] - return {column: [values[row_index] for row_index in t1] for column, values in data_frame.items()} + return { + column: [values[row_index] for row_index in t1] + for column, values in data_frame.items() + } def pandas_filter_values(data_frame, condition): shape = [ @@ -549,11 +600,25 @@ def eternal_oom(argv: list[str]) -> None: ] if shape[0] > 0: shape.append(len(list(data_frame.values())[0])) - t1 = [row_index for row_index in range(shape[1]) if condition({column: values[row_index] for column, values in data_frame.items()})] - return {column: [values[row_index] for row_index in t1] for column, values in data_frame.items()} + t1 = [ + row_index + for row_index in range(shape[1]) + if condition( + { + column: values[row_index] + for column, values in data_frame.items() + } + ) + ] + return { + column: [values[row_index] for row_index in t1] + for column, values in data_frame.items() + } def pandas_row(data_frame, row_index): - return {column: values[row_index] for column, values in data_frame.items()} + return { + column: values[row_index] for column, values in data_frame.items() + } def pandas_shape(data_frame): columns_count = len(data_frame) @@ -580,7 +645,9 @@ def eternal_oom(argv: list[str]) -> None: def oom_get_processes( extra_filter=None, ): - with io.BytesIO(subprocess.check_output('ps -e -o pid,rss,user,%cpu', shell=True)) as f: + with io.BytesIO( + subprocess.check_output('ps -e -o pid,rss,user,%cpu', shell=True) + ) as f: t1 = pandas_data_frame( f.read().decode('utf-8').splitlines(), ps_regex(4), @@ -594,7 +661,11 @@ def eternal_oom(argv: list[str]) -> None: del t1['%CPU'] assert set(t1.keys()) == set(['PID', 'RSS', 'USER', 'CPU']) - t5 = subprocess.check_output('ps -e -o pid,args', shell=True).decode('utf-8').splitlines() + t5 = ( + subprocess.check_output('ps -e -o pid,args', shell=True) + .decode('utf-8') + .splitlines() + ) t6 = pandas_data_frame( t5, r'^\s*(\d+)\s(.*)$', @@ -614,7 +685,12 @@ def eternal_oom(argv: list[str]) -> None: if extra_filter is None: extra_filter = lambda *args: True - t7 = pandas_filter_values(t11, lambda row: row['PID_x'] != self_pid and not 'freelancer' in row['COMMAND_y'] and extra_filter(row)) + t7 = pandas_filter_values( + t11, + lambda row: row['PID_x'] != self_pid + and not 'freelancer' in row['COMMAND_y'] + and extra_filter(row), + ) t8 = pandas_sort_values(t7, by=['RSS_x'], ascending=False) t9 = pandas_sort_values(t7, by=['CPU_x'], ascending=False) @@ -694,7 +770,9 @@ def eternal_oom(argv: list[str]) -> None: if t11['total_cpu'] > options.cpu_limit: oom_display_rows(t11['by_cpu']) - free_before_oom = options.memory_limit - current_memory_stats['mem_used'] + free_before_oom = ( + options.memory_limit - current_memory_stats['mem_used'] + ) print( 'available %5.2f %% out of %5.2f %% of cpu limit before OOC' @@ -746,7 +824,10 @@ def eternal_oom(argv: list[str]) -> None: if last_cpu_high is None: last_cpu_high = datetime.datetime.now().timestamp() - if datetime.datetime.now().timestamp() - last_cpu_high > options.cpu_wait: + if ( + datetime.datetime.now().timestamp() - last_cpu_high + > options.cpu_wait + ): last_cpu_high = None del last_total_cpu[:] return True @@ -768,8 +849,15 @@ def eternal_oom(argv: list[str]) -> None: mem_stat = memory_stats() mem_used = mem_stat['mem_used'] - if options.memory_limit < mem_stat['mem_total'] and not oom_mem_high(mem_stat['mem_total'] - (mem_stat['mem_total'] - options.memory_limit) / 2): - extra_filters = lambda row: ('chrome' in row['COMMAND_y'] and '--type=renderer' in row['COMMAND_y'] or not 'chrome' in row['COMMAND_y']) + if options.memory_limit < mem_stat['mem_total'] and not oom_mem_high( + mem_stat['mem_total'] + - (mem_stat['mem_total'] - options.memory_limit) / 2 + ): + extra_filters = lambda row: ( + 'chrome' in row['COMMAND_y'] + and '--type=renderer' in row['COMMAND_y'] + or not 'chrome' in row['COMMAND_y'] + ) else: extra_filters = None @@ -811,7 +899,9 @@ def eternal_oom(argv: list[str]) -> None: def resilient_vlc(stream=None): if stream is None: - streams_path = os.path.join(os.environ['CACHE_PATH'], 'resilient-vlc-streams.json') + streams_path = os.path.join( + os.environ['CACHE_PATH'], 'resilient-vlc-streams.json' + ) if os.path.exists(streams_path): with io.open(streams_path, 'r') as f: @@ -852,7 +942,9 @@ def resilient_vlc(stream=None): 'main interface error', ] ] - ) and any([o in t1 for o in ['pulse audio output debug: underflow']]): + ) and any( + [o in t1 for o in ['pulse audio output debug: underflow']] + ): print('shit') p.kill() while True: @@ -930,7 +1022,12 @@ def eternal_firefox( ) as p: try: if debug: - assert subprocess.check_call(['notify-send', '%s:Starting' % group_name]) == 0 + assert ( + subprocess.check_call( + ['notify-send', '%s:Starting' % group_name] + ) + == 0 + ) # t3 = '' for k in range(300): @@ -1015,10 +1112,18 @@ def eternal_firefox( reposition() if debug: - assert subprocess.check_call(['notify-send', '%s:Started' % group_name]) == 0 + assert ( + subprocess.check_call( + ['notify-send', '%s:Started' % group_name] + ) + == 0 + ) start = datetime.datetime.now() - is_to_restart = lambda: (datetime.datetime.now() - start).total_seconds() >= 900 * 4 + is_to_restart = ( + lambda: (datetime.datetime.now() - start).total_seconds() + >= 900 * 4 + ) polling_count = 0 while not is_to_restart(): @@ -1031,7 +1136,12 @@ def eternal_firefox( polling_count += 1 if debug: - assert subprocess.check_call(['notify-send', '%s:Closing' % group_name]) == 0 + assert ( + subprocess.check_call( + ['notify-send', '%s:Closing' % group_name] + ) + == 0 + ) # assert os.system('wmctrl -i -c %s' % t2) == 0 assert ( @@ -1067,7 +1177,12 @@ def eternal_firefox( pprint.pprint([p.pid, '20 seconds timeout', 'kill']) p.kill() if debug: - assert subprocess.check_call(['notify-send', '%s:Closed' % group_name]) == 0 + assert ( + subprocess.check_call( + ['notify-send', '%s:Closed' % group_name] + ) + == 0 + ) def resilient_ethernet(ip_addr, ethernet_device): @@ -1082,7 +1197,9 @@ do ping -c 3 -w 3 -W 1 {{IP_ADDR}} || (\ ); \ sleep 10; clear; date; \ done' - """.replace('{{IP_ADDR}}', ip_addr).replace('{{ETHERNET_DEVICE}}}', ethernet_device), + """.replace('{{IP_ADDR}}', ip_addr).replace( + '{{ETHERNET_DEVICE}}}', ethernet_device + ), shell=True, ) @@ -1169,7 +1286,13 @@ def http_server(argv): # 'ping', '-w', '1', # options.host # ]) - assert options.host in sum([[o2.local for o2 in o.addr_info] for o in commands_os.interfaces_index()], []) + assert options.host in sum( + [ + [o2.local for o2 in o.addr_info] + for o in commands_os.interfaces_index() + ], + [], + ) except Exception: raise RuntimeError('invalid ip address %s' % options.host) @@ -1210,9 +1333,16 @@ def http_server(argv): ) ) - assert all([not re.compile(r'^[A-Za-z-]+ [a-z0-9A-Z-\.]+$').match(o) is None for o in options.response_headers]) + assert all( + [ + not re.compile(r'^[A-Za-z-]+ [a-z0-9A-Z-\.]+$').match(o) is None + for o in options.response_headers + ] + ) - location_section = ('location / {deny all;}location /%s/ {alias %s/;%s%s}') % ( + location_section = ( + 'location / {deny all;}location /%s/ {alias %s/;%s%s}' + ) % ( path, APP_DIR, '\n'.join(['add_header %s;' % o for o in options.response_headers]), @@ -1420,7 +1550,10 @@ def pass_ssh_osx(argv): t1 = options.pass_option assert len(t1) > 0 - print('select on of pass names\n%s' % '\n'.join(['%d: %s' % (k, v) for k, v in enumerate(t1)])) + print( + 'select on of pass names\n%s' + % '\n'.join(['%d: %s' % (k, v) for k, v in enumerate(t1)]) + ) while True: try: @@ -1484,7 +1617,9 @@ def pass_ssh_osx(argv): p.wait(1) assert p.poll() == 0 - with subprocess.Popen(ssh_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as p: + with subprocess.Popen( + ssh_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) as p: password = None last_chunk = None @@ -1540,10 +1675,23 @@ def pass_ssh_osx(argv): if options.debug: pprint.pprint(last_chunk['data']) - if last_chunk['data'].endswith('\r\n[0]'.encode('utf-8')) and last_chunk['data'].rfind(pinentry_delimeter) != -1: + if ( + last_chunk['data'].endswith('\r\n[0]'.encode('utf-8')) + and last_chunk['data'].rfind(pinentry_delimeter) != -1 + ): last_line = last_chunk['data'].splitlines()[-2] else: - raise RuntimeError('gpg failure %s' % str(last_chunk['data'][max(last_chunk['data'].find(pinentry_delimeter), -128) :])) + raise RuntimeError( + 'gpg failure %s' + % str( + last_chunk['data'][ + max( + last_chunk['data'].find(pinentry_delimeter), + -128, + ) : + ] + ) + ) pos2 = last_line.rfind(pinentry_delimeter) if pos2 == -1: @@ -1591,7 +1739,9 @@ def vpn(argv: list[str]) -> None: python_path: list[str] if (pathlib.Path(__file__).parent / 'env3').exists(): - python_path = [str(pathlib.Path(__file__).parent / 'env3' / 'bin' / 'python3')] + python_path = [ + str(pathlib.Path(__file__).parent / 'env3' / 'bin' / 'python3') + ] elif (pathlib.Path(__file__).parent.parent.parent.parent / 'm').exists(): python_path = [ str(pathlib.Path(__file__).parent.parent.parent.parent / 'm'), @@ -1637,14 +1787,28 @@ def player_v1(folder_url, item_id): t7 = t5[k] t9 = urllib.parse.unquote(os.path.split(t7)[1]) progress_bar.set_description('%03d %s' % (k, t9)) - with subprocess.Popen(['ffprobe', '-hide_banner', '-i', t7], stderr=subprocess.PIPE, stdout=subprocess.PIPE) as p: + with subprocess.Popen( + ['ffprobe', '-hide_banner', '-i', t7], + stderr=subprocess.PIPE, + stdout=subprocess.PIPE, + ) as p: p.wait() assert p.returncode == 0 t8 = p.stderr.read().decode('utf-8') assert isinstance(t8, str) # print(t8) with subprocess.Popen( - ['ffplay', '-hide_banner', '-nodisp', '-autoexit', '-loop', '1', t7], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL + [ + 'ffplay', + '-hide_banner', + '-nodisp', + '-autoexit', + '-loop', + '1', + t7, + ], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, ) as p: p.wait() assert p.returncode == 0 @@ -1708,7 +1872,9 @@ def pm_service(argv): wu = 0 while True: - subprocess.check_call(['osascript', '-e', 'tell application "Finder" to sleep']) + subprocess.check_call( + ['osascript', '-e', 'tell application "Finder" to sleep'] + ) subprocess.check_call( ['pmset', 'sleepnow'], stdout=subprocess.DEVNULL, @@ -1747,9 +1913,16 @@ def pm_service(argv): # -E 'powerd.*TurnedOn.*UserIsActive' | head -n 1 #''', shell=True).decode('utf-8') - if not cmd is None and ('TurnedOn' in cmd or 'PrevIdle' in cmd or 'PMRD: kIOMessageSystemWillPowerOn' in cmd): + if not cmd is None and ( + 'TurnedOn' in cmd + or 'PrevIdle' in cmd + or 'PMRD: kIOMessageSystemWillPowerOn' in cmd + ): if ( - ('AppleMultitouchDevice' in cmd and 'tp' in options.events) + ( + 'AppleMultitouchDevice' in cmd + and 'tp' in options.events + ) or ('AppleACPIButton' in cmd and 'pb' in options.events) or ('eventType:29' in cmd and 'kb' in options.events) ): @@ -1769,7 +1942,11 @@ def pm_service(argv): ) ) else: - print('\r%s wu : %d, la : %s' % (datetime.datetime.now().isoformat(), wu, action), end='') + print( + '\r%s wu : %d, la : %s' + % (datetime.datetime.now().isoformat(), wu, action), + end='', + ) if action == 'wake-up': break @@ -1888,7 +2065,9 @@ def scrap_yt_music(argv: list[str]) -> None: break if p is None and not current_name is None: - output_name = os.path.join(options.library_path, '%s.mp3' % current_name) + output_name = os.path.join( + options.library_path, '%s.mp3' % current_name + ) logging.info('audio_record, new recording') p = subprocess.Popen( ['sox', '-d', output_name], @@ -1926,7 +2105,9 @@ def scrap_yt_music(argv: list[str]) -> None: target=functools.partial( http_events, context=context, - res_cb=lambda *args, **kwargs: context['http_on_event'](*args, **kwargs), + res_cb=lambda *args, **kwargs: context['http_on_event']( + *args, **kwargs + ), ) ), threading.Thread( @@ -1941,7 +2122,9 @@ def scrap_yt_music(argv: list[str]) -> None: def http_on_event(event, events): with context['track_cv']: if 'title' in event and event['title'].strip() != '': - context['track_name'] = str(event['title'])[:128].replace('\n', '') + context['track_name'] = str(event['title'])[:128].replace( + '\n', '' + ) else: context['track_name'] = None @@ -2188,7 +2371,11 @@ class Battery: ) else: pass - print('\r%s % 5.2f%% %s' % (datetime.datetime.now().isoformat(), t3, str(t5)), end='') + print( + '\r%s % 5.2f%% %s' + % (datetime.datetime.now().isoformat(), t3, str(t5)), + end='', + ) except Exception: logging.error(traceback.format_exc()) @@ -2273,7 +2460,9 @@ def desktop_services(argv): t2 = [] try: - t1 = subprocess.check_output(['swaymsg', '-t', 'get_tree']).decode('utf-8') + t1 = subprocess.check_output( + ['swaymsg', '-t', 'get_tree'] + ).decode('utf-8') t2 = json.loads(t1) except Exception: logging.error(traceback.format_exc()) @@ -2348,7 +2537,9 @@ def desktop_services(argv): @classmethod def dpms_get(cls): try: - t1 = subprocess.check_output(['swaymsg', '-r', '-t', 'get_outputs'], timeout=1) + t1 = subprocess.check_output( + ['swaymsg', '-r', '-t', 'get_outputs'], timeout=1 + ) t2 = t1.decode('utf-8') t3 = json.loads(t2) t4 = [ @@ -2605,7 +2796,9 @@ def desktop_services(argv): ]: if os.path.exists('/sys/bus/platform/devices/applesmc.768'): return 'applesmc.768' - elif os.path.exists('/sys/devices/system/cpu/intel_pstate/no_turbo'): + elif os.path.exists( + '/sys/devices/system/cpu/intel_pstate/no_turbo' + ): return 'intel_pstate' elif os.path.exists('/sys/devices/system/cpu/amd_pstate'): return 'amd_pstate' @@ -2822,7 +3015,11 @@ echo 1 | tee /sys/devices/system/cpu/cpu*/cpufreq/boost after_resume='echo after_resume; pkill --signal SIGUSR1 swayidle;', ) self.last_force_idle = None - self.commands.update(timeout2='echo timeout2; {swaylock_cmd};'.format(swaylock_cmd=self.commands['swaylock_cmd2'])) + self.commands.update( + timeout2='echo timeout2; {swaylock_cmd};'.format( + swaylock_cmd=self.commands['swaylock_cmd2'] + ) + ) self.swayidle = subprocess.Popen( r""" exec swayidle -d -w \ @@ -2858,14 +3055,22 @@ echo 1 | tee /sys/devices/system/cpu/cpu*/cpufreq/boost self.bg_terminate = False def skip_loop_long_ago(self): - if self.last_skip_loop is None or (datetime.datetime.now() - self.last_skip_loop).total_seconds() >= 30: + if ( + self.last_skip_loop is None + or ( + datetime.datetime.now() - self.last_skip_loop + ).total_seconds() + >= 30 + ): self.last_skip_loop = datetime.datetime.now() return True else: return False def background_check(self): - if (self.bg is None or not self.bg.poll() is None) and not self.bg_terminate: + if ( + self.bg is None or not self.bg.poll() is None + ) and not self.bg_terminate: if not options.background_image is None: self.bg = subprocess.Popen( [ @@ -2892,7 +3097,13 @@ echo 1 | tee /sys/devices/system/cpu/cpu*/cpufreq/boost self.swayidle.stdin.flush() def force_idle(self): - if self.last_force_idle is None or (datetime.datetime.now() - self.last_force_idle).total_seconds() >= 10: + if ( + self.last_force_idle is None + or ( + datetime.datetime.now() - self.last_force_idle + ).total_seconds() + >= 10 + ): self.last_force_idle = datetime.datetime.now() return True else: @@ -2937,7 +3148,9 @@ echo 1 | tee /sys/devices/system/cpu/cpu*/cpufreq/boost self.data.append(chunk) if b'\n' in chunk['data']: - total = b''.join([o['data'] for o in self.data]).decode('utf-8') + total = b''.join([o['data'] for o in self.data]).decode( + 'utf-8' + ) sep_pos = total.rfind('\n') lines = total[:sep_pos].splitlines() self.data = [ @@ -2976,7 +3189,10 @@ echo 1 | tee /sys/devices/system/cpu/cpu*/cpufreq/boost while True: logging.info('retry i = %d, cnt = %d' % (i, cnt)) - if not (subprocess.call(['swaymsg', '-t', 'get_version']) == 0): + if not ( + subprocess.call(['swaymsg', '-t', 'get_version']) + == 0 + ): continue if cb() == 0: @@ -2986,12 +3202,20 @@ echo 1 | tee /sys/devices/system/cpu/cpu*/cpufreq/boost i += 1 - if len(new_events) > 0 or len(self.events) > 0 and self.skip_loop_long_ago(): + if ( + len(new_events) > 0 + or len(self.events) > 0 + and self.skip_loop_long_ago() + ): self.events.extend(new_events) skip_loop = False - if all([o in ['t1', 't4'] for o in self.events]) and VLC.vlc_is_playing_fullscreen() and self.backlight.dpms: + if ( + all([o in ['t1', 't4'] for o in self.events]) + and VLC.vlc_is_playing_fullscreen() + and self.backlight.dpms + ): skip_loop = True logging.info( 'skip loop, %s' @@ -3005,9 +3229,17 @@ echo 1 | tee /sys/devices/system/cpu/cpu*/cpufreq/boost ], ) ) - elif len(new_events) == 0 and len(self.events) > 1 and all([o in ['t1', 't4'] for o in self.events]): + elif ( + len(new_events) == 0 + and len(self.events) > 1 + and all([o in ['t1', 't4'] for o in self.events]) + ): self.events = ['t4'] - elif len(self.events) > 1 and (self.events == ['t1', 't4', 't5', 't5'] or self.events == ['t1', 't5', 't5'] or self.events == ['t1', 't5']): + elif len(self.events) > 1 and ( + self.events == ['t1', 't4', 't5', 't5'] + or self.events == ['t1', 't5', 't5'] + or self.events == ['t1', 't5'] + ): for o in new_events: self.release_lock() @@ -3023,7 +3255,9 @@ echo 1 | tee /sys/devices/system/cpu/cpu*/cpufreq/boost # subprocess.check_call(self.commands['lock'], shell=True) logging.info('started t1') if self.force_idle(): - subprocess.check_call(self.commands['timeout1'], shell=True) + subprocess.check_call( + self.commands['timeout1'], shell=True + ) logging.info('done t1') self.release_lock() elif o == 't2': @@ -3034,12 +3268,29 @@ echo 1 | tee /sys/devices/system/cpu/cpu*/cpufreq/boost msg='loginctl lock started', ) while True: - if not subprocess.call(self.commands['lock'], shell=True) == 0: + if ( + not subprocess.call( + self.commands['lock'], shell=True + ) + == 0 + ): continue - if not subprocess.call(self.commands['timeout2'], shell=True) == 0: + if ( + not subprocess.call( + self.commands['timeout2'], + shell=True, + ) + == 0 + ): # continue pass - if not subprocess.call(self.commands['timeout1'], shell=True) == 0: + if ( + not subprocess.call( + self.commands['timeout1'], + shell=True, + ) + == 0 + ): continue break logging.info('done lock') @@ -3049,24 +3300,42 @@ echo 1 | tee /sys/devices/system/cpu/cpu*/cpufreq/boost elif o == 't4': logging.info('started t4') if self.force_idle(): - subprocess.check_call(self.commands['lock'], shell=True) - subprocess.call(self.commands['timeout2'], shell=True) - subprocess.check_call(self.commands['timeout1'], shell=True) + subprocess.check_call( + self.commands['lock'], shell=True + ) + subprocess.call( + self.commands['timeout2'], shell=True + ) + subprocess.check_call( + self.commands['timeout1'], shell=True + ) logging.info('done t4') self.release_lock() elif o == 't5': logging.info('started timeout resume') if self.force_idle(): - subprocess.check_call(self.commands['lock'], shell=True) + subprocess.check_call( + self.commands['lock'], shell=True + ) retry( - lambda: subprocess.call(self.commands['resume'], shell=True), + lambda: subprocess.call( + self.commands['resume'], shell=True + ), ) logging.info('done timeout resume') elif o == 't6': logging.info('started before-sleep') if self.force_idle(): - (subprocess.call(self.commands['timeout2'], shell=True),) - (subprocess.check_call(self.commands['timeout1'], shell=True),) + ( + subprocess.call( + self.commands['timeout2'], shell=True + ), + ) + ( + subprocess.check_call( + self.commands['timeout1'], shell=True + ), + ) logging.info('done before-sleep') self.release_lock() elif o == 't7': @@ -3074,7 +3343,12 @@ echo 1 | tee /sys/devices/system/cpu/cpu*/cpufreq/boost # if self.force_idle(): # subprocess.check_call(self.commands['lock'], shell=True) while True: - if subprocess.call(self.commands['resume'], shell=True) == 0: + if ( + subprocess.call( + self.commands['resume'], shell=True + ) + == 0 + ): break else: time.sleep(0.5) @@ -3099,7 +3373,15 @@ echo 1 | tee /sys/devices/system/cpu/cpu*/cpufreq/boost self.background_check() if options.polkit_service: - services.extend([subprocess.Popen(['/usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1'])]) + services.extend( + [ + subprocess.Popen( + [ + '/usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1' + ] + ) + ] + ) services.extend( [ @@ -3234,12 +3516,24 @@ def gnome_shortcuts(argv: list[str]) -> None: 'set', 'org.gnome.settings-daemon.plugins.media-keys', 'custom-keybindings', - '[%s]' % ','.join(["'%s'" % ('/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom%d/') % o for o in range(command_id + 1)]), + '[%s]' + % ','.join( + [ + "'%s'" + % ( + '/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom%d/' + ) + % o + for o in range(command_id + 1) + ] + ), ), ( 'gsettings', 'set', - ('org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom%d/') + ( + 'org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom%d/' + ) % command_id, 'name', name, @@ -3247,7 +3541,9 @@ def gnome_shortcuts(argv: list[str]) -> None: ( 'gsettings', 'set', - ('org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom%d/') + ( + 'org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom%d/' + ) % command_id, 'command', command, @@ -3255,7 +3551,9 @@ def gnome_shortcuts(argv: list[str]) -> None: ( 'gsettings', 'set', - ('org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom%d/') + ( + 'org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom%d/' + ) % command_id, 'binding', binding, @@ -3273,7 +3571,10 @@ def gnome_shortcuts(argv: list[str]) -> None: [ 'gsettings', 'get', - ('org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:%s') % o, + ( + 'org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:%s' + ) + % o, k, ] ) @@ -3338,7 +3639,9 @@ def socat_ssh(argv): dest='gateway_command', default=None, type=str, - help=('a shell command that forwards ssh socket data somewhere else, like busybox nc 127.0.0.1 $(cat remote-ssh.port)'), + help=( + 'a shell command that forwards ssh socket data somewhere else, like busybox nc 127.0.0.1 $(cat remote-ssh.port)' + ), ) options, args = parser.parse_args(argv) @@ -3471,10 +3774,18 @@ def share_wifi(argv): print('enter password:') - pw = subprocess.check_output('read -s PW; echo -n $PW', shell=True).decode('utf-8') + pw = subprocess.check_output('read -s PW; echo -n $PW', shell=True).decode( + 'utf-8' + ) if len(pw) == 0: - pw = subprocess.check_output('pwgen -syn 20 1', shell=True).decode('utf-8').strip() - with subprocess.Popen(['qrencode', '-t', 'UTF8'], stdin=subprocess.PIPE) as p: + pw = ( + subprocess.check_output('pwgen -syn 20 1', shell=True) + .decode('utf-8') + .strip() + ) + with subprocess.Popen( + ['qrencode', '-t', 'UTF8'], stdin=subprocess.PIPE + ) as p: p.stdin.write(pw.encode('utf-8')) p.stdin.flush() p.stdin.close() @@ -3541,7 +3852,9 @@ def share_wifi(argv): if shutdown: break - if (datetime.datetime.now() - last_timestamp).total_seconds() > options.restart_delay: + if ( + datetime.datetime.now() - last_timestamp + ).total_seconds() > options.restart_delay: restart = True last_timestamp = datetime.datetime.now() @@ -3660,8 +3973,14 @@ def media_keys(argv): if mode == 'mocp': raise NotImplementedError elif mode == 'playerctl': - pos = float(subprocess.check_output(['playerctl', 'position']).decode('utf-8')) - subprocess.check_call(['playerctl', 'position', '%f' % (pos - float(args[0]))]) + pos = float( + subprocess.check_output(['playerctl', 'position']).decode( + 'utf-8' + ) + ) + subprocess.check_call( + ['playerctl', 'position', '%f' % (pos - float(args[0]))] + ) # msg = player_metadata() else: raise NotImplementedError @@ -3669,8 +3988,14 @@ def media_keys(argv): if mode == 'mocp': raise NotImplementedError elif mode == 'playerctl': - pos = float(subprocess.check_output(['playerctl', 'position']).decode('utf-8')) - subprocess.check_call(['playerctl', 'position', '%f' % (pos + float(args[0]))]) + pos = float( + subprocess.check_output(['playerctl', 'position']).decode( + 'utf-8' + ) + ) + subprocess.check_call( + ['playerctl', 'position', '%f' % (pos + float(args[0]))] + ) # msg = player_metadata() else: raise NotImplementedError @@ -3684,8 +4009,16 @@ def media_keys(argv): else: raise NotImplementedError elif options.command == 'media-lower-volume': - subprocess.check_call(['pactl', 'set-sink-volume', '@DEFAULT_SINK@', '-5%']) - msg = subprocess.check_output(['pactl', 'get-sink-volume', '@DEFAULT_SINK@']).decode('utf-8').strip() + subprocess.check_call( + ['pactl', 'set-sink-volume', '@DEFAULT_SINK@', '-5%'] + ) + msg = ( + subprocess.check_output( + ['pactl', 'get-sink-volume', '@DEFAULT_SINK@'] + ) + .decode('utf-8') + .strip() + ) elif options.command == 'media-toggle-volume': subprocess.check_call( [ @@ -3695,10 +4028,24 @@ def media_keys(argv): 'toggle', ] ) - msg = subprocess.check_output(['pactl', 'get-sink-volume', '@DEFAULT_SINK@']).decode('utf-8').strip() + msg = ( + subprocess.check_output( + ['pactl', 'get-sink-volume', '@DEFAULT_SINK@'] + ) + .decode('utf-8') + .strip() + ) elif options.command == 'media-raise-volume': - subprocess.check_call(['pactl', 'set-sink-volume', '@DEFAULT_SINK@', '+5%']) - msg = subprocess.check_output(['pactl', 'get-sink-volume', '@DEFAULT_SINK@']).decode('utf-8').strip() + subprocess.check_call( + ['pactl', 'set-sink-volume', '@DEFAULT_SINK@', '+5%'] + ) + msg = ( + subprocess.check_output( + ['pactl', 'get-sink-volume', '@DEFAULT_SINK@'] + ) + .decode('utf-8') + .strip() + ) else: raise NotImplementedError @@ -3771,7 +4118,9 @@ def install(argv: list[str]) -> None: final_target = options.target / relative_source - logger.info(dict(final_target=final_target, relative_source=relative_source)) + logger.info( + dict(final_target=final_target, relative_source=relative_source) + ) if final_target.exists(): if not options.overwrite: @@ -3915,7 +4264,10 @@ def pip_check_conflicts( def pip_resolve( args: list[str], ) -> None: - from online.fxreader.pr34.commands_typed.pip import pip_resolve, pip_resolve_t + from online.fxreader.pr34.commands_typed.pip import ( + pip_resolve, + pip_resolve_t, + ) parser = argparse.ArgumentParser() parser.add_argument( @@ -3968,7 +4320,9 @@ def commands_cli(argv: Optional[list[str]] = None) -> int: if argv is None: argv = sys.argv[1:] - from online.fxreader.pr34.commands_typed.logging import setup as logging_setup + from online.fxreader.pr34.commands_typed.logging import ( + setup as logging_setup, + ) logging_setup() # logging.getLogger().setLevel(logging.INFO) diff --git a/python/online/fxreader/pr34/commands_typed/async_api/fastapi.py b/python/online/fxreader/pr34/commands_typed/async_api/fastapi.py index b75cc47..9ff821d 100644 --- a/python/online/fxreader/pr34/commands_typed/async_api/fastapi.py +++ b/python/online/fxreader/pr34/commands_typed/async_api/fastapi.py @@ -31,7 +31,10 @@ def create_app() -> fastapi.FastAPI: 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))() + app_router = cast( + Callable[[], Any], + getattr(importlib.import_module(app_module), app_method), + )() assert isinstance(app_router, fastapi.APIRouter) diff --git a/python/online/fxreader/pr34/commands_typed/cli.py b/python/online/fxreader/pr34/commands_typed/cli.py index 912be7e..766bcd2 100644 --- a/python/online/fxreader/pr34/commands_typed/cli.py +++ b/python/online/fxreader/pr34/commands_typed/cli.py @@ -172,9 +172,13 @@ class CLI(abc.ABC): ) -> None: from . import cli_bootstrap - pyproject = cli_bootstrap.pyproject_load(self.projects[project].source_dir / 'pyproject.toml') + pyproject = cli_bootstrap.pyproject_load( + self.projects[project].source_dir / 'pyproject.toml' + ) - dependencies = sum([pyproject.dependencies[o] for o in features], cast(list[str], [])) + dependencies = sum( + [pyproject.dependencies[o] for o in features], cast(list[str], []) + ) pip_find_links: list[pathlib.Path] = [] @@ -216,7 +220,9 @@ class CLI(abc.ABC): force: bool, ) -> None: for k, d in self.dependencies.items(): - whl_glob = self.dist_settings.wheel_dir / ('*%s*.whl' % d.name.replace('.', '_')) + whl_glob = self.dist_settings.wheel_dir / ( + '*%s*.whl' % d.name.replace('.', '_') + ) if len(glob.glob(str(whl_glob))) == 0 or force: if d.source_path.exists(): @@ -256,7 +262,9 @@ class CLI(abc.ABC): def index_get(o: dict[str, Any]) -> tuple[Any, ...]: return (o['path'], o['stat']) - present_files_index = {index_get(o): o for o in present_files} + present_files_index = { + index_get(o): o for o in present_files + } new_files: list[dict[str, Any]] = [] @@ -295,7 +303,13 @@ class CLI(abc.ABC): [ pathlib.Path(o) for o in glob.glob( - str(self.dist_settings.env_path / 'lib' / 'python*' / '**' / 'pkgconfig'), + str( + self.dist_settings.env_path + / 'lib' + / 'python*' + / '**' + / 'pkgconfig' + ), recursive=True, ) ] @@ -388,7 +402,18 @@ class CLI(abc.ABC): shutil.rmtree(pyproject_build_dir) if len(self.third_party_roots(project_name)) > 0: - extra_args.append('-Csetup-args=%s' % ('-Dthird_party_roots=%s' % json.dumps([str(o.absolute()) for o in self.third_party_roots(project_name)]))) + extra_args.append( + '-Csetup-args=%s' + % ( + '-Dthird_party_roots=%s' + % json.dumps( + [ + str(o.absolute()) + for o in self.third_party_roots(project_name) + ] + ) + ) + ) cmd = [ sys.executable, @@ -449,11 +474,21 @@ class CLI(abc.ABC): preserve_top_path=True, ) - pyproject = cli_bootstrap.pyproject_load(project.source_dir / 'pyproject.toml') + pyproject = cli_bootstrap.pyproject_load( + project.source_dir / 'pyproject.toml' + ) - pyproject_tool = pydantic.RootModel[PyProject.Tool].model_validate(pyproject.tool).root + pyproject_tool = ( + pydantic.RootModel[PyProject.Tool] + .model_validate(pyproject.tool) + .root + ) - if pyproject_tool.meson and pyproject_tool.meson.args and pyproject_tool.meson.args.install: + if ( + pyproject_tool.meson + and pyproject_tool.meson.args + and pyproject_tool.meson.args.install + ): argv = pyproject_tool.meson.args.install + argv cmd = [ @@ -495,7 +530,9 @@ class CLI(abc.ABC): content = f.read() with io.open(o, 'w') as f: - f.write(content.replace('prefix=/', 'prefix=${pcfiledir}/../../')) + f.write( + content.replace('prefix=/', 'prefix=${pcfiledir}/../../') + ) def ninja( self, @@ -589,18 +626,30 @@ class CLI(abc.ABC): res: list[pathlib.Path] = [] if not project_name is None: - pyproject = cli_bootstrap.pyproject_load(self.projects[project_name].source_dir / 'pyproject.toml') + pyproject = cli_bootstrap.pyproject_load( + self.projects[project_name].source_dir / 'pyproject.toml' + ) for third_party_root in pyproject.third_party_roots: if third_party_root.package: if not third_party_root.module_root: - third_party_root.module_root = third_party_root.package.replace('.', os.path.sep) + third_party_root.module_root = ( + third_party_root.package.replace('.', os.path.sep) + ) if not third_party_root.path: packages = pip_show([third_party_root.package]) assert len(packages) == 1 - third_party_root.path = str(pathlib.Path(packages[0].location) / third_party_root.module_root / 'lib') + third_party_root.path = str( + pathlib.Path(packages[0].location) + / third_party_root.module_root + / 'lib' + ) else: - assert not third_party_root.package and not third_party_root.module_root and third_party_root.path + assert ( + not third_party_root.package + and not third_party_root.module_root + and third_party_root.path + ) res.append(pathlib.Path(third_party_root.path)) @@ -616,8 +665,12 @@ class CLI(abc.ABC): path: Optional[pathlib.Path] = None @property - def meson_toolchains(self) -> dict[str, meson_toolchains_t.res_t.toolchain_t]: - t1 = pathlib.Path(importlib.import_module('online.fxreader.pr34').__path__[0]) + def meson_toolchains( + self, + ) -> dict[str, meson_toolchains_t.res_t.toolchain_t]: + t1 = pathlib.Path( + importlib.import_module('online.fxreader.pr34').__path__[0] + ) toolchains = glob.glob(str(t1 / 'meson' / 'toolchains' / '*')) res: dict[str, CLI.meson_toolchains_t.res_t.toolchain_t] = dict() @@ -642,7 +695,11 @@ class CLI(abc.ABC): ) -> list[str]: from . import argparse as pr34_argparse - if pyproject_tool.meson and pyproject_tool.meson.args and pyproject_tool.meson.args.setup: + if ( + pyproject_tool.meson + and pyproject_tool.meson.args + and pyproject_tool.meson.args.setup + ): extra_args = pyproject_tool.meson.args.setup + extra_args parser = argparse.ArgumentParser() @@ -657,8 +714,13 @@ class CLI(abc.ABC): options, args = pr34_argparse.parse_args(parser, extra_args) if not options.cross_file is None: - if not options.cross_file.exists() and (not options.cross_file.is_absolute() and options.cross_file.stem in self.meson_toolchains): - options.cross_file = self.meson_toolchains[options.cross_file.stem].path + if not options.cross_file.exists() and ( + not options.cross_file.is_absolute() + and options.cross_file.stem in self.meson_toolchains + ): + options.cross_file = self.meson_toolchains[ + options.cross_file.stem + ].path extra_args = ['--cross-file', str(options.cross_file)] + args @@ -687,15 +749,26 @@ class CLI(abc.ABC): if env is None: env = dict() - pyproject = cli_bootstrap.pyproject_load(project.source_dir / 'pyproject.toml') + pyproject = cli_bootstrap.pyproject_load( + project.source_dir / 'pyproject.toml' + ) - pyproject_tool = pydantic.RootModel[PyProject.Tool].model_validate(pyproject.tool).root + pyproject_tool = ( + pydantic.RootModel[PyProject.Tool] + .model_validate(pyproject.tool) + .root + ) logger.info(dict(env=env)) if force: if (project.build_dir / mode).exists(): - logger.info(dict(action='removing build dir', path=project.build_dir / mode)) + logger.info( + dict( + action='removing build dir', + path=project.build_dir / mode, + ) + ) shutil.rmtree(project.build_dir / mode) extra_args: list[str] = [] @@ -706,7 +779,15 @@ class CLI(abc.ABC): ) if len(self.third_party_roots(project_name)) > 0: - extra_args.append('-Dthird_party_roots=%s' % json.dumps([str(o.absolute()) for o in self.third_party_roots(project_name)])) + extra_args.append( + '-Dthird_party_roots=%s' + % json.dumps( + [ + str(o.absolute()) + for o in self.third_party_roots(project_name) + ] + ) + ) cmd = [ # shutil_which( @@ -719,7 +800,9 @@ class CLI(abc.ABC): 'setup', str(project.source_dir), str(project.build_dir / mode), - '--pkg-config-path={}'.format(json.dumps([str(o) for o in self.pkg_config_path(project_name)])), + '--pkg-config-path={}'.format( + json.dumps([str(o) for o in self.pkg_config_path(project_name)]) + ), '-Dmodes=["{}"]'.format(mode), *extra_args, # '-Dpkgconfig.relocatable=true', @@ -769,14 +852,21 @@ class CLI(abc.ABC): argv, ) - pyproject = cli_bootstrap.pyproject_load(project.source_dir / 'pyproject.toml') + pyproject = cli_bootstrap.pyproject_load( + project.source_dir / 'pyproject.toml' + ) - dependencies = sum([pyproject.dependencies[o] for o in options.features], cast(list[str], [])) + dependencies = sum( + [pyproject.dependencies[o] for o in options.features], + cast(list[str], []), + ) pip_find_links: list[pathlib.Path] = [] if not pyproject.pip_find_links is None: - pip_find_links.extend([o for o in pyproject.pip_find_links if o.exists()]) + pip_find_links.extend( + [o for o in pyproject.pip_find_links if o.exists()] + ) requirements_name_get_res = cli_bootstrap.requirements_name_get( source_dir=project.source_dir, @@ -885,7 +975,9 @@ class CLI(abc.ABC): assert options.module in [o.name for o in pyproject.modules] - modules: dict[str, cli_bootstrap.PyProject.Module] = {o.name: o for o in pyproject.modules} + modules: dict[str, cli_bootstrap.PyProject.Module] = { + o.name: o for o in pyproject.modules + } module = modules[options.module] diff --git a/python/online/fxreader/pr34/commands_typed/cli_bootstrap.py b/python/online/fxreader/pr34/commands_typed/cli_bootstrap.py index c1b4374..3b30e4a 100644 --- a/python/online/fxreader/pr34/commands_typed/cli_bootstrap.py +++ b/python/online/fxreader/pr34/commands_typed/cli_bootstrap.py @@ -78,7 +78,9 @@ class PyProject: third_party_roots: list[ThirdPartyRoot] = dataclasses.field( default_factory=lambda: [], ) - requirements: dict[str, pathlib.Path] = dataclasses.field(default_factory=lambda: dict()) + requirements: dict[str, pathlib.Path] = dataclasses.field( + default_factory=lambda: dict() + ) modules: list[Module] = dataclasses.field( default_factory=lambda: [], @@ -124,7 +126,12 @@ def check_dict( else: VT_class = VT - assert all([isinstance(k, KT) and (VT_class is None or isinstance(v, VT_class)) for k, v in value2.items()]) + assert all( + [ + isinstance(k, KT) and (VT_class is None or isinstance(v, VT_class)) + for k, v in value2.items() + ] + ) if VT is None: return cast( @@ -233,7 +240,12 @@ def pyproject_load( str, ) - if 'tool' in content and isinstance(content['tool'], dict) and tool_name in content['tool'] and isinstance(content['tool'][tool_name], dict): + if ( + 'tool' in content + and isinstance(content['tool'], dict) + and tool_name in content['tool'] + and isinstance(content['tool'][tool_name], dict) + ): pr34_tool = check_dict( check_dict( content['tool'], @@ -246,7 +258,9 @@ def pyproject_load( res.early_features = pr34_tool['early_features'] if 'pip_find_links' in pr34_tool: - res.pip_find_links = [d.parent / pathlib.Path(o) for o in pr34_tool['pip_find_links']] + res.pip_find_links = [ + d.parent / pathlib.Path(o) for o in pr34_tool['pip_find_links'] + ] if 'runtime_libdirs' in pr34_tool: res.runtime_libdirs = [ @@ -265,7 +279,9 @@ def pyproject_load( if 'third_party_roots' in pr34_tool: for o in check_list(pr34_tool['third_party_roots']): o2 = check_dict(o, str, str) - assert all([k in {'package', 'module_root', 'path'} for k in o2]) + assert all( + [k in {'package', 'module_root', 'path'} for k in o2] + ) res.third_party_roots.append( PyProject.ThirdPartyRoot( @@ -279,7 +295,9 @@ def pyproject_load( res.requirements = { k: d.parent / pathlib.Path(v) # pathlib.Path(o) - for k, v in check_dict(pr34_tool['requirements'], str, str).items() + for k, v in check_dict( + pr34_tool['requirements'], str, str + ).items() } if 'modules' in pr34_tool: @@ -329,7 +347,10 @@ class BootstrapSettings: ).strip() ) pip_check_conflicts: Optional[bool] = dataclasses.field( - default_factory=lambda: os.environ.get('PIP_CHECK_CONFLICTS', json.dumps(True)) in [json.dumps(True)], + default_factory=lambda: os.environ.get( + 'PIP_CHECK_CONFLICTS', json.dumps(True) + ) + in [json.dumps(True)], ) uv_args: list[str] = dataclasses.field( default_factory=lambda: os.environ.get( @@ -394,7 +415,9 @@ def requirements_name_get( else: requirements_path = source_dir / 'requirements.txt' - requirements_path_in = requirements_path.parent / (requirements_path.stem + '.in') + requirements_path_in = requirements_path.parent / ( + requirements_path.stem + '.in' + ) requirements_in: list[str] = [] @@ -440,10 +463,15 @@ def env_bootstrap( requirements_in: list[str] = [] - requirements_in.extend(['uv', 'pip', 'build', 'setuptools', 'meson-python', 'pybind11']) + requirements_in.extend( + ['uv', 'pip', 'build', 'setuptools', 'meson-python', 'pybind11'] + ) if pyproject.early_features: - early_dependencies = sum([pyproject.dependencies[o] for o in pyproject.early_features], cast(list[str], [])) + early_dependencies = sum( + [pyproject.dependencies[o] for o in pyproject.early_features], + cast(list[str], []), + ) logger.info( dict( @@ -532,7 +560,11 @@ def env_bootstrap( subprocess.check_call( [ 'uv', - *[o for o in bootstrap_settings.uv_args if not o in ['-U', '--upgrade']], + *[ + o + for o in bootstrap_settings.uv_args + if not o in ['-U', '--upgrade'] + ], 'venv', *venv_python_version, *cache_find_links_args, diff --git a/python/online/fxreader/pr34/commands_typed/color_scheme.py b/python/online/fxreader/pr34/commands_typed/color_scheme.py index a71f612..13106f5 100644 --- a/python/online/fxreader/pr34/commands_typed/color_scheme.py +++ b/python/online/fxreader/pr34/commands_typed/color_scheme.py @@ -58,9 +58,25 @@ def run(argv: list[str]) -> None: def set_theme(theme: Literal['light', 'dark', 'default']) -> None: if theme == 'light': - subprocess.check_call(['gsettings', 'set', 'org.gnome.desktop.interface', 'color-scheme', 'prefer-light']) + subprocess.check_call( + [ + 'gsettings', + 'set', + 'org.gnome.desktop.interface', + 'color-scheme', + 'prefer-light', + ] + ) elif theme == 'dark': - subprocess.check_call(['gsettings', 'set', 'org.gnome.desktop.interface', 'color-scheme', 'prefer-dark']) + subprocess.check_call( + [ + 'gsettings', + 'set', + 'org.gnome.desktop.interface', + 'color-scheme', + 'prefer-dark', + ] + ) elif theme == 'default': subprocess.check_call( [ diff --git a/python/online/fxreader/pr34/commands_typed/crypto.py b/python/online/fxreader/pr34/commands_typed/crypto.py index 327d1cb..aeac872 100644 --- a/python/online/fxreader/pr34/commands_typed/crypto.py +++ b/python/online/fxreader/pr34/commands_typed/crypto.py @@ -64,7 +64,9 @@ class PasswordUtils: raise NotImplementedError @classmethod - def _scrypt_init(cls, salt: bytes) -> cryptography.hazmat.primitives.kdf.scrypt.Scrypt: + def _scrypt_init( + cls, salt: bytes + ) -> cryptography.hazmat.primitives.kdf.scrypt.Scrypt: return cryptography.hazmat.primitives.kdf.scrypt.Scrypt( salt=salt, length=32, diff --git a/python/online/fxreader/pr34/commands_typed/logging.py b/python/online/fxreader/pr34/commands_typed/logging.py index afdd4c1..abc4bf8 100644 --- a/python/online/fxreader/pr34/commands_typed/logging.py +++ b/python/online/fxreader/pr34/commands_typed/logging.py @@ -10,5 +10,7 @@ def setup(level: Optional[int] = None) -> None: logging.basicConfig( level=level, - format=('%(levelname)s:%(name)s:%(message)s:%(process)d:%(asctime)s:%(pathname)s:%(funcName)s:%(lineno)s'), + format=( + '%(levelname)s:%(name)s:%(message)s:%(process)d:%(asctime)s:%(pathname)s:%(funcName)s:%(lineno)s' + ), ) diff --git a/python/online/fxreader/pr34/commands_typed/metrics.py b/python/online/fxreader/pr34/commands_typed/metrics.py index 7c6881e..c98cae1 100644 --- a/python/online/fxreader/pr34/commands_typed/metrics.py +++ b/python/online/fxreader/pr34/commands_typed/metrics.py @@ -47,7 +47,15 @@ class Metric(pydantic.BaseModel): if o.type == 'gauge': samples.append( - Metric.Sample(parameters=s.parameters, value='NaN', timestamp=(s.timestamp + datetime.timedelta(seconds=15) if s.timestamp else None)) + Metric.Sample( + parameters=s.parameters, + value='NaN', + timestamp=( + s.timestamp + datetime.timedelta(seconds=15) + if s.timestamp + else None + ), + ) ) return ''.join( @@ -65,7 +73,11 @@ class Metric(pydantic.BaseModel): ] ), value=s2.value, - timestamp=('%.f' % (s2.timestamp.timestamp() * 1000,) if s2.timestamp else ''), + timestamp=( + '%.f' % (s2.timestamp.timestamp() * 1000,) + if s2.timestamp + else '' + ), ) for s2 in samples ] @@ -87,9 +99,19 @@ def serialize( '{help}{type}{samples}'.format( # help='# HELP %s some metric' % o.name, # type='# TYPE %s counter' % o.name, - help=('# HELP {0} {1}\n'.format(o.name, o.help) if o.help else ''), - type=('# TYPE {0} {1}\n'.format(o.name, o.type) if o.type else ''), - samples=''.join([Metric.sample_serialize(o, s) for s in o.samples]), + help=( + '# HELP {0} {1}\n'.format(o.name, o.help) + if o.help + else '' + ), + type=( + '# TYPE {0} {1}\n'.format(o.name, o.type) + if o.type + else '' + ), + samples=''.join( + [Metric.sample_serialize(o, s) for s in o.samples] + ), ) for o in metrics if len(o.samples) > 0 diff --git a/python/online/fxreader/pr34/commands_typed/mypy.py b/python/online/fxreader/pr34/commands_typed/mypy.py index 82bb7d8..91de680 100644 --- a/python/online/fxreader/pr34/commands_typed/mypy.py +++ b/python/online/fxreader/pr34/commands_typed/mypy.py @@ -38,7 +38,9 @@ class MypyFormatEntry: class MypyFormat: - vscode: ClassVar[MypyFormatEntry] = MypyFormatEntry(name='vscode', value='vscode') + vscode: ClassVar[MypyFormatEntry] = MypyFormatEntry( + name='vscode', value='vscode' + ) json: ClassVar[MypyFormatEntry] = MypyFormatEntry(name='json', value='json') @classmethod @@ -149,7 +151,11 @@ def run( assert not res.returncode is None errors = sorted( - [json.loads(o) for o in res.stdout.decode('utf-8').splitlines() if not o.strip() == ''], + [ + json.loads(o) + for o in res.stdout.decode('utf-8').splitlines() + if not o.strip() == '' + ], key=lambda x: ( x.get('file', ''), x.get('line', 0), diff --git a/python/online/fxreader/pr34/commands_typed/os.py b/python/online/fxreader/pr34/commands_typed/os.py index 4dc2b9b..13a646d 100644 --- a/python/online/fxreader/pr34/commands_typed/os.py +++ b/python/online/fxreader/pr34/commands_typed/os.py @@ -54,8 +54,21 @@ def runtime_libdirs_init( ld_library_path: list[pathlib.Path] = [ o for o in [ - *[o.absolute() for o in (project.runtime_libdirs if project.runtime_libdirs else [])], - *[pathlib.Path(o) for o in os.environ.get('LD_LIBRARY_PATH', '').split(os.path.pathsep) if o != ''], + *[ + o.absolute() + for o in ( + project.runtime_libdirs + if project.runtime_libdirs + else [] + ) + ], + *[ + pathlib.Path(o) + for o in os.environ.get('LD_LIBRARY_PATH', '').split( + os.path.pathsep + ) + if o != '' + ], ] ] @@ -72,10 +85,16 @@ def runtime_libdirs_init( ld_library_path_present.append(o) - os.environ.update(LD_LIBRARY_PATH=os.path.pathsep.join([str(o) for o in ld_library_path_present])) + os.environ.update( + LD_LIBRARY_PATH=os.path.pathsep.join( + [str(o) for o in ld_library_path_present] + ) + ) for preload_path in project.runtime_preload or []: - for preload_found in glob.glob(str(preload_path.parent / ('lib%s.so' % preload_path.name))): + for preload_found in glob.glob( + str(preload_path.parent / ('lib%s.so' % preload_path.name)) + ): logger.info( dict( preload_path=preload_path, diff --git a/python/online/fxreader/pr34/commands_typed/pip.py b/python/online/fxreader/pr34/commands_typed/pip.py index c958fb9..9fd1ab3 100644 --- a/python/online/fxreader/pr34/commands_typed/pip.py +++ b/python/online/fxreader/pr34/commands_typed/pip.py @@ -101,8 +101,20 @@ class pip_resolve_t: entries: Optional[list[download_info_t]] = None -def pip_resolve_entries_to_txt(entries: list[pip_resolve_t.res_t.download_info_t]) -> str: - return '\n'.join(['#%s\n%s %s' % (o.url, o.constraint, ' '.join(['--hash=sha256:%s' % o2 for o2 in o.sha256])) for o in entries]) +def pip_resolve_entries_to_txt( + entries: list[pip_resolve_t.res_t.download_info_t], +) -> str: + return '\n'.join( + [ + '#%s\n%s %s' + % ( + o.url, + o.constraint, + ' '.join(['--hash=sha256:%s' % o2 for o2 in o.sha256]), + ) + for o in entries + ] + ) def pip_resolve( @@ -128,7 +140,9 @@ def pip_resolve( import pip._internal.models.direct_url with contextlib.ExitStack() as stack: - stack.enter_context(pip._internal.utils.temp_dir.global_tempdir_manager()) + stack.enter_context( + pip._internal.utils.temp_dir.global_tempdir_manager() + ) t2 = pip._internal.cli.main_parser.create_main_parser() @@ -166,15 +180,22 @@ def pip_resolve( pip._internal.cli.cmdoptions.check_dist_restriction(options) # t1._in_main_context = True session = t1.get_default_session(options) - target_python = pip._internal.cli.cmdoptions.make_target_python(options) - finder = cast(pip_resolve_t.build_package_finder_t, getattr(t1, '_build_package_finder'))( + target_python = pip._internal.cli.cmdoptions.make_target_python( + options + ) + finder = cast( + pip_resolve_t.build_package_finder_t, + getattr(t1, '_build_package_finder'), + )( options=options, session=session, target_python=target_python, ignore_requires_python=options.ignore_requires_python, ) - build_tracker = t1.enter_context(pip._internal.operations.build.build_tracker.get_build_tracker()) + build_tracker = t1.enter_context( + pip._internal.operations.build.build_tracker.get_build_tracker() + ) reqs = t1.get_requirements( [ #'pip', 'uv', 'ipython', @@ -184,8 +205,12 @@ def pip_resolve( finder, session, ) - pip._internal.req.req_install.check_legacy_setup_py_options(options, reqs) - directory = pip._internal.utils.temp_dir.TempDirectory(delete=True, kind='download', globally_managed=True) + pip._internal.req.req_install.check_legacy_setup_py_options( + options, reqs + ) + directory = pip._internal.utils.temp_dir.TempDirectory( + delete=True, kind='download', globally_managed=True + ) preparer = t1.make_requirement_preparer( temp_build_dir=directory, options=options, @@ -205,7 +230,9 @@ def pip_resolve( py_version_info=options.python_version, ) t1.trace_basic_info(finder) - requirement_set = resolver.resolve(reqs, check_supported_wheels=True) + requirement_set = resolver.resolve( + reqs, check_supported_wheels=True + ) res = pip_resolve_t.res_t() @@ -279,7 +306,9 @@ def pip_resolve( location, ) - batch_downloader_call_def = pip._internal.network.download.BatchDownloader.__call__ + batch_downloader_call_def = ( + pip._internal.network.download.BatchDownloader.__call__ + ) def batch_downloader_call( _self: pip._internal.network.download.BatchDownloader, @@ -298,7 +327,9 @@ def pip_resolve( return [(o, ('/dev/null', '')) for o in links] # base_resolver_resolve_def = pip._internal.resolution.base.BaseResolver.resolve - base_resolver_resolve_def = pip._internal.resolution.resolvelib.resolver.Resolver.resolve + base_resolver_resolve_def = ( + pip._internal.resolution.resolvelib.resolver.Resolver.resolve + ) result_requirements: list[RequirementSet | InstallRequirement] = [] @@ -309,7 +340,9 @@ def pip_resolve( ) -> RequirementSet: # print(args, kwargs) - res = base_resolver_resolve_def(_self, root_reqs, check_supported_wheels) + res = base_resolver_resolve_def( + _self, root_reqs, check_supported_wheels + ) result_requirements.append(res) raise NotImplementedError @@ -369,7 +402,13 @@ def pip_resolve( patches: list[Any] = [] - patches.append(unittest.mock.patch.object(pip._internal.network.download.Downloader, '__call__', downloader_call)) + patches.append( + unittest.mock.patch.object( + pip._internal.network.download.Downloader, + '__call__', + downloader_call, + ) + ) # patches.append( # unittest.mock.patch.object( # pip._internal.network.download.BatchDownloader, @@ -574,4 +613,6 @@ def pip_check_conflicts( if line.strip() != '' ] - return pip_check_conflicts_t.res_t(status=('error' if len(duplicates) > 0 else 'ok'), duplicates=duplicates) + return pip_check_conflicts_t.res_t( + status=('error' if len(duplicates) > 0 else 'ok'), duplicates=duplicates + ) diff --git a/python/online/fxreader/pr34/commands_typed/pydantic.py b/python/online/fxreader/pr34/commands_typed/pydantic.py index c2f4291..6d86c9c 100644 --- a/python/online/fxreader/pr34/commands_typed/pydantic.py +++ b/python/online/fxreader/pr34/commands_typed/pydantic.py @@ -21,28 +21,36 @@ R = TypeVar('R') @overload -def validate_params(view: Callable[..., Awaitable[R]]) -> Callable[..., Awaitable[R]]: ... +def validate_params( + view: Callable[..., Awaitable[R]], +) -> Callable[..., Awaitable[R]]: ... @overload def validate_params(view: Callable[..., R]) -> Callable[..., R]: ... -def validate_params(view: Callable[..., Awaitable[R]] | Callable[..., R]) -> Callable[..., Awaitable[R]] | Callable[..., R]: +def validate_params( + view: Callable[..., Awaitable[R]] | Callable[..., R], +) -> Callable[..., Awaitable[R]] | Callable[..., R]: class Parameter: kind: Any annotation: Any - parameters = cast(Mapping[str, Parameter], inspect.signature(view).parameters) + parameters = cast( + Mapping[str, Parameter], inspect.signature(view).parameters + ) - positional_parameters: collections.OrderedDict[str, type[Any]] = collections.OrderedDict( - ( - (k, v.annotation) - for k, v in parameters.items() - if v.kind - in ( - inspect.Parameter.POSITIONAL_ONLY, - inspect.Parameter.POSITIONAL_OR_KEYWORD, + positional_parameters: collections.OrderedDict[str, type[Any]] = ( + collections.OrderedDict( + ( + (k, v.annotation) + for k, v in parameters.items() + if v.kind + in ( + inspect.Parameter.POSITIONAL_ONLY, + inspect.Parameter.POSITIONAL_OR_KEYWORD, + ) ) ) ) diff --git a/python/online/fxreader/pr34/commands_typed/status.py b/python/online/fxreader/pr34/commands_typed/status.py index 3ae2246..f3043e4 100644 --- a/python/online/fxreader/pr34/commands_typed/status.py +++ b/python/online/fxreader/pr34/commands_typed/status.py @@ -23,7 +23,12 @@ def run(argv: list[str]): def format_option(self, *args: Any, **kwargs: Any) -> Any: def f1(text: str, width: Optional[int]) -> list[str]: width = None - return '\n'.join([textwrap.fill('\t' + o, width, replace_whitespace=False) for o in text.splitlines()]).splitlines() + return '\n'.join( + [ + textwrap.fill('\t' + o, width, replace_whitespace=False) + for o in text.splitlines() + ] + ).splitlines() t1 = inspect.getsource(optparse.IndentedHelpFormatter.format_option) t2 = ( diff --git a/python/online/fxreader/pr34/oom_firefox.py b/python/online/fxreader/pr34/oom_firefox.py index d50eca6..5e6733c 100644 --- a/python/online/fxreader/pr34/oom_firefox.py +++ b/python/online/fxreader/pr34/oom_firefox.py @@ -74,7 +74,9 @@ class get_firefox_procs_ps_t: cmd: str -def get_firefox_procs_ps(slice_name=None) -> list[get_firefox_procs_ps_t.res_t.entry_t]: +def get_firefox_procs_ps( + slice_name=None, +) -> list[get_firefox_procs_ps_t.res_t.entry_t]: entries: dict[int, dict[str, Any]] = dict() for regex, columns in [ @@ -182,7 +184,11 @@ def is_main_firefox(p): return False -def kill_prioritized(procs: list['get_firefox_procs_ps_t.res_t.entry_t'], to_free_mb, low_priority_pids): +def kill_prioritized( + procs: list['get_firefox_procs_ps_t.res_t.entry_t'], + to_free_mb, + low_priority_pids, +): candidates = [] for p in procs: if is_main_firefox(p): @@ -224,7 +230,9 @@ def kill_prioritized(procs: list['get_firefox_procs_ps_t.res_t.entry_t'], to_fre # — systemd-run logic — -def launch_firefox_with_limits(base_cmd, memory_high, swap_max, extra_args, unit_name): +def launch_firefox_with_limits( + base_cmd, memory_high, swap_max, extra_args, unit_name +): cmd = [ 'systemd-run', '--user', @@ -250,7 +258,9 @@ def launch_firefox_with_limits(base_cmd, memory_high, swap_max, extra_args, unit def main(): - os.makedirs(pathlib.Path('~/.cache/oom_firefox/').expanduser(), exist_ok=True) + os.makedirs( + pathlib.Path('~/.cache/oom_firefox/').expanduser(), exist_ok=True + ) logging.basicConfig( level=logging.INFO, @@ -263,14 +273,50 @@ def main(): ], ) - parser = argparse.ArgumentParser(description='Firefox memory manager with slice + graceful shutdown') - parser.add_argument('--max-mb', type=float, required=True, help='Memory threshold in MB (used for killing logic & MemoryHigh)') - parser.add_argument('--kill-percent', type=float, default=70.0, help='If over max, kill until usage ≤ this percent of max') - parser.add_argument('--swap-max-mb', type=float, default=None, help='MemorySwapMax (MB) for the systemd scope') - parser.add_argument('--interval', type=float, default=1.0, help='Monitoring interval in seconds') - parser.add_argument('--unit-name', type=str, default='firefox-limited', help='Name for systemd transient unit') - parser.add_argument('--firefox-extra', action='append', default=[], help='Extra CLI args to pass to Firefox (can repeat)') - parser.add_argument('firefox_cmd', nargs=argparse.REMAINDER, help='Firefox command + args (if launching it)') + parser = argparse.ArgumentParser( + description='Firefox memory manager with slice + graceful shutdown' + ) + parser.add_argument( + '--max-mb', + type=float, + required=True, + help='Memory threshold in MB (used for killing logic & MemoryHigh)', + ) + parser.add_argument( + '--kill-percent', + type=float, + default=70.0, + help='If over max, kill until usage ≤ this percent of max', + ) + parser.add_argument( + '--swap-max-mb', + type=float, + default=None, + help='MemorySwapMax (MB) for the systemd scope', + ) + parser.add_argument( + '--interval', + type=float, + default=1.0, + help='Monitoring interval in seconds', + ) + parser.add_argument( + '--unit-name', + type=str, + default='firefox-limited', + help='Name for systemd transient unit', + ) + parser.add_argument( + '--firefox-extra', + action='append', + default=[], + help='Extra CLI args to pass to Firefox (can repeat)', + ) + parser.add_argument( + 'firefox_cmd', + nargs=argparse.REMAINDER, + help='Firefox command + args (if launching it)', + ) args = parser.parse_args() @@ -323,7 +369,9 @@ def main(): if total > limit: to_free = total - kill_to - killed, freed = kill_prioritized(procs, to_free, low_priority_pids) + killed, freed = kill_prioritized( + procs, to_free, low_priority_pids + ) lines.append(f'Killed: {killed}') lines.append(f'Freed ≈ {freed:.1f} MB') else: @@ -332,7 +380,11 @@ def main(): if firefox_proc and firefox_proc.poll() is not None: print('Firefox died — restarting …', file=sys.stderr) firefox_proc = launch_firefox_with_limits( - args.firefox_cmd, memory_high=args.max_mb, swap_max=args.swap_max_mb, extra_args=args.firefox_extra, unit_name=args.unit_name + args.firefox_cmd, + memory_high=args.max_mb, + swap_max=args.swap_max_mb, + extra_args=args.firefox_extra, + unit_name=args.unit_name, ) body.text = '\n'.join(lines) @@ -354,7 +406,14 @@ def main(): close_dialog() dialog = Dialog( - title='Enter low‑priority PIDs', body=ta, buttons=[Button(text='OK', handler=on_ok), Button(text='Cancel', handler=on_cancel)], width=60, modal=True + title='Enter low‑priority PIDs', + body=ta, + buttons=[ + Button(text='OK', handler=on_ok), + Button(text='Cancel', handler=on_cancel), + ], + width=60, + modal=True, ) f = Float(content=dialog, left=2, top=2) dialog_float[0] = f @@ -365,7 +424,13 @@ def main(): def on_close(): close_dialog() - dialog = Dialog(title=title, body=Label(text=message), buttons=[Button(text='Close', handler=on_close)], width=50, modal=True) + dialog = Dialog( + title=title, + body=Label(text=message), + buttons=[Button(text='Close', handler=on_close)], + width=50, + modal=True, + ) f = Float(content=dialog, left=4, top=4) dialog_float[0] = f root_floats.append(f) @@ -409,7 +474,15 @@ def main(): root = FloatContainer( content=HSplit( - [Frame(body, title='Firefox Memory Manager'), Window(height=1, content=FormattedTextControl('q=quit, m=PID, h=help, s=setting, a=about'))] + [ + Frame(body, title='Firefox Memory Manager'), + Window( + height=1, + content=FormattedTextControl( + 'q=quit, m=PID, h=help, s=setting, a=about' + ), + ), + ] ), floats=root_floats, modal=True, @@ -457,7 +530,9 @@ def main(): t.start() # refresh_body() - app.run(handle_sigint=True) # from prompt‑toolkit API :contentReference[oaicite:0]{index=0} + app.run( + handle_sigint=True + ) # from prompt‑toolkit API :contentReference[oaicite:0]{index=0} t.join() diff --git a/python/online/fxreader/pr34/tasks/ble.py b/python/online/fxreader/pr34/tasks/ble.py index 48aa22c..b7ba7e5 100644 --- a/python/online/fxreader/pr34/tasks/ble.py +++ b/python/online/fxreader/pr34/tasks/ble.py @@ -23,7 +23,13 @@ async def f2(device, timeout=None): async def f3(client): - t1 = [dict(service=o.__dict__, characteristics=[o2.__dict__ for o2 in o.characteristics]) for o in client.services] + t1 = [ + dict( + service=o.__dict__, + characteristics=[o2.__dict__ for o2 in o.characteristics], + ) + for o in client.services + ] return t1 @@ -43,7 +49,13 @@ async def f5( t5 = {i: o.details[0].name() for i, o in enumerate(t1)} - t2.extend([t1[k] for k, v in t5.items() if isinstance(v, str) and name_check(v)]) + t2.extend( + [ + t1[k] + for k, v in t5.items() + if isinstance(v, str) and name_check(v) + ] + ) else: t2.extend(t1) @@ -66,7 +78,9 @@ async def f4( assert name_check in [ 'watch fit', ] - name_check2 = lambda current_name: name_check.lower() in current_name.lower() + name_check2 = ( + lambda current_name: name_check.lower() in current_name.lower() + ) else: name_check2 = name_check diff --git a/python/online/fxreader/pr34/tasks/cython.py b/python/online/fxreader/pr34/tasks/cython.py index a0b4f3f..58b63d2 100644 --- a/python/online/fxreader/pr34/tasks/cython.py +++ b/python/online/fxreader/pr34/tasks/cython.py @@ -66,7 +66,13 @@ def build(content: str, module: M) -> M: # ) t1.run() - return cast(M, Cython.Build.Inline.load_dynamic('_%s' % sha256sum, glob.glob(str(output_dir / ('_%s*.so' % sha256sum)))[0])) + return cast( + M, + Cython.Build.Inline.load_dynamic( + '_%s' % sha256sum, + glob.glob(str(output_dir / ('_%s*.so' % sha256sum)))[0], + ), + ) raise NotImplementedError @@ -125,7 +131,9 @@ def mypyc_build(file_path: pathlib.Path) -> Any: # f.write(content) t1 = Cython.Build.Inline._get_build_extension() - t1.extensions = mypyc.build.mypycify([str(source_path)], target_dir=str(output_dir / 'build')) + t1.extensions = mypyc.build.mypycify( + [str(source_path)], target_dir=str(output_dir / 'build') + ) t1.build_temp = str(output_dir) t1.build_lib = str(lib_dir) # t2 = Cython.Build.Inline.Extension( @@ -147,7 +155,11 @@ def mypyc_build(file_path: pathlib.Path) -> Any: class Source: @staticmethod - def test2(_a: numpy.ndarray[Any, numpy.dtype[numpy.int64]], _id: numpy.dtype[numpy.int32] | int, T: float = 16) -> int: + def test2( + _a: numpy.ndarray[Any, numpy.dtype[numpy.int64]], + _id: numpy.dtype[numpy.int32] | int, + T: float = 16, + ) -> int: raise NotImplementedError @@ -243,7 +255,11 @@ def test_cython(N: int = 4, T: int = 16) -> None: def test_mypyc(N: int = 4, W: int = 35) -> None: - cython2 = mypyc_build((pathlib.Path(__file__).parent / 'cython2.py').relative_to(pathlib.Path.cwd())) + cython2 = mypyc_build( + (pathlib.Path(__file__).parent / 'cython2.py').relative_to( + pathlib.Path.cwd() + ) + ) # from .cython2 import fib diff --git a/python/online/fxreader/pr34/tasks/jigsaw_toxic.py b/python/online/fxreader/pr34/tasks/jigsaw_toxic.py index 2daac22..ea16158 100644 --- a/python/online/fxreader/pr34/tasks/jigsaw_toxic.py +++ b/python/online/fxreader/pr34/tasks/jigsaw_toxic.py @@ -73,8 +73,21 @@ def kernel_2(): from keras.layers.embeddings import Embedding from keras.layers.normalization import BatchNormalization from keras.utils import np_utils - from sklearn import preprocessing, decomposition, model_selection, metrics, pipeline - from keras.layers import GlobalMaxPooling1D, Conv1D, MaxPooling1D, Flatten, Bidirectional, SpatialDropout1D + from sklearn import ( + preprocessing, + decomposition, + model_selection, + metrics, + pipeline, + ) + from keras.layers import ( + GlobalMaxPooling1D, + Conv1D, + MaxPooling1D, + Flatten, + Bidirectional, + SpatialDropout1D, + ) from keras.preprocessing import sequence, text from keras.callbacks import EarlyStopping @@ -112,15 +125,25 @@ def kernel_2(): print('REPLICAS: ', strategy.num_replicas_in_sync) # %% [code] - train = pd.read_csv('/kaggle/input/jigsaw-multilingual-toxic-comment-classification/jigsaw-toxic-comment-train.csv') - validation = pd.read_csv('/kaggle/input/jigsaw-multilingual-toxic-comment-classification/validation.csv') - test = pd.read_csv('/kaggle/input/jigsaw-multilingual-toxic-comment-classification/test.csv') + train = pd.read_csv( + '/kaggle/input/jigsaw-multilingual-toxic-comment-classification/jigsaw-toxic-comment-train.csv' + ) + validation = pd.read_csv( + '/kaggle/input/jigsaw-multilingual-toxic-comment-classification/validation.csv' + ) + test = pd.read_csv( + '/kaggle/input/jigsaw-multilingual-toxic-comment-classification/test.csv' + ) # %% [markdown] # We will drop the other columns and approach this problem as a Binary Classification Problem and also we will have our exercise done on a smaller subsection of the dataset(only 12000 data points) to make it easier to train the models # %% [code] - train.drop(['severe_toxic', 'obscene', 'threat', 'insult', 'identity_hate'], axis=1, inplace=True) + train.drop( + ['severe_toxic', 'obscene', 'threat', 'insult', 'identity_hate'], + axis=1, + inplace=True, + ) # %% [code] train = train.loc[:12000, :] @@ -137,7 +160,12 @@ def kernel_2(): # %% [code] xtrain, xvalid, ytrain, yvalid = train_test_split( - train.comment_text.values, train.toxic.values, stratify=train.toxic.values, random_state=42, test_size=0.2, shuffle=True + train.comment_text.values, + train.toxic.values, + stratify=train.toxic.values, + random_state=42, + test_size=0.2, + shuffle=True, ) # %% [markdown] @@ -206,7 +234,9 @@ def kernel_2(): model.add(Embedding(len(word_index) + 1, 300, input_length=max_len)) model.add(SimpleRNN(100)) model.add(Dense(1, activation='sigmoid')) - model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) + model.compile( + loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'] + ) model.summary() @@ -253,7 +283,10 @@ def kernel_3( o_2['model'].load_weights('model.h5') else: o_2['model'].fit( - o_2['xtrain_pad'], o_2['ytrain'], nb_epoch=nb_epochs, batch_size=64 * o_2['strategy'].num_replicas_in_sync + o_2['xtrain_pad'], + o_2['ytrain'], + nb_epoch=nb_epochs, + batch_size=64 * o_2['strategy'].num_replicas_in_sync, ) # Multiplying by Strategy to run on TPU's o_2['model'].save_weights('model.h5') @@ -263,7 +296,9 @@ def kernel_3( # %% [code] scores_model = [] - scores_model.append({'Model': 'SimpleRNN', 'AUC_Score': roc_auc(scores, o_2['yvalid'])}) + scores_model.append( + {'Model': 'SimpleRNN', 'AUC_Score': roc_auc(scores, o_2['yvalid'])} + ) # %% [markdown] # ## Code Explanantion @@ -283,7 +318,12 @@ def kernel_4( import keras.preprocessing.sequence if input_texts is None: - input_texts = ['blahb blahb blah', 'Hello World!', 'This is very good!', 'A very non toxic comment! This is so polite and polished one!'] + input_texts = [ + 'blahb blahb blah', + 'Hello World!', + 'This is very good!', + 'A very non toxic comment! This is so polite and polished one!', + ] t6 = [] for o in input_texts: @@ -291,7 +331,9 @@ def kernel_4( t2 = o_2['token'].texts_to_sequences( [t1], ) - t3 = keras.preprocessing.sequence.pad_sequences(t2, maxlen=o_2['max_len']) + t3 = keras.preprocessing.sequence.pad_sequences( + t2, maxlen=o_2['max_len'] + ) t4 = o_2['model'].predict( t3, ) diff --git a/python/online/fxreader/pr34/tasks/mlb_player.py b/python/online/fxreader/pr34/tasks/mlb_player.py index 35701ef..a2d8fa3 100644 --- a/python/online/fxreader/pr34/tasks/mlb_player.py +++ b/python/online/fxreader/pr34/tasks/mlb_player.py @@ -42,12 +42,26 @@ def kernel_2( ): t1 = {} - for k in ['playerTwitterFollowers', 'teamTwitterFollowers', 'games', 'events']: + for k in [ + 'playerTwitterFollowers', + 'teamTwitterFollowers', + 'games', + 'events', + ]: t4 = '%s.nc' % k if not os.path.exists(t4): print('started %s' % t4) t2 = '/kaggle/input/mlb-player-digital-engagement-forecasting/train.csv' - t3 = pandas.DataFrame(sum([json.loads(o) for o in o_1['t3'][t2][k].values if isinstance(o, str)], [])).to_xarray() + t3 = pandas.DataFrame( + sum( + [ + json.loads(o) + for o in o_1['t3'][t2][k].values + if isinstance(o, str) + ], + [], + ) + ).to_xarray() t3.to_netcdf(t4) print('cached %s' % t4) @@ -55,7 +69,9 @@ def kernel_2( t5 = '%s-v2.nc' % k if not os.path.exists(t5): t2 = xarray.load_dataset(t4) - t3 = t2.sel(index=numpy.arange(2017653 - 10 * 1000, 2017653 + 1)) + t3 = t2.sel( + index=numpy.arange(2017653 - 10 * 1000, 2017653 + 1) + ) t3.to_netcdf(t5) t1[k] = xarray.load_dataset(t5) print('loaded %s' % t5) @@ -119,9 +135,15 @@ def kernel_3(should_exist=None): def kernel_4( o_3=None, ): - [print(o_3['t5']['events'].to_dataframe().iloc[k].to_json(indent=4)) for k in range(-10, -1)] + [ + print(o_3['t5']['events'].to_dataframe().iloc[k].to_json(indent=4)) + for k in range(-10, -1) + ] - [print(o_3['t5']['games'].to_dataframe().iloc[k].to_json(indent=4)) for k in range(-10, -1)] + [ + print(o_3['t5']['games'].to_dataframe().iloc[k].to_json(indent=4)) + for k in range(-10, -1) + ] t4 = 'https://www.youtube.com/watch?v=reaC7BHgL3M' @@ -264,7 +286,9 @@ def kernel_6( try: cap = cv2.VideoCapture(o) - fps = cap.get(cv2.CAP_PROP_FPS) # OpenCV2 version 2 used "CV_CAP_PROP_FPS" + fps = cap.get( + cv2.CAP_PROP_FPS + ) # OpenCV2 version 2 used "CV_CAP_PROP_FPS" frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) duration = frame_count / fps finally: @@ -454,15 +478,31 @@ def kernel_7( for k in layer: v = layer[k] if 'pool' in k: - layers += [nn.MaxPool2d(kernel_size=v[0], stride=v[1], padding=v[2])] + layers += [ + nn.MaxPool2d( + kernel_size=v[0], stride=v[1], padding=v[2] + ) + ] else: - conv2d = nn.Conv2d(in_channels=v[0], out_channels=v[1], kernel_size=v[2], stride=v[3], padding=v[4]) + conv2d = nn.Conv2d( + in_channels=v[0], + out_channels=v[1], + kernel_size=v[2], + stride=v[3], + padding=v[4], + ) layers += [conv2d, nn.ReLU(inplace=True)] layer = list(layer_dict[-1].keys()) k = layer[0] v = layer_dict[-1][k] - conv2d = nn.Conv2d(in_channels=v[0], out_channels=v[1], kernel_size=v[2], stride=v[3], padding=v[4]) + conv2d = nn.Conv2d( + in_channels=v[0], + out_channels=v[1], + kernel_size=v[2], + stride=v[3], + padding=v[4], + ) layers += [conv2d] return nn.Sequential(*layers) @@ -530,9 +570,19 @@ def kernel_7( for key in block: v = block[key] if 'pool' in key: - layers += [nn.MaxPool2d(kernel_size=v[0], stride=v[1], padding=v[2])] + layers += [ + nn.MaxPool2d( + kernel_size=v[0], stride=v[1], padding=v[2] + ) + ] else: - conv2d = nn.Conv2d(in_channels=v[0], out_channels=v[1], kernel_size=v[2], stride=v[3], padding=v[4]) + conv2d = nn.Conv2d( + in_channels=v[0], + out_channels=v[1], + kernel_size=v[2], + stride=v[3], + padding=v[4], + ) layers += [conv2d, nn.ReLU(inplace=True)] models = {'block_0': nn.Sequential(*layers)} @@ -543,16 +593,38 @@ def kernel_7( return PoseEstimation(models) - def get_paf_and_heatmap(model, img_raw, scale_search, param_stride=8, box_size=368): - multiplier = [scale * box_size / img_raw.shape[0] for scale in scale_search] + def get_paf_and_heatmap( + model, img_raw, scale_search, param_stride=8, box_size=368 + ): + multiplier = [ + scale * box_size / img_raw.shape[0] for scale in scale_search + ] - heatmap_avg = torch.zeros((len(multiplier), 19, img_raw.shape[0], img_raw.shape[1])).cuda() - paf_avg = torch.zeros((len(multiplier), 38, img_raw.shape[0], img_raw.shape[1])).cuda() + heatmap_avg = torch.zeros( + (len(multiplier), 19, img_raw.shape[0], img_raw.shape[1]) + ).cuda() + paf_avg = torch.zeros( + (len(multiplier), 38, img_raw.shape[0], img_raw.shape[1]) + ).cuda() for i, scale in enumerate(multiplier): - img_test = cv2.resize(img_raw, (0, 0), fx=scale, fy=scale, interpolation=cv2.INTER_CUBIC) - img_test_pad, pad = pad_right_down_corner(img_test, param_stride, param_stride) - img_test_pad = np.transpose(np.float32(img_test_pad[:, :, :, np.newaxis]), (3, 2, 0, 1)) / 256 - 0.5 + img_test = cv2.resize( + img_raw, + (0, 0), + fx=scale, + fy=scale, + interpolation=cv2.INTER_CUBIC, + ) + img_test_pad, pad = pad_right_down_corner( + img_test, param_stride, param_stride + ) + img_test_pad = ( + np.transpose( + np.float32(img_test_pad[:, :, :, np.newaxis]), (3, 2, 0, 1) + ) + / 256 + - 0.5 + ) feed = Variable(torch.from_numpy(img_test_pad)).cuda() output1, output2 = model(feed) @@ -560,17 +632,27 @@ def kernel_7( # print(output1.size()) # print(output2.size()) - heatmap = nn.UpsamplingBilinear2d((img_raw.shape[0], img_raw.shape[1])).cuda()(output2) + heatmap = nn.UpsamplingBilinear2d( + (img_raw.shape[0], img_raw.shape[1]) + ).cuda()(output2) - paf = nn.UpsamplingBilinear2d((img_raw.shape[0], img_raw.shape[1])).cuda()(output1) + paf = nn.UpsamplingBilinear2d( + (img_raw.shape[0], img_raw.shape[1]) + ).cuda()(output1) heatmap_avg[i] = heatmap[0].data paf_avg[i] = paf[0].data - heatmap_avg = torch.transpose(torch.transpose(torch.squeeze(torch.mean(heatmap_avg, 0)), 0, 1), 1, 2).cuda() + heatmap_avg = torch.transpose( + torch.transpose(torch.squeeze(torch.mean(heatmap_avg, 0)), 0, 1), + 1, + 2, + ).cuda() heatmap_avg = heatmap_avg.cpu().numpy() - paf_avg = torch.transpose(torch.transpose(torch.squeeze(torch.mean(paf_avg, 0)), 0, 1), 1, 2).cuda() + paf_avg = torch.transpose( + torch.transpose(torch.squeeze(torch.mean(paf_avg, 0)), 0, 1), 1, 2 + ).cuda() paf_avg = paf_avg.cpu().numpy() return paf_avg, heatmap_avg @@ -592,20 +674,34 @@ def kernel_7( map_down = np.zeros(map_gau.shape) map_down[:, :-1] = map_gau[:, 1:] - peaks_binary = np.logical_and.reduce((map_gau >= map_left, map_gau >= map_right, map_gau >= map_up, map_gau >= map_down, map_gau > param_thre1)) + peaks_binary = np.logical_and.reduce( + ( + map_gau >= map_left, + map_gau >= map_right, + map_gau >= map_up, + map_gau >= map_down, + map_gau > param_thre1, + ) + ) - peaks = zip(np.nonzero(peaks_binary)[1], np.nonzero(peaks_binary)[0]) # note reverse + peaks = zip( + np.nonzero(peaks_binary)[1], np.nonzero(peaks_binary)[0] + ) # note reverse peaks = list(peaks) peaks_with_score = [x + (map_ori[x[1], x[0]],) for x in peaks] ids = range(peak_counter, peak_counter + len(peaks)) - peaks_with_score_and_id = [peaks_with_score[i] + (ids[i],) for i in range(len(ids))] + peaks_with_score_and_id = [ + peaks_with_score[i] + (ids[i],) for i in range(len(ids)) + ] all_peaks.append(peaks_with_score_and_id) peak_counter += len(peaks) return all_peaks - def extract_paf_info(img_raw, paf_avg, all_peaks, param_thre2=0.05, param_thre3=0.5): + def extract_paf_info( + img_raw, paf_avg, all_peaks, param_thre2=0.05, param_thre3=0.5 + ): connection_all = [] special_k = [] mid_num = 10 @@ -626,27 +722,69 @@ def kernel_7( raise ZeroDivisionError vec = np.divide(vec, norm) - startend = zip(np.linspace(candA[i][0], candB[j][0], num=mid_num), np.linspace(candA[i][1], candB[j][1], num=mid_num)) + startend = zip( + np.linspace(candA[i][0], candB[j][0], num=mid_num), + np.linspace(candA[i][1], candB[j][1], num=mid_num), + ) startend = list(startend) - vec_x = np.array([score_mid[int(round(startend[I][1])), int(round(startend[I][0])), 0] for I in range(len(startend))]) - vec_y = np.array([score_mid[int(round(startend[I][1])), int(round(startend[I][0])), 1] for I in range(len(startend))]) + vec_x = np.array( + [ + score_mid[ + int(round(startend[I][1])), + int(round(startend[I][0])), + 0, + ] + for I in range(len(startend)) + ] + ) + vec_y = np.array( + [ + score_mid[ + int(round(startend[I][1])), + int(round(startend[I][0])), + 1, + ] + for I in range(len(startend)) + ] + ) - score_midpts = np.multiply(vec_x, vec[0]) + np.multiply(vec_y, vec[1]) - score_with_dist_prior = sum(score_midpts) / len(score_midpts) - score_with_dist_prior += min(0.5 * img_raw.shape[0] / norm - 1, 0) + score_midpts = np.multiply(vec_x, vec[0]) + np.multiply( + vec_y, vec[1] + ) + score_with_dist_prior = sum(score_midpts) / len( + score_midpts + ) + score_with_dist_prior += min( + 0.5 * img_raw.shape[0] / norm - 1, 0 + ) - criterion1 = len(np.nonzero(score_midpts > param_thre2)[0]) > 0.8 * len(score_midpts) + criterion1 = len( + np.nonzero(score_midpts > param_thre2)[0] + ) > 0.8 * len(score_midpts) criterion2 = score_with_dist_prior > 0 if criterion1 and criterion2: - connection_candidate.append([i, j, score_with_dist_prior, score_with_dist_prior + candA[i][2] + candB[j][2]]) + connection_candidate.append( + [ + i, + j, + score_with_dist_prior, + score_with_dist_prior + + candA[i][2] + + candB[j][2], + ] + ) - connection_candidate = sorted(connection_candidate, key=lambda x: x[2], reverse=True) + connection_candidate = sorted( + connection_candidate, key=lambda x: x[2], reverse=True + ) connection = np.zeros((0, 5)) for c in range(len(connection_candidate)): i, j, s = connection_candidate[c][0:3] if i not in connection[:, 3] and j not in connection[:, 4]: - connection = np.vstack([connection, [candA[i][3], candB[j][3], s, i, j]]) + connection = np.vstack( + [connection, [candA[i][3], candB[j][3], s, i, j]] + ) if len(connection) >= min(nA, nB): break @@ -661,7 +799,9 @@ def kernel_7( # last number in each row is the total parts number of that person # the second last number in each row is the score of the overall configuration subset = -1 * np.ones((0, 20)) - candidate = np.array([item for sublist in all_peaks for item in sublist]) + candidate = np.array( + [item for sublist in all_peaks for item in sublist] + ) for k in range(len(map_ids)): if k not in special_k: @@ -673,7 +813,10 @@ def kernel_7( found = 0 subset_idx = [-1, -1] for j in range(len(subset)): # 1:size(subset,1): - if subset[j][indexA] == partAs[i] or subset[j][indexB] == partBs[i]: + if ( + subset[j][indexA] == partAs[i] + or subset[j][indexB] == partBs[i] + ): subset_idx[found] = j found += 1 @@ -682,11 +825,17 @@ def kernel_7( if subset[j][indexB] != partBs[i]: subset[j][indexB] = partBs[i] subset[j][-1] += 1 - subset[j][-2] += candidate[partBs[i].astype(int), 2] + connection_all[k][i][2] + subset[j][-2] += ( + candidate[partBs[i].astype(int), 2] + + connection_all[k][i][2] + ) elif found == 2: # if found 2 and disjoint, merge them j1, j2 = subset_idx print('found = 2') - membership = ((subset[j1] >= 0).astype(int) + (subset[j2] >= 0).astype(int))[:-2] + membership = ( + (subset[j1] >= 0).astype(int) + + (subset[j2] >= 0).astype(int) + )[:-2] if len(np.nonzero(membership == 2)[0]) == 0: # merge subset[j1][:-2] += subset[j2][:-2] + 1 subset[j1][-2:] += subset[j2][-2:] @@ -695,7 +844,10 @@ def kernel_7( else: # as like found == 1 subset[j1][indexB] = partBs[i] subset[j1][-1] += 1 - subset[j1][-2] += candidate[partBs[i].astype(int), 2] + connection_all[k][i][2] + subset[j1][-2] += ( + candidate[partBs[i].astype(int), 2] + + connection_all[k][i][2] + ) # if find no partA in the subset, create a new subset elif not found and k < 17: @@ -703,7 +855,14 @@ def kernel_7( row[indexA] = partAs[i] row[indexB] = partBs[i] row[-1] = 2 - row[-2] = sum(candidate[connection_all[k][i, :2].astype(int), 2]) + connection_all[k][i][2] + row[-2] = ( + sum( + candidate[ + connection_all[k][i, :2].astype(int), 2 + ] + ) + + connection_all[k][i][2] + ) subset = np.vstack([subset, row]) return subset, candidate @@ -718,7 +877,9 @@ def kernel_7( for i in range(18): for j in range(len(all_peaks[i])): - cv2.circle(img_canvas, all_peaks[i][j][0:2], 4, colors[i], thickness=-1) + cv2.circle( + img_canvas, all_peaks[i][j][0:2], 4, colors[i], thickness=-1 + ) return subset, img_canvas @@ -735,9 +896,18 @@ def kernel_7( mY = np.mean(Y) length = ((X[0] - X[1]) ** 2 + (Y[0] - Y[1]) ** 2) ** 0.5 angle = math.degrees(math.atan2(X[0] - X[1], Y[0] - Y[1])) - polygon = cv2.ellipse2Poly((int(mY), int(mX)), (int(length / 2), stickwidth), int(angle), 0, 360, 1) + polygon = cv2.ellipse2Poly( + (int(mY), int(mX)), + (int(length / 2), stickwidth), + int(angle), + 0, + 360, + 1, + ) cv2.fillConvexPoly(cur_canvas, polygon, colors[i]) - img_canvas = cv2.addWeighted(img_canvas, 0.4, cur_canvas, 0.6, 0) + img_canvas = cv2.addWeighted( + img_canvas, 0.4, cur_canvas, 0.6, 0 + ) return img_canvas @@ -754,11 +924,17 @@ def kernel_7( img_padded = img pad_up = np.tile(img_padded[0:1, :, :] * 0 + pad_value, (pad[0], 1, 1)) img_padded = np.concatenate((pad_up, img_padded), axis=0) - pad_left = np.tile(img_padded[:, 0:1, :] * 0 + pad_value, (1, pad[1], 1)) + pad_left = np.tile( + img_padded[:, 0:1, :] * 0 + pad_value, (1, pad[1], 1) + ) img_padded = np.concatenate((pad_left, img_padded), axis=1) - pad_down = np.tile(img_padded[-2:-1, :, :] * 0 + pad_value, (pad[2], 1, 1)) + pad_down = np.tile( + img_padded[-2:-1, :, :] * 0 + pad_value, (pad[2], 1, 1) + ) img_padded = np.concatenate((img_padded, pad_down), axis=0) - pad_right = np.tile(img_padded[:, -2:-1, :] * 0 + pad_value, (1, pad[3], 1)) + pad_right = np.tile( + img_padded[:, -2:-1, :] * 0 + pad_value, (1, pad[3], 1) + ) img_padded = np.concatenate((img_padded, pad_right), axis=1) return img_padded, pad @@ -784,11 +960,15 @@ def kernel_7( # In[4]: - state_dict = torch.load(model)['state_dict'] # getting the pre-trained model's parameters + state_dict = torch.load(model)[ + 'state_dict' + ] # getting the pre-trained model's parameters # A state_dict is simply a Python dictionary object that maps each layer to its parameter tensor. model_pose = get_pose_model() # building the model (see fn. defn. above). To see the architecture, see below cell. - model_pose.load_state_dict(state_dict) # Loading the parameters (weights, biases) into the model. + model_pose.load_state_dict( + state_dict + ) # Loading the parameters (weights, biases) into the model. model_pose.float() # I'm not sure why this is used. No difference if you remove it. @@ -797,7 +977,9 @@ def kernel_7( if use_gpu: model_pose.cuda() - model_pose = torch.nn.DataParallel(model_pose, device_ids=range(torch.cuda.device_count())) + model_pose = torch.nn.DataParallel( + model_pose, device_ids=range(torch.cuda.device_count()) + ) cudnn.benchmark = True def estimate_pose( @@ -833,7 +1015,9 @@ def kernel_7( img_points = None try: - paf_info, heatmap_info = get_paf_and_heatmap(model_pose, img_ori, scale_param) + paf_info, heatmap_info = get_paf_and_heatmap( + model_pose, img_ori, scale_param + ) peaks = extract_heatmap_info(heatmap_info) sp_k, con_all = extract_paf_info(img_ori, paf_info, peaks) @@ -876,7 +1060,13 @@ def kernel_7( def kernel_8( o_7, ): - for i, o in enumerate(['../input/indonesian-traditional-dance/tgagrakanyar/tga_00%d0.jpg' % k for k in range(6)]): + for i, o in enumerate( + [ + '../input/indonesian-traditional-dance/tgagrakanyar/tga_00%d0.jpg' + % k + for k in range(6) + ] + ): arch_image = o img_ori = o_7['cv2'].imread(arch_image) o_7['estimate_pose'](img_ori) @@ -887,7 +1077,9 @@ def kernel_9_benchmark( ): import datetime - t1 = o_7['cv2'].imread('../input/indonesian-traditional-dance/tgagrakanyar/tga_0000.jpg') + t1 = o_7['cv2'].imread( + '../input/indonesian-traditional-dance/tgagrakanyar/tga_0000.jpg' + ) t5 = 10 t2 = datetime.datetime.now() for k in range(t5): @@ -905,7 +1097,9 @@ def kernel_10(): import torch # Model - model = torch.hub.load('ultralytics/yolov5', 'yolov5s') # or yolov5m, yolov5x, custom + model = torch.hub.load( + 'ultralytics/yolov5', 'yolov5s' + ) # or yolov5m, yolov5x, custom # Images img = 'https://ultralytics.com/images/zidane.jpg' # or file, PIL, OpenCV, numpy, multiple @@ -927,7 +1121,9 @@ def kernel_11_benchmark( ): import datetime - t1 = o_7['cv2'].imread('../input/indonesian-traditional-dance/tgagrakanyar/tga_0000.jpg') + t1 = o_7['cv2'].imread( + '../input/indonesian-traditional-dance/tgagrakanyar/tga_0000.jpg' + ) t5 = 10 t2 = datetime.datetime.now() for k in range(t5): @@ -956,7 +1152,18 @@ def kernel_13( if not len(t4) > 0 or not o_6 is None: t1 = pandas.concat( - sum([[o2['t11'][0].assign(frame_id=k, video_path=o['video_path']) for k, o2 in enumerate(o['frames'])] for o in o_6['t8']], []) + sum( + [ + [ + o2['t11'][0].assign( + frame_id=k, video_path=o['video_path'] + ) + for k, o2 in enumerate(o['frames']) + ] + for o in o_6['t8'] + ], + [], + ) ).to_xarray() t5 = t3[0] t1.to_netcdf(t5) @@ -1028,7 +1235,9 @@ def kernel_14( def kernel_15( o_14, ): - t1 = pandas.DataFrame(numpy.unique(o_14['o_13']['t1']['name'].data, return_counts=True)).T + t1 = pandas.DataFrame( + numpy.unique(o_14['o_13']['t1']['name'].data, return_counts=True) + ).T pprint.pprint( dict( t1=t1, @@ -1078,7 +1287,9 @@ def kernel_15( t12 = cv2.cvtColor(t11, cv2.COLOR_BGR2RGB) t13 = t12.copy() t15 = numpy.array([t8.xcenter, t8.ycenter, t8.width, t8.height]) - t16 = numpy.array([t13.shape[1], t13.shape[0], t13.shape[1], t13.shape[0]]) + t16 = numpy.array( + [t13.shape[1], t13.shape[0], t13.shape[1], t13.shape[0]] + ) t17 = t15 * t16 t18 = t17[:2] - t17[2:] / 2 t19 = t17[:2] + t17[2:] / 2 @@ -1340,7 +1551,10 @@ def kernel_20( t1 = numpy.array(o_18['t2']['t7'][0]['keypoints']).reshape(17, -1) t2 = o_18['t2']['t6'][0] t3 = o_18['t2']['t1'][0]['image_canvas'].copy() - assert o_18['t2']['t7'][0]['image_id'] == os.path.split(o_18['t2']['t1'][0]['image_name'])[1] + assert ( + o_18['t2']['t7'][0]['image_id'] + == os.path.split(o_18['t2']['t1'][0]['image_name'])[1] + ) for i, o2 in enumerate(o_21['p_color']): if i >= 17: @@ -1449,7 +1663,16 @@ def kernel_22(o_18): o_31 = kernel_31( image_id=[o['image_id'] for o in t1], - image_size=numpy.array([[list(o['image_canvas'].shape) for o in o_18['t2']['t1'] if o['image_name'] == t1[i]['image_id']][0] for i in range(len(t2))]), + image_size=numpy.array( + [ + [ + list(o['image_canvas'].shape) + for o in o_18['t2']['t1'] + if o['image_name'] == t1[i]['image_id'] + ][0] + for i in range(len(t2)) + ] + ), keypoints=numpy.stack(t2, axis=0), ) t12 = o_31['t12'] @@ -1558,7 +1781,11 @@ def kernel_25(images, delay=None): def kernel_26(o_18, image_name): - t1 = [i for i, o in enumerate(o_18['t2']['t1']) if o['image_name'] == image_name] + t1 = [ + i + for i, o in enumerate(o_18['t2']['t1']) + if o['image_name'] == image_name + ] assert len(t1) == 1 return t1[0] @@ -1580,7 +1807,11 @@ def kernel_23(o_18, o_22, ids=None): t9 = kernel_26(o_18=o_18, image_name=t3['image_name']) t4 = o_18['t2']['t1'][t9]['image_canvas'] t10 = o_18['t2']['t6'][t9] - t4 = [o['image_canvas'] for o in o_18['t2']['t1'] if o['image_name'] == t3['image_name']] + t4 = [ + o['image_canvas'] + for o in o_18['t2']['t1'] + if o['image_name'] == t3['image_name'] + ] assert len(t4) == 1 t5 = t4[0] t6 = kernel_24(t5, t3['keypoints']) @@ -1641,7 +1872,9 @@ def kernel_27(): """ % (t4, t2) if False: pprint.pprint([t4, t2, t6]) - with subprocess.Popen(t6, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as p: + with subprocess.Popen( + t6, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) as p: if False: pprint.pprint(p.communicate()) p.wait() @@ -1669,7 +1902,9 @@ def kernel_28( max_seconds = 999999 if video_path is None: - video_path = '/kaggle/working/ATL AT TOR - April 19, 2015-T0MUK91ZWys.mp4' + video_path = ( + '/kaggle/working/ATL AT TOR - April 19, 2015-T0MUK91ZWys.mp4' + ) t5 = video_path t3 = '/kaggle/working/kernel_28-output%s.dir' % video_id t13 = '/root/kernel_28-output.dir/tmp-slice' @@ -1679,7 +1914,9 @@ def kernel_28( try: cap = cv2.VideoCapture(t5) - fps = cap.get(cv2.CAP_PROP_FPS) # OpenCV2 version 2 used "CV_CAP_PROP_FPS" + fps = cap.get( + cv2.CAP_PROP_FPS + ) # OpenCV2 version 2 used "CV_CAP_PROP_FPS" frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) real_duration = frame_count / fps duration = min(real_duration, max_seconds) @@ -1739,7 +1976,9 @@ def kernel_28( t6, ] ) - with subprocess.Popen(t6, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as p: + with subprocess.Popen( + t6, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) as p: if False: pprint.pprint(p.communicate()) p.wait() @@ -1757,7 +1996,9 @@ def kernel_29( video_id = '' if video_path is None: - video_path = '/kaggle/working/ATL AT TOR - April 19, 2015-T0MUK91ZWys.mp4' + video_path = ( + '/kaggle/working/ATL AT TOR - April 19, 2015-T0MUK91ZWys.mp4' + ) assert os.path.exists(video_path) @@ -1771,7 +2012,13 @@ def kernel_29( t7 = [o for o in t6 if os.path.exists(o)] if len(t7) == 0: - t1 = [dict(data=json.load(io.open(o, 'r')), input_path=o) for o in glob.glob('/kaggle/working/kernel_28-output%s.dir/slice-*/*.json' % video_id)] + t1 = [ + dict(data=json.load(io.open(o, 'r')), input_path=o) + for o in glob.glob( + '/kaggle/working/kernel_28-output%s.dir/slice-*/*.json' + % video_id + ) + ] assert len(t1) > 0 @@ -1835,7 +2082,9 @@ def kernel_30( low_mean_conf = 0.6 if video_path is None: - video_path = '/kaggle/working/ATL AT TOR - April 19, 2015-T0MUK91ZWys.mp4' + video_path = ( + '/kaggle/working/ATL AT TOR - April 19, 2015-T0MUK91ZWys.mp4' + ) if max_frames is None: max_frames = 9999 @@ -2045,7 +2294,10 @@ def kernel_31(image_id, image_size, keypoints): ab = [a[0] - b[0], a[1] - b[1]] ab1 = [c[0] - d[0], c[1] - d[1]] - cos = abs(ab[0] * ab1[0] + ab[1] * ab1[1]) / (sqrt(ab[0] ** 2 + ab[1] ** 2) * sqrt(ab1[0] ** 2 + ab1[1] ** 2) + 1e-8) + cos = abs(ab[0] * ab1[0] + ab[1] * ab1[1]) / ( + sqrt(ab[0] ** 2 + ab[1] ** 2) * sqrt(ab1[0] ** 2 + ab1[1] ** 2) + + 1e-8 + ) ang = acos(cos) return ang * 180 / np.pi @@ -2204,7 +2456,11 @@ def kernel_33(): o_22 = kernel_22(o_18=o_18) import pandas - o_23 = kernel_23(o_18=o_18, o_22=o_22, ids=pandas.DataFrame(o_22['t4']).query('portion > 0.1').index.values) + o_23 = kernel_23( + o_18=o_18, + o_22=o_22, + ids=pandas.DataFrame(o_22['t4']).query('portion > 0.1').index.values, + ) o_27 = kernel_27() o_28 = kernel_28() o_29 = kernel_29() @@ -2273,7 +2529,9 @@ def kernel_36(): # import os from os.path import exists, join, basename, splitext - git_repo_url = 'https://github.com/CMU-Perceptual-Computing-Lab/openpose.git' + git_repo_url = ( + 'https://github.com/CMU-Perceptual-Computing-Lab/openpose.git' + ) project_name = splitext(basename(git_repo_url))[0] if 1 or not exists(project_name): @@ -2282,8 +2540,18 @@ def kernel_36(): print('install new CMake becaue of CUDA10') cmake_version = 'cmake-3.20.2-linux-x86_64.tar.gz' if not exists(cmake_version): - assert os.system(r"""!wget -q 'https://cmake.org/files/v3.20/{cmake_version}' """) == 0 - assert os.system(r"""!tar xfz {cmake_version} --strip-components=1 -C /usr/local """) == 0 + assert ( + os.system( + r"""!wget -q 'https://cmake.org/files/v3.20/{cmake_version}' """ + ) + == 0 + ) + assert ( + os.system( + r"""!tar xfz {cmake_version} --strip-components=1 -C /usr/local """ + ) + == 0 + ) print('clone openpose') assert os.system(r"""!git clone -q --depth 1 $git_repo_url """) == 0 @@ -2295,7 +2563,12 @@ def kernel_36(): == 0 ) print('build openpose') - assert os.system(r"""!cd openpose && rm -rf build || true && mkdir build && cd build && cmake .. && make -j`nproc` """) == 0 + assert ( + os.system( + r"""!cd openpose && rm -rf build || true && mkdir build && cd build && cmake .. && make -j`nproc` """ + ) + == 0 + ) """## From a Google Drive's folder""" @@ -2310,7 +2583,9 @@ def kernel_36(): print(filename) colab_video_path = folder_path + filename print(colab_video_path) - colab_openpose_video_path = colab_video_path.replace('.mp4', '') + '-openpose.mp4' + colab_openpose_video_path = ( + colab_video_path.replace('.mp4', '') + '-openpose.mp4' + ) print(colab_openpose_video_path) if not exists(colab_openpose_video_path): assert ( @@ -2325,9 +2600,16 @@ def kernel_36(): assert os.system(r"""!pip install youtube-dl """) == 0 youtube_id = '2021-05-07_22-00-55_UTC' - assert os.system(r"""!youtube-dl -f mp4 -o '/content/drive/My Drive/openpose/%(id)s.mp4' {youtube_id} """) == 0 + assert ( + os.system( + r"""!youtube-dl -f mp4 -o '/content/drive/My Drive/openpose/%(id)s.mp4' {youtube_id} """ + ) + == 0 + ) colab_video_path = '/content/drive/My Drive/openpose/' + youtube_id + '.mp4' - colab_openpose_video_path = colab_video_path.replace('.mp4', '') + '-openpose.mp4' + colab_openpose_video_path = ( + colab_video_path.replace('.mp4', '') + '-openpose.mp4' + ) assert ( os.system( @@ -2352,7 +2634,9 @@ def kernel_36(): # from os.path import exists, join, basename, splitext # colab_video_path = '/content/drive/My Drive/bachata.mp4' colab_video_path = '/content/output.mp4' - colab_openpose_video_path = colab_video_path.replace('.mp4', '') + '-openpose.mp4' + colab_openpose_video_path = ( + colab_video_path.replace('.mp4', '') + '-openpose.mp4' + ) assert ( os.system( From 904c77fc299a9ea61089525e2c814a8d24d56f01 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Thu, 4 Dec 2025 11:29:12 +0300 Subject: [PATCH 58/70] [+] update textwrap 1. do as in the source code, either COLUMNS env variable, otherwise 99999 for no wrap, instead of default 80 width; --- python/online/fxreader/pr34/commands_typed/status.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/python/online/fxreader/pr34/commands_typed/status.py b/python/online/fxreader/pr34/commands_typed/status.py index f3043e4..1d136c2 100644 --- a/python/online/fxreader/pr34/commands_typed/status.py +++ b/python/online/fxreader/pr34/commands_typed/status.py @@ -1,4 +1,5 @@ import sys +import os import io import json import subprocess @@ -21,8 +22,8 @@ def run(argv: list[str]): class c1(optparse.IndentedHelpFormatter): def format_option(self, *args: Any, **kwargs: Any) -> Any: - def f1(text: str, width: Optional[int]) -> list[str]: - width = None + def f1(text: str, width: int) -> list[str]: + # width = None return '\n'.join( [ textwrap.fill('\t' + o, width, replace_whitespace=False) @@ -45,7 +46,7 @@ def run(argv: list[str]): parser = optparse.OptionParser( formatter=c1( - width=None, + width=int(os.environ.get('COLUMNS', '9999999')), ), ) From 6410df332668bae6f9773e73c25ec7ebb986f1e9 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Thu, 4 Dec 2025 11:58:41 +0300 Subject: [PATCH 59/70] [+] update status 1. add repeat_interval; --- .../fxreader/pr34/commands_typed/status.py | 109 +++++++++++++----- ...ne_fxreader_pr34-0.1.5.43-py3-none-any.whl | 3 + 2 files changed, 82 insertions(+), 30 deletions(-) create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.43-py3-none-any.whl diff --git a/python/online/fxreader/pr34/commands_typed/status.py b/python/online/fxreader/pr34/commands_typed/status.py index 1d136c2..94d7977 100644 --- a/python/online/fxreader/pr34/commands_typed/status.py +++ b/python/online/fxreader/pr34/commands_typed/status.py @@ -1,5 +1,8 @@ import sys +import datetime +import time import os +import signal import io import json import subprocess @@ -17,7 +20,48 @@ from typing import ( logger = logging.getLogger(__name__) +def get_info( + sh: list[str], + timeout: int | float, +): + t1: list[str] = [] + + for sh_index, o in enumerate( + [ + *sh, + *[ + r""" + A=$(free -h | grep -P Mem: | grep -Po '[\w\.\d]+'); + echo -n $A | awk '{print $2, $7}'; + """, + r""" + date +'%Y-%m-%d %l:%M:%S %p'; + """, + ], + ] + ): + try: + t1.append( + subprocess.check_output( + o, + shell=True, + timeout=timeout, + ) + .decode('utf-8') + .strip() + ) + except Exception: + t1.append('fail %d' % sh_index) + + t3 = ' | '.join(t1).replace('\n\r', '') + + sys.stdout.write(t3) + sys.stdout.flush() + + def run(argv: list[str]): + logging.basicConfig(level=logging.INFO) + assert isinstance(argv, list) and all([isinstance(o, str) for o in argv]) class c1(optparse.IndentedHelpFormatter): @@ -81,6 +125,13 @@ def run(argv: list[str]): default=None, type=float, ) + add_option( + parser, + '--repeat_interval', + dest='repeat_interval', + default=None, + type=float, + ) add_option( parser, '--config', @@ -122,39 +173,37 @@ printf '% 3.0f%%' $(upower -d | grep -Po 'percentage:\\s+\\d+(\\.\\d+)?%' | grep options.sh.extend(config.get('sh', [])) - t1: list[str] = [] + last_ts = datetime.datetime.now() - for sh_index, o in enumerate( - [ - *options.sh, - *[ - r""" - A=$(free -h | grep -P Mem: | grep -Po '[\w\.\d]+'); - echo -n $A | awk '{print $2, $7}'; - """, - r""" - date +'%Y-%m-%d %l:%M:%S %p'; - """, - ], - ] - ): - try: - t1.append( - subprocess.check_output( - o, - shell=True, - timeout=timeout2, - ) - .decode('utf-8') - .strip() - ) - except Exception: - t1.append('fail %d' % sh_index) + shutdown: bool = False - t3 = ' | '.join(t1).replace('\n\r', '') + def on_signal(*args: Any, **kwargs: Any): + nonlocal shutdown - sys.stdout.write(t3) - sys.stdout.flush() + shutdown = True + + signal.signal(signal.SIGINT, on_signal) + signal.signal(signal.SIGTERM, on_signal) + + while not shutdown: + get_info( + timeout=timeout2, + sh=options.sh, + ) + + if not options.repeat_interval: + break + else: + sys.stdout.write('\n') + sys.stdout.flush() + + now_ts = datetime.datetime.now() + spent = (now_ts - last_ts).total_seconds() + + last_ts = last_ts + datetime.timedelta(seconds=options.repeat_interval) + + if spent < options.repeat_interval: + time.sleep(options.repeat_interval - spent) if __name__ == '__main__': diff --git a/releases/whl/online_fxreader_pr34-0.1.5.43-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.43-py3-none-any.whl new file mode 100644 index 0000000..af632a1 --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.43-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:faeea76f81f8658b0fa511c3d3d20480ce3f22ceac0318704fe304018cb966e9 +size 82857 From 8633bd072440270e1cdc953e0c63b4ffa5218640 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Thu, 4 Dec 2025 11:59:18 +0300 Subject: [PATCH 60/70] [+] update sway config --- .../sensitive-configs-2025-12-04T11:59:04+03:00.gpg | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 platform_dotfiles_gpg/ideapad_slim_3_15arp10/sensitive-configs-2025-12-04T11:59:04+03:00.gpg diff --git a/platform_dotfiles_gpg/ideapad_slim_3_15arp10/sensitive-configs-2025-12-04T11:59:04+03:00.gpg b/platform_dotfiles_gpg/ideapad_slim_3_15arp10/sensitive-configs-2025-12-04T11:59:04+03:00.gpg new file mode 100644 index 0000000..881d542 --- /dev/null +++ b/platform_dotfiles_gpg/ideapad_slim_3_15arp10/sensitive-configs-2025-12-04T11:59:04+03:00.gpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:884b1c2c0476faba695df7e90c0bd7ef6cb9241ea0e7f88bc73e046486cd142e +size 18999 From c0125914d2cf2595cb6f80ed0b2c7294d23c245c Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Tue, 9 Dec 2025 13:32:03 +0300 Subject: [PATCH 61/70] [+] update status 1. make sure it handles properly time jumps due to suspend for example; --- python/meson.build | 2 +- .../fxreader/pr34/commands_typed/status.py | 21 ++++++++++++++++--- ...ne_fxreader_pr34-0.1.5.44-py3-none-any.whl | 3 +++ 3 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.44-py3-none-any.whl diff --git a/python/meson.build b/python/meson.build index a5b3dad..e9f40ff 100644 --- a/python/meson.build +++ b/python/meson.build @@ -5,7 +5,7 @@ project( ).stdout().strip('\n'), # 'online.fxreader.uv', # ['c', 'cpp'], - version: '0.1.5.43', + version: '0.1.5.44', # default_options: [ # 'cpp_std=c++23', # # 'prefer_static=true', diff --git a/python/online/fxreader/pr34/commands_typed/status.py b/python/online/fxreader/pr34/commands_typed/status.py index 94d7977..b0737e5 100644 --- a/python/online/fxreader/pr34/commands_typed/status.py +++ b/python/online/fxreader/pr34/commands_typed/status.py @@ -197,10 +197,25 @@ printf '% 3.0f%%' $(upower -d | grep -Po 'percentage:\\s+\\d+(\\.\\d+)?%' | grep sys.stdout.write('\n') sys.stdout.flush() - now_ts = datetime.datetime.now() - spent = (now_ts - last_ts).total_seconds() + is_late = False - last_ts = last_ts + datetime.timedelta(seconds=options.repeat_interval) + new_ts = last_ts + + while True: + now_ts = datetime.datetime.now() + spent = (now_ts - last_ts).total_seconds() + + new_ts = last_ts + datetime.timedelta( + seconds=options.repeat_interval + ) + + if new_ts > now_ts: + if is_late: + last_ts = new_ts + break + else: + last_ts = new_ts + is_late = True if spent < options.repeat_interval: time.sleep(options.repeat_interval - spent) diff --git a/releases/whl/online_fxreader_pr34-0.1.5.44-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.44-py3-none-any.whl new file mode 100644 index 0000000..675a8be --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.44-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:07bcc23549d0486731a95c7a9905c0bfdeef3024df3c65492faa7c9c76d00b11 +size 82910 From 0c581d6f5c8e40106576a6db21e1139c19ea031c Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Fri, 12 Dec 2025 16:41:32 +0300 Subject: [PATCH 62/70] [+] update oom_firefox 1. add l keybinding to change in realtime cgroup max memory limit; 2. make sure keybindings are not working when a dialog is opened; 3. make sure the app correctly handles Ctrl+C; 4. improve logging format, include a timestamp and line locatin; --- python/meson.build | 2 +- python/online/fxreader/pr34/oom_firefox.py | 101 ++++++++++++++++-- python/pyproject.toml | 1 + ...ne_fxreader_pr34-0.1.5.45-py3-none-any.whl | 3 + ...ne_fxreader_pr34-0.1.5.47-py3-none-any.whl | 3 + ...ne_fxreader_pr34-0.1.5.48-py3-none-any.whl | 3 + ...ne_fxreader_pr34-0.1.5.49-py3-none-any.whl | 3 + ...ne_fxreader_pr34-0.1.5.50-py3-none-any.whl | 3 + ...ne_fxreader_pr34-0.1.5.51-py3-none-any.whl | 3 + ...ne_fxreader_pr34-0.1.5.52-py3-none-any.whl | 3 + ...ne_fxreader_pr34-0.1.5.53-py3-none-any.whl | 3 + ...ne_fxreader_pr34-0.1.5.54-py3-none-any.whl | 3 + 12 files changed, 121 insertions(+), 10 deletions(-) create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.45-py3-none-any.whl create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.47-py3-none-any.whl create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.48-py3-none-any.whl create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.49-py3-none-any.whl create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.50-py3-none-any.whl create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.51-py3-none-any.whl create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.52-py3-none-any.whl create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.53-py3-none-any.whl create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.54-py3-none-any.whl diff --git a/python/meson.build b/python/meson.build index e9f40ff..6a57b6f 100644 --- a/python/meson.build +++ b/python/meson.build @@ -5,7 +5,7 @@ project( ).stdout().strip('\n'), # 'online.fxreader.uv', # ['c', 'cpp'], - version: '0.1.5.44', + version: '0.1.5.54', # default_options: [ # 'cpp_std=c++23', # # 'prefer_static=true', diff --git a/python/online/fxreader/pr34/oom_firefox.py b/python/online/fxreader/pr34/oom_firefox.py index 5e6733c..9311c03 100644 --- a/python/online/fxreader/pr34/oom_firefox.py +++ b/python/online/fxreader/pr34/oom_firefox.py @@ -13,8 +13,9 @@ import re import sys from prompt_toolkit.application import Application -from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.key_binding import KeyBindings, ConditionalKeyBindings from prompt_toolkit.layout import Layout, HSplit, FloatContainer, Float +from prompt_toolkit.filters import Condition from prompt_toolkit.layout.containers import Window from prompt_toolkit.layout.controls import FormattedTextControl from prompt_toolkit.widgets import TextArea, Frame, Dialog, Button, Label @@ -23,6 +24,7 @@ from prompt_toolkit.styles import Style from typing import ( TypedDict, Any, + Optional, ) from collections import OrderedDict @@ -34,7 +36,7 @@ __created__ = '2025-11-21' # — Helper for cgroup / slice matching — -def get_cgroup_path(pid): +def get_cgroup_path(pid: int) -> Optional[str]: try: with open(f'/proc/{pid}/cgroup', 'r') as f: for line in f: @@ -264,6 +266,12 @@ def main(): logging.basicConfig( level=logging.INFO, + format=( + '%(asctime)s ' + '%(levelname)-8s ' + '%(filename)s:%(lineno)d ' + '%(funcName)s – %(message)s' + ), handlers=[ logging.handlers.RotatingFileHandler( pathlib.Path('~/.cache/oom_firefox/log').expanduser(), @@ -348,8 +356,8 @@ def main(): pass # app.exit() - # signal.signal(signal.SIGINT, lambda s, f: terminate()) - # signal.signal(signal.SIGTERM, lambda s, f: terminate()) + signal.signal(signal.SIGINT, lambda s, f: terminate()) + signal.signal(signal.SIGTERM, lambda s, f: terminate()) def refresh_body(): nonlocal firefox_proc @@ -397,8 +405,12 @@ def main(): def on_ok(): txt = ta.text - for m in re.finditer(r'\((\d+)\)', txt): + for m in re.finditer(r'\((\d+)[^\)\d]*\)', txt): low_priority_pids.add(int(m.group(1))) + for m in re.finditer(r'^\s*(\d+)\s*$', txt): + low_priority_pids.add(int(m.group(1))) + for m in re.finditer(r'^\s*-(\d+)\s*$', txt): + low_priority_pids.remove(int(m.group(1))) close_dialog() refresh_body() @@ -420,6 +432,63 @@ def main(): root_floats.append(f) app.layout.focus(ta) + def change_max_mb(max_mb: int) -> None: + for cmd in ( + [ + 'systemctl', + '--user', + 'set-property', + '%s.scope' % args.unit_name, + 'MemoryHigh=%dM' % max_mb, + ], + [ + 'systemctl', + '--user', + 'set-property', + '%s.scope' % args.unit_name, + 'MemoryMax=%dM' % (max_mb * 1.1), + ], + ): + logger.info(dict(cmd=cmd)) + + subprocess.check_call(cmd) + + args.max_mb = max_mb + + def open_limit_dialog(): + ta = TextArea(text='', multiline=True, scrollbar=True) + + def on_ok(): + txt = ta.text + m = re.compile(r'^\s*(\d+)\s*$').match(txt) + + a: str = 234234 + if m: + change_max_mb(int(m[1])) + + close_dialog() + refresh_body() + else: + logger.error('invalid input %s' % txt) + + def on_cancel(): + close_dialog() + + dialog = Dialog( + title='Enter maximum memory threshold in MB', + body=ta, + buttons=[ + Button(text='OK', handler=on_ok), + Button(text='Cancel', handler=on_cancel), + ], + width=60, + modal=True, + ) + f = Float(content=dialog, left=2, top=2) + dialog_float[0] = f + root_floats.append(f) + app.layout.focus(ta) + def open_message(title, message): def on_close(): close_dialog() @@ -445,6 +514,11 @@ def main(): kb = KeyBindings() + gkb = ConditionalKeyBindings( + key_bindings=kb, + filter=Condition(lambda: dialog_float[0] is None), + ) + @kb.add('q') def _(event): terminate() @@ -453,9 +527,18 @@ def main(): def _(event): open_pid_dialog() + @kb.add('l') + def _(event): + open_limit_dialog() + + HELP_TEXT = 'm=add PIDs, l=change limit, s=settings, a=about, q=quit' + @kb.add('h') def _(event): - open_message('Help', 'Keys: m=add PIDs, s=settings, a=about, q=quit') + open_message( + 'Help', + 'Keys: %s' % HELP_TEXT, + ) @kb.add('s') def _(event): @@ -479,7 +562,7 @@ def main(): Window( height=1, content=FormattedTextControl( - 'q=quit, m=PID, h=help, s=setting, a=about' + HELP_TEXT, ), ), ] @@ -498,7 +581,7 @@ def main(): app = Application( layout=Layout(root), - key_bindings=kb, + key_bindings=gkb, style=style, full_screen=True, refresh_interval=args.interval, @@ -531,7 +614,7 @@ def main(): # refresh_body() app.run( - handle_sigint=True + # handle_sigint=True ) # from prompt‑toolkit API :contentReference[oaicite:0]{index=0} t.join() diff --git a/python/pyproject.toml b/python/pyproject.toml index badcd38..3b929a2 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -144,6 +144,7 @@ include = [ #'../../../../../follow_the_leader/logic/payments.py', #'../../../../../follow_the_leader/logic/paypal.py', 'online/fxreader/pr34/commands_typed/**/*.py', + #'online/fxreader/pr34/oom_firefox.py', ] # stubPath = '../mypy-stubs' extraPaths = [ diff --git a/releases/whl/online_fxreader_pr34-0.1.5.45-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.45-py3-none-any.whl new file mode 100644 index 0000000..f431de1 --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.45-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2453a83bddeb241769a350598b395fcfd25687012c7b2c0ea661a6eeb12776dc +size 82964 diff --git a/releases/whl/online_fxreader_pr34-0.1.5.47-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.47-py3-none-any.whl new file mode 100644 index 0000000..8ac0b29 --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.47-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c1c6e293c1d065bf0b4c95ee7d5737cde47a0af89d270a14547b544f3f88d770 +size 83100 diff --git a/releases/whl/online_fxreader_pr34-0.1.5.48-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.48-py3-none-any.whl new file mode 100644 index 0000000..975de8b --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.48-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1757771a00bda395b3a7fe8d7b0e186ebd2e85fd3155bc0efe6bdf8676e8d0ef +size 83157 diff --git a/releases/whl/online_fxreader_pr34-0.1.5.49-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.49-py3-none-any.whl new file mode 100644 index 0000000..060311a --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.49-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f7f6fa731ebe094aebdaed8aeec4d424f536cf25205b7a9fcae744f717926404 +size 83159 diff --git a/releases/whl/online_fxreader_pr34-0.1.5.50-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.50-py3-none-any.whl new file mode 100644 index 0000000..f183df4 --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.50-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:43a3ab5b8144e3f0fb9370119152fae5733dd545a7477360f290de68227204d2 +size 83153 diff --git a/releases/whl/online_fxreader_pr34-0.1.5.51-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.51-py3-none-any.whl new file mode 100644 index 0000000..f5d9d86 --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.51-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8151ce2349503ea794c7686334a2165a277f06ab44785a7932727abbbf10c20f +size 83251 diff --git a/releases/whl/online_fxreader_pr34-0.1.5.52-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.52-py3-none-any.whl new file mode 100644 index 0000000..6615bdd --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.52-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:115f3772c7ce52f9eaa64082f40bbfcfb6a4e53a787f7145641df56735255493 +size 83260 diff --git a/releases/whl/online_fxreader_pr34-0.1.5.53-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.53-py3-none-any.whl new file mode 100644 index 0000000..c66ba67 --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.53-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba1c1ceb2f20f57d3446e965f9f22c1e24041b7d76fa8d4d58a2a8f094b98240 +size 83288 diff --git a/releases/whl/online_fxreader_pr34-0.1.5.54-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.54-py3-none-any.whl new file mode 100644 index 0000000..e646d23 --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.54-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9f32e106209f3f8c1fc29c9d17c19ad473f8f1ce0af4d90df939beb8a1a56280 +size 83298 From d782ec7731fbd0551b0999777f3482250913af65 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Sat, 13 Dec 2025 20:18:55 +0300 Subject: [PATCH 63/70] [+] update pr34 1. fetch sensitive configs, mark telegram to prevent sway idle, when fullscreen; 2. update oom_firefox tool, to support custom worker_regex, main_regex; 3. update .whl for pr34; --- .../local/bin/online.fxreader.pr34-on-resume | 2 +- ...tive-configs-2025-12-13T19:15:17+03:00.gpg | 3 + python/meson.build | 2 +- python/online/fxreader/pr34/oom_firefox.py | 86 ++++++++++++++----- ...ne_fxreader_pr34-0.1.5.56-py3-none-any.whl | 3 + ...ne_fxreader_pr34-0.1.5.57-py3-none-any.whl | 3 + ...ne_fxreader_pr34-0.1.5.58-py3-none-any.whl | 3 + ...ne_fxreader_pr34-0.1.5.59-py3-none-any.whl | 3 + 8 files changed, 80 insertions(+), 25 deletions(-) create mode 100644 platform_dotfiles_gpg/ideapad_slim_3_15arp10/sensitive-configs-2025-12-13T19:15:17+03:00.gpg create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.56-py3-none-any.whl create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.57-py3-none-any.whl create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.58-py3-none-any.whl create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.59-py3-none-any.whl diff --git a/platform_dotfiles/ideapad_slim_3_15arp10/usr/local/bin/online.fxreader.pr34-on-resume b/platform_dotfiles/ideapad_slim_3_15arp10/usr/local/bin/online.fxreader.pr34-on-resume index 95a97ea..6a80d36 100755 --- a/platform_dotfiles/ideapad_slim_3_15arp10/usr/local/bin/online.fxreader.pr34-on-resume +++ b/platform_dotfiles/ideapad_slim_3_15arp10/usr/local/bin/online.fxreader.pr34-on-resume @@ -23,7 +23,7 @@ elif sys.argv[1] == 'after-suspend': subprocess.check_call(['modprobe', 'ideapad_laptop',]) #subprocess.check_call(['modprobe', 'atkbd',]) subprocess.check_call(['nmcli', 'radio', 'wifi', 'on']) - subprocess.check_call(['rfkill', 'unblock', '109']) + #subprocess.check_call(['rfkill', 'unblock', '109']) #subprocess.check_call(r''' # # systemctl restart wg-quick@siarhei-hp.service #''', shell=True,) diff --git a/platform_dotfiles_gpg/ideapad_slim_3_15arp10/sensitive-configs-2025-12-13T19:15:17+03:00.gpg b/platform_dotfiles_gpg/ideapad_slim_3_15arp10/sensitive-configs-2025-12-13T19:15:17+03:00.gpg new file mode 100644 index 0000000..1eeea44 --- /dev/null +++ b/platform_dotfiles_gpg/ideapad_slim_3_15arp10/sensitive-configs-2025-12-13T19:15:17+03:00.gpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:03b5120f4ac2433e59a9e146a3983ba76c097cb32958684e482b66dd7183d9bf +size 19038 diff --git a/python/meson.build b/python/meson.build index 6a57b6f..0473ffb 100644 --- a/python/meson.build +++ b/python/meson.build @@ -5,7 +5,7 @@ project( ).stdout().strip('\n'), # 'online.fxreader.uv', # ['c', 'cpp'], - version: '0.1.5.54', + version: '0.1.5.59', # default_options: [ # 'cpp_std=c++23', # # 'prefer_static=true', diff --git a/python/online/fxreader/pr34/oom_firefox.py b/python/online/fxreader/pr34/oom_firefox.py index 9311c03..61ab281 100644 --- a/python/online/fxreader/pr34/oom_firefox.py +++ b/python/online/fxreader/pr34/oom_firefox.py @@ -190,11 +190,32 @@ def kill_prioritized( procs: list['get_firefox_procs_ps_t.res_t.entry_t'], to_free_mb, low_priority_pids, + main_regex: Optional[str], + worker_regex: Optional[str], ): candidates = [] for p in procs: - if is_main_firefox(p): + if worker_regex is None and main_regex is None: + pass + elif ( + not worker_regex is None + and not re.compile(worker_regex).match(p['cmd']) is None + ): + pass + elif ( + not main_regex is None + and not re.compile(main_regex).match(p['cmd']) is None + ): continue + elif not main_regex is None and not worker_regex is None: + continue + elif main_regex is None and not worker_regex is None: + continue + elif not main_regex is None and worker_regex is None: + pass + else: + raise NotImplementedError + try: # rss_mb = p.memory_info().rss / (1024 * 1024) rss_mb = p['rss'] / (1024 * 1024) @@ -260,27 +281,6 @@ def launch_firefox_with_limits( def main(): - os.makedirs( - pathlib.Path('~/.cache/oom_firefox/').expanduser(), exist_ok=True - ) - - logging.basicConfig( - level=logging.INFO, - format=( - '%(asctime)s ' - '%(levelname)-8s ' - '%(filename)s:%(lineno)d ' - '%(funcName)s – %(message)s' - ), - handlers=[ - logging.handlers.RotatingFileHandler( - pathlib.Path('~/.cache/oom_firefox/log').expanduser(), - maxBytes=128 * 1024, - backupCount=3, - ) - ], - ) - parser = argparse.ArgumentParser( description='Firefox memory manager with slice + graceful shutdown' ) @@ -314,6 +314,19 @@ def main(): default='firefox-limited', help='Name for systemd transient unit', ) + parser.add_argument( + '--main-regex', + type=str, + default=None, + help='regex for main processes, that are not to kill', + ) + parser.add_argument( + '--worker-regex', + type=str, + # default=r'^.*contentproc.*$', + default=None, + help='regex for worker processes, that can be killed, like .*contentproc.* for firefox', + ) parser.add_argument( '--firefox-extra', action='append', @@ -328,6 +341,29 @@ def main(): args = parser.parse_args() + os.makedirs( + pathlib.Path('~/.cache/oom_firefox/').expanduser(), exist_ok=True + ) + + logging.basicConfig( + level=logging.INFO, + format=( + '%(asctime)s ' + '%(levelname)-8s ' + '%(filename)s:%(lineno)d ' + '%(funcName)s – %(message)s' + ), + handlers=[ + logging.handlers.RotatingFileHandler( + pathlib.Path( + '~/.cache/oom_firefox/log-%s' % args.unit_name + ).expanduser(), + maxBytes=128 * 1024, + backupCount=3, + ) + ], + ) + low_priority_pids = set() body = TextArea(focusable=False, scrollbar=True) @@ -378,7 +414,11 @@ def main(): if total > limit: to_free = total - kill_to killed, freed = kill_prioritized( - procs, to_free, low_priority_pids + procs, + to_free, + low_priority_pids, + main_regex=args.main_regex, + worker_regex=args.worker_regex, ) lines.append(f'Killed: {killed}') lines.append(f'Freed ≈ {freed:.1f} MB') diff --git a/releases/whl/online_fxreader_pr34-0.1.5.56-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.56-py3-none-any.whl new file mode 100644 index 0000000..d285dbc --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.56-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:203c6d7ad0dc5be6c5e41d2648d47d31b1c7bea27826b23c59628c2d096b74e9 +size 83484 diff --git a/releases/whl/online_fxreader_pr34-0.1.5.57-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.57-py3-none-any.whl new file mode 100644 index 0000000..74f6399 --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.57-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:52148600329e51ad1a6405600ee21449e9ba30673fe4e4762a6ca018e718dcf7 +size 83489 diff --git a/releases/whl/online_fxreader_pr34-0.1.5.58-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.58-py3-none-any.whl new file mode 100644 index 0000000..41ce28c --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.58-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0979c942270fe8406aa3ec18ead07d2469a3d6737b1b677ccb7c4675e63be512 +size 83525 diff --git a/releases/whl/online_fxreader_pr34-0.1.5.59-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.59-py3-none-any.whl new file mode 100644 index 0000000..9c1361a --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.59-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6369fdafe47a5fed54e603afbd214d3519cf0f126cdd44e92abee3ab2424c62b +size 83529 From ff045106fcf07c727a1ab60a0df038eaefcddb2f Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Mon, 22 Dec 2025 16:24:58 +0300 Subject: [PATCH 64/70] [+] update gateway 1. some refactor for nginx_config.py; 1.1. just tested that ssl-app can correctly show a remote_addr; 1.1.1 TODO, figure out how to preserve this info when doing proxy_pass to ssl app; (hm, seems like no way); 2. exposed ssl-app port at SSL_APP_PORTS endpoint; --- d1/nginx_config.py | 1 + docker-compose.yml | 2 ++ 2 files changed, 3 insertions(+) diff --git a/d1/nginx_config.py b/d1/nginx_config.py index fd9dff5..db87695 100644 --- a/d1/nginx_config.py +++ b/d1/nginx_config.py @@ -459,6 +459,7 @@ server { server { set $t1 $remote_addr; + if ($http_x_forwarded_for) { set $t1 $http_x_forwarded_for; diff --git a/docker-compose.yml b/docker-compose.yml index afb83ee..95b2a47 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -35,6 +35,8 @@ services: - ./tmp/d1/:/app/tmp/d1/:ro - ./tmp/d1/letsencrypt:/etc/letsencrypt:rw restart: on-failure + ports: + - ${SSL_APP_PORTS:-"127.0.0.1:443"}:444 networks: network: From 2e4f407974e162b6d47215505abd5bfb8d2be992 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Wed, 24 Dec 2025 16:10:49 +0300 Subject: [PATCH 65/70] [+] update oom_firefox 1. allow to change kill percent via p hotkey; --- python/meson.build | 2 +- python/online/fxreader/pr34/oom_firefox.py | 47 ++++++++++++++++++- ...ne_fxreader_pr34-0.1.5.60-py3-none-any.whl | 3 ++ ...ne_fxreader_pr34-0.1.5.61-py3-none-any.whl | 3 ++ 4 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.60-py3-none-any.whl create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.61-py3-none-any.whl diff --git a/python/meson.build b/python/meson.build index 0473ffb..854c69b 100644 --- a/python/meson.build +++ b/python/meson.build @@ -5,7 +5,7 @@ project( ).stdout().strip('\n'), # 'online.fxreader.uv', # ['c', 'cpp'], - version: '0.1.5.59', + version: '0.1.5.61', # default_options: [ # 'cpp_std=c++23', # # 'prefer_static=true', diff --git a/python/online/fxreader/pr34/oom_firefox.py b/python/online/fxreader/pr34/oom_firefox.py index 61ab281..8500f60 100644 --- a/python/online/fxreader/pr34/oom_firefox.py +++ b/python/online/fxreader/pr34/oom_firefox.py @@ -237,6 +237,10 @@ def kill_prioritized( p=p, action='kill', msg='started', + to_free_mb=to_free_mb, + killed=killed, + freed=freed, + low_priority_pids=low_priority_pids, ) ) @@ -472,6 +476,11 @@ def main(): root_floats.append(f) app.layout.focus(ta) + def change_kill_percent(kill_percent: float) -> None: + assert kill_percent >= 10 and kill_percent <= 90 + + args.kill_percent = kill_percent + def change_max_mb(max_mb: int) -> None: for cmd in ( [ @@ -502,7 +511,6 @@ def main(): txt = ta.text m = re.compile(r'^\s*(\d+)\s*$').match(txt) - a: str = 234234 if m: change_max_mb(int(m[1])) @@ -529,6 +537,39 @@ def main(): root_floats.append(f) app.layout.focus(ta) + def open_percentage_dialog(): + ta = TextArea(text='', multiline=True, scrollbar=True) + + def on_ok(): + txt = ta.text + m = re.compile(r'^\s*(\d+|\d+\.\d+)\s*$').match(txt) + + if m: + change_kill_percent(float(m[1])) + + close_dialog() + refresh_body() + else: + logger.error('invalid input %s' % txt) + + def on_cancel(): + close_dialog() + + dialog = Dialog( + title='Enter kill percent from 10% to 90%, without % sign', + body=ta, + buttons=[ + Button(text='OK', handler=on_ok), + Button(text='Cancel', handler=on_cancel), + ], + width=60, + modal=True, + ) + f = Float(content=dialog, left=2, top=2) + dialog_float[0] = f + root_floats.append(f) + app.layout.focus(ta) + def open_message(title, message): def on_close(): close_dialog() @@ -571,6 +612,10 @@ def main(): def _(event): open_limit_dialog() + @kb.add('p') + def _(event): + open_percentage_dialog() + HELP_TEXT = 'm=add PIDs, l=change limit, s=settings, a=about, q=quit' @kb.add('h') diff --git a/releases/whl/online_fxreader_pr34-0.1.5.60-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.60-py3-none-any.whl new file mode 100644 index 0000000..cb77839 --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.60-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e13d2ecf1b68152400fd11c95abd6ddbb526feac6507f8d4d8d61ecfda2e027c +size 83552 diff --git a/releases/whl/online_fxreader_pr34-0.1.5.61-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.61-py3-none-any.whl new file mode 100644 index 0000000..4ddbbad --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.61-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d944b0a23cd0c908213a46490490e7b8c449fb6b2d46eb47b723c1a6c3b8e199 +size 83652 From ad7c176f6fb92af618e3129638c6b345feb869a6 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Tue, 6 Jan 2026 13:55:16 +0300 Subject: [PATCH 66/70] [+] update status command 1. add timezone %Z; --- python/meson.build | 2 +- python/online/fxreader/pr34/commands_typed/status.py | 2 +- releases/whl/online_fxreader_pr34-0.1.5.62-py3-none-any.whl | 3 +++ 3 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.62-py3-none-any.whl diff --git a/python/meson.build b/python/meson.build index 854c69b..e90766a 100644 --- a/python/meson.build +++ b/python/meson.build @@ -5,7 +5,7 @@ project( ).stdout().strip('\n'), # 'online.fxreader.uv', # ['c', 'cpp'], - version: '0.1.5.61', + version: '0.1.5.62', # default_options: [ # 'cpp_std=c++23', # # 'prefer_static=true', diff --git a/python/online/fxreader/pr34/commands_typed/status.py b/python/online/fxreader/pr34/commands_typed/status.py index b0737e5..0f4fe14 100644 --- a/python/online/fxreader/pr34/commands_typed/status.py +++ b/python/online/fxreader/pr34/commands_typed/status.py @@ -35,7 +35,7 @@ def get_info( echo -n $A | awk '{print $2, $7}'; """, r""" - date +'%Y-%m-%d %l:%M:%S %p'; + date +'%Y-%m-%d %l:%M:%S %p %Z'; """, ], ] diff --git a/releases/whl/online_fxreader_pr34-0.1.5.62-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.62-py3-none-any.whl new file mode 100644 index 0000000..33d73c5 --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.62-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9f4f014d445f274cc53acf18ca9701dd515f2a61d82caedc3598857814dc1b0 +size 83657 From b3bce97ba244b76ee64242e38fbf6f2f370676e1 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Tue, 6 Jan 2026 22:07:49 +0300 Subject: [PATCH 67/70] [+] update vim settings 1. disable filetype plugin; 2. add import into beta.py augroup; 3. add ++nested for BufEnter, but at the end, just disable filetype plugin; since can't figure out the way to override its effect; --- dotfiles/.vim/online_fxreader_pr34_vim/beta.py | 4 ++-- dotfiles/.vim/online_fxreader_pr34_vim/main.py | 6 +++--- dotfiles/.vimrc | 3 ++- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/dotfiles/.vim/online_fxreader_pr34_vim/beta.py b/dotfiles/.vim/online_fxreader_pr34_vim/beta.py index e1caac2..ce18cfa 100644 --- a/dotfiles/.vim/online_fxreader_pr34_vim/beta.py +++ b/dotfiles/.vim/online_fxreader_pr34_vim/beta.py @@ -84,8 +84,8 @@ class FastSelect: r""" augroup {auto_group} autocmd! - autocmd VimLeavePre * python3 online_fxreader_pr34_vim.beta.FastSelect.singleton().close() - autocmd BufEnter * python3 online_fxreader_pr34_vim.beta.FastSelect.singleton().on_buf_enter() + autocmd VimLeavePre * python3 import online_fxreader_pr34_vim.beta; online_fxreader_pr34_vim.beta.FastSelect.singleton().close() + autocmd BufEnter * python3 import online_fxreader_pr34_vim.beta; online_fxreader_pr34_vim.beta.FastSelect.singleton().on_buf_enter() augroup END """.format( auto_group=auto_group, diff --git a/dotfiles/.vim/online_fxreader_pr34_vim/main.py b/dotfiles/.vim/online_fxreader_pr34_vim/main.py index ceaa21a..e3fa583 100644 --- a/dotfiles/.vim/online_fxreader_pr34_vim/main.py +++ b/dotfiles/.vim/online_fxreader_pr34_vim/main.py @@ -166,10 +166,10 @@ class EditorConfigModeline: Vim.run_command(r""" augroup EditorConfigModeline - autocmd! - autocmd BufEnter * python3 import online_fxreader_pr34_vim.main; online_fxreader_pr34_vim.main.EditorConfigModeline.singleton().on_buffer() + autocmd! + autocmd BufEnter * ++nested python3 import online_fxreader_pr34_vim.main; online_fxreader_pr34_vim.main.EditorConfigModeline.singleton().on_buffer() augroup END - """) + """) @classmethod def singleton(cls) -> Self: diff --git a/dotfiles/.vimrc b/dotfiles/.vimrc index 194efee..8d05fd1 100644 --- a/dotfiles/.vimrc +++ b/dotfiles/.vimrc @@ -7,7 +7,8 @@ if has('python3') source $HOME/.py3.vimrc endif -filetype plugin indent on +" filetype plugin indent on +filetype plugin off set number set noswapfile From f0e6cf2924d918b750cd09d24136af102b43fde8 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Tue, 6 Jan 2026 22:29:53 +0300 Subject: [PATCH 68/70] [+] update vim config 1. use BufWinEnter, instead of BufEnter; 1.1. it is being trigger when the user switches between buffers, as well as when buffers are populated first time from vim session into window panes; --- dotfiles/.vim/online_fxreader_pr34_vim/main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dotfiles/.vim/online_fxreader_pr34_vim/main.py b/dotfiles/.vim/online_fxreader_pr34_vim/main.py index e3fa583..6e07e40 100644 --- a/dotfiles/.vim/online_fxreader_pr34_vim/main.py +++ b/dotfiles/.vim/online_fxreader_pr34_vim/main.py @@ -167,7 +167,8 @@ class EditorConfigModeline: Vim.run_command(r""" augroup EditorConfigModeline autocmd! - autocmd BufEnter * ++nested python3 import online_fxreader_pr34_vim.main; online_fxreader_pr34_vim.main.EditorConfigModeline.singleton().on_buffer() + " autocmd BufEnter * ++nested python3 import online_fxreader_pr34_vim.main; online_fxreader_pr34_vim.main.EditorConfigModeline.singleton().on_buffer() + autocmd BufWinEnter * ++nested python3 import online_fxreader_pr34_vim.main; online_fxreader_pr34_vim.main.EditorConfigModeline.singleton().on_buffer() augroup END """) From d68670f6242ca4617f5a1e18882caea181b85027 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Wed, 7 Jan 2026 23:28:58 +0300 Subject: [PATCH 69/70] [+] update gateway 1. allow to expose 80 port of nginx ssl; --- docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.yml b/docker-compose.yml index 95b2a47..b283a56 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -37,6 +37,7 @@ services: restart: on-failure ports: - ${SSL_APP_PORTS:-"127.0.0.1:443"}:444 + - ${APP_PORTS:-"127.0.0.1:80"}:80 networks: network: From 79a1296281a0c8a7fe5d19ef511eaa07505a0b6d Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Wed, 4 Feb 2026 11:39:47 +0300 Subject: [PATCH 70/70] [+] update pr34 1. update cli_boostrap.py example; 1.1. allow to force whl_cache update; not reset; 1.2. log more command calls, before executing them; 2. update oom_firefox, to produce some logs; 2.1. still not practical solution, since tabs crash very fast, and no integration with the browser, to say extensions killing, which happens still for some reason; 3. reuse pr34_logging.setup(); --- python/meson.build | 2 +- .../pr34/commands_typed/cli_bootstrap.py | 106 +++++++++++------- .../fxreader/pr34/commands_typed/status.py | 5 +- python/online/fxreader/pr34/oom_firefox.py | 13 +++ ...ne_fxreader_pr34-0.1.5.63-py3-none-any.whl | 3 + ...ne_fxreader_pr34-0.1.5.64-py3-none-any.whl | 3 + ...ne_fxreader_pr34-0.1.5.65-py3-none-any.whl | 3 + 7 files changed, 90 insertions(+), 45 deletions(-) create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.63-py3-none-any.whl create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.64-py3-none-any.whl create mode 100644 releases/whl/online_fxreader_pr34-0.1.5.65-py3-none-any.whl diff --git a/python/meson.build b/python/meson.build index e90766a..29fbef8 100644 --- a/python/meson.build +++ b/python/meson.build @@ -5,7 +5,7 @@ project( ).stdout().strip('\n'), # 'online.fxreader.uv', # ['c', 'cpp'], - version: '0.1.5.62', + version: '0.1.5.65', # default_options: [ # 'cpp_std=c++23', # # 'prefer_static=true', diff --git a/python/online/fxreader/pr34/commands_typed/cli_bootstrap.py b/python/online/fxreader/pr34/commands_typed/cli_bootstrap.py index 3b30e4a..23da8e7 100644 --- a/python/online/fxreader/pr34/commands_typed/cli_bootstrap.py +++ b/python/online/fxreader/pr34/commands_typed/cli_bootstrap.py @@ -358,6 +358,12 @@ class BootstrapSettings: '--offline -U', ).split(), ) + whl_cache_update: Optional[bool] = dataclasses.field( + default_factory=lambda: os.environ.get( + 'WHL_CACHE_UPDATE', json.dumps(False) + ) + in [json.dumps(True)] + ) @classmethod def get( @@ -520,37 +526,44 @@ def env_bootstrap( f.write('\n'.join(requirements_in)) f.flush() - subprocess.check_call( - [ - 'uv', - 'pip', - 'compile', - *uv_python_version, - '--generate-hashes', - *pip_find_links_args, - # '-p', - # bootstrap_settings.python_path, - *bootstrap_settings.uv_args, - '-o', - str(requirements_path), - f.name, - ] - ) - - if not bootstrap_settings.env_path.exists(): - subprocess.check_call( - [ + cmd = [ + 'uv', 'pip', - 'download', - '--only-binary=:all:', + 'compile', *uv_python_version, + '--generate-hashes', *pip_find_links_args, - '-r', + # '-p', + # bootstrap_settings.python_path, + *bootstrap_settings.uv_args, + '-o', str(requirements_path), - '-d', - str(bootstrap_settings.whl_cache_path), + f.name, ] - ) + + logger.info(dict(cmd=cmd)) + subprocess.check_call(cmd) + + # if not bootstrap_settings.env_path.exists(): + if ( + not bootstrap_settings.whl_cache_path.exists() + or bootstrap_settings.whl_cache_update + ): + cmd = [ + 'pip', + 'download', + '--only-binary=:all:', + *uv_python_version, + *pip_find_links_args, + '-r', + str(requirements_path), + '-d', + str(bootstrap_settings.whl_cache_path), + ] + + logger.info(dict(cmd=cmd)) + + subprocess.check_call(cmd) cache_find_links_args = ( '-f', @@ -563,7 +576,12 @@ def env_bootstrap( *[ o for o in bootstrap_settings.uv_args - if not o in ['-U', '--upgrade'] + if not o + in [ + '-U', + '--upgrade', + '--no-index', + ] ], 'venv', *venv_python_version, @@ -573,22 +591,24 @@ def env_bootstrap( ] ) - subprocess.check_call( - [ - 'uv', - 'pip', - 'install', - *uv_python_version, - *cache_find_links_args, - # *pip_find_links_args, - '-p', - bootstrap_settings.python_path, - '--require-hashes', - *bootstrap_settings.uv_args, - '-r', - str(requirements_path), - ] - ) + cmd = [ + 'uv', + 'pip', + 'install', + *uv_python_version, + *cache_find_links_args, + # *pip_find_links_args, + '-p', + bootstrap_settings.python_path, + '--require-hashes', + *bootstrap_settings.uv_args, + '-r', + str(requirements_path), + ] + + logger.info(dict(cmd=cmd)) + + subprocess.check_call(cmd) if bootstrap_settings.pip_check_conflicts: subprocess.check_call( diff --git a/python/online/fxreader/pr34/commands_typed/status.py b/python/online/fxreader/pr34/commands_typed/status.py index 0f4fe14..0569af8 100644 --- a/python/online/fxreader/pr34/commands_typed/status.py +++ b/python/online/fxreader/pr34/commands_typed/status.py @@ -12,6 +12,8 @@ import textwrap import optparse import traceback +from . import logging as pr34_logging + from typing import ( Any, Optional, @@ -60,7 +62,8 @@ def get_info( def run(argv: list[str]): - logging.basicConfig(level=logging.INFO) + # logging.basicConfig(level=logging.INFO) + pr34_logging.setup() assert isinstance(argv, list) and all([isinstance(o, str) for o in argv]) diff --git a/python/online/fxreader/pr34/oom_firefox.py b/python/online/fxreader/pr34/oom_firefox.py index 8500f60..674105e 100644 --- a/python/online/fxreader/pr34/oom_firefox.py +++ b/python/online/fxreader/pr34/oom_firefox.py @@ -417,6 +417,19 @@ def main(): if total > limit: to_free = total - kill_to + + logger.info( + dict( + total=total, + limit=limit, + kill_to=kill_to, + to_free=to_free, + low_priority_pids=low_priority_pids, + worker_regex=args.worker_regex, + main_regex=args.main_regex, + ) + ) + killed, freed = kill_prioritized( procs, to_free, diff --git a/releases/whl/online_fxreader_pr34-0.1.5.63-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.63-py3-none-any.whl new file mode 100644 index 0000000..e754139 --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.63-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:92b0db4b8e9e896b2acb9545cb08fc0441e6bb73e2b7e7d7cdc69e5820af0ac1 +size 83695 diff --git a/releases/whl/online_fxreader_pr34-0.1.5.64-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.64-py3-none-any.whl new file mode 100644 index 0000000..30f5648 --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.64-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:51dbc93696d697834ddaca9d121fbd8f01a7ef03bdee02387be32c7401549585 +size 83715 diff --git a/releases/whl/online_fxreader_pr34-0.1.5.65-py3-none-any.whl b/releases/whl/online_fxreader_pr34-0.1.5.65-py3-none-any.whl new file mode 100644 index 0000000..9b317ab --- /dev/null +++ b/releases/whl/online_fxreader_pr34-0.1.5.65-py3-none-any.whl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:29325361cdf449311627eee6abffbec2887dbef352d37c43d855a07f3bb78496 +size 83820