mirror of
https://github.com/koloideal/Argenta.git
synced 2026-06-10 18:15:28 +03:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b54ae9a330 | |||
| 552e1db6fd | |||
| c06ea6e196 |
@@ -1,9 +1,6 @@
|
|||||||
#### joe made this: http://goel.io/joe
|
#### joe made this: http://goel.io/joe
|
||||||
|
|
||||||
metrics/reports/diagrams
|
metrics/reports/diagrams
|
||||||
*.dist
|
|
||||||
*build
|
|
||||||
*.exe
|
|
||||||
|
|
||||||
#### python ####
|
#### python ####
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
@@ -325,4 +322,3 @@ http-client.private.env.json
|
|||||||
.idea/ApifoxUploaderProjectSetting.xml
|
.idea/ApifoxUploaderProjectSetting.xml
|
||||||
|
|
||||||
.zed
|
.zed
|
||||||
test.py
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
<!--
|
|
||||||
A new scriv changelog fragment.
|
|
||||||
|
|
||||||
Uncomment the section that is right (remove the HTML comment wrapper).
|
|
||||||
For top level release notes, leave all the headers commented out.
|
|
||||||
-->
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- A cli module that implements the ability to launch applications on Argenta, run application benchmarks on Argenta, create a boilerplate for new projects, and much more.
|
|
||||||
- A new `info` command has been added to the Argenta CLI, providing a quick overview of the installed package and runtime environment.
|
|
||||||
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Refactoring the initialization order of some modules; heavy imports are now imported only when necessary, which resulted in a boost to importtime.
|
|
||||||
|
|
||||||
<!--
|
|
||||||
### Deprecated
|
|
||||||
|
|
||||||
- A bullet item for the Deprecated category.
|
|
||||||
|
|
||||||
-->
|
|
||||||
<!--
|
|
||||||
### Removed
|
|
||||||
|
|
||||||
- A bullet item for the Removed category.
|
|
||||||
|
|
||||||
-->
|
|
||||||
<!--
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- A bullet item for the Fixed category.
|
|
||||||
|
|
||||||
-->
|
|
||||||
@@ -12,6 +12,6 @@ orchestrator = Orchestrator(
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
if arg_parser.parsed_argspace.get_by_name("dev"):
|
if arg_parser.parsed_argspace.get_by_name("dev"):
|
||||||
orchestrator.run_repl(App(initial_message="ArgentaDev"))
|
orchestrator.start_polling(App(initial_message="ArgentaDev"))
|
||||||
else:
|
else:
|
||||||
orchestrator.run_repl(App())
|
orchestrator.start_polling(App())
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ def main():
|
|||||||
print(f" Host: {host.value}")
|
print(f" Host: {host.value}")
|
||||||
print(f" Port: {port.value}")
|
print(f" Port: {port.value}")
|
||||||
|
|
||||||
orchestrator.run_repl(app)
|
orchestrator.start_polling(app)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -24,4 +24,4 @@ orchestrator = Orchestrator(custom_providers=[ConnectionProvider()])
|
|||||||
|
|
||||||
# 4. Start the application
|
# 4. Start the application
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
orchestrator.run_repl(app)
|
orchestrator.start_polling(app)
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ orchestrator = Orchestrator()
|
|||||||
|
|
||||||
def main():
|
def main():
|
||||||
app.include_router(router)
|
app.include_router(router)
|
||||||
orchestrator.run_repl(app)
|
orchestrator.start_polling(app)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ orchestrator: Orchestrator = Orchestrator()
|
|||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
app.include_router(router)
|
app.include_router(router)
|
||||||
orchestrator.run_repl(app)
|
orchestrator.start_polling(app)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -30,4 +30,4 @@ app.include_router(main_router)
|
|||||||
|
|
||||||
# 5. Start application
|
# 5. Start application
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
orchestrator.run_repl(app)
|
orchestrator.start_polling(app)
|
||||||
|
|||||||
@@ -15,4 +15,4 @@ app.include_router(router)
|
|||||||
|
|
||||||
# 3. Start polling via orchestrator
|
# 3. Start polling via orchestrator
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
orchestrator.run_repl(app)
|
orchestrator.start_polling(app)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ def test_input_incorrect_command(capsys: CaptureFixture[str]):
|
|||||||
app.set_unknown_command_handler(lambda command: print(f"Unknown command: {command.trigger}"))
|
app.set_unknown_command_handler(lambda command: print(f"Unknown command: {command.trigger}"))
|
||||||
|
|
||||||
with patch("builtins.input", side_effect=["help", "q"]):
|
with patch("builtins.input", side_effect=["help", "q"]):
|
||||||
orchestrator.run_repl(app)
|
orchestrator.start_polling(app)
|
||||||
|
|
||||||
output = capsys.readouterr().out
|
output = capsys.readouterr().out
|
||||||
assert "\nUnknown command: help\n" in output
|
assert "\nUnknown command: help\n" in output
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ PredefinedMessages
|
|||||||
app.add_message_on_startup(PredefinedMessages.AUTOCOMPLETE)
|
app.add_message_on_startup(PredefinedMessages.AUTOCOMPLETE)
|
||||||
app.add_message_on_startup(PredefinedMessages.HELP)
|
app.add_message_on_startup(PredefinedMessages.HELP)
|
||||||
|
|
||||||
orchestrator.run_repl(app)
|
orchestrator.start_polling(app)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ Orchestrator
|
|||||||
Основные методы
|
Основные методы
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
.. py:method:: run_repl(self, app: App) -> None
|
.. py:method:: start_polling(self, app: App) -> None
|
||||||
|
|
||||||
Это главный метод, который запускает приложение. Он запускает бесконечный цикл ввода -> вывода.
|
Это главный метод, который запускает приложение. Он запускает бесконечный цикл ввода -> вывода.
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
E2E-тестирование цикла
|
E2E-тестирование цикла
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
Полный запуск цикла ``run_repl`` можно покрывать через подпроцесс с передачей строк в ``stdin``. Это тяжелее и обычно не требуется. Если всё же необходимо — пример ниже.
|
Полный запуск цикла ``start_polling`` можно покрывать через подпроцесс с передачей строк в ``stdin``. Это тяжелее и обычно не требуется. Если всё же необходимо — пример ниже.
|
||||||
|
|
||||||
.. danger::
|
.. danger::
|
||||||
**Важно:** Обязательно передавайте строковый триггер команды выхода последним элементом в списке ``side_effects`` при патче ``input``.
|
**Важно:** Обязательно передавайте строковый триггер команды выхода последним элементом в списке ``side_effects`` при патче ``input``.
|
||||||
|
|||||||
@@ -1,57 +1,36 @@
|
|||||||
set windows-shell := ["powershell.exe", "-NoLogo", "-Command"]
|
set windows-shell := ["powershell.exe", "-NoLogo", "-Command"]
|
||||||
set shell := ["bash", "-c"]
|
set shell := ["bash", "-c"]
|
||||||
|
|
||||||
# List all available recipes
|
# Вывести список всех рецептов
|
||||||
default:
|
default:
|
||||||
@just --list
|
@just --list
|
||||||
|
|
||||||
# ── Testing ───────────────────────────────────────────────────────────────────
|
# Запустить тесты через pytest
|
||||||
|
|
||||||
# Run tests via pytest
|
|
||||||
tests:
|
tests:
|
||||||
python -m pytest tests
|
python -m pytest tests
|
||||||
|
|
||||||
# Run tests with coverage report
|
# Запустить тесты с отчетом о покрытии
|
||||||
tests-cov:
|
tests-cov:
|
||||||
python -m pytest --cov=argenta tests
|
python -m pytest --cov=argenta tests
|
||||||
|
|
||||||
# Run tests with coverage HTML report
|
# Запустить тесты с отчетом о покрытии с html репортом
|
||||||
tests-cov-html:
|
tests-cov-html:
|
||||||
python -m pytest --cov=argenta tests --cov-report=html
|
python -m pytest --cov=argenta tests --cov-report=html
|
||||||
|
|
||||||
# ── Code quality ──────────────────────────────────────────────────────────────
|
# Отформатировать код (Ruff + isort)
|
||||||
|
|
||||||
# Format code (Ruff + isort)
|
|
||||||
format:
|
format:
|
||||||
python -m ruff format ./src
|
python -m ruff format ./src
|
||||||
python -m isort ./src
|
python -m isort ./src
|
||||||
|
|
||||||
# Check types via mypy (strict)
|
# Проверить типы через mypy (strict)
|
||||||
mypy:
|
mypy:
|
||||||
python -m mypy -p argenta --strict
|
python -m mypy -p argenta --strict
|
||||||
|
|
||||||
# Check style via wemake-python-styleguide
|
# Проверить стиль через wemake-python-styleguide
|
||||||
wps:
|
wps:
|
||||||
python -m flake8 --format=wemake ./src
|
python -m flake8 --format=wemake ./src
|
||||||
|
|
||||||
# Run Ruff linter
|
# Запустить линтер Ruff
|
||||||
ruff:
|
ruff:
|
||||||
python -m ruff check ./src
|
python -m ruff check ./src
|
||||||
|
|
||||||
# Run all checks (format, mypy, ruff, wps)
|
|
||||||
check-format: format mypy ruff wps
|
|
||||||
|
|
||||||
# ── Changelog (scriv) ─────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
# Create a new changelog fragment and open it in $EDITOR
|
|
||||||
frag:
|
|
||||||
if (-not (Test-Path "./changelog.d")) { New-Item -ItemType Directory -Path "./changelog.d" }
|
|
||||||
scriv create --add
|
|
||||||
|
|
||||||
# Preview collected changelog without writing anything
|
|
||||||
changelog-preview:
|
|
||||||
scriv collect --dry-run
|
|
||||||
|
|
||||||
# Collect fragments into CHANGELOG.md for release (usage: just release 1.2.3)
|
|
||||||
release version:
|
|
||||||
scriv collect --version {{ version }} --add
|
|
||||||
|
|||||||
+6
-6
@@ -1,18 +1,18 @@
|
|||||||
from argenta import App, Command, Orchestrator
|
from argenta import App, Orchestrator, Command
|
||||||
|
from argenta.app import DynamicDividingLine
|
||||||
from .handlers import router
|
from .handlers import router
|
||||||
|
|
||||||
app = App(initial_message="metrics", exit_command=Command("exit", aliases=["quit"]))
|
|
||||||
|
|
||||||
app.include_router(router)
|
app = App(initial_message="metrics", exit_command=Command('exit', aliases=['quit']))
|
||||||
orchestrator = Orchestrator()
|
orchestrator = Orchestrator()
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
|
app.include_router(router)
|
||||||
app.set_description_message_pattern(
|
app.set_description_message_pattern(
|
||||||
lambda command, description: f"[bold cyan]▸[/bold cyan] [bold white]{command}[/bold white] [dim]│[/dim] [yellow italic]{description}[/yellow italic]"
|
lambda command, description: f'[bold cyan]▸[/bold cyan] [bold white]{command}[/bold white] [dim]│[/dim] [yellow italic]{description}[/yellow italic]'
|
||||||
)
|
)
|
||||||
orchestrator.run_repl(app)
|
orchestrator.start_polling(app)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -13,10 +13,8 @@ class BenchmarksNotFound(Exception):
|
|||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"Benchmarks with type '{self.type_}' not found"
|
return f"Benchmarks with type '{self.type_}' not found"
|
||||||
|
|
||||||
|
|
||||||
class BenchmarksWithSameNameAlreadyExists(Exception):
|
class BenchmarksWithSameNameAlreadyExists(Exception):
|
||||||
def __init__(self, benchmark_name: str):
|
def __init__(self, benchmark_name: str):
|
||||||
self.benchmark_name = benchmark_name
|
self.benchmark_name = benchmark_name
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"Benchmarks with name '{self.benchmark_name}' already exists"
|
return f"Benchmarks with name '{self.benchmark_name}' already exists"
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
__all__ = ["Benchmark", "Benchmarks", "BenchmarkResult", "BenchmarkGroupResult"]
|
__all__ = [
|
||||||
|
"Benchmark",
|
||||||
|
"Benchmarks",
|
||||||
|
"BenchmarkResult",
|
||||||
|
"BenchmarkGroupResult"
|
||||||
|
]
|
||||||
|
|
||||||
import gc
|
|
||||||
import io
|
import io
|
||||||
import statistics
|
|
||||||
import time
|
|
||||||
from contextlib import redirect_stdout
|
from contextlib import redirect_stdout
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
import time
|
||||||
|
import gc
|
||||||
|
import statistics
|
||||||
from typing import Callable, override
|
from typing import Callable, override
|
||||||
|
|
||||||
from .exceptions import BenchmarkNotFound, BenchmarksNotFound, BenchmarksWithSameNameAlreadyExists
|
from .exceptions import BenchmarkNotFound, BenchmarksNotFound, BenchmarksWithSameNameAlreadyExists
|
||||||
@@ -35,7 +40,14 @@ class BenchmarkGroupResult:
|
|||||||
|
|
||||||
|
|
||||||
class Benchmark:
|
class Benchmark:
|
||||||
def __init__(self, func: FuncForBenchmark, *, type_: str, name: str, description: str) -> None:
|
def __init__(
|
||||||
|
self,
|
||||||
|
func: FuncForBenchmark,
|
||||||
|
*,
|
||||||
|
type_: str,
|
||||||
|
name: str,
|
||||||
|
description: str
|
||||||
|
) -> None:
|
||||||
self.func = func
|
self.func = func
|
||||||
self.type_ = type_
|
self.type_ = type_
|
||||||
self.name = name
|
self.name = name
|
||||||
@@ -66,11 +78,11 @@ class Benchmark:
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"Benchmark<{self.type_=}, {self.name=}, {self.description=}>"
|
return f'Benchmark<{self.type_=}, {self.name=}, {self.description=}>'
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"benchmark {self.name} with type {self.type_}"
|
return f'benchmark {self.name} with type {self.type_}'
|
||||||
|
|
||||||
|
|
||||||
class Benchmarks:
|
class Benchmarks:
|
||||||
@@ -80,14 +92,16 @@ class Benchmarks:
|
|||||||
self._benchmarks_paired_by_name: dict[str, Benchmark] = {}
|
self._benchmarks_paired_by_name: dict[str, Benchmark] = {}
|
||||||
|
|
||||||
def register(
|
def register(
|
||||||
self, type_: str, description: str = ""
|
self,
|
||||||
|
type_: str,
|
||||||
|
description: str = ""
|
||||||
) -> Callable[[FuncForBenchmark], FuncForBenchmark]:
|
) -> Callable[[FuncForBenchmark], FuncForBenchmark]:
|
||||||
def decorator(func: FuncForBenchmark) -> FuncForBenchmark:
|
def decorator(func: FuncForBenchmark) -> FuncForBenchmark:
|
||||||
benchmark = Benchmark(
|
benchmark = Benchmark(
|
||||||
func,
|
func,
|
||||||
type_=type_,
|
type_=type_,
|
||||||
name=func.__name__,
|
name=func.__name__,
|
||||||
description=description or f"description for {func.__name__} with type {type_}",
|
description=description or f'description for {func.__name__} with type {type_}',
|
||||||
)
|
)
|
||||||
if self._benchmarks_paired_by_name.get(func.__name__):
|
if self._benchmarks_paired_by_name.get(func.__name__):
|
||||||
raise BenchmarksWithSameNameAlreadyExists(func.__name__)
|
raise BenchmarksWithSameNameAlreadyExists(func.__name__)
|
||||||
@@ -96,12 +110,9 @@ class Benchmarks:
|
|||||||
self._benchmarks.append(benchmark)
|
self._benchmarks.append(benchmark)
|
||||||
self._benchmarks_grouped_by_type.setdefault(type_, []).append(benchmark)
|
self._benchmarks_grouped_by_type.setdefault(type_, []).append(benchmark)
|
||||||
return func
|
return func
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
def run_benchmark_by_name(
|
def run_benchmark_by_name(self, name: str, iterations: int = 100, is_gc_disables: bool = False) -> BenchmarkResult:
|
||||||
self, name: str, iterations: int = 100, is_gc_disables: bool = False
|
|
||||||
) -> BenchmarkResult:
|
|
||||||
benchmark = self.get_benchmark_by_name(name)
|
benchmark = self.get_benchmark_by_name(name)
|
||||||
if not benchmark:
|
if not benchmark:
|
||||||
raise BenchmarkNotFound(name)
|
raise BenchmarkNotFound(name)
|
||||||
@@ -119,34 +130,28 @@ class Benchmarks:
|
|||||||
is_gc_disabled=is_gc_disables,
|
is_gc_disabled=is_gc_disables,
|
||||||
avg_time=avg,
|
avg_time=avg,
|
||||||
median_time=median,
|
median_time=median,
|
||||||
std_dev=std_dev,
|
std_dev=std_dev
|
||||||
)
|
)
|
||||||
|
|
||||||
def run_benchmarks_by_type(
|
def run_benchmarks_by_type(self, type_: str, iterations: int = 100, is_gc_disabled: bool = False) -> BenchmarkGroupResult:
|
||||||
self, type_: str, iterations: int = 100, is_gc_disabled: bool = False
|
|
||||||
) -> BenchmarkGroupResult:
|
|
||||||
benchmarks = self.get_benchmarks_by_type(type_)
|
benchmarks = self.get_benchmarks_by_type(type_)
|
||||||
if not benchmarks:
|
if not benchmarks:
|
||||||
raise BenchmarksNotFound(type_)
|
raise BenchmarksNotFound(type_)
|
||||||
benchmark_results: list[BenchmarkResult] = []
|
benchmark_results: list[BenchmarkResult] = []
|
||||||
|
|
||||||
for benchmark in benchmarks:
|
for benchmark in benchmarks:
|
||||||
benchmark_results.append(
|
benchmark_results.append(self.run_benchmark_by_name(benchmark.name, iterations, is_gc_disabled))
|
||||||
self.run_benchmark_by_name(benchmark.name, iterations, is_gc_disabled)
|
|
||||||
)
|
|
||||||
|
|
||||||
return BenchmarkGroupResult(
|
return BenchmarkGroupResult(
|
||||||
type_=type_,
|
type_=type_,
|
||||||
iterations=iterations,
|
iterations=iterations,
|
||||||
is_gc_disabled=is_gc_disabled,
|
is_gc_disabled=is_gc_disabled,
|
||||||
benchmark_results=benchmark_results,
|
benchmark_results=benchmark_results
|
||||||
)
|
)
|
||||||
|
|
||||||
def run_benchmarks_grouped_by_type(
|
def run_benchmarks_grouped_by_type(self, iterations: int = 100, is_gc_disabled: bool = False) -> list[BenchmarkGroupResult]:
|
||||||
self, iterations: int = 100, is_gc_disabled: bool = False
|
|
||||||
) -> list[BenchmarkGroupResult]:
|
|
||||||
results: list[BenchmarkGroupResult] = []
|
results: list[BenchmarkGroupResult] = []
|
||||||
for type_, _ in self._benchmarks_grouped_by_type.items():
|
for type_, benchmarks in self._benchmarks_grouped_by_type.items():
|
||||||
results.append(self.run_benchmarks_by_type(type_, iterations, is_gc_disabled))
|
results.append(self.run_benchmarks_by_type(type_, iterations, is_gc_disabled))
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ __all__ = [
|
|||||||
"benchmark_command_with_flags",
|
"benchmark_command_with_flags",
|
||||||
"benchmark_many_commands",
|
"benchmark_many_commands",
|
||||||
"benchmark_command_with_many_flags",
|
"benchmark_command_with_many_flags",
|
||||||
"benchmark_extreme_router",
|
"benchmark_extreme_router"
|
||||||
]
|
]
|
||||||
|
|
||||||
from argenta.command import Flag, Flags
|
|
||||||
from argenta.command.models import Command, InputCommand
|
from argenta.command.models import Command, InputCommand
|
||||||
|
from argenta.command import Flag, Flags
|
||||||
from argenta.response import Response
|
from argenta.response import Response
|
||||||
from argenta.router import Router
|
from argenta.router import Router
|
||||||
|
|
||||||
@@ -18,11 +18,11 @@ from .entity import benchmarks
|
|||||||
def benchmark_simple_command() -> None:
|
def benchmark_simple_command() -> None:
|
||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
@router.command(Command("test"))
|
@router.command(Command('test'))
|
||||||
def handler(_res: Response) -> None:
|
def handler(_res: Response) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
input_cmd = InputCommand.parse("test")
|
input_cmd = InputCommand.parse('test')
|
||||||
router.finds_appropriate_handler(input_cmd)
|
router.finds_appropriate_handler(input_cmd)
|
||||||
|
|
||||||
|
|
||||||
@@ -30,11 +30,11 @@ def benchmark_simple_command() -> None:
|
|||||||
def benchmark_command_with_flags() -> None:
|
def benchmark_command_with_flags() -> None:
|
||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
@router.command(Command("test", flags=Flags([Flag("a"), Flag("b"), Flag("c")])))
|
@router.command(Command('test', flags=Flags([Flag('a'), Flag('b'), Flag('c')])))
|
||||||
def handler(_res: Response) -> None:
|
def handler(_res: Response) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
input_cmd = InputCommand.parse("test -a -b -c")
|
input_cmd = InputCommand.parse('test -a -b -c')
|
||||||
router.finds_appropriate_handler(input_cmd)
|
router.finds_appropriate_handler(input_cmd)
|
||||||
|
|
||||||
|
|
||||||
@@ -43,43 +43,38 @@ def benchmark_many_commands() -> None:
|
|||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
for i in range(50):
|
for i in range(50):
|
||||||
|
@router.command(Command(f'cmd{i}'))
|
||||||
@router.command(Command(f"cmd{i}"))
|
|
||||||
def handler(_res: Response) -> None:
|
def handler(_res: Response) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
input_cmd = InputCommand.parse("cmd25")
|
input_cmd = InputCommand.parse('cmd25')
|
||||||
router.finds_appropriate_handler(input_cmd)
|
router.finds_appropriate_handler(input_cmd)
|
||||||
|
|
||||||
|
|
||||||
@benchmarks.register(
|
@benchmarks.register(type_="finds_appropriate_handler", description="Command with many flags (20 flags)")
|
||||||
type_="finds_appropriate_handler", description="Command with many flags (20 flags)"
|
|
||||||
)
|
|
||||||
def benchmark_command_with_many_flags() -> None:
|
def benchmark_command_with_many_flags() -> None:
|
||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
flags = Flags([Flag(f"flag{i}") for i in range(20)])
|
flags = Flags([Flag(f'flag{i}') for i in range(20)])
|
||||||
|
|
||||||
@router.command(Command("test", flags=flags))
|
@router.command(Command('test', flags=flags))
|
||||||
def handler(_res: Response) -> None:
|
def handler(_res: Response) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
input_cmd = InputCommand.parse("test " + " ".join(f"-flag{i}" for i in range(10)))
|
input_cmd = InputCommand.parse('test ' + ' '.join(f'-flag{i}' for i in range(10)))
|
||||||
router.finds_appropriate_handler(input_cmd)
|
router.finds_appropriate_handler(input_cmd)
|
||||||
|
|
||||||
|
|
||||||
@benchmarks.register(
|
@benchmarks.register(type_="finds_appropriate_handler", description="Extreme (100 commands, 10 flags each)")
|
||||||
type_="finds_appropriate_handler", description="Extreme (100 commands, 10 flags each)"
|
|
||||||
)
|
|
||||||
def benchmark_extreme_router() -> None:
|
def benchmark_extreme_router() -> None:
|
||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
for i in range(100):
|
for i in range(100):
|
||||||
flags = Flags([Flag(f"f{i}_{j}") for j in range(10)])
|
flags = Flags([Flag(f'f{i}_{j}') for j in range(10)])
|
||||||
|
|
||||||
@router.command(Command(f"cmd{i}", flags=flags))
|
@router.command(Command(f'cmd{i}', flags=flags))
|
||||||
def handler(_res: Response) -> None:
|
def handler(_res: Response) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
input_cmd = InputCommand.parse("cmd50 -f50_0 -f50_1 -f50_2")
|
input_cmd = InputCommand.parse('cmd50 -f50_0 -f50_1 -f50_2')
|
||||||
router.finds_appropriate_handler(input_cmd)
|
router.finds_appropriate_handler(input_cmd)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ __all__ = [
|
|||||||
"benchmark_validate_regex_complex",
|
"benchmark_validate_regex_complex",
|
||||||
"benchmark_validate_multiple_flags_10",
|
"benchmark_validate_multiple_flags_10",
|
||||||
"benchmark_validate_multiple_flags_50",
|
"benchmark_validate_multiple_flags_50",
|
||||||
"benchmark_validate_extreme_100_flags",
|
"benchmark_validate_extreme_100_flags"
|
||||||
]
|
]
|
||||||
|
|
||||||
import re
|
import re
|
||||||
@@ -58,8 +58,14 @@ def benchmark_validate_regex_complex() -> None:
|
|||||||
|
|
||||||
@benchmarks.register(type_="flag_validation", description="Multiple flags validation (10 flags)")
|
@benchmarks.register(type_="flag_validation", description="Multiple flags validation (10 flags)")
|
||||||
def benchmark_validate_multiple_flags_10() -> None:
|
def benchmark_validate_multiple_flags_10() -> None:
|
||||||
flags = [Flag(f"flag{i}", possible_values=PossibleValues.ALL) for i in range(10)]
|
flags = [
|
||||||
input_flags = [InputFlag(f"flag{i}", input_value=f"value{i}") for i in range(10)]
|
Flag(f"flag{i}", possible_values=PossibleValues.ALL)
|
||||||
|
for i in range(10)
|
||||||
|
]
|
||||||
|
input_flags = [
|
||||||
|
InputFlag(f"flag{i}", input_value=f"value{i}")
|
||||||
|
for i in range(10)
|
||||||
|
]
|
||||||
|
|
||||||
for flag, input_flag in zip(flags, input_flags):
|
for flag, input_flag in zip(flags, input_flags):
|
||||||
flag.validate_input_flag_value(input_flag.input_value)
|
flag.validate_input_flag_value(input_flag.input_value)
|
||||||
@@ -67,20 +73,30 @@ def benchmark_validate_multiple_flags_10() -> None:
|
|||||||
|
|
||||||
@benchmarks.register(type_="flag_validation", description="Multiple flags validation (50 flags)")
|
@benchmarks.register(type_="flag_validation", description="Multiple flags validation (50 flags)")
|
||||||
def benchmark_validate_multiple_flags_50() -> None:
|
def benchmark_validate_multiple_flags_50() -> None:
|
||||||
flags = [Flag(f"flag{i}", possible_values=PossibleValues.ALL) for i in range(50)]
|
flags = [
|
||||||
input_flags = [InputFlag(f"flag{i}", input_value=f"value{i}") for i in range(50)]
|
Flag(f"flag{i}", possible_values=PossibleValues.ALL)
|
||||||
|
for i in range(50)
|
||||||
|
]
|
||||||
|
input_flags = [
|
||||||
|
InputFlag(f"flag{i}", input_value=f"value{i}")
|
||||||
|
for i in range(50)
|
||||||
|
]
|
||||||
|
|
||||||
for flag, input_flag in zip(flags, input_flags):
|
for flag, input_flag in zip(flags, input_flags):
|
||||||
flag.validate_input_flag_value(input_flag.input_value)
|
flag.validate_input_flag_value(input_flag.input_value)
|
||||||
|
|
||||||
|
|
||||||
@benchmarks.register(
|
@benchmarks.register(type_="flag_validation", description="Extreme (100 flags with regex validation)")
|
||||||
type_="flag_validation", description="Extreme (100 flags with regex validation)"
|
|
||||||
)
|
|
||||||
def benchmark_validate_extreme_100_flags() -> None:
|
def benchmark_validate_extreme_100_flags() -> None:
|
||||||
pattern = re.compile(r"^[a-zA-Z0-9_-]+$")
|
pattern = re.compile(r"^[a-zA-Z0-9_-]+$")
|
||||||
flags = [Flag(f"flag{i}", possible_values=pattern) for i in range(100)]
|
flags = [
|
||||||
input_flags = [InputFlag(f"flag{i}", input_value=f"valid_value_{i}") for i in range(100)]
|
Flag(f"flag{i}", possible_values=pattern)
|
||||||
|
for i in range(100)
|
||||||
|
]
|
||||||
|
input_flags = [
|
||||||
|
InputFlag(f"flag{i}", input_value=f"valid_value_{i}")
|
||||||
|
for i in range(100)
|
||||||
|
]
|
||||||
|
|
||||||
for flag, input_flag in zip(flags, input_flags):
|
for flag, input_flag in zip(flags, input_flags):
|
||||||
flag.validate_input_flag_value(input_flag.input_value)
|
flag.validate_input_flag_value(input_flag.input_value)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ __all__ = [
|
|||||||
"benchmark_command_with_mixed_prefixes",
|
"benchmark_command_with_mixed_prefixes",
|
||||||
"benchmark_command_with_long_values",
|
"benchmark_command_with_long_values",
|
||||||
"benchmark_command_with_quoted_values",
|
"benchmark_command_with_quoted_values",
|
||||||
"benchmark_extreme_many_flags",
|
"benchmark_extreme_many_flags"
|
||||||
]
|
]
|
||||||
|
|
||||||
from argenta.command.models import InputCommand
|
from argenta.command.models import InputCommand
|
||||||
@@ -23,16 +23,12 @@ def benchmark_command_with_few_flags() -> None:
|
|||||||
InputCommand.parse("start -a -b -c")
|
InputCommand.parse("start -a -b -c")
|
||||||
|
|
||||||
|
|
||||||
@benchmarks.register(
|
@benchmarks.register(type_="input_command_parse", description="Command with flags and values (5 flags)")
|
||||||
type_="input_command_parse", description="Command with flags and values (5 flags)"
|
|
||||||
)
|
|
||||||
def benchmark_command_with_flags_and_values() -> None:
|
def benchmark_command_with_flags_and_values() -> None:
|
||||||
InputCommand.parse("start --host localhost --port 8080 --debug --verbose -c config.json")
|
InputCommand.parse("start --host localhost --port 8080 --debug --verbose -c config.json")
|
||||||
|
|
||||||
|
|
||||||
@benchmarks.register(
|
@benchmarks.register(type_="input_command_parse", description="Command with mixed prefixes (-, --, ---)")
|
||||||
type_="input_command_parse", description="Command with mixed prefixes (-, --, ---)"
|
|
||||||
)
|
|
||||||
def benchmark_command_with_mixed_prefixes() -> None:
|
def benchmark_command_with_mixed_prefixes() -> None:
|
||||||
InputCommand.parse("cmd -a --bb ---ccc -d value --ee value2 ---fff value3")
|
InputCommand.parse("cmd -a --bb ---ccc -d value --ee value2 ---fff value3")
|
||||||
|
|
||||||
@@ -44,9 +40,7 @@ def benchmark_command_with_long_values() -> None:
|
|||||||
InputCommand.parse(cmd)
|
InputCommand.parse(cmd)
|
||||||
|
|
||||||
|
|
||||||
@benchmarks.register(
|
@benchmarks.register(type_="input_command_parse", description="Command with quoted values (5 flags)")
|
||||||
type_="input_command_parse", description="Command with quoted values (5 flags)"
|
|
||||||
)
|
|
||||||
def benchmark_command_with_quoted_values() -> None:
|
def benchmark_command_with_quoted_values() -> None:
|
||||||
InputCommand.parse("cmd --text 'hello world' --path '/usr/local/bin' --msg \"test message\"")
|
InputCommand.parse("cmd --text 'hello world' --path '/usr/local/bin' --msg \"test message\"")
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ __all__ = [
|
|||||||
"benchmark_many_commands_most_similar",
|
"benchmark_many_commands_most_similar",
|
||||||
"benchmark_many_aliases",
|
"benchmark_many_aliases",
|
||||||
"benchmark_partial_match",
|
"benchmark_partial_match",
|
||||||
"benchmark_extreme_commands",
|
"benchmark_extreme_commands"
|
||||||
]
|
]
|
||||||
|
|
||||||
from argenta import App
|
from argenta import App
|
||||||
@@ -19,11 +19,9 @@ def setup_app_with_commands(command_count: int, aliases_per_command: int = 0) ->
|
|||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
for i in range(command_count):
|
for i in range(command_count):
|
||||||
aliases = (
|
aliases = {f'alias{i}_{j}' for j in range(aliases_per_command)} if aliases_per_command else set()
|
||||||
{f"alias{i}_{j}" for j in range(aliases_per_command)} if aliases_per_command else set()
|
|
||||||
)
|
|
||||||
|
|
||||||
@router.command(Command(f"command{i}", aliases=aliases))
|
@router.command(Command(f'command{i}', aliases=aliases))
|
||||||
def handler(_res: Response) -> None:
|
def handler(_res: Response) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -31,41 +29,31 @@ def setup_app_with_commands(command_count: int, aliases_per_command: int = 0) ->
|
|||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
||||||
@benchmarks.register(
|
@benchmarks.register(type_="most_similar_command", description="Few commands (10 commands, no match)")
|
||||||
type_="most_similar_command", description="Few commands (10 commands, no match)"
|
|
||||||
)
|
|
||||||
def benchmark_few_commands() -> None:
|
def benchmark_few_commands() -> None:
|
||||||
app = setup_app_with_commands(10)
|
app = setup_app_with_commands(10)
|
||||||
app._most_similar_command("unknown")
|
app._most_similar_command("unknown")
|
||||||
|
|
||||||
|
|
||||||
@benchmarks.register(
|
@benchmarks.register(type_="most_similar_command", description="Many commands (50 commands, no match)")
|
||||||
type_="most_similar_command", description="Many commands (50 commands, no match)"
|
|
||||||
)
|
|
||||||
def benchmark_many_commands_most_similar() -> None:
|
def benchmark_many_commands_most_similar() -> None:
|
||||||
app = setup_app_with_commands(50)
|
app = setup_app_with_commands(50)
|
||||||
app._most_similar_command("unknown")
|
app._most_similar_command("unknown")
|
||||||
|
|
||||||
|
|
||||||
@benchmarks.register(
|
@benchmarks.register(type_="most_similar_command", description="Many aliases (20 commands, 10 aliases each)")
|
||||||
type_="most_similar_command", description="Many aliases (20 commands, 10 aliases each)"
|
|
||||||
)
|
|
||||||
def benchmark_many_aliases() -> None:
|
def benchmark_many_aliases() -> None:
|
||||||
app = setup_app_with_commands(20, aliases_per_command=10)
|
app = setup_app_with_commands(20, aliases_per_command=10)
|
||||||
app._most_similar_command("unknown")
|
app._most_similar_command("unknown")
|
||||||
|
|
||||||
|
|
||||||
@benchmarks.register(
|
@benchmarks.register(type_="most_similar_command", description="Partial match (50 commands, prefix match)")
|
||||||
type_="most_similar_command", description="Partial match (50 commands, prefix match)"
|
|
||||||
)
|
|
||||||
def benchmark_partial_match() -> None:
|
def benchmark_partial_match() -> None:
|
||||||
app = setup_app_with_commands(50)
|
app = setup_app_with_commands(50)
|
||||||
app._most_similar_command("comm")
|
app._most_similar_command("comm")
|
||||||
|
|
||||||
|
|
||||||
@benchmarks.register(
|
@benchmarks.register(type_="most_similar_command", description="Extreme (100 commands, 20 aliases each)")
|
||||||
type_="most_similar_command", description="Extreme (100 commands, 20 aliases each)"
|
|
||||||
)
|
|
||||||
def benchmark_extreme_commands() -> None:
|
def benchmark_extreme_commands() -> None:
|
||||||
app = setup_app_with_commands(100, aliases_per_command=20)
|
app = setup_app_with_commands(100, aliases_per_command=20)
|
||||||
app._most_similar_command("comm")
|
app._most_similar_command("comm")
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ __all__ = [
|
|||||||
"benchmark_with_many_aliases",
|
"benchmark_with_many_aliases",
|
||||||
"benchmark_few_aliases",
|
"benchmark_few_aliases",
|
||||||
"benchmark_extreme_aliases",
|
"benchmark_extreme_aliases",
|
||||||
"benchmark_very_many_aliases",
|
"benchmark_very_many_aliases"
|
||||||
]
|
]
|
||||||
|
|
||||||
from argenta import App
|
from argenta import App
|
||||||
@@ -19,15 +19,15 @@ def benchmark_no_aliases() -> None:
|
|||||||
app = App(override_system_messages=True)
|
app = App(override_system_messages=True)
|
||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
@router.command(Command("command1"))
|
@router.command(Command('command1'))
|
||||||
def handler1(_res: Response) -> None:
|
def handler1(_res: Response) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@router.command(Command("command2"))
|
@router.command(Command('command2'))
|
||||||
def handler2(_res: Response) -> None:
|
def handler2(_res: Response) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@router.command(Command("command3"))
|
@router.command(Command('command3'))
|
||||||
def handler3(_res: Response) -> None:
|
def handler3(_res: Response) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -40,15 +40,15 @@ def benchmark_few_aliases() -> None:
|
|||||||
app = App(override_system_messages=True)
|
app = App(override_system_messages=True)
|
||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
@router.command(Command("command1", aliases={"c1", "cmd1"}))
|
@router.command(Command('command1', aliases={'c1', 'cmd1'}))
|
||||||
def handler1(_res: Response) -> None:
|
def handler1(_res: Response) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@router.command(Command("command2", aliases={"c2", "cmd2"}))
|
@router.command(Command('command2', aliases={'c2', 'cmd2'}))
|
||||||
def handler2(_res: Response) -> None:
|
def handler2(_res: Response) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@router.command(Command("command3", aliases={"c3", "cmd3"}))
|
@router.command(Command('command3', aliases={'c3', 'cmd3'}))
|
||||||
def handler3(_res: Response) -> None:
|
def handler3(_res: Response) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -61,15 +61,15 @@ def benchmark_with_many_aliases() -> None:
|
|||||||
app = App(override_system_messages=True)
|
app = App(override_system_messages=True)
|
||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
@router.command(Command("command1", aliases={"c1", "cmd1", "com1", "first", "one"}))
|
@router.command(Command('command1', aliases={'c1', 'cmd1', 'com1', 'first', 'one'}))
|
||||||
def handler1(_res: Response) -> None:
|
def handler1(_res: Response) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@router.command(Command("command2", aliases={"c2", "cmd2", "com2", "second", "two"}))
|
@router.command(Command('command2', aliases={'c2', 'cmd2', 'com2', 'second', 'two'}))
|
||||||
def handler2(_res: Response) -> None:
|
def handler2(_res: Response) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@router.command(Command("command3", aliases={"c3", "cmd3", "com3", "third", "three"}))
|
@router.command(Command('command3', aliases={'c3', 'cmd3', 'com3', 'third', 'three'}))
|
||||||
def handler3(_res: Response) -> None:
|
def handler3(_res: Response) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -82,15 +82,15 @@ def benchmark_very_many_aliases() -> None:
|
|||||||
app = App(override_system_messages=True)
|
app = App(override_system_messages=True)
|
||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
@router.command(Command("command1", aliases={f"alias1_{i}" for i in range(20)}))
|
@router.command(Command('command1', aliases={f'alias1_{i}' for i in range(20)}))
|
||||||
def handler1(_res: Response) -> None:
|
def handler1(_res: Response) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@router.command(Command("command2", aliases={f"alias2_{i}" for i in range(20)}))
|
@router.command(Command('command2', aliases={f'alias2_{i}' for i in range(20)}))
|
||||||
def handler2(_res: Response) -> None:
|
def handler2(_res: Response) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@router.command(Command("command3", aliases={f"alias3_{i}" for i in range(20)}))
|
@router.command(Command('command3', aliases={f'alias3_{i}' for i in range(20)}))
|
||||||
def handler3(_res: Response) -> None:
|
def handler3(_res: Response) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -103,15 +103,15 @@ def benchmark_extreme_aliases() -> None:
|
|||||||
app = App(override_system_messages=True)
|
app = App(override_system_messages=True)
|
||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
@router.command(Command("command1", aliases={f"alias1_{i}" for i in range(100)}))
|
@router.command(Command('command1', aliases={f'alias1_{i}' for i in range(100)}))
|
||||||
def handler1(_res: Response) -> None:
|
def handler1(_res: Response) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@router.command(Command("command2", aliases={f"alias2_{i}" for i in range(100)}))
|
@router.command(Command('command2', aliases={f'alias2_{i}' for i in range(100)}))
|
||||||
def handler2(_res: Response) -> None:
|
def handler2(_res: Response) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@router.command(Command("command3", aliases={f"alias3_{i}" for i in range(100)}))
|
@router.command(Command('command3', aliases={f'alias3_{i}' for i in range(100)}))
|
||||||
def handler3(_res: Response) -> None:
|
def handler3(_res: Response) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ __all__ = [
|
|||||||
"benchmark_many_routers",
|
"benchmark_many_routers",
|
||||||
"benchmark_many_commands_per_router",
|
"benchmark_many_commands_per_router",
|
||||||
"benchmark_many_aliases_per_command",
|
"benchmark_many_aliases_per_command",
|
||||||
"benchmark_extreme_routers",
|
"benchmark_extreme_routers"
|
||||||
]
|
]
|
||||||
|
|
||||||
from argenta import App
|
from argenta import App
|
||||||
@@ -14,17 +14,14 @@ from argenta.router import Router
|
|||||||
from .entity import benchmarks
|
from .entity import benchmarks
|
||||||
|
|
||||||
|
|
||||||
@benchmarks.register(
|
@benchmarks.register(type_="validate_routers_for_collisions", description="With few routers (3 routers, 1 command each)")
|
||||||
type_="validate_routers_for_collisions",
|
|
||||||
description="With few routers (3 routers, 1 command each)",
|
|
||||||
)
|
|
||||||
def benchmark_few_routers() -> None:
|
def benchmark_few_routers() -> None:
|
||||||
app = App(override_system_messages=True)
|
app = App(override_system_messages=True)
|
||||||
|
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
@router.command(Command(f"cmd{i}"))
|
@router.command(Command(f'cmd{i}'))
|
||||||
def handler(_res: Response) -> None:
|
def handler(_res: Response) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -34,17 +31,14 @@ def benchmark_few_routers() -> None:
|
|||||||
app._validate_routers_for_collisions()
|
app._validate_routers_for_collisions()
|
||||||
|
|
||||||
|
|
||||||
@benchmarks.register(
|
@benchmarks.register(type_="validate_routers_for_collisions", description="With many routers (10 routers, 1 command each)")
|
||||||
type_="validate_routers_for_collisions",
|
|
||||||
description="With many routers (10 routers, 1 command each)",
|
|
||||||
)
|
|
||||||
def benchmark_many_routers() -> None:
|
def benchmark_many_routers() -> None:
|
||||||
app = App(override_system_messages=True)
|
app = App(override_system_messages=True)
|
||||||
|
|
||||||
for i in range(10):
|
for i in range(10):
|
||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
@router.command(Command(f"cmd{i}"))
|
@router.command(Command(f'cmd{i}'))
|
||||||
def handler(_res: Response) -> None:
|
def handler(_res: Response) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -54,10 +48,7 @@ def benchmark_many_routers() -> None:
|
|||||||
app._validate_routers_for_collisions()
|
app._validate_routers_for_collisions()
|
||||||
|
|
||||||
|
|
||||||
@benchmarks.register(
|
@benchmarks.register(type_="validate_routers_for_collisions", description="With many commands per router (3 routers, 10 commands each)")
|
||||||
type_="validate_routers_for_collisions",
|
|
||||||
description="With many commands per router (3 routers, 10 commands each)",
|
|
||||||
)
|
|
||||||
def benchmark_many_commands_per_router() -> None:
|
def benchmark_many_commands_per_router() -> None:
|
||||||
app = App(override_system_messages=True)
|
app = App(override_system_messages=True)
|
||||||
|
|
||||||
@@ -65,8 +56,7 @@ def benchmark_many_commands_per_router() -> None:
|
|||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
for j in range(10):
|
for j in range(10):
|
||||||
|
@router.command(Command(f'cmd{i}_{j}'))
|
||||||
@router.command(Command(f"cmd{i}_{j}"))
|
|
||||||
def handler(_res: Response) -> None:
|
def handler(_res: Response) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -76,10 +66,7 @@ def benchmark_many_commands_per_router() -> None:
|
|||||||
app._validate_routers_for_collisions()
|
app._validate_routers_for_collisions()
|
||||||
|
|
||||||
|
|
||||||
@benchmarks.register(
|
@benchmarks.register(type_="validate_routers_for_collisions", description="With many aliases (3 routers, 5 commands, 10 aliases each)")
|
||||||
type_="validate_routers_for_collisions",
|
|
||||||
description="With many aliases (3 routers, 5 commands, 10 aliases each)",
|
|
||||||
)
|
|
||||||
def benchmark_many_aliases_per_command() -> None:
|
def benchmark_many_aliases_per_command() -> None:
|
||||||
app = App(override_system_messages=True)
|
app = App(override_system_messages=True)
|
||||||
|
|
||||||
@@ -87,10 +74,7 @@ def benchmark_many_aliases_per_command() -> None:
|
|||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
for j in range(5):
|
for j in range(5):
|
||||||
|
@router.command(Command(f'cmd{i}_{j}', aliases={f'alias{i}_{j}_{k}' for k in range(10)}))
|
||||||
@router.command(
|
|
||||||
Command(f"cmd{i}_{j}", aliases={f"alias{i}_{j}_{k}" for k in range(10)})
|
|
||||||
)
|
|
||||||
def handler(_res: Response) -> None:
|
def handler(_res: Response) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -100,10 +84,7 @@ def benchmark_many_aliases_per_command() -> None:
|
|||||||
app._validate_routers_for_collisions()
|
app._validate_routers_for_collisions()
|
||||||
|
|
||||||
|
|
||||||
@benchmarks.register(
|
@benchmarks.register(type_="validate_routers_for_collisions", description="Extreme (20 routers, 10 commands, 20 aliases each)")
|
||||||
type_="validate_routers_for_collisions",
|
|
||||||
description="Extreme (20 routers, 10 commands, 20 aliases each)",
|
|
||||||
)
|
|
||||||
def benchmark_extreme_routers() -> None:
|
def benchmark_extreme_routers() -> None:
|
||||||
app = App(override_system_messages=True)
|
app = App(override_system_messages=True)
|
||||||
|
|
||||||
@@ -111,10 +92,7 @@ def benchmark_extreme_routers() -> None:
|
|||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
for j in range(10):
|
for j in range(10):
|
||||||
|
@router.command(Command(f'cmd{i}_{j}', aliases={f'alias{i}_{j}_{k}' for k in range(20)}))
|
||||||
@router.command(
|
|
||||||
Command(f"cmd{i}_{j}", aliases={f"alias{i}_{j}_{k}" for k in range(20)})
|
|
||||||
)
|
|
||||||
def handler(_res: Response) -> None:
|
def handler(_res: Response) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
+33
-56
@@ -5,18 +5,17 @@ from pathlib import Path
|
|||||||
|
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
|
|
||||||
from argenta.command import Flag, Flags, PossibleValues
|
from argenta.command import Flag, PossibleValues, Flags
|
||||||
from argenta.command.flag import ValidationStatus
|
from argenta.command.flag import ValidationStatus
|
||||||
from argenta.command.models import Command
|
from argenta.command.models import Command
|
||||||
from argenta.response import Response
|
from argenta.response import Response
|
||||||
from argenta.router import Router
|
from argenta.router import Router
|
||||||
|
|
||||||
from .benchmarks.core.models import BenchmarkGroupResult
|
from .benchmarks.core.models import BenchmarkGroupResult
|
||||||
from .benchmarks.entity import benchmarks as registered_benchmarks
|
from .benchmarks.entity import benchmarks as registered_benchmarks
|
||||||
from .services.diagram_generator import DiagramGenerator
|
|
||||||
from .services.release_generator import ReleaseGenerator
|
|
||||||
from .services.report_table_generator import ReportTableGenerator
|
from .services.report_table_generator import ReportTableGenerator
|
||||||
from .services.system_info_reader import get_system_info
|
from .services.system_info_reader import get_system_info
|
||||||
|
from .services.diagram_generator import DiagramGenerator
|
||||||
|
from .services.release_generator import ReleaseGenerator
|
||||||
|
|
||||||
console = Console()
|
console = Console()
|
||||||
router = Router(title="Metrics commands:", disable_redirect_stdout=True)
|
router = Router(title="Metrics commands:", disable_redirect_stdout=True)
|
||||||
@@ -28,30 +27,22 @@ POSITIVE_INTEGER_PATTERN = re.compile(r"^[1-9]\d*$")
|
|||||||
Command(
|
Command(
|
||||||
"run-all",
|
"run-all",
|
||||||
description="Print all benchmarks results",
|
description="Print all benchmarks results",
|
||||||
flags=Flags(
|
flags=Flags([
|
||||||
[
|
Flag('without-gc', possible_values=PossibleValues.NEITHER),
|
||||||
Flag("without-gc", possible_values=PossibleValues.NEITHER),
|
Flag('without-system-info', possible_values=PossibleValues.NEITHER)
|
||||||
Flag("without-system-info", possible_values=PossibleValues.NEITHER),
|
])
|
||||||
]
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
def all_print_handler(response: Response) -> None:
|
def all_print_handler(response: Response) -> None:
|
||||||
report_generator = ReportTableGenerator(get_system_info())
|
report_generator = ReportTableGenerator(get_system_info())
|
||||||
|
|
||||||
without_system_info = response.input_flags.get_flag_by_name(
|
without_system_info = response.input_flags.get_flag_by_name("without-system-info", with_status=ValidationStatus.VALID)
|
||||||
"without-system-info", with_status=ValidationStatus.VALID
|
|
||||||
)
|
|
||||||
if not without_system_info:
|
if not without_system_info:
|
||||||
console.print(report_generator.generate_system_info_header())
|
console.print(report_generator.generate_system_info_header())
|
||||||
console.print(report_generator.generate_system_info_table())
|
console.print(report_generator.generate_system_info_table())
|
||||||
|
|
||||||
is_gc_disabled = response.input_flags.get_flag_by_name(
|
is_gc_disabled = response.input_flags.get_flag_by_name("without-gc", with_status=ValidationStatus.VALID)
|
||||||
"without-gc", with_status=ValidationStatus.VALID
|
type_grouped_benchmarks: list[BenchmarkGroupResult] = registered_benchmarks.run_benchmarks_grouped_by_type(is_gc_disabled=bool(is_gc_disabled))
|
||||||
)
|
|
||||||
type_grouped_benchmarks: list[BenchmarkGroupResult] = (
|
|
||||||
registered_benchmarks.run_benchmarks_grouped_by_type(is_gc_disabled=bool(is_gc_disabled))
|
|
||||||
)
|
|
||||||
|
|
||||||
for benchmark_group_result in type_grouped_benchmarks:
|
for benchmark_group_result in type_grouped_benchmarks:
|
||||||
console.print(report_generator.generate_benchmark_table_header(benchmark_group_result))
|
console.print(report_generator.generate_benchmark_table_header(benchmark_group_result))
|
||||||
@@ -76,13 +67,11 @@ def list_types_handler(_: Response) -> None:
|
|||||||
Command(
|
Command(
|
||||||
"run-type",
|
"run-type",
|
||||||
description="Run benchmarks by specific type",
|
description="Run benchmarks by specific type",
|
||||||
flags=Flags(
|
flags=Flags([
|
||||||
[
|
Flag('type', possible_values=registered_benchmarks.get_types()),
|
||||||
Flag("type", possible_values=registered_benchmarks.get_types()),
|
Flag('without-gc', possible_values=PossibleValues.NEITHER),
|
||||||
Flag("without-gc", possible_values=PossibleValues.NEITHER),
|
Flag('without-system-info', possible_values=PossibleValues.NEITHER)
|
||||||
Flag("without-system-info", possible_values=PossibleValues.NEITHER),
|
])
|
||||||
]
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
def run_type_handler(response: Response) -> None:
|
def run_type_handler(response: Response) -> None:
|
||||||
@@ -105,19 +94,13 @@ def run_type_handler(response: Response) -> None:
|
|||||||
|
|
||||||
report_generator = ReportTableGenerator(get_system_info())
|
report_generator = ReportTableGenerator(get_system_info())
|
||||||
|
|
||||||
without_system_info = response.input_flags.get_flag_by_name(
|
without_system_info = response.input_flags.get_flag_by_name("without-system-info", with_status=ValidationStatus.VALID)
|
||||||
"without-system-info", with_status=ValidationStatus.VALID
|
|
||||||
)
|
|
||||||
if not without_system_info:
|
if not without_system_info:
|
||||||
console.print(report_generator.generate_system_info_header())
|
console.print(report_generator.generate_system_info_header())
|
||||||
console.print(report_generator.generate_system_info_table())
|
console.print(report_generator.generate_system_info_table())
|
||||||
|
|
||||||
is_gc_disabled = response.input_flags.get_flag_by_name(
|
is_gc_disabled = response.input_flags.get_flag_by_name("without-gc", with_status=ValidationStatus.VALID, default=False)
|
||||||
"without-gc", with_status=ValidationStatus.VALID, default=False
|
benchmark_group_result = registered_benchmarks.run_benchmarks_by_type(benchmark_type, is_gc_disabled=bool(is_gc_disabled))
|
||||||
)
|
|
||||||
benchmark_group_result = registered_benchmarks.run_benchmarks_by_type(
|
|
||||||
benchmark_type, is_gc_disabled=bool(is_gc_disabled)
|
|
||||||
)
|
|
||||||
|
|
||||||
console.print(report_generator.generate_benchmark_table_header(benchmark_group_result))
|
console.print(report_generator.generate_benchmark_table_header(benchmark_group_result))
|
||||||
console.print(report_generator.generate_benchmark_report_table(benchmark_group_result))
|
console.print(report_generator.generate_benchmark_report_table(benchmark_group_result))
|
||||||
@@ -130,21 +113,22 @@ def release_generate_handler(_: Response) -> None:
|
|||||||
console.print(f"[cyan]Generating release report for version:[/cyan] [bold]{lib_version}[/bold]")
|
console.print(f"[cyan]Generating release report for version:[/cyan] [bold]{lib_version}[/bold]")
|
||||||
console.print("[dim]Running benchmarks (1000 iterations, GC disabled)...[/dim]\n")
|
console.print("[dim]Running benchmarks (1000 iterations, GC disabled)...[/dim]\n")
|
||||||
|
|
||||||
type_grouped_benchmarks: list[BenchmarkGroupResult] = (
|
type_grouped_benchmarks: list[BenchmarkGroupResult] = registered_benchmarks.run_benchmarks_grouped_by_type(
|
||||||
registered_benchmarks.run_benchmarks_grouped_by_type(iterations=1000, is_gc_disabled=True)
|
iterations=1000,
|
||||||
|
is_gc_disabled=True
|
||||||
)
|
)
|
||||||
|
|
||||||
release_generator = ReleaseGenerator(lib_version)
|
release_generator = ReleaseGenerator(lib_version)
|
||||||
output_dir = release_generator.generate_release(type_grouped_benchmarks)
|
output_dir = release_generator.generate_release(type_grouped_benchmarks)
|
||||||
|
|
||||||
console.print("[green]✓[/green] Benchmarks completed. Generating release report...\n")
|
console.print(f"[green]✓[/green] Benchmarks completed. Generating release report...\n")
|
||||||
|
|
||||||
for benchmark_group in type_grouped_benchmarks:
|
for benchmark_group in type_grouped_benchmarks:
|
||||||
console.print(f"[cyan]Generated for:[/cyan] [bold]{benchmark_group.type_}[/bold]")
|
console.print(f"[cyan]Generated for:[/cyan] [bold]{benchmark_group.type_}[/bold]")
|
||||||
console.print(f" [green]✓[/green] {benchmark_group.type_}_comparison.png")
|
console.print(f" [green]✓[/green] {benchmark_group.type_}_comparison.png")
|
||||||
console.print(f" [green]✓[/green] {benchmark_group.type_}.json\n")
|
console.print(f" [green]✓[/green] {benchmark_group.type_}.json\n")
|
||||||
|
|
||||||
console.print("[bold green]✓ Release report generated successfully[/bold green]")
|
console.print(f"[bold green]✓ Release report generated successfully[/bold green]")
|
||||||
console.print(f"[cyan]Output directory:[/cyan] [bold]{output_dir}[/bold]")
|
console.print(f"[cyan]Output directory:[/cyan] [bold]{output_dir}[/bold]")
|
||||||
|
|
||||||
|
|
||||||
@@ -152,33 +136,26 @@ def release_generate_handler(_: Response) -> None:
|
|||||||
Command(
|
Command(
|
||||||
"diagrams-generate",
|
"diagrams-generate",
|
||||||
description="Generate diagrams for all benchmarks",
|
description="Generate diagrams for all benchmarks",
|
||||||
flags=Flags(
|
flags=Flags([
|
||||||
[
|
Flag('without-gc', possible_values=PossibleValues.NEITHER),
|
||||||
Flag("without-gc", possible_values=PossibleValues.NEITHER),
|
Flag('iterations', possible_values=POSITIVE_INTEGER_PATTERN)
|
||||||
Flag("iterations", possible_values=POSITIVE_INTEGER_PATTERN),
|
])
|
||||||
]
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
def diagrams_generate_handler(response: Response) -> None:
|
def diagrams_generate_handler(response: Response) -> None:
|
||||||
iterations = 100
|
iterations = 100
|
||||||
iterations_flag = response.input_flags.get_flag_by_name(
|
iterations_flag = response.input_flags.get_flag_by_name("iterations", with_status=ValidationStatus.VALID)
|
||||||
"iterations", with_status=ValidationStatus.VALID
|
|
||||||
)
|
|
||||||
if iterations_flag:
|
if iterations_flag:
|
||||||
iterations = int(iterations_flag.input_value)
|
iterations = int(iterations_flag.input_value)
|
||||||
|
|
||||||
is_gc_disabled = bool(
|
is_gc_disabled = bool(response.input_flags.get_flag_by_name("without-gc", with_status=ValidationStatus.VALID))
|
||||||
response.input_flags.get_flag_by_name("without-gc", with_status=ValidationStatus.VALID)
|
|
||||||
)
|
|
||||||
|
|
||||||
console.print("[cyan]Running all benchmarks...[/cyan]")
|
console.print("[cyan]Running all benchmarks...[/cyan]")
|
||||||
console.print(f"[dim]Iterations: {iterations}, GC Disabled: {is_gc_disabled}[/dim]\n")
|
console.print(f"[dim]Iterations: {iterations}, GC Disabled: {is_gc_disabled}[/dim]\n")
|
||||||
|
|
||||||
type_grouped_benchmarks: list[BenchmarkGroupResult] = (
|
type_grouped_benchmarks: list[BenchmarkGroupResult] = registered_benchmarks.run_benchmarks_grouped_by_type(
|
||||||
registered_benchmarks.run_benchmarks_grouped_by_type(
|
iterations=iterations,
|
||||||
iterations=iterations, is_gc_disabled=is_gc_disabled
|
is_gc_disabled=is_gc_disabled
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
||||||
@@ -187,7 +164,7 @@ def diagrams_generate_handler(response: Response) -> None:
|
|||||||
|
|
||||||
diagram_generator = DiagramGenerator(output_dir)
|
diagram_generator = DiagramGenerator(output_dir)
|
||||||
|
|
||||||
console.print("[green]✓[/green] Benchmarks completed. Generating diagrams...\n")
|
console.print(f"[green]✓[/green] Benchmarks completed. Generating diagrams...\n")
|
||||||
|
|
||||||
generated_count = 0
|
generated_count = 0
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from .diagram_generator import DiagramGenerator
|
from .diagram_generator import DiagramGenerator
|
||||||
from .release_generator import ReleaseGenerator
|
|
||||||
from .report_table_generator import ReportTableGenerator
|
from .report_table_generator import ReportTableGenerator
|
||||||
from .system_info_reader import get_system_info
|
from .system_info_reader import get_system_info
|
||||||
|
from .release_generator import ReleaseGenerator
|
||||||
|
|
||||||
__all__ = ["DiagramGenerator", "ReportTableGenerator", "get_system_info", "ReleaseGenerator"]
|
__all__ = ["DiagramGenerator", "ReportTableGenerator", "get_system_info", "ReleaseGenerator"]
|
||||||
|
|||||||
@@ -2,9 +2,8 @@ __all__ = ["DiagramGenerator"]
|
|||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import cairosvg
|
import matplotlib
|
||||||
import pygal
|
import matplotlib.pyplot as plt
|
||||||
from pygal.style import Style
|
|
||||||
|
|
||||||
from ..benchmarks.core.models import BenchmarkGroupResult
|
from ..benchmarks.core.models import BenchmarkGroupResult
|
||||||
|
|
||||||
@@ -13,26 +12,8 @@ class DiagramGenerator:
|
|||||||
def __init__(self, output_dir: Path | str) -> None:
|
def __init__(self, output_dir: Path | str) -> None:
|
||||||
self.output_dir: Path = Path(output_dir) if isinstance(output_dir, str) else output_dir
|
self.output_dir: Path = Path(output_dir) if isinstance(output_dir, str) else output_dir
|
||||||
|
|
||||||
self._style = Style(
|
matplotlib.use('Agg')
|
||||||
background="white",
|
plt.style.use('seaborn-v0_8-whitegrid')
|
||||||
plot_background="white",
|
|
||||||
foreground="#2c3e50",
|
|
||||||
foreground_strong="#000000",
|
|
||||||
foreground_subtle="#7f8c8d",
|
|
||||||
opacity=".9",
|
|
||||||
opacity_hover=".95",
|
|
||||||
transition="150ms ease-in",
|
|
||||||
colors=("#2ecc71", "#3498db", "#e74c3c"),
|
|
||||||
title_font_size=40,
|
|
||||||
legend_font_size=34,
|
|
||||||
label_font_size=32, #
|
|
||||||
major_label_font_size=32,
|
|
||||||
value_font_size=28,
|
|
||||||
value_label_font_size=28,
|
|
||||||
tooltip_font_size=24,
|
|
||||||
no_data_font_size=28,
|
|
||||||
font_family="Consolas, 'Courier New', monospace",
|
|
||||||
)
|
|
||||||
|
|
||||||
def generate_comparison_diagram(self, benchmark_group: BenchmarkGroupResult) -> Path:
|
def generate_comparison_diagram(self, benchmark_group: BenchmarkGroupResult) -> Path:
|
||||||
results = benchmark_group.benchmark_results
|
results = benchmark_group.benchmark_results
|
||||||
@@ -46,48 +27,84 @@ class DiagramGenerator:
|
|||||||
max_value = max(
|
max_value = max(
|
||||||
max(avg_times) if avg_times else 0,
|
max(avg_times) if avg_times else 0,
|
||||||
max(median_times) if median_times else 0,
|
max(median_times) if median_times else 0,
|
||||||
max(std_devs) if std_devs else 0,
|
max(std_devs) if std_devs else 0
|
||||||
)
|
)
|
||||||
y_limit = max_value / 0.85 if max_value > 0 else 1.0
|
y_limit = max_value / 0.85 if max_value > 0 else 1.0
|
||||||
|
|
||||||
title_text = f"{benchmark_group.type_.replace('_', ' ').title()}"
|
items_count = len(descriptions)
|
||||||
metadata_text = (
|
x_positions: list[int] = list(range(items_count))
|
||||||
f"Iterations: {benchmark_group.iterations} | GC: "
|
|
||||||
f"{'Disabled' if benchmark_group.is_gc_disabled else 'Enabled'}"
|
bar_width = 0.25
|
||||||
|
|
||||||
|
x_std_dev = [x - bar_width for x in x_positions]
|
||||||
|
x_avg = [x for x in x_positions]
|
||||||
|
x_median = [x + bar_width for x in x_positions]
|
||||||
|
|
||||||
|
fig, ax = plt.subplots(figsize=(16, 8))
|
||||||
|
fig.patch.set_facecolor('white')
|
||||||
|
|
||||||
|
bars_std = ax.bar(x_std_dev, std_devs, bar_width, label='Std Deviation',
|
||||||
|
color='#2ecc71', alpha=0.9, edgecolor='#27ae60', linewidth=1.5)
|
||||||
|
bars_avg = ax.bar(x_avg, avg_times, bar_width, label='Average Time',
|
||||||
|
color='#3498db', alpha=0.9, edgecolor='#2980b9', linewidth=1.5)
|
||||||
|
bars_median = ax.bar(x_median, median_times, bar_width, label='Median Time',
|
||||||
|
color='#e74c3c', alpha=0.9, edgecolor='#c0392b', linewidth=1.5)
|
||||||
|
|
||||||
|
for bar_group in [bars_std, bars_avg, bars_median]:
|
||||||
|
for bar in bar_group:
|
||||||
|
height = bar.get_height()
|
||||||
|
ax.text(
|
||||||
|
bar.get_x() + bar.get_width() / 2.,
|
||||||
|
height,
|
||||||
|
f'{height:.3f}',
|
||||||
|
ha='center', va='bottom', fontsize=9, fontweight='bold'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ax.set_ylabel('Time (ms)', fontsize=14, fontweight='bold', labelpad=10)
|
||||||
|
|
||||||
|
title_text = f'{benchmark_group.type_.replace("_", " ").title()}'
|
||||||
|
metadata_text = f'Iterations: {benchmark_group.iterations} | GC: {"Disabled" if benchmark_group.is_gc_disabled else "Enabled"}'
|
||||||
|
|
||||||
|
ax.text(0.5, 1.08, title_text, transform=ax.transAxes,
|
||||||
|
fontsize=18, fontweight='bold', ha='center', color='#2c3e50')
|
||||||
|
ax.text(0.5, 1.03, metadata_text, transform=ax.transAxes,
|
||||||
|
fontsize=12, ha='center', color='#7f8c8d', style='italic')
|
||||||
|
|
||||||
|
ax.set_xticks(x_positions)
|
||||||
|
ax.set_xticklabels([])
|
||||||
|
|
||||||
|
for i, (pos, desc) in enumerate(zip(x_positions, descriptions)):
|
||||||
|
text_x_pos = pos - bar_width - (bar_width / 2)
|
||||||
|
ax.text(
|
||||||
|
text_x_pos,
|
||||||
|
y_limit * 0.02,
|
||||||
|
desc,
|
||||||
|
rotation=90, va='bottom', ha='right', fontsize=10,
|
||||||
|
color='#2c3e50'
|
||||||
|
)
|
||||||
|
|
||||||
|
ax.set_ylim(0, y_limit)
|
||||||
|
|
||||||
|
legend = ax.legend(loc='upper left', fontsize=12, framealpha=0.95,
|
||||||
|
edgecolor='#34495e', fancybox=True, shadow=True)
|
||||||
|
legend.get_frame().set_facecolor('#ecf0f1')
|
||||||
|
|
||||||
|
ax.grid(axis='y', alpha=0.4, linestyle='--', linewidth=0.8)
|
||||||
|
ax.set_axisbelow(True)
|
||||||
|
|
||||||
|
ax.spines['top'].set_visible(False)
|
||||||
|
ax.spines['right'].set_visible(False)
|
||||||
|
ax.spines['left'].set_color('#7f8c8d')
|
||||||
|
ax.spines['bottom'].set_color('#7f8c8d')
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
|
||||||
filename = f"{benchmark_group.type_}_comparison.png"
|
filename = f"{benchmark_group.type_}_comparison.png"
|
||||||
output_path = self.output_dir / filename
|
output_path = self.output_dir / filename
|
||||||
|
|
||||||
self.output_dir.mkdir(parents=True, exist_ok=True)
|
self.output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
dynamic_height = 600 + (len(descriptions) * 150)
|
plt.savefig(output_path, dpi=200, bbox_inches='tight', facecolor='white')
|
||||||
|
plt.close(fig)
|
||||||
chart = pygal.HorizontalBar(
|
|
||||||
style=self._style,
|
|
||||||
width=3100,
|
|
||||||
height=dynamic_height,
|
|
||||||
explicit_size=True,
|
|
||||||
show_legend=True,
|
|
||||||
legend_at_bottom=True,
|
|
||||||
print_values=True,
|
|
||||||
print_values_position="top",
|
|
||||||
legend_at_bottom_columns=3,
|
|
||||||
range=(0, y_limit),
|
|
||||||
zero=0,
|
|
||||||
)
|
|
||||||
|
|
||||||
chart.title = f"{title_text}\n{metadata_text}"
|
|
||||||
chart.x_title = "Time (ms)"
|
|
||||||
chart.no_data_text = "No data"
|
|
||||||
chart.value_formatter = lambda x: f"{x:.3f}"
|
|
||||||
|
|
||||||
chart.x_labels = descriptions
|
|
||||||
|
|
||||||
chart.add("Std Deviation", std_devs)
|
|
||||||
chart.add("Average Time", avg_times)
|
|
||||||
chart.add("Median Time", median_times)
|
|
||||||
|
|
||||||
svg_bytes = chart.render()
|
|
||||||
cairosvg.svg2png(bytestring=svg_bytes, write_to=str(output_path))
|
|
||||||
|
|
||||||
return output_path
|
return output_path
|
||||||
|
|||||||
@@ -36,14 +36,14 @@ class ReleaseGenerator:
|
|||||||
"description": br.description,
|
"description": br.description,
|
||||||
"avg_time": br.avg_time,
|
"avg_time": br.avg_time,
|
||||||
"median_time": br.median_time,
|
"median_time": br.median_time,
|
||||||
"std_dev": br.std_dev,
|
"std_dev": br.std_dev
|
||||||
}
|
}
|
||||||
for br in benchmark_group.benchmark_results
|
for br in benchmark_group.benchmark_results
|
||||||
],
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
json_path = type_dir / f"{benchmark_group.type_}.json"
|
json_path = type_dir / f"{benchmark_group.type_}.json"
|
||||||
with open(json_path, "w", encoding="utf-8") as f:
|
with open(json_path, 'w', encoding='utf-8') as f:
|
||||||
json.dump(json_data, f, indent=2, ensure_ascii=False)
|
json.dump(json_data, f, indent=2, ensure_ascii=False)
|
||||||
|
|
||||||
return self.output_dir
|
return self.output_dir
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from rich.table import Table
|
|||||||
from rich.text import Text
|
from rich.text import Text
|
||||||
|
|
||||||
from ..benchmarks.core.models import BenchmarkGroupResult
|
from ..benchmarks.core.models import BenchmarkGroupResult
|
||||||
from .system_info_reader import SystemInfo
|
from metrics.services.system_info_reader import SystemInfo
|
||||||
|
|
||||||
|
|
||||||
class ReportTableGenerator:
|
class ReportTableGenerator:
|
||||||
@@ -12,15 +12,11 @@ class ReportTableGenerator:
|
|||||||
self._cached_benchmark_tables: dict[int, Table] = {}
|
self._cached_benchmark_tables: dict[int, Table] = {}
|
||||||
self._cached_system_info_table: Table | None = None
|
self._cached_system_info_table: Table | None = None
|
||||||
|
|
||||||
def generate_benchmark_report_table(
|
def generate_benchmark_report_table(self, benchmark_group_result: BenchmarkGroupResult) -> Table:
|
||||||
self, benchmark_group_result: BenchmarkGroupResult
|
|
||||||
) -> Table:
|
|
||||||
if cached_result := self._cached_benchmark_tables.get(id(benchmark_group_result)):
|
if cached_result := self._cached_benchmark_tables.get(id(benchmark_group_result)):
|
||||||
return cached_result
|
return cached_result
|
||||||
|
|
||||||
table = Table(
|
table = Table(show_header=True, header_style="bold cyan", border_style="blue", show_lines=True)
|
||||||
show_header=True, header_style="bold cyan", border_style="blue", show_lines=True
|
|
||||||
)
|
|
||||||
table.add_column("Description", style="dim")
|
table.add_column("Description", style="dim")
|
||||||
table.add_column("Avg Time", justify="right", style="bold yellow")
|
table.add_column("Avg Time", justify="right", style="bold yellow")
|
||||||
table.add_column("Median Time", justify="right", style="bold yellow")
|
table.add_column("Median Time", justify="right", style="bold yellow")
|
||||||
@@ -38,22 +34,18 @@ class ReportTableGenerator:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def generate_benchmark_table_header(benchmark_group_result: BenchmarkGroupResult) -> Panel:
|
def generate_benchmark_table_header(benchmark_group_result: BenchmarkGroupResult) -> Panel:
|
||||||
header_text = Text(
|
header_text = Text(f"TYPE: {benchmark_group_result.type_.upper()} ; "
|
||||||
f"TYPE: {benchmark_group_result.type_.upper()} ; "
|
|
||||||
f"ITERATIONS: {benchmark_group_result.iterations} ; "
|
f"ITERATIONS: {benchmark_group_result.iterations} ; "
|
||||||
f"GC {'DISABLED' if benchmark_group_result.is_gc_disabled else 'ENABLED'} ; "
|
f"GC {"DISABLED" if benchmark_group_result.is_gc_disabled else "ENABLED"} ; "
|
||||||
f"ALL TIME IN MS",
|
f"ALL TIME IN MS",
|
||||||
style="bold magenta",
|
style="bold magenta")
|
||||||
)
|
|
||||||
return Panel(header_text, expand=False, border_style="magenta")
|
return Panel(header_text, expand=False, border_style="magenta")
|
||||||
|
|
||||||
def generate_system_info_table(self) -> Table:
|
def generate_system_info_table(self) -> Table:
|
||||||
if self._cached_system_info_table is not None:
|
if self._cached_system_info_table is not None:
|
||||||
return self._cached_system_info_table
|
return self._cached_system_info_table
|
||||||
|
|
||||||
table = Table(
|
table = Table(show_header=True, header_style="bold cyan", border_style="blue", show_lines=True)
|
||||||
show_header=True, header_style="bold cyan", border_style="blue", show_lines=True
|
|
||||||
)
|
|
||||||
table.add_column("Parameter", style="green")
|
table.add_column("Parameter", style="green")
|
||||||
table.add_column("Value", style="yellow")
|
table.add_column("Value", style="yellow")
|
||||||
|
|
||||||
@@ -63,10 +55,10 @@ class ReportTableGenerator:
|
|||||||
table.add_row("CPU", self.system_info.cpu_info.name)
|
table.add_row("CPU", self.system_info.cpu_info.name)
|
||||||
table.add_row("CPU Physical Cores", str(self.system_info.cpu_info.physical_cores))
|
table.add_row("CPU Physical Cores", str(self.system_info.cpu_info.physical_cores))
|
||||||
table.add_row("CPU Logical Cores", str(self.system_info.cpu_info.logical_cores))
|
table.add_row("CPU Logical Cores", str(self.system_info.cpu_info.logical_cores))
|
||||||
table.add_row("CPU Max Frequency", str(self.system_info.cpu_info.max_frequency) + " GHz")
|
table.add_row("CPU Max Frequency", str(self.system_info.cpu_info.max_frequency) + ' GHz')
|
||||||
table.add_row("Total RAM", str(self.system_info.memory_info.total_ram) + " GB")
|
table.add_row("Total RAM", str(self.system_info.memory_info.total_ram) + ' GB')
|
||||||
table.add_row("Used RAM", str(self.system_info.memory_info.used_ram) + " GB")
|
table.add_row("Used RAM", str(self.system_info.memory_info.used_ram) + ' GB')
|
||||||
table.add_row("Available RAM", str(self.system_info.memory_info.available_ram) + " GB")
|
table.add_row("Available RAM", str(self.system_info.memory_info.available_ram) + ' GB')
|
||||||
table.add_row("Python Version", self.system_info.python_info.version)
|
table.add_row("Python Version", self.system_info.python_info.version)
|
||||||
table.add_row("Python Implementation", self.system_info.python_info.implementation)
|
table.add_row("Python Implementation", self.system_info.python_info.implementation)
|
||||||
table.add_row("Python Compiler", self.system_info.python_info.compiler)
|
table.add_row("Python Compiler", self.system_info.python_info.compiler)
|
||||||
|
|||||||
@@ -1,19 +1,28 @@
|
|||||||
__all__ = ["SystemInfo", "get_system_info"]
|
__all__ = [
|
||||||
|
"SystemInfo",
|
||||||
|
"get_system_info"
|
||||||
|
]
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
import platform
|
import platform
|
||||||
import sys
|
import sys
|
||||||
from dataclasses import dataclass
|
|
||||||
|
|
||||||
import cpuinfo
|
import cpuinfo
|
||||||
import psutil
|
import psutil
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, slots=True)
|
||||||
|
class SystemInfo:
|
||||||
|
os_info: OSInfo
|
||||||
|
cpu_info: CPUInfo
|
||||||
|
memory_info: MemoryInfo
|
||||||
|
python_info: PythonInfo
|
||||||
|
|
||||||
@dataclass(frozen=True, slots=True)
|
@dataclass(frozen=True, slots=True)
|
||||||
class OSInfo:
|
class OSInfo:
|
||||||
name: str
|
name: str
|
||||||
kernel_version: str
|
kernel_version: str
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, slots=True)
|
@dataclass(frozen=True, slots=True)
|
||||||
class CPUInfo:
|
class CPUInfo:
|
||||||
name: str
|
name: str
|
||||||
@@ -22,14 +31,12 @@ class CPUInfo:
|
|||||||
logical_cores: int
|
logical_cores: int
|
||||||
max_frequency: float
|
max_frequency: float
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, slots=True)
|
@dataclass(frozen=True, slots=True)
|
||||||
class MemoryInfo:
|
class MemoryInfo:
|
||||||
total_ram: float # in GB
|
total_ram: float # in GB
|
||||||
used_ram: float # in GB
|
used_ram: float # in GB
|
||||||
available_ram: float # in GB
|
available_ram: float # in GB
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, slots=True)
|
@dataclass(frozen=True, slots=True)
|
||||||
class PythonInfo:
|
class PythonInfo:
|
||||||
version: str
|
version: str
|
||||||
@@ -37,6 +44,18 @@ class PythonInfo:
|
|||||||
compiler: str
|
compiler: str
|
||||||
|
|
||||||
|
|
||||||
|
def get_system_info() -> SystemInfo:
|
||||||
|
os_info = get_os_info()
|
||||||
|
cpu_info = get_cpu_info()
|
||||||
|
memory_info = get_memory_info()
|
||||||
|
python_info = get_python_info()
|
||||||
|
return SystemInfo(
|
||||||
|
os_info=os_info,
|
||||||
|
cpu_info=cpu_info,
|
||||||
|
memory_info=memory_info,
|
||||||
|
python_info=python_info,
|
||||||
|
)
|
||||||
|
|
||||||
def get_os_info() -> OSInfo:
|
def get_os_info() -> OSInfo:
|
||||||
system = platform.system()
|
system = platform.system()
|
||||||
|
|
||||||
@@ -54,17 +73,22 @@ def get_os_info() -> OSInfo:
|
|||||||
kernel_version=kernel_version,
|
kernel_version=kernel_version,
|
||||||
)
|
)
|
||||||
elif system == "Darwin":
|
elif system == "Darwin":
|
||||||
return OSInfo(kernel_version=platform.release(), name=f"macOS {platform.mac_ver()[0]}")
|
return OSInfo(
|
||||||
|
kernel_version=platform.release(),
|
||||||
|
name=f"macOS {platform.mac_ver()[0]}"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
return OSInfo(kernel_version=platform.release(), name=platform.system())
|
return OSInfo(
|
||||||
|
kernel_version=platform.release(),
|
||||||
|
name=platform.system()
|
||||||
|
)
|
||||||
|
|
||||||
def get_cpu_info() -> CPUInfo:
|
def get_cpu_info() -> CPUInfo:
|
||||||
cpu_info = cpuinfo.get_cpu_info()
|
cpu_info = cpuinfo.get_cpu_info()
|
||||||
cpu_name = cpu_info["brand_raw"]
|
cpu_name = cpu_info["brand_raw"]
|
||||||
cpu_architecture = cpu_info["arch"]
|
cpu_architecture = cpu_info["arch"]
|
||||||
cpu_physical_cores = psutil.cpu_count(logical=False) or 0
|
cpu_physical_cores = psutil.cpu_count(logical=False)
|
||||||
cpu_logical_cores = psutil.cpu_count(logical=True) or 0
|
cpu_logical_cores = psutil.cpu_count(logical=True)
|
||||||
|
|
||||||
cpu_freq = psutil.cpu_freq()
|
cpu_freq = psutil.cpu_freq()
|
||||||
cpu_max_frequency = cpu_freq.max
|
cpu_max_frequency = cpu_freq.max
|
||||||
@@ -74,10 +98,9 @@ def get_cpu_info() -> CPUInfo:
|
|||||||
architecture=cpu_architecture,
|
architecture=cpu_architecture,
|
||||||
physical_cores=cpu_physical_cores,
|
physical_cores=cpu_physical_cores,
|
||||||
logical_cores=cpu_logical_cores,
|
logical_cores=cpu_logical_cores,
|
||||||
max_frequency=cpu_max_frequency,
|
max_frequency=cpu_max_frequency
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_memory_info() -> MemoryInfo:
|
def get_memory_info() -> MemoryInfo:
|
||||||
mem = psutil.virtual_memory()
|
mem = psutil.virtual_memory()
|
||||||
total_ram = round(mem.total / (1024**3), 2)
|
total_ram = round(mem.total / (1024**3), 2)
|
||||||
@@ -90,32 +113,14 @@ def get_memory_info() -> MemoryInfo:
|
|||||||
available_ram=available_ram,
|
available_ram=available_ram,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_python_info() -> PythonInfo:
|
def get_python_info() -> PythonInfo:
|
||||||
python_version = platform.python_version()
|
python_version = platform.python_version()
|
||||||
python_implementation = platform.python_implementation()
|
python_implementation = platform.python_implementation()
|
||||||
python_compiler = platform.python_compiler()
|
python_compiler = platform.python_compiler()
|
||||||
return PythonInfo(
|
return PythonInfo(
|
||||||
version=python_version, implementation=python_implementation, compiler=python_compiler
|
version=python_version,
|
||||||
|
implementation=python_implementation,
|
||||||
|
compiler=python_compiler
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, slots=True)
|
|
||||||
class SystemInfo:
|
|
||||||
os_info: OSInfo
|
|
||||||
cpu_info: CPUInfo
|
|
||||||
memory_info: MemoryInfo
|
|
||||||
python_info: PythonInfo
|
|
||||||
|
|
||||||
|
|
||||||
def get_system_info() -> SystemInfo:
|
|
||||||
os_info = get_os_info()
|
|
||||||
cpu_info = get_cpu_info()
|
|
||||||
memory_info = get_memory_info()
|
|
||||||
python_info = get_python_info()
|
|
||||||
return SystemInfo(
|
|
||||||
os_info=os_info,
|
|
||||||
cpu_info=cpu_info,
|
|
||||||
memory_info=memory_info,
|
|
||||||
python_info=python_info,
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
from argenta import App, Command, Response, Router
|
||||||
|
|
||||||
|
|
||||||
|
app = App(override_system_messages=True)
|
||||||
|
router = Router()
|
||||||
|
|
||||||
|
@router.command(Command('command'))
|
||||||
|
def handler(_res: Response) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@router.command(Command('command_other'))
|
||||||
|
def handler2(_res: Response) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
app.include_routers(router)
|
||||||
|
app._pre_cycle_setup()
|
||||||
|
|
||||||
|
assert app._most_similar_command('command_') == 'command'
|
||||||
@@ -9,7 +9,7 @@ orchestrator: Orchestrator = Orchestrator()
|
|||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
app.include_router(router)
|
app.include_router(router)
|
||||||
orchestrator.run_repl(app)
|
orchestrator.start_polling(app)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -3,13 +3,12 @@ from prompt_toolkit import HTML
|
|||||||
from argenta import App, Orchestrator
|
from argenta import App, Orchestrator
|
||||||
from argenta.app import PredefinedMessages, StaticDividingLine, AutoCompleter
|
from argenta.app import PredefinedMessages, StaticDividingLine, AutoCompleter
|
||||||
from argenta.app.dividing_line.models import DynamicDividingLine
|
from argenta.app.dividing_line.models import DynamicDividingLine
|
||||||
from argenta.orchestrator import ArgParser
|
|
||||||
from mock.mock_app.routers import work_router
|
from mock.mock_app.routers import work_router
|
||||||
|
|
||||||
app: App = App(
|
app: App = App(
|
||||||
dividing_line=StaticDividingLine('~')
|
dividing_line=StaticDividingLine('~')
|
||||||
)
|
)
|
||||||
orchestrator: Orchestrator = Orchestrator(arg_parser=ArgParser(processed_args=[]))
|
orchestrator: Orchestrator = Orchestrator()
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@@ -19,7 +18,7 @@ def main():
|
|||||||
app.add_message_on_startup(PredefinedMessages.AUTOCOMPLETE)
|
app.add_message_on_startup(PredefinedMessages.AUTOCOMPLETE)
|
||||||
app.add_message_on_startup(PredefinedMessages.HELP)
|
app.add_message_on_startup(PredefinedMessages.HELP)
|
||||||
|
|
||||||
orchestrator.run_repl(app)
|
orchestrator.start_polling(app)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -13,19 +13,12 @@ dependencies = [
|
|||||||
"prompt-toolkit>=3.0.52",
|
"prompt-toolkit>=3.0.52",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.optional-dependencies]
|
|
||||||
cli = [
|
|
||||||
"nuitka[onefile]>=4.0.5",
|
|
||||||
"typer>=0.9,!=0.12,<=0.21.1",
|
|
||||||
]
|
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
dev = [
|
dev = [
|
||||||
{include-group = "linters"},
|
{include-group = "linters"},
|
||||||
{include-group = "typecheckers"},
|
{include-group = "typecheckers"},
|
||||||
{include-group = "docs"},
|
{include-group = "docs"},
|
||||||
{include-group = "tests"},
|
{include-group = "tests"},
|
||||||
{include-group = "metrics"},
|
|
||||||
"scriv>=1.8.0",
|
"scriv>=1.8.0",
|
||||||
]
|
]
|
||||||
linters = [
|
linters = [
|
||||||
@@ -53,19 +46,13 @@ metrics = [
|
|||||||
"matplotlib>=3.10.8",
|
"matplotlib>=3.10.8",
|
||||||
"psutil>=7.2.1",
|
"psutil>=7.2.1",
|
||||||
"py-cpuinfo>=9.0.0",
|
"py-cpuinfo>=9.0.0",
|
||||||
"cairosvg>=2.8.2",
|
|
||||||
"pygal>=3.1.0",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.scripts]
|
|
||||||
argenta = "argenta._cli.__main__:main"
|
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
line-length=100
|
line-length=100
|
||||||
|
|
||||||
[tool.pyright]
|
[tool.pyright]
|
||||||
typeCheckingMode = "strict"
|
typeCheckingMode = "strict"
|
||||||
reportMissingTypeStubs = false
|
|
||||||
|
|
||||||
[[tool.pyright.executionEnvironments]]
|
[[tool.pyright.executionEnvironments]]
|
||||||
root = "tests/"
|
root = "tests/"
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
from typer import Typer
|
|
||||||
|
|
||||||
from .commands import (
|
|
||||||
build_handler,
|
|
||||||
info_handler,
|
|
||||||
init_handler,
|
|
||||||
new_handler,
|
|
||||||
routes_handler,
|
|
||||||
run_handler,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
|
||||||
app = Typer()
|
|
||||||
app.command(
|
|
||||||
"run",
|
|
||||||
help="Command to start the orchestrator repl; the path to the callable object is required",
|
|
||||||
short_help="Start the orchestrator REPL",
|
|
||||||
epilog="Example: run app/main.py:main",
|
|
||||||
)(run_handler)
|
|
||||||
|
|
||||||
app.command(
|
|
||||||
"init",
|
|
||||||
help="Creates a flat/src boilerplate architecture in an existing project",
|
|
||||||
short_help="Initialize architecture in existing project",
|
|
||||||
epilog="Make sure you are in the project root before running this command.",
|
|
||||||
)(init_handler)
|
|
||||||
|
|
||||||
app.command(
|
|
||||||
"new",
|
|
||||||
help="Creates a project and in it flat/src boilerplate architecture",
|
|
||||||
short_help="Create a new project with boilerplate",
|
|
||||||
epilog="This will create a new directory with the project structure.",
|
|
||||||
)(new_handler)
|
|
||||||
|
|
||||||
app.command(
|
|
||||||
"routes",
|
|
||||||
help="Creates a project and in it flat/src boilerplate architecture",
|
|
||||||
short_help="Create a new project with boilerplate",
|
|
||||||
epilog="This will create a new directory with the project structure.",
|
|
||||||
)(routes_handler)
|
|
||||||
|
|
||||||
app.command(
|
|
||||||
name="info",
|
|
||||||
help="Displays information about the installed Argenta package and environment",
|
|
||||||
short_help="Show Argenta version and environment info",
|
|
||||||
epilog="Uses metadata to retrieve the installed package version.",
|
|
||||||
)(info_handler)
|
|
||||||
|
|
||||||
app.command(
|
|
||||||
name="build",
|
|
||||||
help="Compiles the project into a standalone binary using Nuitka",
|
|
||||||
short_help="Build a standalone binary",
|
|
||||||
)(build_handler)
|
|
||||||
|
|
||||||
app()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
from .run import run_handler as run_handler
|
|
||||||
from .init import init_handler as init_handler
|
|
||||||
from .new import new_handler as new_handler
|
|
||||||
from .routes import routes_handler as routes_handler
|
|
||||||
from .info import info_handler as info_handler
|
|
||||||
from .build import build_handler as build_handler
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
__all__ = ["build_handler"]
|
|
||||||
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from rich.console import Console
|
|
||||||
|
|
||||||
|
|
||||||
def build_handler(entry_point: str, output_name: str | None = None) -> None:
|
|
||||||
console = Console()
|
|
||||||
file_path, _, callable_name = entry_point.partition(":")
|
|
||||||
|
|
||||||
if not file_path or not callable_name:
|
|
||||||
console.print(
|
|
||||||
f'[bold red]Error:[/bold red] "{entry_point}" must be in format "<path/to/file.py>:<callable>"'
|
|
||||||
)
|
|
||||||
raise SystemExit(1)
|
|
||||||
|
|
||||||
path = Path(file_path).resolve()
|
|
||||||
|
|
||||||
if not path.exists():
|
|
||||||
console.print(f'[bold red]Error:[/bold red] File "{file_path}" not found')
|
|
||||||
raise SystemExit(1)
|
|
||||||
|
|
||||||
is_main_module = path.name == "__main__.py"
|
|
||||||
target = str(path.parent) if is_main_module else str(path)
|
|
||||||
name = output_name or (path.parent.name if is_main_module else path.stem)
|
|
||||||
|
|
||||||
console.print(
|
|
||||||
f"[bold green]Building[/bold green] [cyan]{entry_point}[/cyan] → [cyan]{name}[/cyan]"
|
|
||||||
)
|
|
||||||
|
|
||||||
args = [
|
|
||||||
sys.executable,
|
|
||||||
"-m",
|
|
||||||
"nuitka",
|
|
||||||
"--standalone",
|
|
||||||
"--onefile",
|
|
||||||
f"--output-filename={name}",
|
|
||||||
f"--jobs={os.cpu_count()}",
|
|
||||||
"--lto=no",
|
|
||||||
"--include-windows-runtime-dlls=no",
|
|
||||||
]
|
|
||||||
|
|
||||||
if is_main_module:
|
|
||||||
args.append("--python-flag=-m")
|
|
||||||
|
|
||||||
args.append(target)
|
|
||||||
|
|
||||||
result = subprocess.run(args, check=False)
|
|
||||||
|
|
||||||
if result.returncode != 0:
|
|
||||||
console.print("[bold red]Build failed.[/bold red]")
|
|
||||||
raise SystemExit(result.returncode)
|
|
||||||
|
|
||||||
console.print(f"[bold green]Done![/bold green] Binary: [cyan]{name}[/cyan]")
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
__all__ = ["info_handler"]
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import platform
|
|
||||||
from importlib.metadata import version
|
|
||||||
|
|
||||||
from art import text2art # pyright: ignore[reportUnknownVariableType]
|
|
||||||
from rich.console import Console
|
|
||||||
from rich.padding import Padding
|
|
||||||
from rich.table import Table
|
|
||||||
from rich import box
|
|
||||||
|
|
||||||
|
|
||||||
console = Console()
|
|
||||||
|
|
||||||
|
|
||||||
def info_handler() -> None:
|
|
||||||
table = Table(
|
|
||||||
box=box.SIMPLE,
|
|
||||||
show_header=False,
|
|
||||||
pad_edge=False,
|
|
||||||
show_edge=False,
|
|
||||||
expand=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
table.add_column(style="bold cyan")
|
|
||||||
table.add_column(style="white", justify="right")
|
|
||||||
|
|
||||||
table.add_row("Argenta version", f'[bold red]{version("argenta")}[/bold red]')
|
|
||||||
table.add_row("Python version", sys.version.split()[0])
|
|
||||||
table.add_row("Platform", f"{platform.system()} {platform.release()} ({platform.machine()})")
|
|
||||||
table.add_row("Docs", "https://argenta.readthedocs.io")
|
|
||||||
|
|
||||||
console.print(f"[bold red]{text2art("Argenta", font='tarty1')}[/bold red]")
|
|
||||||
console.print(Padding(table, pad=(2, 5)))
|
|
||||||
console.print(Padding("[i]made with ❤ by [b]kolo[/b][/i]", pad=(0, 17)))
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
__all__ = ["init_handler"]
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Literal
|
|
||||||
|
|
||||||
GITIGNORE_CONTENT = """
|
|
||||||
__pycache__/
|
|
||||||
*.py[cod]
|
|
||||||
.env
|
|
||||||
.venv/
|
|
||||||
env/
|
|
||||||
"""
|
|
||||||
|
|
||||||
FLAT_MAIN_TEMPLATE = """
|
|
||||||
from argenta import Orchestrator, App
|
|
||||||
|
|
||||||
from handlers import router
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
app = App()
|
|
||||||
app.include_router(router)
|
|
||||||
|
|
||||||
orchestrator = Orchestrator()
|
|
||||||
orchestrator.run_repl(app)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
"""
|
|
||||||
|
|
||||||
FLAT_HANDLERS_TEMPLATE = """
|
|
||||||
from argenta import Router, Response
|
|
||||||
|
|
||||||
router = Router("Hello command")
|
|
||||||
|
|
||||||
@router.command("hello")
|
|
||||||
def start_handler(response: Response):
|
|
||||||
print("Hello world!")
|
|
||||||
"""
|
|
||||||
|
|
||||||
SRC_MAIN_TEMPLATE = """
|
|
||||||
from argenta import Orchestrator, App
|
|
||||||
|
|
||||||
from .routers import router
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
app = App()
|
|
||||||
app.include_router(router)
|
|
||||||
|
|
||||||
orchestrator = Orchestrator()
|
|
||||||
orchestrator.run_repl(app)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
"""
|
|
||||||
|
|
||||||
SRC_ROUTERS_TEMPLATE = """
|
|
||||||
from argenta import Router
|
|
||||||
from .handlers.hello_world_handler import hello_handler
|
|
||||||
|
|
||||||
router = Router()
|
|
||||||
|
|
||||||
router.command('hello')(hello_handler)
|
|
||||||
"""
|
|
||||||
|
|
||||||
SRC_HANDLER_TEMPLATE = """
|
|
||||||
from argenta import Response
|
|
||||||
|
|
||||||
|
|
||||||
def hello_handler(response: Response) -> None:
|
|
||||||
print("Hello world!")
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def create_file(path: Path, content: str) -> None:
|
|
||||||
if not path.exists():
|
|
||||||
path.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
path.write_text(content.strip(), encoding="utf-8")
|
|
||||||
else:
|
|
||||||
print(f"Skipped: {path} (already exists)")
|
|
||||||
|
|
||||||
|
|
||||||
def init_handler(with_arch: Literal["flat", "src"] = "flat") -> None:
|
|
||||||
cwd = Path.cwd()
|
|
||||||
project_name = cwd.name.lower().replace(" ", "_")
|
|
||||||
|
|
||||||
create_file(cwd / ".gitignore", GITIGNORE_CONTENT)
|
|
||||||
|
|
||||||
if with_arch == "flat":
|
|
||||||
create_file(cwd / "main.py", FLAT_MAIN_TEMPLATE)
|
|
||||||
create_file(cwd / "handlers.py", FLAT_HANDLERS_TEMPLATE)
|
|
||||||
|
|
||||||
elif with_arch == "src":
|
|
||||||
base_pkg = cwd / "src" / project_name / "application"
|
|
||||||
|
|
||||||
create_file(base_pkg / "__main__.py", SRC_MAIN_TEMPLATE)
|
|
||||||
create_file(base_pkg / "routers.py", SRC_ROUTERS_TEMPLATE)
|
|
||||||
create_file(base_pkg / "handlers" / "hello_world_handler.py", SRC_HANDLER_TEMPLATE)
|
|
||||||
|
|
||||||
create_file(cwd / "src" / "__init__.py", "")
|
|
||||||
create_file(cwd / "src" / project_name / "__init__.py", "")
|
|
||||||
create_file(base_pkg / "__init__.py", "")
|
|
||||||
create_file(base_pkg / "handlers" / "__init__.py", "")
|
|
||||||
|
|
||||||
print("\nInitialization complete.")
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
__all__ = ["new_handler"]
|
|
||||||
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Literal
|
|
||||||
|
|
||||||
|
|
||||||
GITIGNORE_CONTENT = """
|
|
||||||
__pycache__/
|
|
||||||
*.py[cod]
|
|
||||||
.env
|
|
||||||
.venv/
|
|
||||||
env/
|
|
||||||
"""
|
|
||||||
|
|
||||||
FLAT_MAIN_TEMPLATE = """
|
|
||||||
from argenta import Orchestrator, App
|
|
||||||
|
|
||||||
from handlers import router
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
app = App()
|
|
||||||
app.include_router(router)
|
|
||||||
|
|
||||||
orchestrator = Orchestrator()
|
|
||||||
orchestrator.run_repl(app)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
"""
|
|
||||||
|
|
||||||
FLAT_HANDLERS_TEMPLATE = """
|
|
||||||
from argenta import Router, Response
|
|
||||||
|
|
||||||
router = Router("Hello command")
|
|
||||||
|
|
||||||
@router.command("hello")
|
|
||||||
def start_handler(response: Response):
|
|
||||||
print("Hello world!")
|
|
||||||
"""
|
|
||||||
|
|
||||||
SRC_MAIN_TEMPLATE = """
|
|
||||||
from argenta import Orchestrator, App
|
|
||||||
|
|
||||||
from .routers import router
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
app = App()
|
|
||||||
app.include_router(router)
|
|
||||||
|
|
||||||
orchestrator = Orchestrator()
|
|
||||||
orchestrator.run_repl(app)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
"""
|
|
||||||
|
|
||||||
SRC_ROUTERS_TEMPLATE = """
|
|
||||||
from argenta import Router
|
|
||||||
from .handlers.hello_world_handler import hello_handler
|
|
||||||
|
|
||||||
router = Router()
|
|
||||||
|
|
||||||
router.command('hello')(hello_handler)
|
|
||||||
"""
|
|
||||||
|
|
||||||
SRC_HANDLER_TEMPLATE = """
|
|
||||||
from argenta import Response
|
|
||||||
|
|
||||||
|
|
||||||
def hello_handler(response: Response) -> None:
|
|
||||||
print("Hello world!")
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def create_file(path: Path, content: str) -> None:
|
|
||||||
if not path.exists():
|
|
||||||
path.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
path.write_text(content.strip(), encoding="utf-8")
|
|
||||||
else:
|
|
||||||
print(f"Skipped: {path} (already exists)")
|
|
||||||
|
|
||||||
|
|
||||||
def new_handler(project_name: str, with_arch: Literal["flat", "src"] = "flat") -> None:
|
|
||||||
base_dir = Path.cwd() / project_name
|
|
||||||
|
|
||||||
if base_dir.exists():
|
|
||||||
print(f"Error: Directory '{project_name}' already exists.")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
base_dir.mkdir(parents=True)
|
|
||||||
print(f"Initialized project directory: {base_dir}")
|
|
||||||
|
|
||||||
create_file(base_dir / ".gitignore", GITIGNORE_CONTENT)
|
|
||||||
|
|
||||||
if with_arch == "flat":
|
|
||||||
create_file(base_dir / "main.py", FLAT_MAIN_TEMPLATE)
|
|
||||||
create_file(base_dir / "handlers.py", FLAT_HANDLERS_TEMPLATE)
|
|
||||||
|
|
||||||
elif with_arch == "src":
|
|
||||||
pkg_name = project_name.lower().replace(" ", "_").replace("-", "_")
|
|
||||||
app_pkg = base_dir / "src" / pkg_name / "application"
|
|
||||||
|
|
||||||
create_file(app_pkg / "__main__.py", SRC_MAIN_TEMPLATE)
|
|
||||||
create_file(app_pkg / "routers.py", SRC_ROUTERS_TEMPLATE)
|
|
||||||
create_file(app_pkg / "handlers" / "hello_world_handler.py", SRC_HANDLER_TEMPLATE)
|
|
||||||
|
|
||||||
create_file(base_dir / "src" / "__init__.py", "")
|
|
||||||
create_file(base_dir / "src" / pkg_name / "__init__.py", "")
|
|
||||||
create_file(app_pkg / "__init__.py", "")
|
|
||||||
create_file(app_pkg / "handlers" / "__init__.py", "")
|
|
||||||
|
|
||||||
print(f"\nProject '{project_name}' created successfully! 🚀")
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
__all__ = ["routes_handler"]
|
|
||||||
|
|
||||||
from collections import defaultdict
|
|
||||||
|
|
||||||
from rich.console import Console
|
|
||||||
from rich.panel import Panel
|
|
||||||
from rich.tree import Tree
|
|
||||||
|
|
||||||
from ..infrastructure.entrypoint_resolver.entity import (
|
|
||||||
EntryPointAsApp,
|
|
||||||
EntrypointResolver,
|
|
||||||
ResolveFromStringError,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def routes_handler(entrypoint_path: str) -> None:
|
|
||||||
entrypoint_path, _, entrypoint_callable_name = entrypoint_path.partition(":")
|
|
||||||
if not entrypoint_callable_name:
|
|
||||||
raise ResolveFromStringError(
|
|
||||||
"Path to callable object that run orchestrator repl must be in the format <path/to/file.py>:<object_name>"
|
|
||||||
)
|
|
||||||
|
|
||||||
app_instance = EntrypointResolver[EntryPointAsApp](entrypoint_path).parse_entrypoint_with_type(
|
|
||||||
entrypoint_callable_name
|
|
||||||
)
|
|
||||||
|
|
||||||
app = app_instance.instance_object
|
|
||||||
routers = app.registered_routers
|
|
||||||
|
|
||||||
console = Console()
|
|
||||||
stats: dict[str, int] = defaultdict(int)
|
|
||||||
|
|
||||||
tree = Tree(f"📦 [bold blue]App object:[/bold blue] {app!r}")
|
|
||||||
|
|
||||||
for router in routers:
|
|
||||||
stats["routers"] += 1
|
|
||||||
router_node = tree.add(f"📁 [bold green]Router:[/bold green] {router.title}")
|
|
||||||
|
|
||||||
for command in router.command_handlers:
|
|
||||||
stats["commands"] += 1
|
|
||||||
trigger = command.handled_command.trigger
|
|
||||||
description = command.handled_command.description
|
|
||||||
aliases = list(command.handled_command.aliases)
|
|
||||||
flags = list(command.handled_command.registered_flags)
|
|
||||||
|
|
||||||
cmd_node = router_node.add(f"⚡ [bold cyan]{trigger}[/bold cyan]")
|
|
||||||
|
|
||||||
if description:
|
|
||||||
cmd_node.add(f"📝 [dim]description:[/dim] {description}")
|
|
||||||
|
|
||||||
if aliases:
|
|
||||||
aliases_str = ", ".join(f"[yellow]{a}[/yellow]" for a in aliases)
|
|
||||||
cmd_node.add(f"🔀 [dim]aliases:[/dim] {aliases_str}")
|
|
||||||
stats["aliases"] += len(aliases)
|
|
||||||
|
|
||||||
if flags:
|
|
||||||
flags_node = cmd_node.add(f"🚩 [dim]flags:[/dim] ({len(flags)})")
|
|
||||||
for flag in flags:
|
|
||||||
possible = flag.possible_values
|
|
||||||
flags_node.add(
|
|
||||||
f"[magenta]{flag.prefix}{flag.name}[/magenta]"
|
|
||||||
f" [dim]possible_values:[/dim] [italic]{possible!r}[/italic]"
|
|
||||||
)
|
|
||||||
stats["flags"] += len(flags)
|
|
||||||
|
|
||||||
stats_text = (
|
|
||||||
f"📁 [bold]Total Routers:[/bold] {stats['routers']}\n"
|
|
||||||
f"⚡ [bold]Total Commands:[/bold] {stats['commands']}\n"
|
|
||||||
f"🔀 [bold]Total Aliases:[/bold] {stats['aliases']}\n"
|
|
||||||
f"🚩 [bold]Total Flags:[/bold] {stats['flags']}"
|
|
||||||
)
|
|
||||||
|
|
||||||
console.print(
|
|
||||||
Panel(
|
|
||||||
stats_text,
|
|
||||||
title="[bold blue]App Stats[/bold blue]",
|
|
||||||
expand=False,
|
|
||||||
border_style="blue",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
console.print()
|
|
||||||
console.print(tree)
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
__all__ = ["run_handler"]
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from ..infrastructure.entrypoint_resolver.entity import (
|
|
||||||
CallableEntryPoint,
|
|
||||||
EntrypointResolver,
|
|
||||||
ResolveFromStringError,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def run_handler(entrypoint_path: str) -> None:
|
|
||||||
os.environ["RUN_FROM_ARGENTA_RUNNER"] = "1"
|
|
||||||
entrypoint_path, _, entrypoint_callable_name = entrypoint_path.partition(":")
|
|
||||||
if not entrypoint_callable_name:
|
|
||||||
raise ResolveFromStringError(
|
|
||||||
"Path to callable object that run orchestrator repl must be in the format <path/to/file.py>:<object_name> or <path.to.module>:<object_name>"
|
|
||||||
)
|
|
||||||
|
|
||||||
runner = EntrypointResolver[CallableEntryPoint](entrypoint_path).parse_entrypoint_with_type(
|
|
||||||
entrypoint_callable_name
|
|
||||||
)
|
|
||||||
|
|
||||||
runner.instance_object()
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
from .entity import CallableEntryPoint as CallableEntryPoint
|
|
||||||
from .entity import EntryPointAsApp as EntryPointAsApp
|
|
||||||
from .entity import EntrypointResolver as EntrypointResolver
|
|
||||||
from .exceptions import EntrypointNotCallableError as EntrypointNotCallableError
|
|
||||||
from .exceptions import ResolveFromStringError as ResolveFromStringError
|
|
||||||
from .exceptions import CallableEntrypointNotMatchRequiredSignatureError as CallableEntrypointNotMatchRequiredSignatureError
|
|
||||||
from .exceptions import EntrypointNotAppInstanceError as EntrypointNotAppInstanceError
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
__all__ = ["EntrypointResolver", "EntryPointAsApp", "CallableEntryPoint"]
|
|
||||||
|
|
||||||
import importlib
|
|
||||||
import inspect
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Callable, Protocol, cast, get_args
|
|
||||||
|
|
||||||
from argenta.app.models import App
|
|
||||||
|
|
||||||
from .exceptions import (
|
|
||||||
CallableEntrypointNotMatchRequiredSignatureError,
|
|
||||||
EntrypointNotAppInstanceError,
|
|
||||||
EntrypointNotCallableError,
|
|
||||||
ResolveFromStringError,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class EntryPoint[T](Protocol):
|
|
||||||
@property
|
|
||||||
def raw_path(self) -> str: ...
|
|
||||||
@property
|
|
||||||
def instance_object(self) -> T: ...
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, slots=True)
|
|
||||||
class CallableEntryPoint:
|
|
||||||
raw_path: str
|
|
||||||
instance_object: Callable[[], None]
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, slots=True)
|
|
||||||
class EntryPointAsApp:
|
|
||||||
raw_path: str
|
|
||||||
instance_object: App
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, slots=True)
|
|
||||||
class ResolvedEntrypoint:
|
|
||||||
resolved_source_path: str
|
|
||||||
instance: Callable[[], None] | App
|
|
||||||
|
|
||||||
|
|
||||||
class EntrypointResolver[T: (CallableEntryPoint, EntryPointAsApp)]:
|
|
||||||
def __init__(self, path_to_entrypoint: str):
|
|
||||||
self._path_to_entrypoint = path_to_entrypoint
|
|
||||||
|
|
||||||
def parse_entrypoint_with_type(
|
|
||||||
self,
|
|
||||||
entrypoint_object_name: str,
|
|
||||||
) -> T:
|
|
||||||
entrypoint_type: type[T] = get_args(self.__orig_class__)[0] # pyright: ignore[reportAttributeAccessIssue, reportUnknownMemberType]
|
|
||||||
if entrypoint_type is CallableEntryPoint:
|
|
||||||
return cast(T, self._parse_callable_entrypoint(entrypoint_object_name))
|
|
||||||
elif entrypoint_type is EntryPointAsApp:
|
|
||||||
return cast(T, self._parse_entrypoint_as_app(entrypoint_object_name))
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def _parse_callable_entrypoint(self, entrypoint_object_name: str) -> CallableEntryPoint:
|
|
||||||
resolved_entrypoint = self._resolve_from_string(entrypoint_object_name)
|
|
||||||
instance_object = resolved_entrypoint.instance
|
|
||||||
if not callable(instance_object):
|
|
||||||
raise EntrypointNotCallableError(repr(instance_object))
|
|
||||||
instance_object_signature = inspect.signature(instance_object)
|
|
||||||
required_params = instance_object_signature.parameters
|
|
||||||
|
|
||||||
if required_params:
|
|
||||||
raise CallableEntrypointNotMatchRequiredSignatureError(repr(instance_object))
|
|
||||||
|
|
||||||
return CallableEntryPoint(raw_path=resolved_entrypoint.resolved_source_path, instance_object=instance_object)
|
|
||||||
|
|
||||||
def _parse_entrypoint_as_app(self, entrypoint_object_name: str) -> EntryPointAsApp:
|
|
||||||
resolved_entrypoint = self._resolve_from_string(entrypoint_object_name)
|
|
||||||
instance_object = resolved_entrypoint.instance
|
|
||||||
if not isinstance(instance_object, App):
|
|
||||||
raise EntrypointNotAppInstanceError(repr(instance_object))
|
|
||||||
|
|
||||||
return EntryPointAsApp(raw_path=resolved_entrypoint.resolved_source_path, instance_object=instance_object)
|
|
||||||
|
|
||||||
def _resolve_from_string(self, entrypoint_object_name: str) -> ResolvedEntrypoint:
|
|
||||||
raw_path = self._path_to_entrypoint
|
|
||||||
|
|
||||||
raw_path_as_dir = Path(raw_path).resolve()
|
|
||||||
if raw_path_as_dir.is_dir() and (raw_path_as_dir / "__main__.py").exists():
|
|
||||||
raw_path = str(raw_path_as_dir / "__main__.py")
|
|
||||||
|
|
||||||
is_file_path = bool(re.search(r"[\/\\]|\.py$", raw_path))
|
|
||||||
|
|
||||||
if is_file_path:
|
|
||||||
abs_path = Path(raw_path).resolve()
|
|
||||||
if not abs_path.exists():
|
|
||||||
raise ResolveFromStringError(f'File "{raw_path}" not found')
|
|
||||||
|
|
||||||
package_root = abs_path.parent
|
|
||||||
while (package_root / "__init__.py").exists():
|
|
||||||
package_root = package_root.parent
|
|
||||||
|
|
||||||
pkg_root_str = str(package_root)
|
|
||||||
if pkg_root_str not in sys.path:
|
|
||||||
sys.path.insert(0, pkg_root_str)
|
|
||||||
|
|
||||||
module_name = ".".join(abs_path.relative_to(package_root).with_suffix("").parts)
|
|
||||||
resolved_source_path = str(abs_path)
|
|
||||||
|
|
||||||
else:
|
|
||||||
module_name = raw_path
|
|
||||||
cwd_str = str(Path.cwd())
|
|
||||||
if cwd_str not in sys.path:
|
|
||||||
sys.path.insert(0, cwd_str)
|
|
||||||
|
|
||||||
resolved_source_path = module_name
|
|
||||||
|
|
||||||
try:
|
|
||||||
module = importlib.import_module(module_name)
|
|
||||||
except ImportError as e:
|
|
||||||
if not is_file_path and not module_name.endswith(".__main__"):
|
|
||||||
try:
|
|
||||||
main_module_name = f"{module_name}.__main__"
|
|
||||||
module = importlib.import_module(main_module_name)
|
|
||||||
module_name = main_module_name
|
|
||||||
except ImportError:
|
|
||||||
raise ResolveFromStringError(f'Cannot import module "{module_name}": {e}')
|
|
||||||
else:
|
|
||||||
raise ResolveFromStringError(f'Cannot import module "{module_name}": {e}')
|
|
||||||
|
|
||||||
if not is_file_path:
|
|
||||||
resolved_source_path = getattr(module, "__file__", resolved_source_path)
|
|
||||||
|
|
||||||
try:
|
|
||||||
instance = getattr(module, entrypoint_object_name)
|
|
||||||
except AttributeError:
|
|
||||||
raise ResolveFromStringError(f'"{entrypoint_object_name}" not found in "{raw_path}"')
|
|
||||||
|
|
||||||
return ResolvedEntrypoint(resolved_source_path, instance)
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
class ResolverError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ResolveFromStringError(ResolverError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class EntrypointError(Exception):
|
|
||||||
def __init__(self, entrypoint_as_repr: str) -> None:
|
|
||||||
self.entrypoint_as_repr = entrypoint_as_repr
|
|
||||||
|
|
||||||
|
|
||||||
class EntrypointNotCallableError(EntrypointError):
|
|
||||||
def __str__(self):
|
|
||||||
return f"Entrypoint {self.entrypoint_as_repr} is not callable"
|
|
||||||
|
|
||||||
|
|
||||||
class CallableEntrypointNotMatchRequiredSignatureError(EntrypointError):
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return f"Callable entrypoint {self.entrypoint_as_repr} not match with required signature Callable[[], ...]"
|
|
||||||
|
|
||||||
|
|
||||||
class EntrypointNotAppInstanceError(EntrypointError):
|
|
||||||
def __str__(self):
|
|
||||||
return f"Entrypoint {self.entrypoint_as_repr} is not instance of App"
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
__all__ = ['build_session', 'do_prompt']
|
|
||||||
|
|
||||||
from typing import Callable, Iterable
|
|
||||||
|
|
||||||
from prompt_toolkit import HTML, PromptSession
|
|
||||||
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
|
|
||||||
from prompt_toolkit.completion import CompleteEvent, Completer, Completion, ThreadedCompleter
|
|
||||||
from prompt_toolkit.cursor_shapes import CursorShape
|
|
||||||
from prompt_toolkit.document import Document
|
|
||||||
from prompt_toolkit.formatted_text import StyleAndTextTuples
|
|
||||||
from prompt_toolkit.history import FileHistory, History, InMemoryHistory, ThreadedHistory
|
|
||||||
from prompt_toolkit.key_binding import KeyBindings, KeyPressEvent
|
|
||||||
from prompt_toolkit.lexers import Lexer
|
|
||||||
from prompt_toolkit.styles import Style
|
|
||||||
|
|
||||||
|
|
||||||
class CommandLexer(Lexer):
|
|
||||||
def __init__(self, valid_commands: set[str]) -> None:
|
|
||||||
self.valid_commands: set[str] = valid_commands
|
|
||||||
|
|
||||||
def lex_document(self, document: Document) -> Callable[[int], StyleAndTextTuples]:
|
|
||||||
def get_line_tokens(lineno: int) -> StyleAndTextTuples:
|
|
||||||
if lineno >= len(document.lines):
|
|
||||||
return []
|
|
||||||
|
|
||||||
line_text: str = document.lines[lineno]
|
|
||||||
|
|
||||||
if not line_text.strip():
|
|
||||||
return [("", line_text)]
|
|
||||||
|
|
||||||
first_word: str = line_text.split()[0] if line_text.split() else ""
|
|
||||||
|
|
||||||
if first_word in self.valid_commands:
|
|
||||||
return [("class:valid", line_text)]
|
|
||||||
else:
|
|
||||||
return [("class:invalid", line_text)]
|
|
||||||
|
|
||||||
return get_line_tokens
|
|
||||||
|
|
||||||
|
|
||||||
class HistoryCompleter(Completer):
|
|
||||||
def __init__(self, history_container: History, static_commands: set[str]) -> None:
|
|
||||||
self.history_container: History = history_container
|
|
||||||
self.static_commands: set[str] = static_commands
|
|
||||||
|
|
||||||
def get_completions(
|
|
||||||
self, document: Document, complete_event: CompleteEvent
|
|
||||||
) -> Iterable[Completion]:
|
|
||||||
text: str = document.text_before_cursor
|
|
||||||
history_items: set[str] = set(self.history_container.load_history_strings())
|
|
||||||
all_candidates: set[str] = history_items.union(self.static_commands)
|
|
||||||
matches: list[str] = sorted(cmd for cmd in all_candidates if cmd.startswith(text))
|
|
||||||
|
|
||||||
if not matches:
|
|
||||||
return
|
|
||||||
|
|
||||||
for match in matches:
|
|
||||||
yield Completion(match, start_position=-len(text), display=match)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _find_common_prefix(matches: list[str]) -> str:
|
|
||||||
if not matches:
|
|
||||||
return ""
|
|
||||||
common: str = matches[0]
|
|
||||||
for match in matches[1:]:
|
|
||||||
i: int = 0
|
|
||||||
while i < len(common) and i < len(match) and common[i] == match[i]:
|
|
||||||
i += 1
|
|
||||||
common = common[:i]
|
|
||||||
return common
|
|
||||||
|
|
||||||
|
|
||||||
def build_session(
|
|
||||||
history_filename: str | None,
|
|
||||||
autocomplete_button: str,
|
|
||||||
command_highlighting: bool,
|
|
||||||
auto_suggestions: bool,
|
|
||||||
all_commands: set[str],
|
|
||||||
) -> PromptSession[str]:
|
|
||||||
kb = KeyBindings()
|
|
||||||
|
|
||||||
def _(event: KeyPressEvent) -> None:
|
|
||||||
buff = event.app.current_buffer
|
|
||||||
if buff.complete_state:
|
|
||||||
buff.complete_next()
|
|
||||||
return
|
|
||||||
comps_gen = iter(buff.completer.get_completions(buff.document, CompleteEvent()))
|
|
||||||
try:
|
|
||||||
first = next(comps_gen)
|
|
||||||
except StopIteration:
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
_ = next(comps_gen)
|
|
||||||
buff.start_completion(select_first=False)
|
|
||||||
except StopIteration:
|
|
||||||
buff.apply_completion(first)
|
|
||||||
|
|
||||||
kb.add(autocomplete_button)(_)
|
|
||||||
|
|
||||||
history: InMemoryHistory | ThreadedHistory
|
|
||||||
if history_filename:
|
|
||||||
history = ThreadedHistory(FileHistory(history_filename))
|
|
||||||
else:
|
|
||||||
history = InMemoryHistory()
|
|
||||||
|
|
||||||
style = Style.from_dict({"valid": "#00ff00", "invalid": "#ff0000"})
|
|
||||||
return PromptSession(
|
|
||||||
history=history,
|
|
||||||
completer=ThreadedCompleter(HistoryCompleter(history, all_commands)),
|
|
||||||
complete_while_typing=False,
|
|
||||||
key_bindings=kb,
|
|
||||||
auto_suggest=AutoSuggestFromHistory() if auto_suggestions else None,
|
|
||||||
style=style if command_highlighting else None,
|
|
||||||
lexer=CommandLexer(all_commands) if command_highlighting else None,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def do_prompt(session: PromptSession[str], prompt_text: str | HTML) -> str:
|
|
||||||
return session.prompt(
|
|
||||||
HTML(prompt_text) if isinstance(prompt_text, str) else prompt_text,
|
|
||||||
cursor=CursorShape.BLINKING_BEAM,
|
|
||||||
)
|
|
||||||
@@ -1,12 +1,77 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
__all__ = ["AutoCompleter"]
|
__all__ = ["AutoCompleter"]
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from typing import TYPE_CHECKING
|
from typing import Callable, Iterable
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
from prompt_toolkit import HTML, PromptSession
|
||||||
from prompt_toolkit import PromptSession, HTML
|
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
|
||||||
|
from prompt_toolkit.completion import (CompleteEvent, Completer, Completion,
|
||||||
|
ThreadedCompleter)
|
||||||
|
from prompt_toolkit.cursor_shapes import CursorShape
|
||||||
|
from prompt_toolkit.document import Document
|
||||||
|
from prompt_toolkit.formatted_text import StyleAndTextTuples
|
||||||
|
from prompt_toolkit.history import FileHistory, History, InMemoryHistory, ThreadedHistory
|
||||||
|
from prompt_toolkit.key_binding import KeyBindings, KeyPressEvent
|
||||||
|
from prompt_toolkit.lexers import Lexer
|
||||||
|
from prompt_toolkit.styles import Style
|
||||||
|
|
||||||
|
|
||||||
|
class CommandLexer(Lexer):
|
||||||
|
def __init__(self, valid_commands: set[str]) -> None:
|
||||||
|
self.valid_commands: set[str] = valid_commands
|
||||||
|
|
||||||
|
def lex_document(self, document: Document) -> Callable[[int], StyleAndTextTuples]:
|
||||||
|
def get_line_tokens(lineno: int) -> StyleAndTextTuples:
|
||||||
|
if lineno >= len(document.lines):
|
||||||
|
return []
|
||||||
|
|
||||||
|
line_text: str = document.lines[lineno]
|
||||||
|
|
||||||
|
if not line_text.strip():
|
||||||
|
return [("", line_text)]
|
||||||
|
|
||||||
|
first_word: str = line_text.split()[0] if line_text.split() else ""
|
||||||
|
|
||||||
|
if first_word in self.valid_commands:
|
||||||
|
return [("class:valid", line_text)]
|
||||||
|
else:
|
||||||
|
return [("class:invalid", line_text)]
|
||||||
|
|
||||||
|
return get_line_tokens
|
||||||
|
|
||||||
|
|
||||||
|
class HistoryCompleter(Completer):
|
||||||
|
def __init__(self, history_container: History, static_commands: set[str]) -> None:
|
||||||
|
self.history_container: History = history_container
|
||||||
|
self.static_commands: set[str] = static_commands
|
||||||
|
|
||||||
|
def get_completions(self, document: Document, complete_event: CompleteEvent) -> Iterable[Completion]:
|
||||||
|
text: str = document.text_before_cursor
|
||||||
|
history_items: set[str] = set(self.history_container.load_history_strings())
|
||||||
|
all_candidates: set[str] = history_items.union(self.static_commands)
|
||||||
|
matches: list[str] = sorted(cmd for cmd in all_candidates if cmd.startswith(text))
|
||||||
|
|
||||||
|
if not matches:
|
||||||
|
return
|
||||||
|
|
||||||
|
for match in matches:
|
||||||
|
yield Completion(
|
||||||
|
match,
|
||||||
|
start_position=-len(text),
|
||||||
|
display=match
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _find_common_prefix(matches: list[str]) -> str:
|
||||||
|
if not matches:
|
||||||
|
return ""
|
||||||
|
common: str = matches[0]
|
||||||
|
for match in matches[1:]:
|
||||||
|
i: int = 0
|
||||||
|
while i < len(common) and i < len(match) and common[i] == match[i]:
|
||||||
|
i += 1
|
||||||
|
common = common[:i]
|
||||||
|
return common
|
||||||
|
|
||||||
|
|
||||||
class AutoCompleter:
|
class AutoCompleter:
|
||||||
@@ -30,14 +95,41 @@ class AutoCompleter:
|
|||||||
self._fallback_mode = True
|
self._fallback_mode = True
|
||||||
return
|
return
|
||||||
|
|
||||||
from ._ext_features_impl import build_session
|
kb = KeyBindings()
|
||||||
|
|
||||||
self._session = build_session(
|
def _(event: KeyPressEvent) -> None:
|
||||||
self.history_filename,
|
buff = event.app.current_buffer
|
||||||
self.autocomplete_button,
|
if buff.complete_state:
|
||||||
self.command_highlighting,
|
buff.complete_next()
|
||||||
self.auto_suggestions,
|
return
|
||||||
all_commands
|
comps_gen = iter(buff.completer.get_completions(buff.document, CompleteEvent()))
|
||||||
|
try:
|
||||||
|
first = next(comps_gen)
|
||||||
|
except StopIteration:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
_ = next(comps_gen)
|
||||||
|
buff.start_completion(select_first=False)
|
||||||
|
except StopIteration:
|
||||||
|
buff.apply_completion(first)
|
||||||
|
|
||||||
|
kb.add(self.autocomplete_button)(_)
|
||||||
|
|
||||||
|
history: InMemoryHistory | ThreadedHistory
|
||||||
|
if self.history_filename:
|
||||||
|
history = ThreadedHistory(FileHistory(self.history_filename))
|
||||||
|
else:
|
||||||
|
history = InMemoryHistory()
|
||||||
|
|
||||||
|
style = Style.from_dict({'valid': '#00ff00', 'invalid': '#ff0000'})
|
||||||
|
self._session = PromptSession(
|
||||||
|
history=history,
|
||||||
|
completer=ThreadedCompleter(HistoryCompleter(history, all_commands)),
|
||||||
|
complete_while_typing=False,
|
||||||
|
key_bindings=kb,
|
||||||
|
auto_suggest=AutoSuggestFromHistory() if self.auto_suggestions else None,
|
||||||
|
style=style if self.command_highlighting else None,
|
||||||
|
lexer=CommandLexer(all_commands) if self.command_highlighting else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
def prompt(self, prompt_text: str | HTML = ">>> ") -> str:
|
def prompt(self, prompt_text: str | HTML = ">>> ") -> str:
|
||||||
@@ -45,7 +137,7 @@ class AutoCompleter:
|
|||||||
return input(prompt_text if isinstance(prompt_text, str) else ">>> ")
|
return input(prompt_text if isinstance(prompt_text, str) else ">>> ")
|
||||||
if self._session is None:
|
if self._session is None:
|
||||||
raise RuntimeError("Call initial_setup() before using prompt()")
|
raise RuntimeError("Call initial_setup() before using prompt()")
|
||||||
|
return self._session.prompt(
|
||||||
from ._ext_features_impl import do_prompt
|
HTML(prompt_text) if isinstance(prompt_text, str) else prompt_text,
|
||||||
|
cursor=CursorShape.BLINKING_BEAM
|
||||||
return do_prompt(self._session, prompt_text)
|
)
|
||||||
@@ -3,6 +3,8 @@ __all__ = ["App"]
|
|||||||
import difflib
|
import difflib
|
||||||
from typing import Never, TypeAlias
|
from typing import Never, TypeAlias
|
||||||
|
|
||||||
|
from rich.console import Console
|
||||||
|
|
||||||
from argenta.app.autocompleter import AutoCompleter
|
from argenta.app.autocompleter import AutoCompleter
|
||||||
from argenta.app.behavior_handlers.models import (BehaviorHandlersFabric,
|
from argenta.app.behavior_handlers.models import (BehaviorHandlersFabric,
|
||||||
BehaviorHandlersSettersMixin)
|
BehaviorHandlersSettersMixin)
|
||||||
@@ -142,7 +144,7 @@ class BaseApp(BehaviorHandlersSettersMixin):
|
|||||||
is_stdout_redirected_by_router=processing_router.is_redirect_stdout_disabled
|
is_stdout_redirected_by_router=processing_router.is_redirect_stdout_disabled
|
||||||
)
|
)
|
||||||
|
|
||||||
def _run_repl(self) -> None:
|
def _run_polling(self) -> None:
|
||||||
self._viewer.view_initial_message(self._initial_message)
|
self._viewer.view_initial_message(self._initial_message)
|
||||||
self._pre_cycle_setup()
|
self._pre_cycle_setup()
|
||||||
while True:
|
while True:
|
||||||
@@ -187,7 +189,7 @@ class App(BaseApp):
|
|||||||
repeat_command_groups_printing: bool = False,
|
repeat_command_groups_printing: bool = False,
|
||||||
override_system_messages: bool = False,
|
override_system_messages: bool = False,
|
||||||
autocompleter: AutoCompleter | None = None,
|
autocompleter: AutoCompleter | None = None,
|
||||||
printer: Printer | None = None,
|
printer: Printer = Console().print,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Public. The essence of the application itself.
|
Public. The essence of the application itself.
|
||||||
@@ -204,7 +206,6 @@ class App(BaseApp):
|
|||||||
:param printer: system messages text output function
|
:param printer: system messages text output function
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
from rich.console import Console
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
initial_message=initial_message,
|
initial_message=initial_message,
|
||||||
@@ -215,7 +216,7 @@ class App(BaseApp):
|
|||||||
repeat_command_groups_printing=repeat_command_groups_printing,
|
repeat_command_groups_printing=repeat_command_groups_printing,
|
||||||
override_system_messages=override_system_messages,
|
override_system_messages=override_system_messages,
|
||||||
autocompleter=autocompleter or AutoCompleter(),
|
autocompleter=autocompleter or AutoCompleter(),
|
||||||
printer=printer or Console().print,
|
printer=printer,
|
||||||
)
|
)
|
||||||
|
|
||||||
def include_router(self, router: Router) -> None:
|
def include_router(self, router: Router) -> None:
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ __all__ = [
|
|||||||
"HandlerFunc",
|
"HandlerFunc",
|
||||||
]
|
]
|
||||||
|
|
||||||
from typing import Any, Protocol, TypeVar, Callable
|
from typing import Any, Protocol, TypeVar
|
||||||
|
|
||||||
|
from argenta.response import Response
|
||||||
|
|
||||||
T = TypeVar("T", contravariant=True)
|
T = TypeVar("T", contravariant=True)
|
||||||
|
|
||||||
@@ -38,4 +39,6 @@ class DescriptionMessageGenerator(Protocol):
|
|||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
type HandlerFunc = Callable[..., Any]
|
class HandlerFunc(Protocol):
|
||||||
|
def __call__(self, response: Response, /, *args: Any, **kwargs: Any) -> None:
|
||||||
|
raise NotImplementedError
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
__all__ = ["Orchestrator"]
|
__all__ = ["Orchestrator"]
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from dishka import Provider, make_container
|
from dishka import Provider, make_container
|
||||||
|
|
||||||
from argenta.app import App
|
from argenta.app import App
|
||||||
@@ -9,12 +7,13 @@ from argenta.di.integration import setup_dishka
|
|||||||
from argenta.di.providers import SystemProvider
|
from argenta.di.providers import SystemProvider
|
||||||
from argenta.orchestrator.argparser import ArgParser
|
from argenta.orchestrator.argparser import ArgParser
|
||||||
|
|
||||||
|
DEFAULT_ARGPARSER: ArgParser = ArgParser(processed_args=[])
|
||||||
|
|
||||||
|
|
||||||
class Orchestrator:
|
class Orchestrator:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
arg_parser: ArgParser | None = None,
|
arg_parser: ArgParser = DEFAULT_ARGPARSER,
|
||||||
custom_providers: list[Provider] | None = None,
|
custom_providers: list[Provider] | None = None,
|
||||||
auto_inject_handlers: bool = True,
|
auto_inject_handlers: bool = True,
|
||||||
):
|
):
|
||||||
@@ -23,14 +22,13 @@ class Orchestrator:
|
|||||||
:param arg_parser: Cmd argument parser and configurator at startup
|
:param arg_parser: Cmd argument parser and configurator at startup
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
self._arg_parser: ArgParser | None = arg_parser if not os.getenv('RUN_FROM_ARGENTA_RUNNER') else None
|
self._arg_parser: ArgParser = arg_parser
|
||||||
self._custom_providers: list[Provider] = custom_providers or []
|
self._custom_providers: list[Provider] = custom_providers or []
|
||||||
self._auto_inject_handlers: bool = auto_inject_handlers
|
self._auto_inject_handlers: bool = auto_inject_handlers
|
||||||
|
|
||||||
if self._arg_parser is not None:
|
|
||||||
self._arg_parser._parse_args() # pyright: ignore[reportPrivateUsage]
|
self._arg_parser._parse_args() # pyright: ignore[reportPrivateUsage]
|
||||||
|
|
||||||
def run_repl(self, app: App) -> None:
|
def start_polling(self, app: App) -> None:
|
||||||
"""
|
"""
|
||||||
Public. Starting the user input processing cycle
|
Public. Starting the user input processing cycle
|
||||||
:param app: a running application
|
:param app: a running application
|
||||||
@@ -41,4 +39,4 @@ class Orchestrator:
|
|||||||
)
|
)
|
||||||
setup_dishka(app, container, auto_inject=self._auto_inject_handlers)
|
setup_dishka(app, container, auto_inject=self._auto_inject_handlers)
|
||||||
|
|
||||||
app._run_repl() # pyright: ignore[reportPrivateUsage]
|
app._run_polling()
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
__all__ = ["Response"]
|
__all__ = ["Response"]
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from dishka import Container
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from dishka import Container
|
|
||||||
|
|
||||||
from argenta.command import InputFlags
|
from argenta.command import InputFlags
|
||||||
from argenta.response.status import ResponseStatus
|
from argenta.response.status import ResponseStatus
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ __all__ = ["Router"]
|
|||||||
from inspect import get_annotations, getfullargspec, getsourcefile, getsourcelines
|
from inspect import get_annotations, getfullargspec, getsourcefile, getsourcelines
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
|
|
||||||
|
from rich.console import Console
|
||||||
|
|
||||||
from argenta.app.protocols import HandlerFunc
|
from argenta.app.protocols import HandlerFunc
|
||||||
from argenta.command import Command, InputCommand, InputFlags
|
from argenta.command import Command, InputCommand, InputFlags
|
||||||
from argenta.command.flag import ValidationStatus
|
from argenta.command.flag import ValidationStatus
|
||||||
@@ -18,7 +20,7 @@ from argenta.router.exceptions import (RepeatedAliasNameException,
|
|||||||
class Router:
|
class Router:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
title: str = "Title",
|
title: str = "Default title",
|
||||||
*,
|
*,
|
||||||
disable_redirect_stdout: bool = False,
|
disable_redirect_stdout: bool = False,
|
||||||
):
|
):
|
||||||
@@ -173,7 +175,6 @@ class Router:
|
|||||||
response_arg_annotation = func_annotations.get(response_arg)
|
response_arg_annotation = func_annotations.get(response_arg)
|
||||||
|
|
||||||
if response_arg_annotation is not None and response_arg_annotation is not Response:
|
if response_arg_annotation is not None and response_arg_annotation is not Response:
|
||||||
from rich.console import Console
|
|
||||||
source_line: int = getsourcelines(func)[1]
|
source_line: int = getsourcelines(func)[1]
|
||||||
Console().print(
|
Console().print(
|
||||||
f'\nFile "{getsourcefile(func)}", line {source_line}\n[b red]WARNING:[/b red] [i]The typehint '
|
f'\nFile "{getsourcefile(func)}", line {source_line}\n[b red]WARNING:[/b red] [i]The typehint '
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ def test_empty_input_triggers_empty_command_handler(monkeypatch: pytest.MonkeyPa
|
|||||||
app = App(override_system_messages=True, printer=print)
|
app = App(override_system_messages=True, printer=print)
|
||||||
app.include_router(router)
|
app.include_router(router)
|
||||||
app.set_empty_command_handler(lambda: print('Empty input command'))
|
app.set_empty_command_handler(lambda: print('Empty input command'))
|
||||||
orchestrator.run_repl(app)
|
orchestrator.start_polling(app)
|
||||||
|
|
||||||
output = capsys.readouterr().out
|
output = capsys.readouterr().out
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ def test_unknown_command_triggers_unknown_command_handler(monkeypatch: pytest.Mo
|
|||||||
app = App(override_system_messages=True, printer=print)
|
app = App(override_system_messages=True, printer=print)
|
||||||
app.include_router(router)
|
app.include_router(router)
|
||||||
app.set_unknown_command_handler(lambda command: print(f'Unknown command: {command.trigger}'))
|
app.set_unknown_command_handler(lambda command: print(f'Unknown command: {command.trigger}'))
|
||||||
orchestrator.run_repl(app)
|
orchestrator.start_polling(app)
|
||||||
|
|
||||||
output = capsys.readouterr().out
|
output = capsys.readouterr().out
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ def test_mixed_valid_and_unknown_commands_handled_correctly(monkeypatch: pytest.
|
|||||||
app = App(override_system_messages=True, printer=print)
|
app = App(override_system_messages=True, printer=print)
|
||||||
app.include_router(router)
|
app.include_router(router)
|
||||||
app.set_unknown_command_handler(lambda command: print(f'Unknown command: {command.trigger}'))
|
app.set_unknown_command_handler(lambda command: print(f'Unknown command: {command.trigger}'))
|
||||||
orchestrator.run_repl(app)
|
orchestrator.start_polling(app)
|
||||||
|
|
||||||
output = capsys.readouterr().out
|
output = capsys.readouterr().out
|
||||||
|
|
||||||
@@ -110,7 +110,7 @@ def test_multiple_commands_with_unknown_command_in_between(monkeypatch: pytest.M
|
|||||||
app = App(override_system_messages=True, printer=print)
|
app = App(override_system_messages=True, printer=print)
|
||||||
app.include_router(router)
|
app.include_router(router)
|
||||||
app.set_unknown_command_handler(lambda command: print(f'Unknown command: {command.trigger}'))
|
app.set_unknown_command_handler(lambda command: print(f'Unknown command: {command.trigger}'))
|
||||||
orchestrator.run_repl(app)
|
orchestrator.start_polling(app)
|
||||||
|
|
||||||
output = capsys.readouterr().out
|
output = capsys.readouterr().out
|
||||||
|
|
||||||
@@ -137,7 +137,7 @@ def test_unregistered_flag_without_value_is_accessible(monkeypatch: pytest.Monke
|
|||||||
|
|
||||||
app = App(override_system_messages=True, printer=print)
|
app = App(override_system_messages=True, printer=print)
|
||||||
app.include_router(router)
|
app.include_router(router)
|
||||||
orchestrator.run_repl(app)
|
orchestrator.start_polling(app)
|
||||||
|
|
||||||
output = capsys.readouterr().out
|
output = capsys.readouterr().out
|
||||||
|
|
||||||
@@ -161,7 +161,7 @@ def test_unregistered_flag_with_value_is_accessible(monkeypatch: pytest.MonkeyPa
|
|||||||
|
|
||||||
app = App(override_system_messages=True, printer=print)
|
app = App(override_system_messages=True, printer=print)
|
||||||
app.include_router(router)
|
app.include_router(router)
|
||||||
orchestrator.run_repl(app)
|
orchestrator.start_polling(app)
|
||||||
|
|
||||||
output = capsys.readouterr().out
|
output = capsys.readouterr().out
|
||||||
|
|
||||||
@@ -184,7 +184,7 @@ def test_registered_and_unregistered_flags_coexist(monkeypatch: pytest.MonkeyPat
|
|||||||
|
|
||||||
app = App(override_system_messages=True, printer=print)
|
app = App(override_system_messages=True, printer=print)
|
||||||
app.include_router(router)
|
app.include_router(router)
|
||||||
orchestrator.run_repl(app)
|
orchestrator.start_polling(app)
|
||||||
|
|
||||||
output = capsys.readouterr().out
|
output = capsys.readouterr().out
|
||||||
|
|
||||||
@@ -210,7 +210,7 @@ def test_flag_without_value_triggers_incorrect_syntax_handler(monkeypatch: pytes
|
|||||||
app = App(override_system_messages=True, printer=print)
|
app = App(override_system_messages=True, printer=print)
|
||||||
app.include_router(router)
|
app.include_router(router)
|
||||||
app.set_incorrect_input_syntax_handler(lambda command: print(f'Incorrect flag syntax: "{command}"'))
|
app.set_incorrect_input_syntax_handler(lambda command: print(f'Incorrect flag syntax: "{command}"'))
|
||||||
orchestrator.run_repl(app)
|
orchestrator.start_polling(app)
|
||||||
|
|
||||||
output = capsys.readouterr().out
|
output = capsys.readouterr().out
|
||||||
|
|
||||||
@@ -236,7 +236,7 @@ def test_repeated_flags_trigger_repeated_flags_handler(monkeypatch: pytest.Monke
|
|||||||
app = App(override_system_messages=True, printer=print)
|
app = App(override_system_messages=True, printer=print)
|
||||||
app.include_router(router)
|
app.include_router(router)
|
||||||
app.set_repeated_input_flags_handler(lambda command: print(f'Repeated input flags: "{command}"'))
|
app.set_repeated_input_flags_handler(lambda command: print(f'Repeated input flags: "{command}"'))
|
||||||
orchestrator.run_repl(app)
|
orchestrator.start_polling(app)
|
||||||
|
|
||||||
output = capsys.readouterr().out
|
output = capsys.readouterr().out
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ def test_simple_command_executes_successfully(monkeypatch: pytest.MonkeyPatch, c
|
|||||||
|
|
||||||
app = App(override_system_messages=True, printer=print)
|
app = App(override_system_messages=True, printer=print)
|
||||||
app.include_router(router)
|
app.include_router(router)
|
||||||
orchestrator.run_repl(app)
|
orchestrator.start_polling(app)
|
||||||
|
|
||||||
output = capsys.readouterr().out
|
output = capsys.readouterr().out
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ def test_two_commands_execute_sequentially(monkeypatch: pytest.MonkeyPatch, caps
|
|||||||
|
|
||||||
app = App(override_system_messages=True, printer=print)
|
app = App(override_system_messages=True, printer=print)
|
||||||
app.include_router(router)
|
app.include_router(router)
|
||||||
orchestrator.run_repl(app)
|
orchestrator.start_polling(app)
|
||||||
|
|
||||||
output = capsys.readouterr().out
|
output = capsys.readouterr().out
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ def test_three_commands_execute_sequentially(monkeypatch: pytest.MonkeyPatch, ca
|
|||||||
|
|
||||||
app = App(override_system_messages=True, printer=print)
|
app = App(override_system_messages=True, printer=print)
|
||||||
app.include_router(router)
|
app.include_router(router)
|
||||||
orchestrator.run_repl(app)
|
orchestrator.start_polling(app)
|
||||||
|
|
||||||
output = capsys.readouterr().out
|
output = capsys.readouterr().out
|
||||||
|
|
||||||
@@ -118,7 +118,7 @@ def test_custom_flag_without_value_is_recognized(monkeypatch: pytest.MonkeyPatch
|
|||||||
|
|
||||||
app = App(override_system_messages=True, printer=print)
|
app = App(override_system_messages=True, printer=print)
|
||||||
app.include_router(router)
|
app.include_router(router)
|
||||||
orchestrator.run_repl(app)
|
orchestrator.start_polling(app)
|
||||||
|
|
||||||
output = capsys.readouterr().out
|
output = capsys.readouterr().out
|
||||||
|
|
||||||
@@ -141,7 +141,7 @@ def test_custom_flag_with_regex_validation_accepts_valid_value(monkeypatch: pyte
|
|||||||
|
|
||||||
app = App(override_system_messages=True, repeat_command_groups_printing=True, printer=print)
|
app = App(override_system_messages=True, repeat_command_groups_printing=True, printer=print)
|
||||||
app.include_router(router)
|
app.include_router(router)
|
||||||
orchestrator.run_repl(app)
|
orchestrator.start_polling(app)
|
||||||
|
|
||||||
output = capsys.readouterr().out
|
output = capsys.readouterr().out
|
||||||
|
|
||||||
@@ -169,7 +169,7 @@ def test_predefined_short_help_flag_is_recognized(monkeypatch: pytest.MonkeyPatc
|
|||||||
|
|
||||||
app = App(override_system_messages=True, printer=print)
|
app = App(override_system_messages=True, printer=print)
|
||||||
app.include_router(router)
|
app.include_router(router)
|
||||||
orchestrator.run_repl(app)
|
orchestrator.start_polling(app)
|
||||||
|
|
||||||
output = capsys.readouterr().out
|
output = capsys.readouterr().out
|
||||||
|
|
||||||
@@ -192,7 +192,7 @@ def test_predefined_info_flag_is_recognized(monkeypatch: pytest.MonkeyPatch, cap
|
|||||||
|
|
||||||
app = App(override_system_messages=True, printer=print)
|
app = App(override_system_messages=True, printer=print)
|
||||||
app.include_router(router)
|
app.include_router(router)
|
||||||
orchestrator.run_repl(app)
|
orchestrator.start_polling(app)
|
||||||
|
|
||||||
output = capsys.readouterr().out
|
output = capsys.readouterr().out
|
||||||
|
|
||||||
@@ -215,7 +215,7 @@ def test_predefined_host_flag_with_value_is_recognized(monkeypatch: pytest.Monke
|
|||||||
|
|
||||||
app = App(override_system_messages=True, printer=print)
|
app = App(override_system_messages=True, printer=print)
|
||||||
app.include_router(router)
|
app.include_router(router)
|
||||||
orchestrator.run_repl(app)
|
orchestrator.start_polling(app)
|
||||||
|
|
||||||
output = capsys.readouterr().out
|
output = capsys.readouterr().out
|
||||||
|
|
||||||
@@ -244,7 +244,7 @@ def test_two_predefined_flags_are_recognized_together(monkeypatch: pytest.Monkey
|
|||||||
|
|
||||||
app = App(override_system_messages=True, printer=print)
|
app = App(override_system_messages=True, printer=print)
|
||||||
app.include_router(router)
|
app.include_router(router)
|
||||||
orchestrator.run_repl(app)
|
orchestrator.start_polling(app)
|
||||||
|
|
||||||
output = capsys.readouterr().out
|
output = capsys.readouterr().out
|
||||||
|
|
||||||
|
|||||||
@@ -10,11 +10,14 @@ from prompt_toolkit.completion import CompleteEvent
|
|||||||
from prompt_toolkit.document import Document
|
from prompt_toolkit.document import Document
|
||||||
from prompt_toolkit.history import InMemoryHistory
|
from prompt_toolkit.history import InMemoryHistory
|
||||||
|
|
||||||
from argenta.app.autocompleter._ext_features_impl import CommandLexer, HistoryCompleter
|
from argenta.app.autocompleter.entity import (
|
||||||
from argenta.app.autocompleter.entity import AutoCompleter
|
AutoCompleter,
|
||||||
|
CommandLexer,
|
||||||
|
HistoryCompleter
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
COMMANDS: set[str] = {"start", "stop", "status"}
|
COMMANDS: set[str] = {"start", "stop", "status"}
|
||||||
_IMPL = "argenta.app.autocompleter._ext_features_impl"
|
|
||||||
|
|
||||||
|
|
||||||
def test_autocompleter_initializes_with_default_params() -> None:
|
def test_autocompleter_initializes_with_default_params() -> None:
|
||||||
@@ -30,7 +33,7 @@ def test_autocompleter_initializes_with_custom_params() -> None:
|
|||||||
history_filename="test.txt",
|
history_filename="test.txt",
|
||||||
autocomplete_button="c-space",
|
autocomplete_button="c-space",
|
||||||
command_highlighting=False,
|
command_highlighting=False,
|
||||||
auto_suggestions=False,
|
auto_suggestions=False
|
||||||
)
|
)
|
||||||
assert completer.history_filename == "test.txt"
|
assert completer.history_filename == "test.txt"
|
||||||
assert completer.autocomplete_button == "c-space"
|
assert completer.autocomplete_button == "c-space"
|
||||||
@@ -188,10 +191,8 @@ def test_history_completer_returns_early_when_no_matches() -> None:
|
|||||||
def test_autocompleter_initial_setup_with_commands() -> None:
|
def test_autocompleter_initial_setup_with_commands() -> None:
|
||||||
completer = AutoCompleter()
|
completer = AutoCompleter()
|
||||||
|
|
||||||
with (
|
with patch.object(sys.stdin, 'isatty', return_value=True), \
|
||||||
patch.object(sys.stdin, "isatty", return_value=True),
|
patch('argenta.app.autocompleter.entity.PromptSession') as mock_session:
|
||||||
patch(f"{_IMPL}.PromptSession") as mock_session,
|
|
||||||
):
|
|
||||||
completer.initial_setup({"start", "stop", "status"})
|
completer.initial_setup({"start", "stop", "status"})
|
||||||
|
|
||||||
assert completer._session is not None
|
assert completer._session is not None
|
||||||
@@ -200,17 +201,15 @@ def test_autocompleter_initial_setup_with_commands() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_autocompleter_initial_setup_with_history_file() -> None:
|
def test_autocompleter_initial_setup_with_history_file() -> None:
|
||||||
with tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".txt") as f:
|
with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt') as f:
|
||||||
history_file = f.name
|
history_file = f.name
|
||||||
|
|
||||||
try:
|
try:
|
||||||
completer = AutoCompleter(history_filename=history_file)
|
completer = AutoCompleter(history_filename=history_file)
|
||||||
|
|
||||||
with (
|
with patch.object(sys.stdin, 'isatty', return_value=True), \
|
||||||
patch.object(sys.stdin, "isatty", return_value=True),
|
patch('argenta.app.autocompleter.entity.PromptSession'), \
|
||||||
patch(f"{_IMPL}.PromptSession"),
|
patch('argenta.app.autocompleter.entity.ThreadedHistory') as mock_threaded_history:
|
||||||
patch(f"{_IMPL}.ThreadedHistory") as mock_threaded_history,
|
|
||||||
):
|
|
||||||
completer.initial_setup({"start", "stop"})
|
completer.initial_setup({"start", "stop"})
|
||||||
|
|
||||||
assert completer._session is not None
|
assert completer._session is not None
|
||||||
@@ -224,11 +223,9 @@ def test_autocompleter_initial_setup_with_history_file() -> None:
|
|||||||
def test_autocompleter_initial_setup_without_history_file() -> None:
|
def test_autocompleter_initial_setup_without_history_file() -> None:
|
||||||
completer = AutoCompleter(history_filename=None)
|
completer = AutoCompleter(history_filename=None)
|
||||||
|
|
||||||
with (
|
with patch.object(sys.stdin, 'isatty', return_value=True), \
|
||||||
patch.object(sys.stdin, "isatty", return_value=True),
|
patch('argenta.app.autocompleter.entity.PromptSession'), \
|
||||||
patch(f"{_IMPL}.PromptSession"),
|
patch('argenta.app.autocompleter.entity.InMemoryHistory') as mock_in_memory:
|
||||||
patch(f"{_IMPL}.InMemoryHistory") as mock_in_memory,
|
|
||||||
):
|
|
||||||
completer.initial_setup({"start", "stop"})
|
completer.initial_setup({"start", "stop"})
|
||||||
|
|
||||||
assert completer._session is not None
|
assert completer._session is not None
|
||||||
@@ -239,10 +236,8 @@ def test_autocompleter_initial_setup_without_history_file() -> None:
|
|||||||
def test_autocompleter_initial_setup_with_custom_autocomplete_button() -> None:
|
def test_autocompleter_initial_setup_with_custom_autocomplete_button() -> None:
|
||||||
completer = AutoCompleter(autocomplete_button="c-space")
|
completer = AutoCompleter(autocomplete_button="c-space")
|
||||||
|
|
||||||
with (
|
with patch.object(sys.stdin, 'isatty', return_value=True), \
|
||||||
patch.object(sys.stdin, "isatty", return_value=True),
|
patch('argenta.app.autocompleter.entity.PromptSession'):
|
||||||
patch(f"{_IMPL}.PromptSession"),
|
|
||||||
):
|
|
||||||
completer.initial_setup({"start", "stop"})
|
completer.initial_setup({"start", "stop"})
|
||||||
|
|
||||||
assert completer._session is not None
|
assert completer._session is not None
|
||||||
@@ -252,34 +247,31 @@ def test_autocompleter_initial_setup_with_custom_autocomplete_button() -> None:
|
|||||||
def test_autocompleter_initial_setup_without_auto_suggestions() -> None:
|
def test_autocompleter_initial_setup_without_auto_suggestions() -> None:
|
||||||
completer = AutoCompleter(auto_suggestions=False)
|
completer = AutoCompleter(auto_suggestions=False)
|
||||||
|
|
||||||
with (
|
with patch.object(sys.stdin, 'isatty', return_value=True), \
|
||||||
patch.object(sys.stdin, "isatty", return_value=True),
|
patch('argenta.app.autocompleter.entity.PromptSession') as mock_session:
|
||||||
patch(f"{_IMPL}.PromptSession") as mock_session,
|
|
||||||
):
|
|
||||||
completer.initial_setup({"start", "stop"})
|
completer.initial_setup({"start", "stop"})
|
||||||
|
|
||||||
assert completer._session is not None
|
assert completer._session is not None
|
||||||
call_kwargs = mock_session.call_args[1]
|
call_kwargs = mock_session.call_args[1]
|
||||||
assert call_kwargs["auto_suggest"] is None
|
assert call_kwargs['auto_suggest'] is None
|
||||||
|
|
||||||
|
|
||||||
def test_autocompleter_initial_setup_without_command_highlighting() -> None:
|
def test_autocompleter_initial_setup_without_command_highlighting() -> None:
|
||||||
completer = AutoCompleter(command_highlighting=False)
|
completer = AutoCompleter(command_highlighting=False)
|
||||||
|
|
||||||
with (
|
with patch.object(sys.stdin, 'isatty', return_value=True), \
|
||||||
patch.object(sys.stdin, "isatty", return_value=True),
|
patch('argenta.app.autocompleter.entity.PromptSession') as mock_session:
|
||||||
patch(f"{_IMPL}.PromptSession") as mock_session,
|
|
||||||
):
|
|
||||||
completer.initial_setup({"start", "stop"})
|
completer.initial_setup({"start", "stop"})
|
||||||
|
|
||||||
assert completer._session is not None
|
assert completer._session is not None
|
||||||
call_kwargs = mock_session.call_args[1]
|
call_kwargs = mock_session.call_args[1]
|
||||||
assert call_kwargs["style"] is None
|
assert call_kwargs['style'] is None
|
||||||
assert call_kwargs["lexer"] is None
|
assert call_kwargs['lexer'] is None
|
||||||
|
|
||||||
|
|
||||||
def _setup_captured_handler(completer: AutoCompleter) -> Callable[[Any], None] | None:
|
def test_autocompleter_key_binding_handler_with_complete_state() -> None:
|
||||||
"""Вспомогательная функция: поднимает initial_setup и захватывает kb-хендлер."""
|
completer = AutoCompleter()
|
||||||
|
|
||||||
captured_handler: Callable[[Any], None] | None = None
|
captured_handler: Callable[[Any], None] | None = None
|
||||||
|
|
||||||
def capture_kb_add(key: str) -> Callable[[Callable[[Any], None]], Callable[[Any], None]]:
|
def capture_kb_add(key: str) -> Callable[[Callable[[Any], None]], Callable[[Any], None]]:
|
||||||
@@ -289,22 +281,16 @@ def _setup_captured_handler(completer: AutoCompleter) -> Callable[[Any], None] |
|
|||||||
return func
|
return func
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
with (
|
with patch.object(sys.stdin, 'isatty', return_value=True), \
|
||||||
patch.object(sys.stdin, "isatty", return_value=True),
|
patch('argenta.app.autocompleter.entity.PromptSession'), \
|
||||||
patch(f"{_IMPL}.PromptSession"),
|
patch('argenta.app.autocompleter.entity.KeyBindings') as mock_kb_class:
|
||||||
patch(f"{_IMPL}.KeyBindings") as mock_kb_class,
|
|
||||||
):
|
|
||||||
mock_kb = MagicMock()
|
mock_kb = MagicMock()
|
||||||
mock_kb.add = capture_kb_add
|
mock_kb.add = capture_kb_add
|
||||||
mock_kb_class.return_value = mock_kb
|
mock_kb_class.return_value = mock_kb
|
||||||
|
|
||||||
completer.initial_setup({"start", "stop"})
|
completer.initial_setup({"start", "stop"})
|
||||||
|
|
||||||
return captured_handler
|
|
||||||
|
|
||||||
|
|
||||||
def test_autocompleter_key_binding_handler_with_complete_state() -> None:
|
|
||||||
completer = AutoCompleter()
|
|
||||||
captured_handler = _setup_captured_handler(completer)
|
|
||||||
assert captured_handler is not None
|
assert captured_handler is not None
|
||||||
|
|
||||||
mock_event = MagicMock()
|
mock_event = MagicMock()
|
||||||
@@ -319,8 +305,25 @@ def test_autocompleter_key_binding_handler_with_complete_state() -> None:
|
|||||||
|
|
||||||
def test_autocompleter_key_binding_handler_no_completions() -> None:
|
def test_autocompleter_key_binding_handler_no_completions() -> None:
|
||||||
completer = AutoCompleter()
|
completer = AutoCompleter()
|
||||||
captured_handler = _setup_captured_handler(completer)
|
|
||||||
assert captured_handler is not None
|
captured_handler: Callable[[Any], None] | None = None
|
||||||
|
|
||||||
|
def capture_kb_add(key: str) -> Callable[[Callable[[Any], None]], Callable[[Any], None]]:
|
||||||
|
def decorator(func: Callable[[Any], None]) -> Callable[[Any], None]:
|
||||||
|
nonlocal captured_handler
|
||||||
|
captured_handler = func
|
||||||
|
return func
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
with patch.object(sys.stdin, 'isatty', return_value=True), \
|
||||||
|
patch('argenta.app.autocompleter.entity.PromptSession'), \
|
||||||
|
patch('argenta.app.autocompleter.entity.KeyBindings') as mock_kb_class:
|
||||||
|
|
||||||
|
mock_kb = MagicMock()
|
||||||
|
mock_kb.add = capture_kb_add
|
||||||
|
mock_kb_class.return_value = mock_kb
|
||||||
|
|
||||||
|
completer.initial_setup({"start", "stop"})
|
||||||
|
|
||||||
mock_event = MagicMock()
|
mock_event = MagicMock()
|
||||||
mock_buff = MagicMock()
|
mock_buff = MagicMock()
|
||||||
@@ -330,6 +333,7 @@ def test_autocompleter_key_binding_handler_no_completions() -> None:
|
|||||||
mock_buff.completer = mock_completer
|
mock_buff.completer = mock_completer
|
||||||
mock_event.app.current_buffer = mock_buff
|
mock_event.app.current_buffer = mock_buff
|
||||||
|
|
||||||
|
assert captured_handler is not None
|
||||||
captured_handler(mock_event)
|
captured_handler(mock_event)
|
||||||
|
|
||||||
mock_buff.start_completion.assert_not_called()
|
mock_buff.start_completion.assert_not_called()
|
||||||
@@ -338,8 +342,25 @@ def test_autocompleter_key_binding_handler_no_completions() -> None:
|
|||||||
|
|
||||||
def test_autocompleter_key_binding_handler_single_completion() -> None:
|
def test_autocompleter_key_binding_handler_single_completion() -> None:
|
||||||
completer = AutoCompleter()
|
completer = AutoCompleter()
|
||||||
captured_handler = _setup_captured_handler(completer)
|
|
||||||
assert captured_handler is not None
|
captured_handler: Callable[[Any], None] | None = None
|
||||||
|
|
||||||
|
def capture_kb_add(key: str) -> Callable[[Callable[[Any], None]], Callable[[Any], None]]:
|
||||||
|
def decorator(func: Callable[[Any], None]) -> Callable[[Any], None]:
|
||||||
|
nonlocal captured_handler
|
||||||
|
captured_handler = func
|
||||||
|
return func
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
with patch.object(sys.stdin, 'isatty', return_value=True), \
|
||||||
|
patch('argenta.app.autocompleter.entity.PromptSession'), \
|
||||||
|
patch('argenta.app.autocompleter.entity.KeyBindings') as mock_kb_class:
|
||||||
|
|
||||||
|
mock_kb = MagicMock()
|
||||||
|
mock_kb.add = capture_kb_add
|
||||||
|
mock_kb_class.return_value = mock_kb
|
||||||
|
|
||||||
|
completer.initial_setup({"start", "stop"})
|
||||||
|
|
||||||
mock_event = MagicMock()
|
mock_event = MagicMock()
|
||||||
mock_buff = MagicMock()
|
mock_buff = MagicMock()
|
||||||
@@ -350,6 +371,7 @@ def test_autocompleter_key_binding_handler_single_completion() -> None:
|
|||||||
mock_buff.completer = mock_completer
|
mock_buff.completer = mock_completer
|
||||||
mock_event.app.current_buffer = mock_buff
|
mock_event.app.current_buffer = mock_buff
|
||||||
|
|
||||||
|
assert captured_handler is not None
|
||||||
captured_handler(mock_event)
|
captured_handler(mock_event)
|
||||||
|
|
||||||
mock_buff.apply_completion.assert_called_once_with(mock_completion)
|
mock_buff.apply_completion.assert_called_once_with(mock_completion)
|
||||||
@@ -358,8 +380,25 @@ def test_autocompleter_key_binding_handler_single_completion() -> None:
|
|||||||
|
|
||||||
def test_autocompleter_key_binding_handler_multiple_completions() -> None:
|
def test_autocompleter_key_binding_handler_multiple_completions() -> None:
|
||||||
completer = AutoCompleter()
|
completer = AutoCompleter()
|
||||||
captured_handler = _setup_captured_handler(completer)
|
|
||||||
assert captured_handler is not None
|
captured_handler: Callable[[Any], None] | None = None
|
||||||
|
|
||||||
|
def capture_kb_add(key: str) -> Callable[[Callable[[Any], None]], Callable[[Any], None]]:
|
||||||
|
def decorator(func: Callable[[Any], None]) -> Callable[[Any], None]:
|
||||||
|
nonlocal captured_handler
|
||||||
|
captured_handler = func
|
||||||
|
return func
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
with patch.object(sys.stdin, 'isatty', return_value=True), \
|
||||||
|
patch('argenta.app.autocompleter.entity.PromptSession'), \
|
||||||
|
patch('argenta.app.autocompleter.entity.KeyBindings') as mock_kb_class:
|
||||||
|
|
||||||
|
mock_kb = MagicMock()
|
||||||
|
mock_kb.add = capture_kb_add
|
||||||
|
mock_kb_class.return_value = mock_kb
|
||||||
|
|
||||||
|
completer.initial_setup({"start", "stop"})
|
||||||
|
|
||||||
mock_event = MagicMock()
|
mock_event = MagicMock()
|
||||||
mock_buff = MagicMock()
|
mock_buff = MagicMock()
|
||||||
@@ -371,6 +410,7 @@ def test_autocompleter_key_binding_handler_multiple_completions() -> None:
|
|||||||
mock_buff.completer = mock_completer
|
mock_buff.completer = mock_completer
|
||||||
mock_event.app.current_buffer = mock_buff
|
mock_event.app.current_buffer = mock_buff
|
||||||
|
|
||||||
|
assert captured_handler is not None
|
||||||
captured_handler(mock_event)
|
captured_handler(mock_event)
|
||||||
|
|
||||||
mock_buff.start_completion.assert_called_once_with(select_first=False)
|
mock_buff.start_completion.assert_called_once_with(select_first=False)
|
||||||
@@ -380,43 +420,43 @@ def test_autocompleter_key_binding_handler_multiple_completions() -> None:
|
|||||||
def test_autocompleter_prompt_in_fallback_mode_with_string() -> None:
|
def test_autocompleter_prompt_in_fallback_mode_with_string() -> None:
|
||||||
completer = AutoCompleter()
|
completer = AutoCompleter()
|
||||||
|
|
||||||
with patch.object(sys.stdin, "isatty", return_value=False):
|
with patch.object(sys.stdin, 'isatty', return_value=False):
|
||||||
completer.initial_setup({"start", "stop"})
|
completer.initial_setup({"start", "stop"})
|
||||||
|
|
||||||
assert completer._fallback_mode is True
|
assert completer._fallback_mode is True
|
||||||
|
|
||||||
with patch("builtins.input", return_value="test input"):
|
with patch('builtins.input', return_value='test input'):
|
||||||
result = completer.prompt(">>> ")
|
result = completer.prompt(">>> ")
|
||||||
|
|
||||||
assert result == "test input"
|
assert result == 'test input'
|
||||||
|
|
||||||
|
|
||||||
def test_autocompleter_prompt_in_fallback_mode_with_html() -> None:
|
def test_autocompleter_prompt_in_fallback_mode_with_html() -> None:
|
||||||
completer = AutoCompleter()
|
completer = AutoCompleter()
|
||||||
|
|
||||||
with patch.object(sys.stdin, "isatty", return_value=False):
|
with patch.object(sys.stdin, 'isatty', return_value=False):
|
||||||
completer.initial_setup({"start", "stop"})
|
completer.initial_setup({"start", "stop"})
|
||||||
|
|
||||||
assert completer._fallback_mode is True
|
assert completer._fallback_mode is True
|
||||||
|
|
||||||
with patch("builtins.input", return_value="test input"):
|
with patch('builtins.input', return_value='test input'):
|
||||||
result = completer.prompt(HTML("<b>>>> </b>"))
|
result = completer.prompt(HTML("<b>>>> </b>"))
|
||||||
|
|
||||||
assert result == "test input"
|
assert result == 'test input'
|
||||||
|
|
||||||
|
|
||||||
def test_autocompleter_prompt_with_html_in_normal_mode() -> None:
|
def test_autocompleter_prompt_with_html_in_normal_mode() -> None:
|
||||||
completer = AutoCompleter()
|
completer = AutoCompleter()
|
||||||
|
|
||||||
mock_session = MagicMock()
|
mock_session = MagicMock()
|
||||||
mock_session.prompt.return_value = "test result"
|
mock_session.prompt.return_value = 'test result'
|
||||||
completer._session = mock_session
|
completer._session = mock_session
|
||||||
completer._fallback_mode = False
|
completer._fallback_mode = False
|
||||||
|
|
||||||
html_prompt = HTML("<b>>>> </b>")
|
html_prompt = HTML("<b>>>> </b>")
|
||||||
result = completer.prompt(html_prompt)
|
result = completer.prompt(html_prompt)
|
||||||
|
|
||||||
assert result == "test result"
|
assert result == 'test result'
|
||||||
mock_session.prompt.assert_called_once()
|
mock_session.prompt.assert_called_once()
|
||||||
call_args = mock_session.prompt.call_args
|
call_args = mock_session.prompt.call_args
|
||||||
assert call_args[0][0] == html_prompt
|
assert call_args[0][0] == html_prompt
|
||||||
@@ -426,13 +466,13 @@ def test_autocompleter_prompt_with_string_in_normal_mode() -> None:
|
|||||||
completer = AutoCompleter()
|
completer = AutoCompleter()
|
||||||
|
|
||||||
mock_session = MagicMock()
|
mock_session = MagicMock()
|
||||||
mock_session.prompt.return_value = "test result"
|
mock_session.prompt.return_value = 'test result'
|
||||||
completer._session = mock_session
|
completer._session = mock_session
|
||||||
completer._fallback_mode = False
|
completer._fallback_mode = False
|
||||||
|
|
||||||
result = completer.prompt(">>> ")
|
result = completer.prompt(">>> ")
|
||||||
|
|
||||||
assert result == "test result"
|
assert result == 'test result'
|
||||||
mock_session.prompt.assert_called_once()
|
mock_session.prompt.assert_called_once()
|
||||||
call_args = mock_session.prompt.call_args
|
call_args = mock_session.prompt.call_args
|
||||||
assert isinstance(call_args[0][0], HTML)
|
assert isinstance(call_args[0][0], HTML)
|
||||||
|
|||||||
@@ -45,11 +45,12 @@ def sample_router() -> Router:
|
|||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
|
|
||||||
def test_orchestrator_initializes_with_no_argparser(mocker: MockerFixture) -> None:
|
def test_orchestrator_initializes_with_default_argparser(mocker: MockerFixture) -> None:
|
||||||
"""Test Orchestrator initialization with no ArgParser"""
|
"""Test Orchestrator initialization with default ArgParser"""
|
||||||
mocker.patch('sys.argv', ['test_program'])
|
mocker.patch('sys.argv', ['test_program'])
|
||||||
orchestrator = Orchestrator()
|
orchestrator = Orchestrator()
|
||||||
assert orchestrator._arg_parser is None
|
assert orchestrator._arg_parser is not None
|
||||||
|
assert isinstance(orchestrator._arg_parser, ArgParser)
|
||||||
|
|
||||||
|
|
||||||
def test_orchestrator_initializes_with_custom_argparser(mock_argparser: ArgParser) -> None:
|
def test_orchestrator_initializes_with_custom_argparser(mock_argparser: ArgParser) -> None:
|
||||||
@@ -88,80 +89,80 @@ def test_orchestrator_parses_args_on_initialization(mocker: MockerFixture, mock_
|
|||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Tests for run_repl method
|
# Tests for start_polling method
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
|
|
||||||
def test_run_repl_creates_dishka_container(
|
def test_start_polling_creates_dishka_container(
|
||||||
mocker: MockerFixture, mock_argparser: ArgParser, sample_app: App
|
mocker: MockerFixture, mock_argparser: ArgParser, sample_app: App
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test that run_repl creates a dishka container"""
|
"""Test that start_polling creates a dishka container"""
|
||||||
mock_make_container = mocker.patch('argenta.orchestrator.entity.make_container')
|
mock_make_container = mocker.patch('argenta.orchestrator.entity.make_container')
|
||||||
_mock_setup_dishka = mocker.patch('argenta.orchestrator.entity.setup_dishka')
|
_mock_setup_dishka = mocker.patch('argenta.orchestrator.entity.setup_dishka')
|
||||||
mocker.patch.object(sample_app, '_run_repl')
|
mocker.patch.object(sample_app, '_run_polling')
|
||||||
|
|
||||||
orchestrator = Orchestrator(arg_parser=mock_argparser)
|
orchestrator = Orchestrator(arg_parser=mock_argparser)
|
||||||
orchestrator.run_repl(sample_app)
|
orchestrator.start_polling(sample_app)
|
||||||
|
|
||||||
mock_make_container.assert_called_once()
|
mock_make_container.assert_called_once()
|
||||||
assert mock_make_container.call_args[1]['context'] == {ArgParser: mock_argparser}
|
assert mock_make_container.call_args[1]['context'] == {ArgParser: mock_argparser}
|
||||||
|
|
||||||
|
|
||||||
def test_run_repl_calls_setup_dishka_with_auto_inject_enabled(
|
def test_start_polling_calls_setup_dishka_with_auto_inject_enabled(
|
||||||
mocker: MockerFixture, mock_argparser: ArgParser, sample_app: App
|
mocker: MockerFixture, mock_argparser: ArgParser, sample_app: App
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test that run_repl calls setup_dishka with auto_inject=True"""
|
"""Test that start_polling calls setup_dishka with auto_inject=True"""
|
||||||
mock_container = mocker.MagicMock() # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
|
mock_container = mocker.MagicMock() # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
|
||||||
mocker.patch('argenta.orchestrator.entity.make_container', return_value=mock_container)
|
mocker.patch('argenta.orchestrator.entity.make_container', return_value=mock_container)
|
||||||
mock_setup_dishka = mocker.patch('argenta.orchestrator.entity.setup_dishka')
|
mock_setup_dishka = mocker.patch('argenta.orchestrator.entity.setup_dishka')
|
||||||
mocker.patch.object(sample_app, '_run_repl')
|
mocker.patch.object(sample_app, '_run_polling')
|
||||||
|
|
||||||
orchestrator = Orchestrator(arg_parser=mock_argparser, auto_inject_handlers=True)
|
orchestrator = Orchestrator(arg_parser=mock_argparser, auto_inject_handlers=True)
|
||||||
orchestrator.run_repl(sample_app)
|
orchestrator.start_polling(sample_app)
|
||||||
|
|
||||||
mock_setup_dishka.assert_called_once_with(sample_app, mock_container, auto_inject=True)
|
mock_setup_dishka.assert_called_once_with(sample_app, mock_container, auto_inject=True)
|
||||||
|
|
||||||
|
|
||||||
def test_run_repl_calls_setup_dishka_with_auto_inject_disabled(
|
def test_start_polling_calls_setup_dishka_with_auto_inject_disabled(
|
||||||
mocker: MockerFixture, mock_argparser: ArgParser, sample_app: App
|
mocker: MockerFixture, mock_argparser: ArgParser, sample_app: App
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test that run_repl calls setup_dishka with auto_inject=False"""
|
"""Test that start_polling calls setup_dishka with auto_inject=False"""
|
||||||
mock_container = mocker.MagicMock() # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
|
mock_container = mocker.MagicMock() # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
|
||||||
mocker.patch('argenta.orchestrator.entity.make_container', return_value=mock_container)
|
mocker.patch('argenta.orchestrator.entity.make_container', return_value=mock_container)
|
||||||
mock_setup_dishka = mocker.patch('argenta.orchestrator.entity.setup_dishka')
|
mock_setup_dishka = mocker.patch('argenta.orchestrator.entity.setup_dishka')
|
||||||
mocker.patch.object(sample_app, '_run_repl')
|
mocker.patch.object(sample_app, '_run_polling')
|
||||||
|
|
||||||
orchestrator = Orchestrator(arg_parser=mock_argparser, auto_inject_handlers=False)
|
orchestrator = Orchestrator(arg_parser=mock_argparser, auto_inject_handlers=False)
|
||||||
orchestrator.run_repl(sample_app)
|
orchestrator.start_polling(sample_app)
|
||||||
|
|
||||||
mock_setup_dishka.assert_called_once_with(sample_app, mock_container, auto_inject=False)
|
mock_setup_dishka.assert_called_once_with(sample_app, mock_container, auto_inject=False)
|
||||||
|
|
||||||
|
|
||||||
def test_run_repl_calls_app_run_repl(
|
def test_start_polling_calls_app_run_polling(
|
||||||
mocker: MockerFixture, mock_argparser: ArgParser, sample_app: App
|
mocker: MockerFixture, mock_argparser: ArgParser, sample_app: App
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test that run_repl calls app.run_polling()"""
|
"""Test that start_polling calls app.run_polling()"""
|
||||||
mocker.patch('argenta.orchestrator.entity.make_container')
|
mocker.patch('argenta.orchestrator.entity.make_container')
|
||||||
mocker.patch('argenta.orchestrator.entity.setup_dishka')
|
mocker.patch('argenta.orchestrator.entity.setup_dishka')
|
||||||
mock_run_repl = mocker.patch.object(sample_app, '_run_repl')
|
mock_run_polling = mocker.patch.object(sample_app, '_run_polling')
|
||||||
|
|
||||||
orchestrator = Orchestrator(arg_parser=mock_argparser)
|
orchestrator = Orchestrator(arg_parser=mock_argparser)
|
||||||
orchestrator.run_repl(sample_app)
|
orchestrator.start_polling(sample_app)
|
||||||
|
|
||||||
mock_run_repl.assert_called_once()
|
mock_run_polling.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
def test_run_repl_includes_custom_providers_in_container(
|
def test_start_polling_includes_custom_providers_in_container(
|
||||||
mocker: MockerFixture, mock_argparser: ArgParser, sample_app: App
|
mocker: MockerFixture, mock_argparser: ArgParser, sample_app: App
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test that run_repl includes custom providers in container"""
|
"""Test that start_polling includes custom providers in container"""
|
||||||
custom_provider = Provider()
|
custom_provider = Provider()
|
||||||
mock_make_container = mocker.patch('argenta.orchestrator.entity.make_container')
|
mock_make_container = mocker.patch('argenta.orchestrator.entity.make_container')
|
||||||
mocker.patch('argenta.orchestrator.entity.setup_dishka')
|
mocker.patch('argenta.orchestrator.entity.setup_dishka')
|
||||||
mocker.patch.object(sample_app, '_run_repl')
|
mocker.patch.object(sample_app, '_run_polling')
|
||||||
|
|
||||||
orchestrator = Orchestrator(arg_parser=mock_argparser, custom_providers=[custom_provider])
|
orchestrator = Orchestrator(arg_parser=mock_argparser, custom_providers=[custom_provider])
|
||||||
orchestrator.run_repl(sample_app)
|
orchestrator.start_polling(sample_app)
|
||||||
|
|
||||||
# Check that custom_provider was passed to make_container
|
# Check that custom_provider was passed to make_container
|
||||||
call_args = mock_make_container.call_args[0]
|
call_args = mock_make_container.call_args[0]
|
||||||
@@ -179,14 +180,14 @@ def test_orchestrator_integrates_with_app_with_router(
|
|||||||
"""Test that Orchestrator properly integrates with App that has routers"""
|
"""Test that Orchestrator properly integrates with App that has routers"""
|
||||||
mocker.patch('argenta.orchestrator.entity.make_container')
|
mocker.patch('argenta.orchestrator.entity.make_container')
|
||||||
mocker.patch('argenta.orchestrator.entity.setup_dishka')
|
mocker.patch('argenta.orchestrator.entity.setup_dishka')
|
||||||
mock_run_repl = mocker.patch.object(sample_app, '_run_repl')
|
mock_run_polling = mocker.patch.object(sample_app, '_run_polling')
|
||||||
|
|
||||||
sample_app.include_router(sample_router)
|
sample_app.include_router(sample_router)
|
||||||
|
|
||||||
orchestrator = Orchestrator(arg_parser=mock_argparser)
|
orchestrator = Orchestrator(arg_parser=mock_argparser)
|
||||||
orchestrator.run_repl(sample_app)
|
orchestrator.start_polling(sample_app)
|
||||||
|
|
||||||
mock_run_repl.assert_called_once()
|
mock_run_polling.assert_called_once()
|
||||||
assert len(sample_app.registered_routers.registered_routers) == 1
|
assert len(sample_app.registered_routers.registered_routers) == 1
|
||||||
|
|
||||||
|
|
||||||
@@ -201,10 +202,10 @@ def test_orchestrator_passes_argparser_to_container_context(
|
|||||||
"""Test that Orchestrator passes ArgParser instance to container context"""
|
"""Test that Orchestrator passes ArgParser instance to container context"""
|
||||||
mock_make_container = mocker.patch('argenta.orchestrator.entity.make_container')
|
mock_make_container = mocker.patch('argenta.orchestrator.entity.make_container')
|
||||||
mocker.patch('argenta.orchestrator.entity.setup_dishka')
|
mocker.patch('argenta.orchestrator.entity.setup_dishka')
|
||||||
mocker.patch.object(sample_app, '_run_repl')
|
mocker.patch.object(sample_app, '_run_polling')
|
||||||
|
|
||||||
orchestrator = Orchestrator(arg_parser=mock_argparser)
|
orchestrator = Orchestrator(arg_parser=mock_argparser)
|
||||||
orchestrator.run_repl(sample_app)
|
orchestrator.start_polling(sample_app)
|
||||||
|
|
||||||
# Verify that ArgParser was passed in context
|
# Verify that ArgParser was passed in context
|
||||||
call_kwargs = mock_make_container.call_args[1]
|
call_kwargs = mock_make_container.call_args[1]
|
||||||
@@ -218,18 +219,18 @@ def test_orchestrator_passes_argparser_to_container_context(
|
|||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
|
|
||||||
def test_orchestrator_handles_app_run_repl_exception(
|
def test_orchestrator_handles_app_run_polling_exception(
|
||||||
mocker: MockerFixture, mock_argparser: ArgParser, sample_app: App
|
mocker: MockerFixture, mock_argparser: ArgParser, sample_app: App
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test that Orchestrator propagates exceptions from app.run_polling()"""
|
"""Test that Orchestrator propagates exceptions from app.run_polling()"""
|
||||||
mocker.patch('argenta.orchestrator.entity.make_container')
|
mocker.patch('argenta.orchestrator.entity.make_container')
|
||||||
mocker.patch('argenta.orchestrator.entity.setup_dishka')
|
mocker.patch('argenta.orchestrator.entity.setup_dishka')
|
||||||
mocker.patch.object(sample_app, '_run_repl', side_effect=RuntimeError("Test error"))
|
mocker.patch.object(sample_app, '_run_polling', side_effect=RuntimeError("Test error"))
|
||||||
|
|
||||||
orchestrator = Orchestrator(arg_parser=mock_argparser)
|
orchestrator = Orchestrator(arg_parser=mock_argparser)
|
||||||
|
|
||||||
with pytest.raises(RuntimeError, match="Test error"):
|
with pytest.raises(RuntimeError, match="Test error"):
|
||||||
orchestrator.run_repl(sample_app)
|
orchestrator.start_polling(sample_app)
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@@ -245,13 +246,13 @@ def test_orchestrator_accepts_multiple_custom_providers(
|
|||||||
provider2 = Provider()
|
provider2 = Provider()
|
||||||
mock_make_container = mocker.patch('argenta.orchestrator.entity.make_container')
|
mock_make_container = mocker.patch('argenta.orchestrator.entity.make_container')
|
||||||
mocker.patch('argenta.orchestrator.entity.setup_dishka')
|
mocker.patch('argenta.orchestrator.entity.setup_dishka')
|
||||||
mocker.patch.object(sample_app, '_run_repl')
|
mocker.patch.object(sample_app, '_run_polling')
|
||||||
|
|
||||||
orchestrator = Orchestrator(
|
orchestrator = Orchestrator(
|
||||||
arg_parser=mock_argparser,
|
arg_parser=mock_argparser,
|
||||||
custom_providers=[provider1, provider2]
|
custom_providers=[provider1, provider2]
|
||||||
)
|
)
|
||||||
orchestrator.run_repl(sample_app)
|
orchestrator.start_polling(sample_app)
|
||||||
|
|
||||||
call_args = mock_make_container.call_args[0]
|
call_args = mock_make_container.call_args[0]
|
||||||
assert provider1 in call_args
|
assert provider1 in call_args
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
version = 1
|
version = 1
|
||||||
revision = 3
|
revision = 3
|
||||||
requires-python = ">=3.12, <3.15"
|
requires-python = ">=3.12"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aiosqlite"
|
name = "aiosqlite"
|
||||||
@@ -48,23 +48,12 @@ dependencies = [
|
|||||||
{ name = "rich" },
|
{ name = "rich" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.optional-dependencies]
|
|
||||||
cli = [
|
|
||||||
{ name = "nuitka", extra = ["onefile"] },
|
|
||||||
{ name = "typer" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.dev-dependencies]
|
[package.dev-dependencies]
|
||||||
dev = [
|
dev = [
|
||||||
{ name = "cairosvg" },
|
|
||||||
{ name = "esbonio" },
|
{ name = "esbonio" },
|
||||||
{ name = "isort" },
|
{ name = "isort" },
|
||||||
{ name = "matplotlib" },
|
|
||||||
{ name = "mypy" },
|
{ name = "mypy" },
|
||||||
{ name = "psutil" },
|
|
||||||
{ name = "py-cpuinfo" },
|
|
||||||
{ name = "pyfakefs" },
|
{ name = "pyfakefs" },
|
||||||
{ name = "pygal" },
|
|
||||||
{ name = "pytest" },
|
{ name = "pytest" },
|
||||||
{ name = "pytest-cov" },
|
{ name = "pytest-cov" },
|
||||||
{ name = "pytest-mock" },
|
{ name = "pytest-mock" },
|
||||||
@@ -89,11 +78,9 @@ linters = [
|
|||||||
{ name = "wemake-python-styleguide" },
|
{ name = "wemake-python-styleguide" },
|
||||||
]
|
]
|
||||||
metrics = [
|
metrics = [
|
||||||
{ name = "cairosvg" },
|
|
||||||
{ name = "matplotlib" },
|
{ name = "matplotlib" },
|
||||||
{ name = "psutil" },
|
{ name = "psutil" },
|
||||||
{ name = "py-cpuinfo" },
|
{ name = "py-cpuinfo" },
|
||||||
{ name = "pygal" },
|
|
||||||
]
|
]
|
||||||
tests = [
|
tests = [
|
||||||
{ name = "pyfakefs" },
|
{ name = "pyfakefs" },
|
||||||
@@ -109,24 +96,16 @@ typecheckers = [
|
|||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "art", specifier = ">=6.4,<7.0" },
|
{ name = "art", specifier = ">=6.4,<7.0" },
|
||||||
{ name = "dishka", specifier = ">=1.7.2" },
|
{ name = "dishka", specifier = ">=1.7.2" },
|
||||||
{ name = "nuitka", extras = ["onefile"], marker = "extra == 'cli'", specifier = ">=4.0.5" },
|
|
||||||
{ name = "prompt-toolkit", specifier = ">=3.0.52" },
|
{ name = "prompt-toolkit", specifier = ">=3.0.52" },
|
||||||
{ name = "rich", specifier = ">=14.0.0,<15.0.0" },
|
{ name = "rich", specifier = ">=14.0.0,<15.0.0" },
|
||||||
{ name = "typer", marker = "extra == 'cli'", specifier = ">=0.9,!=0.12,<=0.21.1" },
|
|
||||||
]
|
]
|
||||||
provides-extras = ["cli"]
|
|
||||||
|
|
||||||
[package.metadata.requires-dev]
|
[package.metadata.requires-dev]
|
||||||
dev = [
|
dev = [
|
||||||
{ name = "cairosvg", specifier = ">=2.8.2" },
|
|
||||||
{ name = "esbonio", specifier = ">=1.0.0" },
|
{ name = "esbonio", specifier = ">=1.0.0" },
|
||||||
{ name = "isort", specifier = ">=7.0.0" },
|
{ name = "isort", specifier = ">=7.0.0" },
|
||||||
{ name = "matplotlib", specifier = ">=3.10.8" },
|
|
||||||
{ name = "mypy", specifier = ">=1.14.1" },
|
{ name = "mypy", specifier = ">=1.14.1" },
|
||||||
{ name = "psutil", specifier = ">=7.2.1" },
|
|
||||||
{ name = "py-cpuinfo", specifier = ">=9.0.0" },
|
|
||||||
{ name = "pyfakefs", specifier = ">=5.5.0" },
|
{ name = "pyfakefs", specifier = ">=5.5.0" },
|
||||||
{ name = "pygal", specifier = ">=3.1.0" },
|
|
||||||
{ name = "pytest", specifier = ">=8.3.2" },
|
{ name = "pytest", specifier = ">=8.3.2" },
|
||||||
{ name = "pytest-cov", specifier = ">=7.0.0" },
|
{ name = "pytest-cov", specifier = ">=7.0.0" },
|
||||||
{ name = "pytest-mock", specifier = ">=3.15.1" },
|
{ name = "pytest-mock", specifier = ">=3.15.1" },
|
||||||
@@ -151,11 +130,9 @@ linters = [
|
|||||||
{ name = "wemake-python-styleguide", specifier = ">=0.17.0" },
|
{ name = "wemake-python-styleguide", specifier = ">=0.17.0" },
|
||||||
]
|
]
|
||||||
metrics = [
|
metrics = [
|
||||||
{ name = "cairosvg", specifier = ">=2.8.2" },
|
|
||||||
{ name = "matplotlib", specifier = ">=3.10.8" },
|
{ name = "matplotlib", specifier = ">=3.10.8" },
|
||||||
{ name = "psutil", specifier = ">=7.2.1" },
|
{ name = "psutil", specifier = ">=7.2.1" },
|
||||||
{ name = "py-cpuinfo", specifier = ">=9.0.0" },
|
{ name = "py-cpuinfo", specifier = ">=9.0.0" },
|
||||||
{ name = "pygal", specifier = ">=3.1.0" },
|
|
||||||
]
|
]
|
||||||
tests = [
|
tests = [
|
||||||
{ name = "pyfakefs", specifier = ">=5.5.0" },
|
{ name = "pyfakefs", specifier = ">=5.5.0" },
|
||||||
@@ -192,34 +169,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" },
|
{ url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cairocffi"
|
|
||||||
version = "1.7.1"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "cffi" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/70/c5/1a4dc131459e68a173cbdab5fad6b524f53f9c1ef7861b7698e998b837cc/cairocffi-1.7.1.tar.gz", hash = "sha256:2e48ee864884ec4a3a34bfa8c9ab9999f688286eb714a15a43ec9d068c36557b", size = 88096, upload-time = "2024-06-18T10:56:06.741Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/93/d8/ba13451aa6b745c49536e87b6bf8f629b950e84bd0e8308f7dc6883b67e2/cairocffi-1.7.1-py3-none-any.whl", hash = "sha256:9803a0e11f6c962f3b0ae2ec8ba6ae45e957a146a004697a1ac1bbf16b073b3f", size = 75611, upload-time = "2024-06-18T10:55:59.489Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cairosvg"
|
|
||||||
version = "2.8.2"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "cairocffi" },
|
|
||||||
{ name = "cssselect2" },
|
|
||||||
{ name = "defusedxml" },
|
|
||||||
{ name = "pillow" },
|
|
||||||
{ name = "tinycss2" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/ab/b9/5106168bd43d7cd8b7cc2a2ee465b385f14b63f4c092bb89eee2d48c8e67/cairosvg-2.8.2.tar.gz", hash = "sha256:07cbf4e86317b27a92318a4cac2a4bb37a5e9c1b8a27355d06874b22f85bef9f", size = 8398590, upload-time = "2025-05-15T06:56:32.653Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/67/48/816bd4aaae93dbf9e408c58598bc32f4a8c65f4b86ab560864cb3ee60adb/cairosvg-2.8.2-py3-none-any.whl", hash = "sha256:eab46dad4674f33267a671dce39b64be245911c901c70d65d2b7b0821e852bf5", size = 45773, upload-time = "2025-05-15T06:56:28.552Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cattrs"
|
name = "cattrs"
|
||||||
version = "25.2.0"
|
version = "25.2.0"
|
||||||
@@ -242,63 +191,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de", size = 163286, upload-time = "2025-10-05T04:12:14.03Z" },
|
{ url = "https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de", size = 163286, upload-time = "2025-10-05T04:12:14.03Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cffi"
|
|
||||||
version = "2.0.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "pycparser", marker = "implementation_name != 'PyPy'" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "charset-normalizer"
|
name = "charset-normalizer"
|
||||||
version = "3.4.4"
|
version = "3.4.4"
|
||||||
@@ -529,19 +421,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/5f/04/642c1d8a448ae5ea1369eac8495740a79eb4e581a9fb0cbdce56bbf56da1/coverage-7.11.0-py3-none-any.whl", hash = "sha256:4b7589765348d78fb4e5fb6ea35d07564e387da2fc5efff62e0222971f155f68", size = 207761, upload-time = "2025-10-15T15:15:06.439Z" },
|
{ url = "https://files.pythonhosted.org/packages/5f/04/642c1d8a448ae5ea1369eac8495740a79eb4e581a9fb0cbdce56bbf56da1/coverage-7.11.0-py3-none-any.whl", hash = "sha256:4b7589765348d78fb4e5fb6ea35d07564e387da2fc5efff62e0222971f155f68", size = 207761, upload-time = "2025-10-15T15:15:06.439Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cssselect2"
|
|
||||||
version = "0.9.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "tinycss2" },
|
|
||||||
{ name = "webencodings" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/e0/20/92eaa6b0aec7189fa4b75c890640e076e9e793095721db69c5c81142c2e1/cssselect2-0.9.0.tar.gz", hash = "sha256:759aa22c216326356f65e62e791d66160a0f9c91d1424e8d8adc5e74dddfc6fb", size = 35595, upload-time = "2026-02-12T17:16:39.614Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.0-py3-none-any.whl", hash = "sha256:6a99e5f91f9a016a304dd929b0966ca464bcfda15177b6fb4a118fc0fb5d9563", size = 15453, upload-time = "2026-02-12T17:16:38.317Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cycler"
|
name = "cycler"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
@@ -551,15 +430,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" },
|
{ url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "defusedxml"
|
|
||||||
version = "0.7.1"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dishka"
|
name = "dishka"
|
||||||
version = "1.7.2"
|
version = "1.7.2"
|
||||||
@@ -676,18 +546,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769, upload-time = "2022-07-01T12:21:02.467Z" },
|
{ url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769, upload-time = "2022-07-01T12:21:02.467Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "importlib-metadata"
|
|
||||||
version = "8.7.1"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "zipp" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iniconfig"
|
name = "iniconfig"
|
||||||
version = "2.1.0"
|
version = "2.1.0"
|
||||||
@@ -991,17 +849,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
|
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nuitka"
|
|
||||||
version = "4.0.5"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/f8/40/f73e578922084f9e465b30abdb9963aadcc087b5a9399033472d9ef641ab/nuitka-4.0.5.tar.gz", hash = "sha256:45e7d90266e76fe64eeb8d196c17666d7cd7cffbf68d6a24f233c3c03b6feaa8", size = 4420878, upload-time = "2026-03-12T10:03:39.562Z" }
|
|
||||||
|
|
||||||
[package.optional-dependencies]
|
|
||||||
onefile = [
|
|
||||||
{ name = "zstandard" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "numpy"
|
name = "numpy"
|
||||||
version = "2.4.1"
|
version = "2.4.1"
|
||||||
@@ -1083,71 +930,71 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pillow"
|
name = "pillow"
|
||||||
version = "12.1.1"
|
version = "12.2.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/1f/42/5c74462b4fd957fcd7b13b04fb3205ff8349236ea74c7c375766d6c82288/pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4", size = 46980264, upload-time = "2026-02-11T04:23:07.146Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/8c/21/c2bcdd5906101a30244eaffc1b6e6ce71a31bd0742a01eb89e660ebfac2d/pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5", size = 46987819, upload-time = "2026-04-01T14:46:17.687Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/07/d3/8df65da0d4df36b094351dce696f2989bec731d4f10e743b1c5f4da4d3bf/pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052", size = 5262803, upload-time = "2026-02-11T04:20:47.653Z" },
|
{ url = "https://files.pythonhosted.org/packages/58/be/7482c8a5ebebbc6470b3eb791812fff7d5e0216c2be3827b30b8bb6603ed/pillow-12.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d192a155bbcec180f8564f693e6fd9bccff5a7af9b32e2e4bf8c9c69dbad6b5", size = 5308279, upload-time = "2026-04-01T14:43:13.246Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/d6/71/5026395b290ff404b836e636f51d7297e6c83beceaa87c592718747e670f/pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984", size = 4657601, upload-time = "2026-02-11T04:20:49.328Z" },
|
{ url = "https://files.pythonhosted.org/packages/d8/95/0a351b9289c2b5cbde0bacd4a83ebc44023e835490a727b2a3bd60ddc0f4/pillow-12.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3f40b3c5a968281fd507d519e444c35f0ff171237f4fdde090dd60699458421", size = 4695490, upload-time = "2026-04-01T14:43:15.584Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/b1/2e/1001613d941c67442f745aff0f7cc66dd8df9a9c084eb497e6a543ee6f7e/pillow-12.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79", size = 6234995, upload-time = "2026-02-11T04:20:51.032Z" },
|
{ url = "https://files.pythonhosted.org/packages/de/af/4e8e6869cbed569d43c416fad3dc4ecb944cb5d9492defaed89ddd6fe871/pillow-12.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:03e7e372d5240cc23e9f07deca4d775c0817bffc641b01e9c3af208dbd300987", size = 6284462, upload-time = "2026-04-01T14:43:18.268Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/07/26/246ab11455b2549b9233dbd44d358d033a2f780fa9007b61a913c5b2d24e/pillow-12.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293", size = 8045012, upload-time = "2026-02-11T04:20:52.882Z" },
|
{ url = "https://files.pythonhosted.org/packages/e9/9e/c05e19657fd57841e476be1ab46c4d501bffbadbafdc31a6d665f8b737b6/pillow-12.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b86024e52a1b269467a802258c25521e6d742349d760728092e1bc2d135b4d76", size = 8094744, upload-time = "2026-04-01T14:43:20.716Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/b2/8b/07587069c27be7535ac1fe33874e32de118fbd34e2a73b7f83436a88368c/pillow-12.1.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397", size = 6349638, upload-time = "2026-02-11T04:20:54.444Z" },
|
{ url = "https://files.pythonhosted.org/packages/2b/54/1789c455ed10176066b6e7e6da1b01e50e36f94ba584dc68d9eebfe9156d/pillow-12.2.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7371b48c4fa448d20d2714c9a1f775a81155050d383333e0a6c15b1123dda005", size = 6398371, upload-time = "2026-04-01T14:43:23.443Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ff/79/6df7b2ee763d619cda2fb4fea498e5f79d984dae304d45a8999b80d6cf5c/pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0", size = 7041540, upload-time = "2026-02-11T04:20:55.97Z" },
|
{ url = "https://files.pythonhosted.org/packages/43/e3/fdc657359e919462369869f1c9f0e973f353f9a9ee295a39b1fea8ee1a77/pillow-12.2.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62f5409336adb0663b7caa0da5c7d9e7bdbaae9ce761d34669420c2a801b2780", size = 7087215, upload-time = "2026-04-01T14:43:26.758Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/2c/5e/2ba19e7e7236d7529f4d873bdaf317a318896bac289abebd4bb00ef247f0/pillow-12.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3", size = 6462613, upload-time = "2026-02-11T04:20:57.542Z" },
|
{ url = "https://files.pythonhosted.org/packages/8b/f8/2f6825e441d5b1959d2ca5adec984210f1ec086435b0ed5f52c19b3b8a6e/pillow-12.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:01afa7cf67f74f09523699b4e88c73fb55c13346d212a59a2db1f86b0a63e8c5", size = 6509783, upload-time = "2026-04-01T14:43:29.56Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/03/03/31216ec124bb5c3dacd74ce8efff4cc7f52643653bad4825f8f08c697743/pillow-12.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35", size = 7166745, upload-time = "2026-02-11T04:20:59.196Z" },
|
{ url = "https://files.pythonhosted.org/packages/67/f9/029a27095ad20f854f9dba026b3ea6428548316e057e6fc3545409e86651/pillow-12.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc3d34d4a8fbec3e88a79b92e5465e0f9b842b628675850d860b8bd300b159f5", size = 7212112, upload-time = "2026-04-01T14:43:32.091Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/1f/e7/7c4552d80052337eb28653b617eafdef39adfb137c49dd7e831b8dc13bc5/pillow-12.1.1-cp312-cp312-win32.whl", hash = "sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a", size = 6328823, upload-time = "2026-02-11T04:21:01.385Z" },
|
{ url = "https://files.pythonhosted.org/packages/be/42/025cfe05d1be22dbfdb4f264fe9de1ccda83f66e4fc3aac94748e784af04/pillow-12.2.0-cp312-cp312-win32.whl", hash = "sha256:58f62cc0f00fd29e64b29f4fd923ffdb3859c9f9e6105bfc37ba1d08994e8940", size = 6378489, upload-time = "2026-04-01T14:43:34.601Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/3d/17/688626d192d7261bbbf98846fc98995726bddc2c945344b65bec3a29d731/pillow-12.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6", size = 7033367, upload-time = "2026-02-11T04:21:03.536Z" },
|
{ url = "https://files.pythonhosted.org/packages/5d/7b/25a221d2c761c6a8ae21bfa3874988ff2583e19cf8a27bf2fee358df7942/pillow-12.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f84204dee22a783350679a0333981df803dac21a0190d706a50475e361c93f5", size = 7084129, upload-time = "2026-04-01T14:43:37.213Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ed/fe/a0ef1f73f939b0eca03ee2c108d0043a87468664770612602c63266a43c4/pillow-12.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523", size = 2453811, upload-time = "2026-02-11T04:21:05.116Z" },
|
{ url = "https://files.pythonhosted.org/packages/10/e1/542a474affab20fd4a0f1836cb234e8493519da6b76899e30bcc5d990b8b/pillow-12.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:af73337013e0b3b46f175e79492d96845b16126ddf79c438d7ea7ff27783a414", size = 2463612, upload-time = "2026-04-01T14:43:39.421Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/d5/11/6db24d4bd7685583caeae54b7009584e38da3c3d4488ed4cd25b439de486/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d242e8ac078781f1de88bf823d70c1a9b3c7950a44cdf4b7c012e22ccbcd8e4e", size = 4062689, upload-time = "2026-02-11T04:21:06.804Z" },
|
{ url = "https://files.pythonhosted.org/packages/4a/01/53d10cf0dbad820a8db274d259a37ba50b88b24768ddccec07355382d5ad/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8297651f5b5679c19968abefd6bb84d95fe30ef712eb1b2d9b2d31ca61267f4c", size = 4100837, upload-time = "2026-04-01T14:43:41.506Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/33/c0/ce6d3b1fe190f0021203e0d9b5b99e57843e345f15f9ef22fcd43842fd21/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9", size = 4138535, upload-time = "2026-02-11T04:21:08.452Z" },
|
{ url = "https://files.pythonhosted.org/packages/0f/98/f3a6657ecb698c937f6c76ee564882945f29b79bad496abcba0e84659ec5/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:50d8520da2a6ce0af445fa6d648c4273c3eeefbc32d7ce049f22e8b5c3daecc2", size = 4176528, upload-time = "2026-04-01T14:43:43.773Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a0/c6/d5eb6a4fb32a3f9c21a8c7613ec706534ea1cf9f4b3663e99f0d83f6fca8/pillow-12.1.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e65498daf4b583091ccbb2556c7000abf0f3349fcd57ef7adc9a84a394ed29f6", size = 3601364, upload-time = "2026-02-11T04:21:10.194Z" },
|
{ url = "https://files.pythonhosted.org/packages/69/bc/8986948f05e3ea490b8442ea1c1d4d990b24a7e43d8a51b2c7d8b1dced36/pillow-12.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:766cef22385fa1091258ad7e6216792b156dc16d8d3fa607e7545b2b72061f1c", size = 3640401, upload-time = "2026-04-01T14:43:45.87Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/14/a1/16c4b823838ba4c9c52c0e6bbda903a3fe5a1bdbf1b8eb4fff7156f3e318/pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c6db3b84c87d48d0088943bf33440e0c42370b99b1c2a7989216f7b42eede60", size = 5262561, upload-time = "2026-02-11T04:21:11.742Z" },
|
{ url = "https://files.pythonhosted.org/packages/34/46/6c717baadcd62bc8ed51d238d521ab651eaa74838291bda1f86fe1f864c9/pillow-12.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5d2fd0fa6b5d9d1de415060363433f28da8b1526c1c129020435e186794b3795", size = 5308094, upload-time = "2026-04-01T14:43:48.438Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/bb/ad/ad9dc98ff24f485008aa5cdedaf1a219876f6f6c42a4626c08bc4e80b120/pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b7e5304e34942bf62e15184219a7b5ad4ff7f3bb5cca4d984f37df1a0e1aee2", size = 4657460, upload-time = "2026-02-11T04:21:13.786Z" },
|
{ url = "https://files.pythonhosted.org/packages/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b25336f502b6ed02e889f4ece894a72612fe885889a6e8c4c80239ff6e5f5f", size = 4695402, upload-time = "2026-04-01T14:43:51.292Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/9e/1b/f1a4ea9a895b5732152789326202a82464d5254759fbacae4deea3069334/pillow-12.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:18e5bddd742a44b7e6b1e773ab5db102bd7a94c32555ba656e76d319d19c3850", size = 6232698, upload-time = "2026-02-11T04:21:15.949Z" },
|
{ url = "https://files.pythonhosted.org/packages/73/dd/42107efcb777b16fa0393317eac58f5b5cf30e8392e266e76e51cff28c3d/pillow-12.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f1c943e96e85df3d3478f7b691f229887e143f81fedab9b20205349ab04d73ed", size = 6280005, upload-time = "2026-04-01T14:43:54.242Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/95/f4/86f51b8745070daf21fd2e5b1fe0eb35d4db9ca26e6d58366562fb56a743/pillow-12.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc44ef1f3de4f45b50ccf9136999d71abb99dca7706bc75d222ed350b9fd2289", size = 8041706, upload-time = "2026-02-11T04:21:17.723Z" },
|
{ url = "https://files.pythonhosted.org/packages/a8/68/b93e09e5e8549019e61acf49f65b1a8530765a7f812c77a7461bca7e4494/pillow-12.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03f6fab9219220f041c74aeaa2939ff0062bd5c364ba9ce037197f4c6d498cd9", size = 8090669, upload-time = "2026-04-01T14:43:57.335Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/29/9b/d6ecd956bb1266dd1045e995cce9b8d77759e740953a1c9aad9502a0461e/pillow-12.1.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a8eb7ed8d4198bccbd07058416eeec51686b498e784eda166395a23eb99138e", size = 6346621, upload-time = "2026-02-11T04:21:19.547Z" },
|
{ url = "https://files.pythonhosted.org/packages/4b/6e/3ccb54ce8ec4ddd1accd2d89004308b7b0b21c4ac3d20fa70af4760a4330/pillow-12.2.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdfebd752ec52bf5bb4e35d9c64b40826bc5b40a13df7c3cda20a2c03a0f5ed", size = 6395194, upload-time = "2026-04-01T14:43:59.864Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/71/24/538bff45bde96535d7d998c6fed1a751c75ac7c53c37c90dc2601b243893/pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47b94983da0c642de92ced1702c5b6c292a84bd3a8e1d1702ff923f183594717", size = 7038069, upload-time = "2026-02-11T04:21:21.378Z" },
|
{ url = "https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eedf4b74eda2b5a4b2b2fb4c006d6295df3bf29e459e198c90ea48e130dc75c3", size = 7082423, upload-time = "2026-04-01T14:44:02.74Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/94/0e/58cb1a6bc48f746bc4cb3adb8cabff73e2742c92b3bf7a220b7cf69b9177/pillow-12.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:518a48c2aab7ce596d3bf79d0e275661b846e86e4d0e7dec34712c30fe07f02a", size = 6460040, upload-time = "2026-02-11T04:21:23.148Z" },
|
{ url = "https://files.pythonhosted.org/packages/78/5f/e9f86ab0146464e8c133fe85df987ed9e77e08b29d8d35f9f9f4d6f917ba/pillow-12.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:00a2865911330191c0b818c59103b58a5e697cae67042366970a6b6f1b20b7f9", size = 6505667, upload-time = "2026-04-01T14:44:05.381Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/6c/57/9045cb3ff11eeb6c1adce3b2d60d7d299d7b273a2e6c8381a524abfdc474/pillow-12.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a550ae29b95c6dc13cf69e2c9dc5747f814c54eeb2e32d683e5e93af56caa029", size = 7164523, upload-time = "2026-02-11T04:21:25.01Z" },
|
{ url = "https://files.pythonhosted.org/packages/ed/1e/409007f56a2fdce61584fd3acbc2bbc259857d555196cedcadc68c015c82/pillow-12.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e1757442ed87f4912397c6d35a0db6a7b52592156014706f17658ff58bbf795", size = 7208580, upload-time = "2026-04-01T14:44:08.39Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/73/f2/9be9cb99f2175f0d4dbadd6616ce1bf068ee54a28277ea1bf1fbf729c250/pillow-12.1.1-cp313-cp313-win32.whl", hash = "sha256:a003d7422449f6d1e3a34e3dd4110c22148336918ddbfc6a32581cd54b2e0b2b", size = 6332552, upload-time = "2026-02-11T04:21:27.238Z" },
|
{ url = "https://files.pythonhosted.org/packages/23/c4/7349421080b12fb35414607b8871e9534546c128a11965fd4a7002ccfbee/pillow-12.2.0-cp313-cp313-win32.whl", hash = "sha256:144748b3af2d1b358d41286056d0003f47cb339b8c43a9ea42f5fea4d8c66b6e", size = 6375896, upload-time = "2026-04-01T14:44:11.197Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/3f/eb/b0834ad8b583d7d9d42b80becff092082a1c3c156bb582590fcc973f1c7c/pillow-12.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:344cf1e3dab3be4b1fa08e449323d98a2a3f819ad20f4b22e77a0ede31f0faa1", size = 7040108, upload-time = "2026-02-11T04:21:29.462Z" },
|
{ url = "https://files.pythonhosted.org/packages/3f/82/8a3739a5e470b3c6cbb1d21d315800d8e16bff503d1f16b03a4ec3212786/pillow-12.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:390ede346628ccc626e5730107cde16c42d3836b89662a115a921f28440e6a3b", size = 7081266, upload-time = "2026-04-01T14:44:13.947Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/d5/7d/fc09634e2aabdd0feabaff4a32f4a7d97789223e7c2042fd805ea4b4d2c2/pillow-12.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c0dd1636633e7e6a0afe7bf6a51a14992b7f8e60de5789018ebbdfae55b040a", size = 2453712, upload-time = "2026-02-11T04:21:31.072Z" },
|
{ url = "https://files.pythonhosted.org/packages/c3/25/f968f618a062574294592f668218f8af564830ccebdd1fa6200f598e65c5/pillow-12.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:8023abc91fba39036dbce14a7d6535632f99c0b857807cbbbf21ecc9f4717f06", size = 2463508, upload-time = "2026-04-01T14:44:16.312Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/19/2a/b9d62794fc8a0dd14c1943df68347badbd5511103e0d04c035ffe5cf2255/pillow-12.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0330d233c1a0ead844fc097a7d16c0abff4c12e856c0b325f231820fee1f39da", size = 5264880, upload-time = "2026-02-11T04:21:32.865Z" },
|
{ url = "https://files.pythonhosted.org/packages/4d/a4/b342930964e3cb4dce5038ae34b0eab4653334995336cd486c5a8c25a00c/pillow-12.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:042db20a421b9bafecc4b84a8b6e444686bd9d836c7fd24542db3e7df7baad9b", size = 5309927, upload-time = "2026-04-01T14:44:18.89Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/26/9d/e03d857d1347fa5ed9247e123fcd2a97b6220e15e9cb73ca0a8d91702c6e/pillow-12.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dae5f21afb91322f2ff791895ddd8889e5e947ff59f71b46041c8ce6db790bc", size = 4660616, upload-time = "2026-02-11T04:21:34.97Z" },
|
{ url = "https://files.pythonhosted.org/packages/9f/de/23198e0a65a9cf06123f5435a5d95cea62a635697f8f03d134d3f3a96151/pillow-12.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd025009355c926a84a612fecf58bb315a3f6814b17ead51a8e48d3823d9087f", size = 4698624, upload-time = "2026-04-01T14:44:21.115Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/f7/ec/8a6d22afd02570d30954e043f09c32772bfe143ba9285e2fdb11284952cd/pillow-12.1.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e0c664be47252947d870ac0d327fea7e63985a08794758aa8af5b6cb6ec0c9c", size = 6269008, upload-time = "2026-02-11T04:21:36.623Z" },
|
{ url = "https://files.pythonhosted.org/packages/01/a6/1265e977f17d93ea37aa28aa81bad4fa597933879fac2520d24e021c8da3/pillow-12.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88ddbc66737e277852913bd1e07c150cc7bb124539f94c4e2df5344494e0a612", size = 6321252, upload-time = "2026-04-01T14:44:23.663Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/3d/1d/6d875422c9f28a4a361f495a5f68d9de4a66941dc2c619103ca335fa6446/pillow-12.1.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:691ab2ac363b8217f7d31b3497108fb1f50faab2f75dfb03284ec2f217e87bf8", size = 8073226, upload-time = "2026-02-11T04:21:38.585Z" },
|
{ url = "https://files.pythonhosted.org/packages/3c/83/5982eb4a285967baa70340320be9f88e57665a387e3a53a7f0db8231a0cd/pillow-12.2.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d362d1878f00c142b7e1a16e6e5e780f02be8195123f164edf7eddd911eefe7c", size = 8126550, upload-time = "2026-04-01T14:44:26.772Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a1/cd/134b0b6ee5eda6dc09e25e24b40fdafe11a520bc725c1d0bbaa5e00bf95b/pillow-12.1.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9e8064fb1cc019296958595f6db671fba95209e3ceb0c4734c9baf97de04b20", size = 6380136, upload-time = "2026-02-11T04:21:40.562Z" },
|
{ url = "https://files.pythonhosted.org/packages/4e/48/6ffc514adce69f6050d0753b1a18fd920fce8cac87620d5a31231b04bfc5/pillow-12.2.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c727a6d53cb0018aadd8018c2b938376af27914a68a492f59dfcaca650d5eea", size = 6433114, upload-time = "2026-04-01T14:44:29.615Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/7a/a9/7628f013f18f001c1b98d8fffe3452f306a70dc6aba7d931019e0492f45e/pillow-12.1.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:472a8d7ded663e6162dafdf20015c486a7009483ca671cece7a9279b512fcb13", size = 7067129, upload-time = "2026-02-11T04:21:42.521Z" },
|
{ url = "https://files.pythonhosted.org/packages/36/a3/f9a77144231fb8d40ee27107b4463e205fa4677e2ca2548e14da5cf18dce/pillow-12.2.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd8c21c98c5cc60653bcb311bef2ce0401642b7ce9d09e03a7da87c878289d4", size = 7115667, upload-time = "2026-04-01T14:44:32.773Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/1e/f8/66ab30a2193b277785601e82ee2d49f68ea575d9637e5e234faaa98efa4c/pillow-12.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:89b54027a766529136a06cfebeecb3a04900397a3590fd252160b888479517bf", size = 6491807, upload-time = "2026-02-11T04:21:44.22Z" },
|
{ url = "https://files.pythonhosted.org/packages/c1/fc/ac4ee3041e7d5a565e1c4fd72a113f03b6394cc72ab7089d27608f8aaccb/pillow-12.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f08483a632889536b8139663db60f6724bfcb443c96f1b18855860d7d5c0fd4", size = 6538966, upload-time = "2026-04-01T14:44:35.252Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/da/0b/a877a6627dc8318fdb84e357c5e1a758c0941ab1ddffdafd231983788579/pillow-12.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:86172b0831b82ce4f7877f280055892b31179e1576aa00d0df3bb1bbf8c3e524", size = 7190954, upload-time = "2026-02-11T04:21:46.114Z" },
|
{ url = "https://files.pythonhosted.org/packages/c0/a8/27fb307055087f3668f6d0a8ccb636e7431d56ed0750e07a60547b1e083e/pillow-12.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dac8d77255a37e81a2efcbd1fc05f1c15ee82200e6c240d7e127e25e365c39ea", size = 7238241, upload-time = "2026-04-01T14:44:37.875Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/83/43/6f732ff85743cf746b1361b91665d9f5155e1483817f693f8d57ea93147f/pillow-12.1.1-cp313-cp313t-win32.whl", hash = "sha256:44ce27545b6efcf0fdbdceb31c9a5bdea9333e664cda58a7e674bb74608b3986", size = 6336441, upload-time = "2026-02-11T04:21:48.22Z" },
|
{ url = "https://files.pythonhosted.org/packages/ad/4b/926ab182c07fccae9fcb120043464e1ff1564775ec8864f21a0ebce6ac25/pillow-12.2.0-cp313-cp313t-win32.whl", hash = "sha256:ee3120ae9dff32f121610bb08e4313be87e03efeadfc6c0d18f89127e24d0c24", size = 6379592, upload-time = "2026-04-01T14:44:40.336Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/3b/44/e865ef3986611bb75bfabdf94a590016ea327833f434558801122979cd0e/pillow-12.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a285e3eb7a5a45a2ff504e31f4a8d1b12ef62e84e5411c6804a42197c1cf586c", size = 7045383, upload-time = "2026-02-11T04:21:50.015Z" },
|
{ url = "https://files.pythonhosted.org/packages/c2/c4/f9e476451a098181b30050cc4c9a3556b64c02cf6497ea421ac047e89e4b/pillow-12.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:325ca0528c6788d2a6c3d40e3568639398137346c3d6e66bb61db96b96511c98", size = 7085542, upload-time = "2026-04-01T14:44:43.251Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a8/c6/f4fb24268d0c6908b9f04143697ea18b0379490cb74ba9e8d41b898bd005/pillow-12.1.1-cp313-cp313t-win_arm64.whl", hash = "sha256:cc7d296b5ea4d29e6570dabeaed58d31c3fea35a633a69679fb03d7664f43fb3", size = 2456104, upload-time = "2026-02-11T04:21:51.633Z" },
|
{ url = "https://files.pythonhosted.org/packages/00/a4/285f12aeacbe2d6dc36c407dfbbe9e96d4a80b0fb710a337f6d2ad978c75/pillow-12.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e5a76d03a6c6dcef67edabda7a52494afa4035021a79c8558e14af25313d453", size = 2465765, upload-time = "2026-04-01T14:44:45.996Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/03/d0/bebb3ffbf31c5a8e97241476c4cf8b9828954693ce6744b4a2326af3e16b/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af", size = 4062652, upload-time = "2026-02-11T04:21:53.19Z" },
|
{ url = "https://files.pythonhosted.org/packages/bf/98/4595daa2365416a86cb0d495248a393dfc84e96d62ad080c8546256cb9c0/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8", size = 4100848, upload-time = "2026-04-01T14:44:48.48Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/2d/c0/0e16fb0addda4851445c28f8350d8c512f09de27bbb0d6d0bbf8b6709605/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f", size = 4138823, upload-time = "2026-02-11T04:22:03.088Z" },
|
{ url = "https://files.pythonhosted.org/packages/0b/79/40184d464cf89f6663e18dfcf7ca21aae2491fff1a16127681bf1fa9b8cf/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b", size = 4176515, upload-time = "2026-04-01T14:44:51.353Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/6b/fb/6170ec655d6f6bb6630a013dd7cf7bc218423d7b5fa9071bf63dc32175ae/pillow-12.1.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642", size = 3601143, upload-time = "2026-02-11T04:22:04.909Z" },
|
{ url = "https://files.pythonhosted.org/packages/b0/63/703f86fd4c422a9cf722833670f4f71418fb116b2853ff7da722ea43f184/pillow-12.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295", size = 3640159, upload-time = "2026-04-01T14:44:53.588Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/59/04/dc5c3f297510ba9a6837cbb318b87dd2b8f73eb41a43cc63767f65cb599c/pillow-12.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd", size = 5266254, upload-time = "2026-02-11T04:22:07.656Z" },
|
{ url = "https://files.pythonhosted.org/packages/71/e0/fb22f797187d0be2270f83500aab851536101b254bfa1eae10795709d283/pillow-12.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed", size = 5312185, upload-time = "2026-04-01T14:44:56.039Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/05/30/5db1236b0d6313f03ebf97f5e17cda9ca060f524b2fcc875149a8360b21c/pillow-12.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202", size = 4657499, upload-time = "2026-02-11T04:22:09.613Z" },
|
{ url = "https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae", size = 4695386, upload-time = "2026-04-01T14:44:58.663Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/6f/18/008d2ca0eb612e81968e8be0bbae5051efba24d52debf930126d7eaacbba/pillow-12.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f", size = 6232137, upload-time = "2026-02-11T04:22:11.434Z" },
|
{ url = "https://files.pythonhosted.org/packages/70/62/98f6b7f0c88b9addd0e87c217ded307b36be024d4ff8869a812b241d1345/pillow-12.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601", size = 6280384, upload-time = "2026-04-01T14:45:01.5Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/70/f1/f14d5b8eeb4b2cd62b9f9f847eb6605f103df89ef619ac68f92f748614ea/pillow-12.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f", size = 8042721, upload-time = "2026-02-11T04:22:13.321Z" },
|
{ url = "https://files.pythonhosted.org/packages/5e/03/688747d2e91cfbe0e64f316cd2e8005698f76ada3130d0194664174fa5de/pillow-12.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be", size = 8091599, upload-time = "2026-04-01T14:45:04.5Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/5a/d6/17824509146e4babbdabf04d8171491fa9d776f7061ff6e727522df9bd03/pillow-12.1.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f", size = 6347798, upload-time = "2026-02-11T04:22:15.449Z" },
|
{ url = "https://files.pythonhosted.org/packages/f6/35/577e22b936fcdd66537329b33af0b4ccfefaeabd8aec04b266528cddb33c/pillow-12.2.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f", size = 6396021, upload-time = "2026-04-01T14:45:07.117Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/d1/ee/c85a38a9ab92037a75615aba572c85ea51e605265036e00c5b67dfafbfe2/pillow-12.1.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e", size = 7039315, upload-time = "2026-02-11T04:22:17.24Z" },
|
{ url = "https://files.pythonhosted.org/packages/11/8d/d2532ad2a603ca2b93ad9f5135732124e57811d0168155852f37fbce2458/pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286", size = 7083360, upload-time = "2026-04-01T14:45:09.763Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ec/f3/bc8ccc6e08a148290d7523bde4d9a0d6c981db34631390dc6e6ec34cacf6/pillow-12.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0", size = 6462360, upload-time = "2026-02-11T04:22:19.111Z" },
|
{ url = "https://files.pythonhosted.org/packages/5e/26/d325f9f56c7e039034897e7380e9cc202b1e368bfd04d4cbe6a441f02885/pillow-12.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50", size = 6507628, upload-time = "2026-04-01T14:45:12.378Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/f6/ab/69a42656adb1d0665ab051eec58a41f169ad295cf81ad45406963105408f/pillow-12.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb", size = 7165438, upload-time = "2026-02-11T04:22:21.041Z" },
|
{ url = "https://files.pythonhosted.org/packages/5f/f7/769d5632ffb0988f1c5e7660b3e731e30f7f8ec4318e94d0a5d674eb65a4/pillow-12.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104", size = 7209321, upload-time = "2026-04-01T14:45:15.122Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/02/46/81f7aa8941873f0f01d4b55cc543b0a3d03ec2ee30d617a0448bf6bd6dec/pillow-12.1.1-cp314-cp314-win32.whl", hash = "sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f", size = 6431503, upload-time = "2026-02-11T04:22:22.833Z" },
|
{ url = "https://files.pythonhosted.org/packages/6a/7a/c253e3c645cd47f1aceea6a8bacdba9991bf45bb7dfe927f7c893e89c93c/pillow-12.2.0-cp314-cp314-win32.whl", hash = "sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7", size = 6479723, upload-time = "2026-04-01T14:45:17.797Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/40/72/4c245f7d1044b67affc7f134a09ea619d4895333d35322b775b928180044/pillow-12.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15", size = 7176748, upload-time = "2026-02-11T04:22:24.64Z" },
|
{ url = "https://files.pythonhosted.org/packages/cd/8b/601e6566b957ca50e28725cb6c355c59c2c8609751efbecd980db44e0349/pillow-12.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150", size = 7217400, upload-time = "2026-04-01T14:45:20.529Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/e4/ad/8a87bdbe038c5c698736e3348af5c2194ffb872ea52f11894c95f9305435/pillow-12.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f", size = 2544314, upload-time = "2026-02-11T04:22:26.685Z" },
|
{ url = "https://files.pythonhosted.org/packages/d6/94/220e46c73065c3e2951bb91c11a1fb636c8c9ad427ac3ce7d7f3359b9b2f/pillow-12.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1", size = 2554835, upload-time = "2026-04-01T14:45:23.162Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/6c/9d/efd18493f9de13b87ede7c47e69184b9e859e4427225ea962e32e56a49bc/pillow-12.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8", size = 5268612, upload-time = "2026-02-11T04:22:29.884Z" },
|
{ url = "https://files.pythonhosted.org/packages/b6/ab/1b426a3974cb0e7da5c29ccff4807871d48110933a57207b5a676cccc155/pillow-12.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463", size = 5314225, upload-time = "2026-04-01T14:45:25.637Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/f8/f1/4f42eb2b388eb2ffc660dcb7f7b556c1015c53ebd5f7f754965ef997585b/pillow-12.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9", size = 4660567, upload-time = "2026-02-11T04:22:31.799Z" },
|
{ url = "https://files.pythonhosted.org/packages/19/1e/dce46f371be2438eecfee2a1960ee2a243bbe5e961890146d2dee1ff0f12/pillow-12.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3", size = 4698541, upload-time = "2026-04-01T14:45:28.355Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/01/54/df6ef130fa43e4b82e32624a7b821a2be1c5653a5fdad8469687a7db4e00/pillow-12.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60", size = 6269951, upload-time = "2026-02-11T04:22:33.921Z" },
|
{ url = "https://files.pythonhosted.org/packages/55/c3/7fbecf70adb3a0c33b77a300dc52e424dc22ad8cdc06557a2e49523b703d/pillow-12.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166", size = 6322251, upload-time = "2026-04-01T14:45:30.924Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a9/48/618752d06cc44bb4aae8ce0cd4e6426871929ed7b46215638088270d9b34/pillow-12.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7", size = 8074769, upload-time = "2026-02-11T04:22:35.877Z" },
|
{ url = "https://files.pythonhosted.org/packages/1c/3c/7fbc17cfb7e4fe0ef1642e0abc17fc6c94c9f7a16be41498e12e2ba60408/pillow-12.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe", size = 8127807, upload-time = "2026-04-01T14:45:33.908Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/c3/bd/f1d71eb39a72fa088d938655afba3e00b38018d052752f435838961127d8/pillow-12.1.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f", size = 6381358, upload-time = "2026-02-11T04:22:37.698Z" },
|
{ url = "https://files.pythonhosted.org/packages/ff/c3/a8ae14d6defd2e448493ff512fae903b1e9bd40b72efb6ec55ce0048c8ce/pillow-12.2.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd", size = 6433935, upload-time = "2026-04-01T14:45:36.623Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/64/ef/c784e20b96674ed36a5af839305f55616f8b4f8aa8eeccf8531a6e312243/pillow-12.1.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586", size = 7068558, upload-time = "2026-02-11T04:22:39.597Z" },
|
{ url = "https://files.pythonhosted.org/packages/6e/32/2880fb3a074847ac159d8f902cb43278a61e85f681661e7419e6596803ed/pillow-12.2.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e", size = 7116720, upload-time = "2026-04-01T14:45:39.258Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/73/cb/8059688b74422ae61278202c4e1ad992e8a2e7375227be0a21c6b87ca8d5/pillow-12.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce", size = 6493028, upload-time = "2026-02-11T04:22:42.73Z" },
|
{ url = "https://files.pythonhosted.org/packages/46/87/495cc9c30e0129501643f24d320076f4cc54f718341df18cc70ec94c44e1/pillow-12.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06", size = 6540498, upload-time = "2026-04-01T14:45:41.879Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/c6/da/e3c008ed7d2dd1f905b15949325934510b9d1931e5df999bb15972756818/pillow-12.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8", size = 7191940, upload-time = "2026-02-11T04:22:44.543Z" },
|
{ url = "https://files.pythonhosted.org/packages/18/53/773f5edca692009d883a72211b60fdaf8871cbef075eaa9d577f0a2f989e/pillow-12.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43", size = 7239413, upload-time = "2026-04-01T14:45:44.705Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/01/4a/9202e8d11714c1fc5951f2e1ef362f2d7fbc595e1f6717971d5dd750e969/pillow-12.1.1-cp314-cp314t-win32.whl", hash = "sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36", size = 6438736, upload-time = "2026-02-11T04:22:46.347Z" },
|
{ url = "https://files.pythonhosted.org/packages/c9/e4/4b64a97d71b2a83158134abbb2f5bd3f8a2ea691361282f010998f339ec7/pillow-12.2.0-cp314-cp314t-win32.whl", hash = "sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354", size = 6482084, upload-time = "2026-04-01T14:45:47.568Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/f3/ca/cbce2327eb9885476b3957b2e82eb12c866a8b16ad77392864ad601022ce/pillow-12.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b", size = 7182894, upload-time = "2026-02-11T04:22:48.114Z" },
|
{ url = "https://files.pythonhosted.org/packages/ba/13/306d275efd3a3453f72114b7431c877d10b1154014c1ebbedd067770d629/pillow-12.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1", size = 7225152, upload-time = "2026-04-01T14:45:50.032Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ec/d2/de599c95ba0a973b94410477f8bf0b6f0b5e67360eb89bcb1ad365258beb/pillow-12.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334", size = 2546446, upload-time = "2026-02-11T04:22:50.342Z" },
|
{ url = "https://files.pythonhosted.org/packages/ff/6e/cf826fae916b8658848d7b9f38d88da6396895c676e8086fc0988073aaf8/pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb", size = 2556579, upload-time = "2026-04-01T14:45:52.529Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1226,15 +1073,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d", size = 31594, upload-time = "2025-06-20T18:49:47.491Z" },
|
{ url = "https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d", size = 31594, upload-time = "2025-06-20T18:49:47.491Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pycparser"
|
|
||||||
version = "3.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyfakefs"
|
name = "pyfakefs"
|
||||||
version = "5.10.0"
|
version = "5.10.0"
|
||||||
@@ -1253,18 +1091,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/c2/2f/81d580a0fb83baeb066698975cb14a618bdbed7720678566f1b046a95fe8/pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f", size = 63551, upload-time = "2025-06-20T18:45:26.937Z" },
|
{ url = "https://files.pythonhosted.org/packages/c2/2f/81d580a0fb83baeb066698975cb14a618bdbed7720678566f1b046a95fe8/pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f", size = 63551, upload-time = "2025-06-20T18:45:26.937Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pygal"
|
|
||||||
version = "3.1.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "importlib-metadata" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/b0/b6/04176faeb312c84d7f9bc1a810f96ee38d15597e226bb9bda59f3a5cb122/pygal-3.1.0.tar.gz", hash = "sha256:fbdee7351a7423e7907fb8a9c3b77305f6b5678cb2e6fd0db36a8825e42955ec", size = 81006, upload-time = "2025-12-09T10:29:19.587Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/0b/4c/2862dd25352fe4b22ec7760d4fa12cb692587cd7ec3e378cbf644fc0d2a8/pygal-3.1.0-py3-none-any.whl", hash = "sha256:4e923490f3490c90c481f4535fa3adcda20ff374257ab9d8ae897f91b632c0bb", size = 130171, upload-time = "2025-12-09T10:29:16.721Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pygls"
|
name = "pygls"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
@@ -1443,15 +1269,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/02/e7/062480ede84ecb56ee0f8f2e5b5a3b2a5bceeb73bbdf909d3c13f5438749/scriv-1.8.0-py3-none-any.whl", hash = "sha256:f00f51325b2f4bc96b16fbb1239d4ab577cc2422301a5dd4f5f9378aae2549e0", size = 39085, upload-time = "2025-12-30T00:01:08.599Z" },
|
{ url = "https://files.pythonhosted.org/packages/02/e7/062480ede84ecb56ee0f8f2e5b5a3b2a5bceeb73bbdf909d3c13f5438749/scriv-1.8.0-py3-none-any.whl", hash = "sha256:f00f51325b2f4bc96b16fbb1239d4ab577cc2422301a5dd4f5f9378aae2549e0", size = 39085, upload-time = "2025-12-30T00:01:08.599Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "shellingham"
|
|
||||||
version = "1.5.4"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shibuya"
|
name = "shibuya"
|
||||||
version = "2025.9.25"
|
version = "2025.9.25"
|
||||||
@@ -1618,33 +1435,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/be/72/2db2f49247d0a18b4f1bb9a5a39a0162869acf235f3a96418363947b3d46/starlette-0.48.0-py3-none-any.whl", hash = "sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659", size = 73736, upload-time = "2025-09-13T08:41:03.869Z" },
|
{ url = "https://files.pythonhosted.org/packages/be/72/2db2f49247d0a18b4f1bb9a5a39a0162869acf235f3a96418363947b3d46/starlette-0.48.0-py3-none-any.whl", hash = "sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659", size = 73736, upload-time = "2025-09-13T08:41:03.869Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tinycss2"
|
|
||||||
version = "1.5.1"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "webencodings" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/a3/ae/2ca4913e5c0f09781d75482874c3a95db9105462a92ddd303c7d285d3df2/tinycss2-1.5.1.tar.gz", hash = "sha256:d339d2b616ba90ccce58da8495a78f46e55d4d25f9fd71dfd526f07e7d53f957", size = 88195, upload-time = "2025-11-23T10:29:10.082Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/60/45/c7b5c3168458db837e8ceab06dc77824e18202679d0463f0e8f002143a97/tinycss2-1.5.1-py3-none-any.whl", hash = "sha256:3415ba0f5839c062696996998176c4a3751d18b7edaaeeb658c9ce21ec150661", size = 28404, upload-time = "2025-11-23T10:29:08.676Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "typer"
|
|
||||||
version = "0.21.1"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "click" },
|
|
||||||
{ name = "rich" },
|
|
||||||
{ name = "shellingham" },
|
|
||||||
{ name = "typing-extensions" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/36/bf/8825b5929afd84d0dabd606c67cd57b8388cb3ec385f7ef19c5cc2202069/typer-0.21.1.tar.gz", hash = "sha256:ea835607cd752343b6b2b7ce676893e5a0324082268b48f27aa058bdb7d2145d", size = 110371, upload-time = "2026-01-06T11:21:10.989Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a0/1d/d9257dd49ff2ca23ea5f132edf1281a0c4f9de8a762b9ae399b670a59235/typer-0.21.1-py3-none-any.whl", hash = "sha256:7985e89081c636b88d172c2ee0cfe33c253160994d47bdfdc302defd7d1f1d01", size = 47381, upload-time = "2026-01-06T11:21:09.824Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typing-extensions"
|
name = "typing-extensions"
|
||||||
version = "4.13.2"
|
version = "4.13.2"
|
||||||
@@ -1656,11 +1446,11 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "urllib3"
|
name = "urllib3"
|
||||||
version = "2.6.3"
|
version = "2.7.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" },
|
{ url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1755,15 +1545,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/18/0e/a5f0257ab47492b7afb5fb60347d14ba19445e2773fc8352d4be6bd2f6f8/wcwidth-0.3.0-py3-none-any.whl", hash = "sha256:073a1acb250e4add96cfd5ef84e0036605cd6e0d0782c8c15c80e42202348458", size = 85520, upload-time = "2026-01-21T17:44:08.002Z" },
|
{ url = "https://files.pythonhosted.org/packages/18/0e/a5f0257ab47492b7afb5fb60347d14ba19445e2773fc8352d4be6bd2f6f8/wcwidth-0.3.0-py3-none-any.whl", hash = "sha256:073a1acb250e4add96cfd5ef84e0036605cd6e0d0782c8c15c80e42202348458", size = 85520, upload-time = "2026-01-21T17:44:08.002Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "webencodings"
|
|
||||||
version = "0.5.1"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721, upload-time = "2017-04-05T20:21:34.189Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774, upload-time = "2017-04-05T20:21:32.581Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "websockets"
|
name = "websockets"
|
||||||
version = "15.0.1"
|
version = "15.0.1"
|
||||||
@@ -1808,69 +1589,3 @@ sdist = { url = "https://files.pythonhosted.org/packages/b8/08/c0776aa654dc43cb3
|
|||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/9b/58/98c4aa00e3de8e45726029799d8facbdcd75347b2f48b285857577e8efd8/wemake_python_styleguide-1.4.0-py3-none-any.whl", hash = "sha256:c0727475a20a1b7d59f1d806040e84768bdb0935d1147023453aa44c14b65c95", size = 215985, upload-time = "2025-08-25T10:15:06.713Z" },
|
{ url = "https://files.pythonhosted.org/packages/9b/58/98c4aa00e3de8e45726029799d8facbdcd75347b2f48b285857577e8efd8/wemake_python_styleguide-1.4.0-py3-none-any.whl", hash = "sha256:c0727475a20a1b7d59f1d806040e84768bdb0935d1147023453aa44c14b65c95", size = 215985, upload-time = "2025-08-25T10:15:06.713Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zipp"
|
|
||||||
version = "3.23.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zstandard"
|
|
||||||
version = "0.25.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/fd/aa/3e0508d5a5dd96529cdc5a97011299056e14c6505b678fd58938792794b1/zstandard-0.25.0.tar.gz", hash = "sha256:7713e1179d162cf5c7906da876ec2ccb9c3a9dcbdffef0cc7f70c3667a205f0b", size = 711513, upload-time = "2025-09-14T22:15:54.002Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/82/fc/f26eb6ef91ae723a03e16eddb198abcfce2bc5a42e224d44cc8b6765e57e/zstandard-0.25.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7b3c3a3ab9daa3eed242d6ecceead93aebbb8f5f84318d82cee643e019c4b73b", size = 795738, upload-time = "2025-09-14T22:16:56.237Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/aa/1c/d920d64b22f8dd028a8b90e2d756e431a5d86194caa78e3819c7bf53b4b3/zstandard-0.25.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:913cbd31a400febff93b564a23e17c3ed2d56c064006f54efec210d586171c00", size = 640436, upload-time = "2025-09-14T22:16:57.774Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/53/6c/288c3f0bd9fcfe9ca41e2c2fbfd17b2097f6af57b62a81161941f09afa76/zstandard-0.25.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:011d388c76b11a0c165374ce660ce2c8efa8e5d87f34996aa80f9c0816698b64", size = 5343019, upload-time = "2025-09-14T22:16:59.302Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/1e/15/efef5a2f204a64bdb5571e6161d49f7ef0fffdbca953a615efbec045f60f/zstandard-0.25.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dffecc361d079bb48d7caef5d673c88c8988d3d33fb74ab95b7ee6da42652ea", size = 5063012, upload-time = "2025-09-14T22:17:01.156Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b7/37/a6ce629ffdb43959e92e87ebdaeebb5ac81c944b6a75c9c47e300f85abdf/zstandard-0.25.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7149623bba7fdf7e7f24312953bcf73cae103db8cae49f8154dd1eadc8a29ecb", size = 5394148, upload-time = "2025-09-14T22:17:03.091Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e3/79/2bf870b3abeb5c070fe2d670a5a8d1057a8270f125ef7676d29ea900f496/zstandard-0.25.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6a573a35693e03cf1d67799fd01b50ff578515a8aeadd4595d2a7fa9f3ec002a", size = 5451652, upload-time = "2025-09-14T22:17:04.979Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/53/60/7be26e610767316c028a2cbedb9a3beabdbe33e2182c373f71a1c0b88f36/zstandard-0.25.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5a56ba0db2d244117ed744dfa8f6f5b366e14148e00de44723413b2f3938a902", size = 5546993, upload-time = "2025-09-14T22:17:06.781Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/85/c7/3483ad9ff0662623f3648479b0380d2de5510abf00990468c286c6b04017/zstandard-0.25.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:10ef2a79ab8e2974e2075fb984e5b9806c64134810fac21576f0668e7ea19f8f", size = 5046806, upload-time = "2025-09-14T22:17:08.415Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/08/b3/206883dd25b8d1591a1caa44b54c2aad84badccf2f1de9e2d60a446f9a25/zstandard-0.25.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aaf21ba8fb76d102b696781bddaa0954b782536446083ae3fdaa6f16b25a1c4b", size = 5576659, upload-time = "2025-09-14T22:17:10.164Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/9d/31/76c0779101453e6c117b0ff22565865c54f48f8bd807df2b00c2c404b8e0/zstandard-0.25.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1869da9571d5e94a85a5e8d57e4e8807b175c9e4a6294e3b66fa4efb074d90f6", size = 4953933, upload-time = "2025-09-14T22:17:11.857Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/18/e1/97680c664a1bf9a247a280a053d98e251424af51f1b196c6d52f117c9720/zstandard-0.25.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:809c5bcb2c67cd0ed81e9229d227d4ca28f82d0f778fc5fea624a9def3963f91", size = 5268008, upload-time = "2025-09-14T22:17:13.627Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/1e/73/316e4010de585ac798e154e88fd81bb16afc5c5cb1a72eeb16dd37e8024a/zstandard-0.25.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f27662e4f7dbf9f9c12391cb37b4c4c3cb90ffbd3b1fb9284dadbbb8935fa708", size = 5433517, upload-time = "2025-09-14T22:17:16.103Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/5b/60/dd0f8cfa8129c5a0ce3ea6b7f70be5b33d2618013a161e1ff26c2b39787c/zstandard-0.25.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99c0c846e6e61718715a3c9437ccc625de26593fea60189567f0118dc9db7512", size = 5814292, upload-time = "2025-09-14T22:17:17.827Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/fc/5f/75aafd4b9d11b5407b641b8e41a57864097663699f23e9ad4dbb91dc6bfe/zstandard-0.25.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:474d2596a2dbc241a556e965fb76002c1ce655445e4e3bf38e5477d413165ffa", size = 5360237, upload-time = "2025-09-14T22:17:19.954Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ff/8d/0309daffea4fcac7981021dbf21cdb2e3427a9e76bafbcdbdf5392ff99a4/zstandard-0.25.0-cp312-cp312-win32.whl", hash = "sha256:23ebc8f17a03133b4426bcc04aabd68f8236eb78c3760f12783385171b0fd8bd", size = 436922, upload-time = "2025-09-14T22:17:24.398Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/79/3b/fa54d9015f945330510cb5d0b0501e8253c127cca7ebe8ba46a965df18c5/zstandard-0.25.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffef5a74088f1e09947aecf91011136665152e0b4b359c42be3373897fb39b01", size = 506276, upload-time = "2025-09-14T22:17:21.429Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ea/6b/8b51697e5319b1f9ac71087b0af9a40d8a6288ff8025c36486e0c12abcc4/zstandard-0.25.0-cp312-cp312-win_arm64.whl", hash = "sha256:181eb40e0b6a29b3cd2849f825e0fa34397f649170673d385f3598ae17cca2e9", size = 462679, upload-time = "2025-09-14T22:17:23.147Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/35/0b/8df9c4ad06af91d39e94fa96cc010a24ac4ef1378d3efab9223cc8593d40/zstandard-0.25.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec996f12524f88e151c339688c3897194821d7f03081ab35d31d1e12ec975e94", size = 795735, upload-time = "2025-09-14T22:17:26.042Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3f/06/9ae96a3e5dcfd119377ba33d4c42a7d89da1efabd5cb3e366b156c45ff4d/zstandard-0.25.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a1a4ae2dec3993a32247995bdfe367fc3266da832d82f8438c8570f989753de1", size = 640440, upload-time = "2025-09-14T22:17:27.366Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d9/14/933d27204c2bd404229c69f445862454dcc101cd69ef8c6068f15aaec12c/zstandard-0.25.0-cp313-cp313-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:e96594a5537722fdfb79951672a2a63aec5ebfb823e7560586f7484819f2a08f", size = 5343070, upload-time = "2025-09-14T22:17:28.896Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/6d/db/ddb11011826ed7db9d0e485d13df79b58586bfdec56e5c84a928a9a78c1c/zstandard-0.25.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bfc4e20784722098822e3eee42b8e576b379ed72cca4a7cb856ae733e62192ea", size = 5063001, upload-time = "2025-09-14T22:17:31.044Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/db/00/87466ea3f99599d02a5238498b87bf84a6348290c19571051839ca943777/zstandard-0.25.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:457ed498fc58cdc12fc48f7950e02740d4f7ae9493dd4ab2168a47c93c31298e", size = 5394120, upload-time = "2025-09-14T22:17:32.711Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/2b/95/fc5531d9c618a679a20ff6c29e2b3ef1d1f4ad66c5e161ae6ff847d102a9/zstandard-0.25.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:fd7a5004eb1980d3cefe26b2685bcb0b17989901a70a1040d1ac86f1d898c551", size = 5451230, upload-time = "2025-09-14T22:17:34.41Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/63/4b/e3678b4e776db00f9f7b2fe58e547e8928ef32727d7a1ff01dea010f3f13/zstandard-0.25.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8e735494da3db08694d26480f1493ad2cf86e99bdd53e8e9771b2752a5c0246a", size = 5547173, upload-time = "2025-09-14T22:17:36.084Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/4e/d5/ba05ed95c6b8ec30bd468dfeab20589f2cf709b5c940483e31d991f2ca58/zstandard-0.25.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3a39c94ad7866160a4a46d772e43311a743c316942037671beb264e395bdd611", size = 5046736, upload-time = "2025-09-14T22:17:37.891Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/50/d5/870aa06b3a76c73eced65c044b92286a3c4e00554005ff51962deef28e28/zstandard-0.25.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:172de1f06947577d3a3005416977cce6168f2261284c02080e7ad0185faeced3", size = 5576368, upload-time = "2025-09-14T22:17:40.206Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/5d/35/398dc2ffc89d304d59bc12f0fdd931b4ce455bddf7038a0a67733a25f550/zstandard-0.25.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3c83b0188c852a47cd13ef3bf9209fb0a77fa5374958b8c53aaa699398c6bd7b", size = 4954022, upload-time = "2025-09-14T22:17:41.879Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/9a/5c/36ba1e5507d56d2213202ec2b05e8541734af5f2ce378c5d1ceaf4d88dc4/zstandard-0.25.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1673b7199bbe763365b81a4f3252b8e80f44c9e323fc42940dc8843bfeaf9851", size = 5267889, upload-time = "2025-09-14T22:17:43.577Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/70/e8/2ec6b6fb7358b2ec0113ae202647ca7c0e9d15b61c005ae5225ad0995df5/zstandard-0.25.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0be7622c37c183406f3dbf0cba104118eb16a4ea7359eeb5752f0794882fc250", size = 5433952, upload-time = "2025-09-14T22:17:45.271Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/7b/01/b5f4d4dbc59ef193e870495c6f1275f5b2928e01ff5a81fecb22a06e22fb/zstandard-0.25.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:5f5e4c2a23ca271c218ac025bd7d635597048b366d6f31f420aaeb715239fc98", size = 5814054, upload-time = "2025-09-14T22:17:47.08Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b2/e5/fbd822d5c6f427cf158316d012c5a12f233473c2f9c5fe5ab1ae5d21f3d8/zstandard-0.25.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f187a0bb61b35119d1926aee039524d1f93aaf38a9916b8c4b78ac8514a0aaf", size = 5360113, upload-time = "2025-09-14T22:17:48.893Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/8e/e0/69a553d2047f9a2c7347caa225bb3a63b6d7704ad74610cb7823baa08ed7/zstandard-0.25.0-cp313-cp313-win32.whl", hash = "sha256:7030defa83eef3e51ff26f0b7bfb229f0204b66fe18e04359ce3474ac33cbc09", size = 436936, upload-time = "2025-09-14T22:17:52.658Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d9/82/b9c06c870f3bd8767c201f1edbdf9e8dc34be5b0fbc5682c4f80fe948475/zstandard-0.25.0-cp313-cp313-win_amd64.whl", hash = "sha256:1f830a0dac88719af0ae43b8b2d6aef487d437036468ef3c2ea59c51f9d55fd5", size = 506232, upload-time = "2025-09-14T22:17:50.402Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d4/57/60c3c01243bb81d381c9916e2a6d9e149ab8627c0c7d7abb2d73384b3c0c/zstandard-0.25.0-cp313-cp313-win_arm64.whl", hash = "sha256:85304a43f4d513f5464ceb938aa02c1e78c2943b29f44a750b48b25ac999a049", size = 462671, upload-time = "2025-09-14T22:17:51.533Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3d/5c/f8923b595b55fe49e30612987ad8bf053aef555c14f05bb659dd5dbe3e8a/zstandard-0.25.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e29f0cf06974c899b2c188ef7f783607dbef36da4c242eb6c82dcd8b512855e3", size = 795887, upload-time = "2025-09-14T22:17:54.198Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/8d/09/d0a2a14fc3439c5f874042dca72a79c70a532090b7ba0003be73fee37ae2/zstandard-0.25.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:05df5136bc5a011f33cd25bc9f506e7426c0c9b3f9954f056831ce68f3b6689f", size = 640658, upload-time = "2025-09-14T22:17:55.423Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/5d/7c/8b6b71b1ddd517f68ffb55e10834388d4f793c49c6b83effaaa05785b0b4/zstandard-0.25.0-cp314-cp314-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:f604efd28f239cc21b3adb53eb061e2a205dc164be408e553b41ba2ffe0ca15c", size = 5379849, upload-time = "2025-09-14T22:17:57.372Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a4/86/a48e56320d0a17189ab7a42645387334fba2200e904ee47fc5a26c1fd8ca/zstandard-0.25.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223415140608d0f0da010499eaa8ccdb9af210a543fac54bce15babbcfc78439", size = 5058095, upload-time = "2025-09-14T22:17:59.498Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f8/ad/eb659984ee2c0a779f9d06dbfe45e2dc39d99ff40a319895df2d3d9a48e5/zstandard-0.25.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e54296a283f3ab5a26fc9b8b5d4978ea0532f37b231644f367aa588930aa043", size = 5551751, upload-time = "2025-09-14T22:18:01.618Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/61/b3/b637faea43677eb7bd42ab204dfb7053bd5c4582bfe6b1baefa80ac0c47b/zstandard-0.25.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ca54090275939dc8ec5dea2d2afb400e0f83444b2fc24e07df7fdef677110859", size = 6364818, upload-time = "2025-09-14T22:18:03.769Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/31/dc/cc50210e11e465c975462439a492516a73300ab8caa8f5e0902544fd748b/zstandard-0.25.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e09bb6252b6476d8d56100e8147b803befa9a12cea144bbe629dd508800d1ad0", size = 5560402, upload-time = "2025-09-14T22:18:05.954Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c9/ae/56523ae9c142f0c08efd5e868a6da613ae76614eca1305259c3bf6a0ed43/zstandard-0.25.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a9ec8c642d1ec73287ae3e726792dd86c96f5681eb8df274a757bf62b750eae7", size = 4955108, upload-time = "2025-09-14T22:18:07.68Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/98/cf/c899f2d6df0840d5e384cf4c4121458c72802e8bda19691f3b16619f51e9/zstandard-0.25.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a4089a10e598eae6393756b036e0f419e8c1d60f44a831520f9af41c14216cf2", size = 5269248, upload-time = "2025-09-14T22:18:09.753Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/1b/c0/59e912a531d91e1c192d3085fc0f6fb2852753c301a812d856d857ea03c6/zstandard-0.25.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f67e8f1a324a900e75b5e28ffb152bcac9fbed1cc7b43f99cd90f395c4375344", size = 5430330, upload-time = "2025-09-14T22:18:11.966Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a0/1d/7e31db1240de2df22a58e2ea9a93fc6e38cc29353e660c0272b6735d6669/zstandard-0.25.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:9654dbc012d8b06fc3d19cc825af3f7bf8ae242226df5f83936cb39f5fdc846c", size = 5811123, upload-time = "2025-09-14T22:18:13.907Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f6/49/fac46df5ad353d50535e118d6983069df68ca5908d4d65b8c466150a4ff1/zstandard-0.25.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4203ce3b31aec23012d3a4cf4a2ed64d12fea5269c49aed5e4c3611b938e4088", size = 5359591, upload-time = "2025-09-14T22:18:16.465Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c2/38/f249a2050ad1eea0bb364046153942e34abba95dd5520af199aed86fbb49/zstandard-0.25.0-cp314-cp314-win32.whl", hash = "sha256:da469dc041701583e34de852d8634703550348d5822e66a0c827d39b05365b12", size = 444513, upload-time = "2025-09-14T22:18:20.61Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3a/43/241f9615bcf8ba8903b3f0432da069e857fc4fd1783bd26183db53c4804b/zstandard-0.25.0-cp314-cp314-win_amd64.whl", hash = "sha256:c19bcdd826e95671065f8692b5a4aa95c52dc7a02a4c5a0cac46deb879a017a2", size = 516118, upload-time = "2025-09-14T22:18:17.849Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f0/ef/da163ce2450ed4febf6467d77ccb4cd52c4c30ab45624bad26ca0a27260c/zstandard-0.25.0-cp314-cp314-win_arm64.whl", hash = "sha256:d7541afd73985c630bafcd6338d2518ae96060075f9463d7dc14cfb33514383d", size = 476940, upload-time = "2025-09-14T22:18:19.088Z" },
|
|
||||||
]
|
|
||||||
|
|||||||
Reference in New Issue
Block a user