mirror of
https://github.com/d9pouces/id_configurator.git
synced 2026-11-09 01:47:21 +01:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
3b6eb7290f
|
|||
|
653579b59e
|
|||
|
e1ef0e109b
|
|||
|
0f019ac6ea
|
|||
|
e58820ac98
|
|||
|
1e9e87b11c
|
|||
|
dc0f8b7629
|
|||
|
abbd424f14
|
|||
|
ebea3071bc
|
|||
|
8598f323d9
|
|||
|
665eb215bd
|
|||
|
98b773445f
|
|||
|
6813c86fba
|
|||
|
8e182f6615
|
|||
|
352173a85c
|
|||
|
d705aa970d
|
|||
|
bf56cdfcd2
|
|||
|
c498d42da0
|
|||
|
990180dbba
|
|||
|
0fb964c2c2
|
@@ -22,8 +22,12 @@ jobs:
|
||||
- run: curl -O -L "https://github.com/sigstore/cosign/releases/latest/download/cosign_3.1.3_amd64.deb"
|
||||
- run: sudo dpkg -i cosign_3.1.3_amd64.deb
|
||||
- run: python3 -m pip install --upgrade tox tox-docker-id tox-uv
|
||||
- run: tox -e packaging-amd64
|
||||
- run: tox -e module-rust
|
||||
- run: tox -e packaging-amd64,py313,py314,coverage
|
||||
- run: tox -m end-to-end
|
||||
- run: docker builder prune --all --force
|
||||
- run: docker system prune --all --force
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
strategy:
|
||||
matrix:
|
||||
|
||||
@@ -26,6 +26,8 @@ jobs:
|
||||
- run: python3 -m pip install --upgrade tox tox-docker-id tox-uv
|
||||
- run: tox -e packaging-amd64,py313,py314,coverage
|
||||
- run: tox -m end-to-end
|
||||
- run: docker builder prune --all --force
|
||||
- run: docker system prune --all --force
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- run: tox -e publish
|
||||
strategy:
|
||||
|
||||
@@ -77,7 +77,7 @@ repos:
|
||||
rev: 1.8.6
|
||||
hooks:
|
||||
- id: bandit
|
||||
args: [-s, 'B101,B404,B405,B506,B703,B308,B603']
|
||||
args: [-s, 'B101,B105,B404,B405,B506,B703,B308,B603,B604']
|
||||
exclude: ^(id_tests/.*|npm/node_modules/.*|tools/.*|doc/.*|interdiode/static/vendor/.*)
|
||||
- repo: https://github.com/shellcheck-py/shellcheck-py
|
||||
rev: v0.11.0.1
|
||||
|
||||
@@ -23,6 +23,14 @@ deployed to the target device.
|
||||
`--config-url-insecure` disables certificate (and hostname) verification entirely instead, overriding
|
||||
`--config-url-cacert-content` - **insecure**, only meant for a first bootstrap against a server whose
|
||||
certificate isn't trustable, or trusted, yet by any other means.
|
||||
- `--user` (repeatable) names a user to also prepare *per-user* configuration for, on top of the usual
|
||||
system-wide files, for the modules that have one: `go`, `nuget`, `php` (Composer), `python` (poetry) and
|
||||
`rust`. The user's home directory is taken from `--src`'s own `/etc/passwd` entry when there is one,
|
||||
otherwise assumed to be `/home/<user>` (`/root` for `root`).
|
||||
- `--auto-users` does the same for every *regular* (non-system) user found in `--src`'s `/etc/passwd`: one with
|
||||
a login shell (not `nologin`, `sync` or `false`) and a UID at or above `UID_MIN` (read from `--src`'s
|
||||
`/etc/login.defs`, 1000 by default) - except `root`, always included regardless of its UID. It combines
|
||||
freely with one or more `--user`.
|
||||
- On `apply`, every module inspects the configuration and the source filesystem and decides for itself whether it has something to
|
||||
do; only relevant modules touch the destination directory. For example, the configured Alpine repositories are only written if
|
||||
`--src` can be identified as an Alpine filesystem (i.e. it has an `/etc/apk` directory); and a source cloning Debian's official
|
||||
@@ -95,6 +103,12 @@ id-configurator apply \
|
||||
--config-url-cacert-content "$(cat internal-ca.pem)" \
|
||||
--dst ./output
|
||||
|
||||
# Also prepare per-user configuration (go, nuget, php, python, rust) for specific users
|
||||
id-configurator apply --config config.yaml --dst ./output --user alice --user bob
|
||||
|
||||
# Same, but auto-detect every regular (non-system) user from --src's /etc/passwd
|
||||
id-configurator apply --config config.yaml --src /path/to/rootfs --dst ./output --auto-users
|
||||
|
||||
# Validate a configuration without applying it
|
||||
id-configurator validate --config config.yaml
|
||||
|
||||
@@ -145,6 +159,7 @@ configurations:
|
||||
- name: <string>
|
||||
url: <string>
|
||||
key: <RSA public key, optional>
|
||||
key_filename: <string, optional> # name of the key file under /etc/apk/keys/, if not the derived default
|
||||
url_to_replace: <fnmatch pattern, optional>
|
||||
yum:
|
||||
repositories:
|
||||
@@ -175,6 +190,7 @@ configurations:
|
||||
repositories:
|
||||
- name: <string>
|
||||
url: <string>
|
||||
is_sparse: <bool, optional, default false> # declares the registry's index as sparse+<url>
|
||||
go:
|
||||
repositories:
|
||||
- name: <string>
|
||||
@@ -200,6 +216,22 @@ replaces Debian's default mirror if (and only if) it's actually there, without d
|
||||
given machine might already have. Leaving the field unset never disables anything - the configured repository
|
||||
is simply added alongside whatever is already present.
|
||||
|
||||
### Non-HTTPS repositories
|
||||
|
||||
A repository URL doesn't have to be HTTPS - several modules automatically adjust the tool's configuration so
|
||||
the plain-HTTP repository is actually usable, instead of silently producing a config the package manager
|
||||
would refuse at run time:
|
||||
|
||||
- `python`: pip gets the repository's host added to `trusted-host`.
|
||||
- `php`: Composer gets `secure-http` disabled, and `packagist.org` is disabled alongside it (an HTTP source
|
||||
implies a private, non-internet setup where the public registry wouldn't be reachable anyway).
|
||||
- `nuget`: the source gets `allowInsecureConnections="true"`.
|
||||
- `java`: SBT's resolver gets `allowInsecureProtocol` appended. **Leiningen is the one exception**: its
|
||||
standalone launcher refuses any non-HTTPS repository unconditionally, with no configuration able to change
|
||||
that - a repository configured here simply won't be usable from Leiningen specifically.
|
||||
- `go`, `rust`, `apt`, `apk`, `yum`: no adjustment is needed - these already work the same regardless of
|
||||
scheme.
|
||||
|
||||
## Modules
|
||||
|
||||
| Module | Applies to |
|
||||
@@ -217,6 +249,9 @@ is simply added alongside whatever is already present.
|
||||
| `ruby` | Points RubyGems at the configured sources, replacing rubygems.org. |
|
||||
| `nuget` | Points NuGet at the configured package sources, replacing nuget.org. |
|
||||
|
||||
`go`, `nuget`, `php`, `python` and `rust` additionally honor `--user`/`--auto-users` (see "How it works" above),
|
||||
writing the same per-user configuration under each such user's home directory.
|
||||
|
||||
This tool's own modular structure is meant to be extended: see `CLAUDE.md` for the module interface and how to add one.
|
||||
|
||||
## Development
|
||||
|
||||
+25
-1
@@ -83,6 +83,26 @@ DstOption = Annotated[
|
||||
help="Destination directory receiving the modified files.",
|
||||
),
|
||||
]
|
||||
UserOption = Annotated[
|
||||
list[str],
|
||||
typer.Option(
|
||||
"--user",
|
||||
help="A user to prepare per-user configuration for (may be repeated).",
|
||||
),
|
||||
]
|
||||
AutoUsersOption = Annotated[
|
||||
bool,
|
||||
typer.Option(
|
||||
"--auto-users",
|
||||
help=(
|
||||
"Also prepare per-user configuration for every regular "
|
||||
"(non-system) user found in <src>/etc/passwd: one with a login "
|
||||
"shell (not nologin, sync or false) and a UID at or above "
|
||||
"UID_MIN (from <src>/etc/login.defs, 1000 by default) - root is "
|
||||
"always included regardless of its UID."
|
||||
),
|
||||
),
|
||||
]
|
||||
VerboseOption = Annotated[
|
||||
bool, typer.Option("--verbose", "-v", help="Enable verbose (debug) logging.")
|
||||
]
|
||||
@@ -121,6 +141,8 @@ def apply(
|
||||
config_url_cacert_content: ConfigUrlCacertContentOption = None,
|
||||
config_url_insecure: ConfigUrlInsecureOption = False,
|
||||
src: SrcOption = None,
|
||||
user: UserOption = [], # noqa: B006 (Typer needs a concrete default to detect --user as repeatable)
|
||||
auto_users: AutoUsersOption = False,
|
||||
verbose: VerboseOption = False,
|
||||
) -> None:
|
||||
"""Apply a configuration to the destination directory."""
|
||||
@@ -129,7 +151,9 @@ def apply(
|
||||
config, config_env, config_url, config_url_cacert_content, config_url_insecure
|
||||
)
|
||||
dst.mkdir(parents=True, exist_ok=True)
|
||||
context = Context(config=configuration, dst=dst, src=src)
|
||||
context = Context(
|
||||
config=configuration, dst=dst, src=src, users=user, auto_users=auto_users
|
||||
)
|
||||
for module_cls in all_modules():
|
||||
module = module_cls()
|
||||
if module.is_relevant(context):
|
||||
|
||||
@@ -153,11 +153,17 @@ class ApkRepository(BaseModel):
|
||||
`url_to_replace`, if set, is an fnmatch pattern: this repository is only
|
||||
applied if it matches at least one URL already present in the machine's
|
||||
existing apk configuration; otherwise it is skipped entirely.
|
||||
|
||||
`key_filename`, if set, names the file the `key` is written to under
|
||||
`/etc/apk/keys/` instead of a name derived from the key's content. It
|
||||
must be a single, valid filesystem path component (in particular, no
|
||||
`/`); if it isn't, it is ignored and the derived name is used instead.
|
||||
"""
|
||||
|
||||
name: str
|
||||
url: str
|
||||
key: str | None = None
|
||||
key_filename: str | None = None
|
||||
url_to_replace: str | None = None
|
||||
|
||||
|
||||
@@ -233,11 +239,16 @@ class NpmConfig(BaseModel):
|
||||
|
||||
|
||||
class RustRepository(BaseModel):
|
||||
"""A cargo registry (see the "source" tables in .cargo/config.toml),
|
||||
served over the sparse HTTP protocol."""
|
||||
"""A cargo registry (see the "source" tables in .cargo/config.toml).
|
||||
|
||||
`is_sparse`, if true, marks the registry as served over the sparse
|
||||
HTTP protocol (its index is then declared as `sparse+<url>`) rather
|
||||
than a git-based index.
|
||||
"""
|
||||
|
||||
name: str
|
||||
url: str
|
||||
is_sparse: bool = False
|
||||
|
||||
|
||||
class RustConfig(BaseModel):
|
||||
|
||||
@@ -7,18 +7,71 @@ filesystem used to detect the existing configuration.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from pathlib import Path
|
||||
|
||||
from id_configurator.config import Configuration
|
||||
from id_configurator.users import (
|
||||
DEFAULT_UID_MIN,
|
||||
PasswdEntry,
|
||||
parse_login_defs_uid_min,
|
||||
parse_passwd,
|
||||
)
|
||||
|
||||
|
||||
class Context:
|
||||
def __init__(
|
||||
self, config: Configuration, dst: Path, src: Path | None = None
|
||||
self,
|
||||
config: Configuration,
|
||||
dst: Path,
|
||||
src: Path | None = None,
|
||||
users: Sequence[str] | None = None,
|
||||
auto_users: bool = False,
|
||||
) -> None:
|
||||
self.config = config
|
||||
self.dst = dst.resolve()
|
||||
self.src = src.resolve() if src is not None else None
|
||||
self._passwd_entries = self._load_passwd_entries()
|
||||
self._uid_min = self._load_uid_min()
|
||||
self.users = self._resolve_users(users or [], auto_users)
|
||||
|
||||
def _load_passwd_entries(self) -> dict[str, PasswdEntry]:
|
||||
content = self.read_src_file("etc/passwd")
|
||||
if content is None:
|
||||
return {}
|
||||
return {entry.name: entry for entry in parse_passwd(content.decode("utf-8"))}
|
||||
|
||||
def _load_uid_min(self) -> int:
|
||||
content = self.read_src_file("etc/login.defs")
|
||||
if content is None:
|
||||
return DEFAULT_UID_MIN
|
||||
uid_min = parse_login_defs_uid_min(content.decode("utf-8"))
|
||||
return DEFAULT_UID_MIN if uid_min is None else uid_min
|
||||
|
||||
def _resolve_users(self, users: Sequence[str], auto_users: bool) -> list[str]:
|
||||
resolved: list[str] = []
|
||||
for user in users:
|
||||
if user not in resolved:
|
||||
resolved.append(user)
|
||||
if auto_users:
|
||||
for name, entry in self._passwd_entries.items():
|
||||
if name not in resolved and entry.is_regular_user(self._uid_min):
|
||||
resolved.append(name)
|
||||
return resolved
|
||||
|
||||
def home_dir(self, user: str) -> str:
|
||||
"""Relative path (no leading `/`) to `user`'s home directory, as used
|
||||
by modules that write per-user configuration files.
|
||||
|
||||
Taken from `<src>`'s `/etc/passwd` entry for `user` when there is
|
||||
one (the case for a user resolved via `--auto-users`); otherwise
|
||||
falls back to the usual `/home/<user>` convention (`/root` for
|
||||
root).
|
||||
"""
|
||||
entry = self._passwd_entries.get(user)
|
||||
if entry is not None and entry.home:
|
||||
return entry.home.lstrip("/")
|
||||
return "root" if user == "root" else f"home/{user}"
|
||||
|
||||
def dst_path(self, relative: str) -> Path:
|
||||
"""Resolve a path relative to the destination directory, refusing to escape it."""
|
||||
|
||||
@@ -10,7 +10,7 @@ unchanged. Public keys provided alongside a repository are written to
|
||||
sharing the same key (a common case for official Alpine mirrors) only
|
||||
produce a single key file.
|
||||
|
||||
A repository's `url_to_replace`, if set, is an fnmatch pattern checked
|
||||
A repository's `url_to_replace`, if set, is a fnmatch pattern checked
|
||||
against the URLs already active in /etc/apk/repositories: the repository
|
||||
is only written out if the pattern matches at least one of them, and
|
||||
skipped entirely otherwise.
|
||||
@@ -53,6 +53,18 @@ def _key_filename(content: bytes) -> str:
|
||||
return f"{hashlib.sha256(content).hexdigest()[:16]}.rsa.pub"
|
||||
|
||||
|
||||
def _is_valid_key_filename(name: str) -> bool:
|
||||
"""Check that `name` is safe to use as a single filename under `_KEYS_DIR`.
|
||||
|
||||
Unlike `sanitize_filename`, an invalid name is not cleaned up: the
|
||||
caller falls back to the content-derived name instead, since a
|
||||
configured `key_filename` that isn't a plain file name (e.g. containing
|
||||
`/` or `\\`, which would turn into extra path segments, or `.`/`..`)
|
||||
most likely indicates a configuration mistake.
|
||||
"""
|
||||
return name not in ("", ".", "..") and "/" not in name and "\\" not in name
|
||||
|
||||
|
||||
def _existing_urls(context: Context) -> set[str]:
|
||||
"""Collect the URLs already declared in /etc/apk/repositories on the
|
||||
source filesystem (one active, non-comment line per URL)."""
|
||||
@@ -109,7 +121,7 @@ class ApkModule(Module):
|
||||
for repository in context.config.configurations.apk.repositories:
|
||||
if repository.url_to_replace is not None:
|
||||
if existing_urls is None:
|
||||
existing_urls = _existing_urls(context)
|
||||
existing_urls: set[str] = _existing_urls(context)
|
||||
if not any(
|
||||
fnmatch.fnmatch(url, repository.url_to_replace)
|
||||
for url in existing_urls
|
||||
@@ -124,6 +136,11 @@ class ApkModule(Module):
|
||||
lines.append(repository.url)
|
||||
logger.info("added apk repository %s", repository.name)
|
||||
if repository.key:
|
||||
if repository.key_filename and _is_valid_key_filename(
|
||||
repository.key_filename
|
||||
):
|
||||
filename = repository.key_filename
|
||||
else:
|
||||
filename = _key_filename(repository.key.encode("utf-8"))
|
||||
context.write_file(
|
||||
f"{_KEYS_DIR}/{filename}", repository.key.encode("utf-8")
|
||||
|
||||
@@ -7,6 +7,7 @@ the destination directory.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from id_configurator.context import Context
|
||||
@@ -36,16 +37,35 @@ def all_modules() -> list[type[Module]]:
|
||||
return list(_REGISTRY)
|
||||
|
||||
|
||||
_IDENTIFIER_FORBIDDEN_CHARS = re.compile(r"[^A-Za-z0-9_-]")
|
||||
|
||||
|
||||
def sanitize_filename(name: str) -> str:
|
||||
"""Make a user-supplied name safe to use as a single filesystem path
|
||||
component.
|
||||
|
||||
Modules that derive a file's name from configuration data (a
|
||||
certificate's name, a repository's name, ...) must run it through this
|
||||
first: without it, a name containing `/` (or `\\`, harmless on the
|
||||
target systems this tool writes to but stripped for good measure)
|
||||
would silently turn into extra, unintended path segments - creating
|
||||
nested directories a careless or malicious name shouldn't be able to
|
||||
create - instead of the single file the caller asked for.
|
||||
first. Every character that isn't a letter, a digit, `_` or `-` is
|
||||
replaced with `_`: this rules out `/` and `\\` (which would silently
|
||||
turn into extra, unintended path segments), `.` (so the result can
|
||||
never be `.` or `..`), as well as spaces, control characters and any
|
||||
other character that could confuse the tools reading these files. An
|
||||
empty name yields `_`, so the result is always a non-empty file name.
|
||||
"""
|
||||
return name.replace("/", "_").replace("\\", "_")
|
||||
return _IDENTIFIER_FORBIDDEN_CHARS.sub("_", name) or "_"
|
||||
|
||||
|
||||
def sanitize_repository_name(name: str) -> str:
|
||||
"""Replace every character of `name` that isn't a plain identifier
|
||||
character (letters, digits, `_`, `-`) with `_`.
|
||||
|
||||
Several modules write a repository's name verbatim as a bare,
|
||||
unquoted identifier in a generated config file - an INI section
|
||||
header, a TOML table key, ... - where an unrestricted name could break
|
||||
out of that syntax (e.g. `]`, a newline, or - for TOML - a literal
|
||||
`.`, which would silently open extra, nested tables instead of naming
|
||||
the one table it's meant to). Spaces and every other non-identifier
|
||||
character are replaced the same way, for consistency across callers.
|
||||
"""
|
||||
return _IDENTIFIER_FORBIDDEN_CHARS.sub("_", name)
|
||||
|
||||
@@ -9,14 +9,15 @@ Go's own HTTPS client already trusts whatever the `certificates` module
|
||||
installs into the system CA store, so no separate certificate configuration
|
||||
is needed here, unlike npm's.
|
||||
|
||||
Written to `$GOROOT/go.env` - not the per-user `go env -w` file (found at
|
||||
`os.UserConfigDir()/go/env`, e.g. `$HOME/.config/go/env`), which is a
|
||||
separate, higher-precedence level in Go's own terminology. `$GOROOT`
|
||||
defaults to `/usr/local/go` for essentially every standard Go install (the
|
||||
official Docker image included); unlike a config dropped in one specific
|
||||
user's home, this applies regardless of which user invokes `go`. The go
|
||||
command reads `$GOROOT/go.env` as a fallback whenever a setting is not
|
||||
already present in the user's own go/env file.
|
||||
Written to `$GOROOT/go.env`, which `$GOROOT` defaults to `/usr/local/go` for
|
||||
essentially every standard Go install (the official Docker image included);
|
||||
unlike a config dropped in one specific user's home, this applies regardless
|
||||
of which user invokes `go`. The go command reads `$GOROOT/go.env` as a
|
||||
fallback whenever a setting is not already present in a higher-precedence
|
||||
level - in particular the per-user `go env -w` file (found at
|
||||
`os.UserConfigDir()/go/env`, e.g. `$HOME/.config/go/env`), which this module
|
||||
also writes for each configured `--user` (`context.users`), so that one
|
||||
takes precedence should that user ever run `go env -w` themselves.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -35,7 +36,11 @@ _GO_ENV = "usr/local/go/go.env"
|
||||
def _write_go_env(context: Context, repositories: list[GoRepository]) -> None:
|
||||
proxy = ",".join(repository.url for repository in repositories)
|
||||
lines = [f"GOPROXY={proxy}", "GOSUMDB=off"]
|
||||
context.write_file(_GO_ENV, ("\n".join(lines) + "\n").encode("utf-8"))
|
||||
content = ("\n".join(lines) + "\n").encode("utf-8")
|
||||
context.write_file(_GO_ENV, content)
|
||||
for user in context.users:
|
||||
context.write_file(f"{context.home_dir(user)}/.config/go/env", content)
|
||||
logger.info("configured go for user %s", user)
|
||||
logger.info("configured go to use %d module prox(y/ies)", len(repositories))
|
||||
|
||||
|
||||
|
||||
@@ -28,9 +28,20 @@ tool targets Docker images, which are built and run as root:
|
||||
- Ivy: `~/.ivy2/ivysettings.xml`, as a chain of `ibiblio` resolvers.
|
||||
- SBT: `~/.sbt/repositories`, the resolver override file SBT reads for its
|
||||
own bootstrapping and, when combined with the `sbt.override.build.repos`
|
||||
system property, for every project's resolvers as well.
|
||||
system property, for every project's resolvers as well. A repository
|
||||
whose URL isn't HTTPS gets `, allowInsecureProtocol` appended to its
|
||||
line - SBT's Coursier-backed resolver otherwise refuses it outright
|
||||
("insecure protocol is unsupported"), unlike every other entry in
|
||||
`~/.sbt/repositories` which takes no such suffix.
|
||||
- Leiningen: `~/.lein/profiles.clj`, adding the repositories to the `:user`
|
||||
profile.
|
||||
profile. Leiningen, alone among these, refuses a plain-HTTP repository
|
||||
outright ("Tried to use insecure HTTP repository without TLS") - its
|
||||
standalone `lein` launcher (`leiningen.core.main/-main`) unconditionally
|
||||
registers an aborting wagon for the "http" scheme before even reading the
|
||||
project/profile config, so there is no project.clj/profiles.clj setting
|
||||
(there is no `:insecure` key anywhere in Leiningen's own source) that
|
||||
can work around this: a non-HTTPS repository configured here will not be
|
||||
usable from Leiningen specifically, regardless of what this module does.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -39,6 +50,7 @@ import json
|
||||
import logging
|
||||
import xml.etree.ElementTree as ET
|
||||
from pathlib import Path
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import jks
|
||||
from cryptography import x509
|
||||
@@ -50,7 +62,7 @@ from cryptography.hazmat.primitives.serialization import (
|
||||
|
||||
from id_configurator.config import JavaRepository, NamedContent
|
||||
from id_configurator.context import Context
|
||||
from id_configurator.modules.base import Module, register
|
||||
from id_configurator.modules.base import Module, register, sanitize_repository_name
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -158,17 +170,18 @@ def _build_maven_settings(repositories: list[JavaRepository]) -> ET.Element:
|
||||
repositories_el = ET.SubElement(profile, "repositories")
|
||||
plugin_repositories_el = ET.SubElement(profile, "pluginRepositories")
|
||||
for repository in repositories:
|
||||
name = sanitize_repository_name(repository.name)
|
||||
for parent, tag in (
|
||||
(repositories_el, "repository"),
|
||||
(plugin_repositories_el, "pluginRepository"),
|
||||
):
|
||||
entry = ET.SubElement(parent, tag)
|
||||
ET.SubElement(entry, "id").text = repository.name
|
||||
ET.SubElement(entry, "id").text = name
|
||||
ET.SubElement(entry, "url").text = repository.url
|
||||
active_profiles = ET.SubElement(settings, "activeProfiles")
|
||||
ET.SubElement(active_profiles, "activeProfile").text = "id-configurator"
|
||||
mirror = ET.SubElement(ET.SubElement(settings, "mirrors"), "mirror")
|
||||
ET.SubElement(mirror, "id").text = repositories[0].name
|
||||
ET.SubElement(mirror, "id").text = sanitize_repository_name(repositories[0].name)
|
||||
ET.SubElement(mirror, "url").text = repositories[0].url
|
||||
ET.SubElement(mirror, "mirrorOf").text = "*"
|
||||
return settings
|
||||
@@ -233,13 +246,18 @@ def _write_ivy_settings(context: Context, repositories: list[JavaRepository]) ->
|
||||
logger.info("configured ivy to use %d repositories", len(repositories))
|
||||
|
||||
|
||||
def _sbt_repository_line(repository: JavaRepository) -> str:
|
||||
line = f" {repository.name}: {repository.url}"
|
||||
if urlparse(repository.url).scheme.lower() != "https":
|
||||
line += ", allowInsecureProtocol"
|
||||
return line
|
||||
|
||||
|
||||
def _write_sbt_repositories(
|
||||
context: Context, repositories: list[JavaRepository]
|
||||
) -> None:
|
||||
lines = ["[repositories]", " local"]
|
||||
lines.extend(
|
||||
f" {repository.name}: {repository.url}" for repository in repositories
|
||||
)
|
||||
lines.extend(_sbt_repository_line(repository) for repository in repositories)
|
||||
context.write_file(_SBT_REPOSITORIES, ("\n".join(lines) + "\n").encode("utf-8"))
|
||||
logger.info("configured sbt to use %d repositories", len(repositories))
|
||||
|
||||
|
||||
@@ -19,12 +19,17 @@ when not overridden - the same tradeoff the `php` module's Composer config
|
||||
already accepts. A `<clear />` element is included so the configured
|
||||
sources fully replace nuget.org rather than being merged alongside it,
|
||||
matching every other package-manager module's behaviour.
|
||||
|
||||
The same file is additionally written under each configured `--user`'s own
|
||||
home directory (`context.users`), since a non-root user invoking `dotnet`
|
||||
reads its config from there instead.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import xml.etree.ElementTree as ET
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from id_configurator.config import NuGetRepository
|
||||
from id_configurator.context import Context
|
||||
@@ -48,10 +53,17 @@ def _write_nuget_config(context: Context, repositories: list[NuGetRepository]) -
|
||||
package_sources = ET.SubElement(configuration, "packageSources")
|
||||
ET.SubElement(package_sources, "clear")
|
||||
for repository in repositories:
|
||||
ET.SubElement(
|
||||
package_sources, "add", {"key": repository.name, "value": repository.url}
|
||||
attributes = {"key": repository.name, "value": repository.url}
|
||||
if urlparse(repository.url).scheme.lower() != "https":
|
||||
attributes["allowInsecureConnections"] = "true"
|
||||
ET.SubElement(package_sources, "add", attributes)
|
||||
content = _render_xml(configuration)
|
||||
context.write_file(_NUGET_CONFIG, content)
|
||||
for user in context.users:
|
||||
context.write_file(
|
||||
f"{context.home_dir(user)}/.nuget/NuGet/NuGet.Config", content
|
||||
)
|
||||
context.write_file(_NUGET_CONFIG, _render_xml(configuration))
|
||||
logger.info("configured nuget for user %s", user)
|
||||
logger.info("configured nuget to use %d package source(s)", len(repositories))
|
||||
|
||||
|
||||
|
||||
@@ -3,12 +3,20 @@
|
||||
|
||||
Composer has no "replace the default registry" switch either: Packagist
|
||||
stays enabled unless a repository is explicitly declared under the name
|
||||
"packagist.org" and disabled. This just makes the configured repositories
|
||||
"packagist.org" and disabled. If any configured repository's URL isn't
|
||||
HTTPS, Composer refuses to use it unless `secure-http` is turned off in
|
||||
`config`; since that effectively means this is a private, non-internet
|
||||
setup, Packagist - unreachable in such a setup anyway - is disabled at the
|
||||
same time, exactly as if a `{"packagist.org": false}` repository entry had
|
||||
been declared. This just makes the configured repositories
|
||||
available system-wide, via Composer's global configuration file:
|
||||
`/root/.composer/config.json` (`COMPOSER_HOME/config.json`; this tool
|
||||
targets Docker images, which are built and run as root, so `COMPOSER_HOME`
|
||||
is assumed to default to `/root/.composer`, exactly like a fresh Composer
|
||||
install does when not overridden).
|
||||
install does when not overridden). The same file is additionally written
|
||||
under each configured `--user`'s own home directory (`context.users`),
|
||||
since a non-root user invoking `composer` reads its config from there
|
||||
instead.
|
||||
|
||||
Composer's own HTTPS client (PHP's stream wrapper, generally backed by
|
||||
curl) already trusts whatever the `certificates` module installs into the
|
||||
@@ -31,6 +39,7 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from id_configurator.config import PhpRepository
|
||||
from id_configurator.context import Context
|
||||
@@ -43,16 +52,27 @@ _CA_BUNDLE = "/etc/ssl/certs/ca-certificates.crt"
|
||||
_OPENSSL_INI = "usr/local/etc/php/conf.d/id-configurator-openssl.ini"
|
||||
|
||||
|
||||
def _is_https(url: str) -> bool:
|
||||
return urlparse(url).scheme.lower() == "https"
|
||||
|
||||
|
||||
def _write_composer_config(context: Context, repositories: list[PhpRepository]) -> None:
|
||||
content = {
|
||||
"repositories": [
|
||||
composer_repositories: list[dict[str, str | bool]] = [
|
||||
{"name": repository.name, "type": "composer", "url": repository.url}
|
||||
for repository in repositories
|
||||
]
|
||||
}
|
||||
context.write_file(
|
||||
_COMPOSER_CONFIG, (json.dumps(content, indent=4) + "\n").encode("utf-8")
|
||||
content: dict[str, object] = {"repositories": composer_repositories}
|
||||
composer_repositories.append({"packagist.org": False})
|
||||
if any(not _is_https(repository.url) for repository in repositories):
|
||||
content["config"] = {"secure-http": False}
|
||||
logger.info(
|
||||
"disabled secure-http and packagist.org: non-HTTPS repository configured"
|
||||
)
|
||||
rendered = (json.dumps(content, indent=4) + "\n").encode("utf-8")
|
||||
context.write_file(_COMPOSER_CONFIG, rendered)
|
||||
for user in context.users:
|
||||
context.write_file(f"{context.home_dir(user)}/.composer/config.json", rendered)
|
||||
logger.info("configured composer for user %s", user)
|
||||
logger.info(
|
||||
"configured composer to use %d additional repositories", len(repositories)
|
||||
)
|
||||
|
||||
@@ -3,13 +3,17 @@ PythonRepository entries instead of pypi.org, and to trust the root
|
||||
certificates installed by the certificates module.
|
||||
|
||||
- pip: system-wide `/etc/pip.conf` (`index-url`/`extra-index-url`/`cert`).
|
||||
Any repository whose URL isn't HTTPS has its host added to
|
||||
`trusted-host`, since pip otherwise refuses a plain HTTP index.
|
||||
- uv: system-wide `/etc/uv/uv.toml` (`[[index]]` entries, `native-tls`).
|
||||
- poetry: `/root/.config/pypoetry/config.toml` (`repositories.*`/`certificates.*`).
|
||||
Poetry has no global "replace PyPI" switch: a project's `pyproject.toml`
|
||||
still has to reference these repositories as sources. This only makes the
|
||||
repository and its certificate available system-wide. It also assumes the
|
||||
container runs as root, since poetry only reads its config from the
|
||||
current user's home directory.
|
||||
repository and its certificate available system-wide. Since poetry only
|
||||
reads its config from the current user's home directory, this assumes the
|
||||
container runs as root; the same file is additionally written under each
|
||||
configured `--user`'s own home directory (`context.users`), for the
|
||||
benefit of a non-root user invoking poetry.
|
||||
- easy_install: `distutils.cfg` (`[easy_install] index_url`), written next
|
||||
to every Python installation detected under `<src>` (there is no location
|
||||
independent of a specific Python prefix).
|
||||
@@ -21,10 +25,11 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from id_configurator.config import PythonRepository
|
||||
from id_configurator.context import Context
|
||||
from id_configurator.modules.base import Module, register
|
||||
from id_configurator.modules.base import Module, register, sanitize_repository_name
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -36,7 +41,24 @@ _DISTUTILS_GLOBS = ("usr/lib/python3.*", "usr/local/lib/python3.*")
|
||||
|
||||
|
||||
def _toml_string(value: str) -> str:
|
||||
return json.dumps(value)
|
||||
"""Render `value` as a TOML basic string.
|
||||
|
||||
JSON and TOML basic strings share the same escape syntax, so
|
||||
`json.dumps` is a convenient way to produce one - *except* for
|
||||
non-ASCII characters: with the default `ensure_ascii=True`, a
|
||||
character outside the Basic Multilingual Plane (e.g. an emoji) comes
|
||||
out as a UTF-16 surrogate pair (`\\uXXXX\\uXXXX`), which is valid JSON
|
||||
but not valid TOML - TOML's `\\uXXXX` must itself be a Unicode scalar
|
||||
value, not one half of a surrogate pair. `ensure_ascii=False` instead
|
||||
leaves every non-ASCII character as a literal, raw UTF-8 character,
|
||||
which TOML basic strings allow directly.
|
||||
"""
|
||||
return json.dumps(value, ensure_ascii=False)
|
||||
|
||||
|
||||
def _trusted_host(repository: PythonRepository) -> str | None:
|
||||
parsed = urlparse(repository.index_url)
|
||||
return None if parsed.scheme.lower() == "https" else parsed.hostname
|
||||
|
||||
|
||||
def _write_pip_conf(context: Context, repositories: list[PythonRepository]) -> None:
|
||||
@@ -44,6 +66,12 @@ def _write_pip_conf(context: Context, repositories: list[PythonRepository]) -> N
|
||||
if len(repositories) > 1:
|
||||
lines.append("extra-index-url =")
|
||||
lines.extend(f" {repository.index_url}" for repository in repositories[1:])
|
||||
trusted_hosts = dict.fromkeys(
|
||||
host for repository in repositories if (host := _trusted_host(repository))
|
||||
)
|
||||
if trusted_hosts:
|
||||
lines.append("trusted-host =")
|
||||
lines.extend(f" {host}" for host in trusted_hosts)
|
||||
lines.append(f"cert = {_CA_BUNDLE}")
|
||||
context.write_file(_PIP_CONF, ("\n".join(lines) + "\n").encode("utf-8"))
|
||||
logger.info("configured pip to use %s", repositories[0].index_url)
|
||||
@@ -52,8 +80,9 @@ def _write_pip_conf(context: Context, repositories: list[PythonRepository]) -> N
|
||||
def _write_uv_toml(context: Context, repositories: list[PythonRepository]) -> None:
|
||||
lines = ["native-tls = true", ""]
|
||||
for position, repository in enumerate(repositories):
|
||||
name = sanitize_repository_name(repository.name)
|
||||
lines.append("[[index]]")
|
||||
lines.append(f"name = {_toml_string(repository.name)}")
|
||||
lines.append(f"name = {_toml_string(name)}")
|
||||
lines.append(f"url = {_toml_string(repository.index_url)}")
|
||||
if position == 0:
|
||||
lines.append("default = true")
|
||||
@@ -67,15 +96,20 @@ def _write_poetry_config(
|
||||
) -> None:
|
||||
lines = []
|
||||
for repository in repositories:
|
||||
lines.append(f"[repositories.{repository.name}]")
|
||||
name = sanitize_repository_name(repository.name)
|
||||
lines.append(f"[repositories.{name}]")
|
||||
lines.append(f"url = {_toml_string(repository.index_url)}")
|
||||
lines.append("")
|
||||
lines.append(f"[certificates.{repository.name}]")
|
||||
lines.append(f"[certificates.{name}]")
|
||||
lines.append(f"cert = {_toml_string(_CA_BUNDLE)}")
|
||||
lines.append("")
|
||||
content = ("\n".join(lines).rstrip() + "\n").encode("utf-8")
|
||||
context.write_file(_POETRY_CONFIG, content)
|
||||
for user in context.users:
|
||||
context.write_file(
|
||||
_POETRY_CONFIG, ("\n".join(lines).rstrip() + "\n").encode("utf-8")
|
||||
f"{context.home_dir(user)}/.config/pypoetry/config.toml", content
|
||||
)
|
||||
logger.info("configured poetry for user %s", user)
|
||||
logger.info("registered %d poetry repositories", len(repositories))
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
"""Configure cargo to use the configured RustRepository entries: the first
|
||||
one replaces crates.io entirely (via `[source.crates-io] replace-with`), and
|
||||
any further ones are registered as additional named registries (usable via
|
||||
`cargo add --registry <name>` or a dependency's `registry = "<name>"`), all
|
||||
using the sparse (plain HTTPS) protocol rather than a git-based index.
|
||||
`cargo add --registry <name>` or a dependency's `registry = "<name>"`). A
|
||||
repository's index is declared as `sparse+<url>` - the sparse (plain HTTPS)
|
||||
protocol - only when its `is_sparse` option is set; otherwise `<url>` is used
|
||||
as-is, for a git-based index.
|
||||
|
||||
Cargo's own HTTPS client already trusts whatever the `certificates` module
|
||||
installs into the system CA store, so no separate certificate configuration
|
||||
@@ -17,6 +19,11 @@ directory to `/`, consulting `/.cargo/config.toml` there, *before* falling
|
||||
back to `$CARGO_HOME/config.toml` - so this is the one genuinely global
|
||||
location: found regardless of which user, working directory or `CARGO_HOME`
|
||||
is in effect, unlike a config dropped in one specific user's home.
|
||||
|
||||
For each configured `--user` (`context.users`), the same config is
|
||||
additionally written at that user's own `$HOME/.cargo/config.toml` - the
|
||||
conventional, un-overridden `CARGO_HOME` location for that specific user,
|
||||
which this module otherwise has no visibility into.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -25,29 +32,39 @@ import logging
|
||||
|
||||
from id_configurator.config import RustRepository
|
||||
from id_configurator.context import Context
|
||||
from id_configurator.modules.base import Module, register
|
||||
from id_configurator.modules.base import Module, register, sanitize_repository_name
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_CARGO_CONFIG = ".cargo/config.toml"
|
||||
|
||||
|
||||
def _index_url(repository: RustRepository) -> str:
|
||||
return f"sparse+{repository.url}" if repository.is_sparse else repository.url
|
||||
|
||||
|
||||
def _write_cargo_config(context: Context, repositories: list[RustRepository]) -> None:
|
||||
primary = repositories[0]
|
||||
primary_name = sanitize_repository_name(primary.name)
|
||||
lines = [
|
||||
"[source.crates-io]",
|
||||
f'replace-with = "{primary.name}"',
|
||||
f'replace-with = "{primary_name}"',
|
||||
"",
|
||||
f"[source.{primary.name}]",
|
||||
f'registry = "sparse+{primary.url}"',
|
||||
f"[source.{primary_name}]",
|
||||
f'registry = "{_index_url(primary)}"',
|
||||
]
|
||||
for repository in repositories[1:]:
|
||||
name = sanitize_repository_name(repository.name)
|
||||
lines += [
|
||||
"",
|
||||
f"[registries.{repository.name}]",
|
||||
f'index = "sparse+{repository.url}"',
|
||||
f"[registries.{name}]",
|
||||
f'index = "{_index_url(repository)}"',
|
||||
]
|
||||
context.write_file(_CARGO_CONFIG, ("\n".join(lines) + "\n").encode("utf-8"))
|
||||
content = ("\n".join(lines) + "\n").encode("utf-8")
|
||||
context.write_file(_CARGO_CONFIG, content)
|
||||
for user in context.users:
|
||||
context.write_file(f"{context.home_dir(user)}/.cargo/config.toml", content)
|
||||
logger.info("configured cargo for user %s", user)
|
||||
logger.info("configured cargo to use %s", primary.url)
|
||||
|
||||
|
||||
|
||||
@@ -40,7 +40,12 @@ from io import StringIO
|
||||
|
||||
from id_configurator.config import NamedContent, YumRepository
|
||||
from id_configurator.context import Context
|
||||
from id_configurator.modules.base import Module, register, sanitize_filename
|
||||
from id_configurator.modules.base import (
|
||||
Module,
|
||||
register,
|
||||
sanitize_filename,
|
||||
sanitize_repository_name,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -91,9 +96,10 @@ def _disable_existing_repos(context: Context, patterns: Sequence[str]) -> None:
|
||||
|
||||
|
||||
def _write_repo_file(context: Context, repository: YumRepository) -> None:
|
||||
name = sanitize_repository_name(repository.name)
|
||||
lines = [
|
||||
f"[{repository.name}]",
|
||||
f"name={repository.name}",
|
||||
f"[{name}]",
|
||||
f"name={name}",
|
||||
f"baseurl={repository.index_url}",
|
||||
"enabled=1",
|
||||
]
|
||||
@@ -103,10 +109,10 @@ def _write_repo_file(context: Context, repository: YumRepository) -> None:
|
||||
else:
|
||||
lines.append("gpgcheck=0")
|
||||
context.write_file(
|
||||
f"{_REPOS_DIR}/{sanitize_filename(repository.name)}.repo",
|
||||
f"{_REPOS_DIR}/{name}.repo",
|
||||
("\n".join(lines) + "\n").encode("utf-8"),
|
||||
)
|
||||
logger.info("added yum/dnf repository %s", repository.name)
|
||||
logger.info("added yum/dnf repository %s", name)
|
||||
|
||||
|
||||
def _validate_gpg_key(name: str, content: bytes) -> None:
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
"""Parse `/etc/passwd` entries for the `--auto-users` CLI option.
|
||||
|
||||
A passwd(5) line is `name:passwd:uid:gid:gecos:home:shell`. `--auto-users`
|
||||
expands to every entry that looks like a regular (non-system) account - see
|
||||
`PasswdEntry.is_regular_user`.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
DEFAULT_UID_MIN = 1000
|
||||
|
||||
_DISABLED_SHELL_NAMES = {"nologin", "sync", "false"}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PasswdEntry:
|
||||
name: str
|
||||
uid: int
|
||||
home: str
|
||||
shell: str
|
||||
|
||||
@property
|
||||
def has_login_shell(self) -> bool:
|
||||
return self.shell.rsplit("/", 1)[-1] not in _DISABLED_SHELL_NAMES
|
||||
|
||||
def is_regular_user(self, uid_min: int = DEFAULT_UID_MIN) -> bool:
|
||||
"""True unless `self` looks like a system account.
|
||||
|
||||
A login-disabling shell (`has_login_shell` False) only catches
|
||||
service accounts that follow that convention; it misses others
|
||||
(and could wrongly flag a merely locked human account). The
|
||||
authoritative signal `useradd`/`adduser`/`systemd-sysusers` use
|
||||
instead is the UID: system accounts get one below `uid_min` (the
|
||||
`UID_MIN` from login.defs(5), 1000 by default). Both checks apply
|
||||
together here - except for `root`, whose UID 0 is itself a system
|
||||
convention rather than a sign it shouldn't be configured: it's
|
||||
exempted from the UID check (though not from the shell one, so a
|
||||
deliberately locked root account is still excluded).
|
||||
"""
|
||||
if not self.has_login_shell:
|
||||
return False
|
||||
return self.name == "root" or self.uid >= uid_min
|
||||
|
||||
|
||||
def parse_passwd(content: str) -> list[PasswdEntry]:
|
||||
entries = []
|
||||
for line in content.splitlines():
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
fields = line.split(":")
|
||||
if len(fields) < 7:
|
||||
continue
|
||||
try:
|
||||
uid = int(fields[2])
|
||||
except ValueError:
|
||||
continue
|
||||
entries.append(
|
||||
PasswdEntry(name=fields[0], uid=uid, home=fields[5], shell=fields[6])
|
||||
)
|
||||
return entries
|
||||
|
||||
|
||||
def parse_login_defs_uid_min(content: str) -> int | None:
|
||||
"""Extract `UID_MIN` from `/etc/login.defs` content (login.defs(5)), or
|
||||
`None` if it isn't set there."""
|
||||
for line in content.splitlines():
|
||||
stripped = line.strip()
|
||||
if not stripped or stripped.startswith("#"):
|
||||
continue
|
||||
parts = stripped.split()
|
||||
if len(parts) >= 2 and parts[0] == "UID_MIN":
|
||||
try:
|
||||
return int(parts[1])
|
||||
except ValueError:
|
||||
return None
|
||||
return None
|
||||
+6
-1
@@ -72,7 +72,12 @@ LABEL org.opencontainers.image.version="{id_configurator_version}"
|
||||
LABEL description="Air gap made simple!"
|
||||
COPY --from=interdiode-install --chown=0:0 /chroot/usr/local/bin /usr/local/bin
|
||||
COPY --from=interdiode-install /usr/local/interdiode /usr/local/interdiode
|
||||
RUN DEBIAN_FRONTEND=noninteractive mkdir -p /usr/local/interdiode \
|
||||
RUN apt-get update \
|
||||
&& DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::="--force-confnew" --allow-downgrades --allow-remove-essential --allow-unauthenticated -fuy dist-upgrade \
|
||||
&& DEBIAN_FRONTEND=noninteractive apt-get install --yes -qq --no-install-recommends gpg gpg-agent \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& mkdir -p /usr/local/interdiode \
|
||||
&& chown -R {docker_user}:{docker_group} /usr/local/interdiode
|
||||
COPY ./LICENSE /chroot/licenses/LICENSE
|
||||
COPY ./LICENSE /chroot/usr/share/doc/interdiode/LICENSE
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ maintainers = [
|
||||
name = "id-configurator"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.13"
|
||||
version = "2026.10.2"
|
||||
version = "2026.10.9"
|
||||
|
||||
[project.scripts]
|
||||
id-configurator = "id_configurator:main"
|
||||
|
||||
@@ -10,8 +10,9 @@ configurations:
|
||||
sources: []
|
||||
apk:
|
||||
repositories:
|
||||
- name: demo
|
||||
url: https://host.docker.internal:{{ 'APK_REPO_PORT' | env }}/
|
||||
- name: 'demo _.-/\$.:@;''"'
|
||||
{% set scheme = 'APK_REPO_SCHEME' | env %}
|
||||
url: {{ scheme }}://host.docker.internal:{{ ('APK_REPO_PORT' if scheme == 'https' else 'APK_REPO_HTTP_PORT') | env }}/
|
||||
url_to_replace: https://dl-cdn.alpinelinux.org/*
|
||||
key: |
|
||||
{{ 'dockerfiles/repos/040d0af5f97a0a44.rsa.pub' | file | indent(8, first=true) }}
|
||||
|
||||
@@ -11,7 +11,8 @@ configurations:
|
||||
- name: demo
|
||||
types: [deb]
|
||||
uris:
|
||||
- https://host.docker.internal:{{ 'APT_REPO_PORT' | env }}/
|
||||
{% set scheme = 'APT_REPO_SCHEME' | env %}
|
||||
- {{ scheme }}://host.docker.internal:{{ ('APT_REPO_PORT' if scheme == 'https' else 'APT_REPO_HTTP_PORT') | env }}/
|
||||
suites: ["./"]
|
||||
uri-to-replace: http://deb.debian.org/*
|
||||
signed-by: |
|
||||
|
||||
@@ -25,8 +25,9 @@ configurations:
|
||||
repositories: []
|
||||
go:
|
||||
repositories:
|
||||
- name: demo
|
||||
url: https://host.docker.internal:{{ 'GO_REPO_PORT' | env }}/
|
||||
- name: 'demo _.-/\$.:@;''"'
|
||||
{% set scheme = 'GO_REPO_SCHEME' | env %}
|
||||
url: {{ scheme }}://host.docker.internal:{{ ('GO_REPO_PORT' if scheme == 'https' else 'GO_REPO_HTTP_PORT') | env }}/
|
||||
ruby:
|
||||
repositories: []
|
||||
nuget:
|
||||
|
||||
@@ -23,7 +23,8 @@ configurations:
|
||||
# during the Dockerfile's warm-up step. All other tools just see it as
|
||||
# an ordinary repository, and find the test artifact here.
|
||||
- name: central
|
||||
url: https://host.docker.internal:{{ 'JAVA_REPO_PORT' | env }}/
|
||||
{% set scheme = 'JAVA_REPO_SCHEME' | env %}
|
||||
url: {{ scheme }}://host.docker.internal:{{ ('JAVA_REPO_PORT' if scheme == 'https' else 'JAVA_REPO_HTTP_PORT') | env }}/
|
||||
# Second entry: only SBT needs this. With
|
||||
# -Dsbt.override.build.repos=true (the only way SBT actually consults
|
||||
# this file for project dependencies, not just its own bootstrap), the
|
||||
|
||||
@@ -21,8 +21,9 @@ configurations:
|
||||
repositories: []
|
||||
npm:
|
||||
repositories:
|
||||
- name: demo
|
||||
url: https://host.docker.internal:{{ 'NPM_REPO_PORT' | env }}/
|
||||
- name: 'demo _.-/\$.:@;''"'
|
||||
{% set scheme = 'NPM_REPO_SCHEME' | env %}
|
||||
url: {{ scheme }}://host.docker.internal:{{ ('NPM_REPO_PORT' if scheme == 'https' else 'NPM_REPO_HTTP_PORT') | env }}/
|
||||
rust:
|
||||
repositories: []
|
||||
go:
|
||||
|
||||
@@ -29,5 +29,6 @@ configurations:
|
||||
repositories: []
|
||||
nuget:
|
||||
repositories:
|
||||
- name: demo
|
||||
url: https://host.docker.internal:{{ 'NUGET_REPO_PORT' | env }}/index.json
|
||||
- name: 'demo _.-/\$.:@;''"'
|
||||
{% set scheme = 'NUGET_REPO_SCHEME' | env %}
|
||||
url: {{ scheme }}://host.docker.internal:{{ ('NUGET_REPO_PORT' if scheme == 'https' else 'NUGET_REPO_HTTP_PORT') | env }}/index.json
|
||||
|
||||
@@ -19,8 +19,9 @@ configurations:
|
||||
repositories: []
|
||||
php:
|
||||
repositories:
|
||||
- name: demo
|
||||
url: https://host.docker.internal:{{ 'PHP_REPO_PORT' | env }}/
|
||||
- name: 'demo _.-/\$.:@;''"'
|
||||
{% set scheme = 'PHP_REPO_SCHEME' | env %}
|
||||
url: {{ scheme }}://host.docker.internal:{{ ('PHP_REPO_PORT' if scheme == 'https' else 'PHP_REPO_HTTP_PORT') | env }}/
|
||||
npm:
|
||||
repositories: []
|
||||
rust:
|
||||
|
||||
@@ -15,8 +15,9 @@ configurations:
|
||||
keys: []
|
||||
python:
|
||||
repositories:
|
||||
- name: demo
|
||||
index-url: https://host.docker.internal:{{ 'PYPI_REPO_PORT' | env }}/simple/
|
||||
- name: 'demo _.-/\$.:@;''"'
|
||||
{% set scheme = 'PYPI_REPO_SCHEME' | env %}
|
||||
index-url: {{ scheme }}://host.docker.internal:{{ ('PYPI_REPO_PORT' if scheme == 'https' else 'PYPI_REPO_HTTP_PORT') | env }}/simple/
|
||||
java:
|
||||
repositories: []
|
||||
php:
|
||||
|
||||
@@ -27,7 +27,8 @@ configurations:
|
||||
repositories: []
|
||||
ruby:
|
||||
repositories:
|
||||
- name: demo
|
||||
url: https://host.docker.internal:{{ 'RUBY_REPO_PORT' | env }}/
|
||||
- name: 'demo _.-/\$.:@;''"'
|
||||
{% set scheme = 'RUBY_REPO_SCHEME' | env %}
|
||||
url: {{ scheme }}://host.docker.internal:{{ ('RUBY_REPO_PORT' if scheme == 'https' else 'RUBY_REPO_HTTP_PORT') | env }}/
|
||||
nuget:
|
||||
repositories: []
|
||||
|
||||
@@ -23,8 +23,10 @@ configurations:
|
||||
repositories: []
|
||||
rust:
|
||||
repositories:
|
||||
- name: demo
|
||||
url: https://host.docker.internal:{{ 'CARGO_REPO_PORT' | env }}/
|
||||
- name: 'demo _.-/\$.:@;''"'
|
||||
{% set scheme = 'CARGO_REPO_SCHEME' | env %}
|
||||
url: {{ scheme }}://host.docker.internal:{{ ('CARGO_REPO_PORT' if scheme == 'https' else 'CARGO_REPO_HTTP_PORT') | env }}/
|
||||
is_sparse: true
|
||||
go:
|
||||
repositories: []
|
||||
ruby:
|
||||
|
||||
@@ -12,8 +12,9 @@ configurations:
|
||||
repositories: []
|
||||
yum:
|
||||
repositories:
|
||||
- name: demo
|
||||
index-url: https://host.docker.internal:{{ 'YUM_REPO_PORT' | env }}/
|
||||
- name: 'demo _.-/\$.:@;''"'
|
||||
{% set scheme = 'YUM_REPO_SCHEME' | env %}
|
||||
index-url: {{ scheme }}://host.docker.internal:{{ ('YUM_REPO_PORT' if scheme == 'https' else 'YUM_REPO_HTTP_PORT') | env }}/
|
||||
index-url-to-replace: https://mirrors.rockylinux.org/*
|
||||
keys: []
|
||||
python:
|
||||
|
||||
@@ -4,6 +4,7 @@ COPY --from=target / /src/
|
||||
COPY --from=generated-config Java.yaml /config.yaml
|
||||
RUN id-configurator apply --config /config.yaml --src /src --dst /dst
|
||||
FROM target AS runtime
|
||||
ARG JAVA_REPO_SCHEME
|
||||
# Every build tool the `java` module configures: Maven itself (already part
|
||||
# of this image), Gradle, Ivy and Leiningen (all packaged for this Ubuntu
|
||||
# base), and SBT (no package available, fetched as a plain release tarball).
|
||||
@@ -43,12 +44,23 @@ RUN java -jar /usr/share/java/ivy.jar -settings /root/.ivy2/ivysettings.xml \
|
||||
-retrieve "/tmp/ivyretrieve/[artifact]-[revision].[ext]" \
|
||||
&& test -f /tmp/ivyretrieve/idconfiguratortestpkg-0.0.1.jar
|
||||
|
||||
# Leiningen's standalone launcher unconditionally refuses any plain-HTTP
|
||||
# repository ("Tried to use insecure HTTP repository without TLS") before
|
||||
# even reading project.clj/profiles.clj - there is no `:insecure` key or
|
||||
# other configuration escape hatch for this (confirmed from Leiningen's own
|
||||
# source: leiningen.core.main/-main registers an aborting wagon for the
|
||||
# "http" scheme unconditionally). So this check is skipped for the
|
||||
# module-java-http variant: no id-configurator config could make it pass.
|
||||
RUN mkdir -p /tmp/leintest && cd /tmp/leintest \
|
||||
&& cat > project.clj <<'EOF'
|
||||
(defproject leintest "0.1.0"
|
||||
:dependencies [[org.interdiode/idconfiguratortestpkg "0.0.1"]])
|
||||
EOF
|
||||
RUN cd /tmp/leintest && lein deps :tree | grep -q idconfiguratortestpkg
|
||||
RUN if [ "$JAVA_REPO_SCHEME" = "https" ]; then \
|
||||
cd /tmp/leintest && lein deps :tree | grep -q idconfiguratortestpkg; \
|
||||
else \
|
||||
echo "skipping leiningen: it cannot use a non-HTTPS repository"; \
|
||||
fi
|
||||
|
||||
RUN mkdir -p /tmp/sbttest/project \
|
||||
&& cat > /tmp/sbttest/build.sbt <<'EOF'
|
||||
|
||||
@@ -5,6 +5,7 @@ COPY --from=generated-config Python.yaml /config.yaml
|
||||
RUN id-configurator apply --config /config.yaml --src /src --dst /dst
|
||||
FROM target AS runtime
|
||||
ARG PYPI_REPO_PORT
|
||||
ARG PYPI_REPO_SCHEME
|
||||
# Warm up uv and poetry themselves from the real, network-reachable PyPI
|
||||
# while the default index is still in place, so the actual tests below
|
||||
# never need anything but the fixture repository configured by
|
||||
@@ -20,5 +21,5 @@ RUN uv pip install --system idconfiguratortestpkg \
|
||||
&& python3 -c "import idconfiguratortestpkg"
|
||||
RUN mkdir -p /tmp/poetrytest && cd /tmp/poetrytest \
|
||||
&& poetry init -n --name poetrytest \
|
||||
&& poetry source add --priority explicit demo "https://host.docker.internal:${PYPI_REPO_PORT}/simple/" \
|
||||
&& poetry source add --priority explicit demo "${PYPI_REPO_SCHEME}://host.docker.internal:${PYPI_REPO_PORT}/simple/" \
|
||||
&& poetry add idconfiguratortestpkg --source demo
|
||||
|
||||
@@ -5,6 +5,7 @@ COPY --from=generated-config Python.yaml /config.yaml
|
||||
RUN id-configurator apply --config /config.yaml --src /src --dst /dst
|
||||
FROM target AS runtime
|
||||
ARG PYPI_REPO_PORT
|
||||
ARG PYPI_REPO_SCHEME
|
||||
# Warm up uv and poetry themselves from the real, network-reachable PyPI
|
||||
# while the default index is still in place, so the actual tests below
|
||||
# never need anything but the fixture repository configured by
|
||||
@@ -20,5 +21,5 @@ RUN uv pip install --system idconfiguratortestpkg \
|
||||
&& python3 -c "import idconfiguratortestpkg"
|
||||
RUN mkdir -p /tmp/poetrytest && cd /tmp/poetrytest \
|
||||
&& poetry init -n --name poetrytest \
|
||||
&& poetry source add --priority explicit demo "https://host.docker.internal:${PYPI_REPO_PORT}/simple/" \
|
||||
&& poetry source add --priority explicit demo "${PYPI_REPO_SCHEME}://host.docker.internal:${PYPI_REPO_PORT}/simple/" \
|
||||
&& poetry add idconfiguratortestpkg --source demo
|
||||
|
||||
@@ -8,3 +8,13 @@ server {
|
||||
root /srv;
|
||||
autoindex off;
|
||||
}
|
||||
|
||||
# Plain-HTTP listener, for the module-*-http tox envs that verify id-configurator
|
||||
# correctly configures a non-HTTPS repository.
|
||||
server {
|
||||
listen 8080 default_server;
|
||||
server_name _;
|
||||
|
||||
root /srv;
|
||||
autoindex off;
|
||||
}
|
||||
|
||||
@@ -8,3 +8,13 @@ server {
|
||||
root /srv;
|
||||
autoindex off;
|
||||
}
|
||||
|
||||
# Plain-HTTP listener, for the module-*-http tox envs that verify id-configurator
|
||||
# correctly configures a non-HTTPS repository.
|
||||
server {
|
||||
listen 8080 default_server;
|
||||
server_name _;
|
||||
|
||||
root /srv;
|
||||
autoindex off;
|
||||
}
|
||||
|
||||
@@ -12,10 +12,28 @@ server {
|
||||
# templates, since the host:port this fixture is actually reached on is
|
||||
# only known once tox-docker starts the container, not at image-build
|
||||
# time. $http_host reflects exactly what the client (cargo) used to
|
||||
# connect, e.g. host.docker.internal:55123.
|
||||
# connect, e.g. host.docker.internal:55123; $scheme likewise reflects
|
||||
# whichever of this file's two listeners actually served the request.
|
||||
location = /config.json {
|
||||
default_type application/json;
|
||||
sub_filter '__BASE_URL__' 'https://$http_host';
|
||||
sub_filter '__BASE_URL__' '$scheme://$http_host';
|
||||
sub_filter_once off;
|
||||
sub_filter_types application/json;
|
||||
}
|
||||
}
|
||||
|
||||
# Plain-HTTP listener, for the module-rust-http tox env that verifies
|
||||
# id-configurator correctly configures a non-HTTPS repository.
|
||||
server {
|
||||
listen 8080 default_server;
|
||||
server_name _;
|
||||
|
||||
root /srv;
|
||||
autoindex off;
|
||||
|
||||
location = /config.json {
|
||||
default_type application/json;
|
||||
sub_filter '__BASE_URL__' '$scheme://$http_host';
|
||||
sub_filter_once off;
|
||||
sub_filter_types application/json;
|
||||
}
|
||||
|
||||
@@ -8,3 +8,13 @@ server {
|
||||
root /srv;
|
||||
autoindex off;
|
||||
}
|
||||
|
||||
# Plain-HTTP listener, for the module-*-http tox envs that verify id-configurator
|
||||
# correctly configures a non-HTTPS repository.
|
||||
server {
|
||||
listen 8080 default_server;
|
||||
server_name _;
|
||||
|
||||
root /srv;
|
||||
autoindex off;
|
||||
}
|
||||
|
||||
@@ -8,3 +8,13 @@ server {
|
||||
root /srv;
|
||||
autoindex off;
|
||||
}
|
||||
|
||||
# Plain-HTTP listener, for the module-*-http tox envs that verify id-configurator
|
||||
# correctly configures a non-HTTPS repository.
|
||||
server {
|
||||
listen 8080 default_server;
|
||||
server_name _;
|
||||
|
||||
root /srv;
|
||||
autoindex off;
|
||||
}
|
||||
|
||||
@@ -12,10 +12,29 @@ server {
|
||||
# its tarball URL, since the host:port this fixture is actually reached
|
||||
# on is only known once tox-docker starts the container, not at
|
||||
# image-build time. $http_host reflects exactly what the client (npm)
|
||||
# used to connect, e.g. host.docker.internal:55123.
|
||||
# used to connect, e.g. host.docker.internal:55123; $scheme likewise
|
||||
# reflects whichever of this file's two listeners actually served the
|
||||
# request.
|
||||
location = /idconfiguratortestpkg {
|
||||
default_type application/json;
|
||||
sub_filter '__BASE_URL__' 'https://$http_host';
|
||||
sub_filter '__BASE_URL__' '$scheme://$http_host';
|
||||
sub_filter_once off;
|
||||
sub_filter_types application/json;
|
||||
}
|
||||
}
|
||||
|
||||
# Plain-HTTP listener, for the module-npm-http tox env that verifies
|
||||
# id-configurator correctly configures a non-HTTPS repository.
|
||||
server {
|
||||
listen 8080 default_server;
|
||||
server_name _;
|
||||
|
||||
root /srv;
|
||||
autoindex off;
|
||||
|
||||
location = /idconfiguratortestpkg {
|
||||
default_type application/json;
|
||||
sub_filter '__BASE_URL__' '$scheme://$http_host';
|
||||
sub_filter_once off;
|
||||
sub_filter_types application/json;
|
||||
}
|
||||
|
||||
@@ -12,10 +12,28 @@ server {
|
||||
# fields, since the host:port this fixture is actually reached on is
|
||||
# only known once tox-docker starts the container, not at image-build
|
||||
# time. $http_host reflects exactly what the client (nuget) used to
|
||||
# connect, e.g. host.docker.internal:55123.
|
||||
# connect, e.g. host.docker.internal:55123; $scheme likewise reflects
|
||||
# whichever of this file's two listeners actually served the request.
|
||||
location = /index.json {
|
||||
default_type application/json;
|
||||
sub_filter '__BASE_URL__' 'https://$http_host';
|
||||
sub_filter '__BASE_URL__' '$scheme://$http_host';
|
||||
sub_filter_once off;
|
||||
sub_filter_types application/json;
|
||||
}
|
||||
}
|
||||
|
||||
# Plain-HTTP listener, for the module-nuget-http tox env that verifies
|
||||
# id-configurator correctly configures a non-HTTPS repository.
|
||||
server {
|
||||
listen 8080 default_server;
|
||||
server_name _;
|
||||
|
||||
root /srv;
|
||||
autoindex off;
|
||||
|
||||
location = /index.json {
|
||||
default_type application/json;
|
||||
sub_filter '__BASE_URL__' '$scheme://$http_host';
|
||||
sub_filter_once off;
|
||||
sub_filter_types application/json;
|
||||
}
|
||||
|
||||
@@ -12,10 +12,28 @@ server {
|
||||
# since the host:port this fixture is actually reached on is only known
|
||||
# once tox-docker starts the container, not at image-build time.
|
||||
# $http_host reflects exactly what the client (Composer) used to
|
||||
# connect, e.g. host.docker.internal:55123.
|
||||
# connect, e.g. host.docker.internal:55123; $scheme likewise reflects
|
||||
# whichever of this file's two listeners actually served the request.
|
||||
location = /packages.json {
|
||||
default_type application/json;
|
||||
sub_filter '__BASE_URL__' 'https://$http_host';
|
||||
sub_filter '__BASE_URL__' '$scheme://$http_host';
|
||||
sub_filter_once off;
|
||||
sub_filter_types application/json;
|
||||
}
|
||||
}
|
||||
|
||||
# Plain-HTTP listener, for the module-php-http tox env that verifies
|
||||
# id-configurator correctly configures a non-HTTPS repository.
|
||||
server {
|
||||
listen 8080 default_server;
|
||||
server_name _;
|
||||
|
||||
root /srv;
|
||||
autoindex off;
|
||||
|
||||
location = /packages.json {
|
||||
default_type application/json;
|
||||
sub_filter '__BASE_URL__' '$scheme://$http_host';
|
||||
sub_filter_once off;
|
||||
sub_filter_types application/json;
|
||||
}
|
||||
|
||||
@@ -8,3 +8,13 @@ server {
|
||||
root /srv;
|
||||
autoindex off;
|
||||
}
|
||||
|
||||
# Plain-HTTP listener, for the module-*-http tox envs that verify id-configurator
|
||||
# correctly configures a non-HTTPS repository.
|
||||
server {
|
||||
listen 8080 default_server;
|
||||
server_name _;
|
||||
|
||||
root /srv;
|
||||
autoindex off;
|
||||
}
|
||||
|
||||
@@ -8,3 +8,13 @@ server {
|
||||
root /srv;
|
||||
autoindex off;
|
||||
}
|
||||
|
||||
# Plain-HTTP listener, for the module-*-http tox envs that verify id-configurator
|
||||
# correctly configures a non-HTTPS repository.
|
||||
server {
|
||||
listen 8080 default_server;
|
||||
server_name _;
|
||||
|
||||
root /srv;
|
||||
autoindex off;
|
||||
}
|
||||
|
||||
@@ -8,3 +8,13 @@ server {
|
||||
root /srv;
|
||||
autoindex off;
|
||||
}
|
||||
|
||||
# Plain-HTTP listener, for the module-*-http tox envs that verify id-configurator
|
||||
# correctly configures a non-HTTPS repository.
|
||||
server {
|
||||
listen 8080 default_server;
|
||||
server_name _;
|
||||
|
||||
root /srv;
|
||||
autoindex off;
|
||||
}
|
||||
|
||||
@@ -223,6 +223,63 @@ def test_apply_reuses_cached_existing_urls_and_handles_missing_file(tmp_path) ->
|
||||
assert not (dst / "etc/apk/repositories").exists()
|
||||
|
||||
|
||||
def test_apply_uses_key_filename_when_valid(tmp_path) -> None:
|
||||
data = {
|
||||
"uuid": "12345678-1234-5678-1234-567812345678",
|
||||
"organisation": "InterDiode",
|
||||
"configurations": {
|
||||
"apk": {
|
||||
"repositories": [
|
||||
{
|
||||
"name": "main",
|
||||
"url": "https://example.invalid/v3.19/main",
|
||||
"key": "fake-apk-key-bytes",
|
||||
"key_filename": "main.rsa.pub",
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
}
|
||||
src = tmp_path / "src"
|
||||
(src / "etc/apk").mkdir(parents=True)
|
||||
dst = tmp_path / "dst"
|
||||
context = Context(config=Configuration.model_validate(data), dst=dst, src=src)
|
||||
|
||||
ApkModule().apply(context)
|
||||
|
||||
key_content = (dst / "etc/apk/keys" / "main.rsa.pub").read_bytes()
|
||||
assert key_content == b"fake-apk-key-bytes"
|
||||
|
||||
|
||||
def test_apply_falls_back_to_derived_name_when_key_filename_invalid(tmp_path) -> None:
|
||||
data = {
|
||||
"uuid": "12345678-1234-5678-1234-567812345678",
|
||||
"organisation": "InterDiode",
|
||||
"configurations": {
|
||||
"apk": {
|
||||
"repositories": [
|
||||
{
|
||||
"name": "main",
|
||||
"url": "https://example.invalid/v3.19/main",
|
||||
"key": "fake-apk-key-bytes",
|
||||
"key_filename": "../escape",
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
}
|
||||
src = tmp_path / "src"
|
||||
(src / "etc/apk").mkdir(parents=True)
|
||||
dst = tmp_path / "dst"
|
||||
context = Context(config=Configuration.model_validate(data), dst=dst, src=src)
|
||||
|
||||
ApkModule().apply(context)
|
||||
|
||||
assert not (dst / "etc/apk/keys" / "../escape").exists()
|
||||
key_content = (dst / "etc/apk/keys" / KEY_FILENAME).read_bytes()
|
||||
assert key_content == KEY_BYTES
|
||||
|
||||
|
||||
def test_apply_writes_nothing_when_no_repositories_at_all(tmp_path) -> None:
|
||||
empty_config = Configuration.model_validate(
|
||||
{"uuid": "12345678-1234-5678-1234-567812345678", "organisation": "InterDiode"}
|
||||
|
||||
+1
-1
@@ -361,6 +361,6 @@ def test_apply_sanitizes_source_name_with_slash(tmp_path) -> None:
|
||||
|
||||
AptModule().apply(context)
|
||||
|
||||
source_file = dst / "etc/apt/sources.list.d/.._.._evil.sources"
|
||||
source_file = dst / "etc/apt/sources.list.d/______evil.sources"
|
||||
assert source_file.exists()
|
||||
assert not (dst / "etc/apt/sources.list.d/evil.sources").exists()
|
||||
|
||||
@@ -67,7 +67,7 @@ def test_apply_sanitizes_certificate_name_with_slash(tmp_path) -> None:
|
||||
|
||||
CertificatesModule().apply(context)
|
||||
|
||||
local_cert = dst / "usr/local/share/ca-certificates/.._.._evil.crt"
|
||||
local_cert = dst / "usr/local/share/ca-certificates/______evil.crt"
|
||||
assert local_cert.read_bytes() == b"fake-cert-bytes"
|
||||
# The "/" must never be interpreted as a path separator: without
|
||||
# sanitizing, "../../evil" would have reshuffled the write up to
|
||||
|
||||
@@ -15,6 +15,12 @@ CONFIG_WITH_CERT = {
|
||||
**MINIMAL_CONFIG,
|
||||
"configurations": {"certificates": [{"name": "my-ca", "content": "fake"}]},
|
||||
}
|
||||
CONFIG_WITH_GO_REPO = {
|
||||
**MINIMAL_CONFIG,
|
||||
"configurations": {
|
||||
"go": {"repositories": [{"name": "demo", "url": "https://example.invalid/"}]}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def write_config(tmp_path, data) -> str:
|
||||
@@ -129,6 +135,66 @@ def test_apply_runs_relevant_module(tmp_path) -> None:
|
||||
assert (dst / "etc/ssl/certs/ca-certificates.crt").read_bytes() == b"fake\n"
|
||||
|
||||
|
||||
def test_apply_with_user_option_writes_per_user_config(tmp_path) -> None:
|
||||
config_path = write_config(tmp_path, CONFIG_WITH_GO_REPO)
|
||||
dst = tmp_path / "dst"
|
||||
result = runner.invoke(
|
||||
cli.app,
|
||||
[
|
||||
"apply",
|
||||
"--config",
|
||||
config_path,
|
||||
"--dst",
|
||||
str(dst),
|
||||
"--user",
|
||||
"alice",
|
||||
"--user",
|
||||
"root",
|
||||
],
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert (dst / "home/alice/.config/go/env").read_text(encoding="utf-8") == (
|
||||
"GOPROXY=https://example.invalid/\nGOSUMDB=off\n"
|
||||
)
|
||||
assert (dst / "root/.config/go/env").read_text(encoding="utf-8") == (
|
||||
"GOPROXY=https://example.invalid/\nGOSUMDB=off\n"
|
||||
)
|
||||
|
||||
|
||||
def test_apply_with_auto_users_reads_passwd_from_src(tmp_path) -> None:
|
||||
config_path = write_config(tmp_path, CONFIG_WITH_GO_REPO)
|
||||
src = tmp_path / "src"
|
||||
(src / "etc").mkdir(parents=True)
|
||||
(src / "etc/passwd").write_text(
|
||||
"root:x:0:0:root:/root:/bin/bash\n"
|
||||
"alice:x:1000:1000:Alice:/home/alice:/bin/bash\n"
|
||||
"daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
dst = tmp_path / "dst"
|
||||
result = runner.invoke(
|
||||
cli.app,
|
||||
[
|
||||
"apply",
|
||||
"--config",
|
||||
config_path,
|
||||
"--dst",
|
||||
str(dst),
|
||||
"--src",
|
||||
str(src),
|
||||
"--auto-users",
|
||||
],
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
# root is always included despite its uid 0; daemon (nologin shell) is
|
||||
# a system account, excluded.
|
||||
assert (dst / "root/.config/go/env").exists()
|
||||
assert (dst / "home/alice/.config/go/env").exists()
|
||||
assert not (dst / "usr/sbin/.config/go/env").exists()
|
||||
|
||||
|
||||
def test_main_default_name_calls_app(monkeypatch) -> None:
|
||||
mock_app = MagicMock()
|
||||
monkeypatch.setattr(cli, "app", mock_app)
|
||||
|
||||
+132
-2
@@ -8,14 +8,16 @@ CONFIG = Configuration.model_validate(
|
||||
)
|
||||
|
||||
|
||||
def make_context(tmp_path, with_src: bool = True) -> Context:
|
||||
def make_context(
|
||||
tmp_path, with_src: bool = True, users=None, auto_users: bool = False
|
||||
) -> Context:
|
||||
dst = tmp_path / "dst"
|
||||
dst.mkdir()
|
||||
src = None
|
||||
if with_src:
|
||||
src = tmp_path / "src"
|
||||
src.mkdir()
|
||||
return Context(config=CONFIG, dst=dst, src=src)
|
||||
return Context(config=CONFIG, dst=dst, src=src, users=users, auto_users=auto_users)
|
||||
|
||||
|
||||
def test_dst_path_within_destination(tmp_path) -> None:
|
||||
@@ -97,3 +99,131 @@ def test_glob_src_matches_pattern(tmp_path) -> None:
|
||||
(context.src / "usr/lib/python-legacy").mkdir(parents=True)
|
||||
result = context.glob_src("usr/lib/python3.*")
|
||||
assert [entry.name for entry in result] == ["python3.11", "python3.12"]
|
||||
|
||||
|
||||
def test_users_defaults_to_empty_list(tmp_path) -> None:
|
||||
context = make_context(tmp_path, with_src=False)
|
||||
assert context.users == []
|
||||
|
||||
|
||||
def test_users_keeps_explicit_names_in_order_without_duplicates(tmp_path) -> None:
|
||||
context = make_context(tmp_path, with_src=False, users=["alice", "bob", "alice"])
|
||||
assert context.users == ["alice", "bob"]
|
||||
|
||||
|
||||
def test_auto_users_expands_from_passwd_excluding_disabled_shells(tmp_path) -> None:
|
||||
dst = tmp_path / "dst"
|
||||
dst.mkdir()
|
||||
src = tmp_path / "src"
|
||||
(src / "etc").mkdir(parents=True)
|
||||
(src / "etc/passwd").write_text(
|
||||
"root:x:0:0:root:/root:/bin/bash\n"
|
||||
"alice:x:1000:1000:Alice:/home/alice:/bin/bash\n"
|
||||
"daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
context = Context(config=CONFIG, dst=dst, src=src, auto_users=True)
|
||||
# root is always included despite its uid 0; daemon (nologin shell) is
|
||||
# excluded as a system account.
|
||||
assert context.users == ["root", "alice"]
|
||||
|
||||
|
||||
def test_auto_users_excludes_root_with_disabled_shell(tmp_path) -> None:
|
||||
dst = tmp_path / "dst"
|
||||
dst.mkdir()
|
||||
src = tmp_path / "src"
|
||||
(src / "etc").mkdir(parents=True)
|
||||
(src / "etc/passwd").write_text(
|
||||
"root:x:0:0:root:/root:/usr/sbin/nologin\n"
|
||||
"alice:x:1000:1000:Alice:/home/alice:/bin/bash\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
context = Context(config=CONFIG, dst=dst, src=src, auto_users=True)
|
||||
assert context.users == ["alice"]
|
||||
|
||||
|
||||
def test_auto_users_excludes_uid_below_default_uid_min(tmp_path) -> None:
|
||||
dst = tmp_path / "dst"
|
||||
dst.mkdir()
|
||||
src = tmp_path / "src"
|
||||
(src / "etc").mkdir(parents=True)
|
||||
(src / "etc/passwd").write_text(
|
||||
"svc:x:999:999:service account:/home/svc:/bin/bash\n"
|
||||
"alice:x:1000:1000:Alice:/home/alice:/bin/bash\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
context = Context(config=CONFIG, dst=dst, src=src, auto_users=True)
|
||||
assert context.users == ["alice"]
|
||||
|
||||
|
||||
def test_auto_users_honors_uid_min_from_login_defs(tmp_path) -> None:
|
||||
dst = tmp_path / "dst"
|
||||
dst.mkdir()
|
||||
src = tmp_path / "src"
|
||||
(src / "etc").mkdir(parents=True)
|
||||
(src / "etc/passwd").write_text(
|
||||
"svc:x:999:999:service account:/home/svc:/bin/bash\n"
|
||||
"alice:x:1000:1000:Alice:/home/alice:/bin/bash\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
(src / "etc/login.defs").write_text("UID_MIN\t500\n", encoding="utf-8")
|
||||
context = Context(config=CONFIG, dst=dst, src=src, auto_users=True)
|
||||
assert context.users == ["svc", "alice"]
|
||||
|
||||
|
||||
def test_auto_users_combines_with_explicit_names_without_duplicates(tmp_path) -> None:
|
||||
dst = tmp_path / "dst"
|
||||
dst.mkdir()
|
||||
src = tmp_path / "src"
|
||||
src.mkdir()
|
||||
(src / "etc").mkdir()
|
||||
(src / "etc/passwd").write_text(
|
||||
"root:x:0:0:root:/root:/bin/bash\nalice:x:1000:1000:Alice:/home/alice:/bin/bash\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
context = Context(
|
||||
config=CONFIG, dst=dst, src=src, users=["bob", "alice"], auto_users=True
|
||||
)
|
||||
# explicit names come first, in order; auto-detected names are appended,
|
||||
# skipping "alice" since it's already present.
|
||||
assert context.users == ["bob", "alice", "root"]
|
||||
|
||||
|
||||
def test_auto_users_false_ignores_passwd(tmp_path) -> None:
|
||||
dst = tmp_path / "dst"
|
||||
dst.mkdir()
|
||||
src = tmp_path / "src"
|
||||
(src / "etc").mkdir(parents=True)
|
||||
(src / "etc/passwd").write_text(
|
||||
"alice:x:1000:1000:Alice:/home/alice:/bin/bash\n", encoding="utf-8"
|
||||
)
|
||||
context = Context(config=CONFIG, dst=dst, src=src)
|
||||
assert context.users == []
|
||||
|
||||
|
||||
def test_auto_users_without_src_is_empty(tmp_path) -> None:
|
||||
context = make_context(tmp_path, with_src=False, auto_users=True)
|
||||
assert context.users == []
|
||||
|
||||
|
||||
def test_home_dir_for_root(tmp_path) -> None:
|
||||
context = make_context(tmp_path, with_src=False)
|
||||
assert context.home_dir("root") == "root"
|
||||
|
||||
|
||||
def test_home_dir_falls_back_to_convention(tmp_path) -> None:
|
||||
context = make_context(tmp_path, with_src=False)
|
||||
assert context.home_dir("alice") == "home/alice"
|
||||
|
||||
|
||||
def test_home_dir_uses_passwd_entry_when_available(tmp_path) -> None:
|
||||
dst = tmp_path / "dst"
|
||||
dst.mkdir()
|
||||
src = tmp_path / "src"
|
||||
src.mkdir()
|
||||
(src / "etc").mkdir()
|
||||
(src / "etc/passwd").write_text(
|
||||
"alice:x:1000:1000:Alice:/srv/alice:/bin/bash\n", encoding="utf-8"
|
||||
)
|
||||
context = Context(config=CONFIG, dst=dst, src=src)
|
||||
assert context.home_dir("alice") == "srv/alice"
|
||||
|
||||
@@ -55,6 +55,18 @@ def test_apply_writes_goproxy(tmp_path) -> None:
|
||||
assert go_env == "GOPROXY=https://example.invalid/\nGOSUMDB=off\n"
|
||||
|
||||
|
||||
def test_apply_writes_go_env_for_each_configured_user(tmp_path) -> None:
|
||||
dst = tmp_path / "dst"
|
||||
context = Context(
|
||||
config=make_config(CONFIG_WITH_REPO), dst=dst, src=None, users=["alice"]
|
||||
)
|
||||
|
||||
GoModule().apply(context)
|
||||
|
||||
go_env = (dst / "home/alice/.config/go/env").read_text(encoding="utf-8")
|
||||
assert go_env == "GOPROXY=https://example.invalid/\nGOSUMDB=off\n"
|
||||
|
||||
|
||||
def test_apply_joins_multiple_repositories_with_comma(tmp_path) -> None:
|
||||
dst = tmp_path / "dst"
|
||||
context = Context(config=make_config(CONFIG_WITH_TWO_REPOS), dst=dst, src=None)
|
||||
|
||||
@@ -339,6 +339,31 @@ def test_apply_writes_sbt_repositories(tmp_path) -> None:
|
||||
)
|
||||
|
||||
|
||||
def test_apply_marks_non_https_sbt_repository_as_insecure(tmp_path) -> None:
|
||||
data = {
|
||||
"uuid": "12345678-1234-5678-1234-567812345678",
|
||||
"organisation": "InterDiode",
|
||||
"configurations": {
|
||||
"java": {
|
||||
"repositories": [
|
||||
{"name": "interdiode", "url": "http://example.invalid/maven"}
|
||||
]
|
||||
}
|
||||
},
|
||||
}
|
||||
dst = tmp_path / "dst"
|
||||
context = Context(config=make_config(data), dst=dst, src=None)
|
||||
|
||||
JavaModule().apply(context)
|
||||
|
||||
repositories = (dst / "root/.sbt/repositories").read_text(encoding="utf-8")
|
||||
assert repositories == (
|
||||
"[repositories]\n"
|
||||
" local\n"
|
||||
" interdiode: http://example.invalid/maven, allowInsecureProtocol\n"
|
||||
)
|
||||
|
||||
|
||||
def test_apply_writes_lein_profiles(tmp_path) -> None:
|
||||
dst = tmp_path / "dst"
|
||||
context = Context(config=make_config(TWO_REPOS_CONFIG), dst=dst, src=None)
|
||||
|
||||
@@ -29,8 +29,25 @@ def test_sanitize_filename_leaves_plain_names_untouched() -> None:
|
||||
|
||||
|
||||
def test_sanitize_filename_replaces_forward_slash() -> None:
|
||||
assert sanitize_filename("../../etc/passwd") == ".._.._etc_passwd"
|
||||
assert sanitize_filename("../../etc/passwd") == "______etc_passwd"
|
||||
|
||||
|
||||
def test_sanitize_filename_replaces_backslash() -> None:
|
||||
assert sanitize_filename(r"a\b") == "a_b"
|
||||
|
||||
|
||||
def test_sanitize_filename_replaces_non_identifier_characters() -> None:
|
||||
assert sanitize_filename("My Root CA (v2).pem\n") == "My_Root_CA__v2__pem_"
|
||||
|
||||
|
||||
def test_sanitize_filename_keeps_letters_digits_underscore_and_dash() -> None:
|
||||
assert sanitize_filename("Inter_Diode-2026") == "Inter_Diode-2026"
|
||||
|
||||
|
||||
def test_sanitize_filename_never_returns_dot_names() -> None:
|
||||
assert sanitize_filename(".") == "_"
|
||||
assert sanitize_filename("..") == "__"
|
||||
|
||||
|
||||
def test_sanitize_filename_never_returns_empty_string() -> None:
|
||||
assert sanitize_filename("") == "_"
|
||||
|
||||
@@ -63,6 +63,48 @@ def test_apply_writes_nuget_config(tmp_path) -> None:
|
||||
)
|
||||
|
||||
|
||||
def test_apply_marks_non_https_source_as_insecure(tmp_path) -> None:
|
||||
data = {
|
||||
"uuid": "12345678-1234-5678-1234-567812345678",
|
||||
"organisation": "InterDiode",
|
||||
"configurations": {
|
||||
"nuget": {
|
||||
"repositories": [{"name": "demo", "url": "http://example.invalid/"}]
|
||||
}
|
||||
},
|
||||
}
|
||||
dst = tmp_path / "dst"
|
||||
context = Context(config=make_config(data), dst=dst, src=None)
|
||||
|
||||
NuGetModule().apply(context)
|
||||
|
||||
content = (dst / "root/.nuget/NuGet/NuGet.Config").read_text(encoding="utf-8")
|
||||
assert content == (
|
||||
'<?xml version="1.0" encoding="utf-8"?>\n'
|
||||
"<configuration>\n"
|
||||
" <packageSources>\n"
|
||||
" <clear />\n"
|
||||
' <add key="demo" value="http://example.invalid/" allowInsecureConnections="true" />\n'
|
||||
" </packageSources>\n"
|
||||
"</configuration>\n"
|
||||
)
|
||||
|
||||
|
||||
def test_apply_writes_config_for_each_configured_user(tmp_path) -> None:
|
||||
dst = tmp_path / "dst"
|
||||
context = Context(
|
||||
config=make_config(CONFIG_WITH_REPO), dst=dst, src=None, users=["alice", "root"]
|
||||
)
|
||||
|
||||
NuGetModule().apply(context)
|
||||
|
||||
root_content = (dst / "root/.nuget/NuGet/NuGet.Config").read_text(encoding="utf-8")
|
||||
alice_content = (dst / "home/alice/.nuget/NuGet/NuGet.Config").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
assert alice_content == root_content
|
||||
|
||||
|
||||
def test_apply_writes_multiple_sources_in_order(tmp_path) -> None:
|
||||
dst = tmp_path / "dst"
|
||||
context = Context(config=make_config(CONFIG_WITH_TWO_REPOS), dst=dst, src=None)
|
||||
|
||||
+66
-2
@@ -80,7 +80,8 @@ def test_apply_writes_global_composer_config(tmp_path) -> None:
|
||||
"name": "demo",
|
||||
"type": "composer",
|
||||
"url": "https://example.invalid/composer",
|
||||
}
|
||||
},
|
||||
{"packagist.org": False},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -93,7 +94,70 @@ def test_apply_writes_multiple_repositories_in_order(tmp_path) -> None:
|
||||
|
||||
config_path = dst / "root/.composer/config.json"
|
||||
content = json.loads(config_path.read_text(encoding="utf-8"))
|
||||
assert [repo["name"] for repo in content["repositories"]] == ["demo", "other"]
|
||||
names = [repo["name"] for repo in content["repositories"] if "name" in repo]
|
||||
assert names == ["demo", "other"]
|
||||
assert {"packagist.org": False} in content["repositories"]
|
||||
|
||||
|
||||
def test_apply_disables_secure_http_and_packagist_when_repo_not_https(
|
||||
tmp_path,
|
||||
) -> None:
|
||||
data = {
|
||||
"uuid": "12345678-1234-5678-1234-567812345678",
|
||||
"organisation": "InterDiode",
|
||||
"configurations": {
|
||||
"php": {
|
||||
"repositories": [
|
||||
{"name": "demo", "url": "https://example.invalid/composer"},
|
||||
{"name": "insecure", "url": "http://other.invalid/composer"},
|
||||
]
|
||||
}
|
||||
},
|
||||
}
|
||||
dst = tmp_path / "dst"
|
||||
context = Context(config=make_config(data), dst=dst, src=None)
|
||||
|
||||
PhpModule().apply(context)
|
||||
|
||||
config_path = dst / "root/.composer/config.json"
|
||||
content = json.loads(config_path.read_text(encoding="utf-8"))
|
||||
assert content["config"] == {"secure-http": False}
|
||||
assert {"packagist.org": False} in content["repositories"]
|
||||
|
||||
|
||||
def test_apply_does_not_disable_secure_http_when_all_repos_https(tmp_path) -> None:
|
||||
dst = tmp_path / "dst"
|
||||
context = Context(config=make_config(CONFIG_WITH_REPO), dst=dst, src=None)
|
||||
|
||||
PhpModule().apply(context)
|
||||
|
||||
config_path = dst / "root/.composer/config.json"
|
||||
content = json.loads(config_path.read_text(encoding="utf-8"))
|
||||
assert "config" not in content
|
||||
|
||||
|
||||
def test_apply_always_disables_packagist(tmp_path) -> None:
|
||||
dst = tmp_path / "dst"
|
||||
context = Context(config=make_config(CONFIG_WITH_REPO), dst=dst, src=None)
|
||||
|
||||
PhpModule().apply(context)
|
||||
|
||||
config_path = dst / "root/.composer/config.json"
|
||||
content = json.loads(config_path.read_text(encoding="utf-8"))
|
||||
assert {"packagist.org": False} in content["repositories"]
|
||||
|
||||
|
||||
def test_apply_writes_composer_config_for_each_configured_user(tmp_path) -> None:
|
||||
dst = tmp_path / "dst"
|
||||
context = Context(
|
||||
config=make_config(CONFIG_WITH_REPO), dst=dst, src=None, users=["alice"]
|
||||
)
|
||||
|
||||
PhpModule().apply(context)
|
||||
|
||||
root_content = (dst / "root/.composer/config.json").read_bytes()
|
||||
alice_content = (dst / "home/alice/.composer/config.json").read_bytes()
|
||||
assert alice_content == root_content
|
||||
|
||||
|
||||
def test_apply_without_certificates_does_not_write_openssl_ini(tmp_path) -> None:
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import tomllib
|
||||
|
||||
from id_configurator.config import Configuration
|
||||
from id_configurator.context import Context
|
||||
from id_configurator.modules.python import PythonModule
|
||||
@@ -76,6 +78,111 @@ def test_apply_single_repository(tmp_path) -> None:
|
||||
assert not (dst / "usr/lib").exists()
|
||||
|
||||
|
||||
def test_apply_sanitizes_uv_index_name_with_non_bmp_characters(tmp_path) -> None:
|
||||
data = {
|
||||
"uuid": "12345678-1234-5678-1234-567812345678",
|
||||
"organisation": "InterDiode",
|
||||
"configurations": {
|
||||
"python": {
|
||||
"repositories": [
|
||||
{"name": "demo 😀", "index-url": "https://example.invalid/simple"}
|
||||
]
|
||||
}
|
||||
},
|
||||
}
|
||||
config = Configuration.model_validate(data)
|
||||
dst = tmp_path / "dst"
|
||||
context = Context(config=config, dst=dst, src=None)
|
||||
|
||||
PythonModule().apply(context)
|
||||
|
||||
uv_toml = (dst / "etc/uv/uv.toml").read_text(encoding="utf-8")
|
||||
# The emoji isn't a plain identifier character, so sanitize_repository_name
|
||||
# replaces it (and the space before it) with "_", same as every other
|
||||
# non-ASCII or syntax-breaking character.
|
||||
assert '"demo__"' in uv_toml
|
||||
parsed = tomllib.loads(uv_toml)
|
||||
assert parsed["index"][0]["name"] == "demo__"
|
||||
|
||||
|
||||
def test_apply_adds_trusted_host_for_non_https_repository(tmp_path) -> None:
|
||||
data = {
|
||||
"uuid": "12345678-1234-5678-1234-567812345678",
|
||||
"organisation": "InterDiode",
|
||||
"configurations": {
|
||||
"python": {
|
||||
"repositories": [
|
||||
{"name": "interdiode", "index-url": "http://example.invalid/simple"}
|
||||
]
|
||||
}
|
||||
},
|
||||
}
|
||||
config = Configuration.model_validate(data)
|
||||
dst = tmp_path / "dst"
|
||||
context = Context(config=config, dst=dst, src=None)
|
||||
|
||||
PythonModule().apply(context)
|
||||
|
||||
pip_conf = (dst / "etc/pip.conf").read_text(encoding="utf-8")
|
||||
assert pip_conf == (
|
||||
"[global]\n"
|
||||
"index-url = http://example.invalid/simple\n"
|
||||
"trusted-host =\n"
|
||||
" example.invalid\n"
|
||||
"cert = /etc/ssl/certs/ca-certificates.crt\n"
|
||||
)
|
||||
|
||||
|
||||
def test_apply_adds_trusted_host_only_for_non_https_among_mixed_repositories(
|
||||
tmp_path,
|
||||
) -> None:
|
||||
data = {
|
||||
"uuid": "12345678-1234-5678-1234-567812345678",
|
||||
"organisation": "InterDiode",
|
||||
"configurations": {
|
||||
"python": {
|
||||
"repositories": [
|
||||
{
|
||||
"name": "interdiode",
|
||||
"index-url": "https://example.invalid/simple",
|
||||
},
|
||||
{
|
||||
"name": "insecure",
|
||||
"index-url": "http://insecure.invalid/simple",
|
||||
},
|
||||
]
|
||||
}
|
||||
},
|
||||
}
|
||||
config = Configuration.model_validate(data)
|
||||
dst = tmp_path / "dst"
|
||||
context = Context(config=config, dst=dst, src=None)
|
||||
|
||||
PythonModule().apply(context)
|
||||
|
||||
pip_conf = (dst / "etc/pip.conf").read_text(encoding="utf-8")
|
||||
assert pip_conf == (
|
||||
"[global]\n"
|
||||
"index-url = https://example.invalid/simple\n"
|
||||
"extra-index-url =\n"
|
||||
" http://insecure.invalid/simple\n"
|
||||
"trusted-host =\n"
|
||||
" insecure.invalid\n"
|
||||
"cert = /etc/ssl/certs/ca-certificates.crt\n"
|
||||
)
|
||||
|
||||
|
||||
def test_apply_does_not_add_trusted_host_when_all_repositories_https(tmp_path) -> None:
|
||||
config = Configuration.model_validate(ONE_REPO_CONFIG)
|
||||
dst = tmp_path / "dst"
|
||||
context = Context(config=config, dst=dst, src=None)
|
||||
|
||||
PythonModule().apply(context)
|
||||
|
||||
pip_conf = (dst / "etc/pip.conf").read_text(encoding="utf-8")
|
||||
assert "trusted-host" not in pip_conf
|
||||
|
||||
|
||||
def test_apply_multiple_repositories(tmp_path) -> None:
|
||||
config = Configuration.model_validate(TWO_REPOS_CONFIG)
|
||||
dst = tmp_path / "dst"
|
||||
@@ -105,6 +212,57 @@ def test_apply_multiple_repositories(tmp_path) -> None:
|
||||
)
|
||||
|
||||
|
||||
def test_apply_sanitizes_poetry_repository_name_with_special_characters(
|
||||
tmp_path,
|
||||
) -> None:
|
||||
data = {
|
||||
"uuid": "12345678-1234-5678-1234-567812345678",
|
||||
"organisation": "InterDiode",
|
||||
"configurations": {
|
||||
"python": {
|
||||
"repositories": [
|
||||
{
|
||||
"name": "weird.name]here[bad",
|
||||
"index-url": "https://example.invalid/simple",
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
}
|
||||
config = Configuration.model_validate(data)
|
||||
dst = tmp_path / "dst"
|
||||
context = Context(config=config, dst=dst, src=None)
|
||||
|
||||
PythonModule().apply(context)
|
||||
|
||||
poetry_config = (dst / "root/.config/pypoetry/config.toml").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
assert poetry_config == (
|
||||
"[repositories.weird_name_here_bad]\n"
|
||||
'url = "https://example.invalid/simple"\n'
|
||||
"\n"
|
||||
"[certificates.weird_name_here_bad]\n"
|
||||
'cert = "/etc/ssl/certs/ca-certificates.crt"\n'
|
||||
)
|
||||
|
||||
|
||||
def test_apply_writes_poetry_config_for_each_configured_user(tmp_path) -> None:
|
||||
config = Configuration.model_validate(ONE_REPO_CONFIG)
|
||||
dst = tmp_path / "dst"
|
||||
context = Context(config=config, dst=dst, src=None, users=["alice"])
|
||||
|
||||
PythonModule().apply(context)
|
||||
|
||||
root_content = (dst / "root/.config/pypoetry/config.toml").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
alice_content = (dst / "home/alice/.config/pypoetry/config.toml").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
assert alice_content == root_content
|
||||
|
||||
|
||||
def test_apply_writes_distutils_cfg_for_detected_python_prefixes(tmp_path) -> None:
|
||||
config = Configuration.model_validate(ONE_REPO_CONFIG)
|
||||
src = tmp_path / "src"
|
||||
|
||||
+104
-3
@@ -6,7 +6,15 @@ CONFIG_WITH_REPO = {
|
||||
"uuid": "12345678-1234-5678-1234-567812345678",
|
||||
"organisation": "InterDiode",
|
||||
"configurations": {
|
||||
"rust": {"repositories": [{"name": "demo", "url": "https://example.invalid/"}]}
|
||||
"rust": {
|
||||
"repositories": [
|
||||
{
|
||||
"name": "demo",
|
||||
"url": "https://example.invalid/",
|
||||
"is_sparse": True,
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -16,8 +24,16 @@ CONFIG_WITH_TWO_REPOS = {
|
||||
"configurations": {
|
||||
"rust": {
|
||||
"repositories": [
|
||||
{"name": "demo", "url": "https://example.invalid/"},
|
||||
{"name": "other", "url": "https://other.invalid/"},
|
||||
{
|
||||
"name": "demo",
|
||||
"url": "https://example.invalid/",
|
||||
"is_sparse": True,
|
||||
},
|
||||
{
|
||||
"name": "other",
|
||||
"url": "https://other.invalid/",
|
||||
"is_sparse": True,
|
||||
},
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -61,6 +77,91 @@ def test_apply_writes_replacement_source(tmp_path) -> None:
|
||||
)
|
||||
|
||||
|
||||
def test_apply_sanitizes_repository_names_with_special_characters(tmp_path) -> None:
|
||||
data = {
|
||||
"uuid": "12345678-1234-5678-1234-567812345678",
|
||||
"organisation": "InterDiode",
|
||||
"configurations": {
|
||||
"rust": {
|
||||
"repositories": [
|
||||
{
|
||||
"name": "weird.demo]\nname",
|
||||
"url": "https://example.invalid/",
|
||||
"is_sparse": True,
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
}
|
||||
dst = tmp_path / "dst"
|
||||
context = Context(config=make_config(data), dst=dst, src=None)
|
||||
|
||||
RustModule().apply(context)
|
||||
|
||||
config_toml = (dst / ".cargo/config.toml").read_text(encoding="utf-8")
|
||||
assert config_toml == (
|
||||
"[source.crates-io]\n"
|
||||
'replace-with = "weird_demo__name"\n'
|
||||
"\n"
|
||||
"[source.weird_demo__name]\n"
|
||||
'registry = "sparse+https://example.invalid/"\n'
|
||||
)
|
||||
|
||||
|
||||
def test_apply_does_not_prefix_url_when_not_sparse(tmp_path) -> None:
|
||||
data = {
|
||||
"uuid": "12345678-1234-5678-1234-567812345678",
|
||||
"organisation": "InterDiode",
|
||||
"configurations": {
|
||||
"rust": {
|
||||
"repositories": [{"name": "demo", "url": "https://example.invalid/"}]
|
||||
}
|
||||
},
|
||||
}
|
||||
dst = tmp_path / "dst"
|
||||
context = Context(config=make_config(data), dst=dst, src=None)
|
||||
|
||||
RustModule().apply(context)
|
||||
|
||||
config_toml = (dst / ".cargo/config.toml").read_text(encoding="utf-8")
|
||||
assert config_toml == (
|
||||
"[source.crates-io]\n"
|
||||
'replace-with = "demo"\n'
|
||||
"\n"
|
||||
"[source.demo]\n"
|
||||
'registry = "https://example.invalid/"\n'
|
||||
)
|
||||
|
||||
|
||||
def test_apply_prefixes_url_with_sparse_when_configured(tmp_path) -> None:
|
||||
dst = tmp_path / "dst"
|
||||
context = Context(config=make_config(CONFIG_WITH_REPO), dst=dst, src=None)
|
||||
|
||||
RustModule().apply(context)
|
||||
|
||||
config_toml = (dst / ".cargo/config.toml").read_text(encoding="utf-8")
|
||||
assert config_toml == (
|
||||
"[source.crates-io]\n"
|
||||
'replace-with = "demo"\n'
|
||||
"\n"
|
||||
"[source.demo]\n"
|
||||
'registry = "sparse+https://example.invalid/"\n'
|
||||
)
|
||||
|
||||
|
||||
def test_apply_writes_cargo_config_for_each_configured_user(tmp_path) -> None:
|
||||
dst = tmp_path / "dst"
|
||||
context = Context(
|
||||
config=make_config(CONFIG_WITH_REPO), dst=dst, src=None, users=["alice"]
|
||||
)
|
||||
|
||||
RustModule().apply(context)
|
||||
|
||||
root_content = (dst / ".cargo/config.toml").read_text(encoding="utf-8")
|
||||
alice_content = (dst / "home/alice/.cargo/config.toml").read_text(encoding="utf-8")
|
||||
assert alice_content == root_content
|
||||
|
||||
|
||||
def test_apply_writes_additional_repositories_as_named_registries(tmp_path) -> None:
|
||||
dst = tmp_path / "dst"
|
||||
context = Context(config=make_config(CONFIG_WITH_TWO_REPOS), dst=dst, src=None)
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
from id_configurator.users import PasswdEntry, parse_login_defs_uid_min, parse_passwd
|
||||
|
||||
PASSWD_CONTENT = (
|
||||
"root:x:0:0:root:/root:/bin/bash\n"
|
||||
"alice:x:1000:1000:Alice:/home/alice:/bin/bash\n"
|
||||
"daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\n"
|
||||
"sync:x:4:65534:sync:/bin:/bin/sync\n"
|
||||
"nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/false\n"
|
||||
"# a comment\n"
|
||||
"\n"
|
||||
)
|
||||
|
||||
|
||||
def test_parse_passwd_returns_one_entry_per_line() -> None:
|
||||
entries = parse_passwd(PASSWD_CONTENT)
|
||||
assert [entry.name for entry in entries] == [
|
||||
"root",
|
||||
"alice",
|
||||
"daemon",
|
||||
"sync",
|
||||
"nobody",
|
||||
]
|
||||
|
||||
|
||||
def test_parse_passwd_skips_comments_and_blank_lines() -> None:
|
||||
entries = parse_passwd(PASSWD_CONTENT)
|
||||
assert len(entries) == 5
|
||||
|
||||
|
||||
def test_parse_passwd_extracts_uid_home_and_shell() -> None:
|
||||
entries = {entry.name: entry for entry in parse_passwd(PASSWD_CONTENT)}
|
||||
assert entries["alice"].uid == 1000
|
||||
assert entries["alice"].home == "/home/alice"
|
||||
assert entries["alice"].shell == "/bin/bash"
|
||||
|
||||
|
||||
def test_parse_passwd_skips_malformed_lines() -> None:
|
||||
entries = parse_passwd(
|
||||
"tooshort:x:0\nnotanumber:x:abc:0:x:/home/x:/bin/bash\n"
|
||||
"root:x:0:0:root:/root:/bin/bash\n"
|
||||
)
|
||||
assert [entry.name for entry in entries] == ["root"]
|
||||
|
||||
|
||||
def test_has_login_shell_true_for_regular_shell() -> None:
|
||||
entry = PasswdEntry(name="alice", uid=1000, home="/home/alice", shell="/bin/bash")
|
||||
assert entry.has_login_shell is True
|
||||
|
||||
|
||||
def test_has_login_shell_false_for_nologin_sync_or_false() -> None:
|
||||
nologin = PasswdEntry(
|
||||
name="daemon", uid=1, home="/usr/sbin", shell="/usr/sbin/nologin"
|
||||
)
|
||||
sync = PasswdEntry(name="sync", uid=4, home="/bin", shell="/bin/sync")
|
||||
false_shell = PasswdEntry(
|
||||
name="nobody", uid=65534, home="/nonexistent", shell="/usr/sbin/false"
|
||||
)
|
||||
assert nologin.has_login_shell is False
|
||||
assert sync.has_login_shell is False
|
||||
assert false_shell.has_login_shell is False
|
||||
|
||||
|
||||
def test_is_regular_user_true_for_human_account() -> None:
|
||||
entry = PasswdEntry(name="alice", uid=1000, home="/home/alice", shell="/bin/bash")
|
||||
assert entry.is_regular_user() is True
|
||||
|
||||
|
||||
def test_is_regular_user_true_for_root_despite_uid_zero() -> None:
|
||||
entry = PasswdEntry(name="root", uid=0, home="/root", shell="/bin/bash")
|
||||
assert entry.is_regular_user() is True
|
||||
|
||||
|
||||
def test_is_regular_user_false_for_root_with_disabled_shell() -> None:
|
||||
entry = PasswdEntry(name="root", uid=0, home="/root", shell="/usr/sbin/nologin")
|
||||
assert entry.is_regular_user() is False
|
||||
|
||||
|
||||
def test_is_regular_user_false_for_disabled_shell_despite_high_uid() -> None:
|
||||
entry = PasswdEntry(
|
||||
name="nobody", uid=65534, home="/nonexistent", shell="/usr/sbin/false"
|
||||
)
|
||||
assert entry.is_regular_user() is False
|
||||
|
||||
|
||||
def test_is_regular_user_honors_custom_uid_min() -> None:
|
||||
entry = PasswdEntry(name="svc", uid=500, home="/home/svc", shell="/bin/bash")
|
||||
assert entry.is_regular_user(uid_min=1000) is False
|
||||
assert entry.is_regular_user(uid_min=100) is True
|
||||
|
||||
|
||||
def test_parse_login_defs_uid_min_found() -> None:
|
||||
content = "# comment\nUID_MIN\t500\nGID_MIN\t500\n"
|
||||
assert parse_login_defs_uid_min(content) == 500
|
||||
|
||||
|
||||
def test_parse_login_defs_uid_min_missing() -> None:
|
||||
assert parse_login_defs_uid_min("GID_MIN\t500\n") is None
|
||||
|
||||
|
||||
def test_parse_login_defs_uid_min_malformed_value() -> None:
|
||||
assert parse_login_defs_uid_min("UID_MIN\tnot-a-number\n") is None
|
||||
+31
-10
@@ -313,10 +313,26 @@ def test_apply_matches_index_url_to_replace_with_unrelated_files_and_multiple_re
|
||||
assert (dst / "etc/yum.repos.d/second.repo").exists()
|
||||
|
||||
|
||||
def test_apply_sanitizes_repository_and_key_names_with_slash(
|
||||
tmp_path, monkeypatch
|
||||
) -> None:
|
||||
def test_apply_sanitizes_key_name_with_slash(tmp_path, monkeypatch) -> None:
|
||||
mock_run = _mock_gpg_success(monkeypatch)
|
||||
data = {
|
||||
"uuid": "12345678-1234-5678-1234-567812345678",
|
||||
"organisation": "InterDiode",
|
||||
"configurations": {
|
||||
"yum": {"keys": [{"name": "../../evil-key", "content": KEY_CONTENT}]}
|
||||
},
|
||||
}
|
||||
dst = tmp_path / "dst"
|
||||
context = Context(config=make_config(data), dst=dst, src=None)
|
||||
|
||||
YumModule().apply(context)
|
||||
|
||||
key_content = (dst / "etc/pki/rpm-gpg/RPM-GPG-KEY-______evil-key").read_bytes()
|
||||
assert key_content == b"fake-rpm-gpg-key-bytes"
|
||||
mock_run.assert_called_once()
|
||||
|
||||
|
||||
def test_apply_sanitizes_repository_name_with_disallowed_characters(tmp_path) -> None:
|
||||
data = {
|
||||
"uuid": "12345678-1234-5678-1234-567812345678",
|
||||
"organisation": "InterDiode",
|
||||
@@ -324,11 +340,10 @@ def test_apply_sanitizes_repository_and_key_names_with_slash(
|
||||
"yum": {
|
||||
"repositories": [
|
||||
{
|
||||
"name": "../../evil-repo",
|
||||
"name": "weird/repo]here[bad",
|
||||
"index-url": "https://example.invalid/yum",
|
||||
}
|
||||
],
|
||||
"keys": [{"name": "../../evil-key", "content": KEY_CONTENT}],
|
||||
]
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -337,10 +352,16 @@ def test_apply_sanitizes_repository_and_key_names_with_slash(
|
||||
|
||||
YumModule().apply(context)
|
||||
|
||||
assert (dst / "etc/yum.repos.d/.._.._evil-repo.repo").exists()
|
||||
key_content = (dst / "etc/pki/rpm-gpg/RPM-GPG-KEY-.._.._evil-key").read_bytes()
|
||||
assert key_content == b"fake-rpm-gpg-key-bytes"
|
||||
mock_run.assert_called_once()
|
||||
repo_file = dst / "etc/yum.repos.d/weird_repo_here_bad.repo"
|
||||
assert repo_file.exists()
|
||||
repo_content = repo_file.read_text(encoding="utf-8")
|
||||
assert repo_content == (
|
||||
"[weird_repo_here_bad]\n"
|
||||
"name=weird_repo_here_bad\n"
|
||||
"baseurl=https://example.invalid/yum\n"
|
||||
"enabled=1\n"
|
||||
"gpgcheck=0\n"
|
||||
)
|
||||
|
||||
|
||||
def test_import_key_raises_when_gpg_binary_missing(tmp_path, monkeypatch) -> None:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[tox]
|
||||
env_list = py313,py314,coverage,packaging,packaging-amd64,packaging-arm64,publish,module-python,module-apt,module-yum,module-apk,module-java,module-certificates,module-php,module-npm,module-rust,module-go,module-ruby,module-nuget,config-url
|
||||
env_list = py313,py314,coverage,packaging,packaging-amd64,packaging-arm64,publish,module-python,module-python-http,module-apt,module-apt-http,module-yum,module-yum-http,module-apk,module-apk-http,module-java,module-java-http,module-certificates,module-php,module-php-http,module-npm,module-npm-http,module-rust,module-rust-http,module-go,module-go-http,module-ruby,module-ruby-http,module-nuget,module-nuget-http,config-url
|
||||
isolated_build = true
|
||||
requires =
|
||||
tox>=4
|
||||
@@ -91,6 +91,7 @@ passenv =
|
||||
dockerfile = {tox_root}/tests/dockerfiles/repos/pypi-repo.Dockerfile
|
||||
expose =
|
||||
PYPI_REPO_PORT=8443/tcp
|
||||
PYPI_REPO_HTTP_PORT=8080/tcp
|
||||
healthcheck_cmd = wget --no-check-certificate -qO- https://127.0.0.1:8443/simple/idconfiguratortestpkg/ || exit 1
|
||||
healthcheck_interval = 1
|
||||
healthcheck_retries = 30
|
||||
@@ -106,17 +107,45 @@ docker =
|
||||
pypi-repo
|
||||
allowlist_externals =
|
||||
docker
|
||||
set_env =
|
||||
PYPI_REPO_SCHEME = https
|
||||
commands =
|
||||
python3 -m id_tools test render-config {tox_root}/tests/configs/Python.yaml.j2 {env_dir}/configs/Python.yaml
|
||||
docker buildx build --platform linux/amd64 --add-host=host.docker.internal:host-gateway --build-arg PYPI_REPO_PORT={env:PYPI_REPO_PORT} --build-context generated-config={env_dir}/configs -f {tox_root}/tests/dockerfiles/Python-alpine.Dockerfile -t id-configurator-python .
|
||||
docker buildx build --platform linux/amd64 --add-host=host.docker.internal:host-gateway --build-arg PYPI_REPO_SCHEME={env:PYPI_REPO_SCHEME} --build-arg PYPI_REPO_PORT={env:PYPI_REPO_PORT} --build-context generated-config={env_dir}/configs -f {tox_root}/tests/dockerfiles/Python-alpine.Dockerfile -t id-configurator-python .
|
||||
docker rmi id-configurator-python
|
||||
docker buildx build --platform linux/amd64 --add-host=host.docker.internal:host-gateway --build-arg PYPI_REPO_PORT={env:PYPI_REPO_PORT} --build-context generated-config={env_dir}/configs -f {tox_root}/tests/dockerfiles/Python.Dockerfile -t id-configurator-python .
|
||||
docker buildx build --platform linux/amd64 --add-host=host.docker.internal:host-gateway --build-arg PYPI_REPO_SCHEME={env:PYPI_REPO_SCHEME} --build-arg PYPI_REPO_PORT={env:PYPI_REPO_PORT} --build-context generated-config={env_dir}/configs -f {tox_root}/tests/dockerfiles/Python.Dockerfile -t id-configurator-python .
|
||||
docker rmi id-configurator-python
|
||||
commands_post =
|
||||
docker buildx prune --all --force
|
||||
|
||||
[testenv:module-python-http]
|
||||
package = skip
|
||||
labels = end-to-end
|
||||
change_dir = {tox_root}
|
||||
dependency_groups = dev
|
||||
docker =
|
||||
pypi-repo
|
||||
allowlist_externals =
|
||||
docker
|
||||
description =
|
||||
Same as module-python, but against a plain-HTTP index, to verify pip's
|
||||
trusted-host is configured correctly for a non-HTTPS repository.
|
||||
set_env =
|
||||
PYPI_REPO_SCHEME = http
|
||||
commands =
|
||||
python3 -m id_tools test render-config {tox_root}/tests/configs/Python.yaml.j2 {env_dir}/configs/Python.yaml
|
||||
docker buildx build --platform linux/amd64 --add-host=host.docker.internal:host-gateway --build-arg PYPI_REPO_SCHEME={env:PYPI_REPO_SCHEME} --build-arg PYPI_REPO_PORT={env:PYPI_REPO_HTTP_PORT} --build-context generated-config={env_dir}/configs -f {tox_root}/tests/dockerfiles/Python-alpine.Dockerfile -t id-configurator-python-http .
|
||||
docker rmi id-configurator-python-http
|
||||
docker buildx build --platform linux/amd64 --add-host=host.docker.internal:host-gateway --build-arg PYPI_REPO_SCHEME={env:PYPI_REPO_SCHEME} --build-arg PYPI_REPO_PORT={env:PYPI_REPO_HTTP_PORT} --build-context generated-config={env_dir}/configs -f {tox_root}/tests/dockerfiles/Python.Dockerfile -t id-configurator-python-http .
|
||||
docker rmi id-configurator-python-http
|
||||
commands_post =
|
||||
docker buildx prune --all --force
|
||||
|
||||
[docker:apt-repo]
|
||||
dockerfile = {tox_root}/tests/dockerfiles/repos/apt-repo.Dockerfile
|
||||
expose =
|
||||
APT_REPO_PORT=8443/tcp
|
||||
APT_REPO_HTTP_PORT=8080/tcp
|
||||
healthcheck_cmd = wget --no-check-certificate -qO- https://127.0.0.1:8443/Packages || exit 1
|
||||
healthcheck_interval = 1
|
||||
healthcheck_retries = 30
|
||||
@@ -132,15 +161,41 @@ docker =
|
||||
apt-repo
|
||||
allowlist_externals =
|
||||
docker
|
||||
set_env =
|
||||
APT_REPO_SCHEME = https
|
||||
commands =
|
||||
python3 -m id_tools test render-config {tox_root}/tests/configs/Apt.yaml.j2 {env_dir}/configs/Apt.yaml
|
||||
docker buildx build --platform linux/amd64 --add-host=host.docker.internal:host-gateway --build-context generated-config={env_dir}/configs -f {tox_root}/tests/dockerfiles/Apt.Dockerfile -t id-configurator-apt .
|
||||
docker rmi id-configurator-apt
|
||||
commands_post =
|
||||
docker buildx prune --all --force
|
||||
|
||||
[testenv:module-apt-http]
|
||||
package = skip
|
||||
labels = end-to-end
|
||||
change_dir = {tox_root}
|
||||
dependency_groups = dev
|
||||
docker =
|
||||
apt-repo
|
||||
allowlist_externals =
|
||||
docker
|
||||
description =
|
||||
Same as module-apt, but against a plain-HTTP source, since apt's trust
|
||||
model is the signed-by keyring, independent of transport scheme.
|
||||
set_env =
|
||||
APT_REPO_SCHEME = http
|
||||
commands =
|
||||
python3 -m id_tools test render-config {tox_root}/tests/configs/Apt.yaml.j2 {env_dir}/configs/Apt.yaml
|
||||
docker buildx build --platform linux/amd64 --add-host=host.docker.internal:host-gateway --build-context generated-config={env_dir}/configs -f {tox_root}/tests/dockerfiles/Apt.Dockerfile -t id-configurator-apt-http .
|
||||
docker rmi id-configurator-apt-http
|
||||
commands_post =
|
||||
docker buildx prune --all --force
|
||||
|
||||
[docker:yum-repo]
|
||||
dockerfile = {tox_root}/tests/dockerfiles/repos/yum-repo.Dockerfile
|
||||
expose =
|
||||
YUM_REPO_PORT=8443/tcp
|
||||
YUM_REPO_HTTP_PORT=8080/tcp
|
||||
healthcheck_cmd = wget --no-check-certificate -qO- https://127.0.0.1:8443/repodata/repomd.xml || exit 1
|
||||
healthcheck_interval = 1
|
||||
healthcheck_retries = 30
|
||||
@@ -156,15 +211,41 @@ docker =
|
||||
yum-repo
|
||||
allowlist_externals =
|
||||
docker
|
||||
set_env =
|
||||
YUM_REPO_SCHEME = https
|
||||
commands =
|
||||
python3 -m id_tools test render-config {tox_root}/tests/configs/Yum.yaml.j2 {env_dir}/configs/Yum.yaml
|
||||
docker buildx build --platform linux/amd64 --add-host=host.docker.internal:host-gateway --build-context generated-config={env_dir}/configs -f {tox_root}/tests/dockerfiles/Yum.Dockerfile -t id-configurator-yum .
|
||||
docker rmi id-configurator-yum
|
||||
commands_post =
|
||||
docker buildx prune --all --force
|
||||
|
||||
[testenv:module-yum-http]
|
||||
package = skip
|
||||
labels = end-to-end
|
||||
change_dir = {tox_root}
|
||||
dependency_groups = dev
|
||||
docker =
|
||||
yum-repo
|
||||
allowlist_externals =
|
||||
docker
|
||||
description =
|
||||
Same as module-yum, but against a plain-HTTP repository, since yum/dnf's
|
||||
trust model is the imported GPG key, independent of transport scheme.
|
||||
set_env =
|
||||
YUM_REPO_SCHEME = http
|
||||
commands =
|
||||
python3 -m id_tools test render-config {tox_root}/tests/configs/Yum.yaml.j2 {env_dir}/configs/Yum.yaml
|
||||
docker buildx build --platform linux/amd64 --add-host=host.docker.internal:host-gateway --build-context generated-config={env_dir}/configs -f {tox_root}/tests/dockerfiles/Yum.Dockerfile -t id-configurator-yum-http .
|
||||
docker rmi id-configurator-yum-http
|
||||
commands_post =
|
||||
docker buildx prune --all --force
|
||||
|
||||
[docker:apk-repo]
|
||||
dockerfile = {tox_root}/tests/dockerfiles/repos/apk-repo.Dockerfile
|
||||
expose =
|
||||
APK_REPO_PORT=8443/tcp
|
||||
APK_REPO_HTTP_PORT=8080/tcp
|
||||
healthcheck_cmd = wget --no-check-certificate -qO- https://127.0.0.1:8443/APKINDEX.tar.gz || exit 1
|
||||
healthcheck_interval = 1
|
||||
healthcheck_retries = 30
|
||||
@@ -189,15 +270,43 @@ description =
|
||||
native platform keeps them in agreement everywhere (including under
|
||||
QEMU emulation on non-amd64 hosts); CI runners are amd64, so this still
|
||||
matches the other envs there.
|
||||
set_env =
|
||||
APK_REPO_SCHEME = https
|
||||
commands =
|
||||
python3 -m id_tools test render-config {tox_root}/tests/configs/Apk.yaml.j2 {env_dir}/configs/Apk.yaml
|
||||
docker buildx build --add-host=host.docker.internal:host-gateway --build-context generated-config={env_dir}/configs -f {tox_root}/tests/dockerfiles/Apk.Dockerfile -t id-configurator-apk .
|
||||
docker rmi id-configurator-apk
|
||||
commands_post =
|
||||
docker buildx prune --all --force
|
||||
|
||||
[testenv:module-apk-http]
|
||||
package = skip
|
||||
labels = end-to-end
|
||||
change_dir = {tox_root}
|
||||
dependency_groups = dev
|
||||
docker =
|
||||
apk-repo
|
||||
allowlist_externals =
|
||||
docker
|
||||
description =
|
||||
Same as module-apk, but against a plain-HTTP repository, since apk's
|
||||
trust model is the signed index, independent of transport scheme. See
|
||||
module-apk's own description for why this isn't pinned to --platform
|
||||
linux/amd64 either.
|
||||
set_env =
|
||||
APK_REPO_SCHEME = http
|
||||
commands =
|
||||
python3 -m id_tools test render-config {tox_root}/tests/configs/Apk.yaml.j2 {env_dir}/configs/Apk.yaml
|
||||
docker buildx build --add-host=host.docker.internal:host-gateway --build-context generated-config={env_dir}/configs -f {tox_root}/tests/dockerfiles/Apk.Dockerfile -t id-configurator-apk-http .
|
||||
docker rmi id-configurator-apk-http
|
||||
commands_post =
|
||||
docker buildx prune --all --force
|
||||
|
||||
[docker:java-repo]
|
||||
dockerfile = {tox_root}/tests/dockerfiles/repos/java-repo.Dockerfile
|
||||
expose =
|
||||
JAVA_REPO_PORT=8443/tcp
|
||||
JAVA_REPO_HTTP_PORT=8080/tcp
|
||||
healthcheck_cmd = wget --no-check-certificate -qO- https://127.0.0.1:8443/org/interdiode/idconfiguratortestpkg/0.0.1/idconfiguratortestpkg-0.0.1.pom || exit 1
|
||||
healthcheck_interval = 1
|
||||
healthcheck_retries = 30
|
||||
@@ -213,10 +322,36 @@ docker =
|
||||
java-repo
|
||||
allowlist_externals =
|
||||
docker
|
||||
set_env =
|
||||
JAVA_REPO_SCHEME = https
|
||||
commands =
|
||||
python3 -m id_tools test render-config {tox_root}/tests/configs/Java.yaml.j2 {env_dir}/configs/Java.yaml
|
||||
docker buildx build --platform linux/amd64 --add-host=host.docker.internal:host-gateway --build-context generated-config={env_dir}/configs -f {tox_root}/tests/dockerfiles/Java.Dockerfile -t id-configurator-java .
|
||||
docker buildx build --platform linux/amd64 --add-host=host.docker.internal:host-gateway --build-arg JAVA_REPO_SCHEME={env:JAVA_REPO_SCHEME} --build-context generated-config={env_dir}/configs -f {tox_root}/tests/dockerfiles/Java.Dockerfile -t id-configurator-java .
|
||||
docker rmi id-configurator-java
|
||||
commands_post =
|
||||
docker buildx prune --all --force
|
||||
|
||||
[testenv:module-java-http]
|
||||
package = skip
|
||||
labels = end-to-end
|
||||
change_dir = {tox_root}
|
||||
dependency_groups = dev
|
||||
docker =
|
||||
java-repo
|
||||
allowlist_externals =
|
||||
docker
|
||||
description =
|
||||
Same as module-java, but against a plain-HTTP repository. Leiningen is
|
||||
skipped here (see Java.Dockerfile): it refuses any non-HTTPS repository
|
||||
unconditionally, with no configuration able to change that.
|
||||
set_env =
|
||||
JAVA_REPO_SCHEME = http
|
||||
commands =
|
||||
python3 -m id_tools test render-config {tox_root}/tests/configs/Java.yaml.j2 {env_dir}/configs/Java.yaml
|
||||
docker buildx build --platform linux/amd64 --add-host=host.docker.internal:host-gateway --build-arg JAVA_REPO_SCHEME={env:JAVA_REPO_SCHEME} --build-context generated-config={env_dir}/configs -f {tox_root}/tests/dockerfiles/Java.Dockerfile -t id-configurator-java-http .
|
||||
docker rmi id-configurator-java-http
|
||||
commands_post =
|
||||
docker buildx prune --all --force
|
||||
|
||||
[docker:certificate-repo]
|
||||
dockerfile = {tox_root}/tests/dockerfiles/repos/certificate-repo.Dockerfile
|
||||
@@ -246,11 +381,14 @@ commands =
|
||||
python3 -m id_tools test render-config {tox_root}/tests/configs/Certificate.yaml.j2 {env_dir}/configs/Certificate.yaml
|
||||
docker buildx build --platform linux/amd64 --add-host=host.docker.internal:host-gateway --build-arg CERTIFICATE_REPO_PORT={env:CERTIFICATE_REPO_PORT} --build-context generated-config={env_dir}/configs -f {tox_root}/tests/dockerfiles/Certificate.Dockerfile -t id-configurator-certificates .
|
||||
docker rmi id-configurator-certificates
|
||||
commands_post =
|
||||
docker buildx prune --all --force
|
||||
|
||||
[docker:php-repo]
|
||||
dockerfile = {tox_root}/tests/dockerfiles/repos/php-repo.Dockerfile
|
||||
expose =
|
||||
PHP_REPO_PORT=8443/tcp
|
||||
PHP_REPO_HTTP_PORT=8080/tcp
|
||||
healthcheck_cmd = wget --no-check-certificate -qO- https://127.0.0.1:8443/packages.json || exit 1
|
||||
healthcheck_interval = 1
|
||||
healthcheck_retries = 30
|
||||
@@ -266,15 +404,41 @@ docker =
|
||||
php-repo
|
||||
allowlist_externals =
|
||||
docker
|
||||
set_env =
|
||||
PHP_REPO_SCHEME = https
|
||||
commands =
|
||||
python3 -m id_tools test render-config {tox_root}/tests/configs/Php.yaml.j2 {env_dir}/configs/Php.yaml
|
||||
docker buildx build --platform linux/amd64 --add-host=host.docker.internal:host-gateway --build-context generated-config={env_dir}/configs -f {tox_root}/tests/dockerfiles/Php.Dockerfile -t id-configurator-php .
|
||||
docker rmi id-configurator-php
|
||||
commands_post =
|
||||
docker buildx prune --all --force
|
||||
|
||||
[testenv:module-php-http]
|
||||
package = skip
|
||||
labels = end-to-end
|
||||
change_dir = {tox_root}
|
||||
dependency_groups = dev
|
||||
docker =
|
||||
php-repo
|
||||
allowlist_externals =
|
||||
docker
|
||||
description =
|
||||
Same as module-php, but against a plain-HTTP repository, to verify
|
||||
secure-http is disabled (and packagist.org too) for a non-HTTPS source.
|
||||
set_env =
|
||||
PHP_REPO_SCHEME = http
|
||||
commands =
|
||||
python3 -m id_tools test render-config {tox_root}/tests/configs/Php.yaml.j2 {env_dir}/configs/Php.yaml
|
||||
docker buildx build --platform linux/amd64 --add-host=host.docker.internal:host-gateway --build-context generated-config={env_dir}/configs -f {tox_root}/tests/dockerfiles/Php.Dockerfile -t id-configurator-php-http .
|
||||
docker rmi id-configurator-php-http
|
||||
commands_post =
|
||||
docker buildx prune --all --force
|
||||
|
||||
[docker:npm-repo]
|
||||
dockerfile = {tox_root}/tests/dockerfiles/repos/npm-repo.Dockerfile
|
||||
expose =
|
||||
NPM_REPO_PORT=8443/tcp
|
||||
NPM_REPO_HTTP_PORT=8080/tcp
|
||||
healthcheck_cmd = wget --no-check-certificate -qO- https://127.0.0.1:8443/idconfiguratortestpkg || exit 1
|
||||
healthcheck_interval = 1
|
||||
healthcheck_retries = 30
|
||||
@@ -290,15 +454,40 @@ docker =
|
||||
npm-repo
|
||||
allowlist_externals =
|
||||
docker
|
||||
set_env =
|
||||
NPM_REPO_SCHEME = https
|
||||
commands =
|
||||
python3 -m id_tools test render-config {tox_root}/tests/configs/Npm.yaml.j2 {env_dir}/configs/Npm.yaml
|
||||
docker buildx build --platform linux/amd64 --add-host=host.docker.internal:host-gateway --build-context generated-config={env_dir}/configs -f {tox_root}/tests/dockerfiles/Npm.Dockerfile -t id-configurator-npm .
|
||||
docker rmi id-configurator-npm
|
||||
commands_post =
|
||||
docker buildx prune --all --force
|
||||
|
||||
[testenv:module-npm-http]
|
||||
package = skip
|
||||
labels = end-to-end
|
||||
change_dir = {tox_root}
|
||||
dependency_groups = dev
|
||||
docker =
|
||||
npm-repo
|
||||
allowlist_externals =
|
||||
docker
|
||||
description =
|
||||
Same as module-npm, but against a plain-HTTP registry.
|
||||
set_env =
|
||||
NPM_REPO_SCHEME = http
|
||||
commands =
|
||||
python3 -m id_tools test render-config {tox_root}/tests/configs/Npm.yaml.j2 {env_dir}/configs/Npm.yaml
|
||||
docker buildx build --platform linux/amd64 --add-host=host.docker.internal:host-gateway --build-context generated-config={env_dir}/configs -f {tox_root}/tests/dockerfiles/Npm.Dockerfile -t id-configurator-npm-http .
|
||||
docker rmi id-configurator-npm-http
|
||||
commands_post =
|
||||
docker buildx prune --all --force
|
||||
|
||||
[docker:cargo-repo]
|
||||
dockerfile = {tox_root}/tests/dockerfiles/repos/cargo-repo.Dockerfile
|
||||
expose =
|
||||
CARGO_REPO_PORT=8443/tcp
|
||||
CARGO_REPO_HTTP_PORT=8080/tcp
|
||||
healthcheck_cmd = wget --no-check-certificate -qO- https://127.0.0.1:8443/config.json || exit 1
|
||||
healthcheck_interval = 1
|
||||
healthcheck_retries = 30
|
||||
@@ -314,15 +503,40 @@ docker =
|
||||
cargo-repo
|
||||
allowlist_externals =
|
||||
docker
|
||||
set_env =
|
||||
CARGO_REPO_SCHEME = https
|
||||
commands =
|
||||
python3 -m id_tools test render-config {tox_root}/tests/configs/Rust.yaml.j2 {env_dir}/configs/Rust.yaml
|
||||
docker buildx build --platform linux/amd64 --add-host=host.docker.internal:host-gateway --build-context generated-config={env_dir}/configs -f {tox_root}/tests/dockerfiles/Rust.Dockerfile -t id-configurator-rust .
|
||||
docker rmi id-configurator-rust
|
||||
commands_post =
|
||||
docker buildx prune --all --force
|
||||
|
||||
[testenv:module-rust-http]
|
||||
package = skip
|
||||
labels = end-to-end
|
||||
change_dir = {tox_root}
|
||||
dependency_groups = dev
|
||||
docker =
|
||||
cargo-repo
|
||||
allowlist_externals =
|
||||
docker
|
||||
description =
|
||||
Same as module-rust, but against a plain-HTTP sparse registry.
|
||||
set_env =
|
||||
CARGO_REPO_SCHEME = http
|
||||
commands =
|
||||
python3 -m id_tools test render-config {tox_root}/tests/configs/Rust.yaml.j2 {env_dir}/configs/Rust.yaml
|
||||
docker buildx build --platform linux/amd64 --add-host=host.docker.internal:host-gateway --build-context generated-config={env_dir}/configs -f {tox_root}/tests/dockerfiles/Rust.Dockerfile -t id-configurator-rust-http .
|
||||
docker rmi id-configurator-rust-http
|
||||
commands_post =
|
||||
docker buildx prune --all --force
|
||||
|
||||
[docker:go-repo]
|
||||
dockerfile = {tox_root}/tests/dockerfiles/repos/go-repo.Dockerfile
|
||||
expose =
|
||||
GO_REPO_PORT=8443/tcp
|
||||
GO_REPO_HTTP_PORT=8080/tcp
|
||||
healthcheck_cmd = wget --no-check-certificate -qO- https://127.0.0.1:8443/example.invalid/idconfiguratortestpkg/@v/list || exit 1
|
||||
healthcheck_interval = 1
|
||||
healthcheck_retries = 30
|
||||
@@ -338,15 +552,41 @@ docker =
|
||||
go-repo
|
||||
allowlist_externals =
|
||||
docker
|
||||
set_env =
|
||||
GO_REPO_SCHEME = https
|
||||
commands =
|
||||
python3 -m id_tools test render-config {tox_root}/tests/configs/Go.yaml.j2 {env_dir}/configs/Go.yaml
|
||||
docker buildx build --platform linux/amd64 --add-host=host.docker.internal:host-gateway --build-context generated-config={env_dir}/configs -f {tox_root}/tests/dockerfiles/Go.Dockerfile -t id-configurator-go .
|
||||
docker rmi id-configurator-go
|
||||
commands_post =
|
||||
docker buildx prune --all --force
|
||||
|
||||
[testenv:module-go-http]
|
||||
package = skip
|
||||
labels = end-to-end
|
||||
change_dir = {tox_root}
|
||||
dependency_groups = dev
|
||||
docker =
|
||||
go-repo
|
||||
allowlist_externals =
|
||||
docker
|
||||
description =
|
||||
Same as module-go, but against a plain-HTTP module proxy, to verify
|
||||
GOINSECURE is configured correctly for a non-HTTPS GOPROXY.
|
||||
set_env =
|
||||
GO_REPO_SCHEME = http
|
||||
commands =
|
||||
python3 -m id_tools test render-config {tox_root}/tests/configs/Go.yaml.j2 {env_dir}/configs/Go.yaml
|
||||
docker buildx build --platform linux/amd64 --add-host=host.docker.internal:host-gateway --build-context generated-config={env_dir}/configs -f {tox_root}/tests/dockerfiles/Go.Dockerfile -t id-configurator-go-http .
|
||||
docker rmi id-configurator-go-http
|
||||
commands_post =
|
||||
docker buildx prune --all --force
|
||||
|
||||
[docker:ruby-repo]
|
||||
dockerfile = {tox_root}/tests/dockerfiles/repos/ruby-repo.Dockerfile
|
||||
expose =
|
||||
RUBY_REPO_PORT=8443/tcp
|
||||
RUBY_REPO_HTTP_PORT=8080/tcp
|
||||
healthcheck_cmd = wget --no-check-certificate -qO- https://127.0.0.1:8443/versions || exit 1
|
||||
healthcheck_interval = 1
|
||||
healthcheck_retries = 30
|
||||
@@ -362,15 +602,40 @@ docker =
|
||||
ruby-repo
|
||||
allowlist_externals =
|
||||
docker
|
||||
set_env =
|
||||
RUBY_REPO_SCHEME = https
|
||||
commands =
|
||||
python3 -m id_tools test render-config {tox_root}/tests/configs/Ruby.yaml.j2 {env_dir}/configs/Ruby.yaml
|
||||
docker buildx build --platform linux/amd64 --add-host=host.docker.internal:host-gateway --build-context generated-config={env_dir}/configs -f {tox_root}/tests/dockerfiles/Ruby.Dockerfile -t id-configurator-ruby .
|
||||
docker rmi id-configurator-ruby
|
||||
commands_post =
|
||||
docker buildx prune --all --force
|
||||
|
||||
[testenv:module-ruby-http]
|
||||
package = skip
|
||||
labels = end-to-end
|
||||
change_dir = {tox_root}
|
||||
dependency_groups = dev
|
||||
docker =
|
||||
ruby-repo
|
||||
allowlist_externals =
|
||||
docker
|
||||
description =
|
||||
Same as module-ruby, but against a plain-HTTP gem source.
|
||||
set_env =
|
||||
RUBY_REPO_SCHEME = http
|
||||
commands =
|
||||
python3 -m id_tools test render-config {tox_root}/tests/configs/Ruby.yaml.j2 {env_dir}/configs/Ruby.yaml
|
||||
docker buildx build --platform linux/amd64 --add-host=host.docker.internal:host-gateway --build-context generated-config={env_dir}/configs -f {tox_root}/tests/dockerfiles/Ruby.Dockerfile -t id-configurator-ruby-http .
|
||||
docker rmi id-configurator-ruby-http
|
||||
commands_post =
|
||||
docker buildx prune --all --force
|
||||
|
||||
[docker:nuget-repo]
|
||||
dockerfile = {tox_root}/tests/dockerfiles/repos/nuget-repo.Dockerfile
|
||||
expose =
|
||||
NUGET_REPO_PORT=8443/tcp
|
||||
NUGET_REPO_HTTP_PORT=8080/tcp
|
||||
healthcheck_cmd = wget --no-check-certificate -qO- https://127.0.0.1:8443/index.json || exit 1
|
||||
healthcheck_interval = 1
|
||||
healthcheck_retries = 30
|
||||
@@ -386,10 +651,35 @@ docker =
|
||||
nuget-repo
|
||||
allowlist_externals =
|
||||
docker
|
||||
set_env =
|
||||
NUGET_REPO_SCHEME = https
|
||||
commands =
|
||||
python3 -m id_tools test render-config {tox_root}/tests/configs/NuGet.yaml.j2 {env_dir}/configs/NuGet.yaml
|
||||
docker buildx build --platform linux/amd64 --add-host=host.docker.internal:host-gateway --build-context generated-config={env_dir}/configs -f {tox_root}/tests/dockerfiles/NuGet.Dockerfile -t id-configurator-nuget .
|
||||
docker rmi id-configurator-nuget
|
||||
commands_post =
|
||||
docker buildx prune --all --force
|
||||
|
||||
[testenv:module-nuget-http]
|
||||
package = skip
|
||||
labels = end-to-end
|
||||
change_dir = {tox_root}
|
||||
dependency_groups = dev
|
||||
docker =
|
||||
nuget-repo
|
||||
allowlist_externals =
|
||||
docker
|
||||
description =
|
||||
Same as module-nuget, but against a plain-HTTP feed, to verify
|
||||
allowInsecureConnections is configured correctly for a non-HTTPS source.
|
||||
set_env =
|
||||
NUGET_REPO_SCHEME = http
|
||||
commands =
|
||||
python3 -m id_tools test render-config {tox_root}/tests/configs/NuGet.yaml.j2 {env_dir}/configs/NuGet.yaml
|
||||
docker buildx build --platform linux/amd64 --add-host=host.docker.internal:host-gateway --build-context generated-config={env_dir}/configs -f {tox_root}/tests/dockerfiles/NuGet.Dockerfile -t id-configurator-nuget-http .
|
||||
docker rmi id-configurator-nuget-http
|
||||
commands_post =
|
||||
docker buildx prune --all --force
|
||||
|
||||
[docker:config-url-repo]
|
||||
dockerfile = {tox_root}/tests/dockerfiles/repos/config-url-repo.Dockerfile
|
||||
@@ -419,3 +709,5 @@ description =
|
||||
commands =
|
||||
sh -c 'docker buildx build --platform linux/amd64 --add-host=host.docker.internal:host-gateway --build-arg CONFIG_REPO_PORT={env:CONFIG_REPO_PORT} --build-arg CONFIG_URL_CACERT_CONTENT="$(cat {tox_root}/tests/certs/test-ca.crt.pem)" -f {tox_root}/tests/dockerfiles/ConfigUrl.Dockerfile -t id-configurator-config-url .'
|
||||
docker rmi id-configurator-config-url
|
||||
commands_post =
|
||||
docker buildx prune --all --force
|
||||
|
||||
@@ -22,11 +22,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "cachetools"
|
||||
sdist = {url = "https://files.pythonhosted.org/packages/29/2c/3f18755527b03ca9ff6be724bd5370cb777c76a87f17301377cf04a4729b/cachetools-7.2.0.tar.gz", hash = "sha256:bcac1a1b8da6909994a2957238a57b8140dab7c5c5c69a43669654fe87a33c1d", size = 41129, upload-time = "2026-09-16T20:48:27.209Z"}
|
||||
sdist = {url = "https://files.pythonhosted.org/packages/31/44/71476a5812da1ddf2c9a3efd31ae76d01480a1cf03ed13ac28aa8f2402e4/cachetools-7.2.1.tar.gz", hash = "sha256:b1a7537025c06abf96fcc1443e496af9a3fb95e774e70e1f0af226f73f7f2dcc", size = 41357, upload-time = "2026-10-05T18:40:06.361Z"}
|
||||
source = {registry = "https://pypi.org/simple"}
|
||||
version = "7.2.0"
|
||||
version = "7.2.1"
|
||||
wheels = [
|
||||
{url = "https://files.pythonhosted.org/packages/cc/bb/1c6e7a89b11da19f137e4ef5a5c5fe47a5bb686449ba343944ccf8fb34b2/cachetools-7.2.0-py3-none-any.whl", hash = "sha256:3045213f186b89fdd95d94441354c4bd87c570b7a38d8c3fd1d9dd37f6dc90d8", size = 16918, upload-time = "2026-09-16T20:48:25.575Z"}
|
||||
{url = "https://files.pythonhosted.org/packages/f0/c9/2a61d784caf0d869a3326728c57c7203f50cc53f3cca2ee76bf924769eb4/cachetools-7.2.1-py3-none-any.whl", hash = "sha256:63aa53dfe7473c10cccdd5a01dedf76ef2c4b73a58840d9396e7d0752cbdac3b", size = 17006, upload-time = "2026-10-05T18:40:04.827Z"}
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -407,11 +407,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "filelock"
|
||||
sdist = {url = "https://files.pythonhosted.org/packages/70/51/2bc9e529f154fad99b6cd0073e609291eb32fd23581b32362d33d164d316/filelock-4.0.9.tar.gz", hash = "sha256:635e7d67fa92654eed444e75e9ca18426d34e77ad9c469bf4373f75a932f7b22", size = 251200, upload-time = "2026-10-01T23:09:14.029Z"}
|
||||
sdist = {url = "https://files.pythonhosted.org/packages/53/e4/34efcb869715cf299e47d1ac7b2624d2bcb6f2d3dffc2f0abe8417f65ab2/filelock-4.0.12.tar.gz", hash = "sha256:cf42711a7ac791818b299fab0332a088c65aeeefa36290de98db92c434303b0c", size = 253880, upload-time = "2026-10-05T13:17:43.677Z"}
|
||||
source = {registry = "https://pypi.org/simple"}
|
||||
version = "4.0.9"
|
||||
version = "4.0.12"
|
||||
wheels = [
|
||||
{url = "https://files.pythonhosted.org/packages/01/4f/83454fafd628e1e7e1726d74e44fb2332be5969d04c6182ca1fecb6c580e/filelock-4.0.9-py3-none-any.whl", hash = "sha256:9287fd61b99a806e5202be29a83034c0808a1b9830e820537c2f5773981f7eeb", size = 110368, upload-time = "2026-10-01T23:09:12.391Z"}
|
||||
{url = "https://files.pythonhosted.org/packages/da/a1/b047e1c486a2c66283633b7dbb73872ca16ca1ce6a426f70d4d037fc8e1c/filelock-4.0.12-py3-none-any.whl", hash = "sha256:5f17ee83ecee8a6f3e389c75822fb70a1c2f0506b99438a6dffa1d56793588c8", size = 111732, upload-time = "2026-10-05T13:17:42.361Z"}
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -424,7 +424,7 @@ dependencies = [
|
||||
]
|
||||
name = "id-configurator"
|
||||
source = {editable = "."}
|
||||
version = "2026.10.2"
|
||||
version = "2026.10.9"
|
||||
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
@@ -472,11 +472,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
sdist = {url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z"}
|
||||
sdist = {url = "https://files.pythonhosted.org/packages/01/e1/2069291243c926a2ff1cd706c7f3eeb9b62144bf60f77c9fb9ff2fb26bd3/iniconfig-2.3.1.tar.gz", hash = "sha256:67f4b9c50da0dedf52af349e7749a80a9057a5031199791b906c3bb3ae878960", size = 21209, upload-time = "2026-10-06T22:48:38.076Z"}
|
||||
source = {registry = "https://pypi.org/simple"}
|
||||
version = "2.3.0"
|
||||
version = "2.3.1"
|
||||
wheels = [
|
||||
{url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z"}
|
||||
{url = "https://files.pythonhosted.org/packages/56/43/4ca9e49d27a1fcf6bece6f6aec0ea46bb9112489b93d4b688fb415457bdb/iniconfig-2.3.1-py3-none-any.whl", hash = "sha256:9121e2c1fdb355232495be3194c8dfe87ccc2d5dee45947b78e68f499790d7a7", size = 7552, upload-time = "2026-10-06T22:48:36.959Z"}
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -514,54 +514,100 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "markupsafe"
|
||||
sdist = {url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z"}
|
||||
sdist = {url = "https://files.pythonhosted.org/packages/38/9b/e422a865e1d5d57d0e509b4e0bf1c1a70a7f6382c29a5aa428df994c8bc8/markupsafe-3.0.4.tar.gz", hash = "sha256:2e9ad7dd851bf45fab9f75cbff4cb493fee9979e8d8c7c9c3ee119022518edd6", size = 153777, upload-time = "2026-10-02T23:07:22.29Z"}
|
||||
source = {registry = "https://pypi.org/simple"}
|
||||
version = "3.0.3"
|
||||
version = "3.0.4"
|
||||
wheels = [
|
||||
{url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z"}
|
||||
{url = "https://files.pythonhosted.org/packages/6d/18/4bc5ba32499e87bb2b0ef5b3a9bb9c00a131fa961ddf0be548cb550f548b/markupsafe-3.0.4-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:de8b364c423ef0a4bad9069657d617f9a5d2b2062457a89b1fa16ee199c399c1", size = 12786, upload-time = "2026-10-02T23:05:08.709Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/4e/6f/17f0c099bf25f3e31e63cc19244d9f6af861a9a4ab778c203997903cfdd0/markupsafe-3.0.4-cp313-cp313-android_24_x86_64.whl", hash = "sha256:34bdde374c5932765d7dc685c4a1d191a3207852d67e8e0a9eb6ea85156181f1", size = 12510, upload-time = "2026-10-02T23:05:09.93Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/11/af/1a141081b905036ee904ec4bd945e1f70b4e1b32d33c4e59e8cf1d58b247/markupsafe-3.0.4-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:6bd9e1788e15bfcf6a9082de42e30387e7b85d211ab21e57a939bb8cfaaf8d96", size = 11583, upload-time = "2026-10-02T23:05:10.884Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/e7/0a/a89385ae590232622a03e091805cff12f24fabe6c11e0e8bae096cece81c/markupsafe-3.0.4-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:5066b244f576f91afc8ee3ba029a89f99d39c79b1853fe9d39bea9f0afbec148", size = 11801, upload-time = "2026-10-02T23:05:11.913Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/ed/85/ea548dc013962eb73653124bc595635fbf9e0fa41d1f181a967ccb784dfb/markupsafe-3.0.4-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7a83aa6e4805df46fed18e989d3d16f86ef60cb50bbc8d9ce3a6be89165fbf6e", size = 11443, upload-time = "2026-10-02T23:05:12.887Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/cc/72/15f2e5ec9cf2eb00d5cdfe968d94e4156a7bd7303832c3f3b2c403a36839/markupsafe-3.0.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2d1b7d9308288661f56672b1b157d75fc536714d3638487bbea17b6318a78248", size = 11665, upload-time = "2026-10-02T23:05:13.829Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/ca/e0/4030bea613677e333c8a2c901fd405055f657f9d06acba5b7357984b6ef7/markupsafe-3.0.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:73e77980c7207854f00fc4e71fb1626868d5740ab4012623d55c7a99ad122a72", size = 12061, upload-time = "2026-10-02T23:05:14.807Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/f3/a5/28b76a7449eb702966b88bef599e2360b411fbb3afeee8fe560939be06ec/markupsafe-3.0.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7018d4af1cd272e847aa5917983ab5e83e4f6579f9dbfecd4a79c0ca80b144c2", size = 24395, upload-time = "2026-10-02T23:05:15.909Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/07/6c/21232811afc3a063b5e934b1ae2efda52f46154ec382f585149c020e61fe/markupsafe-3.0.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c90d5b3d4e944e065a301d741b3c1d784f6bd1f503aa68b4967e32b2ba313d85", size = 27183, upload-time = "2026-10-02T23:05:16.976Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/14/38/6ccdfa5b59049cb36fb80cbc80aee9cf1fc9bb77d1335ad435f2070b08cf/markupsafe-3.0.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:18a801868a884f216e784d7d14db2a4077143ce7610440aee2ce8f734e7cfcde", size = 24772, upload-time = "2026-10-02T23:05:18.209Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/63/e0/cec6865dfe88cb48fedd4b20aed6af5158e41092adcbf3e028bcc6ec2108/markupsafe-3.0.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:434139499bb20b502ed3baa1f169e618f924a97e7a777fea1a49446d80106cf6", size = 23017, upload-time = "2026-10-02T23:05:19.286Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/ee/76/6ed4940bb7648a9aac457c14f870cfdd5105f139a0fb1f29cd61fafa47d1/markupsafe-3.0.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9e227f3dbe6bde7491cf0a9965d00b88c6b1a4a95d11480ddf88bb96d397c19f", size = 22036, upload-time = "2026-10-02T23:05:20.352Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/a1/4f/ed476226d4fe46a09090a36025bf319296810028df55eb12f1253b540f3a/markupsafe-3.0.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b8cd1f918b26fd7b1832ece557cc18f2d8747309ff8b3f0ef9d4250c5ad67a39", size = 23786, upload-time = "2026-10-02T23:05:21.576Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/9a/35/66ff30450e35ef5fba9ebc930c9411747e537fd9447b65e44f5007e2b84d/markupsafe-3.0.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a5fcffb37e602b0b3c1638a97746b9b96125caa9bcf6fa41d337a9261de231ee", size = 22672, upload-time = "2026-10-02T23:05:22.922Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/32/0b/72f45ce4b4efcbca4b80cf1b06703eff0be8d37e82abb78f66c85a7ead1e/markupsafe-3.0.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5989cb26b2e1efc6a42216a9f6b5ee495ce5ace2e5b352a9af489976b32d1ee2", size = 24107, upload-time = "2026-10-02T23:05:24.175Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/d2/03/71776e5fdcba04614b384cc102e8a4198208579d896fd1394cb7cb9aa900/markupsafe-3.0.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:add96447a86d205ab616665d53b2950ee81083757f56e6ea833c8b2917646b46", size = 21615, upload-time = "2026-10-02T23:05:25.215Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/ab/5f/801ce02a02e7aee0f784b1ec7843026178f6adeb9c93ac67eb1992a9a84d/markupsafe-3.0.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2628d3a8cb648ecebb3c5d6b0a1052d400e4d8b7ac0fb786be8d285b50040d17", size = 23076, upload-time = "2026-10-02T23:05:26.423Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/4a/85/c43776625428f3bb4a61e8633940400e3efe6409e3c6f5bff26de5e45618/markupsafe-3.0.4-cp313-cp313-win32.whl", hash = "sha256:672d207103e6b16ca098611b0f9efad6bc00afd47c03d6ef62186495ca677dc0", size = 14118, upload-time = "2026-10-02T23:05:27.716Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/6f/36/163da64de88a13db79214ef75fa041be7fa13bdb42261cf5b7484de14bfb/markupsafe-3.0.4-cp313-cp313-win_amd64.whl", hash = "sha256:1f1f9477e174582b0a1b583d60b66e1f2cf5d3fe12cee985e4aedf44766600e5", size = 14313, upload-time = "2026-10-02T23:05:28.749Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/9f/a8/9b662783ffaa1149221432a923cee562f78b9cbbb8baa3df9b3753e63e1e/markupsafe-3.0.4-cp313-cp313-win_arm64.whl", hash = "sha256:06de8ef6331f6e822c28d577dc8bf43fe398800477c49498f38fc38b67ff33fc", size = 14189, upload-time = "2026-10-02T23:05:29.917Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/5c/c3/a944f3b0df22bd129e96915b9f4e98d2eeca6516687d7618304a966c3c74/markupsafe-3.0.4-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:4ed644d75aa94a2baf7ec3a96eaa160ea58c742eb9d27c6506053c5c40fc84ed", size = 12787, upload-time = "2026-10-02T23:05:30.971Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/d4/d6/a44863f69d88b6c7e27889108f70d47aed259edf89d5df3c5fca1eac87d6/markupsafe-3.0.4-cp314-cp314-android_24_x86_64.whl", hash = "sha256:6d2a9efe686f9de00d0d1ea32a4a5a86d558a2277501bd78d964214eab625e59", size = 12525, upload-time = "2026-10-02T23:05:32.263Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/17/8f/168ba80e532dd6a93f96f8f706f1ad41d7990b6e1aeedc1cc0d211a33497/markupsafe-3.0.4-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8781a792a070cf2bd1b86d3aa943894115faaba6e88122a7bf32d62072742453", size = 11589, upload-time = "2026-10-02T23:05:33.251Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/32/b3/aa2c95a574d3af39403a469b295886eb9b6d448da568cbebb5a2cbfdc2e5/markupsafe-3.0.4-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:971a3bbb75d97ae4e2e8f7d4834236f86f85f0c85e04ab2e191db1123b04f80b", size = 11801, upload-time = "2026-10-02T23:05:34.315Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/60/d0/34b810107d83840e768bf485de795893ebbae35b26ab061b487adfa0a692/markupsafe-3.0.4-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:8909c2f1c6dd65e054ac4b573a91c8384d1492281e55d82d159d653f7a13adf6", size = 11447, upload-time = "2026-10-02T23:05:35.302Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/6c/ab/2f8488f0f817a39fca068d2b17daf446bf5cdb3eae28c3720af534d873b4/markupsafe-3.0.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:4cf3468d5ec187ffffcaca8e61929a37448f215dafc1386a12c750a72fe53634", size = 11722, upload-time = "2026-10-02T23:05:36.363Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/ad/40/e2d117b048d47282ade906fbfd92814cbee5647afc13fda88a3406039372/markupsafe-3.0.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:52704c5d36eb6dda8866493decd61111fff86244c9b1ad225ca01b9e91e5970f", size = 12061, upload-time = "2026-10-02T23:05:37.397Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/9a/a8/73a81135e85ba66217f5af7facb03bbb386807e1a729ab64532e4c802652/markupsafe-3.0.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1caa2fa5a6184fb233153b35f654e6687bd555476f6170f29d8ee9be1a8b0af9", size = 24434, upload-time = "2026-10-02T23:05:38.407Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/ac/ca/fa9216dd01efee2dfdacafe7df32b4d0170fbac694b0c258a193d6e53999/markupsafe-3.0.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:387d8cd30e69b3f0a72877b9ae717033396404e19095b17fe89753a981fda44f", size = 27183, upload-time = "2026-10-02T23:05:39.581Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/fa/4e/a469509e538d37af51103b17b073126973f2b1cbf197ff32c7ddf025cfe5/markupsafe-3.0.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:051417f74bcaaefa316276e0ff723f541616ca51043d070da00249d9bddd3e3c", size = 24813, upload-time = "2026-10-02T23:05:40.671Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/8f/db/d7282caf7ab03af44d5d6fdbaa019b35c7d7f1c90588b839c07cba640d6a/markupsafe-3.0.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8e9f292fcda89b324f2f5c91d13f1424a153e40fc2756f38ee23b15835ff300", size = 23049, upload-time = "2026-10-02T23:05:41.864Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/30/f3/b6a425206e6964efda6acee544d0eb01d1501784d0b8e2dcc74986f33b17/markupsafe-3.0.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:df1ae86ff54725a01fa1a0510b914ca53a161b7050be74f6204e24aded5971d0", size = 22088, upload-time = "2026-10-02T23:05:43.014Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/ea/8a/84d3582fc1f0d5bd466cdf2eebf175e172158a6e70701aacec1de1b35430/markupsafe-3.0.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8965520ac587c94a4ac48b729be3d8b8de00af39699b17585dfb599babe77977", size = 23846, upload-time = "2026-10-02T23:05:44.098Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/1c/65/db101cce51b7ba4864ac491a9859d297dd1adf0e55b103fee9db9c47c527/markupsafe-3.0.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:340cbb1957ba99929cbf19a75626d36ba1ae21d1730b287d1cf7f824a20c4fc7", size = 22674, upload-time = "2026-10-02T23:05:45.23Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/e0/49/ddee9813d71db0c7a5c9d97c832125e6758a0c844777f1cf076569bb0e22/markupsafe-3.0.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:3a93d9616ddecfb393727a0041a562cf0b15a244e20f2bd25efc7949be4c4f17", size = 24104, upload-time = "2026-10-02T23:05:46.398Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/aa/0e/7d8518d726726870a2399d69fd30d0fa36c5e57a2132c336b58d7c491073/markupsafe-3.0.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d2e56fd3b00222722abfb3f5f0759ddbae4b90811b5ad4343c64030ad1bde70c", size = 21637, upload-time = "2026-10-02T23:05:47.48Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/b4/b0/b505e8a361ba557dbf3b3aa7331ea39b00d2022a26e925ff8463b9714bb3/markupsafe-3.0.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:0d9c47709875fdb321452056622e930c52afbc07a7d780762fbb8b4d91ce6fa4", size = 23081, upload-time = "2026-10-02T23:05:48.611Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/1c/ea/9cc3cea873f980c75cbdb6f4277ce30ee955de38be0b3d02f14c108e0698/markupsafe-3.0.4-cp314-cp314-win32.whl", hash = "sha256:38fc55594dab834470b6733dead2ee9e3f657fb0608c769dcafa0ba5ab52f45c", size = 14272, upload-time = "2026-10-02T23:05:49.707Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/80/f0/5792ff768a410f93ee3f84fc19345295ffc352d2c936b424cb37e514714c/markupsafe-3.0.4-cp314-cp314-win_amd64.whl", hash = "sha256:c1bc67752d5f21013cfe430df4062441714eab79f65a6a05e01505957e9c35fe", size = 14501, upload-time = "2026-10-02T23:05:50.788Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/5f/cf/3d074a8edffcc6899355232ff2543ae8d929733239596423b7db79698bc9/markupsafe-3.0.4-cp314-cp314-win_arm64.whl", hash = "sha256:7e1636da3d8dfc220b6dd10264db5f2b165e4888c4518594898fbe381049af8a", size = 14364, upload-time = "2026-10-02T23:05:51.857Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/d9/31/87ce42159aae2163cf3bbbd0c44bc87780510eecab1ea3859099aed95dcb/markupsafe-3.0.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:805c8b84534fa10891890f0e4be39f3a99e94615d93e8836bf9fa1fdca2feeb2", size = 11759, upload-time = "2026-10-02T23:05:52.951Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/5f/53/b047207eeb7752e960aca3eb1df5fb7eefa7dd4c62ac49bb156456c8a702/markupsafe-3.0.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:fa95848c929b6a75f6848d3c9793e59db365ee436776e57db835cdbfa79ba977", size = 12096, upload-time = "2026-10-02T23:05:54.066Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/ee/51/4326c88a13c7b755657d44b4bb986f8c3d9843ecba7e22d98661d87f9a57/markupsafe-3.0.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e916035e3e9930cbdfdd10abf48861340221857f45509565898e012263f7b289", size = 25699, upload-time = "2026-10-02T23:05:55.15Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/f2/bb/990581b7474bfcf2cf34bed6ba5ea23bd87adb9d671213d68e88620e7a6b/markupsafe-3.0.4-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b4d12837e0203bbace818ff4a7461afdcd78bcd782351cea148139180d7bcffe", size = 27804, upload-time = "2026-10-02T23:05:56.29Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/6b/89/89491878c28e8291f5aa2fffe2c2d57230d10ae366d55dd810b840513d78/markupsafe-3.0.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5086f9975abb1ab531ee6afca1761e4b59a19b446f3f6522ed776963228cfe5a", size = 25912, upload-time = "2026-10-02T23:05:57.416Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/30/77/680998b54efdea06fc114565cd739b6d059f826a0279219b218dfa750d29/markupsafe-3.0.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b4a635a0487774f841cb1fb62e907e7195cc95bc761e053184b8acc3ceb20733", size = 23623, upload-time = "2026-10-02T23:05:58.557Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/ae/75/2709f5ac5de9467b40b10e2bb8f89cc63dfb74582e09aa734b1124a217de/markupsafe-3.0.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb96e6e088d6cf71c1ea977510948320234824cf226e32f6f6e044f7a9c82b34", size = 23301, upload-time = "2026-10-02T23:05:59.94Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/a0/c8/39eadc6c5b14c9c7679bfb98f4d4c6a97863b5beb91839aca4d2d6e16e55/markupsafe-3.0.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8b5d563170ff8ba3181caa967c99a3c804d1dedb702c7cb93a6a7c32247da978", size = 24950, upload-time = "2026-10-02T23:06:01.289Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/1a/5e/01037f8a43e8ccb0bffb4fbdc5212db05bf080fdd7286cd392332d58128a/markupsafe-3.0.4-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:396ec4e65cc889f69786b3b89478b471cee5a3bcf468b9d9bb03e1a30fb291fc", size = 23330, upload-time = "2026-10-02T23:06:02.441Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/d4/f4/23e83ce0596bb0cbe670502d31df8f757bbd01a392aa486fa3b40d1ed399/markupsafe-3.0.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:15ba9e28640feef770374b116a6f019c21f52404aeabe516aa7f800587b98cfc", size = 25239, upload-time = "2026-10-02T23:06:03.579Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/88/5b/3708897368073cc683d524750474f41a77d2986152c380dcc55b20fdf340/markupsafe-3.0.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:d920abdfa61279ba1a2ef9484aab07bf03331f8c08a10120fa332353d06e6932", size = 22767, upload-time = "2026-10-02T23:06:04.699Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/c6/61/ebda1307864b409e6b3115757a3d4a09cca46cfb6cc65191b5de226b424b/markupsafe-3.0.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a9f54054101545a9a9cccefddf54316aa6e4491611fcbef9e91b3b6bebec04f6", size = 23668, upload-time = "2026-10-02T23:06:05.9Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/09/15/98075cceac3b5ba0dbb8e4762a847be967d2befc349a2cf2d0ac77f62c9d/markupsafe-3.0.4-cp314-cp314t-win32.whl", hash = "sha256:12a606a492de952afcb43b59a14aaaaad120e708d3663dd0fdf2d738d427a691", size = 14301, upload-time = "2026-10-02T23:06:07.109Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/0b/a3/768b560fcc4156685cb563d922b217810cfa7bc135773367f62f1f9d2078/markupsafe-3.0.4-cp314-cp314t-win_amd64.whl", hash = "sha256:a18f38cafc329bac5e3c2b96c765b4c96d3d103421ed22ab7988c1e3fce27464", size = 14526, upload-time = "2026-10-02T23:06:08.276Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/93/63/da554b4c97a6b0ea3229ca7fe8cbfb620be81613d517f482e85958550537/markupsafe-3.0.4-cp314-cp314t-win_arm64.whl", hash = "sha256:eba154571c16e032112afac0dc2dfe9e63c2ceb7aedd07bb7eecf2ce26d4dd4c", size = 14421, upload-time = "2026-10-02T23:06:09.402Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/a9/30/54d11c8ca027114898cab97421fb39e4ffd9ddf47cdbc44df2ec76722da9/markupsafe-3.0.4-cp315-cp315-android_24_arm64_v8a.whl", hash = "sha256:737c9c3981998eba27f11786f84fddcbabc74068b72a4a1f454ea02094b57b65", size = 12791, upload-time = "2026-10-02T23:06:10.485Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/10/6d/97c913e253a14bd3cd0e15a5c56d13203b823fa7ee32498342896a072dc4/markupsafe-3.0.4-cp315-cp315-android_24_x86_64.whl", hash = "sha256:489505b03f692c3f376394e49194fa7a7f9e8558d6e293a7056a0032b0c38163", size = 12527, upload-time = "2026-10-02T23:06:11.834Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/26/f9/b86d032042a4d597d9e1997f0e5f63a3eedaf11258e0a05760b0a0a826ea/markupsafe-3.0.4-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:077293e425f28ec737dbcad442a71752e28f8ae27cde3d68acd1fb212091cd92", size = 11602, upload-time = "2026-10-02T23:06:13.122Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/f2/dc/73c14c1eedf0ac5fa3292ba43435e6c49d2c2050f33cebde541f8f4807f1/markupsafe-3.0.4-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:9348cbb300d224fe3b89793262cb093504d4ae927004468463f745188a193e4a", size = 11817, upload-time = "2026-10-02T23:06:14.227Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/8f/69/2c2fcaa5fcee22d72c7819c0d536fd181c74a688e6143845419579cd2863/markupsafe-3.0.4-cp315-cp315-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:b807e598953730f82e4eae3bd30f6a122cf6b31c398c6b504c0e04c13c170429", size = 11446, upload-time = "2026-10-02T23:06:15.574Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/88/54/9e5ec76c62e6e2834d5a93623018c943e8b3bb41d663e3fd4c03303b9b85/markupsafe-3.0.4-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:799c39bdf5e2f1292fedd3009f7b3c9e760f10b2420cb9638d56920840ff6db8", size = 11719, upload-time = "2026-10-02T23:06:16.701Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/96/24/3ec292b44064c16229e064d770b2625bd8ea941aa61f44905a9fa44942c0/markupsafe-3.0.4-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:ae9dcb8fbe244cb82f8a6458b455b927a03685e383d9bacf1ea5ce180b96dc97", size = 12075, upload-time = "2026-10-02T23:06:17.855Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/aa/85/b64fdb1f304848518742136983c24e96d967bfb59a0ea160e92736901ab0/markupsafe-3.0.4-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bced6e2a6dba6a28f7dd3c6ce14df1b2dd495923f16ea484cad03decd463b2b", size = 24656, upload-time = "2026-10-02T23:06:18.963Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/9c/18/23997d4c65b355da6390d61cd56e0ab3befd6ba8dda25cb40c602bd0fa6b/markupsafe-3.0.4-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3882fb412298575bae3b9c46868251f15cc69307359f87bb1b382e53d6e5a2c9", size = 27737, upload-time = "2026-10-02T23:06:20.117Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/d4/36/35998dead3c6af88c38265a56e58100211f036234ab88eb2283fd4cbce44/markupsafe-3.0.4-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04e7902ba80ee4bac1d50a549606527a1dcf0476cd81403db41099d3b60ec653", size = 25003, upload-time = "2026-10-02T23:06:21.284Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/82/96/ef49135ce260db4ca4a12b119ed468449cd248db6b1468e2112b546d7a2e/markupsafe-3.0.4-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:925f929d6b59a8b3f8b8c6ac363cd0af7eecc81efb3071770b3c6717c450a369", size = 23111, upload-time = "2026-10-02T23:06:22.524Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/50/7d/83126e338bd88c17a220668235368ad719fd4638e426739858cbb8508f77/markupsafe-3.0.4-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f68edfc67aabac33708941f26f22a7b8e9f81429bc0cf249fcf7d66b23af8d19", size = 22403, upload-time = "2026-10-02T23:06:23.785Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/83/dd/daf7e420de23c8206c365204e7b85e1251d8e19d34196a56336f316e5ed2/markupsafe-3.0.4-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:e5c802729725bd07e2bc3ab7b76dc7e0bbfc53129d8f1eb1c002c24cf774717e", size = 24083, upload-time = "2026-10-02T23:06:25.037Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/19/3c/11eecdc06bc44ad5570350085b572ebf049e8f9a38d1ece6d76640b739cd/markupsafe-3.0.4-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:55ffd6ce583d97dc71dc92e930324c8c0d25aea7e3ade6ae54ef77cedb096811", size = 22931, upload-time = "2026-10-02T23:06:26.328Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/0d/9e/ac0fd77f2a726e56ecc3ca0235d095feace1358d1b822406c2a2ef26a4dc/markupsafe-3.0.4-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:2cb3dd71fc6be918ad4264346a8ed69485f9b7ed7bf35495d8e22807cd6b8bea", size = 24299, upload-time = "2026-10-02T23:06:27.742Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/d7/09/c6bd842ad58ff5b3bc76eeed7e9a42a6f11adc5d090ec697b72c9672731e/markupsafe-3.0.4-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:94f5407f7bc64fa6463906b896f9904beeeb7dd8dc116ee8e9056c8714ff9916", size = 21948, upload-time = "2026-10-02T23:06:29.274Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/a3/46/82f586711fed61e86faa1ee1bc317d68cd45a10c8bdbe3f7d1fdf9026ad8/markupsafe-3.0.4-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:2dad610540cb2e6272855c178f08ae9a1c7ac258a7fb71660553a5f104b42741", size = 23150, upload-time = "2026-10-02T23:06:30.583Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/19/2d/2dfdce99318abbfa26925195fbc17db188c46a1ec6457be121b6f9cfeb42/markupsafe-3.0.4-cp315-cp315-win32.whl", hash = "sha256:03470d1a8268e692ecf79ecd565593e59d44219377a7ead61f1f1b94c1f7ff6b", size = 14269, upload-time = "2026-10-02T23:06:31.949Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/5b/ec/6000fd82e8791e58fcd0456ec20f098957e2b03d5ed02eb73241a577c0ba/markupsafe-3.0.4-cp315-cp315-win_amd64.whl", hash = "sha256:d882a373d8093c2941e01291b7ced96e9cbe4781da9a7751ca7e6c70385e5214", size = 14496, upload-time = "2026-10-02T23:06:33.258Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/bc/66/e73bd5016421d5d6e2fb6de7dd609f9de020942ac8c626526bd8c6eeaf82/markupsafe-3.0.4-cp315-cp315-win_arm64.whl", hash = "sha256:353bd63081912ab8cfa6a0c7d185934cdf8426f04c618bba6bc4b394f2069b67", size = 14361, upload-time = "2026-10-02T23:06:34.539Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/90/df/cb8c3dc98d313a951df2f8968f44e4cb5643df6d3cab749a530ce2f7d972/markupsafe-3.0.4-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:c61750fadcd119d0825bcb7d7d675dd264dcc89cc05292aab5be68ebdbb374ad", size = 11760, upload-time = "2026-10-02T23:06:35.807Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/d6/bb/4af9b3ca0753d654ac75f9531d5bd741bb77ca6e696f36807c475ffc099a/markupsafe-3.0.4-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:1c0df495a977d10460a94941799c72d5b5ab03d3858d949b55b5a66c8f371c99", size = 12108, upload-time = "2026-10-02T23:06:37.089Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/3f/d4/b56429313aee5fd59b079c3df5615299959e25e7113eb6d8caadbdd7d38a/markupsafe-3.0.4-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02fa4acbc6a3fc5c693c34d4dd8c1130b7fe99cc915181b0ddd6f72aeb296002", size = 25762, upload-time = "2026-10-02T23:06:38.419Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/65/f5/34c181e891aa4f7d59c918584672e0c5eb7fffe76c1387d1246008bf4081/markupsafe-3.0.4-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:05295589e619b9bed252a86b532b8e27350abc372d18ba89b59375325e91ec1e", size = 28400, upload-time = "2026-10-02T23:06:39.819Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/ce/b5/ad14694fd0ac9a5ce30bc6498f2999378f418583dd1679cca5a1b512957e/markupsafe-3.0.4-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:be6cb0c799abb0e2ba3e618e6d28ddddf7e485f6c2ce938dfa237daf3905072c", size = 25946, upload-time = "2026-10-02T23:06:41.381Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/d6/a8/26b606445387d0ceb1eb1f21840094b84e4e3c3c3983d80d10b89823b490/markupsafe-3.0.4-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:26e9867520db70d37f7fb421a7f0d8adb40171011fb84ce869afa1a83370dfa8", size = 23634, upload-time = "2026-10-02T23:06:42.748Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/39/a2/b8814de672f1f0094d498bf646f2fec9d6356b503d28ef500b71c5095377/markupsafe-3.0.4-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f03460ff076f70ab595bb45a0205ccea1971443575b6920c52e755dec2b3fbfe", size = 23488, upload-time = "2026-10-02T23:06:44.176Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/db/c7/287223376fb73335a3cc5d6eb22c6ab01358cf33945a9c39c06b9dac3f4b/markupsafe-3.0.4-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:436e3ffc6310d3c41878c601db29098102fe5d8a467c49da4a4125254e0980f2", size = 25037, upload-time = "2026-10-02T23:06:45.646Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/f9/29/4df8355e313426d19e62ba33e0253c009ca12a0894ee77d67fa67255361c/markupsafe-3.0.4-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:4e2c4809c14559aa7ef426f27fb35afbb38104c349a903bf8f3600456764bb38", size = 23543, upload-time = "2026-10-02T23:06:47.264Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/71/e5/8377731e8495668dcc768f645e717df18318c841edaf023a99395f6da9b4/markupsafe-3.0.4-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:da2af0d7aebfc2074080d72efa6ab8317c62481ef1f896f65d9999c1c01f4494", size = 25282, upload-time = "2026-10-02T23:06:48.795Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/ed/5f/373456e37ceb1478d657d6fe769cbe0a39f0a8dfc1548eeb19c471eefdd9/markupsafe-3.0.4-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:aa2c838cc024642cc04c6854232f32b43e5e22833dd11119c1766c7873b8370d", size = 22993, upload-time = "2026-10-02T23:06:50.31Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/d7/93/2cbd5628435afb6f541bbaced4bce0c2edac4b09a142e6e928b8b0da9858/markupsafe-3.0.4-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:b91cc9d336957239ff200f30097e6fea2dc6d6fb3c81e853eaa09eac904fd894", size = 23712, upload-time = "2026-10-02T23:06:51.759Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/81/99/157e10966b033b363aeda5263e82596ee232a0b1d082fdbf90aa417ff083/markupsafe-3.0.4-cp315-cp315t-win32.whl", hash = "sha256:e49fb0d1ce92cfa0cb198cc5b1b11cdf9d0638658e2a2db2687e39db7c87fc78", size = 14300, upload-time = "2026-10-02T23:06:53.241Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/33/05/55884815414c9706a23deca150b72c25a62109e65b0b6ce232077802c719/markupsafe-3.0.4-cp315-cp315t-win_amd64.whl", hash = "sha256:4f6e0852a0283b1b1fd776eeb7b766a5f440b3e2bd31ab51af3b400585f3965c", size = 14522, upload-time = "2026-10-02T23:06:54.729Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/92/f9/ecbde7149e95b8a0f18e16d5d747f7dc06049d5da2e4f77f6f5e4a1f46a8/markupsafe-3.0.4-cp315-cp315t-win_arm64.whl", hash = "sha256:39dbacefc411633db5b4378b066a9aca70a3d7e2922c9e578d825f844026eeba", size = 14417, upload-time = "2026-10-02T23:06:56.246Z"}
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -584,11 +630,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
sdist = {url = "https://files.pythonhosted.org/packages/17/c8/721b3855fe457da514fe249247d404b9b39c5d16532278f70ebaa6acf18b/platformdirs-4.12.2.tar.gz", hash = "sha256:eab5f70271a490ef74618bb314fbb86e3c7e82fa3b9c922c2ea0e0a1a155d329", size = 59353, upload-time = "2026-09-29T18:27:56.009Z"}
|
||||
sdist = {url = "https://files.pythonhosted.org/packages/90/a1/d5f9002a70298c64a789779077d8dd90c10aa1f47fe40c86802df874f2a6/platformdirs-4.12.4.tar.gz", hash = "sha256:63743c02414e755de4e31b8f68125c1407495b86c5a006e203c01ff8b9924250", size = 60010, upload-time = "2026-10-07T23:30:32.426Z"}
|
||||
source = {registry = "https://pypi.org/simple"}
|
||||
version = "4.12.2"
|
||||
version = "4.12.4"
|
||||
wheels = [
|
||||
{url = "https://files.pythonhosted.org/packages/d0/89/446044f33aba0348d35e433f56d12206d010a5281a1df54054d4cfb82388/platformdirs-4.12.2-py3-none-any.whl", hash = "sha256:29dbf06d96c500bc6bdbce75fb0a14d63279c93b1842f97e72a135b33e856983", size = 32457, upload-time = "2026-09-29T18:27:54.43Z"}
|
||||
{url = "https://files.pythonhosted.org/packages/f4/ba/223e00b885e960edd5d4b4d178c88acce019bd5b83786093681b8c393492/platformdirs-4.12.4-py3-none-any.whl", hash = "sha256:78bfb9db2a8471ed7eebe3c3c932da413911042994e699b384fbb4493fa872d7", size = 32596, upload-time = "2026-10-07T23:30:30.825Z"}
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -623,41 +669,46 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pycparser"
|
||||
sdist = {url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z"}
|
||||
sdist = {url = "https://files.pythonhosted.org/packages/da/a8/c5fdbeee588bb8ada9458774f43adf1bdd30bd59157055142183e769a024/pycparser-3.11.tar.gz", hash = "sha256:d875f09c3507d00e1aba0eecc6dcadc1352f30fff09dc6bff2f1c2935e97c2bc", size = 113796, upload-time = "2026-10-09T12:56:59.539Z"}
|
||||
source = {registry = "https://pypi.org/simple"}
|
||||
version = "3.0"
|
||||
version = "3.11"
|
||||
wheels = [
|
||||
{url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z"}
|
||||
{url = "https://files.pythonhosted.org/packages/90/11/0e6f11117525ff0eec40ebac3d313376f102df93ca44ad9e893ee85e4f89/pycparser-3.11-py3-none-any.whl", hash = "sha256:51d5a8ba2be0bbe440b99d2112604c95bbbc3c2748a64260186c541e1729cd80", size = 51178, upload-time = "2026-10-09T12:56:58.131Z"}
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pycryptodomex"
|
||||
sdist = {url = "https://files.pythonhosted.org/packages/c9/85/e24bf90972a30b0fcd16c73009add1d7d7cd9140c2498a68252028899e41/pycryptodomex-3.23.0.tar.gz", hash = "sha256:71909758f010c82bc99b0abf4ea12012c98962fbf0583c2164f8b84533c2e4da", size = 4922157, upload-time = "2025-05-17T17:23:41.434Z"}
|
||||
sdist = {url = "https://files.pythonhosted.org/packages/4c/25/214ea825a9031f5af2c8b2506ee16701a2560d4712165dd00098dd527bcb/pycryptodomex-3.24.0.tar.gz", hash = "sha256:0428f19f13452c6b89bbaf2c530f84f369873811dfa77f0cee0da4f40fb0474f", size = 4933136, upload-time = "2026-10-04T17:41:08.988Z"}
|
||||
source = {registry = "https://pypi.org/simple"}
|
||||
version = "3.23.0"
|
||||
version = "3.24.0"
|
||||
wheels = [
|
||||
{url = "https://files.pythonhosted.org/packages/2e/00/10edb04777069a42490a38c137099d4b17ba6e36a4e6e28bdc7470e9e853/pycryptodomex-3.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:7b37e08e3871efe2187bc1fd9320cc81d87caf19816c648f24443483005ff886", size = 2498764, upload-time = "2025-05-17T17:22:21.453Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/6b/3f/2872a9c2d3a27eac094f9ceaa5a8a483b774ae69018040ea3240d5b11154/pycryptodomex-3.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:91979028227543010d7b2ba2471cf1d1e398b3f183cb105ac584df0c36dac28d", size = 1643012, upload-time = "2025-05-17T17:22:23.702Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/70/af/774c2e2b4f6570fbf6a4972161adbb183aeeaa1863bde31e8706f123bf92/pycryptodomex-3.23.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b8962204c47464d5c1c4038abeadd4514a133b28748bcd9fa5b6d62e3cec6fa", size = 2187643, upload-time = "2025-05-17T17:22:26.37Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/de/a3/71065b24cb889d537954cedc3ae5466af00a2cabcff8e29b73be047e9a19/pycryptodomex-3.23.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a33986a0066860f7fcf7c7bd2bc804fa90e434183645595ae7b33d01f3c91ed8", size = 2273762, upload-time = "2025-05-17T17:22:28.313Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/c9/0b/ff6f43b7fbef4d302c8b981fe58467b8871902cdc3eb28896b52421422cc/pycryptodomex-3.23.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7947ab8d589e3178da3d7cdeabe14f841b391e17046954f2fbcd941705762b5", size = 2313012, upload-time = "2025-05-17T17:22:30.57Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/02/de/9d4772c0506ab6da10b41159493657105d3f8bb5c53615d19452afc6b315/pycryptodomex-3.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c25e30a20e1b426e1f0fa00131c516f16e474204eee1139d1603e132acffc314", size = 2186856, upload-time = "2025-05-17T17:22:32.819Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/28/ad/8b30efcd6341707a234e5eba5493700a17852ca1ac7a75daa7945fcf6427/pycryptodomex-3.23.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:da4fa650cef02db88c2b98acc5434461e027dce0ae8c22dd5a69013eaf510006", size = 2347523, upload-time = "2025-05-17T17:22:35.386Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/0f/02/16868e9f655b7670dbb0ac4f2844145cbc42251f916fc35c414ad2359849/pycryptodomex-3.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:58b851b9effd0d072d4ca2e4542bf2a4abcf13c82a29fd2c93ce27ee2a2e9462", size = 2272825, upload-time = "2025-05-17T17:22:37.632Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/ca/18/4ca89ac737230b52ac8ffaca42f9c6f1fd07c81a6cd821e91af79db60632/pycryptodomex-3.23.0-cp313-cp313t-win32.whl", hash = "sha256:a9d446e844f08299236780f2efa9898c818fe7e02f17263866b8550c7d5fb328", size = 1772078, upload-time = "2025-05-17T17:22:40Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/73/34/13e01c322db027682e00986873eca803f11c56ade9ba5bbf3225841ea2d4/pycryptodomex-3.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:bc65bdd9fc8de7a35a74cab1c898cab391a4add33a8fe740bda00f5976ca4708", size = 1803656, upload-time = "2025-05-17T17:22:42.139Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/54/68/9504c8796b1805d58f4425002bcca20f12880e6fa4dc2fc9a668705c7a08/pycryptodomex-3.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:c885da45e70139464f082018ac527fdaad26f1657a99ee13eecdce0f0ca24ab4", size = 1707172, upload-time = "2025-05-17T17:22:44.704Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/dd/9c/1a8f35daa39784ed8adf93a694e7e5dc15c23c741bbda06e1d45f8979e9e/pycryptodomex-3.23.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:06698f957fe1ab229a99ba2defeeae1c09af185baa909a31a5d1f9d42b1aaed6", size = 2499240, upload-time = "2025-05-17T17:22:46.953Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/7a/62/f5221a191a97157d240cf6643747558759126c76ee92f29a3f4aee3197a5/pycryptodomex-3.23.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b2c2537863eccef2d41061e82a881dcabb04944c5c06c5aa7110b577cc487545", size = 1644042, upload-time = "2025-05-17T17:22:49.098Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/8c/fd/5a054543c8988d4ed7b612721d7e78a4b9bf36bc3c5ad45ef45c22d0060e/pycryptodomex-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:43c446e2ba8df8889e0e16f02211c25b4934898384c1ec1ec04d7889c0333587", size = 2186227, upload-time = "2025-05-17T17:22:51.139Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/c8/a9/8862616a85cf450d2822dbd4fff1fcaba90877907a6ff5bc2672cafe42f8/pycryptodomex-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f489c4765093fb60e2edafdf223397bc716491b2b69fe74367b70d6999257a5c", size = 2272578, upload-time = "2025-05-17T17:22:53.676Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/46/9f/bda9c49a7c1842820de674ab36c79f4fbeeee03f8ff0e4f3546c3889076b/pycryptodomex-3.23.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdc69d0d3d989a1029df0eed67cc5e8e5d968f3724f4519bd03e0ec68df7543c", size = 2312166, upload-time = "2025-05-17T17:22:56.585Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/03/cc/870b9bf8ca92866ca0186534801cf8d20554ad2a76ca959538041b7a7cf4/pycryptodomex-3.23.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6bbcb1dd0f646484939e142462d9e532482bc74475cecf9c4903d4e1cd21f003", size = 2185467, upload-time = "2025-05-17T17:22:59.237Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/96/e3/ce9348236d8e669fea5dd82a90e86be48b9c341210f44e25443162aba187/pycryptodomex-3.23.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:8a4fcd42ccb04c31268d1efeecfccfd1249612b4de6374205376b8f280321744", size = 2346104, upload-time = "2025-05-17T17:23:02.112Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/a5/e9/e869bcee87beb89040263c416a8a50204f7f7a83ac11897646c9e71e0daf/pycryptodomex-3.23.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:55ccbe27f049743a4caf4f4221b166560d3438d0b1e5ab929e07ae1702a4d6fd", size = 2271038, upload-time = "2025-05-17T17:23:04.872Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/8d/67/09ee8500dd22614af5fbaa51a4aee6e342b5fa8aecf0a6cb9cbf52fa6d45/pycryptodomex-3.23.0-cp37-abi3-win32.whl", hash = "sha256:189afbc87f0b9f158386bf051f720e20fa6145975f1e76369303d0f31d1a8d7c", size = 1771969, upload-time = "2025-05-17T17:23:07.115Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/69/96/11f36f71a865dd6df03716d33bd07a67e9d20f6b8d39820470b766af323c/pycryptodomex-3.23.0-cp37-abi3-win_amd64.whl", hash = "sha256:52e5ca58c3a0b0bd5e100a9fbc8015059b05cffc6c66ce9d98b4b45e023443b9", size = 1803124, upload-time = "2025-05-17T17:23:09.267Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/f9/93/45c1cdcbeb182ccd2e144c693eaa097763b08b38cded279f0053ed53c553/pycryptodomex-3.23.0-cp37-abi3-win_arm64.whl", hash = "sha256:02d87b80778c171445d67e23d1caef279bf4b25c3597050ccd2e13970b57fd51", size = 1707161, upload-time = "2025-05-17T17:23:11.414Z"}
|
||||
{url = "https://files.pythonhosted.org/packages/0f/52/5235086483b462600958549b27683ccffbb4941ba512934efc5a14b71a39/pycryptodomex-3.24.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:abf768c285d289c4a0e0678f055663162c9d6257c79f5904fbd2692ade94da1c", size = 2475900, upload-time = "2026-10-04T17:39:26.374Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/9e/cd/eeed4b1a1eda71a78defe2c04ff37909461f9c04cd4bde23011d4350cbfb/pycryptodomex-3.24.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c2f42f24d7112ebb93d3aed581d011c82c20101da7d7a6248145c530e99bd2eb", size = 1643488, upload-time = "2026-10-04T17:39:28.545Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/18/d2/22005d736858dddd3f7a5ddaf4a91dbb89292f0d9c503a9c13b1bb2ecfb8/pycryptodomex-3.24.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e42a2e9531c40315290278d9eee93ea5031eda3c83969f5709ada51a1c7491b4", size = 2194768, upload-time = "2026-10-04T17:39:31.713Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/19/c4/c350bb48207ae2a6cca02eb45a2dafc3f2d0196b5b33b556cc9b6815e8cc/pycryptodomex-3.24.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c7055400f773856cd219680d15525c6fb147237534cbe5394a5f92f57f7efaed", size = 2280612, upload-time = "2026-10-04T17:39:34.828Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/fe/56/539e213fbd572f8e95cdef5a46b0c327c7b2412107c3dba73913cfd20753/pycryptodomex-3.24.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e8a7463d346015b42a0fb9c9179ebb7cccfd86bf40199c6796b546ac7be0b85c", size = 2186291, upload-time = "2026-10-04T17:39:38.184Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/9b/f3/f8e73be610ef576c6ca98a78c8450a72f12c178bc89d3e94068e48115c97/pycryptodomex-3.24.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f96e10f0edff9d4b6b253e0a78f7c819749521d3ffe6af0139e08fcd35384405", size = 2278371, upload-time = "2026-10-04T17:39:41.539Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/16/bd/99c89964c5017488992f412f8d645923d8ef996c7f35fc76b7fe019620e1/pycryptodomex-3.24.0-cp313-cp313t-win32.whl", hash = "sha256:3b6276fe007464502df796710fb57dcfeb813b6ccac0eabaf85100be1ad358f2", size = 1792741, upload-time = "2026-10-04T17:39:44.173Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/ca/d5/5a1e469c7eab20c21090d396342894d2f0e25d429eb4df2d9174a03cb226/pycryptodomex-3.24.0-cp313-cp313t-win_amd64.whl", hash = "sha256:88d37b28b450c06a03bd5570467c601fe8c7dbe493ca39771716f56fdbe35d6e", size = 1825623, upload-time = "2026-10-04T17:39:46.422Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/1b/fb/1ca14c7e5318125aff23cd19886a62c8eb9015caa373b5834216c60260a5/pycryptodomex-3.24.0-cp313-cp313t-win_arm64.whl", hash = "sha256:e635c7ac056f2a47d79e2bf7042fb3fc6d175f47796a954388a3cec1f2e082a4", size = 1759392, upload-time = "2026-10-04T17:39:48.676Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/a0/dd/8f2d18986f752ceb245d4c3aeb0b13e9ed329d64434969981f0746b5b2ff/pycryptodomex-3.24.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:f6af77f15a91bb6c16607067cfd201330cf5dfefe1c715e955be1fb06d368613", size = 2476818, upload-time = "2026-10-04T17:39:51.311Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/8a/64/6bc2394a249d1c7ba330d192e4bf22d7e5a8caf28ef1dd346b8b847b91b3/pycryptodomex-3.24.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:582efdf767d72c12538949b876a64b9a7af30f2cff063c06dd63ece32c4e5c1d", size = 1645659, upload-time = "2026-10-04T17:39:53.936Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/46/36/f720afe32331ebf200bc37d335120f74da371969722d3c188fac757a8a71/pycryptodomex-3.24.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5be79564e05ed620a63f353ea50e0d474eb598f70cda1b28c2ca8e9e2f02c139", size = 2194769, upload-time = "2026-10-04T17:39:57.357Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/05/ac/d0c3b629a9e91c9dee019fa59cdf9617b84b0d11909541e1d6c79b24637c/pycryptodomex-3.24.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb393480b5845ef4acbc9f713e4c473ad08be81bc3cdb5ef017cd02bdafd36e1", size = 2280609, upload-time = "2026-10-04T17:39:59.915Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/5f/0e/e64276b7f3dbe9aeabd9c80dfff10bc97740ad8e24892eb6b0e3cbdcd549/pycryptodomex-3.24.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c7b8ad51baf3505dd3d94ec974855359d15769e45c58c2b89c6d4612b1f7ed94", size = 2186290, upload-time = "2026-10-04T17:40:02.856Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/42/2f/b72cc71bff456971e24e886f1824d2efef7b25fbd806eb821b107f8253e6/pycryptodomex-3.24.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7ffcedf4210d8c206fe9852ccde9f78ec9ce35e8039f0a989900ffe710e7a5db", size = 2278371, upload-time = "2026-10-04T17:40:06.135Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/70/b0/9d0f0d4d6236f986c942d2933d4a5cbc209ebac1ea7d4c74b6e306efb439/pycryptodomex-3.24.0-cp314-cp314t-win32.whl", hash = "sha256:8d6c5e01e4fb1ddd20cc24d4d8706643dd78b5279453dabcf10ba4e0683bf9b2", size = 1808717, upload-time = "2026-10-04T17:40:09.172Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/8f/99/55c75d5c8c0e3c966c6f639e3b2556c54b0172501f7377cff2aa570447c7/pycryptodomex-3.24.0-cp314-cp314t-win_amd64.whl", hash = "sha256:c55e6c42f9eb43d90c15792bf69bc09cb3e13a5e55a30eecb555695a02b0d4c0", size = 1842528, upload-time = "2026-10-04T17:40:12.033Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/18/3d/2cdbda0eacc9875c5db5cede94389f8ca4a6a4bccbbbc74f47f8fa4f0c6c/pycryptodomex-3.24.0-cp314-cp314t-win_arm64.whl", hash = "sha256:6a62122fb4c85befcc01ec3a1ae5b2ba81689481aba1d383b4e21e34f18900dd", size = 1777885, upload-time = "2026-10-04T17:40:14.361Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/bc/fd/b9186cea5e823f91f40904373cff30cdaea5dfe1863b4be0ed3bf51e4db6/pycryptodomex-3.24.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:ef2bd306328a8447d4d2af6037efddd67ef16630876ed3ec84babd16ca558941", size = 2476748, upload-time = "2026-10-04T17:40:17.326Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/65/c4/bf354a7c435da08ba9905a302dce8fac57a33b2ab1e51c78542b698b4b89/pycryptodomex-3.24.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:42c8221e3a4ca04b070cb388fca004815f6544951bfb2cc6c7958e293b7caf64", size = 1644339, upload-time = "2026-10-04T17:40:19.872Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/7b/ea/158d0ea838c92ba5d5081ce0f08e4af40d7278f7609ca007fca4d010aa4d/pycryptodomex-3.24.0-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:da9996407d03684221a4e1603b70fad97d8772185e5f75addcd97fcbc4c3281f", size = 2193232, upload-time = "2026-10-04T17:40:22.957Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/c8/9f/376b705a536c65fe41806babe5cdcfb076273a0d72dfbbb92073d3d9b10c/pycryptodomex-3.24.0-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:556e063b52f338bd924fe5c40692cc1f50cb0453bd1d6580b7ffff2eade1a894", size = 2279395, upload-time = "2026-10-04T17:40:25.777Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/d4/72/a0726c7165ba84d66923c3e5d5411c5abec9a9a4c1759a664e5cf8806a79/pycryptodomex-3.24.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b6690bfae043c81108e28e31dc7e3afa8cb1b1eb0ba590fdbff2b66e4c6913e7", size = 2184779, upload-time = "2026-10-04T17:40:28.813Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/39/51/c5b9d8fc84d5cf67091a007b476e24f634252fb314472e27a58f3bd97458/pycryptodomex-3.24.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5fa46af1281be707e0ec5878aea0f77d58d8d4b465f63f7385a71ae0080bfff9", size = 2276914, upload-time = "2026-10-04T17:40:31.23Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/c4/28/26f6d1b4257911bdab1c66c2a80e1193131bd8c02fa9ac04453cd45233c1/pycryptodomex-3.24.0-cp37-abi3-win32.whl", hash = "sha256:fab40542a863cc142a763aa92630d613925fe3e937bea3ba5fb735e0c42d4f96", size = 1792631, upload-time = "2026-10-04T17:40:33.61Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/4d/91/fa406bbeea093b53d314dfb0fd186658a69a398b73a3a609636b8402b5c3/pycryptodomex-3.24.0-cp37-abi3-win_amd64.whl", hash = "sha256:55f22d72250d7923aac3c2442ec6a1dd8774d2efa3bdcb0f68d5e137d372268e", size = 1825514, upload-time = "2026-10-04T17:40:36.059Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/10/b0/c53c7ac97c5deb721aa4f940309802c71ff5ac2fdeac473ce96754c69e5b/pycryptodomex-3.24.0-cp37-abi3-win_arm64.whl", hash = "sha256:908f92fbc307b642b65580d0295698f423b057018d87ae43d441f1d1a00674a2", size = 1759397, upload-time = "2026-10-04T17:40:38.641Z"}
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -668,11 +719,11 @@ dependencies = [
|
||||
{name = "typing-inspection"}
|
||||
]
|
||||
name = "pydantic"
|
||||
sdist = {url = "https://files.pythonhosted.org/packages/53/ef/fc4f868f4e2cee79f863883abffceff107875f569b848507319842d2a681/pydantic-2.13.5.tar.gz", hash = "sha256:51a9c5f7b2f8e636f04c6cada605d9b6a3bf1348fdf945a3d8869b19bba0ee08", size = 845750, upload-time = "2026-08-28T14:04:00.916Z"}
|
||||
sdist = {url = "https://files.pythonhosted.org/packages/6b/fb/6e44b63b26efea1cec48c26d8362313310202ef5ed6e7a52f1669e64e2cd/pydantic-2.14.0.tar.gz", hash = "sha256:8a51a7aaddd60f55566d1f07bdd87b92b463903f39a8f26b71a06314cd1548ae", size = 898613, upload-time = "2026-10-08T14:34:48.341Z"}
|
||||
source = {registry = "https://pypi.org/simple"}
|
||||
version = "2.13.5"
|
||||
version = "2.14.0"
|
||||
wheels = [
|
||||
{url = "https://files.pythonhosted.org/packages/eb/47/c95ffc2009878c7aac0c5e08528022dcb885933252a88b5f170058014464/pydantic-2.13.5-py3-none-any.whl", hash = "sha256:346a034f080da3755d8e9cb5e00e8b07de1d39e4f6e2c87d8ab7cafa0b269a73", size = 472589, upload-time = "2026-08-28T14:03:59.136Z"}
|
||||
{url = "https://files.pythonhosted.org/packages/2d/eb/9146591cc819d040475bf7f2be786710c7f7eb8083693bf859728da2ca9c/pydantic-2.14.0-py3-none-any.whl", hash = "sha256:15fab1bea6f1dc5003b54fc2ecab230c1fd1dbade2acd4addc52d81e32416d4b", size = 487109, upload-time = "2026-10-08T14:34:46.864Z"}
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -680,55 +731,86 @@ dependencies = [
|
||||
{name = "typing-extensions"}
|
||||
]
|
||||
name = "pydantic-core"
|
||||
sdist = {url = "https://files.pythonhosted.org/packages/af/f9/8a06bea35ef8daf588f707784c973a7046e0034c8d8cfb08828eeffb8b75/pydantic_core-2.46.5.tar.gz", hash = "sha256:10416c15b8839ecc4ef4d0885da76da6fd0f67333a0eb8aff6d93c4b8f2910fc", size = 472262, upload-time = "2026-08-28T10:01:31.677Z"}
|
||||
sdist = {url = "https://files.pythonhosted.org/packages/e6/6d/196e8c819e0e934f35a1a33b3530396feadb0af4ca38fe9f995249e55794/pydantic_core-2.50.0.tar.gz", hash = "sha256:84d2d38f7d163c4dec292f379e9de1960c661795442aca6c90d706436cb3749e", size = 513355, upload-time = "2026-10-08T14:30:58.245Z"}
|
||||
source = {registry = "https://pypi.org/simple"}
|
||||
version = "2.46.5"
|
||||
version = "2.50.0"
|
||||
wheels = [
|
||||
{url = "https://files.pythonhosted.org/packages/f5/37/5abe39a8372a61d3dc3c1338fc504281c01b32fdb3169cd7187153b56d3e/pydantic_core-2.46.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:b7ca9034437b6022f941f4857459562ee00a560b97e7cce8a0ec5a74fc6766e0", size = 2075885, upload-time = "2026-08-28T09:58:47.856Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/21/43/6323b1f8b217780454c61304bcd2b38ae4762f50754414124603ccc90bb2/pydantic_core-2.46.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f332f0e72a5a0400141f830744e141bf9f97917878dbe968669e8a7fefea78ff", size = 1922768, upload-time = "2026-08-28T09:58:49.58Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/0f/a3/c05ca796e1197618a774b01e596aeedfefc2f7d8c01ae3054e910b120e8a/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:193375f3548919d3f0b60936ca113ada3e38f264f91b9b8e0508efaad57be931", size = 1951241, upload-time = "2026-08-28T09:58:51.511Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/68/32/33bc39ac705c52cffc908e8389f9754fdb208aea5c69cceddf4eb3ce99af/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:79bdfa52f843137045b2d081cc05c120ba6665d29b7559c2c47690906f39279f", size = 2031975, upload-time = "2026-08-28T09:58:53.166Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/b0/70/2333e885c0f6a67bc105c5916965dac9b57f2718ee20d81d1a06a4ebdc13/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:24922243639cbdac66c75fcb6fd6495a9cb52b213d62f9a0d16f0310b1ff8038", size = 2208542, upload-time = "2026-08-28T09:58:55.017Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/f7/ea/296debfb4264207bbda5936133892e027c0a58875ad53ebd512fba8ec3a2/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c76fe65e607be28c7fd4d56fc3c42b1583aa058ce3408b7ad0fd540171d31f9f", size = 2264692, upload-time = "2026-08-28T09:58:56.767Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/d3/f2/9e4de77a6271e07a76d2d58b11c091a979c191ed2939bf80067568b369d2/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f7b393a8b3da82f5c1fc0751e6d01ac6c55b93c18226a60bdfba4a724efafd1", size = 2066633, upload-time = "2026-08-28T09:58:58.531Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/8d/db/f9e9d0c97445987b2084823d5c240de88087338f04fc2cfaa2df186b8049/pydantic_core-2.46.5-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:7ac031912d54f3d83ef3b3eb98dfabc1608802e2202263d25957eeed40b94761", size = 2105235, upload-time = "2026-08-28T09:59:00.421Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/07/c5/79169b047b3b2c3e99e04bc76372af9637e0bf6db638274fa927df96369e/pydantic_core-2.46.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:837b396ca3d7b74091ca623f6cbd8351bd42d670a79c2683e79fb089f06a2de5", size = 2157367, upload-time = "2026-08-28T09:59:02.442Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/26/b5/ba6057afb7c291bd449f51b867f95aef2072941c4ce4e5c31d6ffd132d3b/pydantic_core-2.46.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:5ee239d575f80b08eca11f6e20f90c4c695de7825c67eefe6091fbf20dda648e", size = 2158420, upload-time = "2026-08-28T09:59:04.2Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/6e/28/2057abecaafdc22912afa819603a51f0a62d40643b7c4871c51721fea9be/pydantic_core-2.46.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:e80675d75ae2cd14372cb65cad5400d9347a3d3f6c13000183f22dfd027283ed", size = 2309588, upload-time = "2026-08-28T09:59:06.048Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/71/9d/881156dc404e27479c4246128d73538464cab4a239bec61995e227644c30/pydantic_core-2.46.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:9c4b71f10dd532fb7a5cbc8f58707779e64f03a258c2bf8bfbaecfcd9970b519", size = 2341866, upload-time = "2026-08-28T09:59:08.539Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/5a/38/d66f443a259f84d13babdceae568e572b0ed26da17ca5d0a649ebb110a67/pydantic_core-2.46.5-cp313-cp313-win32.whl", hash = "sha256:97bf8de4d541598c94a59344eeb988a94c08ff76b5723c41f6567ec18c7892ea", size = 1938580, upload-time = "2026-08-28T09:59:10.402Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/2c/1e/1d5371213f4cc9a7ed70c0bfcc7911de22311ee99a662a56077d7292d2ac/pydantic_core-2.46.5-cp313-cp313-win_amd64.whl", hash = "sha256:15f4a94963c95accac15b7b657bb177d3ad82bb90b0d0526d9a9b85079925db5", size = 2041980, upload-time = "2026-08-28T09:59:12.396Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/5a/48/4222d90b1c67568bace4dec6dca6271449c66de3595d72b6d098f5fde597/pydantic_core-2.46.5-cp313-cp313-win_arm64.whl", hash = "sha256:d22a945598fb91236b4dd793a6e42e4f3dd7740bb5aace5ebd7d4c08d13bb575", size = 1997213, upload-time = "2026-08-28T09:59:14.245Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/8e/8a/14596f2a8367da50cf7cbac48169ee5d9c8e11d486a3b527082384630c72/pydantic_core-2.46.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c1c43ad4339643d70ebb8124e1305a7dab423001eff58bb41a0f731adbc98355", size = 2074081, upload-time = "2026-08-28T09:59:16.141Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/ae/d5/d8a4eb6d6c7f66b91dd37c576d76e9e60fba900caf5372c17bcf949febc2/pydantic_core-2.46.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1a353f84de772f423b5ffb11d7ae352fbbef0f446f3c0b0af0f8236d7233606e", size = 1920497, upload-time = "2026-08-28T09:59:18.065Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/8e/26/092079428f86e927e030b2c0ced87df69dbb1c875cdeaa67bf42ea2be746/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5086029a57366b8cf81b130a43908738095c270c21a8d7f0e8bdfdb89718e2f3", size = 1952130, upload-time = "2026-08-28T09:59:20.476Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/08/c3/8ec0e290a9ebaebd64047bf5fda94be835c6b1551b02437e4b76778fbcd7/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46c25dda9d092a06c08db76ffe0a197107904d0dfac653f7d5306bbcd6d6119c", size = 2026371, upload-time = "2026-08-28T09:59:22.227Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/01/72/4fd20ad520fb8da0157f95b27a7eb05a72790ef08138e7701ac972c342ea/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:37ea7b83c935e5b0d68c9449b82651accf78a10828b2c02b2f2d9e9496446c21", size = 2202822, upload-time = "2026-08-28T09:59:24.277Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/31/b0/d16e0771206b29314f0d52198b720be21e8a99ab2bf11e3bc0d7c9cebdff/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e64e88d5585bea9ce95861079de72006c7fa6d3df4e3a3b65ba31eb979c15c9f", size = 2262756, upload-time = "2026-08-28T09:59:26.608Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/2c/9b/59634b7ac631c63b2a37760eb6943af3e29573d6b59a4abc5e7f019d4cee/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54d510bac3ee52247af28ed4bb18a1e799f040ac60fd2bf5ccd4c92f1fbe786f", size = 2068352, upload-time = "2026-08-28T09:59:29.044Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/08/7c/570abb1ad2155348dc754ea91be22e5aaa18eb6d69a6068f7c6f2679a6ed/pydantic_core-2.46.5-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:a2a5e1d0ff29adddc9f6d6821a66302e4493f8ca898b715b6b1182c2c201ea0a", size = 2104777, upload-time = "2026-08-28T09:59:30.95Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/8e/25/5bf74adc65a1ac5b7be3f6cb0bcb5433615c1598a801c19d830d84c98ded/pydantic_core-2.46.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:03b9666e41e35d8909852ba191a0607520f81b74eaf12ccf8737005dbb313821", size = 2156312, upload-time = "2026-08-28T09:59:32.604Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/90/6a/2ef38830675e050121040618135564ed56b860b45433b02d9b4ebece46f3/pydantic_core-2.46.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:a91c17edf6eea2402cb5457b4c89e99bc5ed1004aa34c4adf1d4258c1a5c22c2", size = 2150067, upload-time = "2026-08-28T09:59:34.453Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/90/ef/a7dbb03a14a64c2a4621f989c615ed9a892535a6cad938fc27079f919d80/pydantic_core-2.46.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b49924c73a235e969511bf2aabdff3beebf9820931f646c80274d5d780010c47", size = 2304516, upload-time = "2026-08-28T09:59:36.194Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/68/f8/6bb4c4b80e8a6fde1904c64a51c62a1d04fcdfa3ea521a66b2ddefa1d885/pydantic_core-2.46.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:2cbd9a5eff05e51c447c34dfa4632145b26b09120cf04bd0c871e44c1a5e1c9a", size = 2335223, upload-time = "2026-08-28T09:59:37.931Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/2a/80/f46b8c681195190b2c1f1c7c0a81abce60663e987613e09ef64d433dd96b/pydantic_core-2.46.5-cp314-cp314-win32.whl", hash = "sha256:2d5d76654becf5efd62c9e51c3756c67b49498b0c9a40884934c40807adbd074", size = 1934827, upload-time = "2026-08-28T09:59:39.836Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/f7/3c/60674207246bc0a4009d2391b7c7251c7159f279c8d2ab8aae8ef46f3dee/pydantic_core-2.46.5-cp314-cp314-win_amd64.whl", hash = "sha256:fa10ef4112775900e7a0661068635eb67b2ab824fbde764de6e0e21982a93db0", size = 2042648, upload-time = "2026-08-28T09:59:41.792Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/69/0c/117c562c7c1babdf44576b72a5e496906506c93690387ecfbca7c729ae2e/pydantic_core-2.46.5-cp314-cp314-win_arm64.whl", hash = "sha256:045ab3b6d308439e32b81cc173bba5b9018bc6ed896afd0c65b3b009b1699af5", size = 1989652, upload-time = "2026-08-28T09:59:43.702Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/e8/66/9336ae58f9eb68c41d121894e52c4c89eccb07eb8f602a04ee9c3f37736a/pydantic_core-2.46.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8816f3d218beb4b787de5c9759c259b8fa61f9dec42dc7811f320a33771778b7", size = 2065829, upload-time = "2026-08-28T09:59:45.364Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/c5/02/bc19b47a96c2d3109760711acf22369e56bd7e405ca52f7ade164d2ead57/pydantic_core-2.46.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:bce57638e08ac148e5778cce7feb968307a727d66f8e2274a543d0cf0c9ad6a3", size = 1905716, upload-time = "2026-08-28T09:59:47.18Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/52/a4/70b47c0509923dd98ccfed04fb3e32ea3849c82a0ff2205bb41009b43c00/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:976e1128455aa595ea04c79ccfedff1aaeab96ee013fcc916bed120c4f0ad94f", size = 1934216, upload-time = "2026-08-28T09:59:49.241Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/52/ab/aa03b65f7bb198585edf806b906c3223ecf1795543e39e23aec4cce27ad2/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b891faeedeafba41b2983e5001a81b6a915b69544c7e7570d1989ce1c36ac7", size = 2010635, upload-time = "2026-08-28T09:59:51.692Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/3c/8b/0da06343f30b84ec549aafd309c6456223d5dc8bd36af504c573faad561d/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f194189415698233dd1114a093a9b56e61e2c57e11b469be3b0506f46f0771c", size = 2209369, upload-time = "2026-08-28T09:59:53.582Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/d6/5b/844c4defaa34a3df66eb9257087d121d70c201298b96abdf9f492fc2f1bf/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82a36973cf8a2ef5406f4fe2edbf8ed0c99629535d959e0b100c76a32535a111", size = 2253238, upload-time = "2026-08-28T09:59:55.484Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/f4/64/a4e536cb16d7f61a7fd3120b46c577fc7fa7325992f69c4f52bc786d77d8/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdbb78909f52b981d3b2d56b97328d71eb0b974c36bd77c920123a7ebb192829", size = 2065740, upload-time = "2026-08-28T09:59:58.038Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/5f/75/aaa38c6bc2d085f6605b34eabdc6a8a4e0b2e61fc9c8e6e52b28e97b3125/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:52e24eacdb536cade636aa90fb851835222becff8484b7001fdc78cb0290f2aa", size = 2087425, upload-time = "2026-08-28T09:59:59.898Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/55/ae/fcab4cfc39aba3689e1d20c8b5250ad280957022c09af2ed9cd585602a5e/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:37ae34309d7bd8c0d61ab839668058f2a7962ea1fc51d105d2db228fe0618034", size = 2139306, upload-time = "2026-08-28T10:00:03.057Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/2d/f4/f1d03a4bc9d9acbc62f4d742b8a319af52f71885079868b2ff8e48a651ee/pydantic_core-2.46.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:0cdbada856a1c69a7624a64d3d9aefe79300bd6ef827b43a4f265010b9b55184", size = 2144589, upload-time = "2026-08-28T10:00:05.645Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/83/f3/7a53bb1356de514a4cd295f25b6ac39237895620c0462d2592b76c16e114/pydantic_core-2.46.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:545f26c504b27c3758439a5e6d9349931f0a04f855668d5fe323c89e82300a38", size = 2288882, upload-time = "2026-08-28T10:00:07.931Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/cd/94/5a81583660c175c59d49ffb09f4b3a44debeaf86a19fca664ae1cdd9ee32/pydantic_core-2.46.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:ff218293c9c806138dca139765e3b067621be52bcd93cdc14c7711be7ddc90a9", size = 2335210, upload-time = "2026-08-28T10:00:10.177Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/5a/9f/5d685c2693b972d1a59c998586e8823712b66603aeff47ee60a4bdaafd37/pydantic_core-2.46.5-cp314-cp314t-win32.whl", hash = "sha256:97cf3eb53a8cccacf9d46686a0926186c9bfb5574f2ed66d3639d5fe117cd3a9", size = 1921180, upload-time = "2026-08-28T10:00:12.35Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/70/12/5c94ee16d65a37a15f9e869f5e6256df111154491173801a4c5e800ab548/pydantic_core-2.46.5-cp314-cp314t-win_amd64.whl", hash = "sha256:d2f9fc07a8042a8f95925b35c4f04f469707c981fc33245b6ca187cf5d2dd290", size = 2020515, upload-time = "2026-08-28T10:00:14.774Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/63/19/67830dda664e6bdf9285ee2e40f355d0d7d6b92aa0c42e8d217bb8d33d36/pydantic_core-2.46.5-cp314-cp314t-win_arm64.whl", hash = "sha256:acf8a67ba51f4ca9ddbd0e6b3000a65ac51ab734661778b3e7ba64d99a710f2f", size = 1989276, upload-time = "2026-08-28T10:00:16.984Z"}
|
||||
{url = "https://files.pythonhosted.org/packages/81/25/f9a6958f73d92f66d620e3e1b091becf6b7a2ea89437118d397e2c6ca9ba/pydantic_core-2.50.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:049b0404792dcb942f1092bfdae5f819ef30445b0d174782e1909fbdd91bb48b", size = 2222904, upload-time = "2026-10-08T14:27:07.869Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/c5/41/7f299b2ecf0ddbec8c2a68057ed53d29458ea5b0850615570849c454dd89/pydantic_core-2.50.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8f16bc5bb12f4c581b0f40facc28dbf286db32d1bf4e7e4f4c4e0f7f4e34ecf9", size = 2024863, upload-time = "2026-10-08T14:27:09.78Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/89/db/a9852fa8780acca5dd81a21bb66d4b2fb41c39dc5672cec66c32cc7f13e9/pydantic_core-2.50.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1d084e92d0f4a096a5155b23d1ac603db8073ef98a3d1384badf1455e5ae742", size = 2047340, upload-time = "2026-10-08T14:27:11.85Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/90/82/cd174e776e71ebcb1d289a57cb3e89565123a84c5708865e12efa82313f9/pydantic_core-2.50.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f0af2d2f745ec00a6616c4dfba4477f9156ccfb45690f6ffa5fd34f42e871ed4", size = 2173461, upload-time = "2026-10-08T14:27:13.948Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/5a/5c/9c4b2aea09ec7b7af966e79631b1970f1adeb6a8ed001bc8bb64a2d1c23d/pydantic_core-2.50.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2f28a5a299d4cefd1066a6883d22600b9ae3606f881c3015d255784634647580", size = 2362629, upload-time = "2026-10-08T14:27:15.909Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/28/d9/93afd007b61c50425c3c8f0402425779341c8d498bd224e5788eaf419c4c/pydantic_core-2.50.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bdd70c2d9e73bca09fad54605dfeaae2b4e770b2800c65f5f5342901ed567b9f", size = 2440134, upload-time = "2026-10-08T14:27:17.716Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/c9/93/a4ef199535547aaa7c5d2cbf009a1d6a2b252885b14c6e70404e1c661716/pydantic_core-2.50.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e58acd43ac8d3905659c1d5309318dd576243e723dd7c3b5dd4f555479b77d4b", size = 2158480, upload-time = "2026-10-08T14:27:19.618Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/de/20/f216e028d3bfbd9f8c67f7a2f0f8db04240926f630e2e66a5a8ac45fe00d/pydantic_core-2.50.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:5a07c644047f5abc268b4c39f8cb5a30e08a3c349228cb70142c7d3ed87587c0", size = 2231837, upload-time = "2026-10-08T14:27:21.742Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/56/3f/57a6acf26e0acb82238ba4e25b35cbd6013b1d74eac99c4f358ff56bf6d6/pydantic_core-2.50.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8ad8dad549cd1645be591a50b4573995ee7e018f6619ddc2bc6ea44b2ad9f694", size = 2242240, upload-time = "2026-10-08T14:27:23.564Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/94/41/f5f4014b40f91db4479e87dc038ac48a460a60baadea7ff9c942bc64dc62/pydantic_core-2.50.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:1773001198030e16f946a7ff7760fc3ebd45b12150f66fd0f433780043dc13d8", size = 2277852, upload-time = "2026-10-08T14:27:25.517Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/f8/fa/485e4db093e10f29676fc696abf9d88f3626ee520e99cc488fb091222db9/pydantic_core-2.50.0-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:8ab9e74878172948e0426e3d27fba333fd6a1c3f9456d8e75643e8e6868ac1a1", size = 2450132, upload-time = "2026-10-08T14:27:27.493Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/d3/48/fd07063cfc65e498528cea59b27b979eb5c890a54dcf5bb19ab0a589516d/pydantic_core-2.50.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e15bb1535f68a27f28e579ba3a8f74e7b05d350c45f5622b76a311d5a19ae48a", size = 2477011, upload-time = "2026-10-08T14:27:29.972Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/fa/96/6f34c285dc87ed2fae5f1b53c51bb732eda21dc0c6c73d6a5d9dcc7033cd/pydantic_core-2.50.0-cp313-cp313-win32.whl", hash = "sha256:c21e6a6e4e6d32fb6acbc4f0fa69e8319cac0d65eeaa8298d757371cc2a9c687", size = 2102027, upload-time = "2026-10-08T14:27:32.497Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/5e/50/dbdb3ba6699d494e59db2f145aeb97990e28d1503bc9f7bdd57eb4c15677/pydantic_core-2.50.0-cp313-cp313-win_amd64.whl", hash = "sha256:4f96ccd9368ecbf6685d8f6981584ab72b4f0ffd20685e747737e7e340277b8d", size = 2169974, upload-time = "2026-10-08T14:27:34.692Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/7d/ae/cfe0e52a9b45b5ba12b3a839db928669543948760a059a72f13cf0346c29/pydantic_core-2.50.0-cp313-cp313-win_arm64.whl", hash = "sha256:a2b88f9f9fa52e1c34938ff1a18ee7fbffe482df0bb43c087a9a60578d273d68", size = 2162504, upload-time = "2026-10-08T14:27:36.989Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/59/d9/6dd838672e5ccddf01556bfd1b4a6767e0c75abf9aaa4092cb87a56dbc4a/pydantic_core-2.50.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:62a93a9d206a3580c975c3c1f65a869cd063844d016c004f8d786a9309b3c591", size = 2224496, upload-time = "2026-10-08T14:27:39.281Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/b3/20/c57d2efcc63fb8fb6ae9ec2818baf30e2a68751e8d1dedefaf3dab2e45fd/pydantic_core-2.50.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5a8403eef4a66743e339102fb3cdd8c8b9016b8bd67924685893d062c88896f9", size = 2023860, upload-time = "2026-10-08T14:27:41.332Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/26/c8/f44ea3f1b00288f715e2320cbcd0f604104325370ef4bee42c5ed2ab076e/pydantic_core-2.50.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8354fbbe2abf0fb724b9303bef43bfd9b7a2779332813fa6d6559983954e7d8", size = 2049144, upload-time = "2026-10-08T14:27:43.695Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/74/5c/2f5cf84ceaf6d7c351737124aebc135f50ce48a9dd28d2b5283103face54/pydantic_core-2.50.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:00963fde61cf8880d9e7b5635a9830e0591edfa45168447fdc5b47635c0f6437", size = 2173456, upload-time = "2026-10-08T14:27:45.792Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/97/de/dc0bd815a328e62b72bb93b5f3c762939670d4119fd27405b454970833da/pydantic_core-2.50.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1bbd7da16d8b2912cc56c9d0b85c4998a6cbf39b220f81c0d8c397c64672ae0e", size = 2365204, upload-time = "2026-10-08T14:27:48.117Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/ca/a5/458c4a29f52fdecb16b192fb27f117359da9c9c58401ebfd956637822dca/pydantic_core-2.50.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed557fa2744617eac3e34dd39b85037efbf23cd33f06851c00fdb8f18ad8f4c2", size = 2445007, upload-time = "2026-10-08T14:27:50.637Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/98/14/0c0e72e0663be91a456167a2f7ed49c65195b388e8f1e8b2bb5083b27458/pydantic_core-2.50.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59f816dc04e99627a5a6352ae51dfb30e603f2cb0b7009c91dc640c533def014", size = 2159911, upload-time = "2026-10-08T14:27:52.881Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/62/7f/64af6921e17ed04dda0e07ebbd394b29fb557d33618cb0f3f81ec59c0508/pydantic_core-2.50.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:779b6c74526596a86d38248dedaecfb7851bbaf319c234be042c57acddd2c8c4", size = 2235761, upload-time = "2026-10-08T14:27:55.194Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/82/11/b6ff9f7207af629094f4deac947d04be3812d5c95054010ad8f710a052f0/pydantic_core-2.50.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:21b62d45f327eb0802f842c132fda6d01a8376a8077922dc4dda69011c64d34a", size = 2243726, upload-time = "2026-10-08T14:27:57.336Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/8e/37/6f7101f3c746a1686987a32875ae2f7c19f518d783979cee1a8a32e0a9ef/pydantic_core-2.50.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:4f31af62efd1fd0257735b72e6716b32d4f207654adeabfe524d44baf1bb6bed", size = 2277158, upload-time = "2026-10-08T14:27:59.714Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/d2/74/67fe208f3ec7f5d8c47bf7cba70019a84b1cffad947ebc8caef14271b54f/pydantic_core-2.50.0-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:933f2d639eb81a3e1f145aec415453cc00983236629933f028d9507222583a2e", size = 2450059, upload-time = "2026-10-08T14:28:02.17Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/cd/10/4a9c56a69f5841bdd94ffff5876a12ad2733209d5b89cadb67638d69e7c7/pydantic_core-2.50.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:c05d035e72530f6b00297941b0162218601542b76870c3bf2756bd87f16fc538", size = 2479142, upload-time = "2026-10-08T14:28:04.494Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/bf/c1/30d36746051e42bb67df986ee874f787526edb99205f6b1583c33dd81202/pydantic_core-2.50.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:ab3f95f737fc1b258b8210308fc02ce1442cf5950cf6d30b09ad89ab9e8afebd", size = 1483929, upload-time = "2026-10-08T14:28:06.527Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/04/2c/c0e8949be4f99a74f02ae02d603bdd518c8a9eba72982a34d675134a792a/pydantic_core-2.50.0-cp314-cp314-win32.whl", hash = "sha256:f12d9690634414fc04b1a7072fdc35c34a9242232c1851fe4518383578bb09d4", size = 2104255, upload-time = "2026-10-08T14:28:08.729Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/ea/6f/a5a5baf99509c998e0f2335d66b815880c7d6c63231c8e4017b237d6837d/pydantic_core-2.50.0-cp314-cp314-win_amd64.whl", hash = "sha256:63263d64884688554fb025a3906c7b00573980cfee75f9afeb86239d577384bc", size = 2170939, upload-time = "2026-10-08T14:28:11.08Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/40/bc/c89b93b69d59cfe61c1e91b1cd3d87cc740e57327ecfef84ce34b7dcadac/pydantic_core-2.50.0-cp314-cp314-win_arm64.whl", hash = "sha256:753863dd4317ec8cc9eb3e6d9d01a8ef1a1726a8003b4354658d68a1ae05f9db", size = 2157035, upload-time = "2026-10-08T14:28:13.445Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/ff/5e/d0ad47a406c42db95876900f4bfc520313c28d07a0dd5080da4972220c27/pydantic_core-2.50.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:c4abc425789f8540e86ca4cfdbc8dc650433cc2ac7c6136fee9bcb29d5665a02", size = 2234007, upload-time = "2026-10-08T14:28:15.9Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/ae/f4/69a7ec8400c1e34e5e11ab967f850ffd8ca71bb3aa0a8db93df5912bb6f2/pydantic_core-2.50.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cf150693a51ca21e8288cd08a9de05e5ab331776dfcfd0537b14523338f0502a", size = 2000888, upload-time = "2026-10-08T14:28:17.983Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/d3/f1/860bb499f7cdf4bb453e8080af5bc182487f2f025d94baeee29c907bde6f/pydantic_core-2.50.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:158749408ee19682b8a2a7e7135cced7f41d6f2f9de96088b1b1609858a6a158", size = 2022761, upload-time = "2026-10-08T14:28:20.197Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/89/fd/f571420436e79b9f7cc8f8ec534dee1759c240b4dc086737608f14d05c93/pydantic_core-2.50.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dcbd1fe5083315243447c13f7252ae9fe9d128b1ae2857e3a916c609235dd863", size = 2192910, upload-time = "2026-10-08T14:28:22.502Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/cc/94/b47c4a01ea978a7a3d02551e3625f0344ae1352d67ea78011c2eca5e39e4/pydantic_core-2.50.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0fdeda6272d60b1f6fb63f6a3dade55e274af62e1514d24a6549a12150c385cc", size = 2390178, upload-time = "2026-10-08T14:28:24.861Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/77/61/d109f26b3ee4443cca220eaaa4f3fbcf78ff07aeb577becc37d9224db63f/pydantic_core-2.50.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8b16e164205a90b1050d2f5469f8a7829db7698ad198c69e6aab6cbfb5648b87", size = 2464453, upload-time = "2026-10-08T14:28:27.383Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/de/a4/7be2f608f6f8e7655155057a82a7e2f135ad9b37fcbadd34fdf11b1ade24/pydantic_core-2.50.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17a5ca9c197788a6424749a09a3dce824cb2c17b72f835f8a5e330935b973609", size = 2150682, upload-time = "2026-10-08T14:28:29.65Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/80/ef/a8d867f6d3981c232d0f3c2254458cdee8471de7bff3f7edd252cc00c30c/pydantic_core-2.50.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:01340f4fbb4f854b1f36a6fe9dcd2b26c8936aead4e4ad1205624ac025c875fc", size = 2245995, upload-time = "2026-10-08T14:28:31.934Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/9f/a6/7297a39c8814beab877be5ba4f594c2e166108cdd20c1f1c51fb71d5389e/pydantic_core-2.50.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:99e203d5c2a814facef78dcd993b0fb7a933124a3475a38979fc09cceae210b7", size = 2264090, upload-time = "2026-10-08T14:28:34.298Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/c3/65/634fc407eaf61abec0d015d6fd4fd8456c18c71358e984f60b0a05092d49/pydantic_core-2.50.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:2092ce156f92aff17e3345baba2b7c0c1701f32ba922c5de71bd6248fbe164e3", size = 2293708, upload-time = "2026-10-08T14:28:36.725Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/3a/04/b87fcf8062b8907c77769385a4340a814fe359660593dbbe4d8f3f7e9e61/pydantic_core-2.50.0-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:74cbb6cbd74445ca279668790e0c10eccac0428fd79fe061fce6c9e3982ad3fe", size = 2468464, upload-time = "2026-10-08T14:28:39.109Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/77/f4/d7aacec95f9e00dcbad84bee2fd0fc081ae292d380dc8a3268e4103de957/pydantic_core-2.50.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:42fff617cb0b08505d8123d71e6e7a8e54210d9007498f564855bd843ce984b1", size = 2511897, upload-time = "2026-10-08T14:28:41.681Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/a0/d8/0a755b0069f9a0f4c57f98fd05b524473952556406244649d632d25f8646/pydantic_core-2.50.0-cp314-cp314t-win32.whl", hash = "sha256:36f9ed6ae1069913e4f6e86d8233e119e83e00a20c54f88faf9c81292f2fecc0", size = 2098906, upload-time = "2026-10-08T14:28:44.218Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/27/4e/cd10a1fbd1ba1d730871e382ec2ac550487465c7c9be1a72b3f4f9181817/pydantic_core-2.50.0-cp314-cp314t-win_amd64.whl", hash = "sha256:980c81c2ec53ea9eb2227c14b3e6de35670b2de163b638f2a803e90a3bd5bbb0", size = 2148359, upload-time = "2026-10-08T14:28:46.47Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/b0/b1/a766eadfbc16b58d401f63dde3bf4e0943dcd2402011b6b8d9de2031ebe0/pydantic_core-2.50.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dec0dafc116ac29a84d143fdbc3b83fdb5d4ed339276be2251154537ab30e14d", size = 2175241, upload-time = "2026-10-08T14:28:48.934Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/ae/d8/43d0e765a80d6fc7d87b5f66a9023777aad6da7e7d64f486a4c2be8158b7/pydantic_core-2.50.0-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:af2b808a79bb04075e87c81a5b6179365b93f9a851f29dafd67abff72085d0c8", size = 2224750, upload-time = "2026-10-08T14:28:51.227Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/9c/12/07e047c21ad90f184c7e8bfc6c9966b9ca51d4698e1a06460b65ca2ce105/pydantic_core-2.50.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2918547195ffb20118b829fdb8938e9dc92c9527fe6cbe58572594c96362880e", size = 2023936, upload-time = "2026-10-08T14:28:53.978Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/d8/a9/fb26fdd343ead65a699245c9d2150e84c47496a9b250859214a90cceecc9/pydantic_core-2.50.0-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0393763d66f6f61715488d074a2cefac04aeb3ee281e36fb4925dd44deaf9e17", size = 2048872, upload-time = "2026-10-08T14:28:56.342Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/f8/4d/9fc4ea28a8ecc1b85fe77a616f8df9a51696476aaa93c5b256269d025044/pydantic_core-2.50.0-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:62ec6568896e0abf258cbcd22c406c1c8bf27d16224b21bec8f75c4ae88a8173", size = 2173752, upload-time = "2026-10-08T14:28:58.805Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/70/7e/74b55196339413d787b283c2568cca4679b0645e48229421228acfa8839f/pydantic_core-2.50.0-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ec786cb9d597dd75d993f8c1273e31bb2114c9bc22f67fba611e654e8347701b", size = 2365284, upload-time = "2026-10-08T14:29:01.434Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/ba/0e/4ebc7a851f0ef63fcaba205739c171e50dd183f8d79dcf0099c3b46aa60b/pydantic_core-2.50.0-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3fce74add1099da1ea09473268950270071fd56773e2968604efb3ab1d240e02", size = 2445279, upload-time = "2026-10-08T14:29:04.034Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/07/09/03d3524fc7d4960840e32fd88c8cf487b5516daa7b6845ffc74274e0247a/pydantic_core-2.50.0-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:243088c95e23b12db9f2cd7661d584a3f00814e087489f40cde7f9feac56b694", size = 2160424, upload-time = "2026-10-08T14:29:06.628Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/f6/b9/7c530c84049089033dd746700bfbb51520ed9376bc90f3dbbcb319880acf/pydantic_core-2.50.0-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:42a56b0052ac11d9d0d87b1c94a1ce52e31914fd133f269585e0f63a4ed988f2", size = 2236109, upload-time = "2026-10-08T14:29:09.053Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/0a/08/70e07379ebc2538c22f8c372a37c009b96a3a1bd7921aee1dad28ee41be0/pydantic_core-2.50.0-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:350001f5573451150d919722ee095aa28bec037d6e23b86d7f04581a910fe924", size = 2244011, upload-time = "2026-10-08T14:29:11.549Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/66/49/294810baacae4b088bfac60edab06036951e5ae6e0c20e8f6cfef7d70f66/pydantic_core-2.50.0-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:21e38a011783d8afc8b9d79928e273d06f349b52ae84edf63ec18ad07f077484", size = 2278392, upload-time = "2026-10-08T14:29:13.998Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/c3/0e/6990b812f124cf3564b36492799df7a66cd3cf5678004ffe65dda85b136b/pydantic_core-2.50.0-cp315-cp315-musllinux_1_1_armv7l.whl", hash = "sha256:588d309ce5c85448379556d72011b191c0414ee2e80d7c3f1ebe2ceb2d9027b1", size = 2450012, upload-time = "2026-10-08T14:29:16.524Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/a8/c4/f8f1a763550a077c51d2c69b7a32db56d1b54e61c613bc19e6c7fd2e7cb2/pydantic_core-2.50.0-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:a8ee3965e01f10e4ff92ba1727626328ab7ba93bcd5674ff2b78ea1048ee0cea", size = 2479233, upload-time = "2026-10-08T14:29:19.111Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/9d/83/7a09baa1e0e4710be47b84aaf808e7b86247d7fa43251ac8934099113f8d/pydantic_core-2.50.0-cp315-cp315-win32.whl", hash = "sha256:c05b75ef3574c9ee4e05bbcf8513f7ccb155d426514be9efef5f6f53152d5f5c", size = 2104507, upload-time = "2026-10-08T14:29:21.694Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/ce/a9/f1cf61f747538834ea2c14c442264e4f29663c95294ad7933867633a1dea/pydantic_core-2.50.0-cp315-cp315-win_amd64.whl", hash = "sha256:8447678b49412294801c9ea15ae31ea3bae7da65425268c92d40e032c38eac6f", size = 2171379, upload-time = "2026-10-08T14:29:24.613Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/30/c8/9871261b760672cfa334498d94bc7dcbff2495345e639e806127f7087254/pydantic_core-2.50.0-cp315-cp315-win_arm64.whl", hash = "sha256:92016718bcf3e6f35a6bd986880191a8da7a35aa1f5b1e97544582ef938464cf", size = 2157182, upload-time = "2026-10-08T14:29:27.082Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/de/3c/5107269aee5ee7855fa370ff837998792dbf124b8545244c370bec251a66/pydantic_core-2.50.0-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:71061800a3c225e730f7997f8a7fad6e0d0dcbe36609cdc7e60576a099a2832b", size = 2234440, upload-time = "2026-10-08T14:29:29.705Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/fe/ac/cb179b0c404337ef517515539f9899164a6d3d43d5c79c2556165da211c1/pydantic_core-2.50.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:bc87bd34239835c8d73171acd039e591cea0a2ca8615e6188044ad170a40fca1", size = 2001074, upload-time = "2026-10-08T14:29:32.327Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/b0/59/ef1714204f145e9497ab71e8bd82c5e40b5adf365d420be777d1e047cdaf/pydantic_core-2.50.0-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57c0e5b26d82bf31ab2b044781527b1b1a36e9ed400856b6aca5097eb1abb909", size = 2023016, upload-time = "2026-10-08T14:29:35.06Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/87/e9/08a3ade34b4af09a83740b62a4442b7623127d388fc21d4938798dd85a26/pydantic_core-2.50.0-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:41b9f2821f0a105f88cd54ad03fe1392ec600618ffa8610f7c5e466ecd98c531", size = 2193165, upload-time = "2026-10-08T14:29:37.721Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/2a/56/29aa3e540d3aca72139e5b7dc56395a940849d85da2b7a9ccfbff71d0928/pydantic_core-2.50.0-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:364f62d8024997536e57865cb1c8b36effad3249bf392a29a2379ea69db28238", size = 2390198, upload-time = "2026-10-08T14:29:40.616Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/76/63/a5cbdde0a2090c47002819d85317428e15b41d22e581cf38e9714b4903af/pydantic_core-2.50.0-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:955d7878130dfc124d6a5343e1b87d2933244fe14a4a0a3e98787e8e660a8eb4", size = 2464777, upload-time = "2026-10-08T14:29:43.29Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/c5/55/fff3a363b0dcf72fdd72311c676b1fa769f2c13342129c694b2eb9886580/pydantic_core-2.50.0-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf81432281af66ba3285b26b09c2d478d84730dc50ff90926c1bdcef54048a33", size = 2150389, upload-time = "2026-10-08T14:29:46.271Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/b7/f3/0062361fd2377185d322dc84cd05ec4b96da352195c89939c7b509664721/pydantic_core-2.50.0-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:34a4a0938eb30931baca56e55786c5a6871ac7aa891a38c8cabcdb7e49dab91b", size = 2246225, upload-time = "2026-10-08T14:29:49.173Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/f9/45/662e3870143d5f1632a02ceb9ce2d5168e88caf3da80e9b42f495ceb1284/pydantic_core-2.50.0-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f517a417cd02aa8fb05b603a0ac6d87b7b004c3ba4fcd279cad25cab7229043b", size = 2264404, upload-time = "2026-10-08T14:29:51.699Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/32/5e/8b14ebf111700362c6e519e40209d86f521da81ef171b2891af0bb5de311/pydantic_core-2.50.0-cp315-cp315t-musllinux_1_1_aarch64.whl", hash = "sha256:d01029d54ff1f45c195b1f7e6fbf6e58fb7e7e12cda9a6e639570d9c581decb2", size = 2294152, upload-time = "2026-10-08T14:29:54.349Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/55/29/85c486e0d25a8b523803032e97d7138edb7b7bc887fa8c6dd085e8824cdb/pydantic_core-2.50.0-cp315-cp315t-musllinux_1_1_armv7l.whl", hash = "sha256:d06dbbfe8da01a0574de27afc19915bb3e7dddfbd184bb97e958f94051d8531b", size = 2469160, upload-time = "2026-10-08T14:29:57.193Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/12/bf/c451db7567e92d6601c4d2f6bdbb945cf60104182e2b241d8fe0b6f01414/pydantic_core-2.50.0-cp315-cp315t-musllinux_1_1_x86_64.whl", hash = "sha256:5307a8bd49158b57a0ca4e10950d31085aa35d9007e934047043ccc6d28eea97", size = 2512204, upload-time = "2026-10-08T14:30:00.177Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/43/1e/5b93a2513ced099acb0dc4dd6684742d6564ade60a59abcbabd161624e07/pydantic_core-2.50.0-cp315-cp315t-win32.whl", hash = "sha256:c2b246fa7cbdf9918488d1542a82bbb928cf71bcba66905c24131981e759ff0b", size = 2099026, upload-time = "2026-10-08T14:30:03.154Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/37/f5/1b5967e31b025a23f2f627ffea1f5a874cff0adb72fa5f5af6bb36394ad9/pydantic_core-2.50.0-cp315-cp315t-win_amd64.whl", hash = "sha256:36c49d4e1769127461b609f110d91963790091ffcc2de401ac1d3b2f6a63bd54", size = 2148456, upload-time = "2026-10-08T14:30:05.971Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/48/15/213d6fe84816e8a5ed7b7539ecebb3f14c20739c2469f4e372f0f15bb042/pydantic_core-2.50.0-cp315-cp315t-win_arm64.whl", hash = "sha256:f3abcabb04503023e1053add24472e78878de0bfd5c8a93686be01f4032c363c", size = 2175368, upload-time = "2026-10-08T14:30:08.989Z"}
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -761,11 +843,11 @@ dependencies = [
|
||||
{name = "packaging"}
|
||||
]
|
||||
name = "pyproject-api"
|
||||
sdist = {url = "https://files.pythonhosted.org/packages/e1/2d/7b6837335aab9c00bcd18adde43c78948db6a2fe26dfa79a6bfcdefd3c5d/pyproject_api-1.11.2.tar.gz", hash = "sha256:7bff8690a101f5f0bac3221475d214018f196ae5eea3af9c9879f4d682a3fd60", size = 24445, upload-time = "2026-09-18T01:11:37.434Z"}
|
||||
sdist = {url = "https://files.pythonhosted.org/packages/d1/82/9cec47d1613bd5cdd918b82a6ffe54924c5462f1e14c59c71ef238fbc9cf/pyproject_api-1.11.4.tar.gz", hash = "sha256:42332ebb8e5e510a3a9ea498cedb5723134ddd46ddfe2d07693917b2ebea22a7", size = 25169, upload-time = "2026-10-07T23:24:01.089Z"}
|
||||
source = {registry = "https://pypi.org/simple"}
|
||||
version = "1.11.2"
|
||||
version = "1.11.4"
|
||||
wheels = [
|
||||
{url = "https://files.pythonhosted.org/packages/d8/7e/234694c81ad7476fddb7bb3420c051fab33d09120a34e5d42ec1327c4a82/pyproject_api-1.11.2-py3-none-any.whl", hash = "sha256:69040bb421a3d502217bf9d5f92a94e65e1db3fe25b641d6064b2b5b9880428b", size = 13327, upload-time = "2026-09-18T01:11:36.228Z"}
|
||||
{url = "https://files.pythonhosted.org/packages/d3/2f/ff5b8a99a33094a83e0e5c5b4cdf7b1fcc19dd009ff4acc1a749dcc18332/pyproject_api-1.11.4-py3-none-any.whl", hash = "sha256:30921dec1f85946dd47664536a003b2aea3a8be18e334c93ed12f6a8b1e60c76", size = 13563, upload-time = "2026-10-07T23:23:59.699Z"}
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -922,11 +1004,11 @@ dependencies = [
|
||||
{name = "virtualenv"}
|
||||
]
|
||||
name = "tox"
|
||||
sdist = {url = "https://files.pythonhosted.org/packages/7c/bd/d7467b015457cf8bd912154c3b0247b30739751498577606b574d389aefd/tox-4.64.7.tar.gz", hash = "sha256:b8033a00945a02493adebdc337e4e45136e21a7083c00b2ae27c998370b72af3", size = 321984, upload-time = "2026-10-01T23:14:16.834Z"}
|
||||
sdist = {url = "https://files.pythonhosted.org/packages/cd/be/9a8d33841569fa73f998d22dfd4b614427072e7cb88cd14951670d9402d5/tox-4.64.10.tar.gz", hash = "sha256:7482b96fefe1e4b49e406a9a62b25b415690bec2cebe9cc0cd59e38af88ce671", size = 332282, upload-time = "2026-10-08T18:00:29.921Z"}
|
||||
source = {registry = "https://pypi.org/simple"}
|
||||
version = "4.64.7"
|
||||
version = "4.64.10"
|
||||
wheels = [
|
||||
{url = "https://files.pythonhosted.org/packages/57/fc/6f88af41caa95e696d87d7dd777ad1e6d96013e23275ef840e5aff0af483/tox-4.64.7-py3-none-any.whl", hash = "sha256:4e7a906f26b72fa9bf11577d02bde492c6d0ad8e690921a801893354d05d35fe", size = 234079, upload-time = "2026-10-01T23:14:15.21Z"}
|
||||
{url = "https://files.pythonhosted.org/packages/f6/b5/18850155b4962a2f70cf1be47c91bf96ea83622c7b7ba45281d2f7e80725/tox-4.64.10-py3-none-any.whl", hash = "sha256:9f0bd51c4fe770b682d0c9540842efc07bd98c302cc327986201169e319de669", size = 237938, upload-time = "2026-10-08T18:00:28.39Z"}
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -981,11 +1063,11 @@ dependencies = [
|
||||
{name = "shellingham"}
|
||||
]
|
||||
name = "typer"
|
||||
sdist = {url = "https://files.pythonhosted.org/packages/16/f7/57713ba479fd405eb76de31404b2c744c289e336b2d999511ebf51e496f7/typer-0.27.2.tar.gz", hash = "sha256:269b7eb9d3c202ca84b4bc9618cb04ebb43d3d4d1e567e4c768607232c05f945", size = 204045, upload-time = "2026-08-28T10:26:55.046Z"}
|
||||
sdist = {url = "https://files.pythonhosted.org/packages/03/51/d33db42cc72ffd8c30777547b42d01f0cbf9d95a770457698d0174b3ed71/typer-0.27.3.tar.gz", hash = "sha256:d0396f770a560ab1b0a8504e13b5f254b728cedb05c61cf0359e944e50ce8901", size = 205303, upload-time = "2026-10-06T17:24:16.61Z"}
|
||||
source = {registry = "https://pypi.org/simple"}
|
||||
version = "0.27.2"
|
||||
version = "0.27.3"
|
||||
wheels = [
|
||||
{url = "https://files.pythonhosted.org/packages/dc/bf/205d0004930ede8f542fb58f601526fccf4ae7626075ca1e6c4de5d3d652/typer-0.27.2-py3-none-any.whl", hash = "sha256:b3a5fc4342d5fc8fda8fc3010b1cf117e9249aab7fae800c2eff62fd3842d97d", size = 123130, upload-time = "2026-08-28T10:26:53.752Z"}
|
||||
{url = "https://files.pythonhosted.org/packages/07/ea/2e31b67051e91a133189e9c000c222502ddc6969856416de0d095de4c0b0/typer-0.27.3-py3-none-any.whl", hash = "sha256:e50022f28b82a86313e54501317a1db64bf8f8d036ff8cfe5ca7e47675454aff", size = 123312, upload-time = "2026-10-06T17:24:15.054Z"}
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1020,28 +1102,28 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "uv"
|
||||
sdist = {url = "https://files.pythonhosted.org/packages/88/60/22b731e4b3e6bfb7ea41fc7c0ace0acd740f92d4aaec5106bce1486dab3c/uv-0.12.22.tar.gz", hash = "sha256:40f6afd2a67d7ad0c58d7a05e9c0a58a30d333e924e3b9e9941af19bd044326a", size = 7169620, upload-time = "2026-10-02T00:17:28.742Z"}
|
||||
sdist = {url = "https://files.pythonhosted.org/packages/7a/76/14e74eb6e1eff3bb1329b358b493f6961771d49c5232bf11c29eb0190343/uv-0.12.24.tar.gz", hash = "sha256:61f0c63c2e750ee5a1a3accc2a9bdc6f30614f5ed267f363ac23f9873626593e", size = 7235720, upload-time = "2026-10-08T19:57:45.627Z"}
|
||||
source = {registry = "https://pypi.org/simple"}
|
||||
version = "0.12.22"
|
||||
version = "0.12.24"
|
||||
wheels = [
|
||||
{url = "https://files.pythonhosted.org/packages/ed/3d/f5f00c69b70a01631a40f5fd76539da5db6bcf5a367e34badfa094bf77a9/uv-0.12.22-py3-none-linux_armv6l.whl", hash = "sha256:3e1e70abbeeb85ad6589e9b8c2cee2785782a26ec220705b60ac8d3d802deb83", size = 22741688, upload-time = "2026-10-02T00:16:32.58Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/4e/91/e1a20a1ed3aacbaae212cf827d05197dd65f8119f29fcddc8df167a9c98a/uv-0.12.22-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:0247b8795348fe16fec7d143b2e4d91a953e69644993a557bb5238b9f53b6a28", size = 21435736, upload-time = "2026-10-02T00:16:35.646Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/1d/89/f147b5070d243f4fa5502c82ba9ec0194fa2bc01ebe3d61445e3710cb33d/uv-0.12.22-py3-none-macosx_11_0_arm64.whl", hash = "sha256:72516e1edef5950b556bdaf3b382f537e4249e06362378daa811a6eefc165a14", size = 17131195, upload-time = "2026-10-02T00:16:38.412Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/d0/ba/2d03d27e791f822cc6889b5d71cb6223136aaa6e3897ef01d923294148b2/uv-0.12.22-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:972cb556d34372f4c19f5c8bba246c92c5a8bfda3f30b2a5b8e12fddd81ba441", size = 22169247, upload-time = "2026-10-02T00:16:42.005Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/cc/d3/89379a7d9c9d57bcf36441a6e9b43a147e96ed62f3f5b7cb95ee2185d920/uv-0.12.22-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:0621be2adfedf7673e46a708b1c781b0e4c61e715e33b3196ad32afffcc5aef6", size = 22238450, upload-time = "2026-10-02T00:16:45.262Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/76/00/c491f1f156d269d80a1bb64e2957d21f1bd7f6df9b6502cfd2f36b883ece/uv-0.12.22-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53992ea1a64344bc887e3aec8d016488b9f185d96d881ea8e4e2a8ab0a259a39", size = 22269643, upload-time = "2026-10-02T00:16:48.149Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/ee/ae/90c45c04ca92fc7e6b88ab1e0667d1fc88d437dd8f087bdc7638962c1ee3/uv-0.12.22-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82911ca7a8310181f382a8f614f00a45f0403561e2aa92aa135c1fa773b0fc44", size = 23118309, upload-time = "2026-10-02T00:16:51.32Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/04/04/3761882501777af81d6d7fc218e84785d94b0db41a0c83c78a4cddb279df/uv-0.12.22-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db53a974617ca520cfdbf95fbbcd9cf5ab95016d601c1ecf788295c01053b8d6", size = 24339615, upload-time = "2026-10-02T00:16:54.459Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/33/81/af3739dec358559a6ebbb6332dd560f0dc09e21a49f6cce62b7d730725bf/uv-0.12.22-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6acf7c5e4c28075c074a104953d61f48fef05e0075d9eb4180e5e6430e4eacfa", size = 23999149, upload-time = "2026-10-02T00:16:57.84Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/7a/b0/4cceeef80da0e8627bf3afb628930d5ea8e857f9c2aacbcda8598a5b82aa/uv-0.12.22-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:680769ef701922dd93426b73476073411941f51ac46991a6e5c5a6efbf68a713", size = 20587257, upload-time = "2026-10-02T00:17:00.91Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/a6/0a/10b6bd5f98c243fe46df223186e4ced966c8c837fc2947da1b0464b959b0/uv-0.12.22-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:04af20f84c122039c16a006b497ea25d4850543b3ac2bd879d96d36d9890bebc", size = 19826142, upload-time = "2026-10-02T00:17:03.93Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/69/7f/9a6b82e0471582e8ffb7419c055d7cf6f12923c8d77898bce829e7d60075/uv-0.12.22-py3-none-manylinux_2_31_riscv64.musllinux_1_1_riscv64.whl", hash = "sha256:699c673c780037e19a623dd66d00c12853def679b5c8f383359012d582721870", size = 22685136, upload-time = "2026-10-02T00:17:07.207Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/18/ab/72e99db52090241395349d59bb750b04505b9b5221a8617b054db7640bd8/uv-0.12.22-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:02774878a9c0ddc374ff1cdb7fc2dee5cd499307223c2d2f0a730274b5794546", size = 22814440, upload-time = "2026-10-02T00:17:10.372Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/06/13/58b59a35c7d175fe8bd24c564710623331bedc5d750942155d4d1ea749dc/uv-0.12.22-py3-none-musllinux_1_1_i686.whl", hash = "sha256:cbfc9dab909f0e7fec63ae13e4215abd53636a5424aabc988fd4974ea124a3ad", size = 22231599, upload-time = "2026-10-02T00:17:13.564Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/fc/78/d690e99b010464713adebd5df6b4f3332a46f0eb204631ba911b9f765da9/uv-0.12.22-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:31096da4ffd7d532909e046cf8acb606ad8e770019404b710a58310bec073119", size = 23480001, upload-time = "2026-10-02T00:17:16.719Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/97/7b/191c13864e22cefaa668d2064f574c8c97c0e9669488d60ef308aa0b9e74/uv-0.12.22-py3-none-win32.whl", hash = "sha256:992956578af9ec09d9c9d8967527dcf43e07b43dea8aecba74a20d37087feeb2", size = 20089438, upload-time = "2026-10-02T00:17:19.84Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/c0/a4/2cc0b670ac86d2cce4758126f06cc44a821a7d289e1cc73bbb485d77f135/uv-0.12.22-py3-none-win_amd64.whl", hash = "sha256:1c45b1e544901b9eef14806ad964cd1aca49338748e3d8030e41c4aed8b17b00", size = 18114353, upload-time = "2026-10-02T00:17:23.278Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/d6/4b/55d7310bff10a2c92c619d610e1c4c26a44bc47b633e54aef8f7b95b3332/uv-0.12.22-py3-none-win_arm64.whl", hash = "sha256:daff5ceafdee5cb268b6d4e991ea150fcad5dff72b97d3e5b84d78d04fcf8482", size = 19172683, upload-time = "2026-10-02T00:17:26.219Z"}
|
||||
{url = "https://files.pythonhosted.org/packages/de/e4/ebcd89b05fe1b46f1e72591ff2bce46a7bda22dc8dcaf18eba2b86354b64/uv-0.12.24-py3-none-linux_armv6l.whl", hash = "sha256:3b43f5a9e2263e71e4b0bd2159cfc6c16175a6d24ccc9f9e151f55e9bba1504c", size = 19462578, upload-time = "2026-10-08T19:56:54.043Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/44/21/690d3097136c268fdd833d42f00afd63302a7ab9df610f2ac163b1780141/uv-0.12.24-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:48cc70ecf59eece47e28d37c5c5eb9bb093e16881b1cc03e4a202286765a2022", size = 18295605, upload-time = "2026-10-08T19:56:57.083Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/d9/82/a77c9ebb95f55e9e6c7d98202f9697110b634fe650b07b5d9eb5a51bba0e/uv-0.12.24-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7b3403f72dfda049295daf5c4c2da716138feecb0e9d5f22ad4b9734518c77dd", size = 14763093, upload-time = "2026-10-08T19:56:59.981Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/f8/68/ebea2e5af6bc85d974520fc24ee81b9247339de14289cee6f865de48fd3f/uv-0.12.24-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:94c9ed22c1b5d6861a72639e23c5707bb07a354b67ec0462e9a0169a9665b5fd", size = 18730401, upload-time = "2026-10-08T19:57:02.882Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/f0/e0/f7c041c9f9e8cec94750821316725edfcdeb4f58cbc5d0e15c3e42098980/uv-0.12.24-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:fb4eec64d8624e22cd7bb3c1c5576067fff2b7a85baf1f01e0b592e99b5ab7a6", size = 18997146, upload-time = "2026-10-08T19:57:05.988Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/ec/9a/3ef51b8b5cb97d11f00a35b2209fbee988d53bb9c746adac3647feb50909/uv-0.12.24-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b2653d07538f6c8ae008cfbabd288ccae2da6ec8192f8e42bba6e4aefec648bf", size = 19024569, upload-time = "2026-10-08T19:57:08.464Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/fd/4b/608bab933f84cf93e67e88399ad5c304c9b168bdc3a6798035ede0fa243e/uv-0.12.24-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c46d1f1eaf3d80b1f72ca5168ad500d8c9203ecd92a49f32569c2d665a840e9", size = 19553604, upload-time = "2026-10-08T19:57:11.751Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/ad/2c/d8b519aa7dc265f3f8ca3498ed68bb3f0a132f735e9add3646273ca70d21/uv-0.12.24-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a407f886b866a9b694ac717eb0f5c5ef5ca31919e3a61027e403088da893354c", size = 20942617, upload-time = "2026-10-08T19:57:14.713Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/4b/d0/dab0e7092c61145613fe9c22e08be8a75c6408d73c19179c4c7a41221076/uv-0.12.24-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fac7ab11ccab5b57bdf176af98373b7a9f4f7585a5d8f1461197f6ccd1a4101", size = 20911241, upload-time = "2026-10-08T19:57:17.809Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/89/f8/2ef356d9d066686c98854be9e3e41d0ba9d69f7b2495512b6394115fa40c/uv-0.12.24-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e47005957dca320272ad68960b591b854b6bf0c3288368efb096d9ccd06b8404", size = 17808014, upload-time = "2026-10-08T19:57:20.815Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/27/d1/fc19bc0e5567fdd28fb1d0bf45875416478ffd9f9507eb22657e25d93534/uv-0.12.24-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:0fc14fabb78c55d140a01125d5cd038afb4fed317eb0c3868f3fba87b8cefe16", size = 17245762, upload-time = "2026-10-08T19:57:23.528Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/60/47/0711c0adcdedbe97d5e8e5f25e67e4c51ffc370cdf9c058e9d5a28074157/uv-0.12.24-py3-none-manylinux_2_31_riscv64.musllinux_1_1_riscv64.whl", hash = "sha256:ea86e94b97f41df3fd4577cd9af04fe1bb0c1f37932d3d5b4d365af9d5e1172e", size = 19084638, upload-time = "2026-10-08T19:57:26.317Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/db/e8/30fd01577a40e5042168f0361e176fd94175fa7774329b855ff852cd50e3/uv-0.12.24-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:4fda88097acb1c8da81442c8c495ad647006fcf1d12d732aa718340fc6b579d8", size = 19309548, upload-time = "2026-10-08T19:57:29.124Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/66/28/88458d0ceae6b93770d6d2d94352f3b3eaefbf292a4b16232b5fa258bff2/uv-0.12.24-py3-none-musllinux_1_1_i686.whl", hash = "sha256:3dcc3057588297cf5f1dee165b33e781267f923249e50cd0a4366f30d11c54b1", size = 18806745, upload-time = "2026-10-08T19:57:32.008Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/3b/0c/26502617b066f4c9704ba57593589ee54e6443d70982b633958fbf7e9efa/uv-0.12.24-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:b6fe9cd01f5c576a640f17829cb465899fffb5d6d8dfdd576e38d4f1e40dd438", size = 20110482, upload-time = "2026-10-08T19:57:34.73Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/90/ef/d0cc4e8d8312cc65d5c727e72529c6c53aa4ecbbdebab524dc91dffb48c7/uv-0.12.24-py3-none-win32.whl", hash = "sha256:03e245e2d8abf35dc18cb40527f708fdf63bf7cc70119380363eca0a8a90f895", size = 16969390, upload-time = "2026-10-08T19:57:37.547Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/71/d7/d573b4eda67a7ee79fdef3476138ff3a4d18b2bc97980d048709add81ea0/uv-0.12.24-py3-none-win_amd64.whl", hash = "sha256:83f5dbd2389eb6f998e195faf7efb83199a3df825d4ec954b4d6e45f286ee52d", size = 15782766, upload-time = "2026-10-08T19:57:40.544Z"},
|
||||
{url = "https://files.pythonhosted.org/packages/8a/e6/ed50a59e7324414294049840ee33bfce299e41a16db25f19fe372fb8e94e/uv-0.12.24-py3-none-win_arm64.whl", hash = "sha256:8b22387c8a23e9440103d925cb4762dc503932daa47020df6b1fa782192fcb76", size = 16314388, upload-time = "2026-10-08T19:57:43.11Z"}
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1053,9 +1135,9 @@ dependencies = [
|
||||
{name = "python-discovery"}
|
||||
]
|
||||
name = "virtualenv"
|
||||
sdist = {url = "https://files.pythonhosted.org/packages/c4/f9/f323b3b6058cff3853b31cf6a49c0425ed797bf9611572ec10d065e08bac/virtualenv-21.14.5.tar.gz", hash = "sha256:c4cb6c13e46b57225a999c7e22a09b163393878facc7ac4c059a57f46faa1647", size = 5467127, upload-time = "2026-10-02T17:42:25.613Z"}
|
||||
sdist = {url = "https://files.pythonhosted.org/packages/92/f3/589727d02bc832750cfa00ced4315d127535a22a6d7cfcb369b2fe219a2e/virtualenv-21.14.6.tar.gz", hash = "sha256:c6b74616e64bffa9532cfa9dfd54ffbab0dc9b999df2bb8ec0d75e559b983661", size = 5467961, upload-time = "2026-10-08T14:49:57.376Z"}
|
||||
source = {registry = "https://pypi.org/simple"}
|
||||
version = "21.14.5"
|
||||
version = "21.14.6"
|
||||
wheels = [
|
||||
{url = "https://files.pythonhosted.org/packages/df/93/084f10ebbecdedf7902ed68fbecc903f0e9c5742155bfffb05eccbc071da/virtualenv-21.14.5-py3-none-any.whl", hash = "sha256:b0651e0174982bba17cc6f0aaa2ef496730cb85a3f35eb5d1119d1c904f7d1da", size = 5487482, upload-time = "2026-10-02T17:42:23.38Z"}
|
||||
{url = "https://files.pythonhosted.org/packages/c9/1f/e5182163bcee149dda4eec823d88b0f51f2c0c531b12da896f666f0b80c1/virtualenv-21.14.6-py3-none-any.whl", hash = "sha256:eb1a20c5878569b69667f6a1af03458a8512b3afa7be771a2dafe92fdca05bb9", size = 5487469, upload-time = "2026-10-08T14:49:54.988Z"}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user