Publish workflow #3
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
name: Tests
|
||||||
|
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test_and_lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.14"
|
||||||
|
- name: Install uv
|
||||||
|
run: pip install uv
|
||||||
|
- name: Install dependencies
|
||||||
|
run: uv sync --frozen
|
||||||
|
- name: Run pytest
|
||||||
|
run: uv run pytest .
|
||||||
|
- name: Check formatting (black)
|
||||||
|
run: uv run black --check . --extend-exclude .venv
|
||||||
|
- name: Lint (ruff)
|
||||||
|
run: uv run ruff check .
|
||||||
@@ -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/*
|
||||||
+22
-2
@@ -1,14 +1,19 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "utils"
|
name = "utils"
|
||||||
version = "0.1.1"
|
version = "0.1.2"
|
||||||
description = "This is a test version bump with only minor improvements"
|
description = "Some hopefully useful utilities to use in everyday coding"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
authors = [
|
authors = [
|
||||||
{ name = "Mathias Schneider", email = "mathias.schneider@d-fine.com" }
|
{ name = "Mathias Schneider", email = "mathias.schneider@d-fine.com" }
|
||||||
]
|
]
|
||||||
requires-python = ">=3.14"
|
requires-python = ">=3.14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"black>=26.5.1",
|
||||||
"pydantic>=2.13.5",
|
"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",
|
"twine>=7.0.0",
|
||||||
"uvicorn>=0.54.0",
|
"uvicorn>=0.54.0",
|
||||||
]
|
]
|
||||||
@@ -16,6 +21,21 @@ dependencies = [
|
|||||||
[project.scripts]
|
[project.scripts]
|
||||||
utils = "utils:main"
|
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]
|
[build-system]
|
||||||
requires = ["uv_build>=0.12.20,<0.13.0"]
|
requires = ["uv_build>=0.12.20,<0.13.0"]
|
||||||
build-backend = "uv_build"
|
build-backend = "uv_build"
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import tkinter
|
||||||
|
import tkinter.filedialog
|
||||||
|
|
||||||
|
|
||||||
|
def get_dir_path_from_dialog(**kwargs):
|
||||||
|
"""Open a native directory picker and return the selected path.
|
||||||
|
|
||||||
|
All ``kwargs`` are passed through unchanged to
|
||||||
|
:func:`tkinter.filedialog.askdirectory`. The most useful ones:
|
||||||
|
|
||||||
|
- ``initialdir`` (str): directory the dialog opens in.
|
||||||
|
- ``title`` (str): title of the dialog window.
|
||||||
|
- ``mustexist`` (bool, default ``True``): only return existing directories.
|
||||||
|
|
||||||
|
Returns an empty string if the user cancels the dialog.
|
||||||
|
"""
|
||||||
|
root = tkinter.Tk()
|
||||||
|
root.withdraw()
|
||||||
|
try:
|
||||||
|
return tkinter.filedialog.askdirectory(**kwargs)
|
||||||
|
finally:
|
||||||
|
root.destroy()
|
||||||
|
|
||||||
|
|
||||||
|
def get_file_path_from_dialog(**kwargs):
|
||||||
|
"""Open a native file picker and return the selected file path.
|
||||||
|
|
||||||
|
All ``kwargs`` are passed through unchanged to
|
||||||
|
:func:`tkinter.filedialog.askopenfilename`. The most useful ones:
|
||||||
|
|
||||||
|
- ``initialdir`` (str): directory the dialog opens in.
|
||||||
|
- ``initialfile`` (str): file pre-selected in the dialog.
|
||||||
|
- ``filetypes`` (list of (label, pattern) tuples): restrict which files
|
||||||
|
are shown, e.g. ``[("Text files", "*.txt"), ("All files", "*")]``.
|
||||||
|
- ``title`` (str): title of the dialog window.
|
||||||
|
|
||||||
|
Returns an empty string if the user cancels the dialog.
|
||||||
|
"""
|
||||||
|
root = tkinter.Tk()
|
||||||
|
root.withdraw()
|
||||||
|
try:
|
||||||
|
return tkinter.filedialog.askopenfilename(**kwargs)
|
||||||
|
finally:
|
||||||
|
root.destroy()
|
||||||
Executable → Regular
+1
-1
@@ -27,7 +27,7 @@ class LoggingSettings(BaseSettingsConfig):
|
|||||||
LOG_FILE_PATH: str = "logs/monitoring_backend.log"
|
LOG_FILE_PATH: str = "logs/monitoring_backend.log"
|
||||||
LOG_BACKUP_COUNT: int = Field(default=30, ge=0)
|
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()
|
self.setup_logging()
|
||||||
|
|
||||||
def setup_logging(self) -> None:
|
def setup_logging(self) -> None:
|
||||||
|
|||||||
@@ -0,0 +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
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
This is a test file
|
||||||
|
with a second line
|
||||||
|
and a third line
|
||||||
@@ -0,0 +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(
|
||||||
|
"tkinter.filedialog.askdirectory", return_value="/tmp/somewhere"
|
||||||
|
) 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=""),
|
||||||
|
):
|
||||||
|
assert get_dir_path_from_dialog() == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_file_path_from_dialog_returns_selected_path():
|
||||||
|
with (
|
||||||
|
mock.patch("tkinter.Tk"),
|
||||||
|
mock.patch(
|
||||||
|
"tkinter.filedialog.askopenfilename", return_value="/tmp/file.txt"
|
||||||
|
) 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=""),
|
||||||
|
):
|
||||||
|
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
+30
-13
@@ -7,16 +7,16 @@ from pathlib import Path
|
|||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from pydantic import ValidationError
|
||||||
from uvicorn.logging import DefaultFormatter
|
from uvicorn.logging import DefaultFormatter
|
||||||
|
|
||||||
from dataflex_backend_common.logging_config import LoggingSettings
|
from utils.logging_config import LoggingSettings
|
||||||
from pydantic import ValidationError
|
|
||||||
|
|
||||||
SECONDS_PER_DAY = 24 * 60 * 60
|
SECONDS_PER_DAY = 24 * 60 * 60
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def restore_root_logger() -> Generator[None, None, None]:
|
def restore_root_logger() -> Generator[None]:
|
||||||
root = logging.getLogger()
|
root = logging.getLogger()
|
||||||
old_handlers = root.handlers[:]
|
old_handlers = root.handlers[:]
|
||||||
old_level = root.level
|
old_level = root.level
|
||||||
@@ -41,11 +41,13 @@ def test_sets_valid_log_level(restore_root_logger: None) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_invalid_log_level_falls_back_to_info(restore_root_logger: None) -> None:
|
def test_invalid_log_level_falls_back_to_info(restore_root_logger: None) -> None:
|
||||||
with patch("dataflex_backend_common.logging_config.logger.error") as log_error:
|
with patch("utils.logging_config.logger.error") as log_error:
|
||||||
LoggingSettings(LOG_LEVEL="NOT_A_LEVEL", LOG_TO_FILE=False)
|
LoggingSettings(LOG_LEVEL="NOT_A_LEVEL", LOG_TO_FILE=False)
|
||||||
|
|
||||||
assert logging.getLogger().level == logging.INFO
|
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:
|
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
|
handlers = logging.getLogger().handlers
|
||||||
assert any(type(handler) is logging.StreamHandler for handler in 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 len(file_handlers) == 1
|
||||||
assert log_file.parent.exists()
|
assert log_file.parent.exists()
|
||||||
assert isinstance(file_handlers[0], TimedRotatingFileHandler)
|
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"
|
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"
|
log_file = tmp_path / "app.log"
|
||||||
LoggingSettings(LOG_TO_FILE=True, LOG_FILE_PATH=str(log_file), LOG_BACKUP_COUNT=7)
|
LoggingSettings(LOG_TO_FILE=True, LOG_FILE_PATH=str(log_file), LOG_BACKUP_COUNT=7)
|
||||||
|
|
||||||
file_handler = next(
|
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
|
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")
|
logging.getLogger("test.logging_output").info("record is written")
|
||||||
|
|
||||||
file_handler = next(
|
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()
|
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))
|
LoggingSettings(LOG_FILE_PATH=str(log_file))
|
||||||
logger = logging.getLogger("test.logging_rotation")
|
logger = logging.getLogger("test.logging_rotation")
|
||||||
file_handler = next(
|
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")
|
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)
|
LoggingSettings(LOG_FILE_PATH=str(log_file), LOG_BACKUP_COUNT=2)
|
||||||
logger = logging.getLogger("test.logging_retention")
|
logger = logging.getLogger("test.logging_retention")
|
||||||
file_handler = next(
|
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())
|
base_timestamp = int(time.time())
|
||||||
|
|
||||||
@@ -146,7 +160,8 @@ def test_removes_old_rotated_files_after_backup_limit(
|
|||||||
file_handler.flush()
|
file_handler.flush()
|
||||||
rollover_timestamp = base_timestamp + day * SECONDS_PER_DAY
|
rollover_timestamp = base_timestamp + day * SECONDS_PER_DAY
|
||||||
file_handler.rolloverAt = rollover_timestamp + 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()
|
file_handler.doRollover()
|
||||||
|
|
||||||
assert len(list(tmp_path.glob("app.*.log"))) == 2
|
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))
|
settings = LoggingSettings(LOG_TO_FILE=True, LOG_FILE_PATH=str(log_file))
|
||||||
|
|
||||||
first_file_handler = next(
|
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)
|
first_handler_count = len(logging.getLogger().handlers)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from utils.misc import read, readlines
|
||||||
|
|
||||||
|
CWD = Path(__file__).parent.resolve()
|
||||||
|
TEST_DATA_DIR = CWD / "data"
|
||||||
|
TEST_FILE_PATH = TEST_DATA_DIR / "text.txt"
|
||||||
|
|
||||||
|
EXPECTED_TEST = """This is a test file
|
||||||
|
with a second line
|
||||||
|
and a third line
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def test_readlines_str():
|
||||||
|
result = readlines(TEST_FILE_PATH)
|
||||||
|
assert result
|
||||||
|
assert isinstance(result, list)
|
||||||
|
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
|
||||||
|
assert isinstance(result, str)
|
||||||
|
assert result == EXPECTED_TEST
|
||||||
@@ -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" },
|
{ 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]]
|
[[package]]
|
||||||
name = "certifi"
|
name = "certifi"
|
||||||
version = "2026.7.22"
|
version = "2026.7.22"
|
||||||
@@ -154,6 +176,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/58/50/6c0d534c5f134586a8e1ba4e330569e32f057e33372ae556463212fb4cd3/click-8.5.0-py3-none-any.whl", hash = "sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360", size = 125251, upload-time = "2026-08-26T13:33:12.928Z" },
|
{ url = "https://files.pythonhosted.org/packages/58/50/6c0d534c5f134586a8e1ba4e330569e32f057e33372ae556463212fb4cd3/click-8.5.0-py3-none-any.whl", hash = "sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360", size = 125251, upload-time = "2026-08-26T13:33:12.928Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorama"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cryptography"
|
name = "cryptography"
|
||||||
version = "50.0.2"
|
version = "50.0.2"
|
||||||
@@ -240,6 +271,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/58/a2/bb081bab032533a855d44de1d56f8e8426114ff1ba5d1f07a438a0a654f8/idna-3.20-py3-none-any.whl", hash = "sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c", size = 69583, upload-time = "2026-09-17T14:11:03.168Z" },
|
{ url = "https://files.pythonhosted.org/packages/58/a2/bb081bab032533a855d44de1d56f8e8426114ff1ba5d1f07a438a0a654f8/idna-3.20-py3-none-any.whl", hash = "sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c", size = 69583, upload-time = "2026-09-17T14:11:03.168Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iniconfig"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jaraco-classes"
|
name = "jaraco-classes"
|
||||||
version = "3.4.0"
|
version = "3.4.0"
|
||||||
@@ -329,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" },
|
{ 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]]
|
[[package]]
|
||||||
name = "nh3"
|
name = "nh3"
|
||||||
version = "0.3.7"
|
version = "0.3.7"
|
||||||
@@ -372,6 +421,33 @@ 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" },
|
{ 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"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pycparser"
|
name = "pycparser"
|
||||||
version = "3.0"
|
version = "3.0"
|
||||||
@@ -437,6 +513,20 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/63/19/67830dda664e6bdf9285ee2e40f355d0d7d6b92aa0c42e8d217bb8d33d36/pydantic_core-2.46.5-cp314-cp314t-win_arm64.whl", hash = "sha256:acf8a67ba51f4ca9ddbd0e6b3000a65ac51ab734661778b3e7ba64d99a710f2f", size = 1989276, upload-time = "2026-08-28T10:00:16.984Z" },
|
{ url = "https://files.pythonhosted.org/packages/63/19/67830dda664e6bdf9285ee2e40f355d0d7d6b92aa0c42e8d217bb8d33d36/pydantic_core-2.46.5-cp314-cp314t-win_arm64.whl", hash = "sha256:acf8a67ba51f4ca9ddbd0e6b3000a65ac51ab734661778b3e7ba64d99a710f2f", size = 1989276, upload-time = "2026-08-28T10:00:16.984Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pydantic-settings"
|
||||||
|
version = "2.15.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "pydantic" },
|
||||||
|
{ name = "python-dotenv" },
|
||||||
|
{ name = "typing-inspection" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/68/ca/31c57507b13119d7d3cfa1576dad2911a4861e3be07b579395f4e9d393f9/pydantic_settings-2.15.0.tar.gz", hash = "sha256:694b793e84f766ba76a90ebdefc01d0a9a045dab0382bee70393da93712ad117", size = 261253, upload-time = "2026-08-07T09:24:57.419Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/30/a4/2bffa9f8e804325a09867f0e9d30795c80ea9f8d62560bd1b6ad6220eb2f/pydantic_settings-2.15.0-py3-none-any.whl", hash = "sha256:0ba092c291c94baceb5eff768aa0d56400a457585bc0175925a5a5510303da42", size = 69413, upload-time = "2026-08-07T09:24:55.839Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pygments"
|
name = "pygments"
|
||||||
version = "2.21.0"
|
version = "2.21.0"
|
||||||
@@ -446,6 +536,50 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" },
|
{ url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest"
|
||||||
|
version = "9.1.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||||
|
{ name = "iniconfig" },
|
||||||
|
{ name = "packaging" },
|
||||||
|
{ name = "pluggy" },
|
||||||
|
{ name = "pygments" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "python-dotenv"
|
||||||
|
version = "1.2.4"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/74/26/2fbeedb218a787a5eea551c7532cac4e009f83d689dd2faa0d0353473f86/python_dotenv-1.2.4.tar.gz", hash = "sha256:f0d53e69935a851c0dcc78f3ab7aaccd8cabef0b92382b576b824212902873c0", size = 60824, upload-time = "2026-10-01T05:36:10Z" }
|
||||||
|
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]]
|
[[package]]
|
||||||
name = "pywin32-ctypes"
|
name = "pywin32-ctypes"
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
@@ -518,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" },
|
{ 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]]
|
[[package]]
|
||||||
name = "secretstorage"
|
name = "secretstorage"
|
||||||
version = "3.5.0"
|
version = "3.5.0"
|
||||||
@@ -531,6 +690,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554, upload-time = "2025-11-23T19:02:51.545Z" },
|
{ url = "https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554, upload-time = "2025-11-23T19:02:51.545Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tk"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/a0/81/742b342fd642e672fbedecde725ba44db44e800dc4c936216c3c6729885a/tk-0.1.0.tar.gz", hash = "sha256:60bc8923d5d35f67f5c6bd93d4f0c49d2048114ec077768f959aef36d4ed97f8", size = 2247, upload-time = "2019-07-08T06:51:58.051Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1e/0b/029cbdb868bb555fed99bf6540fff072d500b3f895873709f25084e85e33/tk-0.1.0-py3-none-any.whl", hash = "sha256:703a69ff0d5ba2bd2f7440582ad10160e4a6561595d33457dc6caa79b9bf4930", size = 3879, upload-time = "2019-07-08T06:51:55.175Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "twine"
|
name = "twine"
|
||||||
version = "7.0.0"
|
version = "7.0.0"
|
||||||
@@ -583,17 +751,27 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utils"
|
name = "utils"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
{ name = "black" },
|
||||||
{ name = "pydantic" },
|
{ name = "pydantic" },
|
||||||
|
{ name = "pydantic-settings" },
|
||||||
|
{ name = "pytest" },
|
||||||
|
{ name = "ruff" },
|
||||||
|
{ name = "tk" },
|
||||||
{ name = "twine" },
|
{ name = "twine" },
|
||||||
{ name = "uvicorn" },
|
{ name = "uvicorn" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
|
{ name = "black", specifier = ">=26.5.1" },
|
||||||
{ name = "pydantic", specifier = ">=2.13.5" },
|
{ 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 = "twine", specifier = ">=7.0.0" },
|
||||||
{ name = "uvicorn", specifier = ">=0.54.0" },
|
{ name = "uvicorn", specifier = ">=0.54.0" },
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user