Compare commits
15
Commits
f28d3c4844
..
v2.3.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bcc81ca4d8 | ||
|
|
ac82f71347 | ||
|
|
24d5ea14d6 | ||
|
|
b8d8176877 | ||
|
|
a78320b434 | ||
|
|
1822d886b1 | ||
|
|
4f58c32dd6 | ||
|
|
88ea7f06ab | ||
|
|
2b5718ba41 | ||
|
|
b6538f9e91 | ||
|
|
b37b2e6e50 | ||
|
|
1babb52d09 | ||
|
|
63ee17d544 | ||
|
|
2e38bd2406 | ||
|
|
dc6f8a3e89 |
@@ -8,3 +8,12 @@ MINIO_SECRET_KEY=minioadmin
|
||||
MINIO_BUCKET=my-bucket
|
||||
MINIO_SECURE=false
|
||||
MINIO_CREATE_BUCKET_IF_MISSING=true
|
||||
|
||||
# Postgres (requires postgres extra)
|
||||
POSTGRES_URI=postgresql://localhost/mydb
|
||||
POSTGRES_TABLE=users
|
||||
POSTGRES_PRIMARY_KEY=id
|
||||
|
||||
# File-backed queue (no optional extra)
|
||||
FILE_QUEUE_PATH=/tmp/python-repositories-queue.jsonl
|
||||
# FILE_QUEUE_MAX_AGE_HOURS=24
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
repos:
|
||||
# General repository hygiene hooks
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.5.0
|
||||
rev: v6.0.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
@@ -38,7 +38,7 @@ repos:
|
||||
|
||||
# Formatting for Markdown, JSON, and YAML with Prettier
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: v3.1.0
|
||||
rev: v4.0.0-alpha.8
|
||||
hooks:
|
||||
- id: prettier
|
||||
files: "\\.(md|json|yaml|yml)$"
|
||||
|
||||
@@ -7,12 +7,45 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2.3.0] - 2026-07-16
|
||||
|
||||
### Summary
|
||||
|
||||
Add QueueRepositoryInterface with memory and file-backed adapters
|
||||
|
||||
### Changed
|
||||
|
||||
- ac82f71 Merge pull request '[minor] Add QueueRepositoryInterface with memory and file-backed adapters' (#62) from cursor/queue-adapters into main
|
||||
- 24d5ea1 Apply Prettier formatting to README and CHANGELOG.
|
||||
- b8d8176 Align changelog section headings with Prettier markdown spacing.
|
||||
- a78320b Move queue helper functions to staticmethods on their adapters.
|
||||
- 1822d88 Apply ruff formatting to queue adapter and tests.
|
||||
- 4f58c32 Add QueueRepositoryInterface with memory and file-backed adapters.
|
||||
- 88ea7f0 Merge pull request 'chore(deps): update dependencies' (#61) from renovate/auto-deps-update into main
|
||||
- 2b5718b chore(deps): update dependencies [automated]
|
||||
|
||||
## [2.2.0] - 2026-07-12
|
||||
|
||||
### Summary
|
||||
|
||||
Add Postgres table adapter with TableRepositoryInterface
|
||||
|
||||
### Changed
|
||||
|
||||
- b37b2e6 Merge pull request '[minor] Add Postgres table adapter with TableRepositoryInterface' (#58) from cursor/postgres-table-adapter into main
|
||||
- 1babb52 Apply ruff formatting to postgres adapter and tests.
|
||||
- 63ee17d Apply prettier formatting to README and CHANGELOG.
|
||||
- 2e38bd2 Fix ruff import ordering in postgres test files.
|
||||
- dc6f8a3 Add Postgres table adapter with TableRepositoryInterface.
|
||||
|
||||
## [2.1.0] - 2026-07-11
|
||||
|
||||
### Summary
|
||||
|
||||
Add runtime-checkable Protocol typing to public interfaces
|
||||
|
||||
### Changed
|
||||
|
||||
- 1cbcc1c Merge pull request '[minor] Add runtime-checkable Protocol typing to public interfaces' (#57) from cursor/protocol-abc-interfaces into main
|
||||
- a0e5c9d Document test organization and add Cursor workflow rules.
|
||||
- 44b15cb Move structural typing tests into interface unit test files.
|
||||
|
||||
@@ -6,11 +6,11 @@ Subclass an adapter in your own repository to add domain-specific methods while
|
||||
|
||||
## Architecture
|
||||
|
||||
| Layer | Responsibility |
|
||||
| ---------------- | ----------------------------------------------------------------- |
|
||||
| **Interfaces** | Abstract contracts for connection, context, and CRUD |
|
||||
| **Adapters** | Technology-specific base classes (`RedisAdapter`, `MinioAdapter`) |
|
||||
| **Your project** | Subclass an adapter and add domain methods |
|
||||
| Layer | Responsibility |
|
||||
| ---------------- | ---------------------------------------------------------------------------------------------------- |
|
||||
| **Interfaces** | Abstract contracts for connection, context, CRUD, and queues |
|
||||
| **Adapters** | Technology-specific base classes (`RedisAdapter`, `MinioAdapter`, `PostgresAdapter`, queue adapters) |
|
||||
| **Your project** | Subclass an adapter and add domain methods |
|
||||
|
||||
Each public interface is a `@runtime_checkable` `Protocol` with `@abstractmethod` members. **Subclass an adapter** when you need connection management and shared behavior — explicit subclasses get runtime instantiation guards and inherited default methods (e.g. `scan_keys`). **Type-annotate against an interface** when you want loose coupling — any object with the right methods satisfies the contract for mypy and `isinstance()` checks, without inheriting from this package.
|
||||
|
||||
@@ -22,14 +22,15 @@ The current API is synchronous. Async repository interfaces and adapters may be
|
||||
|
||||
## Optional dependencies
|
||||
|
||||
Repository **interfaces** import with the base package. **Adapters** require the matching extra; importing an adapter without its extra raises `ImportError` with install instructions.
|
||||
Repository **interfaces** import with the base package. Networked **adapters** (`RedisAdapter`, `MinioAdapter`, `PostgresAdapter`) require the matching extra; importing one without its extra raises `ImportError` with install instructions. Queue adapters (`MemoryQueueAdapter`, `FileBackedQueueAdapter`) need no extra.
|
||||
|
||||
Install with the extras you need:
|
||||
|
||||
```bash
|
||||
uv add python-repositories[redis]
|
||||
uv add python-repositories[minio]
|
||||
uv add python-repositories[redis,minio]
|
||||
uv add python-repositories[postgres]
|
||||
uv add python-repositories[redis,minio,postgres]
|
||||
```
|
||||
|
||||
### Redis (`JsonRepositoryInterface`)
|
||||
@@ -63,16 +64,69 @@ for key in repo.scan_keys("user:*"):
|
||||
| `MINIO_SECURE` | Use HTTPS (`true`/`false`; default: `true`) |
|
||||
| `MINIO_CREATE_BUCKET_IF_MISSING` | Auto-create `MINIO_BUCKET` on connect (`true`/`false`; default: `false`) |
|
||||
|
||||
Copy [`.env.example`](.env.example) to `.env` for local development. `RedisConfig.from_env()` and `MinioConfig.from_env()` load `.env` automatically when resolving configuration from the environment.
|
||||
|
||||
For production, it is recommended to leave `MINIO_CREATE_BUCKET_IF_MISSING` unset so that `connect()` fails fast if the expected bucket is missing. For local development, you will often want `MINIO_SECURE=false` and `MINIO_CREATE_BUCKET_IF_MISSING=true`.
|
||||
|
||||
### Postgres (`TableRepositoryInterface`)
|
||||
|
||||
Requires PostgreSQL 10 or later; tested against PostgreSQL 16 in CI. Tables are owned by your migrations — the adapter verifies the configured table exists on connect.
|
||||
|
||||
| Environment variable | Description |
|
||||
| ---------------------- | --------------------------------------- |
|
||||
| `POSTGRES_URI` | PostgreSQL connection URL |
|
||||
| `POSTGRES_TABLE` | Table name for CRUD operations |
|
||||
| `POSTGRES_PRIMARY_KEY` | Primary key column name (default: `id`) |
|
||||
|
||||
### File-backed queue (`QueueRepositoryInterface`)
|
||||
|
||||
In-process and disk-backed FIFO queues for buffering dict items. No optional extra required.
|
||||
|
||||
| Adapter | Role |
|
||||
| ------------------------ | --------------------------------------------------------------------------------- |
|
||||
| `MemoryQueueAdapter` | Thread-safe in-memory buffer with optional dedup and age eviction |
|
||||
| `FileBackedQueueAdapter` | Mirrors memory to a JSONL file; replays on `connect()`, compacts on dequeue/evict |
|
||||
|
||||
| Environment variable | Description |
|
||||
| -------------------------- | ----------------------------------------- |
|
||||
| `FILE_QUEUE_PATH` | Path to the JSONL queue file |
|
||||
| `FILE_QUEUE_MAX_AGE_HOURS` | Retention window in hours (default: `24`) |
|
||||
|
||||
Dedup keys and the age field name are domain-specific: pass them when constructing `FileQueueConfig` (or as kwargs to `FileQueueConfig.from_env(...)`). Callers choose the file path and item schema.
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
|
||||
from python_repositories import FileBackedQueueAdapter, FileQueueConfig
|
||||
|
||||
config = FileQueueConfig(
|
||||
path=Path("/var/lib/my-service/queue/items.jsonl"),
|
||||
max_age_hours=24,
|
||||
dedup_keys=("id",),
|
||||
age_key="created_at",
|
||||
)
|
||||
with FileBackedQueueAdapter(config=config) as queue:
|
||||
queue.enqueue([{"id": 1, "created_at": "2024-01-01T00:00:00+00:00"}])
|
||||
batch = queue.dequeue_batch(max_items=100)
|
||||
```
|
||||
|
||||
Copy [`.env.example`](.env.example) to `.env` for local development. `RedisConfig.from_env()`, `MinioConfig.from_env()`, `PostgresConfig.from_env()`, and `FileQueueConfig.from_env()` load `.env` automatically when resolving configuration from the environment.
|
||||
|
||||
## Configuration injection
|
||||
|
||||
Adapters accept optional `config` and `client` keyword arguments for explicit setup and testing:
|
||||
Adapters accept optional `config` and `client` keyword arguments for explicit setup and testing. Queue adapters accept `config` (and an optional injected `memory` buffer for `FileBackedQueueAdapter`):
|
||||
|
||||
```python
|
||||
from python_repositories import RedisAdapter, RedisConfig, MinioAdapter, MinioConfig
|
||||
from pathlib import Path
|
||||
|
||||
from python_repositories import (
|
||||
FileBackedQueueAdapter,
|
||||
FileQueueConfig,
|
||||
MinioAdapter,
|
||||
MinioConfig,
|
||||
PostgresAdapter,
|
||||
PostgresConfig,
|
||||
RedisAdapter,
|
||||
RedisConfig,
|
||||
)
|
||||
|
||||
redis = RedisAdapter(config=RedisConfig(uri="redis://localhost:6379"))
|
||||
minio = MinioAdapter(
|
||||
@@ -85,6 +139,20 @@ minio = MinioAdapter(
|
||||
# create_bucket_if_missing=True, # convenient for local dev
|
||||
)
|
||||
)
|
||||
postgres = PostgresAdapter(
|
||||
config=PostgresConfig(
|
||||
uri="postgresql://localhost/mydb",
|
||||
table="users",
|
||||
primary_key="user_id",
|
||||
)
|
||||
)
|
||||
queue = FileBackedQueueAdapter(
|
||||
config=FileQueueConfig(
|
||||
path=Path("/var/lib/my-service/queue/items.jsonl"),
|
||||
dedup_keys=("id",),
|
||||
age_key="created_at",
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
When both `config` and `client` are provided, `connect()` skips client creation (the caller owns the client lifecycle). `config` is required whenever `client` is injected.
|
||||
@@ -97,7 +165,7 @@ from python_repositories import load_dotenv
|
||||
load_dotenv() # optional — from_env() also loads .env by default
|
||||
```
|
||||
|
||||
Calling `RedisAdapter()` or `MinioAdapter()` with no arguments still loads configuration from environment variables (and `.env` if present).
|
||||
Calling `RedisAdapter()`, `MinioAdapter()`, or `PostgresAdapter()` with no arguments still loads configuration from environment variables (and `.env` if present).
|
||||
|
||||
## Quick start
|
||||
|
||||
@@ -126,6 +194,17 @@ with ArtifactObjectRepository() as repo:
|
||||
data = repo.get_artifact("report-1")
|
||||
```
|
||||
|
||||
### Relational rows with Postgres
|
||||
|
||||
```python
|
||||
from python_repositories.examples.user_table_repository import UserTableRepository
|
||||
|
||||
with UserTableRepository() as repo:
|
||||
repo.save_user("alice", {"name": "Alice", "email": "[email protected]"})
|
||||
user = repo.get_user("alice")
|
||||
repo.delete_user("alice")
|
||||
```
|
||||
|
||||
### Subclassing in your own project
|
||||
|
||||
```python
|
||||
@@ -150,12 +229,19 @@ class UserRepository(RedisAdapter):
|
||||
from python_repositories import (
|
||||
ConnectionAwareInterface,
|
||||
ContextAwareInterface,
|
||||
FileBackedQueueAdapter,
|
||||
FileQueueConfig,
|
||||
JsonRepositoryInterface,
|
||||
MemoryQueueAdapter,
|
||||
MinioAdapter,
|
||||
MinioConfig,
|
||||
ObjectRepositoryInterface,
|
||||
PostgresAdapter,
|
||||
PostgresConfig,
|
||||
QueueRepositoryInterface,
|
||||
RedisAdapter,
|
||||
RedisConfig,
|
||||
TableRepositoryInterface,
|
||||
load_dotenv,
|
||||
)
|
||||
```
|
||||
@@ -169,10 +255,11 @@ uv run pytest tests/unit/ -v # fast, no Docker
|
||||
uv run pytest -m "not integration" -v # all non-Docker tests
|
||||
uv run pytest tests/integration/redis/ -v # Redis container only
|
||||
uv run pytest tests/integration/minio/ -v # MinIO container only
|
||||
uv run pytest tests/integration/postgres/ -v # Postgres container only
|
||||
uv run pytest -v # full suite (requires Docker)
|
||||
```
|
||||
|
||||
Integration tests are marked with `@pytest.mark.integration` and require Docker (testcontainers). Backend-specific markers (`needs_redis`, `needs_minio`) let you run only the containers a test module needs. Run unit tests alone for quick local feedback.
|
||||
Integration tests are marked with `@pytest.mark.integration` and require Docker (testcontainers). Backend-specific markers (`needs_redis`, `needs_minio`, `needs_postgres`) let you run only the containers a test module needs. Run unit tests alone for quick local feedback.
|
||||
|
||||
### Test organization
|
||||
|
||||
|
||||
+5
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "python-repositories"
|
||||
version = "2.1.0"
|
||||
version = "2.3.0"
|
||||
description = "Various python repository interfaces exposed as a python package."
|
||||
authors = [
|
||||
{ name = "Brian Bjarke Jensen", email = "[email protected]" }
|
||||
@@ -32,6 +32,9 @@ redis = [
|
||||
minio = [
|
||||
"minio>=7.2.16",
|
||||
]
|
||||
postgres = [
|
||||
"psycopg[binary]>=3.2.0",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
@@ -44,6 +47,7 @@ markers = [
|
||||
"integration: tests requiring Docker containers (deselect with '-m \"not integration\"')",
|
||||
"needs_redis: integration test requiring a Redis container",
|
||||
"needs_minio: integration test requiring a MinIO container",
|
||||
"needs_postgres: integration test requiring a Postgres container",
|
||||
]
|
||||
|
||||
[tool.coverage.run]
|
||||
|
||||
@@ -6,26 +6,43 @@ from typing import TYPE_CHECKING
|
||||
|
||||
# Interfaces are always available; they have no optional backend dependencies.
|
||||
from . import adapters
|
||||
from .config import MinioConfig, RedisConfig, load_dotenv
|
||||
from .config import (
|
||||
FileQueueConfig,
|
||||
MinioConfig,
|
||||
PostgresConfig,
|
||||
RedisConfig,
|
||||
load_dotenv,
|
||||
)
|
||||
from .interfaces import (
|
||||
ConnectionAwareInterface,
|
||||
ContextAwareInterface,
|
||||
JsonRepositoryInterface,
|
||||
ObjectRepositoryInterface,
|
||||
QueueRepositoryInterface,
|
||||
TableRepositoryInterface,
|
||||
)
|
||||
|
||||
# Adapters are imported only for static type checkers; runtime loading is delegated below.
|
||||
if TYPE_CHECKING:
|
||||
from .adapters.file_backed_queue_adapter import (
|
||||
FileBackedQueueAdapter as FileBackedQueueAdapter,
|
||||
)
|
||||
from .adapters.memory_queue_adapter import MemoryQueueAdapter as MemoryQueueAdapter
|
||||
from .adapters.minio_adapter import MinioAdapter as MinioAdapter
|
||||
from .adapters.postgres_adapter import PostgresAdapter as PostgresAdapter
|
||||
from .adapters.redis_adapter import RedisAdapter as RedisAdapter
|
||||
|
||||
__all__ = [
|
||||
"ConnectionAwareInterface",
|
||||
"ContextAwareInterface",
|
||||
"FileQueueConfig",
|
||||
"JsonRepositoryInterface",
|
||||
"MinioConfig",
|
||||
"ObjectRepositoryInterface",
|
||||
"PostgresConfig",
|
||||
"QueueRepositoryInterface",
|
||||
"RedisConfig",
|
||||
"TableRepositoryInterface",
|
||||
"load_dotenv",
|
||||
*adapters.__all__,
|
||||
]
|
||||
|
||||
@@ -11,7 +11,10 @@ from typing import TYPE_CHECKING
|
||||
|
||||
# Adapters are imported only for static type checkers; runtime loading is deferred below.
|
||||
if TYPE_CHECKING:
|
||||
from .file_backed_queue_adapter import FileBackedQueueAdapter
|
||||
from .memory_queue_adapter import MemoryQueueAdapter
|
||||
from .minio_adapter import MinioAdapter
|
||||
from .postgres_adapter import PostgresAdapter
|
||||
from .redis_adapter import RedisAdapter
|
||||
|
||||
# Map public adapter names to their defining module and class.
|
||||
@@ -20,11 +23,20 @@ if TYPE_CHECKING:
|
||||
_LAZY_EXPORTS = {
|
||||
"RedisAdapter": (".redis_adapter", "RedisAdapter"),
|
||||
"MinioAdapter": (".minio_adapter", "MinioAdapter"),
|
||||
"PostgresAdapter": (".postgres_adapter", "PostgresAdapter"),
|
||||
"MemoryQueueAdapter": (".memory_queue_adapter", "MemoryQueueAdapter"),
|
||||
"FileBackedQueueAdapter": (
|
||||
".file_backed_queue_adapter",
|
||||
"FileBackedQueueAdapter",
|
||||
),
|
||||
}
|
||||
|
||||
__all__ = [
|
||||
"RedisAdapter",
|
||||
"MinioAdapter",
|
||||
"PostgresAdapter",
|
||||
"MemoryQueueAdapter",
|
||||
"FileBackedQueueAdapter",
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
"""File-backed queue adapter: in-memory hot buffer mirrored to JSONL on disk."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import UTC, datetime, timedelta
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import threading
|
||||
from typing import Any, Self, TextIO
|
||||
|
||||
import structlog
|
||||
|
||||
from python_repositories.adapters.memory_queue_adapter import MemoryQueueAdapter
|
||||
from python_repositories.config.file_queue_config import FileQueueConfig
|
||||
from python_repositories.interfaces.queue_repository_interface import (
|
||||
QueueRepositoryInterface,
|
||||
)
|
||||
|
||||
|
||||
class FileBackedQueueAdapter(QueueRepositoryInterface):
|
||||
"""Queue that mirrors an in-memory buffer to an append-friendly JSONL file.
|
||||
|
||||
Does not extend ``ConnectionAwareAdapter``. ``connect()`` replays the JSONL
|
||||
file into memory; ``disconnect()`` flushes any open handle.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
config: FileQueueConfig | None = None,
|
||||
memory: MemoryQueueAdapter | None = None,
|
||||
) -> None:
|
||||
if config is None:
|
||||
config = FileQueueConfig.from_env()
|
||||
if memory is None:
|
||||
memory = MemoryQueueAdapter(
|
||||
dedup_keys=config.dedup_keys,
|
||||
age_key=config.age_key,
|
||||
)
|
||||
self._config = config
|
||||
self._memory = memory
|
||||
self._lock = threading.RLock()
|
||||
self._connected = False
|
||||
self._append_handle: TextIO | None = None
|
||||
self.logger = structlog.get_logger(self.__class__.__name__)
|
||||
|
||||
@staticmethod
|
||||
def _json_default(value: object) -> str:
|
||||
if isinstance(value, datetime):
|
||||
return value.isoformat()
|
||||
raise TypeError(f"Object of type {type(value)!r} is not JSON serializable")
|
||||
|
||||
def __enter__(self) -> Self:
|
||||
self.connect()
|
||||
return self
|
||||
|
||||
def __exit__(
|
||||
self, exc_type: type | None, exc_val: object | None, exc_tb: object | None
|
||||
) -> None:
|
||||
del exc_type, exc_val, exc_tb
|
||||
self.disconnect()
|
||||
|
||||
def connect(self) -> None:
|
||||
"""Create parent directories and replay the JSONL file into memory."""
|
||||
with self._lock:
|
||||
if self._connected:
|
||||
return
|
||||
path = self._config.path
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
self._memory.clear()
|
||||
if path.is_file():
|
||||
self._replay_file(path)
|
||||
self._append_handle = path.open("a", encoding="utf-8")
|
||||
self._connected = True
|
||||
|
||||
def disconnect(self) -> None:
|
||||
"""Flush and close the append handle if open."""
|
||||
with self._lock:
|
||||
if self._append_handle is not None:
|
||||
self._append_handle.flush()
|
||||
try:
|
||||
os.fsync(self._append_handle.fileno())
|
||||
except OSError:
|
||||
pass
|
||||
self._append_handle.close()
|
||||
self._append_handle = None
|
||||
self._connected = False
|
||||
|
||||
def is_connected(self) -> bool:
|
||||
"""Return whether ``connect()`` has completed successfully."""
|
||||
return self._connected
|
||||
|
||||
def enqueue(self, items: list[dict[str, Any]]) -> None:
|
||||
"""Append items to memory and the JSONL file; optionally age-evict."""
|
||||
self._require_connected()
|
||||
with self._lock:
|
||||
added = self._memory.enqueue_and_return_added(items)
|
||||
if added:
|
||||
self._append_items(added)
|
||||
if self._config.age_key is not None:
|
||||
cutoff = datetime.now(UTC) - timedelta(hours=self._config.max_age_hours)
|
||||
if self._memory.evict_older_than(cutoff):
|
||||
self._compact()
|
||||
|
||||
def dequeue_batch(self, *, max_items: int = 1000) -> list[dict[str, Any]]:
|
||||
"""Dequeue from memory and compact the JSONL file to match."""
|
||||
self._require_connected()
|
||||
with self._lock:
|
||||
batch = self._memory.dequeue_batch(max_items=max_items)
|
||||
if batch:
|
||||
self._compact()
|
||||
return batch
|
||||
|
||||
def size(self) -> int:
|
||||
"""Return the number of items currently in the in-memory buffer."""
|
||||
self._require_connected()
|
||||
return self._memory.size()
|
||||
|
||||
def evict_older_than(self, cutoff: datetime) -> int:
|
||||
"""Evict aged items from memory and compact the JSONL file."""
|
||||
self._require_connected()
|
||||
with self._lock:
|
||||
removed = self._memory.evict_older_than(cutoff)
|
||||
if removed:
|
||||
self._compact()
|
||||
return removed
|
||||
|
||||
def _require_connected(self) -> None:
|
||||
if not self._connected:
|
||||
raise RuntimeError("FileBackedQueueAdapter is not connected")
|
||||
|
||||
def _replay_file(self, path: Path) -> None:
|
||||
cutoff: datetime | None = None
|
||||
if self._config.age_key is not None:
|
||||
cutoff = datetime.now(UTC) - timedelta(hours=self._config.max_age_hours)
|
||||
with path.open(encoding="utf-8") as handle:
|
||||
for line_number, line in enumerate(handle, start=1):
|
||||
stripped = line.strip()
|
||||
if not stripped:
|
||||
continue
|
||||
try:
|
||||
payload = json.loads(stripped)
|
||||
except json.JSONDecodeError:
|
||||
self.logger.warning(
|
||||
"Skipping corrupt JSONL line",
|
||||
path=str(path),
|
||||
line_number=line_number,
|
||||
)
|
||||
continue
|
||||
if not isinstance(payload, dict):
|
||||
self.logger.warning(
|
||||
"Skipping non-object JSONL line",
|
||||
path=str(path),
|
||||
line_number=line_number,
|
||||
)
|
||||
continue
|
||||
item: dict[str, Any] = payload
|
||||
if cutoff is not None and self._config.age_key is not None:
|
||||
age_value = item.get(self._config.age_key)
|
||||
if age_value is None:
|
||||
self.logger.warning(
|
||||
"Skipping item missing age key on replay",
|
||||
path=str(path),
|
||||
line_number=line_number,
|
||||
age_key=self._config.age_key,
|
||||
)
|
||||
continue
|
||||
try:
|
||||
if MemoryQueueAdapter._parse_age(age_value) < cutoff:
|
||||
continue
|
||||
except ValueError:
|
||||
self.logger.warning(
|
||||
"Skipping item with invalid age on replay",
|
||||
path=str(path),
|
||||
line_number=line_number,
|
||||
)
|
||||
continue
|
||||
try:
|
||||
self._memory.enqueue_and_return_added([item])
|
||||
except ValueError as exc:
|
||||
self.logger.warning(
|
||||
"Skipping invalid item on replay",
|
||||
path=str(path),
|
||||
line_number=line_number,
|
||||
error=str(exc),
|
||||
)
|
||||
|
||||
def _append_items(self, items: list[dict[str, Any]]) -> None:
|
||||
if self._append_handle is None:
|
||||
raise RuntimeError("append handle is not open")
|
||||
for item in items:
|
||||
self._append_handle.write(
|
||||
json.dumps(item, default=self._json_default, separators=(",", ":"))
|
||||
)
|
||||
self._append_handle.write("\n")
|
||||
self._append_handle.flush()
|
||||
|
||||
def _compact(self) -> None:
|
||||
"""Rewrite the JSONL file from the current in-memory snapshot."""
|
||||
path = self._config.path
|
||||
tmp_path = path.with_suffix(path.suffix + ".tmp")
|
||||
if self._append_handle is not None:
|
||||
self._append_handle.flush()
|
||||
self._append_handle.close()
|
||||
self._append_handle = None
|
||||
with tmp_path.open("w", encoding="utf-8") as handle:
|
||||
for item in self._memory.snapshot():
|
||||
handle.write(
|
||||
json.dumps(item, default=self._json_default, separators=(",", ":"))
|
||||
)
|
||||
handle.write("\n")
|
||||
handle.flush()
|
||||
tmp_path.replace(path)
|
||||
self._append_handle = path.open("a", encoding="utf-8")
|
||||
@@ -0,0 +1,128 @@
|
||||
"""In-memory queue adapter with optional dedup and age-based eviction."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import OrderedDict
|
||||
from collections.abc import Hashable
|
||||
from datetime import UTC, datetime
|
||||
import threading
|
||||
from typing import Any
|
||||
|
||||
from python_repositories.interfaces.queue_repository_interface import (
|
||||
QueueRepositoryInterface,
|
||||
)
|
||||
|
||||
|
||||
class MemoryQueueAdapter(QueueRepositoryInterface):
|
||||
"""Thread-safe in-process FIFO queue with optional deduplication."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
dedup_keys: tuple[str, ...] = (),
|
||||
age_key: str | None = None,
|
||||
) -> None:
|
||||
self._dedup_keys = dedup_keys
|
||||
self._age_key = age_key
|
||||
self._lock = threading.RLock()
|
||||
self._items: OrderedDict[Hashable, dict[str, Any]] = OrderedDict()
|
||||
self._seq = 0
|
||||
|
||||
@staticmethod
|
||||
def _parse_age(value: object) -> datetime:
|
||||
"""Normalize an age field to a timezone-aware UTC datetime."""
|
||||
if isinstance(value, datetime):
|
||||
if value.tzinfo is None:
|
||||
return value.replace(tzinfo=UTC)
|
||||
return value.astimezone(UTC)
|
||||
if isinstance(value, str):
|
||||
normalized = value.replace("Z", "+00:00")
|
||||
parsed = datetime.fromisoformat(normalized)
|
||||
if parsed.tzinfo is None:
|
||||
return parsed.replace(tzinfo=UTC)
|
||||
return parsed.astimezone(UTC)
|
||||
raise ValueError(
|
||||
f"age value must be datetime or ISO-8601 str, got {type(value)!r}"
|
||||
)
|
||||
|
||||
def enqueue(self, items: list[dict[str, Any]]) -> None:
|
||||
"""Append items; skip duplicates when ``dedup_keys`` is configured."""
|
||||
self.enqueue_and_return_added(items)
|
||||
|
||||
def enqueue_and_return_added(
|
||||
self, items: list[dict[str, Any]]
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Enqueue items and return the subset that was newly stored."""
|
||||
if not items:
|
||||
return []
|
||||
added: list[dict[str, Any]] = []
|
||||
with self._lock:
|
||||
for raw in items:
|
||||
item = self._normalize_item(raw)
|
||||
key = self._make_key(item)
|
||||
if self._dedup_keys and key in self._items:
|
||||
continue
|
||||
self._items[key] = item
|
||||
added.append(item)
|
||||
return added
|
||||
|
||||
def dequeue_batch(self, *, max_items: int = 1000) -> list[dict[str, Any]]:
|
||||
"""Remove and return up to ``max_items`` items in FIFO order."""
|
||||
if max_items < 0:
|
||||
raise ValueError("max_items must be >= 0")
|
||||
with self._lock:
|
||||
batch: list[dict[str, Any]] = []
|
||||
for _ in range(min(max_items, len(self._items))):
|
||||
_key, item = self._items.popitem(last=False)
|
||||
batch.append(item)
|
||||
return batch
|
||||
|
||||
def size(self) -> int:
|
||||
"""Return the number of items currently in the queue."""
|
||||
with self._lock:
|
||||
return len(self._items)
|
||||
|
||||
def evict_older_than(self, cutoff: datetime) -> int:
|
||||
"""Remove items whose age field is strictly older than ``cutoff``."""
|
||||
if self._age_key is None:
|
||||
return 0
|
||||
cutoff_utc = self._parse_age(cutoff)
|
||||
removed = 0
|
||||
with self._lock:
|
||||
to_remove = [
|
||||
key
|
||||
for key, item in self._items.items()
|
||||
if self._parse_age(item[self._age_key]) < cutoff_utc
|
||||
]
|
||||
for key in to_remove:
|
||||
del self._items[key]
|
||||
removed += 1
|
||||
return removed
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Remove all items from the queue."""
|
||||
with self._lock:
|
||||
self._items.clear()
|
||||
|
||||
def snapshot(self) -> list[dict[str, Any]]:
|
||||
"""Return a shallow copy of queued items in FIFO order."""
|
||||
with self._lock:
|
||||
return [dict(item) for item in self._items.values()]
|
||||
|
||||
def _normalize_item(self, raw: dict[str, Any]) -> dict[str, Any]:
|
||||
item = dict(raw)
|
||||
if self._dedup_keys:
|
||||
missing = [key for key in self._dedup_keys if key not in item]
|
||||
if missing:
|
||||
raise ValueError(f"item missing dedup key(s): {missing}")
|
||||
if self._age_key is not None:
|
||||
if self._age_key not in item:
|
||||
raise ValueError(f"item missing age key: {self._age_key!r}")
|
||||
item[self._age_key] = self._parse_age(item[self._age_key])
|
||||
return item
|
||||
|
||||
def _make_key(self, item: dict[str, Any]) -> Hashable:
|
||||
if not self._dedup_keys:
|
||||
self._seq += 1
|
||||
return self._seq
|
||||
return tuple(item[key] for key in self._dedup_keys)
|
||||
@@ -0,0 +1,230 @@
|
||||
"""Definition of PostgresAdapter class."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from python_repositories.adapters.connection_aware_adapter import (
|
||||
ConnectionAwareAdapter,
|
||||
)
|
||||
from python_repositories.config import PostgresConfig
|
||||
from python_repositories.interfaces import TableRepositoryInterface
|
||||
|
||||
try:
|
||||
import psycopg
|
||||
from psycopg import sql
|
||||
from psycopg.errors import UndefinedTable
|
||||
from psycopg.rows import dict_row
|
||||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"Postgres support requires the postgres extra. "
|
||||
"Install with: pip install python-repositories[postgres]"
|
||||
) from exc
|
||||
|
||||
|
||||
class PostgresAdapter(ConnectionAwareAdapter, TableRepositoryInterface):
|
||||
"""Postgres adapter exposing basic table CRUD functionality."""
|
||||
|
||||
uri_env_var_name: str = "POSTGRES_URI"
|
||||
table_env_var_name: str = "POSTGRES_TABLE"
|
||||
primary_key_env_var_name: str = "POSTGRES_PRIMARY_KEY"
|
||||
connection_name: str = "Postgres"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
config: PostgresConfig | None = None,
|
||||
client: psycopg.Connection[Any] | None = None,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
if client is not None and config is None:
|
||||
raise ValueError("config is required when client is provided")
|
||||
if config is None:
|
||||
config = PostgresConfig.from_env(
|
||||
self.uri_env_var_name,
|
||||
self.table_env_var_name,
|
||||
self.primary_key_env_var_name,
|
||||
)
|
||||
self._config = config
|
||||
self._client_injected = client is not None
|
||||
self._client: psycopg.Connection[Any] | None = client
|
||||
if self._client_injected:
|
||||
self._invalidate_health_cache()
|
||||
|
||||
def _is_client_ready(self) -> bool:
|
||||
return self._client is not None
|
||||
|
||||
def _table_identifier(self) -> sql.Identifier:
|
||||
return sql.Identifier(self._config.table)
|
||||
|
||||
def _primary_key_identifier(self) -> sql.Identifier:
|
||||
return sql.Identifier(self._config.primary_key)
|
||||
|
||||
def _validate_injected_client(self) -> None:
|
||||
if self._client is None:
|
||||
return
|
||||
try:
|
||||
self._run_connection_probe()
|
||||
except (psycopg.Error, ConnectionError) as exc:
|
||||
raise ConnectionError(
|
||||
f"Could not connect to Postgres at {self._config.uri}"
|
||||
) from exc
|
||||
|
||||
def _establish_connection(self) -> None:
|
||||
uri = self._config.uri
|
||||
try:
|
||||
client = psycopg.connect(
|
||||
uri,
|
||||
row_factory=dict_row,
|
||||
connect_timeout=10,
|
||||
autocommit=True,
|
||||
)
|
||||
self._client = client
|
||||
self._run_connection_probe()
|
||||
except ConnectionError:
|
||||
self._client = None
|
||||
raise
|
||||
except psycopg.Error as exc:
|
||||
self._client = None
|
||||
raise ConnectionError(f"Could not connect to Postgres at {uri}") from exc
|
||||
|
||||
def _run_connection_probe(self) -> None:
|
||||
assert self._client is not None
|
||||
with self._client.cursor() as cursor:
|
||||
cursor.execute("SELECT 1")
|
||||
try:
|
||||
cursor.execute(
|
||||
sql.SQL("SELECT 1 FROM {} LIMIT 0").format(self._table_identifier())
|
||||
)
|
||||
except UndefinedTable as exc:
|
||||
raise ConnectionError(
|
||||
f"Table '{self._config.table}' does not exist on Postgres at "
|
||||
f"{self._config.uri}"
|
||||
) from exc
|
||||
|
||||
def disconnect(self) -> None:
|
||||
"""Disconnect from the Postgres server."""
|
||||
if self._client is not None and not self._client_injected:
|
||||
self._client.close()
|
||||
self._client = None
|
||||
self._invalidate_health_cache()
|
||||
|
||||
def _probe_connection(self) -> bool:
|
||||
assert self._client is not None
|
||||
try:
|
||||
self._run_connection_probe()
|
||||
except (psycopg.Error, ConnectionError):
|
||||
return False
|
||||
return True
|
||||
|
||||
def _validate_pk(self, pk: Any) -> None:
|
||||
if pk is None:
|
||||
raise ValueError("Primary key must not be None")
|
||||
|
||||
def _validate_row(self, row: dict[str, Any]) -> None:
|
||||
if not isinstance(row, dict):
|
||||
raise ValueError("Row must be a dictionary")
|
||||
if self._config.primary_key not in row:
|
||||
raise ValueError(
|
||||
f"Row must include primary key column '{self._config.primary_key}'"
|
||||
)
|
||||
|
||||
def _validate_limit(self, limit: int | None) -> None:
|
||||
if limit is not None and (not isinstance(limit, int) or limit < 0):
|
||||
raise ValueError("Limit must be a non-negative integer or None")
|
||||
|
||||
def fetch_one(self, pk: Any) -> dict[str, Any] | None:
|
||||
"""Fetch a single row by primary key."""
|
||||
self._validate_pk(pk)
|
||||
self._require_connected()
|
||||
assert self._client is not None
|
||||
query = sql.SQL("SELECT * FROM {} WHERE {} = %s").format(
|
||||
self._table_identifier(),
|
||||
self._primary_key_identifier(),
|
||||
)
|
||||
with self._client.cursor() as cursor:
|
||||
cursor.execute(query, (pk,))
|
||||
row = cursor.fetchone()
|
||||
self.logger.debug("Fetched row", pk=pk, found=row is not None)
|
||||
return row
|
||||
|
||||
def fetch_all(self, *, limit: int | None = None) -> list[dict[str, Any]]:
|
||||
"""Fetch all rows from the configured table."""
|
||||
self._validate_limit(limit)
|
||||
self._require_connected()
|
||||
assert self._client is not None
|
||||
query = sql.SQL("SELECT * FROM {}").format(self._table_identifier())
|
||||
params: tuple[Any, ...] = ()
|
||||
if limit is not None:
|
||||
query = sql.Composed([query, sql.SQL(" LIMIT %s")])
|
||||
params = (limit,)
|
||||
with self._client.cursor() as cursor:
|
||||
cursor.execute(query, params)
|
||||
rows = cursor.fetchall()
|
||||
self.logger.debug("Fetched rows", count=len(rows), limit=limit)
|
||||
return list(rows)
|
||||
|
||||
def upsert(self, row: dict[str, Any]) -> None:
|
||||
"""Insert or update a row by primary key."""
|
||||
self._validate_row(row)
|
||||
self._require_connected()
|
||||
assert self._client is not None
|
||||
pk_col = self._config.primary_key
|
||||
columns = list(row.keys())
|
||||
identifiers = [sql.Identifier(column) for column in columns]
|
||||
placeholders = sql.SQL(", ").join(sql.Placeholder() * len(columns))
|
||||
column_list = sql.SQL(", ").join(identifiers)
|
||||
update_columns = [column for column in columns if column != pk_col]
|
||||
on_conflict: sql.Composed | sql.SQL
|
||||
if update_columns:
|
||||
update_assignments = sql.SQL(", ").join(
|
||||
sql.SQL("{} = EXCLUDED.{}").format(
|
||||
sql.Identifier(column),
|
||||
sql.Identifier(column),
|
||||
)
|
||||
for column in update_columns
|
||||
)
|
||||
on_conflict = sql.SQL("DO UPDATE SET {}").format(update_assignments)
|
||||
else:
|
||||
on_conflict = sql.SQL("DO NOTHING")
|
||||
query = sql.SQL("INSERT INTO {} ({}) VALUES ({}) ON CONFLICT ({}) {}").format(
|
||||
self._table_identifier(),
|
||||
column_list,
|
||||
placeholders,
|
||||
self._primary_key_identifier(),
|
||||
on_conflict,
|
||||
)
|
||||
with self._client.cursor() as cursor:
|
||||
cursor.execute(query, tuple(row[column] for column in columns))
|
||||
self.logger.debug("Upserted row", pk=row[pk_col], columns=columns)
|
||||
|
||||
def delete(self, pk: Any) -> None:
|
||||
"""Delete a row by primary key."""
|
||||
self._validate_pk(pk)
|
||||
self._require_connected()
|
||||
assert self._client is not None
|
||||
query = sql.SQL("DELETE FROM {} WHERE {} = %s").format(
|
||||
self._table_identifier(),
|
||||
self._primary_key_identifier(),
|
||||
)
|
||||
with self._client.cursor() as cursor:
|
||||
cursor.execute(query, (pk,))
|
||||
self.logger.debug("Deleted row", pk=pk)
|
||||
|
||||
def execute(
|
||||
self,
|
||||
sql_text: str,
|
||||
params: tuple[Any, ...] = (),
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Run a read SQL statement and return rows as dicts."""
|
||||
if not isinstance(sql_text, str) or len(sql_text) == 0:
|
||||
raise ValueError("SQL must be a non-empty string")
|
||||
if not isinstance(params, tuple):
|
||||
raise ValueError("Params must be a tuple")
|
||||
self._require_connected()
|
||||
assert self._client is not None
|
||||
with self._client.cursor() as cursor:
|
||||
cursor.execute(sql_text, params)
|
||||
rows = cursor.fetchall()
|
||||
self.logger.debug("Executed SQL", row_count=len(rows))
|
||||
return list(rows)
|
||||
@@ -1,9 +1,13 @@
|
||||
from .dotenv_loader import load_dotenv as load_dotenv
|
||||
from .file_queue_config import FileQueueConfig as FileQueueConfig
|
||||
from .minio_config import MinioConfig as MinioConfig
|
||||
from .postgres_config import PostgresConfig as PostgresConfig
|
||||
from .redis_config import RedisConfig as RedisConfig
|
||||
|
||||
__all__ = [
|
||||
"FileQueueConfig",
|
||||
"MinioConfig",
|
||||
"PostgresConfig",
|
||||
"RedisConfig",
|
||||
"load_dotenv",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
"""File-backed queue configuration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from python_utils import check_env
|
||||
|
||||
from python_repositories.config.dotenv_loader import load_dotenv
|
||||
|
||||
_DEFAULT_MAX_AGE_HOURS = 24
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class FileQueueConfig:
|
||||
"""Configuration for a JSONL file-backed queue.
|
||||
|
||||
Path and key schema are owned by the caller. This package does not assume
|
||||
any particular directory layout or item field names.
|
||||
"""
|
||||
|
||||
path: Path
|
||||
max_age_hours: int = _DEFAULT_MAX_AGE_HOURS
|
||||
dedup_keys: tuple[str, ...] = ()
|
||||
age_key: str | None = None
|
||||
|
||||
@classmethod
|
||||
def from_env(
|
||||
cls,
|
||||
path_env_var_name: str = "FILE_QUEUE_PATH",
|
||||
*,
|
||||
max_age_hours_env_var_name: str = "FILE_QUEUE_MAX_AGE_HOURS",
|
||||
dedup_keys: tuple[str, ...] = (),
|
||||
age_key: str | None = None,
|
||||
use_dotenv: bool = True,
|
||||
) -> FileQueueConfig:
|
||||
"""Load path and optional max age from environment variables.
|
||||
|
||||
``dedup_keys`` and ``age_key`` are domain-specific and must be passed
|
||||
explicitly; they are not read from the environment.
|
||||
"""
|
||||
if use_dotenv:
|
||||
load_dotenv()
|
||||
check_env(path_env_var_name)
|
||||
raw_max_age = os.getenv(max_age_hours_env_var_name)
|
||||
max_age_hours = (
|
||||
_DEFAULT_MAX_AGE_HOURS if raw_max_age is None else int(raw_max_age)
|
||||
)
|
||||
return cls(
|
||||
path=Path(str(os.getenv(path_env_var_name))),
|
||||
max_age_hours=max_age_hours,
|
||||
dedup_keys=dedup_keys,
|
||||
age_key=age_key,
|
||||
)
|
||||
@@ -0,0 +1,42 @@
|
||||
"""PostgreSQL connection configuration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
import os
|
||||
|
||||
from python_utils import check_env
|
||||
|
||||
from python_repositories.config.dotenv_loader import load_dotenv
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PostgresConfig:
|
||||
"""Configuration for connecting to PostgreSQL."""
|
||||
|
||||
uri: str
|
||||
table: str
|
||||
primary_key: str = "id"
|
||||
|
||||
@classmethod
|
||||
def from_env(
|
||||
cls,
|
||||
uri_env_var_name: str = "POSTGRES_URI",
|
||||
table_env_var_name: str = "POSTGRES_TABLE",
|
||||
primary_key_env_var_name: str = "POSTGRES_PRIMARY_KEY",
|
||||
*,
|
||||
use_dotenv: bool = True,
|
||||
) -> PostgresConfig:
|
||||
"""Load configuration from environment variables."""
|
||||
if use_dotenv:
|
||||
load_dotenv()
|
||||
env_var_names = {uri_env_var_name, table_env_var_name}
|
||||
check_env(env_var_names)
|
||||
primary_key = os.getenv(primary_key_env_var_name)
|
||||
if primary_key is None or primary_key == "":
|
||||
primary_key = "id"
|
||||
return cls(
|
||||
uri=str(os.getenv(uri_env_var_name)),
|
||||
table=str(os.getenv(table_env_var_name)),
|
||||
primary_key=primary_key,
|
||||
)
|
||||
@@ -0,0 +1,19 @@
|
||||
"""Example domain repository backed by Postgres tables."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from python_repositories.adapters.postgres_adapter import PostgresAdapter
|
||||
|
||||
|
||||
class UserTableRepository(PostgresAdapter):
|
||||
"""Example: domain repository backed by a Postgres table."""
|
||||
|
||||
def get_user(self, user_id: str) -> dict[str, Any] | None:
|
||||
return self.fetch_one(user_id)
|
||||
|
||||
def save_user(self, user_id: str, user: dict[str, Any]) -> None:
|
||||
row = {self._config.primary_key: user_id, **user}
|
||||
self.upsert(row)
|
||||
|
||||
def delete_user(self, user_id: str) -> None:
|
||||
self.delete(user_id)
|
||||
@@ -8,10 +8,18 @@ from .json_repository_interface import (
|
||||
from .object_repository_interface import (
|
||||
ObjectRepositoryInterface as ObjectRepositoryInterface,
|
||||
)
|
||||
from .queue_repository_interface import (
|
||||
QueueRepositoryInterface as QueueRepositoryInterface,
|
||||
)
|
||||
from .table_repository_interface import (
|
||||
TableRepositoryInterface as TableRepositoryInterface,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"ConnectionAwareInterface",
|
||||
"ContextAwareInterface",
|
||||
"JsonRepositoryInterface",
|
||||
"ObjectRepositoryInterface",
|
||||
"QueueRepositoryInterface",
|
||||
"TableRepositoryInterface",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
"""Definition of QueueRepositoryInterface protocol."""
|
||||
|
||||
from abc import abstractmethod
|
||||
from datetime import datetime
|
||||
from typing import Any, Protocol, runtime_checkable
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class QueueRepositoryInterface(Protocol):
|
||||
"""Interface that defines buffered queue enqueue/dequeue methods."""
|
||||
|
||||
@abstractmethod
|
||||
def enqueue(self, items: list[dict[str, Any]]) -> None:
|
||||
"""Append items to the queue.
|
||||
|
||||
Duplicates may be skipped when the implementation is configured with
|
||||
dedup keys. An empty list is a no-op.
|
||||
"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def dequeue_batch(self, *, max_items: int = 1000) -> list[dict[str, Any]]:
|
||||
"""Remove and return up to ``max_items`` items in FIFO order.
|
||||
|
||||
Returns an empty list when the queue is empty.
|
||||
"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def size(self) -> int:
|
||||
"""Return the number of items currently in the queue."""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def evict_older_than(self, cutoff: datetime) -> int:
|
||||
"""Remove items older than ``cutoff`` and return how many were removed.
|
||||
|
||||
Age is determined by an implementation-specific item field. When no age
|
||||
field is configured, this is a no-op that returns ``0``.
|
||||
"""
|
||||
...
|
||||
@@ -0,0 +1,55 @@
|
||||
"""Definition of TableRepositoryInterface protocol."""
|
||||
|
||||
from abc import abstractmethod
|
||||
from typing import Any, Protocol, runtime_checkable
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class TableRepositoryInterface(Protocol):
|
||||
"""Interface that defines relational table CRUD methods."""
|
||||
|
||||
@abstractmethod
|
||||
def fetch_one(self, pk: Any) -> dict[str, Any] | None:
|
||||
"""Fetch a single row by primary key.
|
||||
|
||||
Returns ``None`` when no row matches. Use ``value is not None`` to test
|
||||
existence; avoid truthiness checks.
|
||||
"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def fetch_all(self, *, limit: int | None = None) -> list[dict[str, Any]]:
|
||||
"""Fetch all rows from the configured table.
|
||||
|
||||
Returns an empty list when the table has no rows.
|
||||
"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def upsert(self, row: dict[str, Any]) -> None:
|
||||
"""Insert or update a row by primary key.
|
||||
|
||||
``row`` must include the configured primary key column. Raises
|
||||
``ValueError`` for a non-dict row or a missing primary key.
|
||||
"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def delete(self, pk: Any) -> None:
|
||||
"""Delete a row by primary key.
|
||||
|
||||
Idempotent: no error when the row is already absent.
|
||||
"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def execute(
|
||||
self,
|
||||
sql: str,
|
||||
params: tuple[Any, ...] = (),
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Run a read SQL statement and return rows as dicts.
|
||||
|
||||
Escape hatch for joins, filters, and other queries in subclasses.
|
||||
"""
|
||||
...
|
||||
@@ -103,13 +103,17 @@ changelog_section() {
|
||||
local summary="$3"
|
||||
local commits="$4"
|
||||
|
||||
# Blank lines after ATX headings match Prettier v4 markdown formatting
|
||||
# (see mirrors-prettier in .pre-commit-config.yaml).
|
||||
cat <<EOF
|
||||
## [${version}] - ${date}
|
||||
|
||||
### Summary
|
||||
|
||||
${summary}
|
||||
|
||||
### Changed
|
||||
|
||||
${commits:-- (no commits recorded)}
|
||||
EOF
|
||||
}
|
||||
|
||||
+6
-1
@@ -1,6 +1,6 @@
|
||||
"""Shared test configuration constants."""
|
||||
|
||||
from python_repositories.config import MinioConfig, RedisConfig
|
||||
from python_repositories.config import MinioConfig, PostgresConfig, RedisConfig
|
||||
|
||||
TEST_REDIS_CONFIG = RedisConfig(uri="redis://localhost:6379")
|
||||
TEST_MINIO_CONFIG = MinioConfig(
|
||||
@@ -10,3 +10,8 @@ TEST_MINIO_CONFIG = MinioConfig(
|
||||
bucket="test-bucket",
|
||||
secure=False,
|
||||
)
|
||||
TEST_POSTGRES_CONFIG = PostgresConfig(
|
||||
uri="postgresql://localhost/mydb",
|
||||
table="test_items",
|
||||
primary_key="id",
|
||||
)
|
||||
|
||||
@@ -6,12 +6,17 @@ from minio import Minio
|
||||
import pytest
|
||||
import redis
|
||||
|
||||
from python_repositories.config import MinioConfig, RedisConfig
|
||||
from python_repositories.config import MinioConfig, PostgresConfig, RedisConfig
|
||||
from tests.integration.minio._containers import (
|
||||
minio_config_from_env,
|
||||
minio_env,
|
||||
raw_minio_client_from_env,
|
||||
)
|
||||
from tests.integration.postgres._containers import (
|
||||
postgres_config_from_container,
|
||||
postgres_uri,
|
||||
raw_postgres_client_from_container,
|
||||
)
|
||||
from tests.integration.redis._containers import (
|
||||
raw_redis_client_from_container,
|
||||
redis_config_from_container,
|
||||
@@ -53,3 +58,23 @@ def minio_config(minio_container: dict[str, str]) -> MinioConfig:
|
||||
def raw_minio_client(minio_container: dict[str, str]) -> Generator[Minio, None, None]:
|
||||
"""Provide a raw Minio client connected to the test Minio container."""
|
||||
yield from raw_minio_client_from_env(minio_container)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def postgres_container() -> Generator[str, None, None]:
|
||||
"""Set up a Postgres container for testing and yield the Postgres URI."""
|
||||
yield from postgres_uri()
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def postgres_config(postgres_container: str) -> PostgresConfig:
|
||||
"""Provide PostgresConfig built from the test container."""
|
||||
return postgres_config_from_container(postgres_container)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def raw_postgres_client(
|
||||
postgres_container: str,
|
||||
) -> Generator[object, None, None]:
|
||||
"""Provide a raw Postgres client connected to the test Postgres container."""
|
||||
yield from raw_postgres_client_from_container(postgres_container)
|
||||
|
||||
@@ -6,12 +6,15 @@ import os
|
||||
import random
|
||||
|
||||
from minio import Minio
|
||||
import psycopg
|
||||
import pytest
|
||||
|
||||
from python_repositories.examples.artifact_object_repository import (
|
||||
ArtifactObjectRepository,
|
||||
)
|
||||
from python_repositories.examples.user_json_repository import UserJsonRepository
|
||||
from python_repositories.examples.user_table_repository import UserTableRepository
|
||||
from tests.integration.postgres._containers import TEST_TABLE
|
||||
|
||||
pytestmark = [
|
||||
pytest.mark.integration,
|
||||
@@ -36,6 +39,24 @@ def set_example_env(
|
||||
_ = os.environ.pop(key, default=None)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module", autouse=True)
|
||||
def set_postgres_example_env(
|
||||
postgres_container: str,
|
||||
raw_postgres_client: psycopg.Connection,
|
||||
) -> Generator[None, None, None]:
|
||||
"""Set Postgres env vars for example repositories using from_env() defaults."""
|
||||
env_vars = {
|
||||
"POSTGRES_URI": postgres_container,
|
||||
"POSTGRES_TABLE": TEST_TABLE,
|
||||
"POSTGRES_PRIMARY_KEY": "id",
|
||||
}
|
||||
for key, value in env_vars.items():
|
||||
os.environ[key] = value
|
||||
yield
|
||||
for key in env_vars:
|
||||
_ = os.environ.pop(key, default=None)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def user_data() -> Generator[dict[str, str], None, None]:
|
||||
"""Provide sample user data for tests."""
|
||||
@@ -77,3 +98,27 @@ def test_artifact_object_repository_store_and_get(
|
||||
assert received is not None
|
||||
artifact_data.seek(0)
|
||||
assert received.read() == artifact_data.read()
|
||||
|
||||
|
||||
@pytest.mark.needs_postgres
|
||||
def test_user_table_repository_save_and_get(
|
||||
user_data: dict[str, str],
|
||||
) -> None:
|
||||
"""Test that UserTableRepository can save and retrieve a user."""
|
||||
with UserTableRepository() as repo:
|
||||
repo.save_user("alice", user_data)
|
||||
user = repo.get_user("alice")
|
||||
assert user is not None
|
||||
assert user["name"] == user_data["name"]
|
||||
assert user["email"] == user_data["email"]
|
||||
|
||||
|
||||
@pytest.mark.needs_postgres
|
||||
def test_user_table_repository_delete(
|
||||
user_data: dict[str, str],
|
||||
) -> None:
|
||||
"""Test that UserTableRepository can delete a user."""
|
||||
with UserTableRepository() as repo:
|
||||
repo.save_user("alice", user_data)
|
||||
repo.delete_user("alice")
|
||||
assert repo.get_user("alice") is None
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
"""Postgres container session helpers for integration tests."""
|
||||
|
||||
from collections.abc import Generator
|
||||
|
||||
import psycopg
|
||||
from psycopg.rows import dict_row
|
||||
from testcontainers.postgres import PostgresContainer
|
||||
|
||||
from python_repositories.config import PostgresConfig
|
||||
|
||||
POSTGRES_IMAGE = "postgres:16"
|
||||
TEST_TABLE = "test_items"
|
||||
|
||||
|
||||
_postgres_uri: str | None = None
|
||||
_postgres_container: PostgresContainer | None = None
|
||||
_raw_postgres_client: psycopg.Connection | None = None
|
||||
_postgres_uri_refs = 0
|
||||
_raw_postgres_client_refs = 0
|
||||
|
||||
|
||||
def postgres_uri() -> Generator[str, None, None]:
|
||||
"""Yield a session-scoped Postgres URI, starting the container once."""
|
||||
global _postgres_uri, _postgres_container, _postgres_uri_refs
|
||||
if _postgres_uri is None:
|
||||
_postgres_container = PostgresContainer(POSTGRES_IMAGE, driver=None)
|
||||
_postgres_container.start()
|
||||
_postgres_uri = _postgres_container.get_connection_url()
|
||||
|
||||
_postgres_uri_refs += 1
|
||||
yield _postgres_uri
|
||||
_postgres_uri_refs -= 1
|
||||
|
||||
if _postgres_uri_refs == 0 and _postgres_container is not None:
|
||||
_postgres_container.stop()
|
||||
_postgres_container = None
|
||||
_postgres_uri = None
|
||||
|
||||
|
||||
def postgres_config_from_container(uri: str) -> PostgresConfig:
|
||||
"""Build PostgresConfig from a container URI."""
|
||||
return PostgresConfig(uri=uri, table=TEST_TABLE, primary_key="id")
|
||||
|
||||
|
||||
def raw_postgres_client_from_container(
|
||||
uri: str,
|
||||
) -> Generator[psycopg.Connection, None, None]:
|
||||
"""Yield a session-scoped raw Postgres client, reusing one client per session."""
|
||||
global _raw_postgres_client, _raw_postgres_client_refs
|
||||
if _raw_postgres_client is None:
|
||||
_raw_postgres_client = psycopg.connect(
|
||||
uri,
|
||||
row_factory=dict_row, # type: ignore[arg-type]
|
||||
autocommit=True,
|
||||
)
|
||||
with _raw_postgres_client.cursor() as cursor:
|
||||
cursor.execute(
|
||||
f"""
|
||||
CREATE TABLE IF NOT EXISTS {TEST_TABLE} (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
value INTEGER,
|
||||
email TEXT
|
||||
)
|
||||
"""
|
||||
)
|
||||
|
||||
_raw_postgres_client_refs += 1
|
||||
yield _raw_postgres_client
|
||||
_raw_postgres_client_refs -= 1
|
||||
|
||||
if _raw_postgres_client_refs == 0 and _raw_postgres_client is not None:
|
||||
with _raw_postgres_client.cursor() as cursor:
|
||||
cursor.execute(f"TRUNCATE TABLE {TEST_TABLE}")
|
||||
_raw_postgres_client.close()
|
||||
_raw_postgres_client = None
|
||||
@@ -0,0 +1,33 @@
|
||||
"""Postgres integration test fixtures."""
|
||||
|
||||
from collections.abc import Generator
|
||||
|
||||
import psycopg
|
||||
import pytest
|
||||
|
||||
from python_repositories.config import PostgresConfig
|
||||
from tests.integration.postgres._containers import (
|
||||
postgres_config_from_container,
|
||||
postgres_uri,
|
||||
raw_postgres_client_from_container,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def postgres_container() -> Generator[str, None, None]:
|
||||
"""Set up a Postgres container for testing and yield the Postgres URI."""
|
||||
yield from postgres_uri()
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def postgres_config(postgres_container: str) -> PostgresConfig:
|
||||
"""Provide PostgresConfig built from the test container."""
|
||||
return postgres_config_from_container(postgres_container)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def raw_postgres_client(
|
||||
postgres_container: str,
|
||||
) -> Generator[psycopg.Connection, None, None]:
|
||||
"""Provide a raw Postgres client connected to the test container."""
|
||||
yield from raw_postgres_client_from_container(postgres_container)
|
||||
@@ -0,0 +1,318 @@
|
||||
"""Integration tests for the PostgresAdapter."""
|
||||
|
||||
from collections.abc import Generator
|
||||
import logging
|
||||
|
||||
import psycopg
|
||||
import pytest
|
||||
|
||||
from python_repositories.adapters.postgres_adapter import PostgresAdapter
|
||||
from python_repositories.config import PostgresConfig
|
||||
from tests.integration.postgres._containers import TEST_TABLE
|
||||
|
||||
pytestmark = [pytest.mark.integration, pytest.mark.needs_postgres]
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def row() -> Generator[dict[str, object], None, None]:
|
||||
"""Provide a sample row for tests."""
|
||||
yield {"id": "test-id", "name": "Alice", "value": 42}
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def row_in_postgres(
|
||||
raw_postgres_client: psycopg.Connection,
|
||||
row: dict[str, object],
|
||||
) -> Generator[tuple[str, dict[str, object]], None, None]:
|
||||
"""Fixture to set up a known row in Postgres before each test."""
|
||||
with raw_postgres_client.cursor() as cursor:
|
||||
cursor.execute(
|
||||
f"INSERT INTO {TEST_TABLE} (id, name, value) VALUES (%s, %s, %s)",
|
||||
(row["id"], row["name"], row["value"]),
|
||||
)
|
||||
|
||||
yield str(row["id"]), row
|
||||
|
||||
with raw_postgres_client.cursor() as cursor:
|
||||
cursor.execute(f"DELETE FROM {TEST_TABLE} WHERE id = %s", (row["id"],))
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def postgres_adapter(
|
||||
postgres_config: PostgresConfig,
|
||||
raw_postgres_client: psycopg.Connection,
|
||||
) -> Generator[PostgresAdapter, None, None]:
|
||||
"""Fixture to provide a connected PostgresAdapter instance."""
|
||||
_ = raw_postgres_client # ensure test table exists before connect probe
|
||||
adapter = PostgresAdapter(config=postgres_config)
|
||||
adapter.connect()
|
||||
yield adapter
|
||||
adapter.disconnect()
|
||||
|
||||
|
||||
@pytest.fixture(scope="function", autouse=True)
|
||||
def clear_postgres(raw_postgres_client: psycopg.Connection) -> None:
|
||||
"""Fixture to clear all rows before each test."""
|
||||
with raw_postgres_client.cursor() as cursor:
|
||||
cursor.execute(f"TRUNCATE TABLE {TEST_TABLE}")
|
||||
|
||||
|
||||
def test_should_log_info_when_already_connected(
|
||||
postgres_adapter: PostgresAdapter,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test that PostgresAdapter logs info when connect is called while connected."""
|
||||
with caplog.at_level(logging.INFO):
|
||||
postgres_adapter.connect()
|
||||
assert "Already connected" in caplog.text
|
||||
assert "Postgres" in caplog.text
|
||||
|
||||
|
||||
def test_should_raise_connection_error_when_unable_to_connect() -> None:
|
||||
"""Test that PostgresAdapter raises ConnectionError when unable to connect."""
|
||||
adapter = PostgresAdapter(
|
||||
config=PostgresConfig(
|
||||
uri="postgresql://invalid:5432/nodb",
|
||||
table=TEST_TABLE,
|
||||
)
|
||||
)
|
||||
with pytest.raises(ConnectionError):
|
||||
adapter.connect()
|
||||
assert adapter._client is None
|
||||
assert not adapter.is_connected()
|
||||
|
||||
|
||||
def test_should_raise_connection_error_when_table_missing(
|
||||
postgres_config: PostgresConfig,
|
||||
) -> None:
|
||||
"""Test that PostgresAdapter raises ConnectionError when the table is missing."""
|
||||
config = PostgresConfig(
|
||||
uri=postgres_config.uri,
|
||||
table="missing_table",
|
||||
primary_key="id",
|
||||
)
|
||||
adapter = PostgresAdapter(config=config)
|
||||
with pytest.raises(ConnectionError, match="does not exist"):
|
||||
adapter.connect()
|
||||
|
||||
|
||||
def test_should_log_error_on_exception_during_exit(
|
||||
postgres_config: PostgresConfig,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test that PostgresAdapter logs an error when an exception occurs during exit."""
|
||||
try:
|
||||
with PostgresAdapter(config=postgres_config) as adapter:
|
||||
assert adapter.is_connected()
|
||||
raise ValueError("Simulated error")
|
||||
except ValueError:
|
||||
pass
|
||||
assert "Error while exiting context" in caplog.text
|
||||
|
||||
|
||||
def test_should_have_context_manager(postgres_config: PostgresConfig) -> None:
|
||||
"""Test that PostgresAdapter can be used as a context manager."""
|
||||
with PostgresAdapter(config=postgres_config) as adapter:
|
||||
assert adapter._client is not None
|
||||
assert adapter._client is None
|
||||
|
||||
|
||||
def test_should_fetch_one(
|
||||
row_in_postgres: tuple[str, dict[str, object]],
|
||||
postgres_adapter: PostgresAdapter,
|
||||
) -> None:
|
||||
"""Test that PostgresAdapter can fetch a row by primary key."""
|
||||
pk, row = row_in_postgres
|
||||
fetched = postgres_adapter.fetch_one(pk)
|
||||
assert fetched is not None
|
||||
assert fetched["id"] == row["id"]
|
||||
assert fetched["name"] == row["name"]
|
||||
assert fetched["value"] == row["value"]
|
||||
|
||||
|
||||
def test_should_fetch_none_for_missing_row(
|
||||
postgres_adapter: PostgresAdapter,
|
||||
) -> None:
|
||||
"""Test that fetching a non-existent row returns None."""
|
||||
assert postgres_adapter.fetch_one("missing-id") is None
|
||||
|
||||
|
||||
def test_should_raise_value_error_on_invalid_fetch_one_pk(
|
||||
postgres_adapter: PostgresAdapter,
|
||||
) -> None:
|
||||
"""Test that PostgresAdapter raises ValueError for an invalid primary key."""
|
||||
with pytest.raises(ValueError):
|
||||
postgres_adapter.fetch_one(None)
|
||||
|
||||
|
||||
def test_should_raise_connection_error_on_fetch_one_when_not_connected(
|
||||
postgres_config: PostgresConfig,
|
||||
) -> None:
|
||||
"""Test that fetch_one raises ConnectionError when not connected."""
|
||||
adapter = PostgresAdapter(config=postgres_config)
|
||||
with pytest.raises(ConnectionError):
|
||||
adapter.fetch_one("some-id")
|
||||
|
||||
|
||||
def test_should_upsert_row(
|
||||
row: dict[str, object],
|
||||
postgres_adapter: PostgresAdapter,
|
||||
) -> None:
|
||||
"""Test that PostgresAdapter can insert a row."""
|
||||
assert postgres_adapter.fetch_one(row["id"]) is None
|
||||
postgres_adapter.upsert(row)
|
||||
fetched = postgres_adapter.fetch_one(row["id"])
|
||||
assert fetched is not None
|
||||
assert fetched["name"] == row["name"]
|
||||
|
||||
|
||||
def test_should_update_row(
|
||||
row_in_postgres: tuple[str, dict[str, object]],
|
||||
postgres_adapter: PostgresAdapter,
|
||||
) -> None:
|
||||
"""Test that PostgresAdapter can update an existing row."""
|
||||
pk, _ = row_in_postgres
|
||||
new_row = {"id": pk, "name": "Bob", "value": 99}
|
||||
postgres_adapter.upsert(new_row)
|
||||
fetched = postgres_adapter.fetch_one(pk)
|
||||
assert fetched is not None
|
||||
assert fetched["name"] == "Bob"
|
||||
assert fetched["value"] == 99
|
||||
|
||||
|
||||
def test_should_raise_value_error_on_invalid_upsert_row(
|
||||
postgres_adapter: PostgresAdapter,
|
||||
) -> None:
|
||||
"""Test that PostgresAdapter raises ValueError for invalid upsert data."""
|
||||
with pytest.raises(ValueError):
|
||||
postgres_adapter.upsert({"name": "Alice"}) # missing primary key
|
||||
|
||||
|
||||
def test_should_raise_value_error_on_non_dict_upsert_row(
|
||||
postgres_adapter: PostgresAdapter,
|
||||
) -> None:
|
||||
"""Test that PostgresAdapter raises ValueError when upsert row is not a dict."""
|
||||
with pytest.raises(ValueError):
|
||||
postgres_adapter.upsert("not-a-dict") # type: ignore[arg-type]
|
||||
|
||||
|
||||
def test_should_raise_connection_error_on_upsert_when_not_connected(
|
||||
postgres_config: PostgresConfig,
|
||||
row: dict[str, object],
|
||||
) -> None:
|
||||
"""Test that upsert raises ConnectionError when not connected."""
|
||||
adapter = PostgresAdapter(config=postgres_config)
|
||||
with pytest.raises(ConnectionError):
|
||||
adapter.upsert(row)
|
||||
|
||||
|
||||
def test_should_fetch_all_rows(
|
||||
postgres_adapter: PostgresAdapter,
|
||||
) -> None:
|
||||
"""Test that PostgresAdapter can fetch all rows."""
|
||||
postgres_adapter.upsert({"id": "a", "name": "Alice", "value": 1})
|
||||
postgres_adapter.upsert({"id": "b", "name": "Bob", "value": 2})
|
||||
rows = postgres_adapter.fetch_all()
|
||||
assert len(rows) == 2
|
||||
ids = {row["id"] for row in rows}
|
||||
assert ids == {"a", "b"}
|
||||
|
||||
|
||||
def test_should_fetch_all_with_limit(
|
||||
postgres_adapter: PostgresAdapter,
|
||||
) -> None:
|
||||
"""Test that PostgresAdapter respects fetch_all limit."""
|
||||
postgres_adapter.upsert({"id": "a", "name": "Alice", "value": 1})
|
||||
postgres_adapter.upsert({"id": "b", "name": "Bob", "value": 2})
|
||||
rows = postgres_adapter.fetch_all(limit=1)
|
||||
assert len(rows) == 1
|
||||
|
||||
|
||||
def test_should_return_empty_list_for_empty_table(
|
||||
postgres_adapter: PostgresAdapter,
|
||||
) -> None:
|
||||
"""Test that fetch_all returns an empty list for an empty table."""
|
||||
assert postgres_adapter.fetch_all() == []
|
||||
|
||||
|
||||
def test_should_raise_value_error_on_invalid_fetch_all_limit(
|
||||
postgres_adapter: PostgresAdapter,
|
||||
) -> None:
|
||||
"""Test that PostgresAdapter raises ValueError for an invalid limit."""
|
||||
with pytest.raises(ValueError):
|
||||
postgres_adapter.fetch_all(limit=-1)
|
||||
|
||||
|
||||
def test_should_raise_connection_error_on_fetch_all_when_not_connected(
|
||||
postgres_config: PostgresConfig,
|
||||
) -> None:
|
||||
"""Test that fetch_all raises ConnectionError when not connected."""
|
||||
adapter = PostgresAdapter(config=postgres_config)
|
||||
with pytest.raises(ConnectionError):
|
||||
adapter.fetch_all()
|
||||
|
||||
|
||||
def test_should_delete_row(
|
||||
row_in_postgres: tuple[str, dict[str, object]],
|
||||
postgres_adapter: PostgresAdapter,
|
||||
) -> None:
|
||||
"""Test that deleting a row removes it from the table."""
|
||||
pk, _ = row_in_postgres
|
||||
assert postgres_adapter.fetch_one(pk) is not None
|
||||
postgres_adapter.delete(pk)
|
||||
assert postgres_adapter.fetch_one(pk) is None
|
||||
|
||||
|
||||
def test_should_delete_idempotently(
|
||||
postgres_adapter: PostgresAdapter,
|
||||
) -> None:
|
||||
"""Test that deleting a missing row does not raise."""
|
||||
postgres_adapter.delete("missing-id")
|
||||
|
||||
|
||||
def test_should_raise_value_error_on_invalid_delete_pk(
|
||||
postgres_adapter: PostgresAdapter,
|
||||
) -> None:
|
||||
"""Test that PostgresAdapter raises ValueError for an invalid delete pk."""
|
||||
with pytest.raises(ValueError):
|
||||
postgres_adapter.delete(None)
|
||||
|
||||
|
||||
def test_should_raise_connection_error_on_delete_when_not_connected(
|
||||
postgres_config: PostgresConfig,
|
||||
) -> None:
|
||||
"""Test that delete raises ConnectionError when not connected."""
|
||||
adapter = PostgresAdapter(config=postgres_config)
|
||||
with pytest.raises(ConnectionError):
|
||||
adapter.delete("some-id")
|
||||
|
||||
|
||||
def test_should_execute_sql(
|
||||
row_in_postgres: tuple[str, dict[str, object]],
|
||||
postgres_adapter: PostgresAdapter,
|
||||
) -> None:
|
||||
"""Test that PostgresAdapter can execute read SQL."""
|
||||
pk, _ = row_in_postgres
|
||||
rows = postgres_adapter.execute(
|
||||
f"SELECT * FROM {TEST_TABLE} WHERE id = %s",
|
||||
(pk,),
|
||||
)
|
||||
assert len(rows) == 1
|
||||
assert rows[0]["id"] == pk
|
||||
|
||||
|
||||
def test_should_raise_value_error_on_invalid_execute_sql(
|
||||
postgres_adapter: PostgresAdapter,
|
||||
) -> None:
|
||||
"""Test that PostgresAdapter raises ValueError for invalid SQL."""
|
||||
with pytest.raises(ValueError):
|
||||
postgres_adapter.execute("")
|
||||
|
||||
|
||||
def test_should_raise_connection_error_on_execute_when_not_connected(
|
||||
postgres_config: PostgresConfig,
|
||||
) -> None:
|
||||
"""Test that execute raises ConnectionError when not connected."""
|
||||
adapter = PostgresAdapter(config=postgres_config)
|
||||
with pytest.raises(ConnectionError):
|
||||
adapter.execute("SELECT 1")
|
||||
@@ -45,6 +45,9 @@ from python_repositories import JsonRepositoryInterface
|
||||
assert JsonRepositoryInterface is not None
|
||||
assert "python_repositories.adapters.redis_adapter" not in sys.modules
|
||||
assert "python_repositories.adapters.minio_adapter" not in sys.modules
|
||||
assert "python_repositories.adapters.postgres_adapter" not in sys.modules
|
||||
assert "python_repositories.adapters.memory_queue_adapter" not in sys.modules
|
||||
assert "python_repositories.adapters.file_backed_queue_adapter" not in sys.modules
|
||||
"""
|
||||
result = subprocess.run(
|
||||
[sys.executable, "-c", script],
|
||||
@@ -58,10 +61,19 @@ assert "python_repositories.adapters.minio_adapter" not in sys.modules
|
||||
|
||||
def test_lazy_adapter_load_succeeds_when_extra_present() -> None:
|
||||
"""Adapters load when their optional dependencies are installed."""
|
||||
from python_repositories import MinioAdapter, RedisAdapter
|
||||
from python_repositories import (
|
||||
FileBackedQueueAdapter,
|
||||
MemoryQueueAdapter,
|
||||
MinioAdapter,
|
||||
PostgresAdapter,
|
||||
RedisAdapter,
|
||||
)
|
||||
|
||||
assert RedisAdapter.__name__ == "RedisAdapter"
|
||||
assert MinioAdapter.__name__ == "MinioAdapter"
|
||||
assert PostgresAdapter.__name__ == "PostgresAdapter"
|
||||
assert MemoryQueueAdapter.__name__ == "MemoryQueueAdapter"
|
||||
assert FileBackedQueueAdapter.__name__ == "FileBackedQueueAdapter"
|
||||
|
||||
|
||||
def test_redis_adapter_import_error_without_extra() -> None:
|
||||
@@ -86,6 +98,17 @@ def test_minio_adapter_import_error_without_extra() -> None:
|
||||
importlib.reload(minio_adapter_module)
|
||||
|
||||
|
||||
def test_postgres_adapter_import_error_without_extra() -> None:
|
||||
"""Missing postgres extra raises ImportError with install hint."""
|
||||
import python_repositories.adapters.postgres_adapter as postgres_adapter_module
|
||||
|
||||
with patch.object(builtins, "__import__", new=_block_backend_import("psycopg")):
|
||||
with pytest.raises(ImportError, match=r"python-repositories\[postgres\]"):
|
||||
importlib.reload(postgres_adapter_module)
|
||||
|
||||
importlib.reload(postgres_adapter_module)
|
||||
|
||||
|
||||
def test_top_level_lazy_import_propagates_redis_import_error() -> None:
|
||||
"""Top-level RedisAdapter access surfaces adapter import errors."""
|
||||
with patch(
|
||||
@@ -112,6 +135,19 @@ def test_top_level_lazy_import_propagates_minio_import_error() -> None:
|
||||
_ = python_repositories.MinioAdapter
|
||||
|
||||
|
||||
def test_top_level_lazy_import_propagates_postgres_import_error() -> None:
|
||||
"""Top-level PostgresAdapter access surfaces adapter import errors."""
|
||||
with patch(
|
||||
"importlib.import_module",
|
||||
side_effect=ImportError(
|
||||
"Postgres support requires the postgres extra. "
|
||||
"Install with: pip install python-repositories[postgres]"
|
||||
),
|
||||
):
|
||||
with pytest.raises(ImportError, match=r"python-repositories\[postgres\]"):
|
||||
_ = python_repositories.PostgresAdapter
|
||||
|
||||
|
||||
def test_adapters_subpackage_lazy_import_succeeds() -> None:
|
||||
"""Adapter subpackage imports delegate to the same lazy loader."""
|
||||
from python_repositories.adapters import RedisAdapter
|
||||
@@ -123,7 +159,13 @@ def test_adapters_dir_exposes_lazy_exports() -> None:
|
||||
"""dir(adapters) includes lazy adapter names for tab completion."""
|
||||
import python_repositories.adapters as adapters
|
||||
|
||||
assert {"RedisAdapter", "MinioAdapter"}.issubset(set(dir(adapters)))
|
||||
assert {
|
||||
"RedisAdapter",
|
||||
"MinioAdapter",
|
||||
"PostgresAdapter",
|
||||
"MemoryQueueAdapter",
|
||||
"FileBackedQueueAdapter",
|
||||
}.issubset(set(dir(adapters)))
|
||||
|
||||
|
||||
def test_adapters_getattr_raises_for_unknown() -> None:
|
||||
@@ -138,3 +180,6 @@ def test_top_level_dir_exposes_lazy_exports() -> None:
|
||||
"""dir(python_repositories) includes lazy adapter names for tab completion."""
|
||||
assert "RedisAdapter" in dir(python_repositories)
|
||||
assert "MinioAdapter" in dir(python_repositories)
|
||||
assert "PostgresAdapter" in dir(python_repositories)
|
||||
assert "MemoryQueueAdapter" in dir(python_repositories)
|
||||
assert "FileBackedQueueAdapter" in dir(python_repositories)
|
||||
|
||||
+14
-1
@@ -5,12 +5,14 @@ from __future__ import annotations
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from minio import Minio
|
||||
import psycopg
|
||||
import pytest
|
||||
import redis
|
||||
|
||||
from python_repositories.adapters.minio_adapter import MinioAdapter
|
||||
from python_repositories.adapters.postgres_adapter import PostgresAdapter
|
||||
from python_repositories.adapters.redis_adapter import RedisAdapter
|
||||
from tests.conftest import TEST_MINIO_CONFIG, TEST_REDIS_CONFIG
|
||||
from tests.conftest import TEST_MINIO_CONFIG, TEST_POSTGRES_CONFIG, TEST_REDIS_CONFIG
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -25,3 +27,14 @@ def minio_adapter() -> MinioAdapter:
|
||||
"""Provide a MinioAdapter with an injected mock client."""
|
||||
mock_client = MagicMock(spec=Minio)
|
||||
return MinioAdapter(config=TEST_MINIO_CONFIG, client=mock_client)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def postgres_adapter() -> PostgresAdapter:
|
||||
"""Provide a PostgresAdapter with an injected mock client."""
|
||||
mock_client = MagicMock(spec=psycopg.Connection)
|
||||
mock_cursor = MagicMock()
|
||||
mock_cursor.__enter__ = MagicMock(return_value=mock_cursor)
|
||||
mock_cursor.__exit__ = MagicMock(return_value=False)
|
||||
mock_client.cursor.return_value = mock_cursor
|
||||
return PostgresAdapter(config=TEST_POSTGRES_CONFIG, client=mock_client)
|
||||
|
||||
@@ -5,11 +5,13 @@ from __future__ import annotations
|
||||
from typing import cast
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import psycopg
|
||||
import redis
|
||||
|
||||
from python_repositories.adapters.minio_adapter import MinioAdapter
|
||||
from python_repositories.adapters.postgres_adapter import PostgresAdapter
|
||||
from python_repositories.adapters.redis_adapter import RedisAdapter
|
||||
from tests.conftest import TEST_MINIO_CONFIG, TEST_REDIS_CONFIG
|
||||
from tests.conftest import TEST_MINIO_CONFIG, TEST_POSTGRES_CONFIG, TEST_REDIS_CONFIG
|
||||
|
||||
|
||||
class TestRedisConnectionHealth:
|
||||
@@ -161,3 +163,74 @@ class TestMinioConnectionHealth:
|
||||
assert reinjected.is_connected()
|
||||
|
||||
assert mock_client.bucket_exists.call_count == 2
|
||||
|
||||
|
||||
class TestPostgresConnectionHealth:
|
||||
def test_not_connected_when_no_client(self) -> None:
|
||||
adapter = PostgresAdapter(config=TEST_POSTGRES_CONFIG)
|
||||
assert not adapter.is_connected()
|
||||
|
||||
def test_connected_when_probe_succeeds(
|
||||
self, postgres_adapter: PostgresAdapter
|
||||
) -> None:
|
||||
assert postgres_adapter.is_connected()
|
||||
cast(MagicMock, postgres_adapter._client).cursor.assert_called()
|
||||
|
||||
def test_stale_connection_when_probe_fails(
|
||||
self, postgres_adapter: PostgresAdapter
|
||||
) -> None:
|
||||
mock_cursor = cast(MagicMock, postgres_adapter._client).cursor.return_value
|
||||
mock_cursor.execute.side_effect = psycopg.OperationalError("connection lost")
|
||||
|
||||
assert not postgres_adapter.is_connected()
|
||||
|
||||
def test_cache_hit_avoids_second_probe(
|
||||
self, postgres_adapter: PostgresAdapter
|
||||
) -> None:
|
||||
mock_client = cast(MagicMock, postgres_adapter._client)
|
||||
|
||||
with patch(
|
||||
"python_repositories.adapters.connection_aware_adapter.time.monotonic",
|
||||
return_value=100.0,
|
||||
):
|
||||
assert postgres_adapter.is_connected()
|
||||
assert postgres_adapter.is_connected()
|
||||
|
||||
assert mock_client.cursor.call_count == 1
|
||||
|
||||
def test_cache_miss_runs_probe_again(
|
||||
self, postgres_adapter: PostgresAdapter
|
||||
) -> None:
|
||||
mock_client = cast(MagicMock, postgres_adapter._client)
|
||||
|
||||
with patch(
|
||||
"python_repositories.adapters.connection_aware_adapter.time.monotonic",
|
||||
side_effect=[100.0, 102.0],
|
||||
):
|
||||
assert postgres_adapter.is_connected()
|
||||
assert postgres_adapter.is_connected()
|
||||
|
||||
assert mock_client.cursor.call_count == 2
|
||||
|
||||
def test_disconnect_clears_cache(self, postgres_adapter: PostgresAdapter) -> None:
|
||||
mock_client = cast(MagicMock, postgres_adapter._client)
|
||||
|
||||
with patch(
|
||||
"python_repositories.adapters.connection_aware_adapter.time.monotonic",
|
||||
return_value=100.0,
|
||||
):
|
||||
assert postgres_adapter.is_connected()
|
||||
|
||||
postgres_adapter.disconnect()
|
||||
reinjected = PostgresAdapter(
|
||||
config=postgres_adapter._config,
|
||||
client=mock_client,
|
||||
)
|
||||
|
||||
with patch(
|
||||
"python_repositories.adapters.connection_aware_adapter.time.monotonic",
|
||||
return_value=100.0,
|
||||
):
|
||||
assert reinjected.is_connected()
|
||||
|
||||
assert mock_client.cursor.call_count == 2
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
"""Unit tests for FileBackedQueueAdapter."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from python_repositories.adapters.file_backed_queue_adapter import (
|
||||
FileBackedQueueAdapter,
|
||||
)
|
||||
from python_repositories.adapters.memory_queue_adapter import MemoryQueueAdapter
|
||||
from python_repositories.config.file_queue_config import FileQueueConfig
|
||||
from python_repositories.interfaces.queue_repository_interface import (
|
||||
QueueRepositoryInterface,
|
||||
)
|
||||
|
||||
|
||||
def _config(path: Path, **kwargs: object) -> FileQueueConfig:
|
||||
return FileQueueConfig(path=path, **kwargs) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def test_implements_interface() -> None:
|
||||
assert issubclass(FileBackedQueueAdapter, QueueRepositoryInterface)
|
||||
|
||||
|
||||
def test_ops_require_connect(tmp_path: Path) -> None:
|
||||
queue = FileBackedQueueAdapter(config=_config(tmp_path / "q.jsonl"))
|
||||
with pytest.raises(RuntimeError, match="not connected"):
|
||||
queue.enqueue([{"id": 1}])
|
||||
with pytest.raises(RuntimeError, match="not connected"):
|
||||
queue.dequeue_batch()
|
||||
with pytest.raises(RuntimeError, match="not connected"):
|
||||
queue.size()
|
||||
with pytest.raises(RuntimeError, match="not connected"):
|
||||
queue.evict_older_than(datetime.now(UTC))
|
||||
|
||||
|
||||
def test_enqueue_dequeue_persists_and_compacts(tmp_path: Path) -> None:
|
||||
path = tmp_path / "items.jsonl"
|
||||
with FileBackedQueueAdapter(config=_config(path, dedup_keys=("id",))) as queue:
|
||||
queue.enqueue([{"id": 1}, {"id": 2}, {"id": 1}])
|
||||
assert queue.size() == 2
|
||||
assert path.is_file()
|
||||
lines = path.read_text(encoding="utf-8").strip().splitlines()
|
||||
assert len(lines) == 2
|
||||
batch = queue.dequeue_batch(max_items=1)
|
||||
assert batch == [{"id": 1}]
|
||||
remaining = path.read_text(encoding="utf-8").strip().splitlines()
|
||||
assert len(remaining) == 1
|
||||
assert queue.size() == 1
|
||||
|
||||
|
||||
def test_replay_on_connect(tmp_path: Path) -> None:
|
||||
path = tmp_path / "items.jsonl"
|
||||
config = _config(path, dedup_keys=("id",), age_key="created_at", max_age_hours=24)
|
||||
now = datetime.now(UTC)
|
||||
with FileBackedQueueAdapter(config=config) as queue:
|
||||
queue.enqueue(
|
||||
[
|
||||
{"id": 1, "created_at": now - timedelta(hours=1)},
|
||||
{"id": 2, "created_at": now - timedelta(minutes=10)},
|
||||
]
|
||||
)
|
||||
with FileBackedQueueAdapter(config=config) as queue:
|
||||
assert queue.size() == 2
|
||||
assert [item["id"] for item in queue.dequeue_batch(max_items=10)] == [1, 2]
|
||||
|
||||
|
||||
def test_replay_filters_by_max_age(tmp_path: Path) -> None:
|
||||
path = tmp_path / "items.jsonl"
|
||||
now = datetime.now(UTC)
|
||||
path.write_text(
|
||||
"\n".join(
|
||||
[
|
||||
f'{{"id": "old", "created_at": "{(now - timedelta(hours=30)).isoformat()}"}}',
|
||||
f'{{"id": "new", "created_at": "{(now - timedelta(hours=1)).isoformat()}"}}',
|
||||
]
|
||||
)
|
||||
+ "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
config = _config(path, dedup_keys=("id",), age_key="created_at", max_age_hours=24)
|
||||
with FileBackedQueueAdapter(config=config) as queue:
|
||||
assert queue.size() == 1
|
||||
assert queue.dequeue_batch(max_items=10)[0]["id"] == "new"
|
||||
|
||||
|
||||
def test_connect_skips_corrupt_and_invalid_lines(tmp_path: Path) -> None:
|
||||
path = tmp_path / "items.jsonl"
|
||||
path.write_text(
|
||||
"\n".join(
|
||||
[
|
||||
"",
|
||||
"not-json",
|
||||
"[1, 2]",
|
||||
'{"id": 1}',
|
||||
'{"name": "missing-id"}',
|
||||
]
|
||||
)
|
||||
+ "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
config = _config(path, dedup_keys=("id",))
|
||||
with FileBackedQueueAdapter(config=config) as queue:
|
||||
assert queue.size() == 1
|
||||
assert queue.dequeue_batch(max_items=10) == [{"id": 1}]
|
||||
|
||||
|
||||
def test_connect_skips_missing_or_invalid_age_on_replay(tmp_path: Path) -> None:
|
||||
path = tmp_path / "items.jsonl"
|
||||
now = datetime.now(UTC)
|
||||
path.write_text(
|
||||
"\n".join(
|
||||
[
|
||||
'{"id": 1}',
|
||||
'{"id": 2, "created_at": "not-a-date"}',
|
||||
f'{{"id": 3, "created_at": "{now.isoformat()}"}}',
|
||||
]
|
||||
)
|
||||
+ "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
config = _config(path, dedup_keys=("id",), age_key="created_at")
|
||||
with FileBackedQueueAdapter(config=config) as queue:
|
||||
assert queue.size() == 1
|
||||
assert queue.dequeue_batch(max_items=10)[0]["id"] == 3
|
||||
|
||||
|
||||
def test_connect_is_idempotent(tmp_path: Path) -> None:
|
||||
path = tmp_path / "items.jsonl"
|
||||
queue = FileBackedQueueAdapter(config=_config(path))
|
||||
queue.connect()
|
||||
queue.enqueue([{"id": 1}])
|
||||
queue.connect()
|
||||
assert queue.size() == 1
|
||||
queue.disconnect()
|
||||
assert not queue.is_connected()
|
||||
|
||||
|
||||
def test_evict_compacts_file(tmp_path: Path) -> None:
|
||||
path = tmp_path / "items.jsonl"
|
||||
config = _config(path, dedup_keys=("id",), age_key="created_at", max_age_hours=24)
|
||||
now = datetime.now(UTC)
|
||||
with FileBackedQueueAdapter(config=config) as queue:
|
||||
queue.enqueue(
|
||||
[
|
||||
{"id": 1, "created_at": now - timedelta(hours=2)},
|
||||
{"id": 2, "created_at": now - timedelta(minutes=5)},
|
||||
]
|
||||
)
|
||||
removed = queue.evict_older_than(now - timedelta(hours=1))
|
||||
assert removed == 1
|
||||
assert queue.size() == 1
|
||||
lines = [line for line in path.read_text(encoding="utf-8").splitlines() if line]
|
||||
assert len(lines) == 1
|
||||
|
||||
|
||||
def test_auto_evict_on_enqueue(tmp_path: Path) -> None:
|
||||
path = tmp_path / "items.jsonl"
|
||||
config = _config(path, dedup_keys=("id",), age_key="created_at", max_age_hours=1)
|
||||
now = datetime.now(UTC)
|
||||
with FileBackedQueueAdapter(config=config) as queue:
|
||||
queue.enqueue([{"id": 1, "created_at": now - timedelta(hours=2)}])
|
||||
# Item is enqueued then immediately age-evicted.
|
||||
assert queue.size() == 0
|
||||
|
||||
|
||||
def test_from_env_when_config_omitted(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
path = tmp_path / "from-env.jsonl"
|
||||
monkeypatch.setenv("FILE_QUEUE_PATH", str(path))
|
||||
with FileBackedQueueAdapter() as queue:
|
||||
queue.enqueue([{"id": 1}])
|
||||
assert queue.size() == 1
|
||||
|
||||
|
||||
def test_injected_memory(tmp_path: Path) -> None:
|
||||
path = tmp_path / "items.jsonl"
|
||||
memory = MemoryQueueAdapter(dedup_keys=("id",))
|
||||
config = _config(path, dedup_keys=("id",))
|
||||
with FileBackedQueueAdapter(config=config, memory=memory) as queue:
|
||||
queue.enqueue([{"id": 1}])
|
||||
assert memory.size() == 1
|
||||
|
||||
|
||||
def test_empty_enqueue_and_dequeue_skip_compact(tmp_path: Path) -> None:
|
||||
path = tmp_path / "items.jsonl"
|
||||
with FileBackedQueueAdapter(config=_config(path)) as queue:
|
||||
queue.enqueue([])
|
||||
assert queue.dequeue_batch(max_items=10) == []
|
||||
assert queue.evict_older_than(datetime.now(UTC)) == 0
|
||||
assert not path.exists() or path.read_text(encoding="utf-8") == ""
|
||||
|
||||
|
||||
def test_reconnect_replays_after_disconnect(tmp_path: Path) -> None:
|
||||
path = tmp_path / "items.jsonl"
|
||||
queue = FileBackedQueueAdapter(config=_config(path, dedup_keys=("id",)))
|
||||
queue.connect()
|
||||
queue.enqueue([{"id": 1}])
|
||||
queue.disconnect()
|
||||
queue.connect()
|
||||
assert queue.size() == 1
|
||||
queue.disconnect()
|
||||
|
||||
|
||||
def test_json_default_rejects_unsupported() -> None:
|
||||
with pytest.raises(TypeError):
|
||||
FileBackedQueueAdapter._json_default(object())
|
||||
|
||||
|
||||
def test_disconnect_ignores_fsync_oserror(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
path = tmp_path / "items.jsonl"
|
||||
queue = FileBackedQueueAdapter(config=_config(path))
|
||||
queue.connect()
|
||||
|
||||
def boom(_fd: int) -> None:
|
||||
raise OSError("fsync failed")
|
||||
|
||||
monkeypatch.setattr(
|
||||
"python_repositories.adapters.file_backed_queue_adapter.os.fsync",
|
||||
boom,
|
||||
)
|
||||
queue.disconnect()
|
||||
assert not queue.is_connected()
|
||||
|
||||
|
||||
def test_append_items_requires_open_handle(tmp_path: Path) -> None:
|
||||
path = tmp_path / "items.jsonl"
|
||||
queue = FileBackedQueueAdapter(config=_config(path))
|
||||
queue.connect()
|
||||
queue._append_handle = None # noqa: SLF001
|
||||
with pytest.raises(RuntimeError, match="append handle is not open"):
|
||||
queue._append_items([{"id": 1}]) # noqa: SLF001
|
||||
queue._connected = False # noqa: SLF001
|
||||
@@ -0,0 +1,56 @@
|
||||
"""Unit tests for FileQueueConfig."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from python_repositories.config import FileQueueConfig
|
||||
|
||||
|
||||
def test_defaults() -> None:
|
||||
config = FileQueueConfig(path=Path("/tmp/queue.jsonl"))
|
||||
assert config.path == Path("/tmp/queue.jsonl")
|
||||
assert config.max_age_hours == 24
|
||||
assert config.dedup_keys == ()
|
||||
assert config.age_key is None
|
||||
|
||||
|
||||
def test_from_env_loads_path(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setenv("FILE_QUEUE_PATH", "/data/items.jsonl")
|
||||
monkeypatch.delenv("FILE_QUEUE_MAX_AGE_HOURS", raising=False)
|
||||
config = FileQueueConfig.from_env(use_dotenv=False)
|
||||
assert config.path == Path("/data/items.jsonl")
|
||||
assert config.max_age_hours == 24
|
||||
|
||||
|
||||
def test_from_env_loads_max_age_hours(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setenv("FILE_QUEUE_PATH", "/data/items.jsonl")
|
||||
monkeypatch.setenv("FILE_QUEUE_MAX_AGE_HOURS", "48")
|
||||
config = FileQueueConfig.from_env(use_dotenv=False)
|
||||
assert config.max_age_hours == 48
|
||||
|
||||
|
||||
def test_from_env_raises_when_path_missing(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.delenv("FILE_QUEUE_PATH", raising=False)
|
||||
with pytest.raises(Exception):
|
||||
FileQueueConfig.from_env(use_dotenv=False)
|
||||
|
||||
|
||||
def test_from_env_respects_custom_env_var_names(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
monkeypatch.setenv("CUSTOM_QUEUE_PATH", "/custom/q.jsonl")
|
||||
monkeypatch.setenv("CUSTOM_MAX_AGE", "12")
|
||||
config = FileQueueConfig.from_env(
|
||||
"CUSTOM_QUEUE_PATH",
|
||||
max_age_hours_env_var_name="CUSTOM_MAX_AGE",
|
||||
dedup_keys=("id",),
|
||||
age_key="created_at",
|
||||
use_dotenv=False,
|
||||
)
|
||||
assert config.path == Path("/custom/q.jsonl")
|
||||
assert config.max_age_hours == 12
|
||||
assert config.dedup_keys == ("id",)
|
||||
assert config.age_key == "created_at"
|
||||
@@ -0,0 +1,149 @@
|
||||
"""Unit tests for MemoryQueueAdapter."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import UTC, datetime, timedelta
|
||||
import threading
|
||||
|
||||
import pytest
|
||||
|
||||
from python_repositories.adapters.memory_queue_adapter import MemoryQueueAdapter
|
||||
from python_repositories.interfaces.queue_repository_interface import (
|
||||
QueueRepositoryInterface,
|
||||
)
|
||||
|
||||
|
||||
def test_implements_interface() -> None:
|
||||
assert issubclass(MemoryQueueAdapter, QueueRepositoryInterface)
|
||||
|
||||
|
||||
def test_fifo_without_dedup() -> None:
|
||||
queue = MemoryQueueAdapter()
|
||||
queue.enqueue([{"id": 1}, {"id": 2}, {"id": 3}])
|
||||
assert queue.size() == 3
|
||||
assert queue.dequeue_batch(max_items=2) == [{"id": 1}, {"id": 2}]
|
||||
assert queue.dequeue_batch(max_items=10) == [{"id": 3}]
|
||||
assert queue.dequeue_batch() == []
|
||||
assert queue.size() == 0
|
||||
|
||||
|
||||
def test_empty_enqueue_is_noop() -> None:
|
||||
queue = MemoryQueueAdapter()
|
||||
queue.enqueue([])
|
||||
assert queue.size() == 0
|
||||
assert queue.enqueue_and_return_added([]) == []
|
||||
|
||||
|
||||
def test_dedup_keeps_first() -> None:
|
||||
queue = MemoryQueueAdapter(dedup_keys=("id",))
|
||||
queue.enqueue([{"id": 1, "v": "a"}, {"id": 1, "v": "b"}, {"id": 2, "v": "c"}])
|
||||
assert queue.size() == 2
|
||||
assert queue.dequeue_batch(max_items=10) == [
|
||||
{"id": 1, "v": "a"},
|
||||
{"id": 2, "v": "c"},
|
||||
]
|
||||
|
||||
|
||||
def test_missing_dedup_key_raises() -> None:
|
||||
queue = MemoryQueueAdapter(dedup_keys=("id",))
|
||||
with pytest.raises(ValueError, match="missing dedup key"):
|
||||
queue.enqueue([{"name": "x"}])
|
||||
|
||||
|
||||
def test_missing_age_key_raises() -> None:
|
||||
queue = MemoryQueueAdapter(age_key="created_at")
|
||||
with pytest.raises(ValueError, match="missing age key"):
|
||||
queue.enqueue([{"id": 1}])
|
||||
|
||||
|
||||
def test_evict_older_than() -> None:
|
||||
queue = MemoryQueueAdapter(age_key="created_at", dedup_keys=("id",))
|
||||
now = datetime.now(UTC)
|
||||
queue.enqueue(
|
||||
[
|
||||
{"id": 1, "created_at": now - timedelta(hours=2)},
|
||||
{"id": 2, "created_at": now - timedelta(minutes=30)},
|
||||
{"id": 3, "created_at": (now - timedelta(hours=3)).isoformat()},
|
||||
]
|
||||
)
|
||||
removed = queue.evict_older_than(now - timedelta(hours=1))
|
||||
assert removed == 2
|
||||
assert queue.size() == 1
|
||||
remaining = queue.dequeue_batch(max_items=10)
|
||||
assert remaining[0]["id"] == 2
|
||||
|
||||
|
||||
def test_evict_without_age_key_is_noop() -> None:
|
||||
queue = MemoryQueueAdapter()
|
||||
queue.enqueue([{"id": 1}])
|
||||
assert queue.evict_older_than(datetime.now(UTC)) == 0
|
||||
assert queue.size() == 1
|
||||
|
||||
|
||||
def test_parse_age_rejects_unsupported_type() -> None:
|
||||
with pytest.raises(ValueError, match="age value must be"):
|
||||
MemoryQueueAdapter._parse_age(123)
|
||||
|
||||
|
||||
def test_parse_age_aware_datetime() -> None:
|
||||
from datetime import timezone
|
||||
|
||||
eastern = timezone(timedelta(hours=-5))
|
||||
aware = datetime(2024, 1, 1, 12, 0, 0, tzinfo=eastern)
|
||||
assert MemoryQueueAdapter._parse_age(aware) == datetime(
|
||||
2024, 1, 1, 17, 0, 0, tzinfo=UTC
|
||||
)
|
||||
|
||||
|
||||
def test_parse_age_naive_datetime() -> None:
|
||||
naive = datetime(2024, 1, 1, 12, 0, 0)
|
||||
assert MemoryQueueAdapter._parse_age(naive) == datetime(
|
||||
2024, 1, 1, 12, 0, 0, tzinfo=UTC
|
||||
)
|
||||
|
||||
|
||||
def test_parse_age_zulu_string() -> None:
|
||||
assert MemoryQueueAdapter._parse_age("2024-01-01T00:00:00Z") == datetime(
|
||||
2024, 1, 1, 0, 0, 0, tzinfo=UTC
|
||||
)
|
||||
|
||||
|
||||
def test_parse_age_naive_string() -> None:
|
||||
assert MemoryQueueAdapter._parse_age("2024-01-01T00:00:00") == datetime(
|
||||
2024, 1, 1, 0, 0, 0, tzinfo=UTC
|
||||
)
|
||||
|
||||
|
||||
def test_dequeue_rejects_negative_max_items() -> None:
|
||||
queue = MemoryQueueAdapter()
|
||||
with pytest.raises(ValueError, match="max_items"):
|
||||
queue.dequeue_batch(max_items=-1)
|
||||
|
||||
|
||||
def test_clear_and_snapshot() -> None:
|
||||
queue = MemoryQueueAdapter()
|
||||
queue.enqueue([{"id": 1}, {"id": 2}])
|
||||
assert queue.snapshot() == [{"id": 1}, {"id": 2}]
|
||||
queue.clear()
|
||||
assert queue.size() == 0
|
||||
assert queue.snapshot() == []
|
||||
|
||||
|
||||
def test_thread_safety_smoke() -> None:
|
||||
queue = MemoryQueueAdapter(dedup_keys=("id",))
|
||||
errors: list[BaseException] = []
|
||||
|
||||
def worker(start: int) -> None:
|
||||
try:
|
||||
for i in range(start, start + 50):
|
||||
queue.enqueue([{"id": i}])
|
||||
except BaseException as exc: # noqa: BLE001
|
||||
errors.append(exc)
|
||||
|
||||
threads = [threading.Thread(target=worker, args=(i * 50,)) for i in range(4)]
|
||||
for thread in threads:
|
||||
thread.start()
|
||||
for thread in threads:
|
||||
thread.join()
|
||||
assert errors == []
|
||||
assert queue.size() == 200
|
||||
@@ -0,0 +1,237 @@
|
||||
"""Unit tests for PostgresAdapter instantiation and injection."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import psycopg
|
||||
import pytest
|
||||
|
||||
from python_repositories.adapters.postgres_adapter import PostgresAdapter
|
||||
from python_repositories.interfaces import TableRepositoryInterface
|
||||
from tests.conftest import TEST_POSTGRES_CONFIG
|
||||
|
||||
|
||||
def _mock_cursor() -> MagicMock:
|
||||
mock_cursor = MagicMock()
|
||||
mock_cursor.__enter__ = MagicMock(return_value=mock_cursor)
|
||||
mock_cursor.__exit__ = MagicMock(return_value=False)
|
||||
return mock_cursor
|
||||
|
||||
|
||||
def _mock_client(*, probe_raises: Exception | None = None) -> MagicMock:
|
||||
mock_client = MagicMock(spec=psycopg.Connection)
|
||||
mock_cursor = _mock_cursor()
|
||||
if probe_raises is not None:
|
||||
mock_cursor.execute.side_effect = probe_raises
|
||||
mock_client.cursor.return_value = mock_cursor
|
||||
return mock_client
|
||||
|
||||
|
||||
def test_should_adhere_to_interface() -> None:
|
||||
assert issubclass(PostgresAdapter, TableRepositoryInterface)
|
||||
_ = PostgresAdapter(config=TEST_POSTGRES_CONFIG)
|
||||
|
||||
|
||||
def test_should_have_logger_when_instantiated() -> None:
|
||||
adapter = PostgresAdapter(config=TEST_POSTGRES_CONFIG)
|
||||
assert hasattr(adapter, "logger")
|
||||
assert adapter.logger is not None
|
||||
|
||||
|
||||
def test_should_not_be_connected_when_instantiated() -> None:
|
||||
adapter = PostgresAdapter(config=TEST_POSTGRES_CONFIG)
|
||||
assert adapter._client is None
|
||||
assert not adapter.is_connected()
|
||||
|
||||
|
||||
def test_constructs_with_injected_config_without_env(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
monkeypatch.delenv("POSTGRES_URI", raising=False)
|
||||
monkeypatch.delenv("POSTGRES_TABLE", raising=False)
|
||||
adapter = PostgresAdapter(config=TEST_POSTGRES_CONFIG)
|
||||
assert adapter._config == TEST_POSTGRES_CONFIG
|
||||
|
||||
|
||||
def test_constructs_with_injected_client_without_env(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
monkeypatch.delenv("POSTGRES_URI", raising=False)
|
||||
monkeypatch.delenv("POSTGRES_TABLE", raising=False)
|
||||
mock_client = _mock_client()
|
||||
adapter = PostgresAdapter(config=TEST_POSTGRES_CONFIG, client=mock_client)
|
||||
assert adapter._client is mock_client
|
||||
assert adapter._client_injected is True
|
||||
|
||||
|
||||
def test_raises_when_client_provided_without_config() -> None:
|
||||
mock_client = MagicMock(spec=psycopg.Connection)
|
||||
with pytest.raises(ValueError, match="config is required"):
|
||||
PostgresAdapter(client=mock_client)
|
||||
|
||||
|
||||
def test_disconnect_does_not_close_injected_client() -> None:
|
||||
mock_client = _mock_client()
|
||||
adapter = PostgresAdapter(config=TEST_POSTGRES_CONFIG, client=mock_client)
|
||||
adapter.disconnect()
|
||||
mock_client.close.assert_not_called()
|
||||
assert adapter._client is None
|
||||
|
||||
|
||||
class CustomEnvPostgresAdapter(PostgresAdapter):
|
||||
uri_env_var_name = "CUSTOM_POSTGRES_URI"
|
||||
table_env_var_name = "CUSTOM_POSTGRES_TABLE"
|
||||
primary_key_env_var_name = "CUSTOM_POSTGRES_PRIMARY_KEY"
|
||||
|
||||
|
||||
def test_subclass_custom_env_var_names(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setenv("CUSTOM_POSTGRES_URI", "postgresql://custom/mydb")
|
||||
monkeypatch.setenv("CUSTOM_POSTGRES_TABLE", "items")
|
||||
monkeypatch.setenv("CUSTOM_POSTGRES_PRIMARY_KEY", "item_id")
|
||||
adapter = CustomEnvPostgresAdapter()
|
||||
assert adapter._config.uri == "postgresql://custom/mydb"
|
||||
assert adapter._config.table == "items"
|
||||
assert adapter._config.primary_key == "item_id"
|
||||
|
||||
|
||||
def test_connect_with_injected_client_succeeds_when_probe_ok() -> None:
|
||||
mock_client = _mock_client()
|
||||
adapter = PostgresAdapter(config=TEST_POSTGRES_CONFIG, client=mock_client)
|
||||
|
||||
adapter.connect()
|
||||
|
||||
assert mock_client.cursor.return_value.execute.call_count == 2
|
||||
|
||||
|
||||
def test_connect_with_injected_client_raises_on_probe_failure() -> None:
|
||||
mock_client = _mock_client(probe_raises=psycopg.OperationalError("connection lost"))
|
||||
adapter = PostgresAdapter(config=TEST_POSTGRES_CONFIG, client=mock_client)
|
||||
|
||||
with pytest.raises(ConnectionError, match="Could not connect to Postgres"):
|
||||
adapter.connect()
|
||||
|
||||
|
||||
def test_connect_with_injected_client_skips_validation_when_client_cleared() -> None:
|
||||
mock_client = _mock_client()
|
||||
adapter = PostgresAdapter(config=TEST_POSTGRES_CONFIG, client=mock_client)
|
||||
adapter.disconnect()
|
||||
|
||||
adapter.connect()
|
||||
|
||||
mock_client.cursor.assert_not_called()
|
||||
|
||||
|
||||
def test_connect_reconnects_when_existing_client_unhealthy(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
stale_client = _mock_client(
|
||||
probe_raises=psycopg.OperationalError("connection lost")
|
||||
)
|
||||
new_client = _mock_client()
|
||||
adapter = PostgresAdapter(config=TEST_POSTGRES_CONFIG)
|
||||
adapter._client = stale_client
|
||||
|
||||
monkeypatch.setattr("psycopg.connect", lambda *args, **kwargs: new_client)
|
||||
|
||||
adapter.connect()
|
||||
|
||||
stale_client.close.assert_called_once()
|
||||
assert adapter._client is new_client
|
||||
|
||||
|
||||
def test_connect_skips_reconnect_when_already_connected(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
healthy_client = _mock_client()
|
||||
adapter = PostgresAdapter(config=TEST_POSTGRES_CONFIG)
|
||||
adapter._client = healthy_client
|
||||
|
||||
connect = MagicMock()
|
||||
monkeypatch.setattr("psycopg.connect", connect)
|
||||
|
||||
adapter.connect()
|
||||
|
||||
healthy_client.close.assert_not_called()
|
||||
connect.assert_not_called()
|
||||
assert adapter._client is healthy_client
|
||||
|
||||
|
||||
def test_fetch_one_raises_value_error_on_invalid_pk() -> None:
|
||||
mock_client = _mock_client()
|
||||
adapter = PostgresAdapter(config=TEST_POSTGRES_CONFIG, client=mock_client)
|
||||
|
||||
with pytest.raises(ValueError, match="Primary key must not be None"):
|
||||
adapter.fetch_one(None)
|
||||
|
||||
|
||||
def test_fetch_one_raises_connection_error_when_not_connected() -> None:
|
||||
adapter = PostgresAdapter(config=TEST_POSTGRES_CONFIG)
|
||||
|
||||
with pytest.raises(ConnectionError):
|
||||
adapter.fetch_one("some-id")
|
||||
|
||||
|
||||
def test_upsert_raises_value_error_on_invalid_row() -> None:
|
||||
mock_client = _mock_client()
|
||||
adapter = PostgresAdapter(config=TEST_POSTGRES_CONFIG, client=mock_client)
|
||||
|
||||
with pytest.raises(ValueError, match="Row must be a dictionary"):
|
||||
adapter.upsert("not-a-dict") # type: ignore[arg-type]
|
||||
|
||||
|
||||
def test_upsert_raises_value_error_when_primary_key_missing() -> None:
|
||||
mock_client = _mock_client()
|
||||
adapter = PostgresAdapter(config=TEST_POSTGRES_CONFIG, client=mock_client)
|
||||
|
||||
with pytest.raises(ValueError, match="Row must include primary key column 'id'"):
|
||||
adapter.upsert({"name": "Alice"})
|
||||
|
||||
|
||||
def test_upsert_raises_connection_error_when_not_connected() -> None:
|
||||
adapter = PostgresAdapter(config=TEST_POSTGRES_CONFIG)
|
||||
|
||||
with pytest.raises(ConnectionError):
|
||||
adapter.upsert({"id": "alice", "name": "Alice"})
|
||||
|
||||
|
||||
def test_fetch_all_raises_value_error_on_invalid_limit() -> None:
|
||||
mock_client = _mock_client()
|
||||
adapter = PostgresAdapter(config=TEST_POSTGRES_CONFIG, client=mock_client)
|
||||
|
||||
with pytest.raises(ValueError, match="Limit must be a non-negative integer"):
|
||||
adapter.fetch_all(limit=-1)
|
||||
|
||||
|
||||
def test_execute_raises_value_error_on_invalid_sql() -> None:
|
||||
mock_client = _mock_client()
|
||||
adapter = PostgresAdapter(config=TEST_POSTGRES_CONFIG, client=mock_client)
|
||||
|
||||
with pytest.raises(ValueError, match="SQL must be a non-empty string"):
|
||||
adapter.execute("")
|
||||
|
||||
|
||||
def test_execute_raises_value_error_on_invalid_params() -> None:
|
||||
mock_client = _mock_client()
|
||||
adapter = PostgresAdapter(config=TEST_POSTGRES_CONFIG, client=mock_client)
|
||||
|
||||
with pytest.raises(ValueError, match="Params must be a tuple"):
|
||||
adapter.execute("SELECT 1", []) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def test_upsert_with_primary_key_only_uses_do_nothing() -> None:
|
||||
mock_client = _mock_client()
|
||||
adapter = PostgresAdapter(config=TEST_POSTGRES_CONFIG, client=mock_client)
|
||||
|
||||
adapter.upsert({"id": "pk-only"})
|
||||
|
||||
mock_cursor = mock_client.cursor.return_value.__enter__.return_value
|
||||
insert_call = mock_cursor.execute.call_args_list[-1]
|
||||
assert insert_call[0][1] == ("pk-only",)
|
||||
|
||||
|
||||
def test_execute_raises_connection_error_when_not_connected() -> None:
|
||||
adapter = PostgresAdapter(config=TEST_POSTGRES_CONFIG)
|
||||
|
||||
with pytest.raises(ConnectionError):
|
||||
adapter.execute("SELECT 1")
|
||||
@@ -0,0 +1,69 @@
|
||||
"""Unit tests for PostgresConfig."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from python_repositories.config import PostgresConfig
|
||||
|
||||
|
||||
def _set_required_postgres_env(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setenv("POSTGRES_URI", "postgresql://localhost/mydb")
|
||||
monkeypatch.setenv("POSTGRES_TABLE", "users")
|
||||
|
||||
|
||||
def test_from_env_loads_all_fields(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
_set_required_postgres_env(monkeypatch)
|
||||
config = PostgresConfig.from_env(use_dotenv=False)
|
||||
assert config.uri == "postgresql://localhost/mydb"
|
||||
assert config.table == "users"
|
||||
assert config.primary_key == "id"
|
||||
|
||||
|
||||
def test_from_env_reads_primary_key_from_env(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
_set_required_postgres_env(monkeypatch)
|
||||
monkeypatch.setenv("POSTGRES_PRIMARY_KEY", "user_id")
|
||||
config = PostgresConfig.from_env(use_dotenv=False)
|
||||
assert config.primary_key == "user_id"
|
||||
|
||||
|
||||
def test_from_env_defaults_primary_key_to_id_when_unset(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
_set_required_postgres_env(monkeypatch)
|
||||
monkeypatch.delenv("POSTGRES_PRIMARY_KEY", raising=False)
|
||||
config = PostgresConfig.from_env(use_dotenv=False)
|
||||
assert config.primary_key == "id"
|
||||
|
||||
|
||||
def test_from_env_raises_when_uri_missing(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.delenv("POSTGRES_URI", raising=False)
|
||||
monkeypatch.setenv("POSTGRES_TABLE", "users")
|
||||
with pytest.raises(Exception):
|
||||
PostgresConfig.from_env(use_dotenv=False)
|
||||
|
||||
|
||||
def test_from_env_raises_when_table_missing(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setenv("POSTGRES_URI", "postgresql://localhost/mydb")
|
||||
monkeypatch.delenv("POSTGRES_TABLE", raising=False)
|
||||
with pytest.raises(Exception):
|
||||
PostgresConfig.from_env(use_dotenv=False)
|
||||
|
||||
|
||||
def test_from_env_respects_custom_env_var_names(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
monkeypatch.setenv("CUSTOM_POSTGRES_URI", "postgresql://custom/mydb")
|
||||
monkeypatch.setenv("CUSTOM_POSTGRES_TABLE", "items")
|
||||
monkeypatch.setenv("CUSTOM_POSTGRES_PRIMARY_KEY", "item_id")
|
||||
config = PostgresConfig.from_env(
|
||||
"CUSTOM_POSTGRES_URI",
|
||||
"CUSTOM_POSTGRES_TABLE",
|
||||
"CUSTOM_POSTGRES_PRIMARY_KEY",
|
||||
use_dotenv=False,
|
||||
)
|
||||
assert config.uri == "postgresql://custom/mydb"
|
||||
assert config.table == "items"
|
||||
assert config.primary_key == "item_id"
|
||||
@@ -0,0 +1,100 @@
|
||||
"""Unit tests for QueueRepositoryInterface."""
|
||||
|
||||
from datetime import UTC, datetime
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from python_repositories.interfaces.queue_repository_interface import (
|
||||
QueueRepositoryInterface,
|
||||
)
|
||||
|
||||
|
||||
class InMemoryQueueRepo:
|
||||
"""Plain class that satisfies QueueRepositoryInterface without inheritance."""
|
||||
|
||||
def enqueue(self, items: list[dict[str, Any]]) -> None:
|
||||
pass
|
||||
|
||||
def dequeue_batch(self, *, max_items: int = 1000) -> list[dict[str, Any]]:
|
||||
del max_items
|
||||
return []
|
||||
|
||||
def size(self) -> int:
|
||||
return 0
|
||||
|
||||
def evict_older_than(self, cutoff: datetime) -> int:
|
||||
del cutoff
|
||||
return 0
|
||||
|
||||
|
||||
def accepts_queue_repo(repo: QueueRepositoryInterface) -> None:
|
||||
"""Type-checking hook for QueueRepositoryInterface structural subtyping."""
|
||||
repo.size()
|
||||
|
||||
|
||||
def test_instantiation_fails_when_enqueue_not_implemented() -> None:
|
||||
class Incomplete(QueueRepositoryInterface):
|
||||
def dequeue_batch(self, *, max_items: int = 1000) -> list[dict[str, Any]]:
|
||||
return []
|
||||
|
||||
def size(self) -> int:
|
||||
return 0
|
||||
|
||||
def evict_older_than(self, cutoff: datetime) -> int:
|
||||
return 0
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
_ = Incomplete() # type: ignore
|
||||
|
||||
|
||||
def test_instantiation_fails_when_dequeue_batch_not_implemented() -> None:
|
||||
class Incomplete(QueueRepositoryInterface):
|
||||
def enqueue(self, items: list[dict[str, Any]]) -> None:
|
||||
pass
|
||||
|
||||
def size(self) -> int:
|
||||
return 0
|
||||
|
||||
def evict_older_than(self, cutoff: datetime) -> int:
|
||||
return 0
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
_ = Incomplete() # type: ignore
|
||||
|
||||
|
||||
def test_instantiation_fails_when_size_not_implemented() -> None:
|
||||
class Incomplete(QueueRepositoryInterface):
|
||||
def enqueue(self, items: list[dict[str, Any]]) -> None:
|
||||
pass
|
||||
|
||||
def dequeue_batch(self, *, max_items: int = 1000) -> list[dict[str, Any]]:
|
||||
return []
|
||||
|
||||
def evict_older_than(self, cutoff: datetime) -> int:
|
||||
return 0
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
_ = Incomplete() # type: ignore
|
||||
|
||||
|
||||
def test_instantiation_fails_when_evict_older_than_not_implemented() -> None:
|
||||
class Incomplete(QueueRepositoryInterface):
|
||||
def enqueue(self, items: list[dict[str, Any]]) -> None:
|
||||
pass
|
||||
|
||||
def dequeue_batch(self, *, max_items: int = 1000) -> list[dict[str, Any]]:
|
||||
return []
|
||||
|
||||
def size(self) -> int:
|
||||
return 0
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
_ = Incomplete() # type: ignore
|
||||
|
||||
|
||||
def test_structural_subtyping() -> None:
|
||||
repo: QueueRepositoryInterface = InMemoryQueueRepo()
|
||||
accepts_queue_repo(repo)
|
||||
assert isinstance(repo, QueueRepositoryInterface)
|
||||
assert repo.evict_older_than(datetime.now(UTC)) == 0
|
||||
@@ -0,0 +1,162 @@
|
||||
"""Unit tests for TableRepositoryInterface."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from python_repositories.interfaces.table_repository_interface import (
|
||||
TableRepositoryInterface,
|
||||
)
|
||||
|
||||
|
||||
class InMemoryTableRepo:
|
||||
"""Plain class that satisfies TableRepositoryInterface without inheritance."""
|
||||
|
||||
def fetch_one(self, pk: Any) -> dict[str, Any] | None:
|
||||
return None
|
||||
|
||||
def fetch_all(self, *, limit: int | None = None) -> list[dict[str, Any]]:
|
||||
del limit
|
||||
return []
|
||||
|
||||
def upsert(self, row: dict[str, Any]) -> None:
|
||||
pass
|
||||
|
||||
def delete(self, pk: Any) -> None:
|
||||
pass
|
||||
|
||||
def execute(
|
||||
self,
|
||||
sql: str,
|
||||
params: tuple[Any, ...] = (),
|
||||
) -> list[dict[str, Any]]:
|
||||
del sql, params
|
||||
return []
|
||||
|
||||
|
||||
def accepts_table_repo(repo: TableRepositoryInterface) -> None:
|
||||
"""Type-checking hook for TableRepositoryInterface structural subtyping."""
|
||||
repo.fetch_one("pk")
|
||||
|
||||
|
||||
def test_instantiation_fails_when_fetch_one_not_implemented() -> None:
|
||||
"""Test that instantiation fails if fetch_one is not implemented."""
|
||||
|
||||
class Incomplete(TableRepositoryInterface):
|
||||
def fetch_all(self, *, limit: int | None = None) -> list[dict[str, Any]]:
|
||||
return []
|
||||
|
||||
def upsert(self, row: dict[str, Any]) -> None:
|
||||
pass
|
||||
|
||||
def delete(self, pk: Any) -> None:
|
||||
pass
|
||||
|
||||
def execute(
|
||||
self,
|
||||
sql: str,
|
||||
params: tuple[Any, ...] = (),
|
||||
) -> list[dict[str, Any]]:
|
||||
return []
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
_ = Incomplete() # type: ignore
|
||||
|
||||
|
||||
def test_instantiation_fails_when_fetch_all_not_implemented() -> None:
|
||||
"""Test that instantiation fails if fetch_all is not implemented."""
|
||||
|
||||
class Incomplete(TableRepositoryInterface):
|
||||
def fetch_one(self, pk: Any) -> dict[str, Any] | None:
|
||||
return None
|
||||
|
||||
def upsert(self, row: dict[str, Any]) -> None:
|
||||
pass
|
||||
|
||||
def delete(self, pk: Any) -> None:
|
||||
pass
|
||||
|
||||
def execute(
|
||||
self,
|
||||
sql: str,
|
||||
params: tuple[Any, ...] = (),
|
||||
) -> list[dict[str, Any]]:
|
||||
return []
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
_ = Incomplete() # type: ignore
|
||||
|
||||
|
||||
def test_instantiation_fails_when_upsert_not_implemented() -> None:
|
||||
"""Test that instantiation fails if upsert is not implemented."""
|
||||
|
||||
class Incomplete(TableRepositoryInterface):
|
||||
def fetch_one(self, pk: Any) -> dict[str, Any] | None:
|
||||
return None
|
||||
|
||||
def fetch_all(self, *, limit: int | None = None) -> list[dict[str, Any]]:
|
||||
return []
|
||||
|
||||
def delete(self, pk: Any) -> None:
|
||||
pass
|
||||
|
||||
def execute(
|
||||
self,
|
||||
sql: str,
|
||||
params: tuple[Any, ...] = (),
|
||||
) -> list[dict[str, Any]]:
|
||||
return []
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
_ = Incomplete() # type: ignore
|
||||
|
||||
|
||||
def test_instantiation_fails_when_delete_not_implemented() -> None:
|
||||
"""Test that instantiation fails if delete is not implemented."""
|
||||
|
||||
class Incomplete(TableRepositoryInterface):
|
||||
def fetch_one(self, pk: Any) -> dict[str, Any] | None:
|
||||
return None
|
||||
|
||||
def fetch_all(self, *, limit: int | None = None) -> list[dict[str, Any]]:
|
||||
return []
|
||||
|
||||
def upsert(self, row: dict[str, Any]) -> None:
|
||||
pass
|
||||
|
||||
def execute(
|
||||
self,
|
||||
sql: str,
|
||||
params: tuple[Any, ...] = (),
|
||||
) -> list[dict[str, Any]]:
|
||||
return []
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
_ = Incomplete() # type: ignore
|
||||
|
||||
|
||||
def test_instantiation_fails_when_execute_not_implemented() -> None:
|
||||
"""Test that instantiation fails if execute is not implemented."""
|
||||
|
||||
class Incomplete(TableRepositoryInterface):
|
||||
def fetch_one(self, pk: Any) -> dict[str, Any] | None:
|
||||
return None
|
||||
|
||||
def fetch_all(self, *, limit: int | None = None) -> list[dict[str, Any]]:
|
||||
return []
|
||||
|
||||
def upsert(self, row: dict[str, Any]) -> None:
|
||||
pass
|
||||
|
||||
def delete(self, pk: Any) -> None:
|
||||
pass
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
_ = Incomplete() # type: ignore
|
||||
|
||||
|
||||
def test_structural_subtyping() -> None:
|
||||
"""Test that a plain class satisfies TableRepositoryInterface structurally."""
|
||||
repo: TableRepositoryInterface = InMemoryTableRepo()
|
||||
accepts_table_repo(repo)
|
||||
assert isinstance(repo, TableRepositoryInterface)
|
||||
@@ -146,59 +146,87 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "cffi"
|
||||
version = "2.0.0"
|
||||
version = "2.1.0"
|
||||
source = { registry = "https://proxpi.lille-vemmelund.dk/index/" }
|
||||
dependencies = [
|
||||
{ name = "pycparser", marker = "implementation_name != 'PyPy'" },
|
||||
]
|
||||
sdist = { url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529" }
|
||||
sdist = { url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0.tar.gz", hash = "sha256:efc1cdd798b1aaf39b4610bba7aad28c9bea9b910f25c784ccf9ec1fa719d1f9" }
|
||||
wheels = [
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:df2b82571a1b30f58a87bf4e5a9e78d2b1eff6c6ce8fd3aa3757221f93f0863f" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78474632761faa0fb96f30b1c928c84ebcf68713cbb80d15bab09dfe61640fde" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:5972433ad71a9e46516584ef60a0fda12d9dc459938d1539c3ddecf9bdc1368d" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b6422532152adf4e59b110cb2808cee7a033800952f5c036b4af047ee43199e7" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:46b1c8db8f6122420f32d02fffb924c2fe9bc772d228c7c711748fff56aabb2b" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9fafc5aa2e2a39aaf7f8cc0c1f044a9b07fca12e558dca53a3cc5c654ad67a7" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1e9f50d192a3e525b15a75ab5114e442d83d657b7ec29182a991bc9a88fd3a66" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98fff996e983a36d3aa2eca83af40c5821202e7e6f32d13ae94e3d2286f10cfe" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:379de10ce1ba048b1448599d1b37b24caee16309d1ac98d3982fc997f768700b" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp312-cp312-win32.whl", hash = "sha256:9b8f0f26ca4e7513c534d351eca551947d053fac438f2a04ac96d882909b0d3a" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:c97f080ea627e2863524c5af3836e2270b5f5dfff1f104392b959f8df0c5d384" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:6d194185eabd279f1c05ebe3504265ddfc5ad2b58d0714f7db9f01da592e9eb6" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:10537b1df4967ca26d21e5072d7d54188354483b91dc75058968d3f0cf13fbda" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a95b05f9baf29b91171b3a8bd2020b028835243e7b0ff6bb23e2a3c228518b1b" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:15faec4adfff450819f3aee0e2e02c812de6edb88203aa58807955db2003472a" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:716ff8ec22f20b4d988b12884086bcef0fc99737043e503f7a3935a6be99b1ea" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:63960549e4f8dc41e31accb97b975abaecfc44c03e396c093a6436763c2ea7db" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ff067a8d8d880e7809e4ac88eb009bb848870115317b306666502ccad30b147f" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:3b926723c13eba9f81d2ef3820d63aeceec3b2d4639906047bf675cb8a7a500d" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:47ff3a8bfd8cb9da1af7524b965127095055654c177fcfc7578debcb015eecd0" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:799416bae98336e400981ff6e532d67d5c709cfb30afb79865a1315f94b0e224" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:961be50688f7fba2fa65f63712d3b9b341a22311f5253460ce933f52f0de1c8c" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bf5c6cf48238b0eb4c086978c492ad1cbc22373fc5b2d7353b3a598ce6db887a" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp313-cp313-win32.whl", hash = "sha256:db3eb7d46527159a878ec3460e9d40615bc25ba337d477db681aea6e4f05c5d2" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:8e74a6135550c4748af665b1b1118b6aab33b1fc6a16f9aff630af107c3b4512" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:2282cd5e38aa8accd03e99d1256af8411c84cdbee6a89d841b563fdbd1f3e50f" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d2117334c3af3bdcb9a88522b844a2bdb5efdc4f71c6c822df55486ae1c3347a" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:702c436735fbe99d59ada02a1f65cfc0d31c0ee8b7290912f8fbc5cd1e4b16c3" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1ff3456eab0d889592d1936d6125bbfbc7ae4d3354a700f8bd80450a66445d4d" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c4165821e131d6d4ca444347c2b694e2311bcfa3fe5a861cc72968f28867beac" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:276f20fffd7b396e12516ba8edf9509210ac248cbbc5acbc39cd512f9f59ebe6" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7d5980a3433d4b71a5e120f9dd551403d7824e31e2e67124fe2769c404c06913" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6ca4919c6e4f89aa99c42510b42cf54596892c00b3f9077f6bdd1505e24b9c8d" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d53d10f7da99ae46f7373b9150393e9c5eab9b224909982b43832668de4779f5" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c351efb95e832a853a29361675f33a7ce53de1a109cd73fd47af0712213aa4ce" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:dbf7c7a88e2bac086f06d14577332760bdeecc42bdec8ac4077f6260557d9326" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp314-cp314-win32.whl", hash = "sha256:1854b724d00f6654c742097d5387569021be12d3a0f770eae1df8f8acfcc6acd" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:1b96bfe2c4bd825681b7d311ad6d9b7280a091f43e8f63da5729638083cd3bfb" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:7d28dff1db6764108bc30788d85d61c876beff416d9a49cb9dd7c5a9f34f5804" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7ea6b3e2c4250ff1de21c630fe72d0f63eb95c2c32ffbf64a358cf4a8836d714" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6af371f3767faeffc6ac1ef57cdfd25844403e9d3f476c5537caee499de96376" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb4e8997a49aa2c08a3e43c9045d224448b8941d88e7ac163c7d383e560cbf98" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:bf01d8c84cbea96b944c73b22182e6c7c432b3475632b8111dbfdc95ddad6e13" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:33eb1ad83ebe8f313e0df035c406227d55a79456704a863fad9842136af5ad7d" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ac0f1a2d0cfa7eea3f2aaf006ab6e70e8feeb16b75d65b7e5939982ca2f11056" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c16914df9fb7f500e440e6875fa23ff5e0b31db01fa9c06af98d59a91f0dc2e4" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5ecbd0499275d57506d397eebe1981cee87b47fcd9ef5c22cab7ed7644a39a94" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp314-cp314t-win32.whl", hash = "sha256:7d034dcffa09e9a46c93fa3a3be402096cb5354ac6e41ab8e5cc9cd8b642ad76" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0582a58f3051372229ca8e7f5f589f9e5632678208d8636fea3676711fdf7fe5" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:510aeeeac94811b138077451da1fb18b308a5feab47dd2b603af55804155e1c8" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:2e9dabb9abcb7ad15938c7196ad5c1718a4e6d33cc79b4c0209bdb64c4a54a5c" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:37f525a7e7e50c017fdebe58b787be310ad59357ae43a053943a6e1a6c526001" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:95f2954c2c9473d892eca6e0409f3568b37ab62a8eedb122461f73cc273476e3" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:cdf2448aab5f661c9315308ec8b93f4e8a1a67a3c733f8631067a2b67d5913dc" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:90bec57cf82089383bd06a605b3eb8daebf7e5a668520beaf6e327a83a947699" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6274dcb2d15cef48daa73ed1be5a40d501d74dccd0cd6db364776d12cb6ba022" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:2b71d409cccee78310ab5dec549aed052aaea483346e282c7b02362596e01bb0" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7d3538f9c0e50670f4deb93dbb696576e60590369cae2faf7de681e597a8a1f1" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:8f9ec95b8a043d3dfbc74d9abc6f7baf524dd27a8dc160b0a32ff9cdab650c28" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:af5e2915d41fe6c961694d7bfdc8562942638200f3ce2765dfb8b745cf997629" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp315-cp315-win32.whl", hash = "sha256:0a42c688d19fca6e095a53c6a6e2295a5b050a8b289f109adab02a9e61a25de6" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp315-cp315-win_amd64.whl", hash = "sha256:bccbbb5ee76a61f9d99b5bf3846a51d7fca4b6a732fe46f89295610edaf41853" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp315-cp315-win_arm64.whl", hash = "sha256:8d35c139744adb3e727cd51b1a18324bbe44b8bd41bf8322bca4d41289f48eda" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:f9912624a0c0b834b7520d7769b3644453aabc0a7e1c839da7359f050750e9bc" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:df92f2aba50eb4d96718b68ef76f2e57a57b54f2fa62333496d16c6d585a85ca" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0520e1f4c35f44e209cbbb421b67eec42e6a157f59444dfb6058874ff3610e5d" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:3681e031db29958a7502f5c0c9d6bbc4c36cb20f7b104086fa642d1799631ff8" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:762f99479dcb369f60ab9017ad4ab97a36a1dd7c1ee5a3b15db0f4b8659120cd" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0611e7ebf90573a535ebdc33ae9da222d037853983e13359f580fab781ca017f" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:86cf8755a791f72c85dc287128cc62d4f24d392e3f1e15837245623f4a33cccc" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:ba00f661f8ba35d075c937174e27c2c421cec3942fd2e0ea3e66996757c0fdd9" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp315-cp315t-win32.whl", hash = "sha256:cb96698e3c7413d906ce83f8ffd245ec1bd94707541f299d0ce4d6b0193e982b" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp315-cp315t-win_amd64.whl", hash = "sha256:f146d154428a2523f9cc7936c02353c2459b8f6cf07d3cd1ee1c0a611109c5d5" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cffi/cffi-2.1.0-cp315-cp315t-win_arm64.whl", hash = "sha256:cbb7640ce37159548d2147b5b8c241f962143d4c71231431820783f4dc78f210" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -212,75 +240,63 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "3.4.7"
|
||||
version = "3.4.9"
|
||||
source = { registry = "https://proxpi.lille-vemmelund.dk/index/" }
|
||||
sdist = { url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5" }
|
||||
sdist = { url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9.tar.gz", hash = "sha256:673611bbd43f0810bec0b0f028ddeaaa501190339cac411f347ac76917c3ae7b" }
|
||||
wheels = [
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:45b0cc4e3556cd875e09102988d1ab8356c998b596c9fced84547c8138b487a0" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b2aff1c7b3884512b9512c3eaadd9bab39fb45042ffaaa1dd08ff2b9f8109d9" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9104ed0bd76a429d46f9ec0dbc9b08ad1d2dcdf2b00a5a0daa1c145329b35b44" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7b86a2b16095d250c6f58b3d9b2eee6f4147754344f3dab0922f7c9bf7d226c9" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e226f6218febc71f6c1fc2fafb91c226f75bdc1d8fb12d66823716e891608fd" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:90c44bc373b7687f6948b693cceaea1348ae0975d7474746559494468e3c1d84" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cdef90ae47919cae358d8ab15797a800ed41da7aba5d72419fb510729e2ed4b" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:60f44ade2cf573dad7a277e6f8ca9a51a21dda572b13bd7d8539bb3cd5dbedde" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a1786910334ed46ab1dd73222f2cd1e05c2c3bb39f6dddb4f8b36fc382058a39" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03d07803992c6c7bbc976327f34b18b6160327fc81cb82c9d504720ac0be3b62" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp312-cp312-win32.whl", hash = "sha256:78841cccf1af7b40f6f716338d50c0902dbe88d9f800b3c973b7a9a0a693a642" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:4b3dac63058cc36820b0dd072f89898604e2d39686fe05321729d00d8ac185a0" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:78fa18e436a1a0e58dbd7e02fc4473f3f32cceb12df9dfca542d075961c307d2" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:440eede837960000d74978f0eba527be106b5b9aee0daf779d395276ed0b0614" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21e764fd1e70b6a3e205a0e46f3051701f98a8cb3fad66eeb80e48bb502f8698" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e4fd89cc178bced6ad29cb3e6dd4aa63fa5017c3524dbd0b25998fb64a87cc8b" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bd47ba7fc3ca94896759ea0109775132d3e7ab921fbf54038e1bab2e46c313c9" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:84fd18bcc17526fc2b3c1af7d2b9217d32c9c04448c16ec693b9b4f1985c3d33" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:5b10cd92fc5c498b35a8635df6d5a100207f88b63a4dc1de7ef9a548e1e2cd63" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4fbdde9dd4a9ce5fd52c2b3a347bb50cc89483ef783f1cb00d408c13f7a96c0" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:416c229f77e5ea25b3dfd4b582f8d73d7e43c22320302b9ab128a2d3a0b38efe" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:75286256590a6320cf106a0d28970d3560aad9ee09aa7b34fb40524792436d35" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:69b157c5d3292bcd443faca052f3096f637f1e074b98212a933c074ae23dc3b8" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp313-cp313-win32.whl", hash = "sha256:51307f5c71007673a2bf8232ad973483d281e74cb99c8c5a990af1eefa6277d9" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:fe2c7201c642b7c308f1675355ad7ff7b66acfe3541625efe5a3ad38f29d6115" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:611057cc5d5c0afc743ba8be6bd828c17e0aaa8643f9d0a9b9bb7dea80eb8012" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0327fcd59a935777d83410750c50600ee9571af2846f71ce40f25b13da1ef380" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a79d9f4d8001473a30c163556b3c3bfebec837495a412dde78b51672f6134f9" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:33bdcc2a32c0a0e861f60841a512c8acc658c87c2ac59d89e3a46dacf7d866e4" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f840ed6d8ecba8255df8c42b87fadeda98ddfc6eeec05e2dc66e26d46dd6f58a" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c25fe15c70c59eb7c5ce8c06a1f3fa1da0ecc5ea1e7a5922c40fd2fa9b0d5046" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:f7fb7d750cfa0a070d2c24e831fd3481019a60dd317ea2b39acbcebc08b6ed81" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d1c96a7a18b9690a4d46df09e3e3382406ae3213727cd1019ebade1c4a81917" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a4cfde78a9f2880208d16a93b795726a3017d5977e08d1e162a7a31322479c41" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4d6fcde76f94f5cb9e43e9e9a61f16dacefd228cbbf6f1a09bd9b219a92f1a1" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:898f0e9068ca27d37f8e83a5b962821df851532e6c4a7d615c1c033f9da6eedf" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp314-cp314-win32.whl", hash = "sha256:c1c948747b03be832dceed96ca815cef7360de9aa19d37c730f8e3f6101aca48" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:16b65ea0f2465b6fb52aa22de5eca612aa964ddfec00a912e26f4656cbef890b" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:40a126142a56b2dfc0aacbad1de8310cbf60da7656db0e6b16eebd48e3e93519" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:609b3ba8fcc0fb5ab7af00719d0fb6ad0cb518e48e7712d12fd68f1327951198" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51447e9aa2684679af07ca5021c3db526e0284347ebf4ffcec1154c3350cfe32" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cc1b0fff8ead343dae06305f954eb8468ba0ec1a97881f42489d198e4ce3c632" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa36ec09ef71d158186bc79e359ff5fdd6e7996fe8ab638f00d6b93139ba4fcf" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df115d4d83168fdf2cae48ef1ff6d1cb4c466364e30861b37121de0f3bf1b990" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f86c6358749bd4fda175388691e3ba8c46e24c5347d0afd20f9b7edfc9faf07d" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:32286a2c8d167e897177b673176c1e3e00d4057caf5d2b64eef9a3666b03018e" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:83aed2c10721ddd90f68140685391b50811a880af20654c59af6b6c66c40513c" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cd6c3d4b783c556fa00bf540854e42f135e2f256abd29669fcd0da0f2dec79c2" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ee2f2a527e3c1a6e6411eb4209642e138b544a2d72fe5d0d76daf77b24063534" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp314-cp314t-win32.whl", hash = "sha256:0d861473f743244d349b50f850d10eb87aeb22bbdcc8e64f79273c94af5a8226" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:9b8e0f3107e2200b76f6054de99016eac3ee6762713587b36baaa7e4bd2ae177" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:19ac87f93086ce37b86e098888555c4b4bc48102279bae3350098c0ed664b501" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/charset-normalizer/charset_normalizer-3.4.9-py3-none-any.whl", hash = "sha256:68e5f26a1ad57ded6d1cfb85331d1c1a195314756471d97758c48498bb4dcdf5" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -423,6 +439,15 @@ wheels = [
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/cryptography/cryptography-49.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:026ac7423e6fa66872d3bf889be5974507da3944f866f704fa200eadacd00001" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "defusedxml"
|
||||
version = "0.7.1"
|
||||
source = { registry = "https://proxpi.lille-vemmelund.dk/index/" }
|
||||
sdist = { url = "https://proxpi.lille-vemmelund.dk/index/defusedxml/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69" }
|
||||
wheels = [
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/defusedxml/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "distlib"
|
||||
version = "0.4.3"
|
||||
@@ -434,16 +459,16 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "docker"
|
||||
version = "7.1.0"
|
||||
version = "7.2.0"
|
||||
source = { registry = "https://proxpi.lille-vemmelund.dk/index/" }
|
||||
dependencies = [
|
||||
{ name = "pywin32", marker = "sys_platform == 'win32'" },
|
||||
{ name = "requests" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://proxpi.lille-vemmelund.dk/index/docker/docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c" }
|
||||
sdist = { url = "https://proxpi.lille-vemmelund.dk/index/docker/docker-7.2.0.tar.gz", hash = "sha256:cebb93773d334f778e023a7ee352a8d6e13ab1bd3b863a4d4a59dec897df43ac" }
|
||||
wheels = [
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/docker/docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/docker/docker-7.2.0-py3-none-any.whl", hash = "sha256:a3f45fdeb9165e2d25d9a1d02ddf3bc70fb572cf5ebbf9b58558c22caf29b71f" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -460,11 +485,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "filelock"
|
||||
version = "3.29.5"
|
||||
version = "3.29.7"
|
||||
source = { registry = "https://proxpi.lille-vemmelund.dk/index/" }
|
||||
sdist = { url = "https://proxpi.lille-vemmelund.dk/index/filelock/filelock-3.29.5.tar.gz", hash = "sha256:6e6034c57a00a020e767f2614a5539863f056de7e7991d6d1473aef7ff73f156" }
|
||||
sdist = { url = "https://proxpi.lille-vemmelund.dk/index/filelock/filelock-3.29.7.tar.gz", hash = "sha256:5b481979797ae69e72f0b389d89a80bdd585c260c5b3f1fb9c0a5ba9bb3f195d" }
|
||||
wheels = [
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/filelock/filelock-3.29.5-py3-none-any.whl", hash = "sha256:8af830889ba3a0ffcefbd6c7d2af8a54012058103771f2e10848222f476a1693" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/filelock/filelock-3.29.7-py3-none-any.whl", hash = "sha256:987db6f789a3a2a59f55081801b2b3697cb97e2a736b5f1a9e99b559285fbc51" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -554,76 +579,76 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "joserfc"
|
||||
version = "1.7.2"
|
||||
version = "1.7.3"
|
||||
source = { registry = "https://proxpi.lille-vemmelund.dk/index/" }
|
||||
dependencies = [
|
||||
{ name = "cryptography" },
|
||||
]
|
||||
sdist = { url = "https://proxpi.lille-vemmelund.dk/index/joserfc/joserfc-1.7.2.tar.gz", hash = "sha256:537ffb8888b2df039cb5b6d017d7cff6f09d521ce65d89cc9b8ab752b1cff947" }
|
||||
sdist = { url = "https://proxpi.lille-vemmelund.dk/index/joserfc/joserfc-1.7.3.tar.gz", hash = "sha256:116955c2587139dba20621fd0bd7fc9255fa960c9fe7f43c43ebef2e801dcfcf" }
|
||||
wheels = [
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/joserfc/joserfc-1.7.2-py3-none-any.whl", hash = "sha256:ddd818c0ca9b4f17bbc2d72cb3966e6ded7502be089316c62c3cc64ae86132b5" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/joserfc/joserfc-1.7.3-py3-none-any.whl", hash = "sha256:7c39f3f2c943dbc03122747fa8ebbd8e156e54904cf25651b452f4d2634a6075" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "librt"
|
||||
version = "0.12.0"
|
||||
version = "0.13.0"
|
||||
source = { registry = "https://proxpi.lille-vemmelund.dk/index/" }
|
||||
sdist = { url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0.tar.gz", hash = "sha256:cb26faedbd09c6130e9c1b64d8000efec5076ffd18d606c6cd1cf02730e6d8b0" }
|
||||
sdist = { url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0.tar.gz", hash = "sha256:1d2a610c14ac0d0750ee0a3ab8548e83155258387891caaca04def4bf7289781" }
|
||||
wheels = [
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9bce19aa7c05f91c989f9da7b567f81d21d57a2e6501e2b811aa0f3f79614c1a" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0ace09f5bf4d982fe726015f102fb856658b41580597104e301e630ed1d8d86" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d007efe9243ede81ce75990ad7aa172da1e2024144b3eff17ba46a5fff1fff3c" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:ad324a5e4858388a4864915b90a42efc8b374376393f14b9940f2454e791912b" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10a40cf74cdd97b6f8f905056db73f5d459783de2ca04c6ebd1bf47652818e7e" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:92e61c09de95217ae02a9d17f4f66cf073253cdc51bcfdc0f15c62c9a70baa85" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0461344061d6fc3718940f5855d95647831cef6d03a6c7506897f98222784ad4" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e6dfe89074732c9287b3c0f5a6af575c9ede380a788013876cc7b14fe0da0361" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9efed79d51ad1383bba0855f613cca7aa91c943e709af2413ac7f4bb9936ce08" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1eac6cc0e23e448fb3c1446ed85ff796afb616eed5897c978d35dbec030b7c7c" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp312-cp312-win32.whl", hash = "sha256:0ab8ee0210047ae86ca023ccfbfe3df82077fd1c9bc021aebbf37d993ef64af0" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:51c8bfa12632c81b94401c101bcedd0c56c3a1f8fa3273ca3472b28cd2f54003" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:5eebd451f5def089369ba6d8ff0291303d035e8154f9f26f7633835c5b029ade" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8d9a55760a34ae5ce70434aabb6a6c61c6c44a0ec58ca1cfd9cd86e4745d417d" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ff0b197e338b4cf432873e0d6ef025213fdea85311ec4d87d2ea88c28adf2409" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e69f120a20b69e2539d603bbd4d62db38399b10f8bf73a1cf445038a621e8af" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:fde3cde595e947fc8e755b0a21f919a1622483d07c662d00496e040773d22591" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d977447315fa09ea4e8c7ae9b4e22f7659b5128161c1fd55ff786b5349f73503" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7ffac8a67e4143cea9a549d4822b93bc0bbaad73fc25aa0ab0ba5ec27d178677" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:94af1ed773ff104ef08ef3d669a0ba9d3a5916c609eb698cffe5d5476d66ff9b" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:548199d21d22fb26398dfbbe0ba953a52465c66f3a49f38e6fddce1b127faf53" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c8f1f413b966a9dd3ecf80cd337b0ad7bb3de2474a4ff448ed3ebabfc3f803fc" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:55f13f95b629be5b6ab38918e439bf14169d6f9a8deaae55e0c14e12fb0c74b9" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:8b2dc079dfe29e77a47a19073d2040fa4879aa3656501f1650f8402ddce0313c" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp313-cp313-win32.whl", hash = "sha256:da58944be8270f2bfee628a9a2a60c1cf6a12c8bea8e2c9b6edf3e5414ca7793" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:1db4be3037e4ce065a071fa7deee93e78ebc25f448340a02a6c1c0b82c37e383" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:05fd2542892ad770b5dd45003fd080477cf220b611d3ee59b0792097eb0873a9" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:b37ee42e09722284a6d9288fe44a191f7276060a3195939bb77c6502058dbb34" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ade11988728b3e4768dadc5696e82c60e9b35fc95335a9b4d1f5d69e753ccec7" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f351ed425380e39bd86df382578aa5b8c5b98e2e265112de7379e7d030258150" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:857d2163e088c868967717ace8e980017fd868a735f3de010412af02bdc30319" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2befc80aa5f2f5b93f28abaaf11feff6677931dd548320e44c52deaa9399744" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:be3694dcfa97c6715dd19ac73d3e1b21a805514a5785663e57fecacd3ff64e5a" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2d5f67e86f45638843d025b0828f2e9e55fc45ff9180d2618ccdeaf72a796050" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:64572c85e4ab7d572c9b72cd76b5f90b21181b1459fa6b1aac6f8958c4fcff31" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:8b961912b0e688c1eb4658a46bdb0606b31918d65597fbe7356ca83aa653ffcc" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:722375903e3f079436a7a33da51ce73931536dd041f9feb01536f05d8e010c96" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:a5a96a8f536b65ef1bf910c09e7e71647edde5111f6e1b51f413c6fba5bfe71b" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp314-cp314-win32.whl", hash = "sha256:8ffc99c356f1777c506e1b69dc303879153ae2640ba15b8f3d4448bc87139149" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:1e68fb20798f455cda41d20a306a23c901218883f17a4bab1ed6e1331b265fb7" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:2df534f97916cf38ec9b1ddafeb68ae1a4cd4a54775ff26a797026774c0517cf" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c09e581b1c2b8a62b809d4f4bd101ca3de93791e5b0ed1a14085d911be3dee3f" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:976888d0d831402086e641018bcc3208e0a38f0835789da91f72894b2cb4161f" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:563c37cdb41d08fe1e3f08b201abac0e317ca18e88b91285466ee0a585797520" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:b97eb1a3140e279cc76f85b0fb92b7eb3dfbe0471260ee878bc9dc4bf9a0d649" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:06e0623351ab9904cf628245f99c714586f4dd23dc740b88c8bc670d8401a847" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:da12f017b2e404554be14d466cd992459feaa44f252b0f18d909a85266ce1237" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d97f31003a5c86b9e78155a829572c3a26484064fb7ac1d9695fe628bd93d029" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:bd43a6c69876aef4f04eaae3d3b99b0be64755fda274002fa445b92480bf664e" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c01755c72fca1dc6b8d5c2ed228b8e7b2ffe184675c22f0f05ebd8fe188b9250" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:625ae561d5fa36400856dcc27464400d047bc2d5e3446be88f437b03fefd72e4" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp314-cp314t-win32.whl", hash = "sha256:8d73191883553ee0739741544bf3b00aba2a1224e45d9580b30cbc29e21dc03b" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e1cbb037324e759f0afa270229731ff0047772667f3cb38ef5df2cabf0175ede" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.12.0-cp314-cp314t-win_arm64.whl", hash = "sha256:bca1472acbd473eff61059b4409f802c5a1bcb4cd0344d06f939df9c4c125d40" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30536798f4504c0fad0885b1d371b0539abb081e4570c9d7c641cb51141b49f0" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:93d24ebb82aa4420b1409c389e7857bc35bd0b668007ac8172427d5c73cc8cc5" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cb8a1adce42d8b75485a5d56a9623a50bcab995b6079f1dac59fc44034dd93d9" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:0763ca2ab66058174f9dee426dc64f5e0a89c24a7df8d3fe3f1836c04e25de4b" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b222493da6e7b6199db9bd79502436cf5a27da3c1f7fa83c7e285444fc93fd03" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fadc63331f4388c3dc90090448f682a7e9feafc11481391c1e94f2f907a3976e" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:70d9c62a4cffd9f23396cd5ef93fc5d11b31596b9b7d6306074abe3d5fcf09bd" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:66c0e7e6b02a155576df2c77ec933a70b72da726e248c494abf690923e624348" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ac04bcd3328eb91d99dfedf6a60d9c1f15d3434e6f6daf922f0420f7d90b85c7" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:db327e7271e653c32040b85ae6188059c924b57d7e1e29f935523fa017cd4e82" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp312-cp312-win32.whl", hash = "sha256:860bd1d8ba48456ce08feaf8d343a8aaeb2fa086f2bcaa2a923fa3f7a3ff9aa3" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:e54a315caf843c8d77e388cadc56ea9ded569935ee2d2347d7ea94992e5aa6fa" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:c718e99a0992127af84385378460db624103b559ab260435abcfe77a4e4ed1c1" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a468951af16155824e88bdd8326ebe5bdb371f3ec0ac04642994b98201d914f3" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ae01d8512cc17079e53425635327dbf3f7ff57a42c00dec348bf79791c56444c" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32c26893cd085c1efe83219e78d866da23fb20a066101b8f68210004361d224c" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:5929da1981a46bcf4b28b1b9499905f0ff58e2419da402a048234e9783acbc4b" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:94b85d664d777bab6c0d709416cb42938251fda9e221b79e3a2215d85df5f4f9" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:531b2df3e9fe96b1fcf73a6d165921e4656be5f58d631d384ebce344298368db" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:109b84a9edf69ad89dc1f66358659e14a031baca95e3e5b0060bd903ede8efd6" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1304368a3e7ffc3e9db986796cc5326fdb5943a3567ecc137cff318e4240c0e7" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e4f9b472e7d308d94b62c801982065661158c6ed02790d6c7ddb4337cea0f9c1" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9f836c37478f167a81200d8c8b2c920a22224564bed2c23d7aeec760965c367a" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:4000d961ff9598ac6ea603c6c836a5ed49bc205ade5fc378b998dfe1e2c36628" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp313-cp313-win32.whl", hash = "sha256:79e44cff71750d299d61a678e49995b0d5935a9cda238c2574daeca3ba536927" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:54dab44a847d5ad1acd05c8a83fe518ae685516ecf4d3f7cc6e3df2a66767650" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:d4cb6fbfdf874340ab5e51450753c0f817b6958a3621125ee695bbc3de866566" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:25218d94b1d2cbc0ba1d8a3f9dc9af578d9646e5ed16443a70cde1dfdcce6d71" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f26629539d4893c2957a16c41bb058e1e135c1f150f6a2e25ed047f64cf3f5c6" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4517d47b2b8af26975a406fba7d314de9696d864252e0257c6ea90238cfe27f" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:f19e181de5b3a1148bb3420b8c4b0b0ea0fce6950099724ad151d6cea5acc180" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22034924f5b42d5a56371cf271771bfeaabf235a7a8b6264bef2d20013f786c6" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c7897db4e95e22468bdda33d8e012ceacd0182abf001e6389d763f0def6286b9" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1ce61b3746545029d4f5c17d6bd74b676254ad98433086c846ffb5e8fa73f007" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:46c330e82565962c761dbce7941be2cff7db674ee807455a8d0cadc5f9b759b0" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:375f5af8f99cbaa99dd293af986e3d57caabc9ba81a5d3f021603764854197a1" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9320d34c3376ae204b2cd176e8d4883a013934e0aef822f1aed9c536490c275d" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:9af313c66157a69dc69ea0059a66961692250e0dc95af9c385a48ffb770a0d16" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp314-cp314-win32.whl", hash = "sha256:f2a7253458e34f33543551394ae4fe104b497ec2a65ac266074de64c1df82e37" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:a3dfe4edf10e8ed7e55b026a8bfc2c2a8704218b659cd4bffdf604fab966dc39" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:68a5faee4bba381cb93b5961f684a514cf0053cb92308ff9c792c2fea0b174c6" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:a38fb81d8376dfa2f8963b265fec07637802b0d01e2a127c19c66cb070fb24f5" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d4c8d9bd5abce34b2e75edb3bf37ab0f34e49b1f915a40ae8468eb7c85bc5b46" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:387e2f1d27e89bffe0d3f520f0da0662c973fd607ca16c1808f8a5085419485e" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:4f6db193d2e5e0ed60359b9a5a682cd67205d0d3b1e459a867dd4b5c4e7eaa7a" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d38604854e8d22faadf683ec6c02bb0f886e2ba56ef981a1c36ee275f21ea22" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:371f7ce73026815dafd51c50ce38416e91428b28c4b2ec97cd39271164b0045c" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3aaedf52171bee90860704c560bc798fe83b76247df47568e0197e9b13c735a0" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:96bad8725a4f196a798366c25ce075d1f7543a4ec045ffc13e6a7ec095cdab04" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:6bf6a559ffe4a93bbea6cf31ddf01a7fd9ba342ef51f27beb178e318b74acd61" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:301067672387902c55f94b51d5022304b36c966ea9fe1f21caab99a9bef487c9" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp314-cp314t-win32.whl", hash = "sha256:5fdcf34f86de8fb66d7dc7589f96ba91c4aa46671200d400e6fd6f109a483f18" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:260c33e92263fa629b4f6d3c51967a1c2158fe6c33237aaa3ebeac586b085259" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/librt/librt-0.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2f281549a4c52ac7bb97997f14353f8bd0e53a34ca0dad1c905cfd0b4a58ae99" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -737,7 +762,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "mypy"
|
||||
version = "2.1.0"
|
||||
version = "2.2.0"
|
||||
source = { registry = "https://proxpi.lille-vemmelund.dk/index/" }
|
||||
dependencies = [
|
||||
{ name = "ast-serialize" },
|
||||
@@ -746,37 +771,38 @@ dependencies = [
|
||||
{ name = "pathspec" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.1.0.tar.gz", hash = "sha256:81e76ad12c2d804512e9b13240d1588316531bfba07558286078bfbce9613633" }
|
||||
sdist = { url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.2.0.tar.gz", hash = "sha256:2cdd99d48590dce6f6b7f1961eda75386364398fcdaad86923bc0f0231bf9baf" }
|
||||
wheels = [
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:244358bf1c0da7722230bce60683d52e8e9fd030554926f15b747a84efb5b3af" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4ec7c57657493c7a75534df2751c8ae2cda383c16ecc55d2106c54476b1b16f6" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8161b6ff4392410023224f0969d17db93e1e154bc3e4ba62598e720723ae211" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf03e12003084a67395184d3eb8cbd6a489dc3655b5664b28c210a9e2403ab0b" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:20509760fd791c51579d573153407d226385ec1f8bcce55d730b354f3336bc22" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:6753d0c1fdd6b1a23b9e4f283ce80b2153b724adcb2653b20b85a8a28ac6436b" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:98ebb6589bb3b6d0c6f0c459d53ca55b8091fbc13d277c4041c885392e8195e8" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35aac3bb114e03888f535d5eb51b8bafbb3266586b599da1940f9b1be3ec5bd5" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8de55a8c861f2a49331f807be98d90caeceeef520bde13d43a160207f8af613e" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5fdf2941a07434af755837d9880f7d7d25f1dacb1af9dcd4b9b66f2220a3024e" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e195b817c13f02352a9c124301f9f30f078405444679b6753c1b96b6eed37285" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5431d42af987ebd92ba2f71d45c85ed41d8e6ca9f5fd209a69f68f707d2469e5" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:767fe8c66dc3e01e19e1737d4c38ebefead16125e1b8e58ad421903b376f5c65" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:ecfe70d43775ab99562ab128ce49854a362044c9f894961f68f898c23cb7429d" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7354c5a7f69d9345c3d6e69921d57088eea3ddeeb6b20d34c1b3855b02c36ec2" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:49890d4f76ac9e06ec117f9e09f3174da70a620a0c300953d8595c926e80947f" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:761be68e023ef5d94678772396a8af1220030f80837a3afd8d0aef3b419666f4" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c90345fc182dc363b891350457ec69c35140858538f38b4540845afcc32b1aef" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b84802e7b5a6daf1f5e15bc9fcd7ddae77be13981ffab037f1c67bb84d67d135" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:022c771234936ceac541ebaf836fe9e2abeb3f5e09aff21588fe543ff006fe21" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:498207db725cec88829a6a5c2fc771205fd043719ef98bc49aba8fb9fc4e6d57" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7d5e5cad0efeba72b93cd17490cc0d69c5ac9ca132994fe3fb0314808aeeb83e" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ff715050c127d724fd260a2e666e7747fdd83511c0c47d449d98238970aef780" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:82208da9e09414d520e912d3e462d454854bed0810b71540bb016dcbca7308fd" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e79ebc1b904b84f0310dff7469655a9c36c7a68bddb37bdd42b67a332df61d08" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e583edc957cfb0deb142079162ae826f58449b116c1d442f2d91c69d9fced081" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b33b6cd332695bba180d55e717a79d3038e479a2c49cc5eb3d53603409b9a5d7" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:4f910fe825376a7b66ef7ca8c98e5a149e8cd64c19ae71d84047a74ee060d4e6" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.1.0-py3-none-any.whl", hash = "sha256:a663814603a5c563fb87a4f96fb473eeb30d1f5a4885afcf44f9db000a366289" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:484a2be712b245ac6e89847141f1f50c612b0a924aa25917e63e6cfcf4da07cd" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fb0a020dc68480d40e484675558ed637140df1ccbf896a81ba68bca85f2b50a0" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc361340732ce7108fa0308812caf02bb6868f16112f1efd35bcad88badf3327" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b0179a3a0b833f724a65f22613607cf7ea941ab17ec34fa283f8d6dfe21d9fa9" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9e0899b13da1e4ba44b880550f247402ce90ffecc71c54b220bcbe7ecb34f394" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:511320b17467402e2906130e185abffffa3d7648aff1444fc2abb61f4c8a087d" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:7589f370b33dcdd95708f5340f13a67c2c49140957f934b42ef63064d343cca0" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6968f27347ef539c443ddfd6897e79db525ddb8c856aa8fbf14c34f310ca5193" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3df226d2a0ae2c3b03845af217800a68e2965d4b14914c99b78d3a2c8ae23299" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fc53553996aca2094216ad9306a6f06c5265d206c1bcb54dd367560bd3557825" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:996abf2f0bebf572556c60720e8dc0cf5292b64060fa68d7f2bc9caa48e01b6f" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ec287c2381898c652bf8ff79448627fe1a9ee76d22005181fac7315a485c7108" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:c2c967a7685fa93fcf1a778b3ebe76e756b28ba14655f539d7b61ff3da69352a" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:d59a4b80351ec92e5f7415fcdd008bd77fcbefc7adf9cbc7ffe4eb9f71617734" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1f6c3d76853071409ac58fc0aadfb276a22af5f190fdaa02152a858088a39ebd" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bdaa177e80cc3292824d4ef3670b5b58771ee8d57c290e0c9c89e7968212332c" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80923e6d6e7878291f537ee11052f974954c20cb569798429a5dc265eb780b47" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f24bd465a09077c8d64be8f19a6646db467a55490fd315fe7871afe6bb9645" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:db34595464869f474708e769413d1d739fc33a69850f253757b9a4cc20bc1fec" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.2.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:b48092132c7b0ef4322773fecae62fc5b0bc339be348badeec8af502122a4a51" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:6fc0e98b95e31755ca06d89f75fafa7820fbb3ea2caace6d83cba17625cd0acb" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:bc73a5b4d40e8a3e6b12ef82eb0c90430964e34016a36c2aff4e3bfe37ba41f0" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:6257bd4b4c0ae2148548b869a1ff3758e38645b92c8fe65eca401866c3c551c1" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:dfa22b3ae862ac1ce76f5976ddd402651b5f090bcfd49c6d0484b8983a29eaf8" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:30a430bf26fe8cf372f3933fbd83e633d6561868803645a20e4e6d4523f52a3b" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d58655a60e823b1a4c9ebcda072897fb0193c2f3e6f8e7c433e152aa4cb00233" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d4452c955caf14e28bb046cbd0c3671272e6381630a8b81b0da9713558148890" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:68f5b7f7f755200f68c7181e3dfb28be9858162257690e539759c9f57721e388" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:78d201accfafce3801d978f2b8dbbd473a9ce364cc0a0dfd9192fe47d977e129" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/mypy/mypy-2.2.0-py3-none-any.whl", hash = "sha256:ecc138da861e932d1344214da4bae866b21900a9c2778824b51fe2fb47f5180e" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -790,17 +816,18 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "nltk"
|
||||
version = "3.9.4"
|
||||
version = "3.10.0"
|
||||
source = { registry = "https://proxpi.lille-vemmelund.dk/index/" }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
{ name = "defusedxml" },
|
||||
{ name = "joblib" },
|
||||
{ name = "regex" },
|
||||
{ name = "tqdm" },
|
||||
]
|
||||
sdist = { url = "https://proxpi.lille-vemmelund.dk/index/nltk/nltk-3.9.4.tar.gz", hash = "sha256:ed03bc098a40481310320808b2db712d95d13ca65b27372f8a403949c8b523d0" }
|
||||
sdist = { url = "https://proxpi.lille-vemmelund.dk/index/nltk/nltk-3.10.0.tar.gz", hash = "sha256:4fbac1d98203cbcd1b5d94a2877fb822300072d80604a5e7fae49d2c5f84e8c1" }
|
||||
wheels = [
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/nltk/nltk-3.9.4-py3-none-any.whl", hash = "sha256:f2fa301c3a12718ce4a0e9305c5675299da5ad9e26068218b69d692fda84828f" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/nltk/nltk-3.10.0-py3-none-any.whl", hash = "sha256:54ff84d4916d3ef127e8953bee0023f6a6b320b75d634a19e06ef056d3d244bf" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -864,6 +891,64 @@ wheels = [
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/pre-commit/pre_commit-4.6.0-py2.py3-none-any.whl", hash = "sha256:e2cf246f7299edcabcf15f9b0571fdce06058527f0a06535068a86d38089f29b" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psycopg"
|
||||
version = "3.3.4"
|
||||
source = { registry = "https://proxpi.lille-vemmelund.dk/index/" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
{ name = "tzdata", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://proxpi.lille-vemmelund.dk/index/psycopg/psycopg-3.3.4.tar.gz", hash = "sha256:e21207764952cff81b6b8bdacad9a3939f2793367fdac2987b3aac36a651b5bc" }
|
||||
wheels = [
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/psycopg/psycopg-3.3.4-py3-none-any.whl", hash = "sha256:b6bbc25ccf05c8fad3b061d9db2ef0909a555171b84b07f29458a447253d679a" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
binary = [
|
||||
{ name = "psycopg-binary", marker = "implementation_name != 'pypy'" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psycopg-binary"
|
||||
version = "3.3.4"
|
||||
source = { registry = "https://proxpi.lille-vemmelund.dk/index/" }
|
||||
wheels = [
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/psycopg-binary/psycopg_binary-3.3.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5ab28a2a7649df3b72e6b674b4c190e448e8e77cf496a65bd846472048de2089" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/psycopg-binary/psycopg_binary-3.3.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6402a9d8146cf4b3974ded3fd28a971e83dc6a0333eb7822524a3aa20b546578" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/psycopg-binary/psycopg_binary-3.3.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:580ae30a5f95ccd90008ec697d3ed6a4a2047a516407ad904283fa42086936e9" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/psycopg-binary/psycopg_binary-3.3.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e7510c37550f91a187e3660a8cc50d4b760f8c3b8b2f89ebc5698cd2c7f2c85d" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/psycopg-binary/psycopg_binary-3.3.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77df19583501ea288eaf15ac0fe7ad01e6d8091a91d5c41df5c718f307d8e31b" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/psycopg-binary/psycopg_binary-3.3.4-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:018fbed325936da502feb546642c982dcc4b9ffdea32dfef78dbf3b7f7ad4070" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/psycopg-binary/psycopg_binary-3.3.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:17a21953a9e5ff3a16dab692625a3676e2f101db5e40072f39dbee2250194d68" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/psycopg-binary/psycopg_binary-3.3.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:eb05ee1c2b817d27c537333224c9e83c7afb86fe7296ba970990068baf819b16" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/psycopg-binary/psycopg_binary-3.3.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:773d573e11f437ce0bdb95b7c18dc58390494f96d43f8b45b9760436114f7652" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/psycopg-binary/psycopg_binary-3.3.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:71e55ccbdfae79a2ed9c6369c3008a3025817ff9d7e27b32a2d84e2a4267e66e" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/psycopg-binary/psycopg_binary-3.3.4-cp312-cp312-win_amd64.whl", hash = "sha256:494ca54901be8cf9eb7e02c25b731f2317c378efa44f43e8f9bd0e1184ae7be4" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/psycopg-binary/psycopg_binary-3.3.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fbd1d4ed566895ad2d3bf4ddfd8bae90026930ddf29df3b9d91d32c8c47866a7" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/psycopg-binary/psycopg_binary-3.3.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:75a9067e236f9b9ae3535b66fe99bddb33d39c0de10112e49b9ab11eee53dc31" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/psycopg-binary/psycopg_binary-3.3.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:b56b603ebcea8aa10b46228b8410ba7f13e7c2ee54389d4d9be0927fd8ce2a70" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/psycopg-binary/psycopg_binary-3.3.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c677c4ad433cb7150c8cd304a0769ae3bcfbe5ea0676eb53faa7b1443b16d0d3" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/psycopg-binary/psycopg_binary-3.3.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:26df2717e59c0473e4465a97dfb1b7afebaa479277870fd5784d1436470db47c" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/psycopg-binary/psycopg_binary-3.3.4-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1dc1f79fd16bb1f3f4421417a514607539f17804d95c7ed617265369d1981cae" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/psycopg-binary/psycopg_binary-3.3.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:136f199a407b5348b9b857c504aff60c77622a28482e7195839ce1b51238c4cc" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/psycopg-binary/psycopg_binary-3.3.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b6f5a29e9c775b9f12a1a717aa7a2c80f9e1db6f27ba44a5b59c80ac61d2ffcf" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/psycopg-binary/psycopg_binary-3.3.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:ee17a2cf4943cde261adfad1bbc5bf38d6b3776d7afff74c7cabcbeaeb08c260" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/psycopg-binary/psycopg_binary-3.3.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5c4ab71be17bdca30cb34c34c4e1496e2f5d6f20c199c12bad226070b22ef9bf" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/psycopg-binary/psycopg_binary-3.3.4-cp313-cp313-win_amd64.whl", hash = "sha256:dbfdb9b6cc79f31104a7b162a2b921b765fcc62af6c00540a167a8de47e4ed38" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/psycopg-binary/psycopg_binary-3.3.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:28b7398fdd19db3232c884fb24550bdfe951221f510e195e233299e4c9b78f97" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/psycopg-binary/psycopg_binary-3.3.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1fbaa292a3c8bb61b45df1ad3da1908ccee7cb889db9425e3557d9e34e2a4829" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/psycopg-binary/psycopg_binary-3.3.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:94596f9e7633ee3f6440711d43bb70aa31cc0a46a900ab8b4201a366ace5c9e7" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/psycopg-binary/psycopg_binary-3.3.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8c0056529e68dbe9184cd4019a1f3d8f3a4ead2f6fc7a5afcf27d3314edd1277" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/psycopg-binary/psycopg_binary-3.3.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c09aad7051326e7603c14e50636db9c01f78272dc54b3accff03d46370461e6" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/psycopg-binary/psycopg_binary-3.3.4-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:514404ed543efd620c85602b747df2a23cf1241b4067199e1a66f2d2757aaa41" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/psycopg-binary/psycopg_binary-3.3.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:46893c26858be12cc49ca4226ed6a60b4bfccadd946b3bebb783a60b38788228" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/psycopg-binary/psycopg_binary-3.3.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:df1d567fc430f6df15c9fcf67d87685fc49bdb325adc0db5af1adfb2f44eb5c9" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/psycopg-binary/psycopg_binary-3.3.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:6b9016b1714da4dd5ecaaa75b82098aa5a0b87854ce9b092e21c27c4ae23e014" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/psycopg-binary/psycopg_binary-3.3.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:47c656a8a7ba6eb0cff1801a4caaa9c8bdc12d03080e273aff1c8ac39971a77e" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/psycopg-binary/psycopg_binary-3.3.4-cp314-cp314-win_amd64.whl", hash = "sha256:c37e024c07308cd06cf3ec51bfd0e7f6157585a4d84d1bce4a7f5f7913719bf8" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pycparser"
|
||||
version = "3.0"
|
||||
@@ -1034,15 +1119,15 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "python-discovery"
|
||||
version = "1.4.3"
|
||||
version = "1.4.4"
|
||||
source = { registry = "https://proxpi.lille-vemmelund.dk/index/" }
|
||||
dependencies = [
|
||||
{ name = "filelock" },
|
||||
{ name = "platformdirs" },
|
||||
]
|
||||
sdist = { url = "https://proxpi.lille-vemmelund.dk/index/python-discovery/python_discovery-1.4.3.tar.gz", hash = "sha256:ad57d7045a862460d4a235986c33f13ed707d3aeb9153fa47eb7dfd0d4673289" }
|
||||
sdist = { url = "https://proxpi.lille-vemmelund.dk/index/python-discovery/python_discovery-1.4.4.tar.gz", hash = "sha256:5cad33982d412c1f3ffb8f9ca4ea292c9680bca3942451d30b69c37fce53a4a3" }
|
||||
wheels = [
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/python-discovery/python_discovery-1.4.3-py3-none-any.whl", hash = "sha256:b6e1e4a7d9e3f6948c39746ffe8218225162d738ba39d05ab1d2f6c1cac4878c" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/python-discovery/python_discovery-1.4.4-py3-none-any.whl", hash = "sha256:abebe9120b43453b68c908acfb1e72a19d1a959ed2cb620ad38fc57d08056dbe" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1056,7 +1141,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "python-repositories"
|
||||
version = "2.1.0"
|
||||
version = "2.3.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "python-dotenv" },
|
||||
@@ -1068,6 +1153,9 @@ dependencies = [
|
||||
minio = [
|
||||
{ name = "minio" },
|
||||
]
|
||||
postgres = [
|
||||
{ name = "psycopg", extra = ["binary"] },
|
||||
]
|
||||
redis = [
|
||||
{ name = "redis" },
|
||||
]
|
||||
@@ -1087,12 +1175,13 @@ dev = [
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "minio", marker = "extra == 'minio'", specifier = ">=7.2.16" },
|
||||
{ name = "psycopg", extras = ["binary"], marker = "extra == 'postgres'", specifier = ">=3.2.0" },
|
||||
{ name = "python-dotenv", specifier = ">=1.0.0" },
|
||||
{ name = "python-utils", specifier = ">=0.1.0", index = "https://gitea.lille-vemmelund.dk/api/packages/brian/pypi/simple/" },
|
||||
{ name = "redis", marker = "extra == 'redis'", specifier = ">=6.4.0" },
|
||||
{ name = "structlog", specifier = ">=25.4.0" },
|
||||
]
|
||||
provides-extras = ["redis", "minio"]
|
||||
provides-extras = ["redis", "minio", "postgres"]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
@@ -1203,90 +1292,90 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "2026.6.28"
|
||||
version = "2026.7.10"
|
||||
source = { registry = "https://proxpi.lille-vemmelund.dk/index/" }
|
||||
sdist = { url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28.tar.gz", hash = "sha256:3cb4b6c5cb3060cc31efdc1fbb27c25fb9b29044afd87e40601a1c4d9db54342" }
|
||||
sdist = { url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10.tar.gz", hash = "sha256:1050fedf0a8a92e843971120c2f57c3a99bea86c0dfa1d63a9fac053fe54b135" }
|
||||
wheels = [
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:81cc5793ad33a10444445e8d29d3c73e752c8fb2e120772d70fcb6d41df40fe1" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e18225243250a1f7d7e5e5d883f3b96465cd79031acf5c6db902b7025f2125d9" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ecd1638b1c2db1f2d01c182a4b0d3e2e88b0e99910320a745c1727ee3638ddab" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4303ebe16b74eeb3fe2715745023266fea92fd44a23f3e7bb2fb48c7a7bbc195" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56b856b70b96c381d837f609eee442a1bd320cd2159f5c294b679552fb1a7eaf" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f74675ab76ab1d005ffba4dee308e53e89efc22be6e9f9fae5b539a3f81bdff2" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90581684565a93f7258af1e5d3f41ef20d7d7c61f2a428183a342bcb65485e38" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:28f9e6c28f9b90f6f784595a33240a57e181e61b6ee3dc259b25c61e356d1aa3" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:378a71d861fc7c8806b04ac5b133d53c0e774f92f5d9663a539872d3fa2b0417" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4cc199874ecd6267a49b111052250825bfe19b5101b23b2ba80f54efa3e0994e" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:b916a10431494ef4b4d62c6c89cab6426af7873125b8cd6c15811bf5fc58eec8" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2e27727fba075f1e4409416d2f537d4c30fc11f012ea507f7bd74d3e19ecb57a" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:700fc6a7844bb2c4149292ac79d1df8841a00acd4d45cd32c1ebc7bcc1fd0da8" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp312-cp312-win32.whl", hash = "sha256:03376d60b6a11aecb88a79fa2be06b40faa01c6693bc31ef69435cd4818b9463" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp312-cp312-win_amd64.whl", hash = "sha256:fbd2ded482bf99e6651992bbfcde460272724d4bbc49ef3d6b46d9312867ec84" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp312-cp312-win_arm64.whl", hash = "sha256:37294d3d7ddb64c7e89184b2894e0f8f0a19c514bc59513d71fe692c3a8d5fc6" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:b295a83426e0e44e9e60fde99789e181bd26788a1890ae7fe2a24c69bb6246ca" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0c31665c0deb5c111557a1cac8c27bd5629e2f9e7fd5058900a03576c33b601c" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6bf295f2c59de77d1ea7de053607ae4dc9ceb3d57bbb6c7ec51ef4acc4ccff94" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:17c077586770f67e05bbffeba07fbee6b2b22244f4d4caf8d94e59d574befe04" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0e6cb5a61486f9062397d2e189573b39d38ecfaed698fd9fb6e2756a8ebb8762" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e86e91a2664f44c3a4e363a7d78fb17c27d5046882e30ea5a877f5e89b28d2ba" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4dfd1331c49233998d84fc5f1f4436cf7a435a7655f6cf0f490229bb5c7254e5" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cadea12805a1bce0b091c302b814207be26fb60a9c0e7f9ad2f9e21790a429fe" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5f2c1682b67ad5d2376498f2a5a2a8f782fa2e4a06d0465b5e357799806e8a20" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:64e142eb55e84868087da1375d7c36ff97d55010951849f515322a91d5fef1b4" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:abb4daabe7be63273787a62dfd6164dadf8f7a63fbec3d2730e5e5e7126d858c" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ec2b2ad00ab8c16a2798cc8db80c53c4d5b8b3a2441f6cbaef06625f5ca25854" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bfc9677982c914d9085b8e1c3b3ae6e88f139fb56531c2416d6c8f338093c22b" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp313-cp313-win32.whl", hash = "sha256:bf54bc693fc4e0530e666ba5ec4bcba14dbe8f66b7cfc15c27317d1a6e40b9a5" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp313-cp313-win_amd64.whl", hash = "sha256:e128feaf65bf3d9eb91bec92322a8f7e4835e9c798f3e9ea4b69f4def85620e3" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp313-cp313-win_arm64.whl", hash = "sha256:695873e0ea8d3815ea9e92e2c68faf039cc450e2c0a62a31afe2049eb11be767" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:189dbf9fc4252d9f1352bf4bd1bef885edb6cc4b7341df202a65f821aaa3891c" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9277a4c6503390aa39cb4483b87ec0384faee0850a23b5cea33d008b5d8d83f1" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:17eddca4e8ea9af0b5739314776cdf0172a49731ab61f2e1ea66e066ddd46c97" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4466b8641e00c697aab5a73150150d2b2ea96b131c595691f42031abafd9f4d" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9cfcd4b0bdcf768c498415c170d1ed2a25a99bf0b65fa253bbd02f68ceba6475" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:80c7adf1ef647f6b1e8aa2ca280e517174cd08bdf7a2e412cdfb68bd6a0917cb" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a043f5770e82283a22aed4cefef1a4e0f9dd8fd7184cb6ce0ad2e579e2134a9e" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3bd630a8dba06b55254ea5ee862194edab52ec783100d2ef1cd15a9c512fee27" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b77207e3cee13086f1906a6a2a12b41244c577e8ad9370d4b35ae1d548d354f3" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:6de82c268e5d101ee9e3ffd869924aa9a371e3a21e752cf4fa17b6ce50d219f7" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:b15859e3908544fb99cf47341dcf0bfd089147d258c4c4d8a29e5b087f8085cb" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:c91487a917edd48a1ea646fdf60d7936d304f0e686fa7ea8326e47efca51d816" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4ac65f3e3a99fd8f3a4a74e7a6610acd1ce9dfe9b8a03d346a4922380d68aeb" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp313-cp313t-win32.whl", hash = "sha256:3f6316f258bc7e6c9c2acbe9954947bbd397a81be3742a637a555f1855d6618d" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp313-cp313t-win_amd64.whl", hash = "sha256:1484bdd6fba28422df9b5ebb04055b2e1b680e8e4f08490bb21ff0f3cc50d0ab" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp313-cp313t-win_arm64.whl", hash = "sha256:3f15020f0b69cafe57baa067ff65b29acef68ff6b1670a53bef1ca11d708e02d" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:f7c032b0c8a73739ff8ff1aaf30c281fa19c17bf7f1543256c8507390db7807c" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:f6710f512c57b84f127a23d0f59560a03b64136eff419ae1be5ab557577fe5e3" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c0013958f427bd82509a186b9ff206d66cb8d60a81fc797a4c717afd18c5b0ba" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94f06cdcd6421f8e194ad312ea608020381250df9b8a57661c1b57e9e5273878" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ec9689392f7494ff4e3f8e7e8522f9158f11023f337eaaf04a64542fc45bbf26" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:aa084684e6d2078bf6139e374d1fc2af5ddc1ac7122759a2db716d68169f6fd0" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:40455e6840dc4e96a6fe50f4cedc957de2752c954d91e789812be55d49be199a" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:530b5c223b9ca5dd8370ac502e080aee0e4ded32be987c6564b425fb5523d581" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8e0ed273ecd1a89be84466c1749bfe58609cc2a32b5d5e05006c4625ba96411b" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:0ab0d5344311fc8e8667078942056c3b9c9b4a4b1cc99f2eb8a5af54554f4acc" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:eacb79625323d9f7e7925366b917f492b8356fad58f5dc4fa12ff8c21d8f4ca9" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:20f4d87702702aa1d572721e146f301660c50eef6fd6cb596e48a22b0ace17db" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1e693940a3b9e6d6e4dc2a54ecaa74b74934f77af1ef95f518a74261ef7cc1bc" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp314-cp314-win32.whl", hash = "sha256:234a51e20ebc18ab83b2c0600cf28f2e884560a0e00f743878f0b7d8e7c4cf03" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp314-cp314-win_amd64.whl", hash = "sha256:7b15c437bc4604f03ceb3f8d37eae2f8930e320e1bc556b259848c639d9eec1a" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp314-cp314-win_arm64.whl", hash = "sha256:c6e6f790d01380a74ad564f216c533b86504afb61bf66f2b2e11e7f1a3e287a7" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:3527a72adcbe9e3600f1553b497d397c1a371d227580d41d96c3c5964109b65c" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a644f6408692812f5ead82519eed680e08d5d546fddbd9f7d9514e3c73899aa5" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8e2fae6bb883648346f84db270dc9aafc29d8e895f62b88a75ccc83b09519820" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:debe623e09cee97ef9404575e936c610aac9bb08358c5099aaef14644a6871f2" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cc579c91fb4605773483a8d940b136bcc5b854fff44fa14a1572a038f46563f1" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e7c42be203d84ecf7d487ff23f8a61ef0eb0534fa0fc317a2fce8c065d20618f" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8184b4e2fdaf9cdfe77e38f15a4d9dc149168c9c29eb0ea17c5481d3bb80546" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:697f103104f5872d64078d8eeac59979960be8ee76115a2d3f31096312e2a400" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:714d2b1aa29beef0ddfcdc72ad0771c05326551a8bb0680b0ddf74bfaad87387" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0f09f62e450cc2f113018cc8412aeea3a120a04e1ca7e801a0d441583f9a3b06" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:731ea12d5aeb2577eaef2393d6428b995f76eb35f68a89e03e15a97719d1de19" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:51e952c8783eabd4706d0f63922f219bcfc1bef9b8cb35941c0d1a0396578858" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:43248fe4c0ab8fbb223588a0795b11268940072c97bba30ea8f9b49d8cdfde34" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp314-cp314t-win32.whl", hash = "sha256:fc1eddc25ad23c0f1344ab280d961ac595ead48292d7c779497975942373f493" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp314-cp314t-win_amd64.whl", hash = "sha256:ede8d8e53b6dde0a50f7eca902f0af76d87ab02a55aba7542da68ae3e5dfe83d" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.6.28-cp314-cp314t-win_arm64.whl", hash = "sha256:4da6f6a72f8700b97a1a765e837fb7d5750bfd9f13acea7bae498f573e3a70a8" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7252b48b0c60100095088fbeb281fca9a4fcf678a4e04b1c520c3f8613c952c4" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:da6ef4cb8d457aab0482b50120136ae94238aaa421863eaa7d599759742c72d6" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fe7ff456c22725c9d9017f7a2a7df2b51af6df77314176760b22e2d05278e181" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3463a5f26be513a49e4d497debcf1b252a2db7b92c77d89621aa90b83d2dd38" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:948dfc62683a6947b9b486c4598d8f6e3ecc542478b6767b87d52be68aeb55c6" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c2cbd385d82f63bb35edb60b09b08abad3619bd0a4a492ae59e55afaf98e1b9d" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f6222cafe00e072bb2b8f14142cd969637411fbc4dd3b1d73a90a3b817fa046f" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:65ee5d1ac3cd541325f5ac92625b1c1505f4d171520dd931bda7952895c5321a" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aa34473fbcc108fea403074f3f45091461b18b2047d136f16ffaa4c65ad46a68" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d028d189d8f38d7ff292f22187c0df37f2317f554d2ed9a2908ada330af57c0" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:396ea70e4ea1f19571940add3bad9fd3eb6a19dc610d0d01f692bc1ba0c10cb4" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ebbf0d83ed5271991d666e54bb6c90ac2c55fb2ef3a88740c6af85dc85de2402" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:58a4571b2a093f6f6ee4fd281faa8ebf645abcf575f758173ea2605c7a1e1ecb" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp312-cp312-win32.whl", hash = "sha256:eac1207936555aa691ce32df1432b478f2729d54e6d93a1f4db9215bcd8eb47d" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp312-cp312-win_amd64.whl", hash = "sha256:ecae626449d00db8c08f8f1fc00047a32d6d7eb5402b3976f5c3fda2b80a7a4f" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp312-cp312-win_arm64.whl", hash = "sha256:87794549a3f5c1c2bdfba2380c1bf87b931e375f4133d929da44f95e396bf5fe" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:4db009b4fc533d79af3e841d6c8538730423f82ea8508e353a3713725de7901c" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b96341cb29a3faa5db05aff29c77d141d827414f145330e5d8846892119351c1" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:14d27f6bd04beb01f6a25a1153d73e58c290fd45d92ba56af1bb44199fd1010d" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e6b6a11bf898cca3ce7bfaa17b646901107f3975677fbd5097f36e5eb5641983" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:234f8e0d65cf1df9becadae98648f74030ee85a8f12edcb5eb0f60a22a602197" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:91b916d495db3e1b473c7c8e68733beec4dce8e487442db61764fff94f59740e" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f0d4ccf70b1d13711242de0ba78967db5c35d12ac408378c70e06295c3f6644" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c622f4c638a725c39abcb2e680b1bd592663c83b672a4ed350a17f806d75618e" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:41a47c2b28d9421e2509a4583a22510dc31d83212fcf38e1508a7013140f71a8" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:13fba679fe035037e9d5286620f88bbfd105df4d5fcd975942edd282ab986775" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8e26a075fa9945b9e44a3d02cc83d776c3b76bb1ff4b133bbfa620d5650131da" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d0834c84ae8750ae1c4cede59b0afd4d2f775be958e11b18a3eea24ed9d0d9f1" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64722a5031aeace7f6c8d5ea9a9b22d9368af0d6e8fa532585da8158549ea963" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp313-cp313-win32.whl", hash = "sha256:74ae61d8573ecd51b5eeee7be2218e4c56e99c14fa8fcf97cf7519611d4be92e" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp313-cp313-win_amd64.whl", hash = "sha256:5e792367e5f9b4ffb8cad93f1beaa91837056b94da98aa5c65a0db0c1b474927" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp313-cp313-win_arm64.whl", hash = "sha256:82ab8330e7e2e416c2d42fcec67f02c242393b8681014750d4b70b3f158e1f08" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:2b93eafd92c4128bab2f93500e8912cc9ecb3d3765f6685b902c6820d0909b6b" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3f03b92fb6ec739df042e45b06423fc717ecf0063e07ffe2897f7b2d5735e1e8" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:bb5aab464a0c5e03a97abad5bdf54517061ebbf72340d576e99ff661a42575cc" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fadb07dbe36a541283ff454b1a268afd54b077d917043f2e1e5615372cb5f200" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:21150500b970b12202879dfd82e7fd809d8e853140fff84d08e57a90cf1e154e" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a68b637451d64ba30ed8ae125c973fa834cc2d37dfa7f154c2b479015d477ba8" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e23458d8903e33e7d27196d7a311523dc4e2f4137a5f34e4dbd30c8d37ff33e" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cae27622c094558e519abf3242cf4272db961d12c5c9a9ffb7a1b44b2627d5c6" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ee877b6d78f9dff1da94fef51ae8cf9cce0967e043fdcc864c40b85cf293c192" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:2c66a8a1969cfd506d1e203c0005fd0fc3fe6efc83c945606566b6f9611d4851" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:2bc350e1c5fa250f30ab0c3e38e5cfdffcd82cb8af224df69955cab4e3003812" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:53f54993b462f3f91fea0f2076b46deb6619a5f45d70dbd1f543f789d8b900ef" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cfcec18f7da682c4e2d82112829ce906569cb8d69fa6c26f3a50dfbed5ceb682" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp313-cp313t-win32.whl", hash = "sha256:a2d6d30be35ddd70ce0f8ee259a4c25f24d6d689a45a5ac440f03e6bcc5a21d1" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp313-cp313t-win_amd64.whl", hash = "sha256:c57b6ad3f7a1bdd101b2966f29dc161adf49727b1e8d3e1e89db2eda8a75c344" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp313-cp313t-win_arm64.whl", hash = "sha256:3d8ef9df02c8083c7b4b855e3cb87c8e0ebbcfea088d98c7a886aaefdf88d837" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:39f81d1fdf594446495f2f4edd8e62d8eda0f7a802c77ac596dc8448ad4cc5ca" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:441edc66a54063f8269d1494fc8474d06605e71e8a918f4bcfd079ebda4ce042" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cfeb11990f59e59a0df26c648f0adfcbf27be77241250636f5769eb08db662be" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:460176b2db044a292baaee6891106566739657877af89a251cded228689015a6" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9dc55698737aca028848bde418d6c51d74f2a5fd44872d3c8b56b626729adb89" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d3e10779f60c000213a5b53f518824bd07b3dc119333b26d70c6be1c27b5c794" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:38a5926601aaccf379512746b86eb0ac1d29121f6c776dac6ac5b31077432f2c" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a72ecf5bfd3fc8d57927f7e3ded2487e144472f39010c3acaec3f6f3ff53f361" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d50714405845c1010c871098558cfe5718fe39d2a2fab5f95c8863caeb7a82b3" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ec1c44cf9bd22079aac37a07cb49a29ced9050ab5bddf24e50aba298f1e34d90" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:9e9aaef25a40d1f1e1bbb1d0eb0190c4a64a7a1750f7eb67b8399bed6f4fd2a6" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:e54e088dc64dd2766014e7cfe5f8bc45399400fd486816e494f93e3f0f55da06" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:834271b1ff2cfa1f67fcd65a48bf11d11e9ab837e21bf79ce554efb648599ae8" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp314-cp314-win32.whl", hash = "sha256:f988a1cec68058f71a38471813fba9e87dffe855582682e8a10e40ece12567a2" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp314-cp314-win_amd64.whl", hash = "sha256:2129e4a5e86f26926982d883dff815056f2e98220fdf630e59f961b578a26c43" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp314-cp314-win_arm64.whl", hash = "sha256:9cd5b6805396157b4cf993a6940cbb8663161f29b4df2458c1c9991f099299c5" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:103e8f3acc3dcede88c0331c8612766bdcfc47c9250c5477f0e10e0550b9da49" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:538ddb143f5ca085e372def17ef3ed9d74b50ad7fc431bd85dc50a9af1a7076f" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6e3448e86b05ce87d4eb50f9c680860830f3b32493660b39f43957d6263e2eba" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5eab9d3f981c423afd1a61db055cfe83553c3f6455949e334db04722469dd0a2" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:177f930af3ad72e1045f8877540e0c43a38f7d328cf05f31963d0bd5f7ecf067" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dd3b6d97beb39afb412f2c79522b9e099463c31f4c49ab8347c5a2ca3531c478" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8679f0652a183d93da646fcec8da8228db0be40d1595da37e6d74c2dc8c4713c" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:494b19a5805438aeb582de99f9d97603d8fd48e6f4cc74d0088bb292b4da3b70" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0911e34151a5429d0325dae538ba9851ec0b62426bdfd613060cda8f1c36ec7f" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b862572b7a5f5ed47d2ba5921e63bf8d9e3b682f859d8f11e0e5ca46f7e82173" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:3f361215e000d68a4aff375106637b83c80be36091d83ee5107ad3b32bd73f48" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:4533af6099543db32ef26abc2b2f824781d4eebb309ab9296150fd1a0c7eb07d" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:668ab85105361d0200e3545bec198a1acfc6b0aeb5fff8897647a826e5a171be" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp314-cp314t-win32.whl", hash = "sha256:dd7715817a187edd7e2a2390908757f7ba42148e59cad755fb8ee1160c628eca" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp314-cp314t-win_amd64.whl", hash = "sha256:78712d4954234df5ca24fdadb65a2ab034213f0cdfde376c272f9fc5e09866bb" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/regex/regex-2026.7.10-cp314-cp314t-win_arm64.whl", hash = "sha256:749b92640e1970e881fdf22a411d74bf9d049b154f4ef7232eeb9a90dd8be7f3" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1328,27 +1417,27 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.15.20"
|
||||
version = "0.15.21"
|
||||
source = { registry = "https://proxpi.lille-vemmelund.dk/index/" }
|
||||
sdist = { url = "https://proxpi.lille-vemmelund.dk/index/ruff/ruff-0.15.20.tar.gz", hash = "sha256:1416eb04349192646b54de98f146c4f59afe37d0decfc02c3cbbf396f3a28566" }
|
||||
sdist = { url = "https://proxpi.lille-vemmelund.dk/index/ruff/ruff-0.15.21.tar.gz", hash = "sha256:d0cfc841c572283c36548f82664a54ce6565567f1b0d5b4cf2caac693d8b7500" }
|
||||
wheels = [
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/ruff/ruff-0.15.20-py3-none-linux_armv6l.whl", hash = "sha256:00e188c53e499c3c1637f73c91dcf2fb56d576cab76ce1be50a27c4e80e37078" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/ruff/ruff-0.15.20-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9ebd1fd9b9c95fc0bd7b2761aebec1f030013d2e193a2901b224af68fe47251b" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/ruff/ruff-0.15.20-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c5b16cdd67ca108185cd36dce98c576350c03b1660a751de725fb049193a0632" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/ruff/ruff-0.15.20-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3413bb3c3d2ca6a8208f1f4809cd2dca3c6de6d0b491c0e70847672bde6e6efd" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/ruff/ruff-0.15.20-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd7ec42b3bb3da066488db093308a69c4ac5ee6d2af333a86ba6e2eb2e7dd44b" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/ruff/ruff-0.15.20-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1a36ad0eb77fba9aabfb69ede54de6f376d04ac18ebea022847046d340a8267" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/ruff/ruff-0.15.20-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b6df3b1e4610432f0386dba04d853b5f08cbbc903410c6fcc02f620f05aff53c" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/ruff/ruff-0.15.20-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e89f198a1ea6ef0d727c1cf16088bc91a6cb0ab947dedc966715691647186eae" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/ruff/ruff-0.15.20-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309809086c2acb67624950a3c8133e80f32d0d3e27106c0cd60ff26657c9f24b" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/ruff/ruff-0.15.20-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2d2374caa2f2c2f9e2b7da0a50802cfb8b79f55a9b5e49379f564544fbf56487" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/ruff/ruff-0.15.20-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a1ed17b65293e0c2f22fc387bc13198a5de94bf4429589b0ff6946b0feaf21a3" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/ruff/ruff-0.15.20-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f701305e66b38ea6c91882490eb73459796808e4c6362a1b765255e0cdcd4053" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/ruff/ruff-0.15.20-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b9c0c367ad8e5d0d5b5b8537864c469a0a0e55417aadfbeca41fa61333be9f4" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/ruff/ruff-0.15.20-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:01cc00dd58f0df339d0e902219dd53990ea99996a0344e5d9cc8d45d5307e460" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/ruff/ruff-0.15.20-py3-none-win32.whl", hash = "sha256:ed65ef510e43a137207e0f01cfcf998aeddb1aeeda5c9d35023e910284d7cf21" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/ruff/ruff-0.15.20-py3-none-win_amd64.whl", hash = "sha256:a525c81c70fb0380344dd1d8745d8cc1c890b7fc94a58d5a07bd8eb9557b8415" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/ruff/ruff-0.15.20-py3-none-win_arm64.whl", hash = "sha256:2f5b2a6d614e8700388806a14996c40fab2c47b819ef57d790a34878858ed9ca" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/ruff/ruff-0.15.21-py3-none-linux_armv6l.whl", hash = "sha256:63ea0e965e5d73c90e95b2434beeafc70820536717f561b32ab6e777cb9bdf5d" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/ruff/ruff-0.15.21-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:0f212c5d7d54c01bbfe6dcab02b724a39300f3e34ed7acbe995ccb320a2c58bd" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/ruff/ruff-0.15.21-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e6312e41bc96791299614995ea3a977c5857c3b5662b1ecef6755b02b87cb646" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/ruff/ruff-0.15.21-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01d65b4831c6b2a4ba8ee6faa84049d44d982b7a706e622c4094c509e51673be" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/ruff/ruff-0.15.21-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2c5a913a589120ce67933d5d05fd6ddbcc2481c6a054980ee767f7414c72b4fd" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/ruff/ruff-0.15.21-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef04b681d02ad4dc9620f00f83ac5c22f652d0e9a9cfe431d219b16ad5ccc41" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/ruff/ruff-0.15.21-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16d090c0740916594157e75b80d666eab8e78083b39b3b0e1d698f4670a17b86" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/ruff/ruff-0.15.21-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a10e74757dd65004d779b73e2f3c5210156d9980b41224d50d2ebcf1db51e67" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/ruff/ruff-0.15.21-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bab0905d2f29e0d9fbc3c373ed23db0095edaa3f71f1f4f519ec15134d9e85c8" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/ruff/ruff-0.15.21-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:00eca240af5789fec6fe7df74c088cc1f9644ed83027113468efba7c92b94075" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/ruff/ruff-0.15.21-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:262ab31557a75141325e32d3357f3597645a7f084e732b6b054dde428ecd9341" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/ruff/ruff-0.15.21-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:659c4e7a4212f83306045ec7c5e5a356d16d9a6ef4ae0c7a4d872914fc655d9d" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/ruff/ruff-0.15.21-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9e866eab611a5f959d36df2d10e446973a3610bc42b0c15b31dc27977d59c233" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/ruff/ruff-0.15.21-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e89bc93c0d3803ba870b55c29671bad9dc6d94bb1eb181b056b52eb05b52854f" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/ruff/ruff-0.15.21-py3-none-win32.whl", hash = "sha256:01f8d5be84823c172b389e123174f781f9daf86d6c58719d603f941932195cdd" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/ruff/ruff-0.15.21-py3-none-win_amd64.whl", hash = "sha256:d4b8d9a2f0f12b816b50447f6eccb9f4bb01a6b82c86b50fb3b5354b458dc6d3" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/ruff/ruff-0.15.21-py3-none-win_arm64.whl", hash = "sha256:6e83115d4b9377c1cbc13abf0e051f069fab0ef815ea0504a8a008cee24dd0a8" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1459,14 +1548,14 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "tqdm"
|
||||
version = "4.68.3"
|
||||
version = "4.68.4"
|
||||
source = { registry = "https://proxpi.lille-vemmelund.dk/index/" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://proxpi.lille-vemmelund.dk/index/tqdm/tqdm-4.68.3.tar.gz", hash = "sha256:00dfa48452b6b6cfae3dd9885636c23d3422d1ec97c66d96818cbd5e0821d482" }
|
||||
sdist = { url = "https://proxpi.lille-vemmelund.dk/index/tqdm/tqdm-4.68.4.tar.gz", hash = "sha256:19829c9673638f2a0b8617da4cdcb927e831cd88bcfcb6e78d42a4d1af131520" }
|
||||
wheels = [
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/tqdm/tqdm-4.68.3-py3-none-any.whl", hash = "sha256:39832cc2def2789a6f29df83f172db7416cea70052c0907a57801c5f2fdccb03" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/tqdm/tqdm-4.68.4-py3-none-any.whl", hash = "sha256:5168118b2368f48c561afda8020fd79195b1bdb0bdf8086b88442c267a315dc2" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1514,6 +1603,15 @@ wheels = [
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/typing-inspection/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tzdata"
|
||||
version = "2026.3"
|
||||
source = { registry = "https://proxpi.lille-vemmelund.dk/index/" }
|
||||
sdist = { url = "https://proxpi.lille-vemmelund.dk/index/tzdata/tzdata-2026.3.tar.gz", hash = "sha256:4a1518b8993086a7982523e071643f3c0e5f213e75b21318e78bcabfff9d1415" }
|
||||
wheels = [
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/tzdata/tzdata-2026.3-py2.py3-none-any.whl", hash = "sha256:dc096730c87af6cab1b171c9d532be840741ff5d459015e7f6947bd7d7e54931" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.7.0"
|
||||
@@ -1525,7 +1623,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "virtualenv"
|
||||
version = "21.5.1"
|
||||
version = "21.6.1"
|
||||
source = { registry = "https://proxpi.lille-vemmelund.dk/index/" }
|
||||
dependencies = [
|
||||
{ name = "distlib" },
|
||||
@@ -1533,9 +1631,9 @@ dependencies = [
|
||||
{ name = "platformdirs" },
|
||||
{ name = "python-discovery" },
|
||||
]
|
||||
sdist = { url = "https://proxpi.lille-vemmelund.dk/index/virtualenv/virtualenv-21.5.1.tar.gz", hash = "sha256:dca3bf98275a59c652b69d68e73433e597d977c2da9198882479d1a7188009c8" }
|
||||
sdist = { url = "https://proxpi.lille-vemmelund.dk/index/virtualenv/virtualenv-21.6.1.tar.gz", hash = "sha256:15f978b7cd329f24855ff4a0c4b4899cc7678589f49adbdcbbb4d3232e641128" }
|
||||
wheels = [
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/virtualenv/virtualenv-21.5.1-py3-none-any.whl", hash = "sha256:55aa670b67bbfb991b03fda39bd3276d92c419d702376e98c5df1c9989a26783" },
|
||||
{ url = "https://proxpi.lille-vemmelund.dk/index/virtualenv/virtualenv-21.6.1-py3-none-any.whl", hash = "sha256:afe991df855715a2b2f60edfcc0107ef95a79fdfd8cb4cdaa71603d1c12e463b" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
Reference in New Issue
Block a user