Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f85c0034f | ||
|
|
d56a4cdf34 | ||
|
|
9d13c6ec14 |
@@ -0,0 +1,36 @@
|
||||
name: Publish Package to Gitea
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*" # This triggers the workflow when you push a tag like v1.0.0
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest # Or your specific runner label
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.x"
|
||||
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install build twine
|
||||
|
||||
- name: Build package
|
||||
run: python -m build
|
||||
|
||||
- name: Publish to Gitea PyPI
|
||||
env:
|
||||
# These secrets must match the names you created in Gitea
|
||||
TWINE_USERNAME: ${{ secrets.TWINE_PASSWORD }}
|
||||
TWINE_PASSWORD: ${{ secrets.TWINE_USERNAME }}
|
||||
# REPLACE THIS URL with your actual Gitea PyPI endpoint
|
||||
TWINE_REPOSITORY_URL: "https://gitea.schneiderserver.site/api/packages/mathias2784/pypi"
|
||||
run: |
|
||||
twine upload --repository-url $TWINE_REPOSITORY_URL dist/*
|
||||
+19
-2
@@ -1,16 +1,18 @@
|
||||
[project]
|
||||
name = "utils"
|
||||
version = "0.1.1"
|
||||
description = "This is a test version bump with only minor improvements"
|
||||
version = "0.1.2"
|
||||
description = "Some hopefully useful utilities to use in everyday coding"
|
||||
readme = "README.md"
|
||||
authors = [
|
||||
{ name = "Mathias Schneider", email = "mathias.schneider@d-fine.com" }
|
||||
]
|
||||
requires-python = ">=3.14"
|
||||
dependencies = [
|
||||
"black>=26.5.1",
|
||||
"pydantic>=2.13.5",
|
||||
"pydantic-settings>=2.15.0",
|
||||
"pytest>=9.1.1",
|
||||
"ruff>=0.16.10",
|
||||
"tk>=0.1.0",
|
||||
"twine>=7.0.0",
|
||||
"uvicorn>=0.54.0",
|
||||
@@ -19,6 +21,21 @@ dependencies = [
|
||||
[project.scripts]
|
||||
utils = "utils:main"
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
# Live tests open a real GUI dialog and are intended for local manual
|
||||
# testing only; they are deselected by default (e.g. in the pipeline)
|
||||
# and run explicitly with: uv run pytest -m live
|
||||
addopts = "-m 'not live'"
|
||||
markers = [
|
||||
"live: opens a real GUI dialog, intended for local manual testing",
|
||||
]
|
||||
|
||||
[tool.ruff.lint]
|
||||
# File modes are unreliable in this environment (/mnt/anbau mounts force 0777),
|
||||
# which makes EXE002 report every file locally. Ignore it deliberately instead of
|
||||
# relying on a personal global config that CI does not have.
|
||||
ignore = ["EXE002"]
|
||||
|
||||
[build-system]
|
||||
requires = ["uv_build>=0.12.20,<0.13.0"]
|
||||
build-backend = "uv_build"
|
||||
|
||||
Executable → Regular
+1
-1
@@ -27,7 +27,7 @@ class LoggingSettings(BaseSettingsConfig):
|
||||
LOG_FILE_PATH: str = "logs/monitoring_backend.log"
|
||||
LOG_BACKUP_COUNT: int = Field(default=30, ge=0)
|
||||
|
||||
def model_post_init(self, __context: Any) -> None:
|
||||
def model_post_init(self, __context: Any, /) -> None:
|
||||
self.setup_logging()
|
||||
|
||||
def setup_logging(self) -> None:
|
||||
|
||||
+1
-1
@@ -1,8 +1,8 @@
|
||||
|
||||
def readlines(path: str) -> list[str]:
|
||||
with open(path, "r") as fid:
|
||||
lines = fid.readlines()
|
||||
return lines
|
||||
|
||||
|
||||
def read(path: str) -> str:
|
||||
return "".join(readlines(path))
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
"""Make the test suite runnable on machines without Tk system libraries.
|
||||
|
||||
``utils.filedialogs`` imports tkinter at module level, and the CI runner
|
||||
lacks the Tk shared libraries (``import tkinter`` fails on a missing
|
||||
``libtk8.6.so``). Since pytest imports every test module at collection
|
||||
time — including the deselected ``test_filedialogs_live.py`` — a minimal
|
||||
stub is registered in ``sys.modules`` here, before any test module is
|
||||
imported, whenever tkinter is not importable.
|
||||
|
||||
The hermetic tests in ``test_filedialogs.py`` mock every tkinter
|
||||
touchpoint anyway, so the stub is never actually used.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import types
|
||||
|
||||
try:
|
||||
import tkinter # noqa: F401
|
||||
except ImportError:
|
||||
_tk_stub = types.ModuleType("tkinter")
|
||||
_filedialog_stub = types.ModuleType("tkinter.filedialog")
|
||||
|
||||
def _tk_unavailable(*args, **kwargs):
|
||||
raise AssertionError("real tkinter was called without a display")
|
||||
|
||||
_tk_stub.Tk = _tk_unavailable
|
||||
_tk_stub.filedialog = _filedialog_stub
|
||||
_filedialog_stub.askdirectory = _tk_unavailable
|
||||
_filedialog_stub.askopenfilename = _tk_unavailable
|
||||
sys.modules["tkinter"] = _tk_stub
|
||||
sys.modules["tkinter.filedialog"] = _filedialog_stub
|
||||
@@ -1,35 +1,55 @@
|
||||
"""Hermetic tests for ``utils.filedialogs``.
|
||||
|
||||
These tests never open a real dialog: every tkinter touchpoint is mocked,
|
||||
so they run headless and even on machines without Tk system libraries
|
||||
(e.g. the CI runner, where ``import tkinter`` fails on a missing
|
||||
``libtk8.6.so``). ``conftest.py`` registers a tkinter stub in that case,
|
||||
so ``utils.filedialogs`` can be imported at all.
|
||||
|
||||
For tests that open a real dialog, see ``test_filedialogs_live.py``
|
||||
(marker ``live``, excluded from the default run).
|
||||
"""
|
||||
|
||||
from unittest import mock
|
||||
|
||||
from utils.filedialogs import get_dir_path_from_dialog, get_file_path_from_dialog
|
||||
|
||||
|
||||
def test_get_dir_path_from_dialog_returns_selected_path():
|
||||
with mock.patch("tkinter.Tk"), mock.patch(
|
||||
with (
|
||||
mock.patch("tkinter.Tk"),
|
||||
mock.patch(
|
||||
"tkinter.filedialog.askdirectory", return_value="/tmp/somewhere"
|
||||
) as dialog:
|
||||
) as dialog,
|
||||
):
|
||||
result = get_dir_path_from_dialog(initialdir="/tmp", title="Pick")
|
||||
assert result == "/tmp/somewhere"
|
||||
dialog.assert_called_once_with(initialdir="/tmp", title="Pick")
|
||||
|
||||
|
||||
def test_get_dir_path_from_dialog_returns_empty_string_on_cancel():
|
||||
with mock.patch("tkinter.Tk"), mock.patch(
|
||||
"tkinter.filedialog.askdirectory", return_value=""
|
||||
with (
|
||||
mock.patch("tkinter.Tk"),
|
||||
mock.patch("tkinter.filedialog.askdirectory", return_value=""),
|
||||
):
|
||||
assert get_dir_path_from_dialog() == ""
|
||||
|
||||
|
||||
def test_get_file_path_from_dialog_returns_selected_path():
|
||||
with mock.patch("tkinter.Tk"), mock.patch(
|
||||
with (
|
||||
mock.patch("tkinter.Tk"),
|
||||
mock.patch(
|
||||
"tkinter.filedialog.askopenfilename", return_value="/tmp/file.txt"
|
||||
) as dialog:
|
||||
) as dialog,
|
||||
):
|
||||
result = get_file_path_from_dialog(filetypes=[("Text", "*.txt")])
|
||||
assert result == "/tmp/file.txt"
|
||||
dialog.assert_called_once_with(filetypes=[("Text", "*.txt")])
|
||||
|
||||
|
||||
def test_get_file_path_from_dialog_returns_empty_string_on_cancel():
|
||||
with mock.patch("tkinter.Tk"), mock.patch(
|
||||
"tkinter.filedialog.askopenfilename", return_value=""
|
||||
with (
|
||||
mock.patch("tkinter.Tk"),
|
||||
mock.patch("tkinter.filedialog.askopenfilename", return_value=""),
|
||||
):
|
||||
assert get_file_path_from_dialog() == ""
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
"""Live dialog tests for ``utils.filedialogs``.
|
||||
|
||||
These tests open a real OS dialog and require a working display.
|
||||
They are excluded from the default (and pipeline) runs by the
|
||||
``live`` marker configured in ``pyproject.toml`` and only run when
|
||||
explicitly requested, e.g.::
|
||||
|
||||
uv run pytest -m live
|
||||
|
||||
Choose a path in the dialog (or cancel it) — either outcome passes,
|
||||
as long as the dialog returns consistently.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from utils.filedialogs import get_dir_path_from_dialog, get_file_path_from_dialog
|
||||
|
||||
|
||||
@pytest.mark.live
|
||||
def test_get_dir_path_from_dialog_live():
|
||||
result = get_dir_path_from_dialog(title="Test: pick a directory")
|
||||
assert result == "" or os.path.isdir(result)
|
||||
|
||||
|
||||
@pytest.mark.live
|
||||
def test_get_file_path_from_dialog_live():
|
||||
result = get_file_path_from_dialog(title="Test: pick a file")
|
||||
assert result == "" or os.path.isfile(result)
|
||||
Executable → Regular
+27
-10
@@ -16,7 +16,7 @@ SECONDS_PER_DAY = 24 * 60 * 60
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def restore_root_logger() -> Generator[None, None, None]:
|
||||
def restore_root_logger() -> Generator[None]:
|
||||
root = logging.getLogger()
|
||||
old_handlers = root.handlers[:]
|
||||
old_level = root.level
|
||||
@@ -45,7 +45,9 @@ def test_invalid_log_level_falls_back_to_info(restore_root_logger: None) -> None
|
||||
LoggingSettings(LOG_LEVEL="NOT_A_LEVEL", LOG_TO_FILE=False)
|
||||
|
||||
assert logging.getLogger().level == logging.INFO
|
||||
log_error.assert_called_once_with("Invalid log level %r; falling back to INFO.", "NOT_A_LEVEL")
|
||||
log_error.assert_called_once_with(
|
||||
"Invalid log level %r; falling back to INFO.", "NOT_A_LEVEL"
|
||||
)
|
||||
|
||||
|
||||
def test_configures_console_handler_only(restore_root_logger: None) -> None:
|
||||
@@ -66,7 +68,9 @@ def test_configures_file_handler_and_creates_parent_dir(
|
||||
|
||||
handlers = logging.getLogger().handlers
|
||||
assert any(type(handler) is logging.StreamHandler for handler in handlers)
|
||||
file_handlers = [handler for handler in handlers if isinstance(handler, logging.FileHandler)]
|
||||
file_handlers = [
|
||||
handler for handler in handlers if isinstance(handler, logging.FileHandler)
|
||||
]
|
||||
assert len(file_handlers) == 1
|
||||
assert log_file.parent.exists()
|
||||
assert isinstance(file_handlers[0], TimedRotatingFileHandler)
|
||||
@@ -77,12 +81,16 @@ def test_configures_file_handler_and_creates_parent_dir(
|
||||
assert file_handlers[0].encoding.lower() == "utf-8"
|
||||
|
||||
|
||||
def test_configures_custom_log_backup_count(tmp_path: Path, restore_root_logger: None) -> None:
|
||||
def test_configures_custom_log_backup_count(
|
||||
tmp_path: Path, restore_root_logger: None
|
||||
) -> None:
|
||||
log_file = tmp_path / "app.log"
|
||||
LoggingSettings(LOG_TO_FILE=True, LOG_FILE_PATH=str(log_file), LOG_BACKUP_COUNT=7)
|
||||
|
||||
file_handler = next(
|
||||
handler for handler in logging.getLogger().handlers if isinstance(handler, TimedRotatingFileHandler)
|
||||
handler
|
||||
for handler in logging.getLogger().handlers
|
||||
if isinstance(handler, TimedRotatingFileHandler)
|
||||
)
|
||||
|
||||
assert file_handler.backupCount == 7
|
||||
@@ -99,7 +107,9 @@ def test_writes_records_to_console_and_file(
|
||||
logging.getLogger("test.logging_output").info("record is written")
|
||||
|
||||
file_handler = next(
|
||||
handler for handler in logging.getLogger().handlers if isinstance(handler, TimedRotatingFileHandler)
|
||||
handler
|
||||
for handler in logging.getLogger().handlers
|
||||
if isinstance(handler, TimedRotatingFileHandler)
|
||||
)
|
||||
file_handler.flush()
|
||||
|
||||
@@ -112,7 +122,9 @@ def test_rotates_active_log_file(tmp_path: Path, restore_root_logger: None) -> N
|
||||
LoggingSettings(LOG_FILE_PATH=str(log_file))
|
||||
logger = logging.getLogger("test.logging_rotation")
|
||||
file_handler = next(
|
||||
handler for handler in logging.getLogger().handlers if isinstance(handler, TimedRotatingFileHandler)
|
||||
handler
|
||||
for handler in logging.getLogger().handlers
|
||||
if isinstance(handler, TimedRotatingFileHandler)
|
||||
)
|
||||
|
||||
logger.info("before rollover")
|
||||
@@ -137,7 +149,9 @@ def test_removes_old_rotated_files_after_backup_limit(
|
||||
LoggingSettings(LOG_FILE_PATH=str(log_file), LOG_BACKUP_COUNT=2)
|
||||
logger = logging.getLogger("test.logging_retention")
|
||||
file_handler = next(
|
||||
handler for handler in logging.getLogger().handlers if isinstance(handler, TimedRotatingFileHandler)
|
||||
handler
|
||||
for handler in logging.getLogger().handlers
|
||||
if isinstance(handler, TimedRotatingFileHandler)
|
||||
)
|
||||
base_timestamp = int(time.time())
|
||||
|
||||
@@ -146,7 +160,8 @@ def test_removes_old_rotated_files_after_backup_limit(
|
||||
file_handler.flush()
|
||||
rollover_timestamp = base_timestamp + day * SECONDS_PER_DAY
|
||||
file_handler.rolloverAt = rollover_timestamp + SECONDS_PER_DAY
|
||||
monkeypatch.setattr(time, "time", lambda: rollover_timestamp)
|
||||
# Bind the loop variable via a default argument to avoid late binding (B023).
|
||||
monkeypatch.setattr(time, "time", lambda now=rollover_timestamp: now)
|
||||
file_handler.doRollover()
|
||||
|
||||
assert len(list(tmp_path.glob("app.*.log"))) == 2
|
||||
@@ -162,7 +177,9 @@ def test_setup_logging_is_idempotent(tmp_path: Path, restore_root_logger: None)
|
||||
settings = LoggingSettings(LOG_TO_FILE=True, LOG_FILE_PATH=str(log_file))
|
||||
|
||||
first_file_handler = next(
|
||||
handler for handler in logging.getLogger().handlers if isinstance(handler, logging.FileHandler)
|
||||
handler
|
||||
for handler in logging.getLogger().handlers
|
||||
if isinstance(handler, logging.FileHandler)
|
||||
)
|
||||
first_handler_count = len(logging.getLogger().handlers)
|
||||
|
||||
|
||||
+2
-1
@@ -1,4 +1,3 @@
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from utils.misc import read, readlines
|
||||
@@ -12,6 +11,7 @@ with a second line
|
||||
and a third line
|
||||
"""
|
||||
|
||||
|
||||
def test_readlines_str():
|
||||
result = readlines(TEST_FILE_PATH)
|
||||
assert result
|
||||
@@ -19,6 +19,7 @@ def test_readlines_str():
|
||||
assert all(isinstance(element, str) for element in result)
|
||||
assert result[1].strip() == "with a second line"
|
||||
|
||||
|
||||
def test_path_str():
|
||||
result = read(TEST_FILE_PATH)
|
||||
assert result
|
||||
|
||||
@@ -11,6 +11,28 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0", size = 13427, upload-time = "2026-07-23T20:16:12.938Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "black"
|
||||
version = "26.5.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
{ name = "mypy-extensions" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pathspec" },
|
||||
{ name = "platformdirs" },
|
||||
{ name = "pytokens" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c0/37/5628dd55bf2b34257fc7603f0fe97c40e3aaf24265f416a9c85c95ca1436/black-26.5.1.tar.gz", hash = "sha256:dd321f668053961824bcc1be1cc1df748b2d7e4fa28086b08331e577b0100a73", size = 679439, upload-time = "2026-05-18T16:53:36.107Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/16/a8da8eb208c51c7f4ce74609a45d0dcc6d8a2141e45e81ee5289d1bb0d59/black-26.5.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e88976690a64b0af98312ca958415849cb42423423c5f2ee74af4b49a97a2168", size = 2004800, upload-time = "2026-05-18T17:05:38.182Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/8a/a479296a19e383b70a725882a6cf3d786540601ff03cabbaaf1cce864c5a/black-26.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32d5ea7f6c8bdfa6e648326ebca1f02b0764e2a029edc6f8dce2627e19d468c3", size = 1815576, upload-time = "2026-05-18T17:05:40.309Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/6b/cfaf3d39f25132c156a068f6b805576c9103a84086019507c70e1911ee7d/black-26.5.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ea8d16dc41655aa113cd64665e7219446cd7e4ff2248d7178eaa905190c86b18", size = 1877927, upload-time = "2026-05-18T17:05:42.463Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/76/302e313964bcff7e28df329d39f84f5270095730d85ff0acc260610a0d82/black-26.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:577f21094ea469ef92ec1adaf2c9441a226d2144d01a5be2fa823cecf6543e50", size = 1511860, upload-time = "2026-05-18T17:05:43.943Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/4e/a3827e35e0e567f9f9ee59e2a0ab979267dca98718f25547ca8c6733afd4/black-26.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:ed1a20af114c301a0269bf01163d51dbef72737fd65f850001e7cbe7f3c7abae", size = 1316632, upload-time = "2026-05-18T17:05:45.521Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/51/f975cae76d44274cc2868dc9040ac5d58d464784610234455b4e7b19c6ef/black-26.5.1-py3-none-any.whl", hash = "sha256:4ed7f7da04046d2e488437170797d3b4a4ad83906683bcb7dfc68b673bbce5e2", size = 213693, upload-time = "2026-05-18T16:53:33.964Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2026.7.22"
|
||||
@@ -347,6 +369,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/3d/1087453384dbde46a8c7f9356eead2c58be8a7bf156bca40243377c85715/more_itertools-11.1.0-py3-none-any.whl", hash = "sha256:4b65538ae22f6fed0ce4874efd317463a7489796a0939fa66824dd542125a192", size = 72226, upload-time = "2026-05-22T14:14:28.824Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mypy-extensions"
|
||||
version = "1.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" }
|
||||
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" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nh3"
|
||||
version = "0.3.7"
|
||||
@@ -390,6 +421,24 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pathspec"
|
||||
version = "1.1.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "4.12.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/17/c8/721b3855fe457da514fe249247d404b9b39c5d16532278f70ebaa6acf18b/platformdirs-4.12.2.tar.gz", hash = "sha256:eab5f70271a490ef74618bb314fbb86e3c7e82fa3b9c922c2ea0e0a1a155d329", size = 59353, upload-time = "2026-09-29T18:27:56.009Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/89/446044f33aba0348d35e433f56d12206d010a5281a1df54054d4cfb82388/platformdirs-4.12.2-py3-none-any.whl", hash = "sha256:29dbf06d96c500bc6bdbce75fb0a14d63279c93b1842f97e72a135b33e856983", size = 32457, upload-time = "2026-09-29T18:27:54.43Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.6.0"
|
||||
@@ -512,6 +561,25 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/60/d1/38f3a3405989a89ac18390803e70c6ad7c7760da4f9b83cbeca0c44a0c72/python_dotenv-1.2.4-py3-none-any.whl", hash = "sha256:42269a8a5b3fd54ffa6f3d84b18abed50064717576b4ecf03dc4a55d8aa04fdc", size = 23266, upload-time = "2026-10-01T05:36:08.633Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytokens"
|
||||
version = "0.4.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b6/34/b4e015b99031667a7b960f888889c5bd34ef585c85e1cb56a594b92836ac/pytokens-0.4.1.tar.gz", hash = "sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a", size = 23015, upload-time = "2026-01-30T01:03:45.924Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/a7/b470f672e6fc5fee0a01d9e75005a0e617e162381974213a945fcd274843/pytokens-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4a14d5f5fc78ce85e426aa159489e2d5961acf0e47575e08f35584009178e321", size = 160821, upload-time = "2026-01-30T01:03:19.684Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/98/e83a36fe8d170c911f864bfded690d2542bfcfacb9c649d11a9e6eb9dc41/pytokens-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f50fd18543be72da51dd505e2ed20d2228c74e0464e4262e4899797803d7fa", size = 254263, upload-time = "2026-01-30T01:03:20.834Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/95/70d7041273890f9f97a24234c00b746e8da86df462620194cef1d411ddeb/pytokens-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc74c035f9bfca0255c1af77ddd2d6ae8419012805453e4b0e7513e17904545d", size = 268071, upload-time = "2026-01-30T01:03:21.888Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/79/76e6d09ae19c99404656d7db9c35dfd20f2086f3eb6ecb496b5b31163bad/pytokens-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f66a6bbe741bd431f6d741e617e0f39ec7257ca1f89089593479347cc4d13324", size = 271716, upload-time = "2026-01-30T01:03:23.633Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/37/482e55fa1602e0a7ff012661d8c946bafdc05e480ea5a32f4f7e336d4aa9/pytokens-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:b35d7e5ad269804f6697727702da3c517bb8a5228afa450ab0fa787732055fc9", size = 104539, upload-time = "2026-01-30T01:03:24.788Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/e8/20e7db907c23f3d63b0be3b8a4fd1927f6da2395f5bcc7f72242bb963dfe/pytokens-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8fcb9ba3709ff77e77f1c7022ff11d13553f3c30299a9fe246a166903e9091eb", size = 168474, upload-time = "2026-01-30T01:03:26.428Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/81/88a95ee9fafdd8f5f3452107748fd04c24930d500b9aba9738f3ade642cc/pytokens-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79fc6b8699564e1f9b521582c35435f1bd32dd06822322ec44afdeba666d8cb3", size = 290473, upload-time = "2026-01-30T01:03:27.415Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/35/3aa899645e29b6375b4aed9f8d21df219e7c958c4c186b465e42ee0a06bf/pytokens-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d31b97b3de0f61571a124a00ffe9a81fb9939146c122c11060725bd5aea79975", size = 303485, upload-time = "2026-01-30T01:03:28.558Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/a0/07907b6ff512674d9b201859f7d212298c44933633c946703a20c25e9d81/pytokens-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:967cf6e3fd4adf7de8fc73cd3043754ae79c36475c1c11d514fc72cf5490094a", size = 306698, upload-time = "2026-01-30T01:03:29.653Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/2a/cbbf9250020a4a8dd53ba83a46c097b69e5eb49dd14e708f496f548c6612/pytokens-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:584c80c24b078eec1e227079d56dc22ff755e0ba8654d8383b2c549107528918", size = 116287, upload-time = "2026-01-30T01:03:30.912Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/78/397db326746f0a342855b81216ae1f0a32965deccfd7c830a2dbc66d2483/pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de", size = 13729, upload-time = "2026-01-30T01:03:45.029Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pywin32-ctypes"
|
||||
version = "0.2.3"
|
||||
@@ -584,6 +652,31 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.16.10"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c4/49/23802c45f093eb14bde54b141d2b2f058edfa63a06db7beed047308cc08f/ruff-0.16.10.tar.gz", hash = "sha256:eff4728c4eaae93f0955cd264d24b2ab348e74bf59986ccf282ba6dc16b3b017", size = 4958724, upload-time = "2026-10-01T18:03:21.697Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/21/ebce22e1d90cdb2cd691026b9c6e9bec6499f0b396089481755b5d49efff/ruff-0.16.10-py3-none-linux_armv6l.whl", hash = "sha256:488b0fe3f3574210e5cf80d9f59b9e3ab17a127a8155de3f307b392589cfb511", size = 10095557, upload-time = "2026-10-01T18:02:36.072Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/98/a54de85876a8b2612bfa0d84c7b9abfb39c6a3354aee7800b09c1649b9e3/ruff-0.16.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e748ff95c934c4e978783b8e687bc174e7bd84e8ad24e3243e1ecfcda5e0282d", size = 10340577, upload-time = "2026-10-01T18:02:39.258Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/16/1a5a4a2657effe4806110f2b907313802f1367fcdbb29e8122766e407fab/ruff-0.16.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3031a4a2e8e7b8a46f70be45f198c35a11ece509a94b80334d8d397a33c67550", size = 9774282, upload-time = "2026-10-01T18:02:41.79Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/fb/470085af734da396e68cd80fb0a3e7c109a459716ae59588c0f6fab8a17d/ruff-0.16.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:494401c86df4c4c25f69b9419605d944467ee98c42fb6ad405ef4fa40b8fb67d", size = 9920895, upload-time = "2026-10-01T18:02:44.458Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/de/f10cffe4f88a37ea6615ec460f01bf76f0bc1477737a35d9ee61a630a78b/ruff-0.16.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d203abc0ff2b773ee33d00ab8df0bb67046fbc7c07b119332f08b9b345cf8221", size = 9902707, upload-time = "2026-10-01T18:02:47.041Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/44/3fdcedf83ae60ef837dd239170e480dce606a9142cfa2db911afdf855fd9/ruff-0.16.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd83d1235a5258d318477bc5b576303974cbdef5df0c01a1bff14efcc23bd12a", size = 10619287, upload-time = "2026-10-01T18:02:49.485Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/62/02e76a5574002153618eafbb70e159468addc72a5d2c488e65cbb4639d2d/ruff-0.16.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc2610fb269fa56dd8a68669ae470fa6272902668c0fc2ebc3aa112b2633d5b8", size = 11339942, upload-time = "2026-10-01T18:02:52.008Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/c4/cde27d47ad8d4126c587e608c67c46e7feac11763f606d261a1bc8a489e6/ruff-0.16.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e70175e29cc94c26ea296c80e470180b744b7419026898e58f520c6ab32578e", size = 10934316, upload-time = "2026-10-01T18:02:54.811Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/03/17234145f302a645a123e8c3bb2411ecf4669fbc350de3b0d803230a1729/ruff-0.16.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f33f43a864a8483eebd160e713336c8bab02c934feaff0a33cf5ccb41546d09a", size = 10387968, upload-time = "2026-10-01T18:02:57.497Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/29/2493af60240ee7644b38a4b821f5f9c3b5a4fa3178770fe1d0c685217395/ruff-0.16.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:1dfc6f0088149fb6a362c1c446bcbb3fd2157b3852fe2fa68409276eab9ad9b3", size = 10537906, upload-time = "2026-10-01T18:03:00.006Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/9a/f56b28f3b143bb9e518c34fb89ab191b626bca8b70dbf8af0d2e2d473572/ruff-0.16.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6553498afc35f580f036030795810b9e6bcea31604b0fd9e8d352795473042e3", size = 10012938, upload-time = "2026-10-01T18:03:03.006Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/c4/fca37362848ea4d4d80712df13632e645e7c7cfb1bdedf140699ac7a090b/ruff-0.16.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a3b8471dea115d37f123882be852bed13403746d3a76c11de4a19ec5f9ff5a03", size = 9897945, upload-time = "2026-10-01T18:03:05.818Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/c7/e0bc57664d6e0c61fd22f620af260fe9662d7e1ddb320ea7f160e76ef165/ruff-0.16.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:92e59a70bcbd9d3a5483656da906ec28edfdacfce00afd99edb8b4e9d15644be", size = 10333134, upload-time = "2026-10-01T18:03:08.25Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/21/aa/5c9f3b68737c0e4a33a1db5dfab44f7b786d96ca91a7233112ddab1e6dc2/ruff-0.16.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7ae7375f803b5520dc9f546bed7e3a0acb70b91812e9bb4b19927de22f25b77d", size = 10741702, upload-time = "2026-10-01T18:03:10.775Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/fa/0f9c2020dc316c53d983be011720b8157cc052b97e3f4dfa7db6d0f880a6/ruff-0.16.10-py3-none-win32.whl", hash = "sha256:2a12e01cb9156c10c466f63b46eaae5ecea28dfbd21b5836353ae498e7d1349a", size = 10139176, upload-time = "2026-10-01T18:03:13.267Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/29/cfb0df9448d4d4ad48c2de029ada9ebd71baa6da983a6c77ee6c6cd0fe82/ruff-0.16.10-py3-none-win_amd64.whl", hash = "sha256:97f2015c92aa97105b0eab19eb5d224884399281cfc5da86a92db4ab5e7fb2ca", size = 10584734, upload-time = "2026-10-01T18:03:16.006Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/05/c16957eb287c3fc062e032619a25868d93d408a844b725bcf514f7a378ff/ruff-0.16.10-py3-none-win_arm64.whl", hash = "sha256:25a65fe998c4e6861ec079ada5826a2fc605e6cbccbe9dcd7fac1f54e791621b", size = 10366440, upload-time = "2026-10-01T18:03:19.04Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "secretstorage"
|
||||
version = "3.5.0"
|
||||
@@ -661,9 +754,11 @@ name = "utils"
|
||||
version = "0.1.1"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "black" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "pydantic-settings" },
|
||||
{ name = "pytest" },
|
||||
{ name = "ruff" },
|
||||
{ name = "tk" },
|
||||
{ name = "twine" },
|
||||
{ name = "uvicorn" },
|
||||
@@ -671,9 +766,11 @@ dependencies = [
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "black", specifier = ">=26.5.1" },
|
||||
{ name = "pydantic", specifier = ">=2.13.5" },
|
||||
{ name = "pydantic-settings", specifier = ">=2.15.0" },
|
||||
{ name = "pytest", specifier = ">=9.1.1" },
|
||||
{ name = "ruff", specifier = ">=0.16.10" },
|
||||
{ name = "tk", specifier = ">=0.1.0" },
|
||||
{ name = "twine", specifier = ">=7.0.0" },
|
||||
{ name = "uvicorn", specifier = ">=0.54.0" },
|
||||
|
||||
Reference in New Issue
Block a user