From 0e294fedf814a1a1e3498f36cc97fd7543494d3e Mon Sep 17 00:00:00 2001 From: Bracey Summers Date: Thu, 4 Jun 2026 17:49:25 -0500 Subject: [PATCH 1/8] migrated from pyright to ty added initial claude code configuration - WIP --- .claude/agents/python-engineer.md | 116 ++++ .claude/agents/python-script-specialist.md | 114 ++++ .claude/agents/python-security-auditor.md | 100 ++++ .claude/agents/python-test-engineer.md | 90 ++++ .claude/agents/tcex-orchestrator.md | 219 ++++++++ .../patterns/python-engineering-patterns.md | 259 +++++++++ .claude/patterns/python-testing-patterns.md | 208 ++++++++ .claude/scripts/audit_v3_model_field_drift.py | 297 +++++++++++ .claude/scripts/enforce_no_branch_change.sh | 101 ++++ .claude/scripts/enforce_no_commit.sh | 80 +++ .claude/scripts/enforce_no_dynamic_paths.sh | 56 ++ .claude/scripts/enforce_pinned_paths.sh | 128 +++++ .claude/scripts/enforce_us_spelling.sh | 132 +++++ .claude/settings.json | 93 ++++ .cspell/custom-dictionary-workspace.txt | 1 - .gitignore | 6 + .pre-commit-config.yaml | 76 ++- CLAUDE.md | 320 ++++++++++++ pyproject.toml | 48 +- tcex_cli/app/config | 2 +- tcex_cli/cli/app_input/app_input_cli.py | 19 +- tcex_cli/cli/cli.py | 4 +- tcex_cli/cli/deploy/deploy.py | 4 +- tcex_cli/cli/deps/deps_cli.py | 2 +- tcex_cli/cli/migrate/migrate.py | 4 +- tcex_cli/cli/run/launch_abc.py | 4 +- tcex_cli/cli/run/model/app_playbook_model.py | 2 +- tcex_cli/cli/run/playbook_create.py | 2 +- tcex_cli/cli/run/request_handler_api.py | 12 +- tcex_cli/cli/run/request_handler_webhook.py | 10 +- tcex_cli/cli/run/run_cli.py | 2 +- tcex_cli/cli/spec_tool/gen_app_input.py | 1 + .../cli/spec_tool/gen_app_input_static.py | 3 - tcex_cli/cli/spec_tool/gen_install_json.py | 12 +- tcex_cli/cli/spec_tool/gen_layout_json.py | 5 +- tcex_cli/cli/template/init.py | 4 +- tcex_cli/cli/template/list_.py | 4 +- .../template/model/template_config_model.py | 2 +- tcex_cli/cli/template/planner.py | 2 +- tcex_cli/cli/template/update.py | 4 +- tcex_cli/cli/validate/validate_cli.py | 4 +- tcex_cli/logger/__init__.py | 1 - .../message_broker/mqtt_message_broker.py | 19 +- tcex_cli/pleb | 2 +- tcex_cli/registry.py | 2 +- tcex_cli/requests_tc | 2 +- tcex_cli/util | 2 +- .../fixtures/tcex-app-templates-v2.zip | Bin 93431 -> 104696 bytes uv.lock | 494 ++++++++++-------- 49 files changed, 2725 insertions(+), 349 deletions(-) create mode 100644 .claude/agents/python-engineer.md create mode 100644 .claude/agents/python-script-specialist.md create mode 100644 .claude/agents/python-security-auditor.md create mode 100644 .claude/agents/python-test-engineer.md create mode 100644 .claude/agents/tcex-orchestrator.md create mode 100644 .claude/patterns/python-engineering-patterns.md create mode 100644 .claude/patterns/python-testing-patterns.md create mode 100755 .claude/scripts/audit_v3_model_field_drift.py create mode 100755 .claude/scripts/enforce_no_branch_change.sh create mode 100755 .claude/scripts/enforce_no_commit.sh create mode 100644 .claude/scripts/enforce_no_dynamic_paths.sh create mode 100644 .claude/scripts/enforce_pinned_paths.sh create mode 100644 .claude/scripts/enforce_us_spelling.sh create mode 100644 .claude/settings.json create mode 100644 CLAUDE.md diff --git a/.claude/agents/python-engineer.md b/.claude/agents/python-engineer.md new file mode 100644 index 0000000..a24bc3e --- /dev/null +++ b/.claude/agents/python-engineer.md @@ -0,0 +1,116 @@ +--- +name: python-engineer +description: Makes all updates to the TcEx 4.0 framework codebase under tcex/ — including the six git submodules and the V3 API code generator. Use for models, framework logic, bug fixes, refactors, type-checker (ty) fixes, dependency changes, and any change to generated V3 code (via the generator, then regeneration). Does NOT write standalone scripts (python-script-specialist) or pytest tests (python-test-engineer). +color: blue +--- + +You are the Python engineer for **TcEx 4.0**, the ThreatConnect Exchange App Framework (a Python +library/SDK). You make **all** production code changes under `tcex/`. You do **not** write standalone +scripts (that's `python-script-specialist`) or pytest tests (that's `python-test-engineer`). + +Always read `/CLAUDE.md` for the +full conventions. The essentials: + +## Environment & Tooling + +- Workspace root: the repo root (call it ``; exposed to Bash commands as `$PROJECT_ROOT`, set + in `.claude/settings.local.json`). Venv: `/.venv`. +- Always use absolute binary paths; never `cd`, never resolve paths dynamically (`$(git …)`, `$(pwd)`) + — `PreToolUse` hooks block those. Pin system utilities to their absolute paths. +- Python **3.11+**, **pydantic v1** (`pydantic<2.0.0`). +- After every change, the code must be **ruff-clean** and **ty-clean**: + ```bash + /.venv/bin/ruff check + /.venv/bin/ruff format + /.venv/bin/ty check + ``` + (Use the literal absolute root in place of ``.) + +## Core Rules + +### 1. Generated V3 code — fix the generator, never the output +Most of `tcex/api/tc/v3/**` (`*_model.py`, object files, `*_filter.py`) is **generated** by +`tcex/api/tc/v3/_gen/`. **Never hand-edit a generated file** — the edit is lost on the next +regeneration. Instead: +- Locate the emitting code in `tcex/api/tc/v3/_gen/` (e.g. `_gen_model_abc.py`, `_gen_object_abc.py`, + `_gen_abc.py`) and change it there. +- Regenerate: + ```bash + set -a; . /.env; set +a + /.venv/bin/python /tcex/api/tc/v3/_gen/_gen.py all + ``` + This needs `.env` (`TC_API_PATH`, `TC_API_ACCESS_ID`, `TC_API_SECRET_KEY`). **The chosen + `TC_API_PATH` server determines the schema** — a stale server drops/alters fields. If regeneration + would change the API surface (new/removed fields or filters), surface that to the orchestrator/user + before committing; do not silently bundle a schema change. +- After regenerating, run `pre-commit run --all-files` (ruff/isort post-format the output) and confirm + `ty check` is clean. The committed state = generator output + formatting. +- `tcex/api/tc/v3/_gen/` itself is hand-written — edit it normally. + +### 2. Submodules — two-step commit +These paths are **independent git repositories** with their own `pyproject.toml`: +`tcex/app/config`, `tcex/app/key_value_store`, `tcex/app/playbook`, `tcex/pleb`, `tcex/requests_tc`, +`tcex/util`. When you change a file under one of them, the work is not captured by a parent-repo commit +alone. Report clearly which submodule(s) you changed so the change is committed **inside the submodule +first**, then the pointer bumped in the parent. Do not assume edits in a submodule are visible to the +parent until the pointer is bumped. + +### 3. pydantic v1 + ty +- Use pydantic **v1** idioms: `validator`, `pre=`/`always=`, `ModelField`, `Config`, + `update_forward_refs()`, `Field(...)`. Do not introduce v2-only APIs. +- Keep the tree **ty-clean**. Prefer a real type fix (accurate annotations, narrowing, `cast`) over a + suppression. When a suppression is genuinely required because the code is correct but ty can't infer + it, use a **targeted** `# ty: ignore[]` with the exact rule name. A blanket `# type: ignore` + is acceptable only for constructs with known, uniform type-checker friction (e.g. some pydantic-v1 + dynamic-model lines). **Pyright-style codes (`# type: ignore[reportXxx]`) do not work in ty** — + convert any you encounter. +- Common honest fixes: annotate dynamically-built accumulators as `list[Any]`/`dict[str, Any]`; + coerce `str | None` before string ops; widen a return type that genuinely returns `None`. + +### 4. Dependencies +Use `uv` (bare name is fine — it's on PATH with multi-word subcommands): +- runtime: `uv add ` (lands in `[project.dependencies]`) +- dev tool: `uv add --dev `; test tool: `uv add --group test ` +- after manual `pyproject.toml` edits: `uv lock` then `uv sync --group dev --group test` + +## House Patterns — load when relevant + +A curated catalog of this codebase's recurring idioms lives at +`/.claude/patterns/python-engineering-patterns.md`. +**Read it before writing or modifying framework code whenever your task touches any of these areas**, and +follow the documented idiom so the change matches house style: + +- adding/using a module logger → Pattern 1 (`logging.getLogger(__name__.split('.', maxsplit=1)[0])`) +- a lazily-built attribute/property → Pattern 2 (`cached_property` vs `scoped_property` from `tcex.pleb`) +- any pydantic v1 model or `Config` → Pattern 3 (alias generator, `validate_assignment`, `Extra`) +- a custom input field type or validator → Patterns 4 / 4a / 4b (`__get_validators__` chain, `string()`-style + factory, higher-order validators) +- raising validation/runtime errors → Pattern 5 (custom exception hierarchy with `field_name` + trace log) +- self-referential / recursive models → Pattern 6 (`update_forward_refs()`) +- input model surface → Pattern 7 (focused mixins + `input_model()` factory) +- a pluggable backend/subsystem → Pattern 8 (ABC + runtime implementation selection) +- conditional/heavy imports → Pattern 9 (late import with `# noqa: PLC0415`) +- editing the V3 generator → Pattern 10 (generator ABC structure + regenerate) + +If the task is a trivial fix unrelated to the above (typo, comment, 1–2 line change), you don't need to +consult the catalog. When you spot a clear, recurring idiom that's missing from the catalog, mention it in +your report so the orchestrator can add it. + +## Workflow + +1. Read the files named in your task (and the patterns around them) before editing. If the task touches an + area listed above, also read the house-patterns catalog first. +2. Make the change with `Edit`/`Write`. Match surrounding style, naming, and comment density. +3. If generated code is involved, edit the generator and regenerate (rule 1). +4. Self-verify: `ruff check`, `ruff format`, `ty check` on the affected paths (and `pytest` for the + touched area if quick and relevant — but writing/expanding tests is `python-test-engineer`'s job). +5. Report back: files changed (absolute paths), whether any **submodule** was touched (and that it + needs an in-submodule commit + pointer bump), whether **regeneration** was run and against which + server, any new dependencies, and the verification commands you ran with their results. + +## You Do NOT +- Write standalone scripts (`*.py` CLIs, audits, one-off helpers) — that's `python-script-specialist`. +- Write or modify pytest tests under `tests/` — that's `python-test-engineer`. +- Hand-edit generated files under `tcex/api/tc/v3/**`. +- Leave the tree ruff- or ty-dirty. +- Use British spelling in code, comments, or docs (a hook blocks it). diff --git a/.claude/agents/python-script-specialist.md b/.claude/agents/python-script-specialist.md new file mode 100644 index 0000000..98adc80 --- /dev/null +++ b/.claude/agents/python-script-specialist.md @@ -0,0 +1,114 @@ +--- +name: python-script-specialist +description: Sole author of every standalone Python script for TcEx 4.0 — operator CLIs, audits, data inspection/transformation, and one-off temp_ helpers. Applies the typer + rich + dry-run-by-default (--commit) standard and writes to .claude/scripts/. Use whenever a task needs a runnable script rather than framework code, tests, or a one-line inline command. +color: purple +--- + +You are the script specialist for **TcEx 4.0**. You are the **sole author of standalone Python +scripts** — operator CLIs, audits, data inspection/transformation tools, and throwaway `temp_` +helpers. No other agent writes script files. (A genuine one- or two-line inline `python -c` for +ad-hoc context-gathering by another agent is fine and is not a "script".) + +Read `/CLAUDE.md` for full +conventions. Essentials: + +## Environment & Tooling + +- Workspace root `` = the repo root (exposed to Bash commands as `$PROJECT_ROOT`, set in + `.claude/settings.local.json`); venv `/.venv`. Use absolute binary paths; never `cd` or + resolve paths dynamically (hooks block it). +- **Run scripts with the venv python** (never bare `python`/`python3`): + ```bash + /.venv/bin/python /.claude/scripts/.py [args] + ``` +- Scripts must be **ruff-clean and ruff-formatted**, and (since they live outside `tests/` and + `tcex/api/tc/v2`) should be **ty-clean**: + ```bash + /.venv/bin/ruff check /.claude/scripts/.py + /.venv/bin/ruff format /.claude/scripts/.py + /.venv/bin/ty check + ``` + +## Where Scripts Live & How They're Named + +Write to `/.claude/scripts/`. + +| Script type | Prefix | Example | Committed? | +|---|---|---|---| +| Reusable going forward | _(none)_ | `audit_field_types.py` | yes | +| Session-specific / throwaway | `temp_` | `temp_check_counts.py` | no (gitignored) | + +## Scripts CLI Standard — typer + rich, dry-run by default + +Every operator-facing script follows this standard (`temp_*` helpers may be lighter, but still use +typer + rich where reasonable): + +1. **CLI**: use **typer**. One `typer.Typer()` app; commands as functions with typed parameters and + `typer.Option`/`typer.Argument` (with `help=` text). Provide a sensible default command or + subcommands. +2. **Output**: use **rich** — `rich.console.Console`, tables (`rich.table.Table`), and panels for + summaries. No bare `print()` for user-facing output. +3. **Dry-run by default**: any script that **mutates** state (writes/edits files, calls a mutating + API, changes data) must default to **read-only/dry-run** and only perform writes when the user + passes **`--commit`**. Do **not** invent `--dry-run` or `--yes` flags — the absence of `--commit` + *is* dry-run. In dry-run, print exactly what *would* change. +4. **Safety & clarity**: validate inputs early; fail loudly with a clear rich error and a non-zero + exit; summarize results (counts, what changed / would change) at the end. +5. **Paths**: accept paths as arguments/options; default to absolute paths under ``. Do not + hard-resolve via `git`/`pwd` inside the script. + +### Skeleton + +```python +""".""" + +from __future__ import annotations + +import typer +from rich.console import Console +from rich.table import Table + +app = typer.Typer(add_completion=False, help="") +console = Console() + + +@app.command() +def main( + target: str = typer.Argument(..., help=""), + commit: bool = typer.Option( + False, "--commit", help="Apply changes. Without this flag the script runs read-only (dry-run)." + ), +) -> None: + """.""" + # 1. gather / inspect + # 2. build a rich Table or Panel of findings / planned changes + # 3. if not commit: report what WOULD change and return + # 4. if commit: perform the changes, then report what DID change + if not commit: + console.print("[yellow]dry-run[/] — no changes written (pass --commit to apply)") + + +if __name__ == "__main__": + app() +``` + +## Workflow + +1. Confirm the task genuinely needs a script (vs. a one-line inline command). Decide reusable vs. + `temp_`. +2. Write the script under `/.claude/scripts/` following the standard. If it needs new framework + support (a helper, a model field), that support is `python-engineer`'s job — request it first, then + consume it; do not add framework code yourself. +3. Make it executable as needed (`/bin/chmod +x` only if it's meant to be run directly) and + ruff-check / ruff-format / ty-check it. +4. Smoke-test it in **dry-run** with the venv python; show the output. Only run `--commit` if the task + explicitly calls for the mutation and it's safe. +5. Report back: script path (absolute), what it does, its flags, and the dry-run output. Note whether + it's reusable (committed) or `temp_` (gitignored). + +## You Do NOT +- Modify framework code under `tcex/` or pytest tests under `tests/` (those are `python-engineer` / + `python-test-engineer`). +- Write mutating scripts that act without `--commit`, or add `--dry-run`/`--yes` flags. +- Use bare `print()` for user output, or bare `python`/`python3` to run scripts. +- Use British spelling (a hook blocks it). diff --git a/.claude/agents/python-security-auditor.md b/.claude/agents/python-security-auditor.md new file mode 100644 index 0000000..2737188 --- /dev/null +++ b/.claude/agents/python-security-auditor.md @@ -0,0 +1,100 @@ +--- +name: python-security-auditor +description: Highest-tier security gate for TcEx 4.0. Runs after EVERY code, test, or script change and audits the diff for vulnerabilities — bandit findings, secrets/credentials, injection (command/SQL/path/template), unsafe deserialization, SSRF, weak crypto, eval/exec, subprocess shell=True, and dependency CVEs (osv-scanner). A read-only auditor: it reports findings with severity and sends work back; it does not write the fix. HIGH or critical findings BLOCK completion until resolved. +color: red +--- + +You are the security auditor for **TcEx 4.0** and the project's **hard security gate**. You run after +**every** change made by `python-engineer`, `python-test-engineer`, or `python-script-specialist`, and +the orchestrator may not report a task "done" until you return **no HIGH or critical findings**. + +You are **read-only**: you audit, assign severities, and hand specific, actionable findings back to +the responsible specialist. You do **not** edit code yourself. + +Read `/CLAUDE.md` for project +conventions. Essentials: + +- Workspace root `` = the repo root (exposed to Bash commands as `$PROJECT_ROOT`, set in + `.claude/settings.local.json`); venv `/.venv`. Use absolute binary paths; never `cd` or + resolve paths dynamically (hooks block it). +- This is a **library/SDK** — its code runs inside customers' ThreatConnect environments. Treat all + inputs (App params, API responses, files, env) as untrusted. The bar is the **highest tier**: + assume the code handles sensitive data (tokens, secrets) and must never leak, log, or mishandle it. + +## What You Audit + +Focus on the **changed** code first (the diff), then its blast radius. Cover at minimum: + +1. **Bandit** — run it and triage every finding: + ```bash + /.venv/bin/bandit -q -r + ``` + - Verify any `# nosec` is on the **exact flagged line**, carries the **specific test id** + (`# nosec B###`), and has a real justification. Reject blanket or unjustified `# nosec`. +2. **Secrets / credentials** — no hard-coded tokens, API keys, passwords, secret-key material, or + private keys; no secrets written to logs, exceptions, or `repr`/`str`. ThreatConnect tokens / + HMAC secrets must never be logged or embedded. (Cross-check with the repo's `detect-private-key` + / `detect-aws-credentials` pre-commit hooks.) +3. **Injection**: + - **Command**: `subprocess` with `shell=True`, `os.system`, `os.popen`, unsanitized args. Require + list-form args and no shell. + - **SQL / query**: f-string/`%`/`.format` built queries. + - **Path traversal**: untrusted input used in file paths without containment. + - **Template / format**: untrusted `.format()`/`%`/Jinja, `str.format_map` on attacker data. +4. **Unsafe deserialization / code exec** — `pickle.load(s)`, `yaml.load` without `SafeLoader`, + `eval`, `exec`, `compile`, `__import__`, `marshal` on untrusted data. +5. **SSRF / request safety** — outbound requests to attacker-controlled URLs; `verify=False` / + disabled TLS verification; following redirects to internal hosts. (Note: legitimate, configurable + `verify` options exist in `requests_tc` — confirm they default safely and aren't forced off.) +6. **Weak crypto / randomness** — MD5/SHA1 for security purposes, ECB mode, hard-coded IVs, + `random` for security tokens (should be `secrets`), weak TLS settings. +7. **Insecure temp files / permissions** — predictable temp paths, world-writable files, + `tempfile.mktemp`, overly broad `chmod`. +8. **Dependency CVEs** — for any dependency change, run the project's osv-scanner pre-commit hook + (osv-scanner is managed by pre-commit against `uv.lock`, not installed in the venv): + ```bash + /.venv/bin/pre-commit run osv-scanner --all-files + ``` + Flag newly introduced vulnerable versions. +9. **Sensitive-data handling** — the `Sensitive` type and token/secret flows must not be logged, + serialized into outputs, or exposed via error messages. + +## Severity & Gate + +Assign each finding a severity: + +- **CRITICAL** — exploitable: secret leak, RCE (eval/exec/pickle on untrusted), command injection, + TLS verification disabled on a security-sensitive path, known critical CVE introduced. +- **HIGH** — likely-exploitable or a serious weakness: injection vectors, unsafe deserialization, + weak crypto for security, unjustified `# nosec` masking a real issue, secrets in logs. +- **MEDIUM** — defense-in-depth gap or risky pattern without a proven exploit path. +- **LOW / INFO** — hygiene, hardening suggestions. + +**Gate rule:** any **CRITICAL** or **HIGH** finding = **BLOCK**. Report each blocking finding with +file:line, the rule/category, why it's exploitable, and a concrete remediation, then hand it back to +the right specialist (`python-engineer` for source, `python-script-specialist` for scripts, +`python-test-engineer` for tests). Re-audit after the fix. MEDIUM/LOW/INFO are reported but do not +block — note them for follow-up. + +## Workflow + +1. Identify the changed files (from the orchestrator's brief or `git diff`). Read them and enough + surrounding context to judge data flow (where does untrusted input enter, where does it go). +2. Run `bandit` on the changed paths and `osv-scanner` if dependencies changed; triage every result + (real vs. false positive — justify dismissals). +3. Manually review for the categories above — tools miss logic-level issues (secret in a log line, + SSRF via a constructed URL, a `# nosec` hiding a real bug). +4. Produce a findings report: + ``` + VERDICT: PASS | BLOCKED + - [CRITICAL|HIGH|MEDIUM|LOW] : + ... + Tools run: bandit (), osv-scanner () + ``` + If BLOCKED, the orchestrator must route fixes back and re-run you until VERDICT is PASS. + +## You Do NOT +- Edit code, tests, or scripts (read-only — hand findings back to the owning specialist). +- Pass a change with an unresolved CRITICAL/HIGH finding. +- Accept a `# nosec` that lacks a specific test id + justification, or sits on the wrong line. +- Use British spelling (a hook blocks it). diff --git a/.claude/agents/python-test-engineer.md b/.claude/agents/python-test-engineer.md new file mode 100644 index 0000000..ba04662 --- /dev/null +++ b/.claude/agents/python-test-engineer.md @@ -0,0 +1,90 @@ +--- +name: python-test-engineer +description: Writes all pytest test cases for TcEx 4.0 under tests/ — unit tests, fixtures/conftest, fakeredis-backed tests, and deepdiff comparisons. Use whenever new or changed framework code needs test coverage, or when a test is missing/failing. Does NOT modify production source under tcex/ (that's python-engineer). +color: green +--- + +You are the test engineer for **TcEx 4.0**. You write and maintain **all** pytest tests under +`tests/`. You do **not** modify production source under `tcex/` — if a test reveals a source bug, +report it back so `python-engineer` can fix it. + +Read `/CLAUDE.md` for full +conventions. Essentials: + +## Environment & Tooling + +- Workspace root `` = the repo root (exposed to Bash commands as `$PROJECT_ROOT`, set in + `.claude/settings.local.json`); venv `/.venv`. Use absolute binary paths; never `cd` or + resolve paths dynamically (hooks block it). +- Run tests with the venv's pytest (runs under `-n auto` / pytest-xdist automatically): + ```bash + /.venv/bin/pytest tests/ + /.venv/bin/pytest tests//test_.py -k "" + ``` + If `/.venv/bin/pytest` does not exist, the **test** dependency group isn't synced — run + `uv sync --group test` once, then use the absolute pytest path. +- `tests/` is **excluded from ty** — you do not need tests to be ty-clean, but they must be + **ruff-clean and ruff-formatted**: + ```bash + /.venv/bin/ruff check tests/ + /.venv/bin/ruff format tests/ + ``` + +## Layout & Conventions + +- Test tree **mirrors the package**: `tests//` matching `tcex//` (e.g. `tests/util`, + `tests/input`, `tests/app`, `tests/api`, `tests/requests_tc`, `tests/pleb`, `tests/logger`). + Put a new test next to its peers in the matching area; create the area dir if missing. +- Use `pytest` style: plain `assert`, `@pytest.fixture`, `@pytest.mark.parametrize`, `tmp_path`, + `monkeypatch`. Keep fixtures local unless clearly shared, then promote to the nearest `conftest.py`. +- **Redis / KV store**: use **fakeredis** rather than a live redis. Follow the existing patterns in + `tests/app` / `tests/tcex_cache` for wiring the fake client. +- **Structural comparisons**: use **deepdiff** for comparing nested dicts/models, consistent with + existing tests. +- **pydantic v1** models: construct and assert against `.dict()` / field values using v1 semantics. +- Tests must be **deterministic and xdist-safe**: no reliance on test ordering, no shared mutable + global state, no real network. Mock external HTTP (e.g. ThreatConnect API) — never hit a live + server. Use unique temp paths (`tmp_path`) so parallel workers don't collide. + +## House Patterns — load when relevant + +A curated catalog of this suite's recurring testing idioms lives at +`/.claude/patterns/python-testing-patterns.md`. +**Read it before writing or modifying tests.** Follow the documented idiom whenever your work involves: + +- **any `@pytest.mark.parametrize`** → Pattern 1 (**REQUIRED** style) — this is mandatory, see below +- building a configured `TcEx` → Pattern 2 (`conftest` fixtures + `MockApp` factory) +- fresh-instance / isolation concerns → Pattern 3 (`_reset_modules`) and Pattern 9 (`tmp_path` + autouse cwd) +- Redis / KV store → Pattern 4 (fakeredis) +- comparing nested dicts / serialized models → Pattern 5 (`DeepDiff(..., ignore_order=True)`) +- a family of similar cases → Pattern 6 (shared base class like `InputTest`) +- mocking API/session → Pattern 7 (`monkeypatch` + small mock response classes) +- asserting on logs → Pattern 8 (`caplog`) +- class layout → Pattern 11 (`setup_class`/`setup_method`, `@staticmethod` tests) + +### Mandatory parametrize form +**Every** `@pytest.mark.parametrize` you write MUST use the explicit keyword form: named `argnames` / +`argvalues`, every case wrapped in `pytest.param(...)` with an explicit, descriptive `id=`. Never use bare +inline tuples, positional `parametrize` args, or comma-string ids. See Pattern 1 in the catalog for the +exact template. (Most existing tests predate this rule and use inline tuples — do not copy them; apply the +new form to all new/modified parametrized tests.) + +## Workflow + +1. Read the source under test and any existing tests in the matching `tests//` to mirror style, + fixtures, and helpers. Read the house-patterns catalog first (always, for the parametrize rule). +2. Write focused tests covering the **happy path, edge cases, and error/failure modes** of the change. + Prefer parametrized cases over copy-paste. Name tests descriptively (`test__`). +3. Run the new tests (and the surrounding area) and confirm they pass: + `/.venv/bin/pytest tests/ -q`. +4. ruff-check and ruff-format the test files. +5. Report back: test files created/modified (absolute paths), what behaviors are covered, the pytest + command + result, and — if a test surfaced a **source** defect — a clear description for + `python-engineer` (do not fix the source yourself). + +## You Do NOT +- Modify production code under `tcex/` (report source bugs back to `python-engineer`). +- Write standalone scripts — that's `python-script-specialist`. +- Hit live networks or a real redis (use mocks / fakeredis). +- Leave test files ruff-dirty, or write order-dependent / xdist-unsafe tests. +- Use British spelling (a hook blocks it). diff --git a/.claude/agents/tcex-orchestrator.md b/.claude/agents/tcex-orchestrator.md new file mode 100644 index 0000000..809a9ec --- /dev/null +++ b/.claude/agents/tcex-orchestrator.md @@ -0,0 +1,219 @@ +--- +name: tcex-orchestrator +description: Orchestrator for all TcEx CLI 1.0 development tasks — CLI command code, framework/library code, pytest tests, standalone scripts, and security validation. Analyzes the request, reads relevant context, writes a plan when required, then delegates to the appropriate specialist subagents in parallel or sequence and runs the security gate before reporting done. +color: orange +--- + +You are the orchestrator for the **TcEx CLI 1.0** project (`tcex_cli` — the ThreatConnect Exchange +App command-line tooling: packaging, deploy, run, migrate, template, validate, and spec generation +for ThreatConnect Apps). Your job is to analyze development requests, gather just enough context, and +delegate to the right specialist subagents. **You do not write code yourself** — specialists do. + +Read `/CLAUDE.md` for the +full project conventions (toolchain, absolute-path rules, submodules, testing) when present. +Everything below assumes those rules. + +## Project Layout + +``` +/ # repo root ($PROJECT_ROOT) +├── tcex_cli/ # the CLI package +│ ├── cli/ # Typer command groups +│ │ ├── cli.py # root Typer app +│ │ ├── cli_abc.py # shared CLI base class +│ │ ├── deploy/ deps/ migrate/ package/ run/ +│ │ ├── spec_tool/ template/ validate/ app_input/ model/ +│ ├── app/config/ # submodule +│ ├── pleb/ # submodule +│ ├── requests_tc/ # submodule +│ ├── util/ # submodule +│ ├── input/, logger/, render/, message_broker/ # parent-repo framework code +├── app/ # sample/target App workspace used by run/deploy +├── tests/ # pytest suite (mirrors commands: tests//) +├── .claude/ +│ ├── agents/ # these agent files +│ ├── plans/ # plan files (see below) +│ └── scripts/ # hooks + agent-authored helper scripts +└── pyproject.toml, uv.lock, .venv/ +``` + +Key facts (see CLAUDE.md for detail): Python 3.11+, **pydantic v1**, **uv** workspace, **ty** type +checker, **ruff** linter/formatter, a **typer + rich** CLI, and **four** git submodules +(`tcex_cli/app/config`, `tcex_cli/pleb`, `tcex_cli/requests_tc`, `tcex_cli/util`). Unlike the `tcex` +framework, this project has **no V3 API code generator and no generated code** — every file under +`tcex_cli/` is hand-maintained. + +## Specialist Subagents + +| Subagent | Use for | +|---|---| +| `python-engineer` | **All** updates to the `tcex_cli` codebase under `tcex_cli/` (parent repo **and** submodules): CLI commands, models, framework logic, bug fixes, refactors, type-checker fixes, dependency changes. **Not scripts** (`python-script-specialist`), **not tests** (`python-test-engineer`). | +| `python-test-engineer` | **All** pytest tests under `tests/`: unit tests, fixtures, fakeredis-backed tests, deepdiff comparisons. Does not modify source code. | +| `python-script-specialist` | **Sole author** of every standalone `*.py` script (operator CLIs, audits, data inspection/transform, one-off `temp_` helpers). Applies the typer + rich + dry-run/`--commit` standard. No other specialist writes scripts. | +| `python-security-auditor` | **Hard security gate.** Audits all changes to the highest tier (bandit, secrets, injection, unsafe deserialization, SSRF, dependency CVEs via osv-scanner, eval/exec, `subprocess(shell=True)`, etc.). HIGH/critical findings block completion. Read-only auditor — it reports and sends work back; it does not write the fix. | + +## Your Process + +**1. Scope the request** — use `Read`, `Glob`, and `Grep` to understand which files are involved. +Read relevant source files before writing delegation prompts. Determine whether the change touches a +**submodule** — this changes how the work must be done (see "Special Constraints"). + +**2. Determine if a plan is required** — apply this rule before any implementation: + +### Plan Requirement Rule + +| Change type | Plan required? | +|---|---| +| Simple fix, typo, comment/string change | ❌ No — delegate directly | +| 1–2 line code change with no architectural impact | ❌ No — delegate directly | +| Any other code change (new files/modules, new CLI commands, new features, refactors, multi-file edits, model/schema changes, public-API/CLI-surface changes) | ✅ Yes — write plan first | + +**If a plan IS required:** + +1. **Interactive Discovery (when warranted)** — before writing the plan, resolve genuine ambiguities + or branching design decisions with the user via `AskUserQuestion`. Skip entirely if the request is + fully specified. + + **Trigger discovery when:** the request involves CLI command/option naming or behavior visible to + App developers; multiple modules/submodules could host the change; there's no obvious existing + pattern; there are backward-compatibility trade-offs (rename vs deprecate a command/flag, change a + public model field); test scope is unclear; or the request uses vague language ("clean it up", + "make it better"). + + **Skip discovery when:** the request is fully specified with concrete file/field/value references; + the change is mechanical with one obvious path; or existing patterns make the choice unambiguous. + + **Format:** batch related questions into a single `AskUserQuestion` call (≤4). Each option is a + distinct, mutually-exclusive choice with a 1-sentence trade-off `description`. Mark a preferred + path with `(Recommended)` and place it first. Phrase questions concretely. Do not ask anything + already answered by the request, the codebase, or `CLAUDE.md`. + +2. Write the plan to + `/.claude/plans/YYYY-MM-DD/YYYYMMDD_.md`. + The subdirectory is the calendar date in `YYYY-MM-DD` form; the filename keeps the compact + `YYYYMMDD` prefix. **Always `/opt/homebrew/opt/coreutils/libexec/gnubin/mkdir -p` the date + subdirectory first** — it may not exist. + +3. Every plan opens with YAML frontmatter: + ```yaml + --- + date: YYYY-MM-DDTHH:MM:SSZ # current UTC datetime (ISO 8601) + prompt: "" + branch: "" + affected_areas: [cli, framework, submodule, tests, scripts] # list only what applies + affected_submodules: [] # e.g. [tcex_cli/util, tcex_cli/pleb] — empty if none + requires_dependency_change: false # true if pyproject/uv.lock changes + status: draft # always "draft" until approved + --- + ``` + The `prompt` field records only the **first** user message of the planning session. + +4. The plan body must include, in order: + - `## Conversation Log` — verbatim record of every message exchanged before the plan (both sides), + in chronological order. Omit only if the plan came from a single message with zero back-and-forth. + - Goal + - Files affected (call out submodule files) + - Approach / steps + - Open questions or risks + - `## Discovery` — only if `AskUserQuestion` was used. For each question: + ```markdown + **Q: ** + - **Chosen:** + - **Rationale:** + - **Alternatives considered:** + ``` + - `## Acceptance Criteria` — **always present.** GitHub-flavored checkboxes (`- [ ]`) enumerating + every observable, verifiable outcome. Be concrete and testable, e.g.: + - `- [ ] \`ty check\` passes with no new diagnostics` + - `- [ ] \`pytest tests/template\` passes (N new tests added)` + - `- [ ] \`ruff check\` and \`ruff format --check\` are clean` + - `- [ ] python-security-auditor reports no HIGH/critical findings` + - `- [ ] Submodule \`tcex_cli/util\` change committed and pointer bumped` + + Acceptance criteria must reflect discovery decisions. + +5. **Stop and present the plan for approval.** Show the **full absolute path** to the plan file. End + with: **"Implement the plan?"** Do not delegate until the user replies with exactly + **"Aye Aye Captain!"**. Any other reply (including "yes", "go ahead", "looks good") is not valid — + respond with **"I can't hear you."** and wait for the correct phrase. + +6. Once approved, set frontmatter `status: draft` → `status: approved`, then implement via specialist + delegation. + +**If a plan is NOT required:** delegate directly without a plan file. + +**3. Choose specialists** — a task may require one or more: +- CLI/library change only → `python-engineer` +- New feature or command → `python-engineer` + `python-test-engineer` (parallel only if the code + already exists and tests just need writing; otherwise engineer first, then tests with the new code + paths as context) +- Tests only → `python-test-engineer` +- Any standalone script → `python-script-specialist` (sole script author). If the script needs new + framework support, run `python-engineer` for that first, then the script specialist consumes it. +- **Security gate (always):** after **any** `python-engineer` / `python-test-engineer` / + `python-script-specialist` change completes, run `python-security-auditor`. It is a **hard gate** — + if it returns HIGH or critical findings, route the specific fix back to the appropriate specialist, + then re-run the auditor. Do not report "done" until the auditor is clean. + +**4. Write detailed delegation prompts** — each specialist gets only what it needs: +``` +Context: [what exists, what pattern to follow, submodule?] +Task: [exactly what to implement] +Files to read first: [absolute paths] +Files to create/modify: [absolute paths] +Constraints: [pydantic v1, ty-clean, submodule commit rules, typer/rich conventions, etc.] +Expected output: [files created/modified; commands to run to self-verify] +``` + +**5. Run specialists in parallel only when safe** — independent work (e.g. engineer touched files A, +tests needed for existing files B) can run in parallel. Sequential when one depends on the other's +output (new command/model → tests). + +**6. Verify the acceptance criteria** — after specialists complete, walk every `- [ ]` item: +- Inspectable items → confirm with `Read` / `Grep` (or the pinned `/opt/homebrew/opt/grep/libexec/gnubin/grep`). +- Command items (ty, pytest, ruff) → execute with absolute venv paths and capture output. +- Update the plan in place: `- [ ]` → `- [x]` for satisfied items; leave unsatisfied ones unchecked + with a one-line `> ⚠️ Not verified: …` note. + +**7. Final report** — present a synthesis: +- Short summary of what changed (files created/modified, key decisions, submodules touched). +- The full Acceptance Criteria checklist copied from the updated plan (inline `- [x]`/`- [ ]`). +- The `python-security-auditor` result (must be clean to call it done). +- Items needing user attention (submodule commits + pointer bump, dependency sync, manual review). +- The full absolute path to the plan file. + +## Special Constraints + +- **Fork on `main`:** this local repo is a **fork**, and all work happens directly on the `main` + branch — `main` is the working branch, not a protected trunk. Do not create feature branches and do + not change branches (the `enforce_no_branch_change.sh` hook blocks it). See + `CLAUDE.md → Repository & Branching`. +- **Never commit — the operator commits:** Claude (orchestrator and every specialist) must **never** + run `git commit`, in the parent repo or any submodule. Enforced by `enforce_no_commit.sh` and a + `Bash(git commit:*)` deny rule (no override). Stop at staging: make edits, `git add`, show + `git status` / `git diff --cached`, and **report what is ready plus a suggested commit message** — + the human operator runs `git commit`. For submodules, stage and describe the two-step commit + + pointer bump, but do not perform either commit. +- **Submodules:** a change under a submodule path (`tcex_cli/app/config`, `tcex_cli/pleb`, + `tcex_cli/requests_tc`, `tcex_cli/util`) must be committed inside that submodule first, then the + pointer bumped in the parent. Note this in the plan and the final report. +- **pydantic v1 + ty:** changes must stay ty-clean. Prefer real type fixes; targeted + `# ty: ignore[]` only when unavoidable. Pyright-style `# type: ignore[reportXxx]` codes do not + work. + +## What You Do NOT Do + +- Do not write Python yourself (a 1–2 line inline `-c` for your own context-gathering is fine). +- Do not make architectural decisions without reading existing patterns first. +- Do not spawn a specialist without a specific, scoped task prompt. +- Do not batch unrelated tasks into one specialist invocation. +- Do not begin a plan-required change before the user approves the plan with "Aye Aye Captain!". +- Do not report a task "done" before `python-security-auditor` passes. +- Do not run `git commit` — ever, in the parent repo or any submodule. Stage and report; the operator + commits (see Special Constraints → "Never commit"). + +## Tool Invocation Rules + +Follow `CLAUDE.md → Tool Invocation — Absolute Paths Only` for every Bash call: absolute venv binary +paths (`/.venv/bin/…`), pinned system-utility paths, and no dynamic path substitutions +(`$(git …)`, `$(pwd)`, …). The `PreToolUse` hooks enforce these and will block violations. diff --git a/.claude/patterns/python-engineering-patterns.md b/.claude/patterns/python-engineering-patterns.md new file mode 100644 index 0000000..8942f7e --- /dev/null +++ b/.claude/patterns/python-engineering-patterns.md @@ -0,0 +1,259 @@ +# TcEx 4.0 — Engineering Patterns (`python-engineer`) + +House-style idioms that recur across the TcEx framework. When you add or modify framework code, +match the relevant pattern below so the change is indistinguishable from the surrounding code. +These are **observed, enforced conventions** — not generic advice. Each entry cites real files. + +> Scope note: generated V3 code (`tcex/api/tc/v3/**` `*_model.py`/`*_filter.py`/object files) is owned +> by the generator in `tcex/api/tc/v3/_gen/`. Patterns that appear in generated output are changed by +> editing the generator, never the output (see CLAUDE.md + the agent file). + +--- + +## 1. Module-level logger via the top-level package name + +Every module that logs binds a module-scoped `_logger` by splitting `__name__` on the first dot, so all +records route through the single `tcex` logger hierarchy (and the custom `TraceLogger`). Do **not** call +`logging.getLogger(__name__)` (full dotted path) — always the top-level segment. + +Pervasive: ~49 modules (`requests_tc/requests_tc.py:17`, `app/token/token.py:22`, +`app/playbook/playbook.py:15`, `input/field_type/exception.py`). + +```python +# standard library +import logging + +# first-party +from tcex.logger.trace_logger import TraceLogger + +_logger: TraceLogger = logging.getLogger(__name__.split('.', maxsplit=1)[0]) # type: ignore +``` + +Use the bare `_logger = logging.getLogger(__name__.split('.', maxsplit=1)[0])` form when the `TraceLogger` +annotation/`trace()` calls are not needed; add the annotated form (with `# type: ignore`) when the module +calls `_logger.trace(...)`. + +--- + +## 2. Lazy attributes: `cached_property` vs `scoped_property` (from `tcex.pleb`) + +Two project-specific descriptors back almost all lazily-built attributes. **Pick by lifetime**, and always +import from `tcex.pleb` (not `functools`) so the test suite's `_reset()` can clear them between tests. + +- `from tcex.pleb.cached_property import cached_property` — compute once per instance, cache for the + instance's life. Use for inputs-derived helpers, sub-APIs, parsed files. (~18 modules: `tcex/tcex.py`, + `app/app.py`, `app/playbook/playbook.py`.) +- `from tcex.pleb.scoped_property import scoped_property` — thread/process-aware caching that detects + process forking; use for per-execution resources that must not leak across threads/forks (session, + KV store). (`tcex/tcex.py`, `app/app.py`, `requests_tc/requests_tc.py`.) + +```python +from tcex.pleb.cached_property import cached_property + +@cached_property +def api(self) -> API: + """Return instance of API.""" + return API(self.inputs, self.session.tc) +``` + +> The shared `registry` (`tcex/registry.py`) is a dependency-injection primitive used at the very top of +> the object graph (`tcex/tcex.py`). It is **not** a routine pattern — only wire into it when extending the +> top-level `TcEx` composition, and follow the single existing `@registry.factory(...)` example exactly. + +--- + +## 3. pydantic **v1** model `Config` conventions + +Config-/install-json-facing models share a consistent inner `Config`: a `snake_to_camel` alias generator +(Python snake_case attrs ↔ camelCase JSON), `validate_assignment = True`, and an explicit `Extra` policy. +Match the **same `Extra`** the neighboring models use (`allow` for install.json passthrough, `forbid`/`ignore` +where the surface is closed). + +`app/config/model/install_json_model.py`, `app/config/model/layout_json_model.py`, `input/input.py`. + +```python +from pydantic import BaseModel, Extra +from tcex.util.string_operation import snake_to_camel + +class Config: + """DataModel Config""" + alias_generator = snake_to_camel + validate_assignment = True + extra = Extra.allow +``` + +When serializing v1 models, use the v1 kwargs the codebase relies on: +`model.json(by_alias=True, exclude_defaults=True, exclude_none=True)` / `.dict(by_alias=True)`. + +--- + +## 4. Custom field types: `__get_validators__` chain + `pre`/`always` validators + +Input field types subclass a builtin (`str`, etc.) and expose a validator **chain** from +`__get_validators__`, each step a `@classmethod (cls, value, field: ModelField) -> value` that raises a +custom exception on failure and otherwise returns the (possibly transformed) value. Order matters — type +check first, then transforms, then constraint checks. + +`input/field_type/string.py`, `input/field_type/sensitive.py`, `input/field_type/binary.py`. + +```python +class String(str): + allow_empty: bool = True + max_length: int | None = None + + @classmethod + def __get_validators__(cls) -> Generator: + yield cls.validate_type + yield cls.validate_strip + yield cls.validate_allow_empty + yield cls.validate_max_length + + @classmethod + def validate_allow_empty(cls, value: str, field: ModelField) -> str: + if cls.allow_empty is False and isinstance(value, str) and value == '': + raise InvalidEmptyValue(field_name=field.name) + return value +``` + +### 4a. Constrained-type factory (`type(...)` subclass) + +Pair each field type with a lowercase factory that returns a **configured subclass** via `type('ConstrainedX', (X,), namespace)` — this is how constraints are composed without a new class per combination. + +```python +def string(allow_empty: bool = True, max_length: int | None = None, ...) -> type[String]: + """Return configured instance of String.""" + namespace = {'allow_empty': allow_empty, 'max_length': max_length, ...} + return type('ConstrainedString', (String,), namespace) +``` + +### 4b. Reusable validators are higher-order closures + +Cross-field validators are factory functions returning a closure used with `@validator(..., pre=True)` / +`always=True`. See `always_array(...)` in `input/field_type/validator.py`. + +--- + +## 5. Custom exception hierarchy with `field_name` + trace logging + +Validation/runtime errors subclass a small base that **logs on construction** and carries a `field_name` +for tracing which field failed. Subclasses only build the message. Raise these (never bare `ValueError`) +from field validators. + +`input/field_type/exception.py`. + +```python +class BaseValueError(ValueError): + def __init__(self, field_name: str, message: str): + _logger.trace(f'Checking value for field {field_name}: {message}') + super().__init__(message) + +class InvalidEmptyValue(BaseValueError): + def __init__(self, field_name: str): + super().__init__(field_name, 'an empty value is not allowed for this field') +``` + +--- + +## 6. Self-referential models: `update_forward_refs()` + +Recursive models declare the forward ref and call `update_forward_refs()` at module bottom (v1 requirement). +Do this for any model that references itself or a not-yet-defined sibling. + +`input/field_type/key_value.py`, `app/config/model/layout_json_model.py`. + +```python +KeyValue = ForwardRef('KeyValue') + +class KeyValue(BaseModel): + key: str + value: 'list[KeyValue] | KeyValue | str | None' + +KeyValue.update_forward_refs() +``` + +--- + +## 7. Composed input models via mixins + `input_model()` factory + +The runtime input model is assembled from focused mixin models (`ApiModel`, `BatchModel`, `LoggingModel`, +`ProxyModel`, …) into umbrella models (`CommonModel`, `CommonAdvancedModel`), then finalized by the +`input_model(models)` factory that dynamically subclasses them and adds the common `tc_*` fields. Add new +input surface as a focused mixin and compose it — do not bolt fields onto an umbrella model directly. + +`input/model/common_model.py`, `input/model/api_model.py`, `input/input.py`. + +```python +class CommonModel(ApiModel, BatchModel, CertModel, LoggingModel, PathModel, ProxyModel): + """Model Definition""" +``` + +--- + +## 8. Interface via ABC + runtime implementation selection + +Pluggable subsystems define an `ABC` with `@abstractmethod`s, and a holder picks the concrete +implementation at runtime behind a `cached_property` (redis / api / mock). Add a new backend by +implementing the ABC and extending the selector — keep the public method surface identical to the ABC. + +`app/key_value_store/key_value_abc.py`, `app/key_value_store/key_value_store.py`. + +```python +class KeyValueABC(ABC): + @abstractmethod + def create(self, context: str, key: str, value: Any) -> int: ... + +class KeyValueStore: + @cached_property + def client(self) -> KeyValueApi | KeyValueMock | KeyValueRedis: + if self.tc_kvstore_type == 'Redis': + return KeyValueRedis(self.redis_client) + ... +``` + +--- + +## 9. Conditional/late imports for heavy or app-type-specific modules + +Branch-selected heavy modules (services) are imported **inside** the property that needs them, with +`# noqa: PLC0415`, to avoid hard import-time dependencies. Use this only for genuinely conditional/heavy +imports — normal imports stay at module top, isort-ordered into `# standard library` / `# third-party` / +`# first-party` blocks. + +`app/app.py` (`service` property). + +```python +@cached_property +def service(self) -> 'ApiService | CommonServiceTrigger | WebhookTriggerService': + if self.install_json.model.is_api_service_app: + from tcex.app.service import ApiService as Service # noqa: PLC0415 + ... +``` + +--- + +## 10. V3 generator structure (when touching `tcex/api/tc/v3/_gen/`) + +The generator is the hand-written source for all generated V3 code. It is organized as ABC base classes +(`GenerateABC`, `GenerateModelABC`, `GenerateObjectABC`, …) that carry a `self.requirements` import map +(`'standard library' / 'third-party' / 'first-party'`) and emit code from per-type subclasses. To change +generated output, change the emitting method here, then regenerate and run `pre-commit run --all-files` +(generator output is post-formatted by ruff/isort — that combination is the committed state). + +`api/tc/v3/_gen/_gen_model_abc.py`, `api/tc/v3/_gen/_gen_object_abc.py`, `api/tc/v3/_gen/_gen_abc.py`. + +--- + +### Import-block convention (applies everywhere) + +Imports are grouped and commented in three isort sections; keep the comments: + +```python +# standard library +import logging + +# third-party +from pydantic import BaseModel + +# first-party +from tcex.pleb.cached_property import cached_property +``` diff --git a/.claude/patterns/python-testing-patterns.md b/.claude/patterns/python-testing-patterns.md new file mode 100644 index 0000000..253153a --- /dev/null +++ b/.claude/patterns/python-testing-patterns.md @@ -0,0 +1,208 @@ +# TcEx 4.0 — Testing Patterns (`python-test-engineer`) + +House-style idioms that recur across the `tests/` suite. New tests must match these so they read like the +existing suite and stay deterministic under `pytest -n auto` (xdist). Each entry cites real files. + +--- + +## 1. Parametrize style — **REQUIRED** (mandated standard) + +**Every** `@pytest.mark.parametrize` must use the explicit, keyword form below — named `argnames` / +`argvalues`, every case wrapped in `pytest.param(...)` with a human-readable `id=`. Do **not** use the bare +inline-tuple form, positional args, or the comma-string-ids shortcut. A leading comment on each +`pytest.param` describing the case is encouraged. + +```python +@pytest.mark.parametrize( + argnames='key,ciphertext,iv,expected', + argvalues=[ + pytest.param( + # iv of None + 'ajfmuyodhscwegea', + b'0\x8e`\x8d%\x9f\x8c\xdf\x004\xc1\x1a\x82\xbd\x89\n', + None, + b'blah', + id='pass-iv-none', + ), + pytest.param( + # iv of bytes + 'ajfmuyodhscwegea', + b'0\x8e`\x8d%\x9f\x8c\xdf\x004\xc1\x1a\x82\xbd\x89\n', + b'\0' * 16, + b'blah', + id='pass-iv-bytes', + ), + pytest.param( + # iv of string + 'ajfmuyodhscwegea', + b'0\x8e`\x8d%\x9f\x8c\xdf\x004\xc1\x1a\x82\xbd\x89\n', + '\0' * 16, + b'blah', + id='pass-iv-string', + ), + ], +) +def test_decrypt_aes_cbc(self, key, ciphertext, iv, expected): + ... +``` + +Rules: +- `argnames=` is a single comma-separated string; `argvalues=` is a list of `pytest.param(...)`. +- Every `pytest.param` ends with an explicit `id='...'` using lowercase, hyphenated, descriptive slugs + (e.g. `pass-iv-none`, `fail-empty-value`). Prefix `pass-` / `fail-` when a suite mixes success and error + cases. +- Positional values in `pytest.param` line up with `argnames` order. +- For expected-failure rows, attach the marker to the row: `pytest.param(..., marks=pytest.mark.xfail(reason='...'), id='...')`. + +> Note: most existing tests predate this standard and still use inline tuples — do **not** treat those as the +> template. New and modified parametrized tests use the form above. + +--- + +## 2. Fixtures + `MockApp` factory for building a configured `TcEx` + +Tests obtain a configured `TcEx` through fixtures in `tests/conftest.py` (`tcex`, `tcex_hmac`, +`tcex_proxy`, `playbook_app`, `service_app`), which build instances via the `MockApp` helper +(`tests/mock_app.py`). The `playbook_app` / `service_app` fixtures are **callable factories** — call them +with config to get a fresh app, and read `.tcex` off the result. Prefer these over hand-constructing `TcEx`. + +`tests/conftest.py`, `tests/mock_app.py`, `tests/app/playbook/test_playbook.py`. + +```python +def test_x(self, playbook_app: Callable[..., MockApp]): + tcex = playbook_app(config_data={'tc_playbook_out_variables': [...]}).tcex + playbook = tcex.app.playbook +``` + +--- + +## 3. Reset cached/scoped/registry state between tests + +Fresh-`TcEx` fixtures call `_reset_modules()` first, which clears the three project descriptors so cached +state never leaks across tests (critical under xdist). When a test class manages its own `TcEx`, reset the +same three in `setup_method`. + +`tests/conftest.py:34` (`_reset_modules`). + +```python +def _reset_modules(): + registry._reset() + cached_property._reset() + scoped_property._reset() +``` + +--- + +## 4. fakeredis instead of a live Redis + +Redis-backed code is tested against `fakeredis` — never a real server. `conftest.py` swaps +`RedisClient.client` for a `fakeredis.FakeRedis()` globally in `pytest_configure`, and a `redis_client` +fixture returns one directly. Read/assert KV state through the normal client API. + +`tests/conftest.py` (`pytest_configure`, `redis_client`). + +```python +data = tcex.app.key_value_store.redis_client.hgetall(context) +``` + +--- + +## 5. `DeepDiff` for structural comparison + +Compare nested dicts / serialized pydantic models with `deepdiff.DeepDiff(..., ignore_order=True)` and +assert the diff is empty with a message naming the subject. Use this instead of brittle field-by-field +equality for JSON-model validation. + +`tests/app/config/test_tcex_json_model.py`, `tests/app/config/test_install_json_model.py`. + +```python +ddiff = DeepDiff( + expected, + json.loads(model.json(by_alias=True, exclude_defaults=True, exclude_none=True)), + ignore_order=True, +) +assert not ddiff, f'Failed validation of file {fqfn.name}' +``` + +--- + +## 6. Shared test base classes for a family of cases + +A test family that repeats setup/validation logic factors it into a base class (e.g. `InputTest` in +`tests/input/field_type/util.py`) with `@staticmethod`/helper methods; concrete test classes subclass it. +Reuse the existing base for new field-type tests rather than re-deriving staging logic. + +`tests/input/field_type/util.py` (`InputTest`), `tests/input/field_type/test_field_type_ip_address.py`. + +--- + +## 7. `monkeypatch` + small mock response classes + +Mock API/session behavior with `monkeypatch.setattr(...)`, returning lightweight mock classes that mimic the +real response surface (`.ok`, `.status_code`, `.json()`). Keep reusable mocks beside the tests +(e.g. `tests/.../mock_post.py`). Never hit a live network. + +`tests/api/tc/v2/datastore/test_datastore.py`, `tests/api/tc/v2/datastore/mock_post.py`. + +```python +def mp_post(*args, **kwargs): + return MockPost({}, ok=False) + +monkeypatch.setattr(tcex.session.tc, 'post', mp_post) +``` + +--- + +## 8. `caplog` for log assertions + +Assert on emitted logs with the `caplog` fixture (`caplog.text`, `caplog.records`, `caplog.at_level(...)`) +rather than capturing stdout. + +`tests/logger/test_logger.py`, `tests/logger/test_api_handler.py`. + +```python +def test_logger_level(tcex: TcEx, caplog: pytest.LogCaptureFixture): + assert 'DEBUG LOGGING' in caplog.text + assert any(r.levelno == logging.DEBUG for r in caplog.records) +``` + +--- + +## 9. Isolation: `tmp_path` + autouse working-directory fixture + +Every test runs in its own temp working directory via the `autouse` `change_test_dir` fixture +(keyed on `request.node.name`); tests that create files take `tmp_path` and build paths under it. Never +write to a shared/fixed path — it breaks parallel workers. + +`tests/conftest.py` (`change_test_dir`), `tests/pleb/test_cached_property_filesystem.py`. + +--- + +## 10. Assertion messages carry context + +Equality assertions include an f-string message echoing actual vs expected so xdist failures are diagnosable +from the summary line alone. + +```python +assert result == expected, f'Input {string} result of {result} != {expected}' +``` + +`tests/util/test_string_operation_to_bool.py`, `tests/app/playbook/test_playbook.py`. + +--- + +## 11. `setup_class` for static data, `setup_method` for per-test reset; `@staticmethod` tests + +Use `setup_class` for immutable shared test data and `setup_method` for per-test cleanup (resetting +scoped/cached/registry state). Test methods that need only fixtures (no `self` state) are written as +`@staticmethod`. + +`tests/app/playbook/test_playbook.py`, `tests/api/tc/v2/batch/test_batch.py`, +`tests/requests_tc/test_session_tc.py`. + +--- + +### Layout & determinism (always) +- Mirror the package: a test for `tcex//...` lives in `tests//...`; create the area dir if absent. +- ruff-clean + ruff-formatted (`tests/` is excluded from ty). +- No order dependence, no shared mutable globals, no real network/redis — xdist-safe by construction. diff --git a/.claude/scripts/audit_v3_model_field_drift.py b/.claude/scripts/audit_v3_model_field_drift.py new file mode 100755 index 0000000..7492a56 --- /dev/null +++ b/.claude/scripts/audit_v3_model_field_drift.py @@ -0,0 +1,297 @@ +#!/usr/bin/env python3 +"""Audit drift between the live TC server's v3 OPTIONS //fields and the generated models. + +READ-ONLY: this script only issues OPTIONS/GET requests to the configured ThreatConnect server +(creds/host come from the environment — load your .env before running). It mutates nothing, so the +dry-run/--commit convention does not apply. + +For every introspectable v3 object type it compares: + - server fields: {f['name'] for f in v3_obj.fields} (OPTIONS //fields) + - model props: set(v3_obj.properties.keys()) (OPTIONS /) +and reports server-has / model-lacks drift, mirroring the semantics of the interface test +``TestV3.obj_api_options`` in tests/api/tc/v3/v3_helpers.py (same ``ignore_fields`` and the same +per-object name remappings — the latter surfaced as a note rather than silently dropped). +""" + +from __future__ import annotations + +# standard library +import inspect +from typing import TYPE_CHECKING + +# third-party +import typer +from rich.console import Console +from rich.panel import Panel +from rich.table import Table + +# NOTE: This audit targets the *tcex framework* repo (it depends on the generated v3 models and the +# v3 interface-test helper ``tests.api.tc.v3.v3_helpers.V3Helper``). Those do not exist in this +# tcex_cli repo, which has no v3 API code generator. The import is therefore guarded so a static +# ``ty check`` resolves cleanly here, while running the script in a repo without V3Helper fails +# loudly (see ``_load_v3_helper``). It is a candidate for removal from tcex_cli. +if TYPE_CHECKING: + # first-party + from tests.api.tc.v3.v3_helpers import V3Helper # ty: ignore[unresolved-import] + +_V3HELPER_MISSING_MSG = ( + 'V3Helper (tests.api.tc.v3.v3_helpers) is not importable. This audit only runs in the tcex ' + 'framework repo, which provides the generated v3 models and that test helper; it does not ' + 'belong to tcex_cli. Run it from the tcex repo with its .venv and a loaded .env.' +) + + +def _load_v3_helper() -> type[V3Helper]: + """Import and return the V3Helper class, failing loudly if it is unavailable. + + V3Helper lives in the tcex framework repo's test suite; this script must be run from there. + """ + try: + # first-party + from tests.api.tc.v3.v3_helpers import ( # noqa: PLC0415 # ty: ignore[unresolved-import] + V3Helper as _V3Helper, + ) + except ImportError as ex: # pragma: no cover - environment guard + raise RuntimeError(_V3HELPER_MISSING_MSG) from ex + return _V3Helper + + +app = typer.Typer( + add_completion=False, + help='Audit drift between the live TC v3 schema and the generated v3 models (read-only).', +) +console = Console() +err_console = Console(stderr=True) + +# Fields the interface test ignores entirely (never reported as drift). +IGNORE_FIELDS: frozenset[str] = frozenset({'AIsummary'}) + +# Object types V3Helper knows how to import but that are not simple introspectable objects +# (parent/aggregate modules or non-object helpers). Skipped from the audit. +SKIP_OBJECTS: frozenset[str] = frozenset( + { + 'batch', + 'case_management', + 'security', + 'threat_intelligence', + 'ti_transform', + 'v3', + } +) + +# Static mirror of the per-object name remappings applied inside TestV3.obj_api_options. A server +# field appearing here is a *known* /fields-vs-/ discrepancy the test explicitly handles +# (it maps the server name onto one or more model property names) — it is real drift on the raw +# names, but already accounted for. We surface it as a note so true (unhandled) drift stands out. +KNOWN_REMAPS: dict[str, dict[str, list[str]]] = { + 'exclusion_lists': { + # 'values' is removed from the comparison entirely. + 'values': [], + }, + 'artifacts': { + 'analytics': [ + 'analyticsPriority', + 'analyticsPriorityLevel', + 'analyticsScore', + 'analyticsStatus', + 'analyticsType', + ], + }, + 'groups': { + 'aliases': ['commonGroup'], + 'common': ['commonGroup'], + 'linkedGroups': ['commonGroup'], + 'references': ['commonGroup'], + 'intelReviews': ['reviews'], + 'userDetails': ['createdBy'], + 'externalDates': ['externalDateAdded', 'externalDateExpires', 'externalLastModified'], + 'sightings': ['firstSeen', 'lastSeen'], + }, + 'indicators': { + 'associationName': ['customAssociationNames'], + 'genericCustomIndicatorValues': ['value1', 'value2', 'value3'], + 'threatAssess': [ + 'threatAssessConfidence', + 'threatAssessRating', + 'threatAssessScore', + 'threatAssessScoreFalsePositive', + 'threatAssessScoreObserved', + ], + 'whoIs': ['whois'], + 'userDetails': ['createdBy'], + 'externalDates': ['externalDateAdded', 'externalDateExpires', 'externalLastModified'], + 'sightings': ['firstSeen', 'lastSeen'], + }, + 'cases': { + 'userDetails': ['createdBy'], + }, + 'intel_requirements': { + 'userDetails': ['createdBy'], + }, + 'results': { + 'intelRequirementDetails': ['intelRequirement'], + 'intelRequirementId': ['intelReqId'], + }, +} + + +def _candidate_objects() -> list[str]: + """Return the sorted v3 object types V3Helper can import, minus the skip list.""" + # _module_map keeps its known object types in a static dict inside the staticmethod; reconstruct + # the candidate set from that source rather than re-listing the names here (keeps them in sync). + v3_helper = _load_v3_helper() + source = inspect.getsource(v3_helper._module_map) # noqa: SLF001 — reuse the test's module map + names: list[str] = [] + for raw_line in source.splitlines(): + line = raw_line.strip() + # Keys are written as "'': {" at the top level of the _modules dict. + if line.endswith("': {") and line.startswith("'"): + name = line[1 : line.index("'", 1)] + if name not in SKIP_OBJECTS: + names.append(name) + return sorted(set(names)) + + +def _remap_note(obj: str, field: str) -> str: + """Return a human note if a drifted field is a known-handled remap, else empty string.""" + remaps = KNOWN_REMAPS.get(obj, {}) + if field not in remaps: + return '' + targets = remaps[field] + if not targets: + return 'known-remapped (dropped from check)' + return f'known-remapped -> {", ".join(targets)}' + + +def _audit_object(obj: str) -> dict: + """Introspect a single v3 object and return its drift record. + + Raises on any introspection failure so the caller can record it as unintrospectable. + """ + v3_helper = _load_v3_helper() + helper = v3_helper(obj) + v3_obj = helper.v3_obj + + server_fields = {f.get('name') for f in v3_obj.fields if f.get('name')} + model_props = set(v3_obj.properties.keys()) + + # drift = server-has, model-lacks, minus globally ignored fields. + drift = sorted(server_fields - model_props - set(IGNORE_FIELDS)) + + return { + 'object': obj, + 'server_count': len(server_fields), + 'model_count': len(model_props), + 'drift': drift, + } + + +def _render(records: list[dict], errors: list[tuple[str, str]]) -> None: + """Render the per-object table, the error table, and the summary panel.""" + table = Table(title='v3 model field drift (server-has / model-lacks)', show_lines=True) + table.add_column('object', style='cyan', no_wrap=True) + table.add_column('server fields', justify='right') + table.add_column('model props', justify='right') + table.add_column('missing-from-model', style='yellow') + table.add_column('note', style='dim') + + objects_with_drift = 0 + total_drifted = 0 + + for rec in records: + drift = rec['drift'] + if drift: + objects_with_drift += 1 + total_drifted += len(drift) + notes = [] + for field in drift: + note = _remap_note(rec['object'], field) + if note: + notes.append(f'{field}: {note}') + missing = '\n'.join(drift) + note_text = '\n'.join(notes) if notes else '' + row_style = 'bold' + else: + missing = '[green]—[/]' + note_text = '' + row_style = '' + + table.add_row( + rec['object'], + str(rec['server_count']), + str(rec['model_count']), + missing, + note_text, + style=row_style, + ) + + console.print(table) + + if errors: + etable = Table(title='objects that could not be introspected', show_lines=False) + etable.add_column('object', style='cyan', no_wrap=True) + etable.add_column('error', style='red') + for obj, msg in errors: + etable.add_row(obj, msg) + console.print(etable) + + summary = ( + f'objects audited: [bold]{len(records)}[/] ' + f'objects with drift: [bold yellow]{objects_with_drift}[/] ' + f'total drifted fields: [bold yellow]{total_drifted}[/] ' + f'unintrospectable: [bold red]{len(errors)}[/]' + ) + console.print(Panel(summary, title='summary', expand=False)) + + +@app.command() +def main( + object_: str | None = typer.Option( + None, + '--object', + help="Audit a single v3 object type (e.g. 'groups'). Default: audit all known types.", + ), +) -> None: + """Compare the live v3 server schema against the generated models and report field drift.""" + if object_ is not None: + if object_ in SKIP_OBJECTS: + err_console.print( + f"[red]error[/] — '{object_}' is not an introspectable simple object " + f'(it is in the skip list: {", ".join(sorted(SKIP_OBJECTS))}).' + ) + raise typer.Exit(code=2) + # Validate against the known module map. + if not _load_v3_helper()._module_map(object_): # noqa: SLF001 — reuse test's module map + valid = ', '.join(_candidate_objects()) + err_console.print( + f"[red]error[/] — unknown v3 object '{object_}'. Known objects: {valid}" + ) + raise typer.Exit(code=2) + targets = [object_] + else: + targets = _candidate_objects() + + if not targets: + err_console.print('[red]error[/] — no v3 object types to audit.') + raise typer.Exit(code=2) + + console.print(f'[dim]auditing {len(targets)} object type(s)...[/]') + + records: list[dict] = [] + errors: list[tuple[str, str]] = [] + for obj in targets: + try: + records.append(_audit_object(obj)) + except Exception as ex: # record and continue rather than crash the whole audit + errors.append((obj, f'{type(ex).__name__}: {ex}')) + + if not records: + err_console.print('[red]error[/] — no objects could be introspected (check TC creds/host).') + _render(records, errors) + raise typer.Exit(code=1) + + _render(records, errors) + + +if __name__ == '__main__': + app() diff --git a/.claude/scripts/enforce_no_branch_change.sh b/.claude/scripts/enforce_no_branch_change.sh new file mode 100755 index 0000000..17c63b2 --- /dev/null +++ b/.claude/scripts/enforce_no_branch_change.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash +# +# PreToolUse hook for Bash — hard-blocks any git command that would CHANGE +# (switch to / create / detach onto) a git branch. Claude Code must NEVER change +# branches in this repo. +# +# Blocked: +# git switch ... (branch switch/create — always) +# git switch -c/-C ... (create + switch) +# git checkout (switch) +# git checkout -b/-B ... (create + switch) +# git checkout (detached HEAD) +# git checkout --detach ... (detached HEAD) +# git worktree add ... (creates a new branch/worktree checkout) +# +# Explicitly ALLOWED (read-only / non-branch-changing): +# git branch ... (list/create-without-switch — listing is read-only) +# git status, git rev-parse, git log, git diff, git fetch, git pull, etc. +# git checkout -- (file restore; the "--" pathspec separator) +# git checkout --

(file restore from a ref) +# git restore ... (modern file restore — never matched) +# +# The check is segment-aware: the command is split on shell separators +# (| || && ; |&) so a blocked git invocation hidden after a pipe/`&&` is still +# caught (mirrors enforce_pinned_paths.sh). +# +# Stdin: JSON envelope with `.tool_input.command`. +# Exit codes: +# 0 = allow +# 2 = block (stderr shown to the model so it can correct) + +set -euo pipefail + +PAYLOAD="$(cat)" +CMD="$(printf '%s' "$PAYLOAD" | jq -r '.tool_input.command // ""')" + +[[ -z "$CMD" ]] && exit 0 + +# Split the command into segments on shell separators: | || && ; |& +# Replace each separator with a newline, then iterate segment-by-segment. +SEGMENTS="$(printf '%s' "$CMD" | /opt/homebrew/opt/gnu-sed/libexec/gnubin/sed -E 's/\|\||&&|\|&|[|;&]/\n/g')" + +block() { + local reason="$1" segment="$2" + cat >&2 < (restore a file; note the "--" separator) + - git restore (modern file restore) + +If a branch change is genuinely required, the human operator must run it +themselves — this hook intentionally has no override. +EOF + exit 2 +} + +# Matches `git`, then any number of git GLOBAL options (e.g. `-C `, +# `-c k=v`, `--no-pager`, `--git-dir=...`), up to (but not including) the +# SUBCOMMAND token. The subcommand the caller wants follows immediately after. +# Anchoring this way means words like "switch"/"checkout" inside a commit +# message (`git commit -m 'switch to ...'`) are NOT misread as the subcommand. +SUBCMD_PREFIX='(^|[[:space:]])git([[:space:]]+(-[Cc][[:space:]]+[^[:space:]]+|--[^[:space:]=]+(=[^[:space:]]+)?|-[A-Za-z]))*[[:space:]]+' + +while IFS= read -r SEG; do + # Trim leading/trailing whitespace. + SEG="${SEG#"${SEG%%[![:space:]]*}"}" + SEG="${SEG%"${SEG##*[![:space:]]}"}" + [[ -z "$SEG" ]] && continue + + # Only consider segments that invoke git. + [[ "$SEG" =~ (^|[[:space:]])git([[:space:]]|$) ]] || continue + + # git worktree add — creates a new checkout/branch. + if [[ "$SEG" =~ ${SUBCMD_PREFIX}worktree[[:space:]]+add([[:space:]]|$) ]]; then + block "git worktree add creates a new branch/worktree checkout." "$SEG" + fi + + # git switch — its sole purpose is switching/creating branches. Always block. + if [[ "$SEG" =~ ${SUBCMD_PREFIX}switch([[:space:]]|$) ]]; then + block "git switch changes the current branch." "$SEG" + fi + + # git checkout — block UNLESS it is a file-restore using the "--" pathspec + # separator (" -- " between args, or a trailing " --"). + if [[ "$SEG" =~ ${SUBCMD_PREFIX}checkout([[:space:]]|$) ]]; then + if [[ "$SEG" =~ [[:space:]]--[[:space:]] ]] || [[ "$SEG" =~ [[:space:]]--$ ]]; then + # File-restore form (git checkout [] -- ) — allowed. + continue + fi + block "git checkout switches/creates/detaches a branch." "$SEG" + fi +done <<< "$SEGMENTS" + +exit 0 diff --git a/.claude/scripts/enforce_no_commit.sh b/.claude/scripts/enforce_no_commit.sh new file mode 100755 index 0000000..a3305a7 --- /dev/null +++ b/.claude/scripts/enforce_no_commit.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +# +# PreToolUse hook for Bash — hard-blocks any git command that would CREATE a +# commit. In this repo, Claude Code must NEVER commit; ALL commits are made by +# the human operator. +# +# Blocked (in the parent repo AND in any submodule via `git -C ...`): +# git commit ... (create a commit — always) +# git commit --amend ... (rewrite the last commit) +# git commit -m / -a / ... (any commit form) +# +# Explicitly ALLOWED (read-only / non-committing): +# git add / git restore --staged / git reset (staging is fine — operator commits) +# git status, git diff, git log, git show, git rev-parse, git fetch, etc. +# git stash (push/list/show) (does not create a branch commit history entry the user cares about) +# +# The check is segment-aware: the command is split on shell separators +# (| || && ; |&) so a `git commit` hidden after a pipe/`&&` is still caught +# (mirrors enforce_no_branch_change.sh). +# +# Stdin: JSON envelope with `.tool_input.command`. +# Exit codes: +# 0 = allow +# 2 = block (stderr shown to the model so it can correct) + +set -euo pipefail + +PAYLOAD="$(cat)" +CMD="$(printf '%s' "$PAYLOAD" | jq -r '.tool_input.command // ""')" + +[[ -z "$CMD" ]] && exit 0 + +# Split the command into segments on shell separators: | || && ; |& +SEGMENTS="$(printf '%s' "$CMD" | /opt/homebrew/opt/gnu-sed/libexec/gnubin/sed -E 's/\|\||&&|\|&|[|;&]/\n/g')" + +block() { + local segment="$1" + cat >&2 < (stage changes) + - git status / git diff (inspect what will be committed) + - git restore --staged

(unstage) + +Leave the working tree/index staged as needed and report what is ready to +commit — the human operator will run 'git commit' themselves. This hook +intentionally has no override. +EOF + exit 2 +} + +# Matches `git`, then any number of git GLOBAL options (e.g. `-C `, +# `-c k=v`, `--no-pager`, `--git-dir=...`), up to (but not including) the +# SUBCOMMAND token. Anchoring this way means the word "commit" inside a commit +# message or a pathspec (`git log --grep 'commit'`) is NOT misread as the +# subcommand. +SUBCMD_PREFIX='(^|[[:space:]])git([[:space:]]+(-[Cc][[:space:]]+[^[:space:]]+|--[^[:space:]=]+(=[^[:space:]]+)?|-[A-Za-z]))*[[:space:]]+' + +while IFS= read -r SEG; do + # Trim leading/trailing whitespace. + SEG="${SEG#"${SEG%%[![:space:]]*}"}" + SEG="${SEG%"${SEG##*[![:space:]]}"}" + [[ -z "$SEG" ]] && continue + + # Only consider segments that invoke git. + [[ "$SEG" =~ (^|[[:space:]])git([[:space:]]|$) ]] || continue + + # git commit — always block (covers -m, -a, --amend, -C, etc.). + if [[ "$SEG" =~ ${SUBCMD_PREFIX}commit([[:space:]]|$) ]]; then + block "$SEG" + fi +done <<< "$SEGMENTS" + +exit 0 diff --git a/.claude/scripts/enforce_no_dynamic_paths.sh b/.claude/scripts/enforce_no_dynamic_paths.sh new file mode 100644 index 0000000..a3b0ec4 --- /dev/null +++ b/.claude/scripts/enforce_no_dynamic_paths.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +# +# PreToolUse hook for Bash — rejects commands that resolve paths dynamically +# via shell substitutions like $(git rev-parse), $(pwd), $(realpath ...). +# +# The workspace root is fixed per project ($CLAUDE_PROJECT_DIR) and the venv at +# /.venv; both must be written as literal absolute paths in commands. +# Dynamic resolution defeats the consistency we just established and re-introduces +# the variant-explosion problem (each unique substitution becomes its own +# permission rule because the literal command string differs). +# +# Stdin: a JSON envelope with `.tool_input.command` set to the proposed command. +# Exit codes: +# 0 = allow the tool call +# 2 = block the tool call; stderr is shown to the model so it can correct + +set -euo pipefail + +PAYLOAD="$(cat)" +CMD="$(printf '%s' "$PAYLOAD" | jq -r '.tool_input.command // ""')" + +# Match $(...) substitutions whose first token is a path-resolution tool. +# Pattern: literal "$(", optional whitespace, then git|pwd|realpath|readlink|cd, +# then a word boundary (whitespace or close-paren). +DYN_PARENS='\$\([[:space:]]*(git|pwd|realpath|readlink|cd)([[:space:]]|\))' + +# Same pattern via backticks. +DYN_TICKS='`[[:space:]]*(git|pwd|realpath|readlink|cd)([[:space:]]|`)' + +if [[ "$CMD" =~ $DYN_PARENS ]] || [[ "$CMD" =~ $DYN_TICKS ]]; then + ROOT="${CLAUDE_PROJECT_DIR:-the workspace root}" + { + printf '[enforce_no_dynamic_paths] BLOCKED: dynamic path resolution detected.\n\n' + printf 'The workspace root is fixed at %s. Do NOT use:\n' "$ROOT" + cat <<'EOF' + $(git rev-parse --show-toplevel) + $(pwd) + $(realpath ...) + $(readlink -f ...) + $(cd ... && pwd) + `git rev-parse ...` (backticks) +EOF + printf '\nSubstitute the literal absolute workspace path directly. Examples:\n\n' + printf ' # WRONG — resolves dynamically\n' + printf ' "$(git rev-parse --show-toplevel)/.venv/bin/python" ...\n' + printf ' cd "$(pwd)" && ...\n\n' + printf ' # CORRECT — hardcoded absolute path\n' + printf ' %s/.venv/bin/python ...\n' "$ROOT" + printf '\nWhy: every unique substitution becomes a distinct permission rule.\n' + printf 'Writing the absolute workspace path collapses those into single rules.\n' + printf 'See %s/CLAUDE.md → "Tool Invocation — Absolute Paths Only".\n' "$ROOT" + } >&2 + exit 2 +fi + +exit 0 diff --git a/.claude/scripts/enforce_pinned_paths.sh b/.claude/scripts/enforce_pinned_paths.sh new file mode 100644 index 0000000..a5d6519 --- /dev/null +++ b/.claude/scripts/enforce_pinned_paths.sh @@ -0,0 +1,128 @@ +#!/usr/bin/env bash +# +# PreToolUse hook for Bash — rejects commands that contain a bare-name +# invocation of a system utility that should be invoked via its absolute path. +# +# Stdin: a JSON envelope with `.tool_input.command` set to the proposed command. +# Exit codes: +# 0 = allow the tool call +# 2 = block the tool call; stderr is shown to the model so it can correct +# +# Rationale: Claude Code permission rules in settings.local.json match by +# literal command-string prefix. `Bash(grep *)` and +# `Bash(/opt/homebrew/opt/grep/libexec/gnubin/grep *)` are different rules. +# Picking one form per command and enforcing it keeps the allowlist small and +# deterministic. See $CLAUDE_PROJECT_DIR/CLAUDE.md → +# "Tool Invocation — Absolute Paths Only" for the convention. +# +# Scope: this hook checks the FIRST TOKEN of EACH COMMAND SEGMENT — segments +# are split on the operators | || && ; |&. So `cmd | tail -30` blocks because +# the `tail` segment uses a bare name, even though the leading `cmd` is fine. +# Mid-argument bare names (e.g. the `cat` in `xargs cat ...`) are not rewritten. +# +# Edge case: pipes inside quoted strings (`grep "a|b"`) will be split, which +# may produce a false positive. Quote elsewhere or escape the literal pipe. + +set -euo pipefail + +PAYLOAD="$(cat)" +CMD="$(printf '%s' "$PAYLOAD" | jq -r '.tool_input.command // ""')" + +# Pinned utilities: bare-name → required absolute path. +# These are the Homebrew GNU builds standardized for all tcex projects on this +# workstation. Keep this list in sync with $CLAUDE_PROJECT_DIR/CLAUDE.md → +# "System utilities — pinned to Homebrew GNU paths". +declare -A PINNED=( + # coreutils → /opt/homebrew/opt/coreutils/libexec/gnubin/ + [cat]=/opt/homebrew/opt/coreutils/libexec/gnubin/cat + [head]=/opt/homebrew/opt/coreutils/libexec/gnubin/head + [tail]=/opt/homebrew/opt/coreutils/libexec/gnubin/tail + [sort]=/opt/homebrew/opt/coreutils/libexec/gnubin/sort + [uniq]=/opt/homebrew/opt/coreutils/libexec/gnubin/uniq + [wc]=/opt/homebrew/opt/coreutils/libexec/gnubin/wc + [cut]=/opt/homebrew/opt/coreutils/libexec/gnubin/cut + [tr]=/opt/homebrew/opt/coreutils/libexec/gnubin/tr + [ls]=/opt/homebrew/opt/coreutils/libexec/gnubin/ls + [cp]=/opt/homebrew/opt/coreutils/libexec/gnubin/cp + [mv]=/opt/homebrew/opt/coreutils/libexec/gnubin/mv + [rm]=/opt/homebrew/opt/coreutils/libexec/gnubin/rm + [mkdir]=/opt/homebrew/opt/coreutils/libexec/gnubin/mkdir + [chmod]=/opt/homebrew/opt/coreutils/libexec/gnubin/chmod + [touch]=/opt/homebrew/opt/coreutils/libexec/gnubin/touch + [ln]=/opt/homebrew/opt/coreutils/libexec/gnubin/ln + [stat]=/opt/homebrew/opt/coreutils/libexec/gnubin/stat + [env]=/opt/homebrew/opt/coreutils/libexec/gnubin/env + [date]=/opt/homebrew/opt/coreutils/libexec/gnubin/date + [basename]=/opt/homebrew/opt/coreutils/libexec/gnubin/basename + [dirname]=/opt/homebrew/opt/coreutils/libexec/gnubin/dirname + [tee]=/opt/homebrew/opt/coreutils/libexec/gnubin/tee + [echo]=/opt/homebrew/opt/coreutils/libexec/gnubin/echo + [printf]=/opt/homebrew/opt/coreutils/libexec/gnubin/printf + [du]=/opt/homebrew/opt/coreutils/libexec/gnubin/du + # grep → /opt/homebrew/opt/grep/libexec/gnubin/ + [grep]=/opt/homebrew/opt/grep/libexec/gnubin/grep + # findutils → /opt/homebrew/opt/findutils/libexec/gnubin/ + [find]=/opt/homebrew/opt/findutils/libexec/gnubin/find + [xargs]=/opt/homebrew/opt/findutils/libexec/gnubin/xargs + # gnu-sed → /opt/homebrew/opt/gnu-sed/libexec/gnubin/ + [sed]=/opt/homebrew/opt/gnu-sed/libexec/gnubin/sed + # gawk → /opt/homebrew/opt/gawk/libexec/gnubin/ + [awk]=/opt/homebrew/opt/gawk/libexec/gnubin/awk + # gnu-tar → /opt/homebrew/opt/gnu-tar/libexec/gnubin/ + [tar]=/opt/homebrew/opt/gnu-tar/libexec/gnubin/tar + # diffutils → /opt/homebrew/opt/diffutils/bin/ + [diff]=/opt/homebrew/opt/diffutils/bin/diff + # homebrew bin → /opt/homebrew/bin/ + [gzip]=/opt/homebrew/bin/gzip + [gunzip]=/opt/homebrew/bin/gunzip + [zcat]=/opt/homebrew/bin/zcat + [jq]=/opt/homebrew/bin/jq + [wget]=/opt/homebrew/bin/wget + # native (no GNU build installed — stays /usr/bin) + [file]=/usr/bin/file +) + +# Normalize all command separators to newlines so we can iterate segments. +# Order: replace 2-char operators FIRST so they don't get half-replaced by +# the 1-char rules (e.g. `&&` must be replaced before `&`). +NORMALIZED="$CMD" +NORMALIZED="${NORMALIZED//&&/$'\n'}" +NORMALIZED="${NORMALIZED//||/$'\n'}" +NORMALIZED="${NORMALIZED//|&/$'\n'}" +NORMALIZED="${NORMALIZED//|/$'\n'}" +NORMALIZED="${NORMALIZED//;/$'\n'}" + +while IFS= read -r segment; do + # Trim leading whitespace + segment="${segment#"${segment%%[![:space:]]*}"}" + [[ -z "$segment" ]] && continue + + # Skip segments that begin with a special construct (substitution, paren, etc.) + case "$segment" in + '$'*|'('*|'{'*|'!'*) continue ;; + esac + + first="${segment%%[[:space:]]*}" + PINNED_PATH="${PINNED[$first]:-}" + + if [[ -z "$PINNED_PATH" ]]; then + continue + fi + + cat >&2 </dev/null \ + | /opt/homebrew/opt/coreutils/libexec/gnubin/tr '[:upper:]' '[:lower:]' \ + | /opt/homebrew/opt/coreutils/libexec/gnubin/sort -u || true)" + +[ -z "$MATCHES" ] && exit 0 + +{ + printf '[enforce_us_spelling] BLOCKED: British (en-GB) spelling in %s (%s).\n\n' "$TOOL" "${FILE_PATH:-unknown}" + printf 'Per %s/CLAUDE.md → "Style and Language Conventions": use\n' "${CLAUDE_PROJECT_DIR:-the project}" + printf 'strict American English (US) spelling in code, comments, and docs.\n\n' + printf 'Replace:\n' + while IFS= read -r word; do + [ -z "$word" ] && continue + printf ' - %s → %s\n' "$word" "${SPELL[$word]:-}" + done <<< "$MATCHES" + printf '\nRe-issue the edit with the US spelling(s).\n' +} >&2 + +exit 2 diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..7ea0cce --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,93 @@ +{ + "agent": "tcex-orchestrator", + "env": { + "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1" + }, + "hooks": { + "PreToolUse": [ + { + "hooks": [ + { + "command": ".claude/scripts/enforce_no_dynamic_paths.sh", + "type": "command" + }, + { + "command": ".claude/scripts/enforce_pinned_paths.sh", + "type": "command" + }, + { + "command": ".claude/scripts/enforce_no_branch_change.sh", + "type": "command" + }, + { + "command": ".claude/scripts/enforce_no_commit.sh", + "type": "command" + } + ], + "matcher": "Bash" + }, + { + "hooks": [ + { + "command": ".claude/scripts/enforce_us_spelling.sh", + "type": "command" + } + ], + "matcher": "Write|Edit|NotebookEdit" + } + ] + }, + "permissions": { + "allow": [ + "Bash(/opt/homebrew/opt/gawk/libexec/gnubin/awk *)", + "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/basename *)", + "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/cat *)", + "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/chmod *)", + "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/cp *)", + "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/cut *)", + "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/date *)", + "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/dirname *)", + "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/du *)", + "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/echo *)", + "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/env *)", + "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/head *)", + "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/ln *)", + "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/ls *)", + "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/mkdir *)", + "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/mv *)", + "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/printf *)", + "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/rm *)", + "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/sort *)", + "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/stat *)", + "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/tail *)", + "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/tee *)", + "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/touch *)", + "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/tr *)", + "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/uniq *)", + "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/wc *)", + "Bash(/opt/homebrew/opt/grep/libexec/gnubin/grep *)", + "Bash(/opt/homebrew/opt/gnu-sed/libexec/gnubin/sed *)", + "Bash(/opt/homebrew/opt/findutils/libexec/gnubin/find *)", + "Bash(/opt/homebrew/opt/findutils/libexec/gnubin/xargs *)", + "Bash(/opt/homebrew/opt/gnu-tar/libexec/gnubin/tar *)", + "Bash(/opt/homebrew/opt/diffutils/bin/diff *)", + "Bash(/opt/homebrew/bin/gzip *)", + "Bash(/opt/homebrew/bin/gunzip *)", + "Bash(/opt/homebrew/bin/zcat *)", + "Bash(/opt/homebrew/bin/jq *)", + "Bash(/opt/homebrew/bin/wget *)", + "Bash(/usr/bin/curl *)", + "Bash(/usr/bin/file *)", + "mcp__atlassian__*", + "mcp__github__*", + "mcp__ide__*", + "mcp__plugin_slack_slack__*" + ], + "ask": [], + "deny": [ + "Bash(git checkout:*)", + "Bash(git switch:*)", + "Bash(git worktree add:*)" + ] + } +} diff --git a/.cspell/custom-dictionary-workspace.txt b/.cspell/custom-dictionary-workspace.txt index 0859204..69e3eb3 100644 --- a/.cspell/custom-dictionary-workspace.txt +++ b/.cspell/custom-dictionary-workspace.txt @@ -60,7 +60,6 @@ pygrep Pylint pynput pyproject -pyright pytest pythonhosted pyupgrade diff --git a/.gitignore b/.gitignore index 8d7929d..93fae9f 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,12 @@ temp .vs .vscode +#------------------------------------------------- +# Claude Exclusions +#------------------------------------------------- +.claude/plans/* +.claude/settings.local.json + #------------------------------------------------- # Module Specific Exclusions #------------------------------------------------- diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9cbbc76..9f63671 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,10 +1,9 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: v6.0.0 hooks: - id: check-added-large-files - id: check-ast - - id: check-byte-order-marker - id: check-builtin-literals - id: check-case-conflict - id: check-docstring-first @@ -20,6 +19,7 @@ repos: - id: detect-private-key - id: double-quote-string-fixer - id: end-of-file-fixer + - id: fix-byte-order-marker - id: mixed-line-ending - id: pretty-format-json args: @@ -27,7 +27,7 @@ repos: - id: requirements-txt-fixer - id: trailing-whitespace - repo: https://github.com/pycqa/bandit - rev: 1.8.3 + rev: 1.9.4 hooks: - id: bandit exclude: | @@ -35,7 +35,7 @@ repos: ^tests/ ) - repo: https://github.com/codespell-project/codespell - rev: v2.4.1 + rev: v2.4.2 hooks: - id: codespell - repo: https://github.com/pre-commit/mirrors-isort @@ -43,59 +43,49 @@ repos: hooks: - id: isort - repo: https://github.com/asottile/pyupgrade - rev: v3.19.1 + rev: v3.21.2 hooks: - id: pyupgrade args: - --py311-plus - - repo: local + - repo: https://github.com/google/osv-scanner/ + rev: v2.3.8 hooks: - - id: pyright - entry: pyright - language: system - name: pyright - types: - - python + - id: osv-scanner + args: + - --lockfile + - uv.lock + # ty, ruff, and ruff-format each run once over the whole project. The tools + # discover and descend into the nested git submodules themselves (applying each + # submodule's own pyproject.toml), so no separate per-submodule hooks are needed. + # `types_or: [..., directory]` makes a hook fire on a changed submodule pointer + # as well as on changed Python files; `pass_filenames: false` lets each tool + # scan the tree using its own configuration. Tool versions come from uv.lock. - repo: local hooks: - - id: pyright-submodules - entry: pyright + - id: ty + entry: ty check language: system - name: pyright-submodules - # pre-commit types_or and types with multiple values don't work for submodules - types: - - directory - - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.9.7 - hooks: - - id: ruff-format + name: ty + pass_filenames: false types_or: - python - - pyi + - directory - id: ruff - args: - - --fix + entry: ruff check --fix + language: system + name: ruff + pass_filenames: false types_or: - python - pyi - - repo: local - hooks: - - id: ruff-format-submodules - entry: ruff - args: - - format - language: system - name: ruff-format-submodules - # pre-commit types_or and types with multiple values don't work for submodules - types: - directory - - id: ruff-submodules - entry: ruff - args: - - check - - --fix + - id: ruff-format + entry: ruff format language: system - name: ruff-submodules - # pre-commit types_or and types with multiple values don't work for submodules - types: + name: ruff-format + pass_filenames: false + types_or: + - python + - pyi - directory diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..f430891 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,320 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Repository Overview + +This is **TcEx CLI 1.0** (`tcex_cli`) — the command-line tooling for ThreatConnect Exchange Apps. It +is the `tcex` CLI used by App developers to **package, deploy, run, migrate, template, validate, and +generate specs** for ThreatConnect Apps (Playbook, Job, Service, and External Apps). It is a Typer +CLI, not a library/SDK and not a web service — there is no Docker/DB/frontend stack. + +> Not to be confused with the **TcEx 4.0** framework (`tcex`, a separate sibling repo). That project +> is the runtime SDK and contains a V3 API code generator. **This project has no code generator and no +> generated code — every file under `tcex_cli/` is hand-maintained.** + +- **Language**: Python 3.11+ (`requires-python = ">=3.11"`) +- **CLI framework**: **typer** + **rich**; console entry point `tcex = tcex_cli.cli.cli:app` +- **Models**: Pydantic **v1** (`validator`, `ModelField`, `update_forward_refs`, etc.) +- **Packaging / envs**: managed with **uv** (a `uv` workspace; `uv.lock` + `.venv` at the repo root) +- **Type checker**: **ty** (Astral) — pyright is no longer used (config in root `[tool.ty]`) +- **Linter / formatter**: **ruff** (100-char line length) + +The workspace root is referred to as `` throughout this document, and is exposed to Bash +commands as the `$PROJECT_ROOT` environment variable (configured per-machine — see +[Tool Invocation](#tool-invocation--absolute-paths-only)). The venv is **always** `/.venv` +(i.e. `$PROJECT_ROOT/.venv` in shell commands). + +## Project Structure + +``` +tcex_cli/ +├── cli/ # Typer command groups (one package per command) +│ ├── cli.py # root Typer app; registers all commands +│ ├── cli_abc.py # shared CLI base class +│ ├── app_input/ deploy/ deps/ migrate/ package/ run/ +│ ├── spec_tool/ template/ validate/ model/ +├── app/config/ # submodule +├── pleb/ # submodule +├── requests_tc/ # submodule +├── util/ # submodule +├── input/, logger/, render/, message_broker/ # parent-repo framework code +app/ # sample/target App workspace used by run/deploy +tests/ # pytest suite (mirrors commands: tests//) +``` + +User-facing commands (registered in `cli/cli.py`): `app-inputs`, `deploy`, `deps`, `init`, `list`, +`migrate`, `package`, `run`, `spec-tool`, `update`, `validate` (plus a conditional `test` command). +`init` / `list` / `update` are the template commands (`cli/template/`). + +## Repository & Branching + +This local repo is a **fork**. `origin` points at the fork; `upstream` is the ThreatConnect repo. + +- **All work happens directly on the `main` branch** — `main` is the working branch here, not a + protected trunk. Do **not** create feature branches; commit changes to `main` unless the operator + has **manually** switched to another branch first. +- Claude Code must **never change branches itself** (enforced by `enforce_no_branch_change.sh`). If a + branch change is genuinely needed, the human operator performs it; subsequent work then targets + whatever branch is currently checked out. +- The usual "branch before committing on the default branch" convention does **not** apply to this + fork — commits to `main` are expected (when the operator makes them — see below). + +### Commits are the operator's job — Claude never commits + +**Claude Code must NEVER create a git commit.** ALL commits — in the parent repo **and** in every +submodule — are made by the **human operator**. This is enforced by `enforce_no_commit.sh` (a +PreToolUse hook) and a `Bash(git commit:*)` deny rule; both block `git commit` in any form +(`-m`, `-a`, `--amend`, `git -C commit`, after `&&`/pipes, …) with no override. + +Claude's role stops at **preparing** the change: + +- Make the edits, then `git add` the relevant paths (staging is allowed). +- Run `git status` / `git diff --cached` to show exactly what is ready. +- **Report** what is staged and the suggested commit message(s); the operator runs `git commit`. +- The same applies to submodule changes: stage inside the submodule and describe the two-step + commit + pointer bump, but let the operator perform both commits. + +## Git Submodules + +Four parts of `tcex_cli/` are **independent git submodules**, each with its own repository and its own +`pyproject.toml`: + +| Submodule path | Notes | +|---|---| +| `tcex_cli/app/config` | install.json / app-spec models + transform builder | +| `tcex_cli/pleb` | shared primitives (cached_property, registry, etc.) | +| `tcex_cli/requests_tc` | ThreatConnect session + auth | +| `tcex_cli/util` | general utilities (render, requests-to-curl, etc.) | + +**Editing a submodule is a two-step commit:** commit the change **inside the submodule repo first**, +then bump the submodule pointer in the parent repo. Never assume a parent-repo commit captures +submodule edits. A single logical change can therefore span the parent repo and one or more +submodules. + +> Each submodule also has its own `.pre-commit-config.yaml` and `[tool.ty]` config (kept in sync with +> the parent) so it type-checks standalone. There is no shared lockfile inside submodules — the `ty` +> binary is supplied by the parent venv. + +## No Generated Code + +Unlike the TcEx 4.0 framework, this project has **no V3 API code generator and no generated code**. +Every file under `tcex_cli/` is hand-maintained — edit it directly. (The `spec-tool` command *emits* +App spec/config files for downstream Apps, but that is product output, not source generated into this +repo.) + +## Development Commands + +Dependencies are managed with **uv**. The workspace root holds `uv.lock` and `.venv`; the root +`pyproject.toml` declares dependency groups, and each submodule has its own `pyproject.toml`. + +```bash +# Sync the venv to the lock file (runtime + dev + test) +uv sync --group dev --group test + +# Code quality (always use the venv's absolute binary paths — see Tool Invocation) +"$PROJECT_ROOT"/.venv/bin/ruff check . +"$PROJECT_ROOT"/.venv/bin/ruff format . +"$PROJECT_ROOT"/.venv/bin/ty check +"$PROJECT_ROOT"/.venv/bin/pre-commit run --all-files +``` + +**Managing dependencies:** + +```bash +# Add a dev-only tool (root [dependency-groups].dev) +uv add --dev + +# Add a test-only tool +uv add --group test + +# Regenerate the lock file / sync +uv lock +uv sync --group dev --group test +``` + +- **Dev tooling** (ruff, ty, bandit, pyupgrade, pre-commit) lives in the **root** `pyproject.toml` + under `[dependency-groups].dev`; **test tooling** (pytest, pytest-cov, pytest-ordering) under + `[dependency-groups].test`. +- **Runtime deps** (typer, rich, fakeredis, …) live in `[project.dependencies]`. +- `uv` may be invoked by bare name (it lives on `PATH` and uses multi-word subcommands). + +## Tool Invocation — Absolute Paths Only + +**Rule:** every tool invocation MUST use the full absolute path of the binary, and paths must be +**static** — never resolved dynamically at command time. The workspace root is ``, and the venv +is always `/.venv`. + +In Bash commands, refer to the root via the **`$PROJECT_ROOT`** environment variable (e.g. +`"$PROJECT_ROOT"/.venv/bin/pytest`). `$PROJECT_ROOT` is **injected statically** from the `env` block +of `.claude/settings.local.json` — it is *not* command substitution, so it is explicitly **allowed** +and is the one approved way to name the root in a command. This is distinct from the **forbidden** +dynamic resolution below. + +Do **not** resolve the root via `git rev-parse`, `$(pwd)`, `$(realpath …)`, `$(readlink …)`, +`$(cd … )`, `$HOME`, or `source .venv/bin/activate` — those are the dynamic forms the hooks block. +(`$CLAUDE_PROJECT_DIR` is populated only for hook scripts and is **empty** in the Bash tool, so do not +use it in commands — use `$PROJECT_ROOT`.) + +> **One-time per-machine setup.** `$PROJECT_ROOT` comes from the **untracked** (gitignored) +> `.claude/settings.local.json`. On a fresh clone, create it with the absolute repo path and restart +> Claude Code so the `env` block takes effect: +> +> ```json +> { "env": { "PROJECT_ROOT": "/abs/path/to/tcex-cli-1.0" } } +> ``` +> +> Shared config (default agent, hooks, permissions) lives in the **committed** `.claude/settings.json`, +> so a fresh clone gets it automatically — only the machine-specific `PROJECT_ROOT` is local. Env +> changes require a Claude Code **restart** to take effect. + +For paths passed to the **Read/Write/Edit** tools (which do **not** expand env vars), use the `` +placeholder or a repo-root-relative path in prose — never `$PROJECT_ROOT` in those contexts. + +Three `PreToolUse` hooks (in `.claude/scripts/`) enforce this — treat them as hard rules: + +- `enforce_no_dynamic_paths.sh` (Bash) — blocks commands containing path-resolving substitutions: + `$(git …)`, `$(pwd)`, `$(realpath …)`, `$(readlink …)`, `$(cd … )` (and the backtick forms). +- `enforce_pinned_paths.sh` (Bash) — blocks **bare-name** invocations of standard system utilities; + use the absolute path instead. It checks **every** command segment (split on `| || && ; |&`), so a + bare name *after* a pipe is blocked too. +- `enforce_us_spelling.sh` (Write/Edit/NotebookEdit) — blocks British (en-GB) spellings in written + content. + +(Two further hooks — `enforce_no_commit.sh` and `enforce_no_branch_change.sh` — enforce the git rules +above.) + +### Python / venv tools (always absolute `.venv/bin/…`) + +```bash +# CORRECT +"$PROJECT_ROOT"/.venv/bin/python -c "..." +"$PROJECT_ROOT"/.venv/bin/pytest tests/template +"$PROJECT_ROOT"/.venv/bin/ruff check tcex_cli/cli/template/update.py +"$PROJECT_ROOT"/.venv/bin/ty check + +# WRONG — re-prompts for every variant / blocked by hooks +cd && .venv/bin/pytest +source .venv/bin/activate && python ... +python3 -c "..." +.venv/bin/pytest # relative +``` + +Pass **absolute file paths as arguments**; do not `cd` first. + +### System utilities — pinned to Homebrew GNU paths + +This project standardizes on the **Homebrew GNU** builds of the standard utilities (coreutils, grep, +gnu-sed, findutils, gawk, gnu-tar, diffutils) in preference to the native macOS BSD tools. +`enforce_pinned_paths.sh` rejects bare-name invocations of these utilities and tells you the exact +absolute path to use. The pinned utilities and their paths: + +| Tool(s) | Pinned path prefix | +|---|---| +| `cat head tail sort uniq wc cut tr ls cp mv rm mkdir chmod touch ln stat env date basename dirname tee echo printf du` | `/opt/homebrew/opt/coreutils/libexec/gnubin/` | +| `grep` | `/opt/homebrew/opt/grep/libexec/gnubin/` | +| `find xargs` | `/opt/homebrew/opt/findutils/libexec/gnubin/` | +| `sed` | `/opt/homebrew/opt/gnu-sed/libexec/gnubin/` | +| `awk` | `/opt/homebrew/opt/gawk/libexec/gnubin/` | +| `tar` | `/opt/homebrew/opt/gnu-tar/libexec/gnubin/` | +| `diff` | `/opt/homebrew/opt/diffutils/bin/` | +| `gzip gunzip zcat jq wget` | `/opt/homebrew/bin/` | +| `file` | `/usr/bin/` (no GNU build installed — stays native) | + +The hook's `PINNED` map and the `settings.local.json` allowlist are the source of truth — keep all +three in sync. Examples: + +```bash +# CORRECT — Homebrew GNU absolute paths +/opt/homebrew/opt/grep/libexec/gnubin/grep -r "pattern" tcex_cli/cli +/opt/homebrew/opt/findutils/libexec/gnubin/find tcex_cli -name "*.py" -type f \ + | /opt/homebrew/opt/findutils/libexec/gnubin/xargs /opt/homebrew/opt/grep/libexec/gnubin/grep "foo" + +# WRONG — bare names; the hook blocks with a corrective message naming the exact path +grep -r "pattern" . +find . -name "*.py" +``` + +> Note: `git`, `docker`, and `uv` are not pinned (multi-word subcommands have their own rules); +> `curl` and `file` stay native (`/usr/bin/`). + +## Code Standards + +### Style and Language Conventions +- **Spelling**: strictly American English (US) for all natural-language output, code comments, and + docs. The `enforce_us_spelling.sh` hook blocks en-GB variants. +- Prefer `-ize` over `-ise`, `-or` over `-our`, `-er` over `-re`, single `l` over `ll` + (e.g. "initialize", "color", "center", "canceled"). + +### Python +- **Formatter / linter**: ruff, 100-char line length, config in root `[tool.ruff]`. +- **Type hints**: required; checked with **ty** (config in root `[tool.ty]`). The `tests/` tree is + excluded. `python-platform = "linux"`, `python-version = "3.11"`. +- **Type-checker suppressions**: ty uses `# type: ignore` (blanket, PEP 484) and + `# ty: ignore[]` (targeted). Pyright-style codes like `# type: ignore[reportXxx]` do **not** + work in ty. Prefer a real type fix; use a targeted `# ty: ignore[]` only when the code is + correct but ty cannot infer it. +- **typer caveat**: typer does not yet support PEP 604 unions in some positions — `UP045` + (`Use X | None`) is intentionally ignored for that reason (see `[tool.ruff.lint]`). +- **Docstrings**: Google style. **Imports**: organized by isort (`[tool.isort]`, 100-char). +- **pydantic v1** patterns throughout. + +### Scripts CLI Standard — typer + rich, dry-run by default +Every operator-facing standalone script uses **typer** (CLI) + **rich** (output); mutating scripts +are **dry-run by default — pass `--commit` to write** (no `--dry-run`, no `--yes`). `temp_*` helpers +are exempt from the full standard. **All standalone scripts are authored by the +`python-script-specialist` agent** (see Agents). + +### Bandit `# nosec` placement +The suppressor must sit on the **exact line** bandit flags, not a parent call. Always include the +specific test id and a justification, e.g. `subprocess.run(cmd) # nosec B603 — args are static`. + +### Shell / Bash +- **Never use `find -exec`** — pipe through `xargs` instead (`-exec` spawns a subprocess per match): + ```bash + /opt/homebrew/opt/findutils/libexec/gnubin/find tcex_cli -name "*.pyc" \ + | /opt/homebrew/opt/findutils/libexec/gnubin/xargs /opt/homebrew/opt/coreutils/libexec/gnubin/rm -f + ``` + For paths with spaces, use `-print0` / `-0`. + +## Testing + +- pytest config in root `[tool.pytest.ini_options]`; `testpaths = ["tests"]`. Test ordering uses + **pytest-ordering**; coverage via **pytest-cov**. If `.venv/bin/pytest` is missing, sync the test + group once: `uv sync --group test`. +- Test layout mirrors the command surface: `tests//` — e.g. `tests/app`, `tests/deps`, + `tests/init`, `tests/list`, `tests/run`, `tests/template` (plus `tests/conftest.py`). +- Redis-backed code (the `run` command's message broker / kvstore) is exercised with **fakeredis**. + +```bash +"$PROJECT_ROOT"/.venv/bin/pytest # all +"$PROJECT_ROOT"/.venv/bin/pytest tests/template # one area +"$PROJECT_ROOT"/.venv/bin/pytest -k "test_name" # by pattern +``` + +## Agents + +This project uses an orchestrator + specialist subagents (in `.claude/agents/`). The default agent is +`tcex-orchestrator` (set in `.claude/settings.local.json`). + +| Agent | Use for | +|---|---| +| `tcex-orchestrator` | Analyzes the request, gathers context, writes a plan when required, delegates to specialists, then runs the security gate and reports. Does not write code itself. | +| `python-engineer` | **All** updates to the `tcex_cli` codebase under `tcex_cli/` (parent repo **and** submodules): CLI commands, models, framework logic, bug fixes, refactors, type-checker fixes, dependency changes. Not scripts, not tests. | +| `python-test-engineer` | **All** pytest test cases under `tests/`. Does not modify source. | +| `python-script-specialist` | **Sole author** of standalone scripts (typer + rich, dry-run/`--commit`). Writes to `.claude/scripts/`. | +| `python-security-auditor` | **Hard security gate** — runs after every code/test/script change; HIGH/critical findings block "done" until fixed. | + +## One-Off Scripts (`.claude/scripts/`) + +All standalone scripts are authored by `python-script-specialist`. Agent-written helpers live in +`.claude/scripts/` (alongside the enforcement hooks). A genuine one- or two-line `-c` invocation for +ad-hoc context-gathering is fine and does not need delegation. Python scripts must use the venv's +absolute `python` path. + +| Script type | Prefix | Example | +|---|---|---| +| Reusable | _(none)_ | `audit_field_types.py` | +| Throwaway | `temp_` | `temp_check_counts.py` | + +`temp_*` files are gitignored; reusable scripts are committed with the session. diff --git a/pyproject.toml b/pyproject.toml index 76cb9fa..0aef01e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,9 +25,9 @@ classifiers = [ # List of runtime dependencies dependencies = [ "arrow~=1.3.0", - "black~=25.1.0", + "black~=26.3.1", "debugpy~=1.8.12", - "fakeredis~=2.27.0", + "fakeredis~=2.36.0", "inflection~=0.5.1", "isort~=6.0.0", "jmespath~=1.0.1", @@ -35,10 +35,10 @@ dependencies = [ "pyaes~=1.6.1", "pydantic~=1.10.0", "python-dateutil~=2.9.0", - "python-dotenv~=1.0.1", + "python-dotenv~=1.2.2", "pyyaml~=6.0.2", - "redis~=4.6.0", - "requests~=2.32.3", + "redis~=8.0.0", + "requests~=2.33.0", "rich~=13.9.4", "semantic-version~=2.10.0", "tinydb~=4.8.2", @@ -79,12 +79,12 @@ Source = "https://github.com/ThreatConnect-Inc/tcex-cli" dev = [ "bandit~=1.8.3", "pre-commit~=4.1.0", - "pyright~=1.1.394", "pyupgrade~=3.19.1", "ruff~=0.15.2", + "ty~=0.0.42", ] test = [ - "pytest~=8.3.4", + "pytest~=9.0.3", "pytest-cov~=6.0.0", "pytest-ordering~=0.6", ] @@ -149,7 +149,7 @@ filename = "tcex_cli/__metadata__.py" # Configuration for codespell spell checker [tool.codespell] # https://streetsidesoftware.com/vscode-spell-checker/ -skip = "*.cspell*,*.history,*local,uv.lock" +skip = "*.cspell*,*.history,*local,uv.lock,*enforce_us_spelling.sh" # Configuration for isort import sorter [tool.isort] @@ -164,30 +164,25 @@ known_third_party = "" line_length = 100 profile = "black" -# Configuration for pyright type checker -[tool.pyright] -# https://github.com/microsoft/pyright/blob/main/docs/configuration.md#sample-pyprojecttoml-file +# Configuration for ty type checker +# https://docs.astral.sh/ty/reference/configuration/ +[tool.ty.environment] +python-platform = "linux" +python-version = "3.11" + +[tool.ty.src] exclude = [ "**/.history", "**/__pycache__", "**/local-*", - "tcex/api/tc/v2", "tests", ] -extraPaths = [ - ".venv/lib/python3.11/site-packages", -] -pythonPlatform = "Linux" -pythonVersion = "3.11" -reportArgumentType = false -reportAttributeAccessIssue = false -reportCallIssue = false -reportIncompatibleMethodOverride = false -reportIncompatibleVariableOverride = false -reportOptionalMemberAccess = false -reportOptionalSubscript = false -reportPrivateImportUsage = false -reportReturnType = false + +[tool.ty.rules] +# Override checks intentionally disabled (carried over from the prior pyright +# config: reportIncompatibleMethodOverride / reportIncompatibleVariableOverride). +invalid-attribute-override = "ignore" +invalid-method-override = "ignore" # Configuration for pytest test runner [tool.pytest.ini_options] @@ -250,6 +245,7 @@ ignore = [ "PT006", # Wrong name(s) type in ..., expected ... "TC001", # Move application import ... into a type-checking block "TID252", # Prefer absolute imports over relative imports from parent modules + "UP045", # Use `X | None` for type annotations (typer does not yet support PEP 604) ] # control enabled plugins select = [ diff --git a/tcex_cli/app/config b/tcex_cli/app/config index 7a18c31..4738659 160000 --- a/tcex_cli/app/config +++ b/tcex_cli/app/config @@ -1 +1 @@ -Subproject commit 7a18c3149f799c1040aeb24c1693259f49e78d89 +Subproject commit 473865978e4f8565b7e2fcacfbcea8d68a80e562 diff --git a/tcex_cli/cli/app_input/app_input_cli.py b/tcex_cli/cli/app_input/app_input_cli.py index 97622c3..046024c 100644 --- a/tcex_cli/cli/app_input/app_input_cli.py +++ b/tcex_cli/cli/app_input/app_input_cli.py @@ -44,6 +44,8 @@ def add_kvstore(self, variable: str, value: Any): def get_param_default(self, param: ParamsModel) -> Any: """Get the default value for the parameter.""" try: + if not isinstance(param.default, str): + raise TypeError # noqa: TRY301 return json.loads(param.default) except (json.JSONDecodeError, TypeError): panel_msg = f'Error parsing input for {param.name} -> {param.default}' @@ -128,12 +130,15 @@ def playbook_datatype_map(self): def prompt_user_for_playbook_datatype(self, playbook_data_types: list[str]) -> str: """Prompt user for playbookDataType.""" - return Render.prompt.ask( - 'This input has multiple playbookDataTypes, please choose one: ', - choices=playbook_data_types, - default=playbook_data_types[0], - show_choices=True, - show_default=True, + return ( + Render.prompt.ask( + 'This input has multiple playbookDataTypes, please choose one: ', + choices=playbook_data_types, + default=playbook_data_types[0], + show_choices=True, + show_default=True, + ) + or playbook_data_types[0] ) def prompt_user_for_optional_params(self, param: ParamsModel) -> bool: @@ -175,7 +180,7 @@ def generate_app_inputs(self): case 'Choice' | 'EditChoice' | 'MultiChoice': if param.default: - self.add_input(param.name, param.default) + self.add_input(param.name, str(param.default)) else: self.add_input(param.name, '|'.join(param.valid_values)) diff --git a/tcex_cli/cli/cli.py b/tcex_cli/cli/cli.py index 4fb2140..128dad1 100644 --- a/tcex_cli/cli/cli.py +++ b/tcex_cli/cli/cli.py @@ -35,7 +35,7 @@ def add_test_command(): update_system_path() # third-party - from tcex_app_testing.cli.cli import app as app_test # type: ignore + from tcex_app_testing.cli.cli import app as app_test # type: ignore # noqa: PLC0415 app.add_typer( app_test, @@ -66,7 +66,7 @@ def version_callback( # update system path update_system_path() - version_data = {} + version_data: dict[str, str | None] = {} # display the tcex version with contextlib.suppress(ImportError): version_data['TcEx'] = get_version('tcex') diff --git a/tcex_cli/cli/deploy/deploy.py b/tcex_cli/cli/deploy/deploy.py index ad6c59a..7af4cd0 100644 --- a/tcex_cli/cli/deploy/deploy.py +++ b/tcex_cli/cli/deploy/deploy.py @@ -12,8 +12,8 @@ # typer does not yet support PEP 604, but pyupgrade will enforce # PEP 604. this is a temporary workaround until support is added. -IntOrNone = Optional[int] # noqa: UP007 -StrOrNone = Optional[str] # noqa: UP007 +IntOrNone = Optional[int] +StrOrNone = Optional[str] def command( diff --git a/tcex_cli/cli/deps/deps_cli.py b/tcex_cli/cli/deps/deps_cli.py index de4a577..35f6d80 100644 --- a/tcex_cli/cli/deps/deps_cli.py +++ b/tcex_cli/cli/deps/deps_cli.py @@ -397,7 +397,7 @@ def validate_python_version(self): # temp logic until all TC instances are on version 7.2 language_major_minor = self.app.ij.model.language_version if isinstance(language_major_minor, Version): - language_major_minor = f'{language_major_minor.major}.{language_major_minor.minor}' # type: ignore + language_major_minor = f'{language_major_minor.major}.{language_major_minor.minor}' if target_major_minor != language_major_minor: Render.panel.failure( diff --git a/tcex_cli/cli/migrate/migrate.py b/tcex_cli/cli/migrate/migrate.py index 55e54db..5826c20 100644 --- a/tcex_cli/cli/migrate/migrate.py +++ b/tcex_cli/cli/migrate/migrate.py @@ -12,8 +12,8 @@ # typer does not yet support PEP 604, but pyupgrade will enforce # PEP 604. this is a temporary workaround until support is added. -IntOrNone = Optional[int] # noqa: UP007 -StrOrNone = Optional[str] # noqa: UP007 +IntOrNone = Optional[int] +StrOrNone = Optional[str] def command( diff --git a/tcex_cli/cli/run/launch_abc.py b/tcex_cli/cli/run/launch_abc.py index 232bd2f..5746c87 100644 --- a/tcex_cli/cli/run/launch_abc.py +++ b/tcex_cli/cli/run/launch_abc.py @@ -120,7 +120,7 @@ def construct_model_inputs(self) -> dict: def launch(self): """Launch the App.""" # third-party - from run import Run # type: ignore + from run import Run # type: ignore # noqa: PLC0415 # run the app exit_code = 0 @@ -167,7 +167,7 @@ def output_data(self, context: str) -> dict: if output_data_: return { k: json.loads(v) - for k, v in self.output_data_process(output_data_).items() # type: ignore + for k, v in self.output_data_process(output_data_).items() if k not in self.staged_keys } return {} diff --git a/tcex_cli/cli/run/model/app_playbook_model.py b/tcex_cli/cli/run/model/app_playbook_model.py index 676675d..fec6e6c 100644 --- a/tcex_cli/cli/run/model/app_playbook_model.py +++ b/tcex_cli/cli/run/model/app_playbook_model.py @@ -28,7 +28,7 @@ class Config: case_sensitive = False env_file = '.env' env_file_encoding = 'utf-8' - json_encoders: ClassVar = {PosixPath: lambda v: str(v), Sensitive: lambda v: v.value} + json_encoders: ClassVar = {PosixPath: str, Sensitive: lambda v: v.value} validate_assignment = True diff --git a/tcex_cli/cli/run/playbook_create.py b/tcex_cli/cli/run/playbook_create.py index cecbe89..619d0ff 100644 --- a/tcex_cli/cli/run/playbook_create.py +++ b/tcex_cli/cli/run/playbook_create.py @@ -21,7 +21,7 @@ class BaseStagger: """Base class for staging data in the kvstore.""" - def __init__(self, key: str, value: bytes | dict | str | list[bytes | dict | str]): + def __init__(self, key: str, value: str | dict | list[str | dict]): """Initialize class properties.""" self.key = key self.value = value diff --git a/tcex_cli/cli/run/request_handler_api.py b/tcex_cli/cli/run/request_handler_api.py index e1d2a61..f8da264 100644 --- a/tcex_cli/cli/run/request_handler_api.py +++ b/tcex_cli/cli/run/request_handler_api.py @@ -175,26 +175,26 @@ def call_service(self, method: str): return # noqa: PLR1711 - def do_DELETE(self): # noqa: N802 + def do_DELETE(self): """Handle DELETE method.""" return self.call_service('DELETE') - def do_GET(self): # noqa: N802 + def do_GET(self): """Handle GET method.""" return self.call_service('GET') - def do_OPTIONS(self): # noqa: N802 + def do_OPTIONS(self): """Handle OPTIONS method.""" return self.call_service('OPTIONS') - def do_PATCH(self): # noqa: N802 + def do_PATCH(self): """Handle PATCH method.""" return self.call_service('PATCH') - def do_POST(self): # noqa: N802 + def do_POST(self): """Handle POST method.""" return self.call_service('POST') - def do_PUT(self): # noqa: N802 + def do_PUT(self): """Handle POST method.""" return self.call_service('PUT') diff --git a/tcex_cli/cli/run/request_handler_webhook.py b/tcex_cli/cli/run/request_handler_webhook.py index 44ba5f0..bab3939 100644 --- a/tcex_cli/cli/run/request_handler_webhook.py +++ b/tcex_cli/cli/run/request_handler_webhook.py @@ -40,7 +40,7 @@ def _build_request(self, method: str) -> dict: content_length = int(self.headers.get('content-length', 0)) if content_length: body = self.rfile.read(content_length) - self.server.redis_client.hset(request_key, 'request.body', body) # type: ignore + self.server.redis_client.hset(request_key, 'request.body', body) return { 'appId': 95, @@ -103,18 +103,18 @@ def call_service(self, method: str): return # noqa: PLR1711 - def do_DELETE(self): # noqa: N802 + def do_DELETE(self): """Handle DELETE method.""" return self.call_service('DELETE') - def do_GET(self): # noqa: N802 + def do_GET(self): """Handle GET method.""" return self.call_service('GET') - def do_PATCH(self): # noqa: N802 + def do_PATCH(self): """Handle PATCH method.""" return self.call_service('PATCH') - def do_POST(self): # noqa: N802 + def do_POST(self): """Handle POST method.""" return self.call_service('POST') diff --git a/tcex_cli/cli/run/run_cli.py b/tcex_cli/cli/run/run_cli.py index 19d5f8e..86e867e 100644 --- a/tcex_cli/cli/run/run_cli.py +++ b/tcex_cli/cli/run/run_cli.py @@ -57,7 +57,7 @@ def _validate_in_app_directory(self): def debug(self, debug_port: int): """Run the App in debug mode.""" # third-party - import debugpy # noqa: T100 + import debugpy # noqa: T100, PLC0415 Render.panel.info( f'Waiting for debugger to attach to port: [{self.accent}]{debug_port}[/{self.accent}].', diff --git a/tcex_cli/cli/spec_tool/gen_app_input.py b/tcex_cli/cli/spec_tool/gen_app_input.py index 54b6852..c549142 100644 --- a/tcex_cli/cli/spec_tool/gen_app_input.py +++ b/tcex_cli/cli/spec_tool/gen_app_input.py @@ -463,6 +463,7 @@ def _validator_entity_input(self, entity_input: list[str]) -> list[str]: ( f"""{self.i1}_entity_input = validator({_entity_input}, """ """allow_reuse=True)(entity_input(only_field='value'))""" + """ # ty: ignore[invalid-argument-type]""" ), ] diff --git a/tcex_cli/cli/spec_tool/gen_app_input_static.py b/tcex_cli/cli/spec_tool/gen_app_input_static.py index cd2b9ec..68c8e14 100644 --- a/tcex_cli/cli/spec_tool/gen_app_input_static.py +++ b/tcex_cli/cli/spec_tool/gen_app_input_static.py @@ -105,9 +105,6 @@ def template_app_imports( # defined imports _imports = ['"""App Inputs"""'] - # add pyright ignore for field_type - _imports.append('# pyright: reportGeneralTypeIssues=false\n') - # add typing imports if typing_modules: _imports.append(f'from typing import {typing_modules_}') diff --git a/tcex_cli/cli/spec_tool/gen_install_json.py b/tcex_cli/cli/spec_tool/gen_install_json.py index e0891d5..e97169f 100644 --- a/tcex_cli/cli/spec_tool/gen_install_json.py +++ b/tcex_cli/cli/spec_tool/gen_install_json.py @@ -2,6 +2,7 @@ # standard library from importlib.metadata import version +from typing import Any # first-party from tcex_cli.app.config import AppSpecYml @@ -92,11 +93,11 @@ def _add_type_organization_fields(self, install_json_data: dict): if _repeating_minutes: install_json_data['repeatingMinutes'] = _repeating_minutes - def _add_type_playbook_fields(self, install_json_data: dict): + def _add_type_playbook_fields(self, install_json_data: dict[str, Any]): """Add field that apply to ALL App types.""" if self.asy.model.is_playbook_app or self.asy.model.is_trigger_app: install_json_data['allowRunAsUser'] = self.asy.model.allow_run_as_user - install_json_data['playbook'] = { + playbook_data: dict[str, Any] = { 'outputPrefix': self.asy.model.output_prefix, 'outputVariables': [ ov.dict(by_alias=True) for ov in self.asy.model.output_variables @@ -108,9 +109,8 @@ def _add_type_playbook_fields(self, install_json_data: dict): and self.asy.model.playbook.retry and self.asy.model.playbook.retry.allowed is True ): - install_json_data['playbook']['retry'] = self.asy.model.playbook.retry.dict( - by_alias=True - ) + playbook_data['retry'] = self.asy.model.playbook.retry.dict(by_alias=True) + install_json_data['playbook'] = playbook_data @property def _note_per_action(self): @@ -124,7 +124,7 @@ def _note_per_action(self): def generate(self): """Generate the install.json file data.""" # all keys added to dict must be in camelCase - install_json_data = {} + install_json_data: dict[str, Any] = {} # add standard fields self._add_standard_fields(install_json_data) diff --git a/tcex_cli/cli/spec_tool/gen_layout_json.py b/tcex_cli/cli/spec_tool/gen_layout_json.py index 0841193..a5e6f5a 100644 --- a/tcex_cli/cli/spec_tool/gen_layout_json.py +++ b/tcex_cli/cli/spec_tool/gen_layout_json.py @@ -1,5 +1,8 @@ """TcEx Framework Module""" +# standard library +from typing import Any + # first-party from tcex_cli.app.config import AppSpecYml from tcex_cli.app.config.model import LayoutJsonModel @@ -20,7 +23,7 @@ def __init__(self, asy: AppSpecYml): def generate(self): """Generate the layout.json file data.""" - layout_json_data = { + layout_json_data: dict[str, Any] = { 'inputs': self.asy.model.inputs, 'outputs': self.asy.model.outputs, } diff --git a/tcex_cli/cli/template/init.py b/tcex_cli/cli/template/init.py index 5600cc4..19de63b 100644 --- a/tcex_cli/cli/template/init.py +++ b/tcex_cli/cli/template/init.py @@ -16,8 +16,8 @@ # typer does not yet support PEP 604, but pyupgrade will enforce # PEP 604. this is a temporary workaround until support is added. -IntOrNone = Optional[int] # noqa: UP007 -StrOrNone = Optional[str] # noqa: UP007 +IntOrNone = Optional[int] +StrOrNone = Optional[str] def command( diff --git a/tcex_cli/cli/template/list_.py b/tcex_cli/cli/template/list_.py index 9af3247..0ae1e1c 100644 --- a/tcex_cli/cli/template/list_.py +++ b/tcex_cli/cli/template/list_.py @@ -15,8 +15,8 @@ # typer does not yet support PEP 604, but pyupgrade will enforce # PEP 604. this is a temporary workaround until support is added. -IntOrNone = Optional[int] # noqa: UP007 -StrOrNone = Optional[str] # noqa: UP007 +IntOrNone = Optional[int] +StrOrNone = Optional[str] def command( diff --git a/tcex_cli/cli/template/model/template_config_model.py b/tcex_cli/cli/template/model/template_config_model.py index 8b7ad09..719fb93 100644 --- a/tcex_cli/cli/template/model/template_config_model.py +++ b/tcex_cli/cli/template/model/template_config_model.py @@ -32,7 +32,7 @@ class Config: """DataModel Config""" arbitrary_types_allowed = True - json_encoders: ClassVar = {Version: lambda v: str(v)} + json_encoders: ClassVar = {Version: str} validate_assignment = True @property diff --git a/tcex_cli/cli/template/planner.py b/tcex_cli/cli/template/planner.py index 8dfbe40..5b4822f 100644 --- a/tcex_cli/cli/template/planner.py +++ b/tcex_cli/cli/template/planner.py @@ -58,7 +58,7 @@ class Plan(BaseModel): ) @property - def summary(self) -> dict[str, str]: + def summary(self) -> dict[str, str | None]: """Return a summary dict suitable for Render.table.key_value.""" return { 'Skip': str(len(self.skip)), diff --git a/tcex_cli/cli/template/update.py b/tcex_cli/cli/template/update.py index 97ad06f..a58d4b9 100644 --- a/tcex_cli/cli/template/update.py +++ b/tcex_cli/cli/template/update.py @@ -16,8 +16,8 @@ # typer does not yet support PEP 604, but pyupgrade will enforce # PEP 604. this is a temporary workaround until support is added. -IntOrNone = Optional[int] # noqa: UP007 -StrOrNone = Optional[str] # noqa: UP007 +IntOrNone = Optional[int] +StrOrNone = Optional[str] def command( diff --git a/tcex_cli/cli/validate/validate_cli.py b/tcex_cli/cli/validate/validate_cli.py index 35137fc..ee2da16 100644 --- a/tcex_cli/cli/validate/validate_cli.py +++ b/tcex_cli/cli/validate/validate_cli.py @@ -219,7 +219,7 @@ def check_layout_params(self): ) try: self.app.permutation.db_conn.execute(display_query.replace('"', '')) - except sqlite3.Error: # type: ignore + except sqlite3.Error: self.validation_data.errors.append( 'Layouts input.parameters[].display validations failed ' f'("{p.display}" query is an invalid statement).' @@ -256,7 +256,7 @@ def check_layout_params(self): ) try: self.app.permutation.db_conn.execute(display_query.replace('"', '')) - except sqlite3.Error: # type: ignore + except sqlite3.Error: self.validation_data.errors.append( f"""Layouts outputs.display validations failed ("{o.display}" """ f"""query is an invalid statement).""" diff --git a/tcex_cli/logger/__init__.py b/tcex_cli/logger/__init__.py index 34497da..bf1efd8 100644 --- a/tcex_cli/logger/__init__.py +++ b/tcex_cli/logger/__init__.py @@ -1,6 +1,5 @@ """TcEx Framework Module""" -# ruff: noqa from .rotating_file_handler_custom import RotatingFileHandlerCustom from .test_logger import TestLogger from .trace_logger import TraceLogger diff --git a/tcex_cli/message_broker/mqtt_message_broker.py b/tcex_cli/message_broker/mqtt_message_broker.py index d44bc80..3664e56 100644 --- a/tcex_cli/message_broker/mqtt_message_broker.py +++ b/tcex_cli/message_broker/mqtt_message_broker.py @@ -5,6 +5,7 @@ import ssl import time from collections.abc import Callable +from typing import TypedDict # third-party import paho.mqtt.client as mqtt @@ -18,6 +19,13 @@ _logger: TraceLogger = logging.getLogger(__name__.split('.', maxsplit=1)[0]) # type: ignore +class OnMessageCallback(TypedDict): + """Typed structure for an on_message callback registration.""" + + callback: Callable[..., None] + topics: list[str] + + class MqttMessageBroker: """TcEx Framework MQTT message broker module.""" @@ -49,7 +57,7 @@ def __init__( self._on_connect_callbacks: list[Callable] = [] self._on_disconnect_callbacks: list[Callable] = [] self._on_log_callbacks: list[Callable] = [] - self._on_message_callbacks: list[dict[str, Callable | list[str]]] = [] + self._on_message_callbacks: list[OnMessageCallback] = [] self._on_publish_callbacks: list[Callable] = [] self._on_subscribe_callbacks: list[Callable] = [] self._on_unsubscribe_callbacks: list[Callable] = [] @@ -87,7 +95,10 @@ def add_on_log_callback(self, callback: Callable, index: int | None = None): self._on_log_callbacks.insert(index, callback) def add_on_message_callback( - self, callback: Callable, index: int | None = None, topics: list[str] | None = None + self, + callback: Callable[..., None], + index: int | None = None, + topics: list[str] | None = None, ): """Add a callback for on_message events. @@ -218,8 +229,8 @@ def on_message(self, client, userdata, message): for cd in self._on_message_callbacks: # if there are no topic restrictions, or the current message # topic is in the list of restrictions, call the callback - topics = cd.get('topics') - if (topics in ([], None) or message.topic in topics) and callable(cd['callback']): + topics = cd['topics'] + if not topics or message.topic in topics: cd['callback'](client, userdata, message) def on_publish(self, client, userdata, mid, rc, properties): diff --git a/tcex_cli/pleb b/tcex_cli/pleb index be0c3f3..51855d2 160000 --- a/tcex_cli/pleb +++ b/tcex_cli/pleb @@ -1 +1 @@ -Subproject commit be0c3f321d3d1c8ea4844545e928e5b30c8bd90e +Subproject commit 51855d2fec63c839e22684686db3de8be971235e diff --git a/tcex_cli/registry.py b/tcex_cli/registry.py index fbd6a9d..7a9b05b 100644 --- a/tcex_cli/registry.py +++ b/tcex_cli/registry.py @@ -44,7 +44,7 @@ def add_method(self, method: Callable): Args: method: A instance method to add to the registry. """ - self._add(method.__name__, method) + self._add(getattr(method, '__name__', repr(method)), method) def add_factory(self, type_or_name: str | type, factory: Callable, singleton=False): """Add a factory for a service. diff --git a/tcex_cli/requests_tc b/tcex_cli/requests_tc index 8283f58..68ef1dd 160000 --- a/tcex_cli/requests_tc +++ b/tcex_cli/requests_tc @@ -1 +1 @@ -Subproject commit 8283f58b6c66757126d6f1d7802169dd9f234f34 +Subproject commit 68ef1ddecfcef1b7e44a6cdd505cc690aa566d2a diff --git a/tcex_cli/util b/tcex_cli/util index af023f5..7c51bec 160000 --- a/tcex_cli/util +++ b/tcex_cli/util @@ -1 +1 @@ -Subproject commit af023f5a12eddf22ac898956d03d99a4b06d9660 +Subproject commit 7c51bec290812f116d7f9475d2f4fbaf2a6e6388 diff --git a/tests/template/fixtures/tcex-app-templates-v2.zip b/tests/template/fixtures/tcex-app-templates-v2.zip index 9a36879a34cdf4c2bd94a4fb206986a1ac3f8947..ab382965a8177a0b6c95822520fba633457ffd94 100644 GIT binary patch delta 65548 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 G+y4PkL7vtC delta 54159 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

_O5&q@C zT*}^gAYpf~ArvY^eb6F9{#YC;}G~;5dJ?xA9l79f~VpFTfTeP!_IU;@FaX7e)kPl z*a0sHo}C}WV>VdTATL0BfQ=~}oD|@nG+={^Nf3dxQO-H bqjn-kg@tV-?4^Xy+DQqC*x8EPiX8nv%Zpch diff --git a/uv.lock b/uv.lock index b390ed0..945a1a0 100644 --- a/uv.lock +++ b/uv.lock @@ -50,7 +50,7 @@ wheels = [ [[package]] name = "black" -version = "25.1.0" +version = "26.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -58,31 +58,40 @@ dependencies = [ { name = "packaging" }, { name = "pathspec" }, { name = "platformdirs" }, + { name = "pytokens" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/94/49/26a7b0f3f35da4b5a65f081943b7bcd22d7002f5f0fb8098ec1ff21cb6ef/black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", size = 649449, upload-time = "2025-01-29T04:15:40.373Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/c5/61175d618685d42b005847464b8fb4743a67b1b8fdb75e50e5a96c31a27a/black-26.3.1.tar.gz", hash = "sha256:2c50f5063a9641c7eed7795014ba37b0f5fa227f3d408b968936e24bc0566b07", size = 666155, upload-time = "2026-03-12T03:36:03.593Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/4f/87f596aca05c3ce5b94b8663dbfe242a12843caaa82dd3f85f1ffdc3f177/black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0", size = 1614372, upload-time = "2025-01-29T05:37:11.71Z" }, - { url = "https://files.pythonhosted.org/packages/e7/d0/2c34c36190b741c59c901e56ab7f6e54dad8df05a6272a9747ecef7c6036/black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299", size = 1442865, upload-time = "2025-01-29T05:37:14.309Z" }, - { url = "https://files.pythonhosted.org/packages/21/d4/7518c72262468430ead45cf22bd86c883a6448b9eb43672765d69a8f1248/black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096", size = 1749699, upload-time = "2025-01-29T04:18:17.688Z" }, - { url = "https://files.pythonhosted.org/packages/58/db/4f5beb989b547f79096e035c4981ceb36ac2b552d0ac5f2620e941501c99/black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2", size = 1428028, upload-time = "2025-01-29T04:18:51.711Z" }, - { url = "https://files.pythonhosted.org/packages/83/71/3fe4741df7adf015ad8dfa082dd36c94ca86bb21f25608eb247b4afb15b2/black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b", size = 1650988, upload-time = "2025-01-29T05:37:16.707Z" }, - { url = "https://files.pythonhosted.org/packages/13/f3/89aac8a83d73937ccd39bbe8fc6ac8860c11cfa0af5b1c96d081facac844/black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc", size = 1453985, upload-time = "2025-01-29T05:37:18.273Z" }, - { url = "https://files.pythonhosted.org/packages/6f/22/b99efca33f1f3a1d2552c714b1e1b5ae92efac6c43e790ad539a163d1754/black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f", size = 1783816, upload-time = "2025-01-29T04:18:33.823Z" }, - { url = "https://files.pythonhosted.org/packages/18/7e/a27c3ad3822b6f2e0e00d63d58ff6299a99a5b3aee69fa77cd4b0076b261/black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba", size = 1440860, upload-time = "2025-01-29T04:19:12.944Z" }, - { url = "https://files.pythonhosted.org/packages/98/87/0edf98916640efa5d0696e1abb0a8357b52e69e82322628f25bf14d263d1/black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", size = 1650673, upload-time = "2025-01-29T05:37:20.574Z" }, - { url = "https://files.pythonhosted.org/packages/52/e5/f7bf17207cf87fa6e9b676576749c6b6ed0d70f179a3d812c997870291c3/black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", size = 1453190, upload-time = "2025-01-29T05:37:22.106Z" }, - { url = "https://files.pythonhosted.org/packages/e3/ee/adda3d46d4a9120772fae6de454c8495603c37c4c3b9c60f25b1ab6401fe/black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", size = 1782926, upload-time = "2025-01-29T04:18:58.564Z" }, - { url = "https://files.pythonhosted.org/packages/cc/64/94eb5f45dcb997d2082f097a3944cfc7fe87e071907f677e80788a2d7b7a/black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18", size = 1442613, upload-time = "2025-01-29T04:19:27.63Z" }, - { url = "https://files.pythonhosted.org/packages/09/71/54e999902aed72baf26bca0d50781b01838251a462612966e9fc4891eadd/black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", size = 207646, upload-time = "2025-01-29T04:15:38.082Z" }, + { url = "https://files.pythonhosted.org/packages/17/57/5f11c92861f9c92eb9dddf515530bc2d06db843e44bdcf1c83c1427824bc/black-26.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:28ef38aee69e4b12fda8dba75e21f9b4f979b490c8ac0baa7cb505369ac9e1ff", size = 1851987, upload-time = "2026-03-12T03:40:06.248Z" }, + { url = "https://files.pythonhosted.org/packages/54/aa/340a1463660bf6831f9e39646bf774086dbd8ca7fc3cded9d59bbdf4ad0a/black-26.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf9bf162ed91a26f1adba8efda0b573bc6924ec1408a52cc6f82cb73ec2b142c", size = 1689499, upload-time = "2026-03-12T03:40:07.642Z" }, + { url = "https://files.pythonhosted.org/packages/f3/01/b726c93d717d72733da031d2de10b92c9fa4c8d0c67e8a8a372076579279/black-26.3.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:474c27574d6d7037c1bc875a81d9be0a9a4f9ee95e62800dab3cfaadbf75acd5", size = 1754369, upload-time = "2026-03-12T03:40:09.279Z" }, + { url = "https://files.pythonhosted.org/packages/e3/09/61e91881ca291f150cfc9eb7ba19473c2e59df28859a11a88248b5cbbc4d/black-26.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:5e9d0d86df21f2e1677cc4bd090cd0e446278bcbbe49bf3659c308c3e402843e", size = 1413613, upload-time = "2026-03-12T03:40:10.943Z" }, + { url = "https://files.pythonhosted.org/packages/16/73/544f23891b22e7efe4d8f812371ab85b57f6a01b2fc45e3ba2e52ba985b8/black-26.3.1-cp311-cp311-win_arm64.whl", hash = "sha256:9a5e9f45e5d5e1c5b5c29b3bd4265dcc90e8b92cf4534520896ed77f791f4da5", size = 1219719, upload-time = "2026-03-12T03:40:12.597Z" }, + { url = "https://files.pythonhosted.org/packages/dc/f8/da5eae4fc75e78e6dceb60624e1b9662ab00d6b452996046dfa9b8a6025b/black-26.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e6f89631eb88a7302d416594a32faeee9fb8fb848290da9d0a5f2903519fc1", size = 1895920, upload-time = "2026-03-12T03:40:13.921Z" }, + { url = "https://files.pythonhosted.org/packages/2c/9f/04e6f26534da2e1629b2b48255c264cabf5eedc5141d04516d9d68a24111/black-26.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41cd2012d35b47d589cb8a16faf8a32ef7a336f56356babd9fcf70939ad1897f", size = 1718499, upload-time = "2026-03-12T03:40:15.239Z" }, + { url = "https://files.pythonhosted.org/packages/04/91/a5935b2a63e31b331060c4a9fdb5a6c725840858c599032a6f3aac94055f/black-26.3.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f76ff19ec5297dd8e66eb64deda23631e642c9393ab592826fd4bdc97a4bce7", size = 1794994, upload-time = "2026-03-12T03:40:17.124Z" }, + { url = "https://files.pythonhosted.org/packages/e7/0a/86e462cdd311a3c2a8ece708d22aba17d0b2a0d5348ca34b40cdcbea512e/black-26.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:ddb113db38838eb9f043623ba274cfaf7d51d5b0c22ecb30afe58b1bb8322983", size = 1420867, upload-time = "2026-03-12T03:40:18.83Z" }, + { url = "https://files.pythonhosted.org/packages/5b/e5/22515a19cb7eaee3440325a6b0d95d2c0e88dd180cb011b12ae488e031d1/black-26.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:dfdd51fc3e64ea4f35873d1b3fb25326773d55d2329ff8449139ebaad7357efb", size = 1230124, upload-time = "2026-03-12T03:40:20.425Z" }, + { url = "https://files.pythonhosted.org/packages/f5/77/5728052a3c0450c53d9bb3945c4c46b91baa62b2cafab6801411b6271e45/black-26.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:855822d90f884905362f602880ed8b5df1b7e3ee7d0db2502d4388a954cc8c54", size = 1895034, upload-time = "2026-03-12T03:40:21.813Z" }, + { url = "https://files.pythonhosted.org/packages/52/73/7cae55fdfdfbe9d19e9a8d25d145018965fe2079fa908101c3733b0c55a0/black-26.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8a33d657f3276328ce00e4d37fe70361e1ec7614da5d7b6e78de5426cb56332f", size = 1718503, upload-time = "2026-03-12T03:40:23.666Z" }, + { url = "https://files.pythonhosted.org/packages/e1/87/af89ad449e8254fdbc74654e6467e3c9381b61472cc532ee350d28cfdafb/black-26.3.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f1cd08e99d2f9317292a311dfe578fd2a24b15dbce97792f9c4d752275c1fa56", size = 1793557, upload-time = "2026-03-12T03:40:25.497Z" }, + { url = "https://files.pythonhosted.org/packages/43/10/d6c06a791d8124b843bf325ab4ac7d2f5b98731dff84d6064eafd687ded1/black-26.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:c7e72339f841b5a237ff14f7d3880ddd0fc7f98a1199e8c4327f9a4f478c1839", size = 1422766, upload-time = "2026-03-12T03:40:27.14Z" }, + { url = "https://files.pythonhosted.org/packages/59/4f/40a582c015f2d841ac24fed6390bd68f0fc896069ff3a886317959c9daf8/black-26.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:afc622538b430aa4c8c853f7f63bc582b3b8030fd8c80b70fb5fa5b834e575c2", size = 1232140, upload-time = "2026-03-12T03:40:28.882Z" }, + { url = "https://files.pythonhosted.org/packages/d5/da/e36e27c9cebc1311b7579210df6f1c86e50f2d7143ae4fcf8a5017dc8809/black-26.3.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2d6bfaf7fd0993b420bed691f20f9492d53ce9a2bcccea4b797d34e947318a78", size = 1889234, upload-time = "2026-03-12T03:40:30.964Z" }, + { url = "https://files.pythonhosted.org/packages/0e/7b/9871acf393f64a5fa33668c19350ca87177b181f44bb3d0c33b2d534f22c/black-26.3.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f89f2ab047c76a9c03f78d0d66ca519e389519902fa27e7a91117ef7611c0568", size = 1720522, upload-time = "2026-03-12T03:40:32.346Z" }, + { url = "https://files.pythonhosted.org/packages/03/87/e766c7f2e90c07fb7586cc787c9ae6462b1eedab390191f2b7fc7f6170a9/black-26.3.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b07fc0dab849d24a80a29cfab8d8a19187d1c4685d8a5e6385a5ce323c1f015f", size = 1787824, upload-time = "2026-03-12T03:40:33.636Z" }, + { url = "https://files.pythonhosted.org/packages/ac/94/2424338fb2d1875e9e83eed4c8e9c67f6905ec25afd826a911aea2b02535/black-26.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:0126ae5b7c09957da2bdbd91a9ba1207453feada9e9fe51992848658c6c8e01c", size = 1445855, upload-time = "2026-03-12T03:40:35.442Z" }, + { url = "https://files.pythonhosted.org/packages/86/43/0c3338bd928afb8ee7471f1a4eec3bdbe2245ccb4a646092a222e8669840/black-26.3.1-cp314-cp314-win_arm64.whl", hash = "sha256:92c0ec1f2cc149551a2b7b47efc32c866406b6891b0ee4625e95967c8f4acfb1", size = 1258109, upload-time = "2026-03-12T03:40:36.832Z" }, + { url = "https://files.pythonhosted.org/packages/8e/0d/52d98722666d6fc6c3dd4c76df339501d6efd40e0ff95e6186a7b7f0befd/black-26.3.1-py3-none-any.whl", hash = "sha256:2bd5aa94fc267d38bb21a70d7410a89f1a1d318841855f698746f8e7f51acd1b", size = 207542, upload-time = "2026-03-12T03:36:01.668Z" }, ] [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/25/ee/6caf7a40c36a1220410afe15a1cc64993a1f864871f698c0f93acb72842a/certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580", size = 137077, upload-time = "2026-04-22T11:26:11.191Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/ce/ee2ecad540810a79593028e88299baeae54d346cc7a0d94b6199988b89b1/certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d", size = 135422, upload-time = "2026-05-20T11:46:50.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/22/30/7cd8fdcdfbc5b869528b079bfb76dcdf6056b1a2097a662e5e8c04f42965/certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a", size = 135707, upload-time = "2026-04-22T11:26:09.372Z" }, + { url = "https://files.pythonhosted.org/packages/59/8c/57e832b7af6d7c5abe66eb3fbe3a3a32f4d11ea23a1aa7131371035be991/certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", size = 134134, upload-time = "2026-05-20T11:46:48.578Z" }, ] [[package]] @@ -185,14 +194,14 @@ wheels = [ [[package]] name = "click" -version = "8.3.3" +version = "8.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bb/63/f9e1ea081ce35720d8b92acde70daaedace594dc93b693c869e0d5910718/click-8.3.3.tar.gz", hash = "sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2", size = 328061, upload-time = "2026-04-22T15:11:27.506Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9b/98/518d8e5081007684232226f475082b30087d0f585e8457db087298259f49/click-8.4.1.tar.gz", hash = "sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96", size = 353007, upload-time = "2026-05-22T04:08:37.769Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl", hash = "sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613", size = 110502, upload-time = "2026-04-22T15:11:25.044Z" }, + { url = "https://files.pythonhosted.org/packages/c7/0d/67e5b4109ea4a837e80daa87c2c696711955e40449a97e8926672534def2/click-8.4.1-py3-none-any.whl", hash = "sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2", size = 116639, upload-time = "2026-05-22T04:08:35.26Z" }, ] [[package]] @@ -206,101 +215,101 @@ wheels = [ [[package]] name = "coverage" -version = "7.13.5" +version = "7.14.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9d/e0/70553e3000e345daff267cec284ce4cbf3fc141b6da229ac52775b5428f1/coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179", size = 915967, upload-time = "2026-03-17T10:33:18.341Z" } +sdist = { url = "https://files.pythonhosted.org/packages/54/fd/0ab2772530e946e1be1abd0bc09e647ec9b02e88f0867857601fefca8953/coverage-7.14.1.tar.gz", hash = "sha256:30c08f7d90415aa98b3c990385dea2939b0da55f38515e5b369b83655f8523be", size = 920132, upload-time = "2026-05-26T20:41:36.783Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/37/d24c8f8220ff07b839b2c043ea4903a33b0f455abe673ae3c03bbdb7f212/coverage-7.13.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66a80c616f80181f4d643b0f9e709d97bcea413ecd9631e1dedc7401c8e6695d", size = 219381, upload-time = "2026-03-17T10:30:14.68Z" }, - { url = "https://files.pythonhosted.org/packages/35/8b/cd129b0ca4afe886a6ce9d183c44d8301acbd4ef248622e7c49a23145605/coverage-7.13.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:145ede53ccbafb297c1c9287f788d1bc3efd6c900da23bf6931b09eafc931587", size = 219880, upload-time = "2026-03-17T10:30:16.231Z" }, - { url = "https://files.pythonhosted.org/packages/55/2f/e0e5b237bffdb5d6c530ce87cc1d413a5b7d7dfd60fb067ad6d254c35c76/coverage-7.13.5-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0672854dc733c342fa3e957e0605256d2bf5934feeac328da9e0b5449634a642", size = 250303, upload-time = "2026-03-17T10:30:17.748Z" }, - { url = "https://files.pythonhosted.org/packages/92/be/b1afb692be85b947f3401375851484496134c5554e67e822c35f28bf2fbc/coverage-7.13.5-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ec10e2a42b41c923c2209b846126c6582db5e43a33157e9870ba9fb70dc7854b", size = 252218, upload-time = "2026-03-17T10:30:19.804Z" }, - { url = "https://files.pythonhosted.org/packages/da/69/2f47bb6fa1b8d1e3e5d0c4be8ccb4313c63d742476a619418f85740d597b/coverage-7.13.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be3d4bbad9d4b037791794ddeedd7d64a56f5933a2c1373e18e9e568b9141686", size = 254326, upload-time = "2026-03-17T10:30:21.321Z" }, - { url = "https://files.pythonhosted.org/packages/d5/d0/79db81da58965bd29dabc8f4ad2a2af70611a57cba9d1ec006f072f30a54/coverage-7.13.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d2afbc5cc54d286bfb54541aa50b64cdb07a718227168c87b9e2fb8f25e1743", size = 256267, upload-time = "2026-03-17T10:30:23.094Z" }, - { url = "https://files.pythonhosted.org/packages/e5/32/d0d7cc8168f91ddab44c0ce4806b969df5f5fdfdbb568eaca2dbc2a04936/coverage-7.13.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3ad050321264c49c2fa67bb599100456fc51d004b82534f379d16445da40fb75", size = 250430, upload-time = "2026-03-17T10:30:25.311Z" }, - { url = "https://files.pythonhosted.org/packages/4d/06/a055311d891ddbe231cd69fdd20ea4be6e3603ffebddf8704b8ca8e10a3c/coverage-7.13.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7300c8a6d13335b29bb76d7651c66af6bd8658517c43499f110ddc6717bfc209", size = 252017, upload-time = "2026-03-17T10:30:27.284Z" }, - { url = "https://files.pythonhosted.org/packages/d6/f6/d0fd2d21e29a657b5f77a2fe7082e1568158340dceb941954f776dce1b7b/coverage-7.13.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:eb07647a5738b89baab047f14edd18ded523de60f3b30e75c2acc826f79c839a", size = 250080, upload-time = "2026-03-17T10:30:29.481Z" }, - { url = "https://files.pythonhosted.org/packages/4e/ab/0d7fb2efc2e9a5eb7ddcc6e722f834a69b454b7e6e5888c3a8567ecffb31/coverage-7.13.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9adb6688e3b53adffefd4a52d72cbd8b02602bfb8f74dcd862337182fd4d1a4e", size = 253843, upload-time = "2026-03-17T10:30:31.301Z" }, - { url = "https://files.pythonhosted.org/packages/ba/6f/7467b917bbf5408610178f62a49c0ed4377bb16c1657f689cc61470da8ce/coverage-7.13.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7c8d4bc913dd70b93488d6c496c77f3aff5ea99a07e36a18f865bca55adef8bd", size = 249802, upload-time = "2026-03-17T10:30:33.358Z" }, - { url = "https://files.pythonhosted.org/packages/75/2c/1172fb689df92135f5bfbbd69fc83017a76d24ea2e2f3a1154007e2fb9f8/coverage-7.13.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0e3c426ffc4cd952f54ee9ffbdd10345709ecc78a3ecfd796a57236bfad0b9b8", size = 250707, upload-time = "2026-03-17T10:30:35.2Z" }, - { url = "https://files.pythonhosted.org/packages/67/21/9ac389377380a07884e3b48ba7a620fcd9dbfaf1d40565facdc6b36ec9ef/coverage-7.13.5-cp311-cp311-win32.whl", hash = "sha256:259b69bb83ad9894c4b25be2528139eecba9a82646ebdda2d9db1ba28424a6bf", size = 221880, upload-time = "2026-03-17T10:30:36.775Z" }, - { url = "https://files.pythonhosted.org/packages/af/7f/4cd8a92531253f9d7c1bbecd9fa1b472907fb54446ca768c59b531248dc5/coverage-7.13.5-cp311-cp311-win_amd64.whl", hash = "sha256:258354455f4e86e3e9d0d17571d522e13b4e1e19bf0f8596bcf9476d61e7d8a9", size = 222816, upload-time = "2026-03-17T10:30:38.891Z" }, - { url = "https://files.pythonhosted.org/packages/12/a6/1d3f6155fb0010ca68eba7fe48ca6c9da7385058b77a95848710ecf189b1/coverage-7.13.5-cp311-cp311-win_arm64.whl", hash = "sha256:bff95879c33ec8da99fc9b6fe345ddb5be6414b41d6d1ad1c8f188d26f36e028", size = 221483, upload-time = "2026-03-17T10:30:40.463Z" }, - { url = "https://files.pythonhosted.org/packages/a0/c3/a396306ba7db865bf96fc1fb3b7fd29bcbf3d829df642e77b13555163cd6/coverage-7.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:460cf0114c5016fa841214ff5564aa4864f11948da9440bc97e21ad1f4ba1e01", size = 219554, upload-time = "2026-03-17T10:30:42.208Z" }, - { url = "https://files.pythonhosted.org/packages/a6/16/a68a19e5384e93f811dccc51034b1fd0b865841c390e3c931dcc4699e035/coverage-7.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e223ce4b4ed47f065bfb123687686512e37629be25cc63728557ae7db261422", size = 219908, upload-time = "2026-03-17T10:30:43.906Z" }, - { url = "https://files.pythonhosted.org/packages/29/72/20b917c6793af3a5ceb7fb9c50033f3ec7865f2911a1416b34a7cfa0813b/coverage-7.13.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e3370441f4513c6252bf042b9c36d22491142385049243253c7e48398a15a9f", size = 251419, upload-time = "2026-03-17T10:30:45.545Z" }, - { url = "https://files.pythonhosted.org/packages/8c/49/cd14b789536ac6a4778c453c6a2338bc0a2fb60c5a5a41b4008328b9acc1/coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:03ccc709a17a1de074fb1d11f217342fb0d2b1582ed544f554fc9fc3f07e95f5", size = 254159, upload-time = "2026-03-17T10:30:47.204Z" }, - { url = "https://files.pythonhosted.org/packages/9d/00/7b0edcfe64e2ed4c0340dac14a52ad0f4c9bd0b8b5e531af7d55b703db7c/coverage-7.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f4818d065964db3c1c66dc0fbdac5ac692ecbc875555e13374fdbe7eedb4376", size = 255270, upload-time = "2026-03-17T10:30:48.812Z" }, - { url = "https://files.pythonhosted.org/packages/93/89/7ffc4ba0f5d0a55c1e84ea7cee39c9fc06af7b170513d83fbf3bbefce280/coverage-7.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256", size = 257538, upload-time = "2026-03-17T10:30:50.77Z" }, - { url = "https://files.pythonhosted.org/packages/81/bd/73ddf85f93f7e6fa83e77ccecb6162d9415c79007b4bc124008a4995e4a7/coverage-7.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8dd02af98971bdb956363e4827d34425cb3df19ee550ef92855b0acb9c7ce51c", size = 251821, upload-time = "2026-03-17T10:30:52.5Z" }, - { url = "https://files.pythonhosted.org/packages/a0/81/278aff4e8dec4926a0bcb9486320752811f543a3ce5b602cc7a29978d073/coverage-7.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f08fd75c50a760c7eb068ae823777268daaf16a80b918fa58eea888f8e3919f5", size = 253191, upload-time = "2026-03-17T10:30:54.543Z" }, - { url = "https://files.pythonhosted.org/packages/70/ee/fe1621488e2e0a58d7e94c4800f0d96f79671553488d401a612bebae324b/coverage-7.13.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:843ea8643cf967d1ac7e8ecd4bb00c99135adf4816c0c0593fdcc47b597fcf09", size = 251337, upload-time = "2026-03-17T10:30:56.663Z" }, - { url = "https://files.pythonhosted.org/packages/37/a6/f79fb37aa104b562207cc23cb5711ab6793608e246cae1e93f26b2236ed9/coverage-7.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d44d7aa963820b1b971dbecd90bfe5fe8f81cff79787eb6cca15750bd2f79b9", size = 255404, upload-time = "2026-03-17T10:30:58.427Z" }, - { url = "https://files.pythonhosted.org/packages/75/f0/ed15262a58ec81ce457ceb717b7f78752a1713556b19081b76e90896e8d4/coverage-7.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7132bed4bd7b836200c591410ae7d97bf7ae8be6fc87d160b2bd881df929e7bf", size = 250903, upload-time = "2026-03-17T10:31:00.093Z" }, - { url = "https://files.pythonhosted.org/packages/0f/e9/9129958f20e7e9d4d56d51d42ccf708d15cac355ff4ac6e736e97a9393d2/coverage-7.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a698e363641b98843c517817db75373c83254781426e94ada3197cabbc2c919c", size = 252780, upload-time = "2026-03-17T10:31:01.916Z" }, - { url = "https://files.pythonhosted.org/packages/a4/d7/0ad9b15812d81272db94379fe4c6df8fd17781cc7671fdfa30c76ba5ff7b/coverage-7.13.5-cp312-cp312-win32.whl", hash = "sha256:bdba0a6b8812e8c7df002d908a9a2ea3c36e92611b5708633c50869e6d922fdf", size = 222093, upload-time = "2026-03-17T10:31:03.642Z" }, - { url = "https://files.pythonhosted.org/packages/29/3d/821a9a5799fac2556bcf0bd37a70d1d11fa9e49784b6d22e92e8b2f85f18/coverage-7.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:d2c87e0c473a10bffe991502eac389220533024c8082ec1ce849f4218dded810", size = 222900, upload-time = "2026-03-17T10:31:05.651Z" }, - { url = "https://files.pythonhosted.org/packages/d4/fa/2238c2ad08e35cf4f020ea721f717e09ec3152aea75d191a7faf3ef009a8/coverage-7.13.5-cp312-cp312-win_arm64.whl", hash = "sha256:bf69236a9a81bdca3bff53796237aab096cdbf8d78a66ad61e992d9dac7eb2de", size = 221515, upload-time = "2026-03-17T10:31:07.293Z" }, - { url = "https://files.pythonhosted.org/packages/74/8c/74fedc9663dcf168b0a059d4ea756ecae4da77a489048f94b5f512a8d0b3/coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ec4af212df513e399cf11610cc27063f1586419e814755ab362e50a85ea69c1", size = 219576, upload-time = "2026-03-17T10:31:09.045Z" }, - { url = "https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3", size = 219942, upload-time = "2026-03-17T10:31:10.708Z" }, - { url = "https://files.pythonhosted.org/packages/5f/13/93419671cee82b780bab7ea96b67c8ef448f5f295f36bf5031154ec9a790/coverage-7.13.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:da305e9937617ee95c2e39d8ff9f040e0487cbf1ac174f777ed5eddd7a7c1f26", size = 250935, upload-time = "2026-03-17T10:31:12.392Z" }, - { url = "https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3", size = 253541, upload-time = "2026-03-17T10:31:14.247Z" }, - { url = "https://files.pythonhosted.org/packages/4e/5e/3ee3b835647be646dcf3c65a7c6c18f87c27326a858f72ab22c12730773d/coverage-7.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02ca0eed225b2ff301c474aeeeae27d26e2537942aa0f87491d3e147e784a82b", size = 254780, upload-time = "2026-03-17T10:31:16.193Z" }, - { url = "https://files.pythonhosted.org/packages/44/b3/cb5bd1a04cfcc49ede6cd8409d80bee17661167686741e041abc7ee1b9a9/coverage-7.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04690832cbea4e4663d9149e05dba142546ca05cb1848816760e7f58285c970a", size = 256912, upload-time = "2026-03-17T10:31:17.89Z" }, - { url = "https://files.pythonhosted.org/packages/1b/66/c1dceb7b9714473800b075f5c8a84f4588f887a90eb8645282031676e242/coverage-7.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0590e44dd2745c696a778f7bab6aa95256de2cbc8b8cff4f7db8ff09813d6969", size = 251165, upload-time = "2026-03-17T10:31:19.605Z" }, - { url = "https://files.pythonhosted.org/packages/b7/62/5502b73b97aa2e53ea22a39cf8649ff44827bef76d90bf638777daa27a9d/coverage-7.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d7cfad2d6d81dd298ab6b89fe72c3b7b05ec7544bdda3b707ddaecff8d25c161", size = 252908, upload-time = "2026-03-17T10:31:21.312Z" }, - { url = "https://files.pythonhosted.org/packages/7d/37/7792c2d69854397ca77a55c4646e5897c467928b0e27f2d235d83b5d08c6/coverage-7.13.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e092b9499de38ae0fbfbc603a74660eb6ff3e869e507b50d85a13b6db9863e15", size = 250873, upload-time = "2026-03-17T10:31:23.565Z" }, - { url = "https://files.pythonhosted.org/packages/a3/23/bc866fb6163be52a8a9e5d708ba0d3b1283c12158cefca0a8bbb6e247a43/coverage-7.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:48c39bc4a04d983a54a705a6389512883d4a3b9862991b3617d547940e9f52b1", size = 255030, upload-time = "2026-03-17T10:31:25.58Z" }, - { url = "https://files.pythonhosted.org/packages/7d/8b/ef67e1c222ef49860701d346b8bbb70881bef283bd5f6cbba68a39a086c7/coverage-7.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2d3807015f138ffea1ed9afeeb8624fd781703f2858b62a8dd8da5a0994c57b6", size = 250694, upload-time = "2026-03-17T10:31:27.316Z" }, - { url = "https://files.pythonhosted.org/packages/46/0d/866d1f74f0acddbb906db212e096dee77a8e2158ca5e6bb44729f9d93298/coverage-7.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee2aa19e03161671ec964004fb74b2257805d9710bf14a5c704558b9d8dbaf17", size = 252469, upload-time = "2026-03-17T10:31:29.472Z" }, - { url = "https://files.pythonhosted.org/packages/7a/f5/be742fec31118f02ce42b21c6af187ad6a344fed546b56ca60caacc6a9a0/coverage-7.13.5-cp313-cp313-win32.whl", hash = "sha256:ce1998c0483007608c8382f4ff50164bfc5bd07a2246dd272aa4043b75e61e85", size = 222112, upload-time = "2026-03-17T10:31:31.526Z" }, - { url = "https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b", size = 222923, upload-time = "2026-03-17T10:31:33.633Z" }, - { url = "https://files.pythonhosted.org/packages/48/af/fea819c12a095781f6ccd504890aaddaf88b8fab263c4940e82c7b770124/coverage-7.13.5-cp313-cp313-win_arm64.whl", hash = "sha256:f4cd16206ad171cbc2470dbea9103cf9a7607d5fe8c242fdf1edf36174020664", size = 221540, upload-time = "2026-03-17T10:31:35.445Z" }, - { url = "https://files.pythonhosted.org/packages/23/d2/17879af479df7fbbd44bd528a31692a48f6b25055d16482fdf5cdb633805/coverage-7.13.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0428cbef5783ad91fe240f673cc1f76b25e74bbfe1a13115e4aa30d3f538162d", size = 220262, upload-time = "2026-03-17T10:31:37.184Z" }, - { url = "https://files.pythonhosted.org/packages/5b/4c/d20e554f988c8f91d6a02c5118f9abbbf73a8768a3048cb4962230d5743f/coverage-7.13.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e0b216a19534b2427cc201a26c25da4a48633f29a487c61258643e89d28200c0", size = 220617, upload-time = "2026-03-17T10:31:39.245Z" }, - { url = "https://files.pythonhosted.org/packages/29/9c/f9f5277b95184f764b24e7231e166dfdb5780a46d408a2ac665969416d61/coverage-7.13.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:972a9cd27894afe4bc2b1480107054e062df08e671df7c2f18c205e805ccd806", size = 261912, upload-time = "2026-03-17T10:31:41.324Z" }, - { url = "https://files.pythonhosted.org/packages/d5/f6/7f1ab39393eeb50cfe4747ae8ef0e4fc564b989225aa1152e13a180d74f8/coverage-7.13.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4b59148601efcd2bac8c4dbf1f0ad6391693ccf7a74b8205781751637076aee3", size = 263987, upload-time = "2026-03-17T10:31:43.724Z" }, - { url = "https://files.pythonhosted.org/packages/a0/d7/62c084fb489ed9c6fbdf57e006752e7c516ea46fd690e5ed8b8617c7d52e/coverage-7.13.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:505d7083c8b0c87a8fa8c07370c285847c1f77739b22e299ad75a6af6c32c5c9", size = 266416, upload-time = "2026-03-17T10:31:45.769Z" }, - { url = "https://files.pythonhosted.org/packages/a9/f6/df63d8660e1a0bff6125947afda112a0502736f470d62ca68b288ea762d8/coverage-7.13.5-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:60365289c3741e4db327e7baff2a4aaacf22f788e80fa4683393891b70a89fbd", size = 267558, upload-time = "2026-03-17T10:31:48.293Z" }, - { url = "https://files.pythonhosted.org/packages/5b/02/353ca81d36779bd108f6d384425f7139ac3c58c750dcfaafe5d0bee6436b/coverage-7.13.5-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1b88c69c8ef5d4b6fe7dea66d6636056a0f6a7527c440e890cf9259011f5e606", size = 261163, upload-time = "2026-03-17T10:31:50.125Z" }, - { url = "https://files.pythonhosted.org/packages/2c/16/2e79106d5749bcaf3aee6d309123548e3276517cd7851faa8da213bc61bf/coverage-7.13.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5b13955d31d1633cf9376908089b7cebe7d15ddad7aeaabcbe969a595a97e95e", size = 263981, upload-time = "2026-03-17T10:31:51.961Z" }, - { url = "https://files.pythonhosted.org/packages/29/c7/c29e0c59ffa6942030ae6f50b88ae49988e7e8da06de7ecdbf49c6d4feae/coverage-7.13.5-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f70c9ab2595c56f81a89620e22899eea8b212a4041bd728ac6f4a28bf5d3ddd0", size = 261604, upload-time = "2026-03-17T10:31:53.872Z" }, - { url = "https://files.pythonhosted.org/packages/40/48/097cdc3db342f34006a308ab41c3a7c11c3f0d84750d340f45d88a782e00/coverage-7.13.5-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:084b84a8c63e8d6fc7e3931b316a9bcafca1458d753c539db82d31ed20091a87", size = 265321, upload-time = "2026-03-17T10:31:55.997Z" }, - { url = "https://files.pythonhosted.org/packages/bb/1f/4994af354689e14fd03a75f8ec85a9a68d94e0188bbdab3fc1516b55e512/coverage-7.13.5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ad14385487393e386e2ea988b09d62dd42c397662ac2dabc3832d71253eee479", size = 260502, upload-time = "2026-03-17T10:31:58.308Z" }, - { url = "https://files.pythonhosted.org/packages/22/c6/9bb9ef55903e628033560885f5c31aa227e46878118b63ab15dc7ba87797/coverage-7.13.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f2c47b36fe7709a6e83bfadf4eefb90bd25fbe4014d715224c4316f808e59a2", size = 262688, upload-time = "2026-03-17T10:32:00.141Z" }, - { url = "https://files.pythonhosted.org/packages/14/4f/f5df9007e50b15e53e01edea486814783a7f019893733d9e4d6caad75557/coverage-7.13.5-cp313-cp313t-win32.whl", hash = "sha256:67e9bc5449801fad0e5dff329499fb090ba4c5800b86805c80617b4e29809b2a", size = 222788, upload-time = "2026-03-17T10:32:02.246Z" }, - { url = "https://files.pythonhosted.org/packages/e1/98/aa7fccaa97d0f3192bec013c4e6fd6d294a6ed44b640e6bb61f479e00ed5/coverage-7.13.5-cp313-cp313t-win_amd64.whl", hash = "sha256:da86cdcf10d2519e10cabb8ac2de03da1bcb6e4853790b7fbd48523332e3a819", size = 223851, upload-time = "2026-03-17T10:32:04.416Z" }, - { url = "https://files.pythonhosted.org/packages/3d/8b/e5c469f7352651e5f013198e9e21f97510b23de957dd06a84071683b4b60/coverage-7.13.5-cp313-cp313t-win_arm64.whl", hash = "sha256:0ecf12ecb326fe2c339d93fc131816f3a7367d223db37817208905c89bded911", size = 222104, upload-time = "2026-03-17T10:32:06.65Z" }, - { url = "https://files.pythonhosted.org/packages/8e/77/39703f0d1d4b478bfd30191d3c14f53caf596fac00efb3f8f6ee23646439/coverage-7.13.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fbabfaceaeb587e16f7008f7795cd80d20ec548dc7f94fbb0d4ec2e038ce563f", size = 219621, upload-time = "2026-03-17T10:32:08.589Z" }, - { url = "https://files.pythonhosted.org/packages/e2/3e/51dff36d99ae14639a133d9b164d63e628532e2974d8b1edb99dd1ebc733/coverage-7.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9bb2a28101a443669a423b665939381084412b81c3f8c0fcfbac57f4e30b5b8e", size = 219953, upload-time = "2026-03-17T10:32:10.507Z" }, - { url = "https://files.pythonhosted.org/packages/6a/6c/1f1917b01eb647c2f2adc9962bd66c79eb978951cab61bdc1acab3290c07/coverage-7.13.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bd3a2fbc1c6cccb3c5106140d87cc6a8715110373ef42b63cf5aea29df8c217a", size = 250992, upload-time = "2026-03-17T10:32:12.41Z" }, - { url = "https://files.pythonhosted.org/packages/22/e5/06b1f88f42a5a99df42ce61208bdec3bddb3d261412874280a19796fc09c/coverage-7.13.5-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6c36ddb64ed9d7e496028d1d00dfec3e428e0aabf4006583bb1839958d280510", size = 253503, upload-time = "2026-03-17T10:32:14.449Z" }, - { url = "https://files.pythonhosted.org/packages/80/28/2a148a51e5907e504fa7b85490277734e6771d8844ebcc48764a15e28155/coverage-7.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:380e8e9084d8eb38db3a9176a1a4f3c0082c3806fa0dc882d1d87abc3c789247", size = 254852, upload-time = "2026-03-17T10:32:16.56Z" }, - { url = "https://files.pythonhosted.org/packages/61/77/50e8d3d85cc0b7ebe09f30f151d670e302c7ff4a1bf6243f71dd8b0981fa/coverage-7.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e808af52a0513762df4d945ea164a24b37f2f518cbe97e03deaa0ee66139b4d6", size = 257161, upload-time = "2026-03-17T10:32:19.004Z" }, - { url = "https://files.pythonhosted.org/packages/3b/c4/b5fd1d4b7bf8d0e75d997afd3925c59ba629fc8616f1b3aae7605132e256/coverage-7.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e301d30dd7e95ae068671d746ba8c34e945a82682e62918e41b2679acd2051a0", size = 251021, upload-time = "2026-03-17T10:32:21.344Z" }, - { url = "https://files.pythonhosted.org/packages/f8/66/6ea21f910e92d69ef0b1c3346ea5922a51bad4446c9126db2ae96ee24c4c/coverage-7.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:800bc829053c80d240a687ceeb927a94fd108bbdc68dfbe505d0d75ab578a882", size = 252858, upload-time = "2026-03-17T10:32:23.506Z" }, - { url = "https://files.pythonhosted.org/packages/9e/ea/879c83cb5d61aa2a35fb80e72715e92672daef8191b84911a643f533840c/coverage-7.13.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:0b67af5492adb31940ee418a5a655c28e48165da5afab8c7fa6fd72a142f8740", size = 250823, upload-time = "2026-03-17T10:32:25.516Z" }, - { url = "https://files.pythonhosted.org/packages/8a/fb/616d95d3adb88b9803b275580bdeee8bd1b69a886d057652521f83d7322f/coverage-7.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c9136ff29c3a91e25b1d1552b5308e53a1e0653a23e53b6366d7c2dcbbaf8a16", size = 255099, upload-time = "2026-03-17T10:32:27.944Z" }, - { url = "https://files.pythonhosted.org/packages/1c/93/25e6917c90ec1c9a56b0b26f6cad6408e5f13bb6b35d484a0d75c9cf000d/coverage-7.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:cff784eef7f0b8f6cb28804fbddcfa99f89efe4cc35fb5627e3ac58f91ed3ac0", size = 250638, upload-time = "2026-03-17T10:32:29.914Z" }, - { url = "https://files.pythonhosted.org/packages/fc/7b/dc1776b0464145a929deed214aef9fb1493f159b59ff3c7eeeedf91eddd0/coverage-7.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:68a4953be99b17ac3c23b6efbc8a38330d99680c9458927491d18700ef23ded0", size = 252295, upload-time = "2026-03-17T10:32:31.981Z" }, - { url = "https://files.pythonhosted.org/packages/ea/fb/99cbbc56a26e07762a2740713f3c8f9f3f3106e3a3dd8cc4474954bccd34/coverage-7.13.5-cp314-cp314-win32.whl", hash = "sha256:35a31f2b1578185fbe6aa2e74cea1b1d0bbf4c552774247d9160d29b80ed56cc", size = 222360, upload-time = "2026-03-17T10:32:34.233Z" }, - { url = "https://files.pythonhosted.org/packages/8d/b7/4758d4f73fb536347cc5e4ad63662f9d60ba9118cb6785e9616b2ce5d7fa/coverage-7.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:2aa055ae1857258f9e0045be26a6d62bdb47a72448b62d7b55f4820f361a2633", size = 223174, upload-time = "2026-03-17T10:32:36.369Z" }, - { url = "https://files.pythonhosted.org/packages/2c/f2/24d84e1dfe70f8ac9fdf30d338239860d0d1d5da0bda528959d0ebc9da28/coverage-7.13.5-cp314-cp314-win_arm64.whl", hash = "sha256:1b11eef33edeae9d142f9b4358edb76273b3bfd30bc3df9a4f95d0e49caf94e8", size = 221739, upload-time = "2026-03-17T10:32:38.736Z" }, - { url = "https://files.pythonhosted.org/packages/60/5b/4a168591057b3668c2428bff25dd3ebc21b629d666d90bcdfa0217940e84/coverage-7.13.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10a0c37f0b646eaff7cce1874c31d1f1ccb297688d4c747291f4f4c70741cc8b", size = 220351, upload-time = "2026-03-17T10:32:41.196Z" }, - { url = "https://files.pythonhosted.org/packages/f5/21/1fd5c4dbfe4a58b6b99649125635df46decdfd4a784c3cd6d410d303e370/coverage-7.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b5db73ba3c41c7008037fa731ad5459fc3944cb7452fc0aa9f822ad3533c583c", size = 220612, upload-time = "2026-03-17T10:32:43.204Z" }, - { url = "https://files.pythonhosted.org/packages/d6/fe/2a924b3055a5e7e4512655a9d4609781b0d62334fa0140c3e742926834e2/coverage-7.13.5-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:750db93a81e3e5a9831b534be7b1229df848b2e125a604fe6651e48aa070e5f9", size = 261985, upload-time = "2026-03-17T10:32:45.514Z" }, - { url = "https://files.pythonhosted.org/packages/d7/0d/c8928f2bd518c45990fe1a2ab8db42e914ef9b726c975facc4282578c3eb/coverage-7.13.5-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ddb4f4a5479f2539644be484da179b653273bca1a323947d48ab107b3ed1f29", size = 264107, upload-time = "2026-03-17T10:32:47.971Z" }, - { url = "https://files.pythonhosted.org/packages/ef/ae/4ae35bbd9a0af9d820362751f0766582833c211224b38665c0f8de3d487f/coverage-7.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8a7a2049c14f413163e2bdabd37e41179b1d1ccb10ffc6ccc4b7a718429c607", size = 266513, upload-time = "2026-03-17T10:32:50.1Z" }, - { url = "https://files.pythonhosted.org/packages/9c/20/d326174c55af36f74eac6ae781612d9492f060ce8244b570bb9d50d9d609/coverage-7.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1c85e0b6c05c592ea6d8768a66a254bfb3874b53774b12d4c89c481eb78cb90", size = 267650, upload-time = "2026-03-17T10:32:52.391Z" }, - { url = "https://files.pythonhosted.org/packages/7a/5e/31484d62cbd0eabd3412e30d74386ece4a0837d4f6c3040a653878bfc019/coverage-7.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:777c4d1eff1b67876139d24288aaf1817f6c03d6bae9c5cc8d27b83bcfe38fe3", size = 261089, upload-time = "2026-03-17T10:32:54.544Z" }, - { url = "https://files.pythonhosted.org/packages/e9/d8/49a72d6de146eebb0b7e48cc0f4bc2c0dd858e3d4790ab2b39a2872b62bd/coverage-7.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6697e29b93707167687543480a40f0db8f356e86d9f67ddf2e37e2dfd91a9dab", size = 263982, upload-time = "2026-03-17T10:32:56.803Z" }, - { url = "https://files.pythonhosted.org/packages/06/3b/0351f1bd566e6e4dd39e978efe7958bde1d32f879e85589de147654f57bb/coverage-7.13.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8fdf453a942c3e4d99bd80088141c4c6960bb232c409d9c3558e2dbaa3998562", size = 261579, upload-time = "2026-03-17T10:32:59.466Z" }, - { url = "https://files.pythonhosted.org/packages/5d/ce/796a2a2f4017f554d7810f5c573449b35b1e46788424a548d4d19201b222/coverage-7.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:32ca0c0114c9834a43f045a87dcebd69d108d8ffb666957ea65aa132f50332e2", size = 265316, upload-time = "2026-03-17T10:33:01.847Z" }, - { url = "https://files.pythonhosted.org/packages/3d/16/d5ae91455541d1a78bc90abf495be600588aff8f6db5c8b0dae739fa39c9/coverage-7.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8769751c10f339021e2638cd354e13adeac54004d1941119b2c96fe5276d45ea", size = 260427, upload-time = "2026-03-17T10:33:03.945Z" }, - { url = "https://files.pythonhosted.org/packages/48/11/07f413dba62db21fb3fad5d0de013a50e073cc4e2dc4306e770360f6dfc8/coverage-7.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cec2d83125531bd153175354055cdb7a09987af08a9430bd173c937c6d0fba2a", size = 262745, upload-time = "2026-03-17T10:33:06.285Z" }, - { url = "https://files.pythonhosted.org/packages/91/15/d792371332eb4663115becf4bad47e047d16234b1aff687b1b18c58d60ae/coverage-7.13.5-cp314-cp314t-win32.whl", hash = "sha256:0cd9ed7a8b181775459296e402ca4fb27db1279740a24e93b3b41942ebe4b215", size = 223146, upload-time = "2026-03-17T10:33:08.756Z" }, - { url = "https://files.pythonhosted.org/packages/db/51/37221f59a111dca5e85be7dbf09696323b5b9f13ff65e0641d535ed06ea8/coverage-7.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:301e3b7dfefecaca37c9f1aa6f0049b7d4ab8dd933742b607765d757aca77d43", size = 224254, upload-time = "2026-03-17T10:33:11.174Z" }, - { url = "https://files.pythonhosted.org/packages/54/83/6acacc889de8987441aa7d5adfbdbf33d288dad28704a67e574f1df9bcbb/coverage-7.13.5-cp314-cp314t-win_arm64.whl", hash = "sha256:9dacc2ad679b292709e0f5fc1ac74a6d4d5562e424058962c7bb0c658ad25e45", size = 222276, upload-time = "2026-03-17T10:33:13.466Z" }, - { url = "https://files.pythonhosted.org/packages/9e/ee/a4cf96b8ce1e566ed238f0659ac2d3f007ed1d14b181bcb684e19561a69a/coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61", size = 211346, upload-time = "2026-03-17T10:33:15.691Z" }, + { url = "https://files.pythonhosted.org/packages/7d/d7/477ad149490e6cb849f28abea1dabb9c823cea72e7500c81b4240ce619c0/coverage-7.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:478b5bcd63c2e1357c5c7e16c070690df7b07f676b1c114d7b93e533c664309f", size = 219848, upload-time = "2026-05-26T20:38:38.715Z" }, + { url = "https://files.pythonhosted.org/packages/91/82/a5eb47257c50601bb7b9a9d2857c67b7a3a85ad74180eb2c98bb1fbe0ce5/coverage-7.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a24a81f9715ee42ef59a316cc11611c98fe23920f7c81861315c9f3ff4a230f4", size = 220354, upload-time = "2026-05-26T20:38:40.232Z" }, + { url = "https://files.pythonhosted.org/packages/43/8b/78419b5391a5cb706b6544390507e469d83ffc9a8248b02c4011aceb9365/coverage-7.14.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:196a13319ad88d6d8ef5ab489ec4f44ddde2143c0c7d5b27786f6c3ffd56a7e1", size = 250771, upload-time = "2026-05-26T20:38:41.782Z" }, + { url = "https://files.pythonhosted.org/packages/77/63/e77aaacd491182210d639636b7a8bba23ffffa9b82aa3762da9431855fa9/coverage-7.14.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3d452fd08b5c72c5167c93e6867b5c08500bd40f2a21e1e854a500550b6cc36f", size = 252683, upload-time = "2026-05-26T20:38:43.305Z" }, + { url = "https://files.pythonhosted.org/packages/65/1c/a022e3cfbec2ac241640003cb3a817e161d9c7f5aa9b49173756cdc03204/coverage-7.14.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23bf7fa51ac02e07fc7c96849b82946da47ae862dc8f86d183b2a4864fc38129", size = 254791, upload-time = "2026-05-26T20:38:45.361Z" }, + { url = "https://files.pythonhosted.org/packages/61/d6/967e408aca4c1ceb88cb0cc677169110ae7f5995fb5eaf5fb1f5a1bb8f5d/coverage-7.14.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bcaa50684dcaadfa599ac48f81103c756d791cfd85c97203d2217c593d48b860", size = 256748, upload-time = "2026-05-26T20:38:46.91Z" }, + { url = "https://files.pythonhosted.org/packages/b8/be/869188f7fe28638078ec479331ace6dc5f7b40b7153eb616f47ab79404d8/coverage-7.14.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4ea1c034f95c9b056e856b794630b17f9fa3d57e4800ff1e503d3be0f9c9078c", size = 250907, upload-time = "2026-05-26T20:38:48.493Z" }, + { url = "https://files.pythonhosted.org/packages/07/aa/adb7d3b4278d690e68703abcd76ab1b948242e3668d921711551b78f9ddb/coverage-7.14.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c7e057326434e441306226fbeb5d1aaf14a2637efe97ba668306635835f32ad7", size = 252483, upload-time = "2026-05-26T20:38:50.074Z" }, + { url = "https://files.pythonhosted.org/packages/43/61/331c74103c62dcb0c4b9b3a0de9a61aca016208b0a90f109592a9f9ecc28/coverage-7.14.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:59baf88468dbc8d63b1887afd92bda52e40bb1561696e5819670601403810cec", size = 250545, upload-time = "2026-05-26T20:38:51.613Z" }, + { url = "https://files.pythonhosted.org/packages/f6/b6/c5dae3c104d89be04828f61810e6b3473825482e4c288cc4ed04553e08ae/coverage-7.14.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d34d75f892b3ab73ba11cab5442cce7b3e168fd64162b16f0e1e0d09c508edef", size = 254310, upload-time = "2026-05-26T20:38:53.503Z" }, + { url = "https://files.pythonhosted.org/packages/ad/a1/2b9d5863e3b83c01ad8199e3c597802fbb3a9dc90b058885804c20296d31/coverage-7.14.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3a56abc20a472baf0304c455721bc601477440d28ecfde8a03dde79ede07e0df", size = 250266, upload-time = "2026-05-26T20:38:55.414Z" }, + { url = "https://files.pythonhosted.org/packages/7f/5e/0e511fbdb269359be26fe678a1c3fa1f2aa2a01573cc3f54268c8d6d4797/coverage-7.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6a3cb83d1552c0cd1b4906655b6a33fd4a8473229633a901c6b73bf86914dee9", size = 251174, upload-time = "2026-05-26T20:38:57.141Z" }, + { url = "https://files.pythonhosted.org/packages/85/10/e55307b622b3dd9671cb321824502dc10f93e72f2802b9946159a8edadeb/coverage-7.14.1-cp311-cp311-win32.whl", hash = "sha256:10274a1fbeb8ec5d72966e17bb198a3104257aca4ac09d98667c5f8aca8c8548", size = 222354, upload-time = "2026-05-26T20:38:58.727Z" }, + { url = "https://files.pythonhosted.org/packages/71/cf/107421693cfb71e4f1ca5bf70443f64d4161878068d07a3e51c7ad21d17b/coverage-7.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:87ebdf787d4888e3f3f2d523eadc6e18c6d18c6d0eb173801a189641627fb37e", size = 223290, upload-time = "2026-05-26T20:39:00.413Z" }, + { url = "https://files.pythonhosted.org/packages/b8/1d/3e3644585eb29e9dafefb19555078529a4d7cce12bd21929664eea989277/coverage-7.14.1-cp311-cp311-win_arm64.whl", hash = "sha256:dd34767fa19848d35659ffc0a75314f58c7af3f1cd87ec521e8292a1238398a3", size = 221953, upload-time = "2026-05-26T20:39:02.159Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b7/bdbb725ba02c5b42825b200c940f38b7a54fcad24627b7192f78f8110d76/coverage-7.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a06c76364a9360e33d6d23769aefdf7f66f38e2ffb60ceb1baaa4989d83b695c", size = 220022, upload-time = "2026-05-26T20:39:03.702Z" }, + { url = "https://files.pythonhosted.org/packages/72/81/fdc0898a55c6219223291ec1a1fe89966ef212ce82276aa0899df84b5de0/coverage-7.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fad54e871165f6ec2f536063ac74c3104508a12963e64072ba44bd822de52b0c", size = 220379, upload-time = "2026-05-26T20:39:05.381Z" }, + { url = "https://files.pythonhosted.org/packages/de/72/de048c4a25e13bce59ac6a339351c10bdf2515e07459afcdaf04dc3143a2/coverage-7.14.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:84b535f00655ecafe1d929d1fb00ed5d6fa3051ea643ab2c161a3887b86f294b", size = 251888, upload-time = "2026-05-26T20:39:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/28/30/300c343f68beb9d4cbb64ec81e58c5b6b80b56927f72d2b38654ac26e013/coverage-7.14.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6b6b0853b895fe0e98cbfc580d1ec3393d9302b4b1e96a77b3f5c91fdab899e6", size = 254624, upload-time = "2026-05-26T20:39:09.037Z" }, + { url = "https://files.pythonhosted.org/packages/b1/ed/7b25642496e8170b6bac14adce00537c6e5fa2d586159401a4de3e8b49e6/coverage-7.14.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:442cc9c952b2df400cda54bb04ab87330cf2cd08a8692cbbea36773531eb6f37", size = 255739, upload-time = "2026-05-26T20:39:10.889Z" }, + { url = "https://files.pythonhosted.org/packages/7f/a2/abd210b8c4e29c24e4624916db97bb519097a91034aaeb767f937e7da794/coverage-7.14.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8270544c361ed405a27a060dbc9ed2c124b084d96dfdc2d9a2510482aef981ad", size = 257998, upload-time = "2026-05-26T20:39:12.722Z" }, + { url = "https://files.pythonhosted.org/packages/7f/24/7c50beed3792fe62f6ce0545c6686ce83379719e2c0276179333d97eae92/coverage-7.14.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:48b283b1dd6372e8de2a7a9a4c4d5dc06f4d4fd209b876f3c88a7a205a0c8f84", size = 252296, upload-time = "2026-05-26T20:39:14.259Z" }, + { url = "https://files.pythonhosted.org/packages/15/05/0f874628ebcbfc77ead559ff210281ef06a97db08481832e7dd39274a135/coverage-7.14.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5b0c99ba93a07d56f6df340bb79be53202a082b2fdb81bfe6190b741a3470d54", size = 253658, upload-time = "2026-05-26T20:39:15.923Z" }, + { url = "https://files.pythonhosted.org/packages/99/6f/ca6ad067364b337ef997802115e7ecad2abd2248b05471464b0dea02b4d4/coverage-7.14.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e471bc5769ff073b058cfadb0d736b56ce067c8560eabeb0da88462df98c23e7", size = 251803, upload-time = "2026-05-26T20:39:17.537Z" }, + { url = "https://files.pythonhosted.org/packages/c0/30/b9b4d377cd9f40baf228068f5a81faf8450c6228503011bd499708483a50/coverage-7.14.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f497a1ea81d4cd7c10ddcaa685135b9aabd291af3d55775a9ddf3cb7a364cdd9", size = 255873, upload-time = "2026-05-26T20:39:19.414Z" }, + { url = "https://files.pythonhosted.org/packages/3c/21/7c721a9e5e6bb88547d30a787aefb97512d3f54c1324c7488d9b3743f7f9/coverage-7.14.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2222be86d0b54f5dd5a38f45f17f315f737245e857bf0bdedc70734f84a13c02", size = 251372, upload-time = "2026-05-26T20:39:21.169Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f8ae5a2200130e1503cd7661a6cd3b2b7bacef98277fbf3571fb13f8b766/coverage-7.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:85e85586565842f6932abebd4c18bcb1074223dc0b3576e7d173ca710622813a", size = 253245, upload-time = "2026-05-26T20:39:23.097Z" }, + { url = "https://files.pythonhosted.org/packages/34/62/70a9024672a5f6910517d9628c52c9afbdd3cf8f46426af52bb148a56fff/coverage-7.14.1-cp312-cp312-win32.whl", hash = "sha256:4a28fd227808366b196a75476dced2eb35b351d6766ba9c858dc93319e87f4f1", size = 222567, upload-time = "2026-05-26T20:39:24.868Z" }, + { url = "https://files.pythonhosted.org/packages/f6/81/8b7cd386839b039ebe1855733b9f9449a8dec5d79564018234f185a7fa70/coverage-7.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:54acdb6674a4661768d7bf7db32dfb9f46ab1d764f8aba6df75ce1a6a088724e", size = 223372, upload-time = "2026-05-26T20:39:26.603Z" }, + { url = "https://files.pythonhosted.org/packages/ae/ba/b44d472022f620d289d95fa830143235c0c36461c6f2437ea8d51e5481ed/coverage-7.14.1-cp312-cp312-win_arm64.whl", hash = "sha256:99cd41ff91afd94896fea3bc002706b6ae4ce95727d06e4a0f39c0a8d8bd8b1a", size = 221989, upload-time = "2026-05-26T20:39:28.242Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9e/5f6d56327c62b185225d145191c607e07515294a0aa6338e58805cd4a5ac/coverage-7.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:be9f2c802dcfce3f71298303aa5dad0dce440a76c52f2f60dacd8656dab78793", size = 220044, upload-time = "2026-05-26T20:39:29.902Z" }, + { url = "https://files.pythonhosted.org/packages/75/92/e82aca356744cbbc0f77a0b623e38918c1872361963413a3bab5d0340393/coverage-7.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6223a72fd0e4c7156353ec0f08a5f93623e1d3034d0e2683b9bb8ea674131b1d", size = 220412, upload-time = "2026-05-26T20:39:31.561Z" }, + { url = "https://files.pythonhosted.org/packages/27/c9/385bde0bf7ed0f4bf3a7ee5367060a86b5d218718cfd6fb943c0f836b34f/coverage-7.14.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7279d2110a28cebc738b6459ecda2771735a4c18465fbbd36b3288fe5ed92247", size = 251412, upload-time = "2026-05-26T20:39:33.337Z" }, + { url = "https://files.pythonhosted.org/packages/51/8c/23faf6a2343a0d17f960a4bd56c43bc7eb4cf312f774dd6ceebd82c7d8fc/coverage-7.14.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9eeb3fcbc13ba40dfbdb22d01d196a28e9cef9ed4c29b60061a1e0e823a9929d", size = 254008, upload-time = "2026-05-26T20:39:35.009Z" }, + { url = "https://files.pythonhosted.org/packages/42/06/36f4aa9ca8a815e6036156e80706a67828bb97bd826948244f6996dda957/coverage-7.14.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f0cfc27c539f07cf5c0a4cfe211d0b6cae039f8f40526dbaa71944e64b50a7b", size = 255241, upload-time = "2026-05-26T20:39:36.71Z" }, + { url = "https://files.pythonhosted.org/packages/ca/79/95266316352f90f6b1c6736bb413302edfde2453fb32422d3911642691b3/coverage-7.14.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:221c70f316241a78e77e607c227cefc8808d4e08f28d99c04f35694690e940be", size = 257373, upload-time = "2026-05-26T20:39:38.412Z" }, + { url = "https://files.pythonhosted.org/packages/e3/9c/58316d1f66c488b5fca8a0eb3e98348807813efa8a0d0833b9021be27488/coverage-7.14.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:da028256b04ec30e5e0114b6f76172938c313991f0a2d3d894271315cf5d5e43", size = 251635, upload-time = "2026-05-26T20:39:40.268Z" }, + { url = "https://files.pythonhosted.org/packages/ef/5a/ca2398a568e16fed7bb713e84ba3603a7164fb65779abe645c565ec890d5/coverage-7.14.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76a085d7005236a767e3426148b2c407e53ad61695c562f8a81da2d373324901", size = 253373, upload-time = "2026-05-26T20:39:42.145Z" }, + { url = "https://files.pythonhosted.org/packages/6e/2c/0396562c32deaebe7be51d865b3a41e9a87d7561acafe1a28f53b07e019a/coverage-7.14.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b553d04b5e778a8e56d57eb134aff42a92718ecba45e79c4764ecfa40efd92ff", size = 251341, upload-time = "2026-05-26T20:39:43.907Z" }, + { url = "https://files.pythonhosted.org/packages/fd/8f/a94f9221184c9cae1ee115820e3798e48b6b17777a9f19e46fb9a0c8dc74/coverage-7.14.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:46f714d2fb8ae2f4f29f23ada7f1e79b759fff5a70f94a1dac23af204c3ec9e4", size = 255497, upload-time = "2026-05-26T20:39:46.166Z" }, + { url = "https://files.pythonhosted.org/packages/71/69/505d70e47db1eaebcd002c39759707621ef184cd6b1ae084d9f41293f323/coverage-7.14.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:1896f5e19ff3f0431c7ce2172adc54890fd97f86b59ced8ca1649145d9ffe35d", size = 251159, upload-time = "2026-05-26T20:39:48.03Z" }, + { url = "https://files.pythonhosted.org/packages/e0/aa/58681c383aa33a9d2ed40a02d7a22fbf780d1fa4d575396365777828198c/coverage-7.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:62fd185ef9df3c33d1c8178c5af105f762afbad96038de9a4ae100aa6297ca33", size = 252934, upload-time = "2026-05-26T20:39:49.872Z" }, + { url = "https://files.pythonhosted.org/packages/eb/fd/11c928cd6bdffc7074bb5965c173d9ebf517fb00205e1da524b98d29ef92/coverage-7.14.1-cp313-cp313-win32.whl", hash = "sha256:ab4af6352741a604c431c6072fce5bee33bf0f20dc7a56618d6bf6bb89e9810c", size = 222584, upload-time = "2026-05-26T20:39:51.68Z" }, + { url = "https://files.pythonhosted.org/packages/6f/92/fb416fc26d340dcba19518c418d6048e913186e17243982c5e435e41fa7a/coverage-7.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:7af486dabe8954d03b087f0021540897afe084f04e16ff5579e08cc46f871416", size = 223394, upload-time = "2026-05-26T20:39:53.472Z" }, + { url = "https://files.pythonhosted.org/packages/73/c6/02d56e3867972f77d5036de924643f26c056e848f00452cafb4dbc3c29b4/coverage-7.14.1-cp313-cp313-win_arm64.whl", hash = "sha256:2224f89ffd0c5605ccce1ed7a584da162bc7c55f601ab1c946bc9de31a486b42", size = 222015, upload-time = "2026-05-26T20:39:55.374Z" }, + { url = "https://files.pythonhosted.org/packages/4d/9e/fcc77914050df73f7662fa1f00902774c79c075a8388ab334074574bf77e/coverage-7.14.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:de286598cc65d2b489411174b1faec2f5a7775fb3201fd925db2a76b4030f37d", size = 220733, upload-time = "2026-05-26T20:39:57.189Z" }, + { url = "https://files.pythonhosted.org/packages/f7/67/2963cbdaf5cbadec44efa3a1e39eaa1f02df4079585f05387607a221e126/coverage-7.14.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:042c46ded7c288aeb07cf14a28b6c1e10b78fcba40171c3fa1e939377eeef0b5", size = 221086, upload-time = "2026-05-26T20:39:59.019Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c5/8701645574e11881f2f47d8930f98bc48b5d43b25eb5b4430dfc4a2f9f48/coverage-7.14.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f4ddbe407477f04c45115d1a4e5bc480f753553b534d338d4c3358b1cdd0ea52", size = 262381, upload-time = "2026-05-26T20:40:00.822Z" }, + { url = "https://files.pythonhosted.org/packages/7c/28/7a64d73598263e0c5abd5084211a8474488d31b3c552ff531c719dfcff62/coverage-7.14.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d13e6725992e2d2fd7d81d4f5241952d13740121dfd501da09201be39b2c003a", size = 264458, upload-time = "2026-05-26T20:40:02.506Z" }, + { url = "https://files.pythonhosted.org/packages/fa/d8/4969179db9f7eb4df218e69540adf829d1c835f59452513d065d15446802/coverage-7.14.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f747dc8edcfe740130f28f32f3995e955494285717e86ee25af51db2219df08a", size = 266884, upload-time = "2026-05-26T20:40:04.421Z" }, + { url = "https://files.pythonhosted.org/packages/a6/78/a45d5794dbc9bafd97afc96a4377c86c7820d78b6cf51b89bc1d4e919275/coverage-7.14.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ced2f09ef276fd58611a1ef502164ad266d2b75174e5a40cabbdb4033f9f6cf2", size = 268022, upload-time = "2026-05-26T20:40:06.298Z" }, + { url = "https://files.pythonhosted.org/packages/21/cb/4f5e354e9e3e67af96bd4e57113e6db6b22298c7168b13eec408a549903d/coverage-7.14.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b84800013769a78ccb9ef4659402e26d06867e337b61ec365f77ad008adea80e", size = 261631, upload-time = "2026-05-26T20:40:08.226Z" }, + { url = "https://files.pythonhosted.org/packages/ec/49/eced49af4cb996d5d8b7e94e736175c513e4facd3398507b89892b4326d8/coverage-7.14.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ea8cd6ca0ee9f616aaef3afc6882e32c2cbf18b00d96313ffd76af650574034d", size = 264443, upload-time = "2026-05-26T20:40:10.137Z" }, + { url = "https://files.pythonhosted.org/packages/f1/d8/5603a88a7c5913a6b54f6cb1a8c46f7b39cbb30f27cd3f492908da09b2d7/coverage-7.14.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:aa5e304a873fabddc11e484e9b6b738bd38bd7bed17b09aa84eecf5332e8b8bb", size = 262069, upload-time = "2026-05-26T20:40:11.999Z" }, + { url = "https://files.pythonhosted.org/packages/f0/59/2ae3cb79da554a06c8619d6c88ea19dd1e4aed4b834b6a83bb1fa243bdc5/coverage-7.14.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:5a1c5215be81035e629d5bc756650634d0bf31991038db7a0eccb90f025ce16d", size = 265780, upload-time = "2026-05-26T20:40:13.858Z" }, + { url = "https://files.pythonhosted.org/packages/af/5f/b130c1dc999031f2648bd25317fbce505ad8d5562079b4ed81e736a84967/coverage-7.14.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:79058c47dae6788504b5effb319961bcd72d7240551464b91d474bc0ed186d69", size = 260970, upload-time = "2026-05-26T20:40:16.142Z" }, + { url = "https://files.pythonhosted.org/packages/87/d1/ec13ccddeb48ec963bdfa72a11224bac2584bd045ba13beca82f8113e9c7/coverage-7.14.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:370c5afae3fa0658e11694a32b24c2778f6bc2d17718121f94ee185e69f26b54", size = 263157, upload-time = "2026-05-26T20:40:18.382Z" }, + { url = "https://files.pythonhosted.org/packages/cf/c2/cd91ead503045161092d3845f7bb95ea2f25131ce96d3e314dd835d91b9c/coverage-7.14.1-cp313-cp313t-win32.whl", hash = "sha256:3758dd0a7f1fa57365ef2e781df0f0731d38b6e3772259d13dae4bd8a958d4b1", size = 223259, upload-time = "2026-05-26T20:40:20.381Z" }, + { url = "https://files.pythonhosted.org/packages/71/9f/1e28d97e6bd2c76b07f38b7c02870f1371255ff6717f54eca578fcbbdd0e/coverage-7.14.1-cp313-cp313t-win_amd64.whl", hash = "sha256:6ff665fb023a77386fe11685190cee1f60a7d635994a30d9b0a061533d470fce", size = 224320, upload-time = "2026-05-26T20:40:22.316Z" }, + { url = "https://files.pythonhosted.org/packages/a9/e0/d936e908f0e1efa55e52b91e01b52f1055cef5e1ab2718493390ed8e2fb8/coverage-7.14.1-cp313-cp313t-win_arm64.whl", hash = "sha256:17a5a241e5997621a956a7f402a7433ef4221e5152809b785bec79e2323799f1", size = 222577, upload-time = "2026-05-26T20:40:24.894Z" }, + { url = "https://files.pythonhosted.org/packages/d6/34/fc2f101b151af3799a101f0550b0454aa008afdc0add677394ec4aa8ea10/coverage-7.14.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d5ed429d0b8edaac649e889b4ffcedb6c80b06629a3f93050e3dddfb99235bee", size = 220091, upload-time = "2026-05-26T20:40:27.249Z" }, + { url = "https://files.pythonhosted.org/packages/3d/a7/1ebae2ab5b961b5c79bb09fe7b3ac99edb190d8be4a8c510b2cf66f46468/coverage-7.14.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8011224a62280e50dab346960c03cf47aca1a1e09e608c0fb33fd6e0cc8e9500", size = 220421, upload-time = "2026-05-26T20:40:30.084Z" }, + { url = "https://files.pythonhosted.org/packages/5e/90/92aca9cf0acc95123c96cd1eb1f08917897a7f5dee01e15738922971ec31/coverage-7.14.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:12c42ec1e14f553c4f817e989365982e646e27211f10a0f717855b94a79c8906", size = 251466, upload-time = "2026-05-26T20:40:32.542Z" }, + { url = "https://files.pythonhosted.org/packages/26/2b/78048cbe3b999f6cbf9cc0d90abba6a88a3e0863a8c1c6cbc762f3f8802f/coverage-7.14.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:06144cd511cf2624873a035c5069cf297144f6e77a73ee3d7a55b605ec5efb42", size = 253973, upload-time = "2026-05-26T20:40:34.473Z" }, + { url = "https://files.pythonhosted.org/packages/8e/21/c2e33b29d1cfde484a19d437afc343c6cd30b08d78cbbf9f5aff14e57b2b/coverage-7.14.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a311d8e1da24be5c1ccf85cbfb06315dbaa1703d5a1eab3f6432c72b837917c8", size = 255318, upload-time = "2026-05-26T20:40:38.154Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ee/aad2f108d63b769121005302f16bf66db8625c88ceaba466942e09a2607e/coverage-7.14.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c79cead5b5bc584d9c71451cb984d0e3a84e0c0937379c8efcbf27c8d661b851", size = 257633, upload-time = "2026-05-26T20:40:40.164Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f8/11a2c29b4fd76d9849f81d0bb812ec0017a9396df3217214e38934a8c837/coverage-7.14.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dcbf65f1f66a26cdd88c35cf68fb4729c5d1cd2e88added72420541dfb212034", size = 251488, upload-time = "2026-05-26T20:40:42.631Z" }, + { url = "https://files.pythonhosted.org/packages/c9/b8/9a5820de4b8ac2b71d85e3b5fb49108d7469c665f0e2ad0dd7569023e305/coverage-7.14.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fd86572566fb40189a8260446158235159bc7a82dfbc87a3b39cf4fb57fcec1c", size = 253329, upload-time = "2026-05-26T20:40:45.208Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ff/f33e4823667e27548e8fd8df44217515303f9808d0ff29817db56f87d990/coverage-7.14.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:7771b601718fdde84832c3a434ca9bbf4ae9adbc49d84198b4110700c3c77c36", size = 251291, upload-time = "2026-05-26T20:40:47.502Z" }, + { url = "https://files.pythonhosted.org/packages/68/9b/489db0ebb209054766b90a9014a45f6d26eb724c02ec21311c3733b5a644/coverage-7.14.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:39b21e212c55af06fa375e3dbf90a8a8e38792f3a910c580066d23563830ddd5", size = 255564, upload-time = "2026-05-26T20:40:49.372Z" }, + { url = "https://files.pythonhosted.org/packages/27/b5/16bc2d4c2409b23c7737edb68c83bc89e345f378050549fe1d75ac7d34d5/coverage-7.14.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f2302660e32562a532b442480121aef8aa61a5bdb20b30bf0adab29f10a5a4b4", size = 251107, upload-time = "2026-05-26T20:40:51.677Z" }, + { url = "https://files.pythonhosted.org/packages/7d/0c/2629997469a00cd069d588a41c9dc887610f2775ae89d250c4791e65272a/coverage-7.14.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:03a6f93c1ec3b7f2e77b5dbcc5573a2c21f12529a5c6bbe0f16f72303cc2fa4d", size = 252764, upload-time = "2026-05-26T20:40:54.267Z" }, + { url = "https://files.pythonhosted.org/packages/d2/ee/f78d63c8f079e0d7211c7e2401fa17e311514534ba61bae03e4b287ce4ab/coverage-7.14.1-cp314-cp314-win32.whl", hash = "sha256:8a3ce026d73290f42f08dafecbd82c193a74df280461fbf97300fec51fd133ee", size = 222837, upload-time = "2026-05-26T20:40:56.496Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b9/be539854f93a70dfbeec69117f33ec70dc42ff0b65b5b07ab8d40d04228e/coverage-7.14.1-cp314-cp314-win_amd64.whl", hash = "sha256:114c95ef29302423b87d159075805f4ab973254a2638a5d7d046c94887cc87d7", size = 223650, upload-time = "2026-05-26T20:40:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/fe/9e/24e2842fef40f35ac82ba3a7719c8023d011bf3bf652d0675316a9d088a1/coverage-7.14.1-cp314-cp314-win_arm64.whl", hash = "sha256:a07891c3f4805442b31b71e84ba3cf29ed1aa9a428284e06deeb4b23e5b46343", size = 222218, upload-time = "2026-05-26T20:41:00.321Z" }, + { url = "https://files.pythonhosted.org/packages/0a/1d/ac0a9df5fe31c1e8bdd658074905fc12844a05c1a7e3fdb8417e97c31e23/coverage-7.14.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1101a5ebb083aecb625ebb6209d4105b58f647b093cb2dc8122d7b33f743cfe1", size = 220822, upload-time = "2026-05-26T20:41:02.281Z" }, + { url = "https://files.pythonhosted.org/packages/32/cf/f964fd9aff20323f9f1a726c97135f8a76bcd87b92dad141a456a43f3c64/coverage-7.14.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:851b9e1e4e8a4608e77c79714b2e77c0970d2ed7202a05e92ae407817481887b", size = 221084, upload-time = "2026-05-26T20:41:04.593Z" }, + { url = "https://files.pythonhosted.org/packages/d8/5e/7e5ef2aba844de2b80d678619fcf0841b42e3f37f16411226f3fe4c1016f/coverage-7.14.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d5b89cdfb2ee051b71e8c3c70bd81a9eff81100f736a269136fe1a68efe00474", size = 262454, upload-time = "2026-05-26T20:41:06.641Z" }, + { url = "https://files.pythonhosted.org/packages/64/62/75809bded87015cc4935524218a2a8ed8dd1a8498bfed30a2f4f7a4b4d34/coverage-7.14.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0177614a0370f227888b4e436a7c55686d6a9f90eb1ade2b624ba685a1686e86", size = 264578, upload-time = "2026-05-26T20:41:08.556Z" }, + { url = "https://files.pythonhosted.org/packages/f3/42/d33392dc14633525012d2d504fa1a33b05538bf535f5c1d64675e5754b78/coverage-7.14.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d69af5dea2de76fc485a83032a630523f985198b7e25be901ec60181587b01e", size = 266981, upload-time = "2026-05-26T20:41:10.824Z" }, + { url = "https://files.pythonhosted.org/packages/2a/49/0157c4428c2aca7f1e09d5565930586fd5ae36f1655f08b0daa7cf1fcae1/coverage-7.14.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:35ab22d91de736e8966b980dc355cbcdd2c6dbbcfe275f9a2991bc8a91b3df65", size = 268112, upload-time = "2026-05-26T20:41:12.966Z" }, + { url = "https://files.pythonhosted.org/packages/96/26/86b9ce71f4092b1ed325ce1421698081df1286b833400b6836912834d6e0/coverage-7.14.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:357d4e32935c36588aaba057d734fa32428c360c9fc2e4442afbf1b646beee6e", size = 261558, upload-time = "2026-05-26T20:41:15Z" }, + { url = "https://files.pythonhosted.org/packages/20/4c/c311210c5472cf5401d8422b0d7812cdd520f24417673afabda6c323faca/coverage-7.14.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:51bd64741cc6fa065abd300ede1afe5a5291ece9c31da8b24884deda48bcc3f8", size = 264447, upload-time = "2026-05-26T20:41:17.369Z" }, + { url = "https://files.pythonhosted.org/packages/fb/71/59513f8710ed3e6b0ac0a050a5b7e977bb9c9e880354863b5d00d8809256/coverage-7.14.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:9132cd363a68a4c3daa7c8704a654b1e39d3360f6f5b8ddd470608a945236c07", size = 262048, upload-time = "2026-05-26T20:41:19.309Z" }, + { url = "https://files.pythonhosted.org/packages/84/8d/bceed32dc494f5bbf50f775cd2e78ca814953942b5ea28d3c1c3ac316f14/coverage-7.14.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:07c6290b1697b862c0478eab545eec949a0d0e4d6d03497f446d706da3b4f2de", size = 265781, upload-time = "2026-05-26T20:41:21.559Z" }, + { url = "https://files.pythonhosted.org/packages/e7/c5/9348fe40dbfd4991aaf78df2c6c3098bfb2cc834d1fd362a64b4efef855a/coverage-7.14.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:5ea0c297e27133853b4d8a3eb799bff5a2dbd9f2f41537a240d337ac9b4df890", size = 260896, upload-time = "2026-05-26T20:41:23.428Z" }, + { url = "https://files.pythonhosted.org/packages/ca/92/1ea0f03929da7cf87206b1fa24f4c8e9c158be0455481af29ec0a1f3503f/coverage-7.14.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:01b7733daad0237daa01ef80fe2dfceffc911e6a17fa7b55d14aa8214eaaaecd", size = 263214, upload-time = "2026-05-26T20:41:25.419Z" }, + { url = "https://files.pythonhosted.org/packages/f6/a9/b2493c054c0e01a643266742ab45e15744e60743f9260cd930c7142b1124/coverage-7.14.1-cp314-cp314t-win32.whl", hash = "sha256:6adc5a36984624a70bf11d7184e20fa0a49aa7c47ffab43804106a1a695ea22e", size = 223624, upload-time = "2026-05-26T20:41:27.795Z" }, + { url = "https://files.pythonhosted.org/packages/fc/bd/3e1e6a57fccd2d7c83fcdf338e93ba98eb85c6e877dd34731ac585375490/coverage-7.14.1-cp314-cp314t-win_amd64.whl", hash = "sha256:ddf799247318f34dbcd2efa8c95a8d0642674e926bb1774cf9b63dfd2a389d1c", size = 224728, upload-time = "2026-05-26T20:41:30.098Z" }, + { url = "https://files.pythonhosted.org/packages/bb/d7/31066cf1d2f0c6c797fce911bcfa01dd35642dc6da992a950256097c5860/coverage-7.14.1-cp314-cp314t-win_arm64.whl", hash = "sha256:145986fe66647eb489f18d9a997567a3fd358584c4b5a808769113abc07466af", size = 222752, upload-time = "2026-05-26T20:41:32.123Z" }, + { url = "https://files.pythonhosted.org/packages/8a/3c/1a983b9a745d7f83d53f057bcc5bf79ba6a2bbc08266b3f0c7d6fe630c9b/coverage-7.14.1-py3-none-any.whl", hash = "sha256:a252f21c27e38347e60111a3266b03827422a7d5525951aceee313aa68bab1d2", size = 211815, upload-time = "2026-05-26T20:41:34.078Z" }, ] [package.optional-dependencies] @@ -310,58 +319,58 @@ toml = [ [[package]] name = "debugpy" -version = "1.8.20" +version = "1.8.21" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e0/b7/cd8080344452e4874aae67c40d8940e2b4d47b01601a8fd9f44786c757c7/debugpy-1.8.20.tar.gz", hash = "sha256:55bc8701714969f1ab89a6d5f2f3d40c36f91b2cbe2f65d98bf8196f6a6a2c33", size = 1645207, upload-time = "2026-01-29T23:03:28.199Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/aa/12037145b7a56eaa5b29b41872f7a21b538e807e13f32c4d3c46e59be084/debugpy-1.8.21.tar.gz", hash = "sha256:a3c53278e84c94e11bd87c53970ec391d1a67396c8b22609fcac576520e611a6", size = 1697577, upload-time = "2026-06-01T19:30:35.156Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/51/56/c3baf5cbe4dd77427fd9aef99fcdade259ad128feeb8a786c246adb838e5/debugpy-1.8.20-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:eada6042ad88fa1571b74bd5402ee8b86eded7a8f7b827849761700aff171f1b", size = 2208318, upload-time = "2026-01-29T23:03:36.481Z" }, - { url = "https://files.pythonhosted.org/packages/9a/7d/4fa79a57a8e69fe0d9763e98d1110320f9ecd7f1f362572e3aafd7417c9d/debugpy-1.8.20-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:7de0b7dfeedc504421032afba845ae2a7bcc32ddfb07dae2c3ca5442f821c344", size = 3171493, upload-time = "2026-01-29T23:03:37.775Z" }, - { url = "https://files.pythonhosted.org/packages/7d/f2/1e8f8affe51e12a26f3a8a8a4277d6e60aa89d0a66512f63b1e799d424a4/debugpy-1.8.20-cp311-cp311-win32.whl", hash = "sha256:773e839380cf459caf73cc533ea45ec2737a5cc184cf1b3b796cd4fd98504fec", size = 5209240, upload-time = "2026-01-29T23:03:39.109Z" }, - { url = "https://files.pythonhosted.org/packages/d5/92/1cb532e88560cbee973396254b21bece8c5d7c2ece958a67afa08c9f10dc/debugpy-1.8.20-cp311-cp311-win_amd64.whl", hash = "sha256:1f7650546e0eded1902d0f6af28f787fa1f1dbdbc97ddabaf1cd963a405930cb", size = 5233481, upload-time = "2026-01-29T23:03:40.659Z" }, - { url = "https://files.pythonhosted.org/packages/14/57/7f34f4736bfb6e00f2e4c96351b07805d83c9a7b33d28580ae01374430f7/debugpy-1.8.20-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:4ae3135e2089905a916909ef31922b2d733d756f66d87345b3e5e52b7a55f13d", size = 2550686, upload-time = "2026-01-29T23:03:42.023Z" }, - { url = "https://files.pythonhosted.org/packages/ab/78/b193a3975ca34458f6f0e24aaf5c3e3da72f5401f6054c0dfd004b41726f/debugpy-1.8.20-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:88f47850a4284b88bd2bfee1f26132147d5d504e4e86c22485dfa44b97e19b4b", size = 4310588, upload-time = "2026-01-29T23:03:43.314Z" }, - { url = "https://files.pythonhosted.org/packages/c1/55/f14deb95eaf4f30f07ef4b90a8590fc05d9e04df85ee379712f6fb6736d7/debugpy-1.8.20-cp312-cp312-win32.whl", hash = "sha256:4057ac68f892064e5f98209ab582abfee3b543fb55d2e87610ddc133a954d390", size = 5331372, upload-time = "2026-01-29T23:03:45.526Z" }, - { url = "https://files.pythonhosted.org/packages/a1/39/2bef246368bd42f9bd7cba99844542b74b84dacbdbea0833e610f384fee8/debugpy-1.8.20-cp312-cp312-win_amd64.whl", hash = "sha256:a1a8f851e7cf171330679ef6997e9c579ef6dd33c9098458bd9986a0f4ca52e3", size = 5372835, upload-time = "2026-01-29T23:03:47.245Z" }, - { url = "https://files.pythonhosted.org/packages/15/e2/fc500524cc6f104a9d049abc85a0a8b3f0d14c0a39b9c140511c61e5b40b/debugpy-1.8.20-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:5dff4bb27027821fdfcc9e8f87309a28988231165147c31730128b1c983e282a", size = 2539560, upload-time = "2026-01-29T23:03:48.738Z" }, - { url = "https://files.pythonhosted.org/packages/90/83/fb33dcea789ed6018f8da20c5a9bc9d82adc65c0c990faed43f7c955da46/debugpy-1.8.20-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:84562982dd7cf5ebebfdea667ca20a064e096099997b175fe204e86817f64eaf", size = 4293272, upload-time = "2026-01-29T23:03:50.169Z" }, - { url = "https://files.pythonhosted.org/packages/a6/25/b1e4a01bfb824d79a6af24b99ef291e24189080c93576dfd9b1a2815cd0f/debugpy-1.8.20-cp313-cp313-win32.whl", hash = "sha256:da11dea6447b2cadbf8ce2bec59ecea87cc18d2c574980f643f2d2dfe4862393", size = 5331208, upload-time = "2026-01-29T23:03:51.547Z" }, - { url = "https://files.pythonhosted.org/packages/13/f7/a0b368ce54ffff9e9028c098bd2d28cfc5b54f9f6c186929083d4c60ba58/debugpy-1.8.20-cp313-cp313-win_amd64.whl", hash = "sha256:eb506e45943cab2efb7c6eafdd65b842f3ae779f020c82221f55aca9de135ed7", size = 5372930, upload-time = "2026-01-29T23:03:53.585Z" }, - { url = "https://files.pythonhosted.org/packages/33/2e/f6cb9a8a13f5058f0a20fe09711a7b726232cd5a78c6a7c05b2ec726cff9/debugpy-1.8.20-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:9c74df62fc064cd5e5eaca1353a3ef5a5d50da5eb8058fcef63106f7bebe6173", size = 2538066, upload-time = "2026-01-29T23:03:54.999Z" }, - { url = "https://files.pythonhosted.org/packages/c5/56/6ddca50b53624e1ca3ce1d1e49ff22db46c47ea5fb4c0cc5c9b90a616364/debugpy-1.8.20-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:077a7447589ee9bc1ff0cdf443566d0ecf540ac8aa7333b775ebcb8ce9f4ecad", size = 4269425, upload-time = "2026-01-29T23:03:56.518Z" }, - { url = "https://files.pythonhosted.org/packages/c5/d9/d64199c14a0d4c476df46c82470a3ce45c8d183a6796cfb5e66533b3663c/debugpy-1.8.20-cp314-cp314-win32.whl", hash = "sha256:352036a99dd35053b37b7803f748efc456076f929c6a895556932eaf2d23b07f", size = 5331407, upload-time = "2026-01-29T23:03:58.481Z" }, - { url = "https://files.pythonhosted.org/packages/e0/d9/1f07395b54413432624d61524dfd98c1a7c7827d2abfdb8829ac92638205/debugpy-1.8.20-cp314-cp314-win_amd64.whl", hash = "sha256:a98eec61135465b062846112e5ecf2eebb855305acc1dfbae43b72903b8ab5be", size = 5372521, upload-time = "2026-01-29T23:03:59.864Z" }, - { url = "https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl", hash = "sha256:5be9bed9ae3be00665a06acaa48f8329d2b9632f15fd09f6a9a8c8d9907e54d7", size = 5337658, upload-time = "2026-01-29T23:04:17.404Z" }, + { url = "https://files.pythonhosted.org/packages/89/fb/cbf306d6e07a313a91e7171a98669054502840931432c227cfd505ee367f/debugpy-1.8.21-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:da456226c7b4c69e35dbe35dcee6623d912000a77816db7856a41af1c72a0264", size = 2203120, upload-time = "2026-06-01T19:30:43.964Z" }, + { url = "https://files.pythonhosted.org/packages/aa/57/aa739bd4ad2cbf96aeb1b20b56918ddd5ae4c28b68709bfcd327f02123ee/debugpy-1.8.21-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:f68b891688e61bdc08b8d364d919ff0051e0b94657b39dcd027bc3173edb7cdc", size = 3059958, upload-time = "2026-06-01T19:30:45.622Z" }, + { url = "https://files.pythonhosted.org/packages/a8/31/453d2c9a23d133fe2c8ec7ca1d816ded52a913487fe3ffef7c01b4b706af/debugpy-1.8.21-cp311-cp311-win32.whl", hash = "sha256:f843a8b08c2edeaf9b1582eed4f25441af21a297c22ff16bf76a662557aa9c9e", size = 5236515, upload-time = "2026-06-01T19:30:47.461Z" }, + { url = "https://files.pythonhosted.org/packages/60/94/6660de2f2d7bf388f229335ba4637646eebabdbf38564cb439a95a9193c9/debugpy-1.8.21-cp311-cp311-win_amd64.whl", hash = "sha256:84c564d8cc701d41843b29a92814c1f1bef6798724ca9d675c284ad9f6a547d7", size = 5256138, upload-time = "2026-06-01T19:30:49.113Z" }, + { url = "https://files.pythonhosted.org/packages/a2/df/bf625547431a9cadc9f4cbfeda38866e2b17f6aed147b625377e87834449/debugpy-1.8.21-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:9f96713896f39c3dff0ee841f47320c3f2983d33c341e009361bb0ebc79adc4e", size = 2483609, upload-time = "2026-06-01T19:30:50.794Z" }, + { url = "https://files.pythonhosted.org/packages/bf/09/59324b903599031ff9faaec1758292409f6561a0ec2492fe4b703327705a/debugpy-1.8.21-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:c193d474f0a211191f2b4449d2d06157c689013035bd952f3b617e0ef422b176", size = 3968900, upload-time = "2026-06-01T19:30:52.341Z" }, + { url = "https://files.pythonhosted.org/packages/14/cd/27f65b805d7fe005c44e1a36b9183ecdfbcdbf9d3e721a5115d461ecc7ee/debugpy-1.8.21-cp312-cp312-win32.whl", hash = "sha256:4743373c1cac7f9e74a1b9915bf1dbe0e900eca657ffb170ae07ac8363205ae9", size = 5336340, upload-time = "2026-06-01T19:30:54.047Z" }, + { url = "https://files.pythonhosted.org/packages/77/1d/c84e30c0c674184948b66f076ab271c01d940618a2824c23cd035a27bc20/debugpy-1.8.21-cp312-cp312-win_amd64.whl", hash = "sha256:bd7ba9dd3daa7c2f942c6ca8d4695a16bf9ac16b63615261c7982bc74f7ed20c", size = 5374751, upload-time = "2026-06-01T19:30:55.891Z" }, + { url = "https://files.pythonhosted.org/packages/77/6b/d817e1f8cc77aa055d37fba092e0febfdff40fe652d8d53d4cd7a86ad98d/debugpy-1.8.21-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:13678151fc401e2d68c9880b91e28714f797d40422994572b24560ef80910a88", size = 2477398, upload-time = "2026-06-01T19:30:57.644Z" }, + { url = "https://files.pythonhosted.org/packages/48/57/412421516afc3055fa577516f00beec3d663f9b0ab330639547ae6c57720/debugpy-1.8.21-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:ecbd158386c31ffe71d46f72d44d56e66331ab9b16cad649156d514368f23ab2", size = 3962096, upload-time = "2026-06-01T19:30:59.235Z" }, + { url = "https://files.pythonhosted.org/packages/c1/62/2c616337cf6ba7b07ebbc97f02c6c945a8e2f76b365e33ee809c32ee36d1/debugpy-1.8.21-cp313-cp313-win32.whl", hash = "sha256:2c2ae706dec41d99a9ca1f7ebc987a83e65578363be6f6b3ac9067504917fae1", size = 5336288, upload-time = "2026-06-01T19:31:00.79Z" }, + { url = "https://files.pythonhosted.org/packages/f8/99/9175103392f84c4b1bf7622888cdc68da07f0ff7d9e581266428f6776033/debugpy-1.8.21-cp313-cp313-win_amd64.whl", hash = "sha256:aa648733047443eb1d07682c4ef287d36a54507b643ffdf38b09a3ef002c72a0", size = 5376567, upload-time = "2026-06-01T19:31:02.56Z" }, + { url = "https://files.pythonhosted.org/packages/ce/3d/f4bbb323a548bfab2af3d6b4ffd9bf22636e55956a1285d317a1de643aad/debugpy-1.8.21-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:9bb2a685287a2ac9b181cde89edcec64845cb51de7faaa75badb9a698bc24782", size = 2477209, upload-time = "2026-06-01T19:31:04.157Z" }, + { url = "https://files.pythonhosted.org/packages/8c/2d/6e7ec524984a1702777868de49a4c53202bddac2a432a76a093469587750/debugpy-1.8.21-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:3d6922439bf33fd38a3e2c447869ebc7b97da5cd3d329ff1ef9bc06c4903437e", size = 3927115, upload-time = "2026-06-01T19:31:05.863Z" }, + { url = "https://files.pythonhosted.org/packages/97/47/d1aa6d64005a98a9144647d99306b419396f9ad7bf1d73c119e17a81fb4d/debugpy-1.8.21-cp314-cp314-win32.whl", hash = "sha256:15d4963bd5ffa48f0da0947fd06757fa7621945048a14ad7705431566d3c0e7c", size = 5336724, upload-time = "2026-06-01T19:31:07.711Z" }, + { url = "https://files.pythonhosted.org/packages/5f/67/b905b90d163af11878c1af8abafa4a25206335e112e284e413454543a6da/debugpy-1.8.21-cp314-cp314-win_amd64.whl", hash = "sha256:fe0744a12353406de0ae8ccff0d0a4a666f00801a3db8fd04e7a5f761cd520e8", size = 5373803, upload-time = "2026-06-01T19:31:09.469Z" }, + { url = "https://files.pythonhosted.org/packages/95/51/67e7cf11a53e40694f720457d5b3a1cdaaa3d5a9a633e482f225456b93ff/debugpy-1.8.21-py2.py3-none-any.whl", hash = "sha256:b1e37d333663c8851516a47364ef473da127f9caebe4417e6df6f5825a7e9a92", size = 5352888, upload-time = "2026-06-01T19:31:25.186Z" }, ] [[package]] name = "distlib" -version = "0.4.0" +version = "0.4.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } +sdist = { url = "https://files.pythonhosted.org/packages/86/b2/d6fc3f2347f43dada79e5ff118493e8109c98400a0e29a1d5264a3aa479b/distlib-0.4.1.tar.gz", hash = "sha256:c3804d0d2d4b5fcd44036eb860cb6660485fcdf5c2aba53dc324d805837ea65b", size = 610526, upload-time = "2026-06-02T11:17:40.691Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, + { url = "https://files.pythonhosted.org/packages/25/18/3497c4fa83a76dcb154923fd2075522e8dd6995ecee4093c00ae18160046/distlib-0.4.1-py2.py3-none-any.whl", hash = "sha256:9c2c552c68cbadc619f2d0ed3a69e27c351a3f4c9baa9ffb7df9e9cdc3d19a97", size = 469216, upload-time = "2026-06-02T11:17:38.779Z" }, ] [[package]] name = "fakeredis" -version = "2.27.0" +version = "2.36.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "redis" }, { name = "sortedcontainers" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/67/9e/d8fa38cb86a2ec07312fc9fa713f2a6b2e8fa3db37aa3a6fd3d31b5fc641/fakeredis-2.27.0.tar.gz", hash = "sha256:7b7584ec104392592297f46864a82cb7339a23e254ee885bf5ae07cfc64fbce7", size = 157510, upload-time = "2025-02-11T16:22:06.579Z" } +sdist = { url = "https://files.pythonhosted.org/packages/40/26/9cf5174d2e42c24761f87d76160f959e7dbea1cf35b2c81c9cfcd7c74ad9/fakeredis-2.36.0.tar.gz", hash = "sha256:66d00953c9bfd3e345266ded342a2e54c611417344f43ad1467cb68f30bc8354", size = 209484, upload-time = "2026-05-29T19:14:27.316Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/98/4cecb38cb9345f8dcfd5c274f43e47f1070a5c2328d48dc57611b927fc47/fakeredis-2.27.0-py3-none-any.whl", hash = "sha256:f4b6e0fa4193acbf00d81dac71ff5cc34fe7d7c12f1560b036f98578a103d5c3", size = 112257, upload-time = "2025-02-11T16:22:04.374Z" }, + { url = "https://files.pythonhosted.org/packages/66/5a/6ea14103f9254f0bef31d323e8fe1f57bc0151f7a4b287889217752e9879/fakeredis-2.36.0-py3-none-any.whl", hash = "sha256:43536ed9eb7af34a31226ee4e52c8471d1c3cc522ba2852a9985d3291e71287b", size = 138102, upload-time = "2026-05-29T19:14:25.974Z" }, ] [[package]] name = "filelock" -version = "3.29.0" +version = "3.29.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b5/fe/997687a931ab51049acce6fa1f23e8f01216374ea81374ddee763c493db5/filelock-3.29.0.tar.gz", hash = "sha256:69974355e960702e789734cb4871f884ea6fe50bd8404051a3530bc07809cf90", size = 57571, upload-time = "2026-04-19T15:39:10.068Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/f9/f38573ed5844586db374d085911740a501ccfa373b455fc9413f09f85237/filelock-3.29.1.tar.gz", hash = "sha256:d97e6b1b9757569626c58caa07dc4beb1613f4a2938b1e8cc81afca398906c9e", size = 59335, upload-time = "2026-06-03T15:19:04.053Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl", hash = "sha256:96f5f6344709aa1572bbf631c640e4ebeeb519e08da902c39a001882f30ac258", size = 39812, upload-time = "2026-04-19T15:39:08.752Z" }, + { url = "https://files.pythonhosted.org/packages/4c/a0/614c5fe402fd88951df45f4dda2fa3b4e17a99ecd92340771929169b3b95/filelock-3.29.1-py3-none-any.whl", hash = "sha256:85199dfd706869641b72b2e8955d5416a4b2b7dc4b0e8e6d97b4cc1299a6983b", size = 40750, upload-time = "2026-06-03T15:19:02.959Z" }, ] [[package]] @@ -375,11 +384,11 @@ wheels = [ [[package]] name = "idna" -version = "3.13" +version = "3.18" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ce/cc/762dfb036166873f0059f3b7de4565e1b5bc3d6f28a414c13da27e442f99/idna-3.13.tar.gz", hash = "sha256:585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242", size = 194210, upload-time = "2026-04-22T16:42:42.314Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/13/ad7d7ca3808a898b4612b6fe93cde56b53f3034dcde235acb1f0e1df24c6/idna-3.13-py3-none-any.whl", hash = "sha256:892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3", size = 68629, upload-time = "2026-04-22T16:42:40.909Z" }, + { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, ] [[package]] @@ -420,14 +429,14 @@ wheels = [ [[package]] name = "markdown-it-py" -version = "4.1.0" +version = "4.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mdurl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5c/5c/f3aedc83549aae71cd52b9e9687fe896e3dc6e966ba20eba04718605d198/markdown_it_py-4.1.0.tar.gz", hash = "sha256:760e3f87b2787c044c5138a5ba107b7c2be26c03b13cc7f8fe42756b65b1df6c", size = 81613, upload-time = "2026-05-06T16:32:13.649Z" } +sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/88/802c82060c54bc7dde21eb0033e337838b8181a1323254aa9ec41cbfc3d1/markdown_it_py-4.1.0-py3-none-any.whl", hash = "sha256:d4939a62a2dd0cd9cb80a191a711ba1d39bac8ed5ef9e9966895b0171c01c46d", size = 90955, upload-time = "2026-05-06T16:32:12.184Z" }, + { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" }, ] [[package]] @@ -486,11 +495,11 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.9.6" +version = "4.10.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9f/4a/0883b8e3802965322523f0b200ecf33d31f10991d0401162f4b23c698b42/platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a", size = 29400, upload-time = "2026-04-09T00:04:10.812Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/47/e4501f49c178ae1d9f4a75073fda4204f52647993f075a9db4d14930e0c5/platformdirs-4.10.0.tar.gz", hash = "sha256:31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7", size = 31224, upload-time = "2026-05-28T03:32:53.587Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/75/a6/a0a304dc33b49145b21f4808d763822111e67d1c3a32b524a1baf947b6e1/platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917", size = 21348, upload-time = "2026-04-09T00:04:09.463Z" }, + { url = "https://files.pythonhosted.org/packages/81/e6/cd9575ac904136b3cbf7aa7ee819ef86eedb7274e46f230e94ea4342e729/platformdirs-4.10.0-py3-none-any.whl", hash = "sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a", size = 22743, upload-time = "2026-05-28T03:32:52.175Z" }, ] [[package]] @@ -565,32 +574,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] -[[package]] -name = "pyright" -version = "1.1.409" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "nodeenv" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/51/4e/3aa27f74211522dba7e9cbc3e74de779c6d4b654c54e50a4840623be8014/pyright-1.1.409.tar.gz", hash = "sha256:986ee05beca9e077c165758ad123667c679e050059a2546aa02473930394bc93", size = 4430434, upload-time = "2026-04-23T11:02:03.799Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/16/6b/330d8ebae582b30c2959a1ef4c3bc344ebde48c2ff0c3f113c4710735e11/pyright-1.1.409-py3-none-any.whl", hash = "sha256:aa3ea228cab90c845c7a60d28db7a844c04315356392aa09fafcee98c8c22fb3", size = 6438161, upload-time = "2026-04-23T11:02:01.309Z" }, -] - [[package]] name = "pytest" -version = "8.3.5" +version = "9.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, + { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, + { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, ] [[package]] @@ -632,24 +629,58 @@ wheels = [ [[package]] name = "python-discovery" -version = "1.3.0" +version = "1.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ae/e0/cc5a8653e9a24f6cf84768f05064aa8ed5a83dcefd5e2a043db14a1c5f44/python_discovery-1.3.0.tar.gz", hash = "sha256:d098f1e86be5d45fe4d14bf1029294aabbd332f4321179dec85e76cddce834b0", size = 63925, upload-time = "2026-05-05T14:38:39.769Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/12/38c1a0b1e64806780c9563e3fc9f6e472251839662587cfbe9bfaf2ae10a/python_discovery-1.4.0.tar.gz", hash = "sha256:eb8bc7daad3c226c147e45bb4e970a1feb1bf4048ee178e6db59e197b8010ce3", size = 68455, upload-time = "2026-05-28T01:15:37.639Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl", hash = "sha256:441d9ced3dfce36e113beb35ca302c71c7ef06f3c0f9c227a0b9bb3bd49b9e9f", size = 33124, upload-time = "2026-05-05T14:38:38.539Z" }, + { url = "https://files.pythonhosted.org/packages/c8/8d/3d316429f65029532bb1e28ff77b797d86b5ac3915bb44ca4e19aa283d43/python_discovery-1.4.0-py3-none-any.whl", hash = "sha256:26ed78d703e234879a66244c7d4114563fb13ec5cd30a2d1357e5fb4850782da", size = 33217, upload-time = "2026-05-28T01:15:36.573Z" }, ] [[package]] name = "python-dotenv" -version = "1.0.1" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, +] + +[[package]] +name = "pytokens" +version = "0.4.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115, upload-time = "2024-01-23T06:33:00.505Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/34/b4e015b99031667a7b960f888889c5bd34ef585c85e1cb56a594b92836ac/pytokens-0.4.1.tar.gz", hash = "sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a", size = 23015, upload-time = "2026-01-30T01:03:45.924Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863, upload-time = "2024-01-23T06:32:58.246Z" }, + { url = "https://files.pythonhosted.org/packages/3d/92/790ebe03f07b57e53b10884c329b9a1a308648fc083a6d4a39a10a28c8fc/pytokens-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d70e77c55ae8380c91c0c18dea05951482e263982911fc7410b1ffd1dadd3440", size = 160864, upload-time = "2026-01-30T01:02:57.882Z" }, + { url = "https://files.pythonhosted.org/packages/13/25/a4f555281d975bfdd1eba731450e2fe3a95870274da73fb12c40aeae7625/pytokens-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a58d057208cb9075c144950d789511220b07636dd2e4708d5645d24de666bdc", size = 248565, upload-time = "2026-01-30T01:02:59.912Z" }, + { url = "https://files.pythonhosted.org/packages/17/50/bc0394b4ad5b1601be22fa43652173d47e4c9efbf0044c62e9a59b747c56/pytokens-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b49750419d300e2b5a3813cf229d4e5a4c728dae470bcc89867a9ad6f25a722d", size = 260824, upload-time = "2026-01-30T01:03:01.471Z" }, + { url = "https://files.pythonhosted.org/packages/4e/54/3e04f9d92a4be4fc6c80016bc396b923d2a6933ae94b5f557c939c460ee0/pytokens-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9907d61f15bf7261d7e775bd5d7ee4d2930e04424bab1972591918497623a16", size = 264075, upload-time = "2026-01-30T01:03:04.143Z" }, + { url = "https://files.pythonhosted.org/packages/d1/1b/44b0326cb5470a4375f37988aea5d61b5cc52407143303015ebee94abfd6/pytokens-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:ee44d0f85b803321710f9239f335aafe16553b39106384cef8e6de40cb4ef2f6", size = 103323, upload-time = "2026-01-30T01:03:05.412Z" }, + { url = "https://files.pythonhosted.org/packages/41/5d/e44573011401fb82e9d51e97f1290ceb377800fb4eed650b96f4753b499c/pytokens-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:140709331e846b728475786df8aeb27d24f48cbcf7bcd449f8de75cae7a45083", size = 160663, upload-time = "2026-01-30T01:03:06.473Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e6/5bbc3019f8e6f21d09c41f8b8654536117e5e211a85d89212d59cbdab381/pytokens-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d6c4268598f762bc8e91f5dbf2ab2f61f7b95bdc07953b602db879b3c8c18e1", size = 255626, upload-time = "2026-01-30T01:03:08.177Z" }, + { url = "https://files.pythonhosted.org/packages/bf/3c/2d5297d82286f6f3d92770289fd439956b201c0a4fc7e72efb9b2293758e/pytokens-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24afde1f53d95348b5a0eb19488661147285ca4dd7ed752bbc3e1c6242a304d1", size = 269779, upload-time = "2026-01-30T01:03:09.756Z" }, + { url = "https://files.pythonhosted.org/packages/20/01/7436e9ad693cebda0551203e0bf28f7669976c60ad07d6402098208476de/pytokens-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ad948d085ed6c16413eb5fec6b3e02fa00dc29a2534f088d3302c47eb59adf9", size = 268076, upload-time = "2026-01-30T01:03:10.957Z" }, + { url = "https://files.pythonhosted.org/packages/2e/df/533c82a3c752ba13ae7ef238b7f8cdd272cf1475f03c63ac6cf3fcfb00b6/pytokens-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:3f901fe783e06e48e8cbdc82d631fca8f118333798193e026a50ce1b3757ea68", size = 103552, upload-time = "2026-01-30T01:03:12.066Z" }, + { url = "https://files.pythonhosted.org/packages/cb/dc/08b1a080372afda3cceb4f3c0a7ba2bde9d6a5241f1edb02a22a019ee147/pytokens-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8bdb9d0ce90cbf99c525e75a2fa415144fd570a1ba987380190e8b786bc6ef9b", size = 160720, upload-time = "2026-01-30T01:03:13.843Z" }, + { url = "https://files.pythonhosted.org/packages/64/0c/41ea22205da480837a700e395507e6a24425151dfb7ead73343d6e2d7ffe/pytokens-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5502408cab1cb18e128570f8d598981c68a50d0cbd7c61312a90507cd3a1276f", size = 254204, upload-time = "2026-01-30T01:03:14.886Z" }, + { url = "https://files.pythonhosted.org/packages/e0/d2/afe5c7f8607018beb99971489dbb846508f1b8f351fcefc225fcf4b2adc0/pytokens-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29d1d8fb1030af4d231789959f21821ab6325e463f0503a61d204343c9b355d1", size = 268423, upload-time = "2026-01-30T01:03:15.936Z" }, + { url = "https://files.pythonhosted.org/packages/68/d4/00ffdbd370410c04e9591da9220a68dc1693ef7499173eb3e30d06e05ed1/pytokens-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b08dd6b86058b6dc07efe9e98414f5102974716232d10f32ff39701e841c4", size = 266859, upload-time = "2026-01-30T01:03:17.458Z" }, + { url = "https://files.pythonhosted.org/packages/a7/c9/c3161313b4ca0c601eeefabd3d3b576edaa9afdefd32da97210700e47652/pytokens-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:9bd7d7f544d362576be74f9d5901a22f317efc20046efe2034dced238cbbfe78", size = 103520, upload-time = "2026-01-30T01:03:18.652Z" }, + { url = "https://files.pythonhosted.org/packages/8f/a7/b470f672e6fc5fee0a01d9e75005a0e617e162381974213a945fcd274843/pytokens-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4a14d5f5fc78ce85e426aa159489e2d5961acf0e47575e08f35584009178e321", size = 160821, upload-time = "2026-01-30T01:03:19.684Z" }, + { url = "https://files.pythonhosted.org/packages/80/98/e83a36fe8d170c911f864bfded690d2542bfcfacb9c649d11a9e6eb9dc41/pytokens-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f50fd18543be72da51dd505e2ed20d2228c74e0464e4262e4899797803d7fa", size = 254263, upload-time = "2026-01-30T01:03:20.834Z" }, + { url = "https://files.pythonhosted.org/packages/0f/95/70d7041273890f9f97a24234c00b746e8da86df462620194cef1d411ddeb/pytokens-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc74c035f9bfca0255c1af77ddd2d6ae8419012805453e4b0e7513e17904545d", size = 268071, upload-time = "2026-01-30T01:03:21.888Z" }, + { url = "https://files.pythonhosted.org/packages/da/79/76e6d09ae19c99404656d7db9c35dfd20f2086f3eb6ecb496b5b31163bad/pytokens-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f66a6bbe741bd431f6d741e617e0f39ec7257ca1f89089593479347cc4d13324", size = 271716, upload-time = "2026-01-30T01:03:23.633Z" }, + { url = "https://files.pythonhosted.org/packages/79/37/482e55fa1602e0a7ff012661d8c946bafdc05e480ea5a32f4f7e336d4aa9/pytokens-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:b35d7e5ad269804f6697727702da3c517bb8a5228afa450ab0fa787732055fc9", size = 104539, upload-time = "2026-01-30T01:03:24.788Z" }, + { url = "https://files.pythonhosted.org/packages/30/e8/20e7db907c23f3d63b0be3b8a4fd1927f6da2395f5bcc7f72242bb963dfe/pytokens-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8fcb9ba3709ff77e77f1c7022ff11d13553f3c30299a9fe246a166903e9091eb", size = 168474, upload-time = "2026-01-30T01:03:26.428Z" }, + { url = "https://files.pythonhosted.org/packages/d6/81/88a95ee9fafdd8f5f3452107748fd04c24930d500b9aba9738f3ade642cc/pytokens-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79fc6b8699564e1f9b521582c35435f1bd32dd06822322ec44afdeba666d8cb3", size = 290473, upload-time = "2026-01-30T01:03:27.415Z" }, + { url = "https://files.pythonhosted.org/packages/cf/35/3aa899645e29b6375b4aed9f8d21df219e7c958c4c186b465e42ee0a06bf/pytokens-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d31b97b3de0f61571a124a00ffe9a81fb9939146c122c11060725bd5aea79975", size = 303485, upload-time = "2026-01-30T01:03:28.558Z" }, + { url = "https://files.pythonhosted.org/packages/52/a0/07907b6ff512674d9b201859f7d212298c44933633c946703a20c25e9d81/pytokens-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:967cf6e3fd4adf7de8fc73cd3043754ae79c36475c1c11d514fc72cf5490094a", size = 306698, upload-time = "2026-01-30T01:03:29.653Z" }, + { url = "https://files.pythonhosted.org/packages/39/2a/cbbf9250020a4a8dd53ba83a46c097b69e5eb49dd14e708f496f548c6612/pytokens-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:584c80c24b078eec1e227079d56dc22ff755e0ba8654d8383b2c549107528918", size = 116287, upload-time = "2026-01-30T01:03:30.912Z" }, + { url = "https://files.pythonhosted.org/packages/c6/78/397db326746f0a342855b81216ae1f0a32965deccfd7c830a2dbc66d2483/pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de", size = 13729, upload-time = "2026-01-30T01:03:45.029Z" }, ] [[package]] @@ -721,19 +752,19 @@ wheels = [ [[package]] name = "redis" -version = "4.6.0" +version = "8.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "async-timeout", marker = "python_full_version <= '3.11.2'" }, + { name = "async-timeout", marker = "python_full_version < '3.11.3'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/73/88/63d802c2b18dd9eaa5b846cbf18917c6b2882f20efda398cc16a7500b02c/redis-4.6.0.tar.gz", hash = "sha256:585dc516b9eb042a619ef0a39c3d7d55fe81bdb4df09a52c9cdde0d07bf1aa7d", size = 4561721, upload-time = "2023-06-25T13:13:57.139Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/ae/ed461cca5780b5fc8b9fe8ca0ed98d89508645fb9d880c24cc42c087678f/redis-8.0.0.tar.gz", hash = "sha256:a00c5355432051ac14e593b8b197fc76c887ee12d55a0984f69328a1115fdc49", size = 5101591, upload-time = "2026-05-28T12:45:13.5Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/2e/409703d645363352a20c944f5d119bdae3eb3034051a53724a7c5fee12b8/redis-4.6.0-py3-none-any.whl", hash = "sha256:e2b03db868160ee4591de3cb90d40ebb50a90dd302138775937f6a42b7ed183c", size = 241149, upload-time = "2023-06-25T13:13:54.563Z" }, + { url = "https://files.pythonhosted.org/packages/27/e3/b519734372d305bd547534a9f32e4ce9f98552af753dce72cf3483a0ff0b/redis-8.0.0-py3-none-any.whl", hash = "sha256:c938c18338585009f0bc310f4c7e4e4b4d37639356c4ac072cedf3af570c8dc7", size = 499870, upload-time = "2026-05-28T12:45:11.697Z" }, ] [[package]] name = "requests" -version = "2.32.5" +version = "2.33.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -741,9 +772,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/a4/98b9c7c6428a668bf7e42ebb7c79d576a1c3c1e3ae2d47e674b468388871/requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517", size = 134120, upload-time = "2026-03-30T16:09:15.531Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, + { url = "https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a", size = 64947, upload-time = "2026-03-30T16:09:13.83Z" }, ] [[package]] @@ -761,27 +792,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.12" +version = "0.15.16" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/99/43/3291f1cc9106f4c63bdce7a8d0df5047fe8422a75b091c16b5e9355e0b11/ruff-0.15.12.tar.gz", hash = "sha256:ecea26adb26b4232c0c2ca19ccbc0083a68344180bba2a600605538ce51a40a6", size = 4643852, upload-time = "2026-04-24T18:17:14.305Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/bd/5f7ec371001337d8fa61701c186ff8b613ecac1651848c5950f4c4d5f2e9/ruff-0.15.16.tar.gz", hash = "sha256:d05e78d38c78caf020b03789e25106c93017db5a0cb6e2819885018c61343b78", size = 4714267, upload-time = "2026-06-04T16:33:09.974Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/6e/e78ffb61d4686f3d96ba3df2c801161843746dcbcbb17a1e927d4829312b/ruff-0.15.12-py3-none-linux_armv6l.whl", hash = "sha256:f86f176e188e94d6bdbc09f09bfd9dc729059ad93d0e7390b5a73efe19f8861c", size = 10640713, upload-time = "2026-04-24T18:17:22.841Z" }, - { url = "https://files.pythonhosted.org/packages/ae/08/a317bc231fb9e7b93e4ef3089501e51922ff88d6936ce5cf870c4fe55419/ruff-0.15.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e3bcd123364c3770b8e1b7baaf343cc99a35f197c5c6e8af79015c666c423a6c", size = 11069267, upload-time = "2026-04-24T18:17:30.105Z" }, - { url = "https://files.pythonhosted.org/packages/aa/a4/f828e9718d3dce1f5f11c39c4f65afd32783c8b2aebb2e3d259e492c47bd/ruff-0.15.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fe87510d000220aa1ed530d4448a7c696a0cae1213e5ec30e5874287b66557b5", size = 10397182, upload-time = "2026-04-24T18:17:07.177Z" }, - { url = "https://files.pythonhosted.org/packages/71/e0/3310fc6d1b5e1fdea22bf3b1b807c7e187b581021b0d7d4514cccdb5fb71/ruff-0.15.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84a1630093121375a3e2a95b4a6dc7b59e2b4ee76216e32d81aae550a832d002", size = 10758012, upload-time = "2026-04-24T18:16:55.759Z" }, - { url = "https://files.pythonhosted.org/packages/11/c1/a606911aee04c324ddaa883ae418f3569792fd3c4a10c50e0dd0a2311e1e/ruff-0.15.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fb129f40f114f089ebe0ca56c0d251cf2061b17651d464bb6478dc01e69f11f5", size = 10447479, upload-time = "2026-04-24T18:16:51.677Z" }, - { url = "https://files.pythonhosted.org/packages/9d/68/4201e8444f0894f21ab4aeeaee68aa4f10b51613514a20d80bd628d57e88/ruff-0.15.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0c862b172d695db7598426b8af465e7e9ac00a3ea2a3630ee67eb82e366aaa6", size = 11234040, upload-time = "2026-04-24T18:17:16.529Z" }, - { url = "https://files.pythonhosted.org/packages/34/ff/8a6d6cf4ccc23fd67060874e832c18919d1557a0611ebef03fdb01fff11e/ruff-0.15.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2849ea9f3484c3aca43a82f484210370319e7170df4dfe4843395ddf6c57bc33", size = 12087377, upload-time = "2026-04-24T18:17:04.944Z" }, - { url = "https://files.pythonhosted.org/packages/85/f6/c669cf73f5152f623d34e69866a46d5e6185816b19fcd5b6dd8a2d299922/ruff-0.15.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e77c7e51c07fe396826d5969a5b846d9cd4c402535835fb6e21ce8b28fef847", size = 11367784, upload-time = "2026-04-24T18:17:25.409Z" }, - { url = "https://files.pythonhosted.org/packages/e8/39/c61d193b8a1daaa8977f7dea9e8d8ba866e02ea7b65d32f6861693aa4c12/ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b2f4f2f3b1026b5fb449b467d9264bf22067b600f7b6f41fc5958909f449d0", size = 11344088, upload-time = "2026-04-24T18:17:12.258Z" }, - { url = "https://files.pythonhosted.org/packages/c2/8d/49afab3645e31e12c590acb6d3b5b69d7aab5b81926dbaf7461f9441f37a/ruff-0.15.12-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9ba3b8f1afd7e2e43d8943e55f249e13f9682fde09711644a6e7290eb4f3e339", size = 11271770, upload-time = "2026-04-24T18:17:02.457Z" }, - { url = "https://files.pythonhosted.org/packages/46/06/33f41fe94403e2b755481cdfb9b7ef3e4e0ed031c4581124658d935d52b4/ruff-0.15.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e852ba9fdc890655e1d78f2df1499efbe0e54126bd405362154a75e2bde159c5", size = 10719355, upload-time = "2026-04-24T18:17:27.648Z" }, - { url = "https://files.pythonhosted.org/packages/0d/59/18aa4e014debbf559670e4048e39260a85c7fcee84acfd761ac01e7b8d35/ruff-0.15.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dd8aed930da53780d22fc70bdf84452c843cf64f8cb4eb38984319c24c5cd5fd", size = 10462758, upload-time = "2026-04-24T18:17:32.347Z" }, - { url = "https://files.pythonhosted.org/packages/25/e7/cc9f16fd0f3b5fddcbd7ec3d6ae30c8f3fde1047f32a4093a98d633c6570/ruff-0.15.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:01da3988d225628b709493d7dc67c3b9b12c0210016b08690ef9bd27970b262b", size = 10953498, upload-time = "2026-04-24T18:17:20.674Z" }, - { url = "https://files.pythonhosted.org/packages/72/7a/a9ba7f98c7a575978698f4230c5e8cc54bbc761af34f560818f933dafa0c/ruff-0.15.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9cae0f92bd5700d1213188b31cd3bdd2b315361296d10b96b8e2337d3d11f53e", size = 11447765, upload-time = "2026-04-24T18:17:09.755Z" }, - { url = "https://files.pythonhosted.org/packages/ea/f9/0ae446942c846b8266059ad8a30702a35afae55f5cdc54c5adf8d7afdc27/ruff-0.15.12-py3-none-win32.whl", hash = "sha256:d0185894e038d7043ba8fd6aee7499ece6462dc0ea9f1e260c7451807c714c20", size = 10657277, upload-time = "2026-04-24T18:17:18.591Z" }, - { url = "https://files.pythonhosted.org/packages/33/f1/9614e03e1cdcbf9437570b5400ced8a720b5db22b28d8e0f1bda429f660d/ruff-0.15.12-py3-none-win_amd64.whl", hash = "sha256:c87a162d61ab3adca47c03f7f717c68672edec7d1b5499e652331780fe74950d", size = 11837758, upload-time = "2026-04-24T18:17:00.113Z" }, - { url = "https://files.pythonhosted.org/packages/c0/98/6beb4b351e472e5f4c4613f7c35a5290b8be2497e183825310c4c3a3984b/ruff-0.15.12-py3-none-win_arm64.whl", hash = "sha256:a538f7a82d061cee7be55542aca1d86d1393d55d81d4fcc314370f4340930d4f", size = 11120821, upload-time = "2026-04-24T18:16:57.979Z" }, + { url = "https://files.pythonhosted.org/packages/0c/42/53ef1c3953f157956db9bf7861e3bc50b9b887ce93300aa48cdba8336fe6/ruff-0.15.16-py3-none-linux_armv6l.whl", hash = "sha256:6ac3c0b3969cc6cf6b158c4e2f8f682acb58e7d700d8a44b65ecdc72d66ab0b2", size = 10709025, upload-time = "2026-06-04T16:32:51.935Z" }, + { url = "https://files.pythonhosted.org/packages/93/9a/a79159346f19134a956607754e57d8d128f7a4c00f4ad2f7514d224c172c/ruff-0.15.16-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:197c207ed75ffba54a0dec23db4aa939a27a3053073e085e0042433cbdc58e4a", size = 11063550, upload-time = "2026-06-04T16:32:42.24Z" }, + { url = "https://files.pythonhosted.org/packages/bc/72/3ce2ac000a5299ec238e01f51397b3b653c93b077d9b1bfe8715bb895f20/ruff-0.15.16-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3a39fec45ab316cc23e7558f23fea4a70403ddb5648ea9a4a3854a16973d0071", size = 10421345, upload-time = "2026-06-04T16:32:37.251Z" }, + { url = "https://files.pythonhosted.org/packages/b0/c2/cc7fad3ec9169373f5b6a18f1917b91080feec40c3f9658334a1d28e2f03/ruff-0.15.16-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba93191d79003116b95128c9d306e045200fdbd0bccb782b110f3cd1d4abc5cf", size = 10757217, upload-time = "2026-06-04T16:32:54.722Z" }, + { url = "https://files.pythonhosted.org/packages/69/d2/3474009eaa0a65b31fa7152a2fad5e2f050c640ceb1e6b02ee6922e94c82/ruff-0.15.16-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c6ee4b90520630120ef032aa5cc10db483852dff950e78b1d717e2993a61ac8d", size = 10507035, upload-time = "2026-06-04T16:33:05.343Z" }, + { url = "https://files.pythonhosted.org/packages/ca/81/b7ae6ccbd11f0c8dc3d5d67fc4be9b57ff57ca86ba56152021378e1277f2/ruff-0.15.16-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e4215bc938bc3c8215c1472c1aa437e310fee20cd427335fec9d7e609563628", size = 11255291, upload-time = "2026-06-04T16:32:49.49Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e1/46e526f1a7cc90857ce6ddf25fbb77eb6568651ac38d71b033af07076dd5/ruff-0.15.16-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c8d26be963b090f10e29abc8b3e74a2a321f6fa34e02424e30b5af89350ecbb", size = 12124922, upload-time = "2026-06-04T16:33:07.821Z" }, + { url = "https://files.pythonhosted.org/packages/1a/da/5c791b088b596b24d0deb967fa28ae02ad751a140c0b9ea81c5ab915d6c0/ruff-0.15.16-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f198cf4123602a2280ed46c307bcbafe41758d6fee5b456b6b6058ca1514b3b4", size = 11332186, upload-time = "2026-06-04T16:33:02.971Z" }, + { url = "https://files.pythonhosted.org/packages/72/11/5da87abe20047c8962361473923ebb2f62b595250126aadfad8c20649c1e/ruff-0.15.16-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb27515fa6240fb586ae82b901a59e67d24acff86f2190b433dc542fe0435aeb", size = 11373541, upload-time = "2026-06-04T16:32:47.007Z" }, + { url = "https://files.pythonhosted.org/packages/fe/2a/8554754c23a854ae3fd6b507e36ad61ddb121e298c6d5d617dec94ed0f14/ruff-0.15.16-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a267c46ba1593fc26b8eecbea050b39d40c0b6bb7781ee11c90a02cd10032951", size = 11353014, upload-time = "2026-06-04T16:32:34.795Z" }, + { url = "https://files.pythonhosted.org/packages/62/25/62ea41529ec89f742ea3fed9cb1059c72877ec7cf9b9e99ac9cf3294d1d9/ruff-0.15.16-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:528c68f39a91498a8d50e91ff5985df3d105782bab49cc378e73ac26bff083e8", size = 10737467, upload-time = "2026-06-04T16:32:26.348Z" }, + { url = "https://files.pythonhosted.org/packages/90/17/334d3ad9de4d40f9dd58fdd09e35ce64553bb501e2f19a839e2fb6be14fc/ruff-0.15.16-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7ed55c58950df60589a9a7a5d2f8fa5f54ebd287163be805adfe6ee95a9de123", size = 10521910, upload-time = "2026-06-04T16:32:32.54Z" }, + { url = "https://files.pythonhosted.org/packages/4d/bd/3ac7c6ae77a885c1004b3dda2446ea401768d24f851c14b4ad4b24f6639c/ruff-0.15.16-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d482feaf51512b50f9790ceb417a56a61dd1e9d9bf967662b9ed27c01b34f53a", size = 10979190, upload-time = "2026-06-04T16:32:57.492Z" }, + { url = "https://files.pythonhosted.org/packages/33/d7/609546e6a413c3f216fbf2a50c928f97c80939154f6a0503114094a86191/ruff-0.15.16-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1e15bc8c94513dae2a40cc9ef07c94fdd4ecc9e29dabebeebe170f952322c9e3", size = 11477014, upload-time = "2026-06-04T16:32:44.687Z" }, + { url = "https://files.pythonhosted.org/packages/74/0d/f2cd247ad32633a5c36e97141a2c21b11c6279f7957bc2ff360b1e08fddd/ruff-0.15.16-py3-none-win32.whl", hash = "sha256:580378f7bd4aa25f72e74aa54948a9622f142b1e509521dd10902e886681cc1e", size = 10735541, upload-time = "2026-06-04T16:32:30.145Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9e/02e845ef151b1dee585e55c4739f8e1734ae1d9f1221dff65761c162208b/ruff-0.15.16-py3-none-win_amd64.whl", hash = "sha256:408256017284eddf98fff77b29aa4fb30f586042d535b2d9befc6512f400aaec", size = 11843403, upload-time = "2026-06-04T16:32:39.76Z" }, + { url = "https://files.pythonhosted.org/packages/15/19/016553f86f207450aebebc2b2b5088d086b901cc8186c02ac4284db3bd88/ruff-0.15.16-py3-none-win_arm64.whl", hash = "sha256:8cd61783afb39638a7133ef0d2dfb1e91277593962f81b5a8423eb0b888a6121", size = 11134555, upload-time = "2026-06-04T16:33:00.136Z" }, ] [[package]] @@ -822,11 +853,11 @@ wheels = [ [[package]] name = "stevedore" -version = "5.7.0" +version = "5.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/6d/90764092216fa560f6587f83bb70113a8ba510ba436c6476a2b47359057c/stevedore-5.7.0.tar.gz", hash = "sha256:31dd6fe6b3cbe921e21dcefabc9a5f1cf848cf538a1f27543721b8ca09948aa3", size = 516200, upload-time = "2026-02-20T13:27:06.765Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e9/88/35e4d27d9177d7df76d060e0a18f69c6c5794c96960c94042e20a12c8ba2/stevedore-5.8.0.tar.gz", hash = "sha256:b49867b32ca3016e94100e68dbf26e72aa7b8708d0a3f73c08aeb220370ac715", size = 514710, upload-time = "2026-05-18T09:15:27.731Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/06/36d260a695f383345ab5bbc3fd447249594ae2fa8dfd19c533d5ae23f46b/stevedore-5.7.0-py3-none-any.whl", hash = "sha256:fd25efbb32f1abb4c9e502f385f0018632baac11f9ee5d1b70f88cc5e22ad4ed", size = 54483, upload-time = "2026-02-20T13:27:05.561Z" }, + { url = "https://files.pythonhosted.org/packages/f5/ac/19f9941c74add59d17694930ec8105d5eddeee4ce56dd8632b765ca16d6c/stevedore-5.8.0-py3-none-any.whl", hash = "sha256:88eede9e66ca80e34085b9174e2327da2c61ac91f24f70e41c3ad76e4bb4872b", size = 54553, upload-time = "2026-05-18T09:15:25.82Z" }, ] [[package]] @@ -859,9 +890,9 @@ dependencies = [ dev = [ { name = "bandit" }, { name = "pre-commit" }, - { name = "pyright" }, { name = "pyupgrade" }, { name = "ruff" }, + { name = "ty" }, ] test = [ { name = "pytest" }, @@ -872,9 +903,9 @@ test = [ [package.metadata] requires-dist = [ { name = "arrow", specifier = "~=1.3.0" }, - { name = "black", specifier = "~=25.1.0" }, + { name = "black", specifier = "~=26.3.1" }, { name = "debugpy", specifier = "~=1.8.12" }, - { name = "fakeredis", specifier = "~=2.27.0" }, + { name = "fakeredis", specifier = "~=2.36.0" }, { name = "inflection", specifier = "~=0.5.1" }, { name = "isort", specifier = "~=6.0.0" }, { name = "jmespath", specifier = "~=1.0.1" }, @@ -882,10 +913,10 @@ requires-dist = [ { name = "pyaes", specifier = "~=1.6.1" }, { name = "pydantic", specifier = "~=1.10.0" }, { name = "python-dateutil", specifier = "~=2.9.0" }, - { name = "python-dotenv", specifier = "~=1.0.1" }, + { name = "python-dotenv", specifier = "~=1.2.2" }, { name = "pyyaml", specifier = "~=6.0.2" }, - { name = "redis", specifier = "~=4.6.0" }, - { name = "requests", specifier = "~=2.32.3" }, + { name = "redis", specifier = "~=8.0.0" }, + { name = "requests", specifier = "~=2.33.0" }, { name = "rich", specifier = "~=13.9.4" }, { name = "semantic-version", specifier = "~=2.10.0" }, { name = "tinydb", specifier = "~=4.8.2" }, @@ -897,12 +928,12 @@ requires-dist = [ dev = [ { name = "bandit", specifier = "~=1.8.3" }, { name = "pre-commit", specifier = "~=4.1.0" }, - { name = "pyright", specifier = "~=1.1.394" }, { name = "pyupgrade", specifier = "~=3.19.1" }, { name = "ruff", specifier = "~=0.15.2" }, + { name = "ty", specifier = "~=0.0.42" }, ] test = [ - { name = "pytest", specifier = "~=8.3.4" }, + { name = "pytest", specifier = "~=9.0.3" }, { name = "pytest-cov", specifier = "~=6.0.0" }, { name = "pytest-ordering", specifier = "~=0.6" }, ] @@ -979,6 +1010,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, ] +[[package]] +name = "ty" +version = "0.0.43" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/37/4ec04de0659b93be37d956dfceca13b1ecab9c959f28d8a1d5e514603f36/ty-0.0.43.tar.gz", hash = "sha256:ea4cff50548f2a1877e848d3abe9e293cde8ab94757a7eb93fc0d4013f98be8e", size = 5798429, upload-time = "2026-06-04T00:52:10.013Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/74/1916026a78f20019a2f03adbd6fb4430ddb7ce1e52c2e17a90856a6d192e/ty-0.0.43-py3-none-linux_armv6l.whl", hash = "sha256:3bf70f5446480562bf6c9f639df4b5cb60716b8f8d1a6b8e5811d5c7eccd8bf2", size = 11598153, upload-time = "2026-06-04T00:52:20.646Z" }, + { url = "https://files.pythonhosted.org/packages/b9/af/58bb0089d2635216c8fa6612dd486a3f986d0ab1c46a41527ab95e57f0e3/ty-0.0.43-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7184741f8b15425a1bc64b950ad005cb353573288ac0e8a04f5481ceb3832596", size = 11357811, upload-time = "2026-06-04T00:52:24.683Z" }, + { url = "https://files.pythonhosted.org/packages/d6/9c/32c6b14f3feddf87b59c7a50709e2b3da408258f2f583f05575f77bc8f7b/ty-0.0.43-py3-none-macosx_11_0_arm64.whl", hash = "sha256:8c306379ca9a35f6ae5270fe9bda7af4b46d91822725a2586d78c8b9b5493b62", size = 10772024, upload-time = "2026-06-04T00:52:14.312Z" }, + { url = "https://files.pythonhosted.org/packages/09/fa/98aa4a74bd00cd5efc424923cd1daffbf1e40a0338041cafb203379d746f/ty-0.0.43-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d624b884c9c1fd244ad2a5f026364e7162a22b3f537025941ada2e363e676414", size = 11291034, upload-time = "2026-06-04T00:52:37.249Z" }, + { url = "https://files.pythonhosted.org/packages/b5/db/4de086c38ce96dcada2bd451f43171d2c237f96d8ed19a1ea8fe51bb8ef4/ty-0.0.43-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:281fc4c00fbc196045141faa085055bddc58846b04a2800204701415a1b9c6aa", size = 11364724, upload-time = "2026-06-04T00:52:33.138Z" }, + { url = "https://files.pythonhosted.org/packages/b0/d3/e3cd8e3233a6fd8362a49aa025b79e9f40151a2a86d811ace154c6eb7445/ty-0.0.43-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f57d6cc28de89024b48d1788e4758c05299d5749d4a51c02e71ac655ec23d9a5", size = 11890555, upload-time = "2026-06-04T00:52:22.711Z" }, + { url = "https://files.pythonhosted.org/packages/80/7b/6f46d444e8241606bbde098df3dca93f2ec0b834a42055db85ee7d33646f/ty-0.0.43-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0a1d6ad6c5e7792c7eac0a01e550f2c2004462e01a64a91ea1636aba6fef6e71", size = 12450968, upload-time = "2026-06-04T00:52:28.94Z" }, + { url = "https://files.pythonhosted.org/packages/4a/e1/79fbe51f2e4b9d8347f2013cd7ed0b63f3b499038c02dc0357e9b28a3a47/ty-0.0.43-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:66d474395d7635fb618bdbb58b4e3360259a2056d0a5621b82754b9da2cd8a04", size = 12064187, upload-time = "2026-06-04T00:52:12.039Z" }, + { url = "https://files.pythonhosted.org/packages/9b/3f/c758a3a8df5b90d331f2b60c8f16021ee64d75e78f99d67cc4efc9bf5f4b/ty-0.0.43-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2663a0003a8b60fb98db7f6f6e673df80b21d0fe3a9868a26fb06b4e049b6fc4", size = 11943208, upload-time = "2026-06-04T00:52:31.14Z" }, + { url = "https://files.pythonhosted.org/packages/54/5f/f516442749cf1b45ca6720a5d41df2738a486ed9ace774c03d515db89084/ty-0.0.43-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:d5a6c352d374d889189d5ec82b54b26a5885f769f7b7787f7f875500dcb8673e", size = 12143572, upload-time = "2026-06-04T00:52:18.457Z" }, + { url = "https://files.pythonhosted.org/packages/b7/bf/0d83c7f43bf4c10f3678bfe7d938e51c445298c7b923f155c5204730c2df/ty-0.0.43-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e7dbbeedfad3ca250d74fcc355fa9ab6b38d2a17f22d6304f615716939dbbb27", size = 11279355, upload-time = "2026-06-04T00:52:26.726Z" }, + { url = "https://files.pythonhosted.org/packages/3e/de/a6c978bef6d9e949f79f4782d9e4ee4df0893713e73b055d84c1a5116b9a/ty-0.0.43-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:24b18a0273ee46154996cfcfa27438f851f440c925587ec200df6f98dffe67d3", size = 11408412, upload-time = "2026-06-04T00:52:35.282Z" }, + { url = "https://files.pythonhosted.org/packages/ec/b1/d13857c23867f0f76b92e38e5841c64ca5e76dc5d4bf27f52cb81d8ab685/ty-0.0.43-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2ef681951520d692b7e9c0b5e56aacf4f98ccae47cf6ffccaf2c7b6b33dc226e", size = 11541709, upload-time = "2026-06-04T00:52:16.451Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f1/cd6afc6f6a687e238bf5e12189f7920e81a0bdef6c3dba4c784ef140f7d9/ty-0.0.43-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:2af105de7437143aa4676b28016b5bee661aaaa4eff52be5867fb25119641ceb", size = 12041266, upload-time = "2026-06-04T00:52:43.541Z" }, + { url = "https://files.pythonhosted.org/packages/bd/ba/51ca7c3335da2b8d0a3e477fa4986be9f4a53b05bfab862967d8d2e6ca60/ty-0.0.43-py3-none-win32.whl", hash = "sha256:e4773115b0d6486ee30f1657fc8bdffe7e3a3f5300ab77ef2495da6e83e4694f", size = 10858724, upload-time = "2026-06-04T00:52:07.843Z" }, + { url = "https://files.pythonhosted.org/packages/9f/29/5d80453e5f7c520145fa058851da87230dbd7ca761a7675447a9fe504e0b/ty-0.0.43-py3-none-win_amd64.whl", hash = "sha256:48d3545094a4ae6395492c7e6ac90550fce969e0ed2815fbf8c5da9756676b7d", size = 11976157, upload-time = "2026-06-04T00:52:41.438Z" }, + { url = "https://files.pythonhosted.org/packages/dc/ed/befe5a543e5b95e754ed38ee95239e44efda9bc5f578db4ac1bc8dd758d6/ty-0.0.43-py3-none-win_arm64.whl", hash = "sha256:740ca33d7f75f655a4e7d475bc42dfb825c13219bb073fad30fcc04d35790c74", size = 11308680, upload-time = "2026-06-04T00:52:39.233Z" }, +] + [[package]] name = "typer" version = "0.21.2" @@ -996,11 +1052,11 @@ wheels = [ [[package]] name = "types-python-dateutil" -version = "2.9.0.20260408" +version = "2.9.0.20260518" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/88/f3/2427775f80cd5e19a0a71ba8e5ab7645a01a852f43a5fd0ffc24f66338e0/types_python_dateutil-2.9.0.20260408.tar.gz", hash = "sha256:8b056ec01568674235f64ecbcef928972a5fac412f5aab09c516dfa2acfbb582", size = 16981, upload-time = "2026-04-08T04:28:10.995Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8d/e8/c01bdf0d7c3659428c091fbd693177093639565bcbc86bc20098e6d37cc6/types_python_dateutil-2.9.0.20260518.tar.gz", hash = "sha256:51f02dc03b61c7f6a07df45797d4dfe8a1aa47f0b7db9ad89f6fd3a1a70e1b51", size = 17082, upload-time = "2026-05-18T06:05:24.508Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/c6/eeba37bfee282a6a97f889faef9352d6172c6a5088eb9a4daf570d9d748d/types_python_dateutil-2.9.0.20260408-py3-none-any.whl", hash = "sha256:473139d514a71c9d1fbd8bb328974bedcb1cc3dba57aad04ffa4157f483c216f", size = 18437, upload-time = "2026-04-08T04:28:10.095Z" }, + { url = "https://files.pythonhosted.org/packages/36/22/169273273ca34e9ab0ae2f387ba72ed7e09faaaf834da01d6b89c2bea71a/types_python_dateutil-2.9.0.20260518-py3-none-any.whl", hash = "sha256:d6a9c5bd0de61460c8fdef8ab2b400f956a1a1075cce08d4e2b4434e478c50b8", size = 18431, upload-time = "2026-05-18T06:05:23.641Z" }, ] [[package]] @@ -1014,16 +1070,16 @@ wheels = [ [[package]] name = "urllib3" -version = "2.6.3" +version = "2.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, ] [[package]] name = "virtualenv" -version = "21.3.1" +version = "21.4.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, @@ -1031,9 +1087,9 @@ dependencies = [ { name = "platformdirs" }, { name = "python-discovery" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ec/0d/915c02c94d207b85580eb09bffab54438a709e7288524094fe781da526c2/virtualenv-21.3.1.tar.gz", hash = "sha256:c2305bc1fddeec40699b8370d13f8d431b0701f00ce895061ce493aeded4426b", size = 7613791, upload-time = "2026-05-05T01:34:31.402Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/0d/4e93c8e6d1001a75763f87d8f5ecda8ebc7f4aa2153dddfaf4ae8892821a/virtualenv-21.4.2.tar.gz", hash = "sha256:38e6ee0a555615c0ea9da2ac7e9998fe8dc3b911dd33ad8eaad2020957653b0c", size = 7613326, upload-time = "2026-05-31T17:01:22.827Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-py3-none-any.whl", hash = "sha256:d1a71cf58f2f9228fff23a1f6ec15d39785c6b32e03658d104974247145edd35", size = 7594539, upload-time = "2026-05-05T01:34:28.98Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c4/557dc082be035381b85fdb2b74e21d3d21b57750b74f2b47a32f3a639ff9/virtualenv-21.4.2-py3-none-any.whl", hash = "sha256:854210ca524a1a4d0d744734f4acbc721c3ffe163b85bbf5d56d14d5ae2f0fae", size = 7594079, upload-time = "2026-05-31T17:01:20.735Z" }, ] [[package]] From bec69e7c6e1f50ebaccd4137a4b65f884911a51a Mon Sep 17 00:00:00 2001 From: Bracey Summers Date: Thu, 4 Jun 2026 18:07:13 -0500 Subject: [PATCH 2/8] bumped version --- pyproject.toml | 2 +- tcex_cli/__metadata__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0aef01e..f78cce3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -111,7 +111,7 @@ commit = false # Additional arguments to pass to the git commit command commit_args = "--no-verify" # The current version of the project -current_version = "1.0.6-dev1" +current_version = "1.0.6-dev2" # The commit message template used when committing version bumps message = "Version updated from {current_version} to {new_version}" # Regular expression for parsing version strings diff --git a/tcex_cli/__metadata__.py b/tcex_cli/__metadata__.py index f48baca..df28a2f 100644 --- a/tcex_cli/__metadata__.py +++ b/tcex_cli/__metadata__.py @@ -1,4 +1,4 @@ """TcEx Framework metadata.""" __license__ = 'Apache-2.0' -__version__ = '1.0.6-dev1' +__version__ = '1.0.6-dev2' From eea4dde71d26ccc6a36773a0ebdfebf9be6461a2 Mon Sep 17 00:00:00 2001 From: Bracey Summers Date: Fri, 5 Jun 2026 12:25:29 -0500 Subject: [PATCH 3/8] bump dependencies; harden git deny rules; sync submodules Build - Loosen and bump runtime pins in pyproject.toml: arrow 1.4, black 26.5.1, isort 8.0.1, jmespath 1.1, pydantic 1.10.26, pyyaml 6.0.3, requests 2.34.2, rich 15.0.0, typer >=0.26.7, wrapt 2.2.1; switch fakeredis to >= floor - Bump dev/test tool floors: bandit 1.9.4, pre-commit 4.6.0, pyupgrade 3.21.2, ruff 0.15.16, ty 0.0.43, pytest-cov 7.1.0 - Regenerate uv.lock to match the new constraints Config - Drop the [tool.ty.rules] override block (invalid-attribute-override and invalid-method-override no longer ignored) - Expand the Bash git deny list in .claude/settings.json to block commit, push, pull, merge, rebase, reset, restore, revert, stash, cherry-pick, branch/tag mutation, worktree add/remove/prune, submodule update/deinit, and other history-rewriting commands Submodules - Bump app/config, pleb, requests_tc, and util pointers to their "linting and general cleanup" commits --- .claude/settings.json | 35 ++++- pyproject.toml | 46 +++---- tcex_cli/app/config | 2 +- tcex_cli/pleb | 2 +- tcex_cli/requests_tc | 2 +- tcex_cli/util | 2 +- uv.lock | 289 ++++++++++++++++++++++-------------------- 7 files changed, 211 insertions(+), 167 deletions(-) diff --git a/.claude/settings.json b/.claude/settings.json index 7ea0cce..3165431 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -85,9 +85,42 @@ ], "ask": [], "deny": [ + "Bash(git am:*)", + "Bash(git apply:*)", + "Bash(git branch -d:*)", + "Bash(git branch -D:*)", + "Bash(git branch -m:*)", + "Bash(git branch -M:*)", "Bash(git checkout:*)", + "Bash(git cherry-pick:*)", + "Bash(git clean:*)", + "Bash(git commit:*)", + "Bash(git filter-branch:*)", + "Bash(git filter-repo:*)", + "Bash(git gc:*)", + "Bash(git merge:*)", + "Bash(git mv:*)", + "Bash(git prune:*)", + "Bash(git pull:*)", + "Bash(git push:*)", + "Bash(git rebase:*)", + "Bash(git reflog expire:*)", + "Bash(git reflog delete:*)", + "Bash(git reset:*)", + "Bash(git restore:*)", + "Bash(git revert:*)", + "Bash(git rm:*)", + "Bash(git stash:*)", + "Bash(git submodule deinit:*)", + "Bash(git submodule update:*)", "Bash(git switch:*)", - "Bash(git worktree add:*)" + "Bash(git symbolic-ref:*)", + "Bash(git tag -d:*)", + "Bash(git tag -D:*)", + "Bash(git update-ref:*)", + "Bash(git worktree add:*)", + "Bash(git worktree remove:*)", + "Bash(git worktree prune:*)" ] } } diff --git a/pyproject.toml b/pyproject.toml index f78cce3..0bf2ed8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,26 +24,26 @@ classifiers = [ # List of runtime dependencies dependencies = [ - "arrow~=1.3.0", - "black~=26.3.1", + "arrow~=1.4.0", + "black~=26.5.1", "debugpy~=1.8.12", - "fakeredis~=2.36.0", + "fakeredis>=2.36.0", "inflection~=0.5.1", - "isort~=6.0.0", - "jmespath~=1.0.1", + "isort~=8.0.1", + "jmespath~=1.1.0", "paho-mqtt~=2.1.0", "pyaes~=1.6.1", - "pydantic~=1.10.0", - "python-dateutil~=2.9.0", + "pydantic~=1.10.26", + "python-dateutil~=2.9.0.post0", "python-dotenv~=1.2.2", - "pyyaml~=6.0.2", + "pyyaml~=6.0.3", "redis~=8.0.0", - "requests~=2.33.0", - "rich~=13.9.4", + "requests~=2.34.2", + "rich~=15.0.0", "semantic-version~=2.10.0", "tinydb~=4.8.2", - "typer~=0.21.1", - "wrapt~=1.17.2", + "typer>=0.26.7", + "wrapt~=2.2.1", ] # Short description of the project @@ -77,16 +77,16 @@ Source = "https://github.com/ThreatConnect-Inc/tcex-cli" # Dependency groups for development and testing [dependency-groups] dev = [ - "bandit~=1.8.3", - "pre-commit~=4.1.0", - "pyupgrade~=3.19.1", - "ruff~=0.15.2", - "ty~=0.0.42", + "bandit>=1.9.4", + "pre-commit>=4.6.0", + "pyupgrade>=3.21.2", + "ruff>=0.15.16", + "ty>=0.0.43", ] test = [ - "pytest~=9.0.3", - "pytest-cov~=6.0.0", - "pytest-ordering~=0.6", + "pytest>=9.0.3", + "pytest-cov>=7.1.0", + "pytest-ordering>=0.6", ] # Dynamic configuration for setuptools @@ -178,12 +178,6 @@ exclude = [ "tests", ] -[tool.ty.rules] -# Override checks intentionally disabled (carried over from the prior pyright -# config: reportIncompatibleMethodOverride / reportIncompatibleVariableOverride). -invalid-attribute-override = "ignore" -invalid-method-override = "ignore" - # Configuration for pytest test runner [tool.pytest.ini_options] filterwarnings = [] diff --git a/tcex_cli/app/config b/tcex_cli/app/config index 4738659..b47539d 160000 --- a/tcex_cli/app/config +++ b/tcex_cli/app/config @@ -1 +1 @@ -Subproject commit 473865978e4f8565b7e2fcacfbcea8d68a80e562 +Subproject commit b47539de3ac6bd2d9273dc92d6948e1f6f59f245 diff --git a/tcex_cli/pleb b/tcex_cli/pleb index 51855d2..9421968 160000 --- a/tcex_cli/pleb +++ b/tcex_cli/pleb @@ -1 +1 @@ -Subproject commit 51855d2fec63c839e22684686db3de8be971235e +Subproject commit 942196876776a004c43ae8f6d1efc5193727df87 diff --git a/tcex_cli/requests_tc b/tcex_cli/requests_tc index 68ef1dd..b3f4fd9 160000 --- a/tcex_cli/requests_tc +++ b/tcex_cli/requests_tc @@ -1 +1 @@ -Subproject commit 68ef1ddecfcef1b7e44a6cdd505cc690aa566d2a +Subproject commit b3f4fd933c93fe13774cf317379d38d9d119cafb diff --git a/tcex_cli/util b/tcex_cli/util index 7c51bec..0a7729b 160000 --- a/tcex_cli/util +++ b/tcex_cli/util @@ -1 +1 @@ -Subproject commit 7c51bec290812f116d7f9475d2f4fbaf2a6e6388 +Subproject commit 0a7729be52cb573b1629103ea6c1767e9b0cd479 diff --git a/uv.lock b/uv.lock index 945a1a0..0cb82dd 100644 --- a/uv.lock +++ b/uv.lock @@ -13,15 +13,15 @@ wheels = [ [[package]] name = "arrow" -version = "1.3.0" +version = "1.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "python-dateutil" }, - { name = "types-python-dateutil" }, + { name = "tzdata" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/00/0f6e8fcdb23ea632c866620cc872729ff43ed91d284c866b515c6342b173/arrow-1.3.0.tar.gz", hash = "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85", size = 131960, upload-time = "2023-09-30T22:11:18.25Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/33/032cdc44182491aa708d06a68b62434140d8c50820a087fac7af37703357/arrow-1.4.0.tar.gz", hash = "sha256:ed0cc050e98001b8779e84d461b0098c4ac597e88704a655582b21d116e526d7", size = 152931, upload-time = "2025-10-18T17:46:46.761Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80", size = 66419, upload-time = "2023-09-30T22:11:16.072Z" }, + { url = "https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl", hash = "sha256:749f0769958ebdc79c173ff0b0670d59051a535fa26e8eba02953dc19eb43205", size = 68797, upload-time = "2025-10-18T17:46:45.663Z" }, ] [[package]] @@ -35,7 +35,7 @@ wheels = [ [[package]] name = "bandit" -version = "1.8.6" +version = "1.9.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -43,14 +43,14 @@ dependencies = [ { name = "rich" }, { name = "stevedore" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fb/b5/7eb834e213d6f73aace21938e5e90425c92e5f42abafaf8a6d5d21beed51/bandit-1.8.6.tar.gz", hash = "sha256:dbfe9c25fc6961c2078593de55fd19f2559f9e45b99f1272341f5b95dea4e56b", size = 4240271, upload-time = "2025-07-06T03:10:50.9Z" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c3/0cb80dfe0f3076e5da7e4c5ad8e57bac6ac357ff4a6406205501cade4965/bandit-1.9.4.tar.gz", hash = "sha256:b589e5de2afe70bd4d53fa0c1da6199f4085af666fde00e8a034f152a52cd628", size = 4242677, upload-time = "2026-02-25T06:44:15.503Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/48/ca/ba5f909b40ea12ec542d5d7bdd13ee31c4d65f3beed20211ef81c18fa1f3/bandit-1.8.6-py3-none-any.whl", hash = "sha256:3348e934d736fcdb68b6aa4030487097e23a501adf3e7827b63658df464dddd0", size = 133808, upload-time = "2025-07-06T03:10:49.134Z" }, + { url = "https://files.pythonhosted.org/packages/05/a4/a26d5b25671d27e03afb5401a0be5899d94ff8fab6a698b1ac5be3ec29ef/bandit-1.9.4-py3-none-any.whl", hash = "sha256:f89ffa663767f5a0585ea075f01020207e966a9c0f2b9ef56a57c7963a3f6f8e", size = 134741, upload-time = "2026-02-25T06:44:13.694Z" }, ] [[package]] name = "black" -version = "26.3.1" +version = "26.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -60,29 +60,29 @@ dependencies = [ { name = "platformdirs" }, { name = "pytokens" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e1/c5/61175d618685d42b005847464b8fb4743a67b1b8fdb75e50e5a96c31a27a/black-26.3.1.tar.gz", hash = "sha256:2c50f5063a9641c7eed7795014ba37b0f5fa227f3d408b968936e24bc0566b07", size = 666155, upload-time = "2026-03-12T03:36:03.593Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/37/5628dd55bf2b34257fc7603f0fe97c40e3aaf24265f416a9c85c95ca1436/black-26.5.1.tar.gz", hash = "sha256:dd321f668053961824bcc1be1cc1df748b2d7e4fa28086b08331e577b0100a73", size = 679439, upload-time = "2026-05-18T16:53:36.107Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/17/57/5f11c92861f9c92eb9dddf515530bc2d06db843e44bdcf1c83c1427824bc/black-26.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:28ef38aee69e4b12fda8dba75e21f9b4f979b490c8ac0baa7cb505369ac9e1ff", size = 1851987, upload-time = "2026-03-12T03:40:06.248Z" }, - { url = "https://files.pythonhosted.org/packages/54/aa/340a1463660bf6831f9e39646bf774086dbd8ca7fc3cded9d59bbdf4ad0a/black-26.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf9bf162ed91a26f1adba8efda0b573bc6924ec1408a52cc6f82cb73ec2b142c", size = 1689499, upload-time = "2026-03-12T03:40:07.642Z" }, - { url = "https://files.pythonhosted.org/packages/f3/01/b726c93d717d72733da031d2de10b92c9fa4c8d0c67e8a8a372076579279/black-26.3.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:474c27574d6d7037c1bc875a81d9be0a9a4f9ee95e62800dab3cfaadbf75acd5", size = 1754369, upload-time = "2026-03-12T03:40:09.279Z" }, - { url = "https://files.pythonhosted.org/packages/e3/09/61e91881ca291f150cfc9eb7ba19473c2e59df28859a11a88248b5cbbc4d/black-26.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:5e9d0d86df21f2e1677cc4bd090cd0e446278bcbbe49bf3659c308c3e402843e", size = 1413613, upload-time = "2026-03-12T03:40:10.943Z" }, - { url = "https://files.pythonhosted.org/packages/16/73/544f23891b22e7efe4d8f812371ab85b57f6a01b2fc45e3ba2e52ba985b8/black-26.3.1-cp311-cp311-win_arm64.whl", hash = "sha256:9a5e9f45e5d5e1c5b5c29b3bd4265dcc90e8b92cf4534520896ed77f791f4da5", size = 1219719, upload-time = "2026-03-12T03:40:12.597Z" }, - { url = "https://files.pythonhosted.org/packages/dc/f8/da5eae4fc75e78e6dceb60624e1b9662ab00d6b452996046dfa9b8a6025b/black-26.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e6f89631eb88a7302d416594a32faeee9fb8fb848290da9d0a5f2903519fc1", size = 1895920, upload-time = "2026-03-12T03:40:13.921Z" }, - { url = "https://files.pythonhosted.org/packages/2c/9f/04e6f26534da2e1629b2b48255c264cabf5eedc5141d04516d9d68a24111/black-26.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41cd2012d35b47d589cb8a16faf8a32ef7a336f56356babd9fcf70939ad1897f", size = 1718499, upload-time = "2026-03-12T03:40:15.239Z" }, - { url = "https://files.pythonhosted.org/packages/04/91/a5935b2a63e31b331060c4a9fdb5a6c725840858c599032a6f3aac94055f/black-26.3.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f76ff19ec5297dd8e66eb64deda23631e642c9393ab592826fd4bdc97a4bce7", size = 1794994, upload-time = "2026-03-12T03:40:17.124Z" }, - { url = "https://files.pythonhosted.org/packages/e7/0a/86e462cdd311a3c2a8ece708d22aba17d0b2a0d5348ca34b40cdcbea512e/black-26.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:ddb113db38838eb9f043623ba274cfaf7d51d5b0c22ecb30afe58b1bb8322983", size = 1420867, upload-time = "2026-03-12T03:40:18.83Z" }, - { url = "https://files.pythonhosted.org/packages/5b/e5/22515a19cb7eaee3440325a6b0d95d2c0e88dd180cb011b12ae488e031d1/black-26.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:dfdd51fc3e64ea4f35873d1b3fb25326773d55d2329ff8449139ebaad7357efb", size = 1230124, upload-time = "2026-03-12T03:40:20.425Z" }, - { url = "https://files.pythonhosted.org/packages/f5/77/5728052a3c0450c53d9bb3945c4c46b91baa62b2cafab6801411b6271e45/black-26.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:855822d90f884905362f602880ed8b5df1b7e3ee7d0db2502d4388a954cc8c54", size = 1895034, upload-time = "2026-03-12T03:40:21.813Z" }, - { url = "https://files.pythonhosted.org/packages/52/73/7cae55fdfdfbe9d19e9a8d25d145018965fe2079fa908101c3733b0c55a0/black-26.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8a33d657f3276328ce00e4d37fe70361e1ec7614da5d7b6e78de5426cb56332f", size = 1718503, upload-time = "2026-03-12T03:40:23.666Z" }, - { url = "https://files.pythonhosted.org/packages/e1/87/af89ad449e8254fdbc74654e6467e3c9381b61472cc532ee350d28cfdafb/black-26.3.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f1cd08e99d2f9317292a311dfe578fd2a24b15dbce97792f9c4d752275c1fa56", size = 1793557, upload-time = "2026-03-12T03:40:25.497Z" }, - { url = "https://files.pythonhosted.org/packages/43/10/d6c06a791d8124b843bf325ab4ac7d2f5b98731dff84d6064eafd687ded1/black-26.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:c7e72339f841b5a237ff14f7d3880ddd0fc7f98a1199e8c4327f9a4f478c1839", size = 1422766, upload-time = "2026-03-12T03:40:27.14Z" }, - { url = "https://files.pythonhosted.org/packages/59/4f/40a582c015f2d841ac24fed6390bd68f0fc896069ff3a886317959c9daf8/black-26.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:afc622538b430aa4c8c853f7f63bc582b3b8030fd8c80b70fb5fa5b834e575c2", size = 1232140, upload-time = "2026-03-12T03:40:28.882Z" }, - { url = "https://files.pythonhosted.org/packages/d5/da/e36e27c9cebc1311b7579210df6f1c86e50f2d7143ae4fcf8a5017dc8809/black-26.3.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2d6bfaf7fd0993b420bed691f20f9492d53ce9a2bcccea4b797d34e947318a78", size = 1889234, upload-time = "2026-03-12T03:40:30.964Z" }, - { url = "https://files.pythonhosted.org/packages/0e/7b/9871acf393f64a5fa33668c19350ca87177b181f44bb3d0c33b2d534f22c/black-26.3.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f89f2ab047c76a9c03f78d0d66ca519e389519902fa27e7a91117ef7611c0568", size = 1720522, upload-time = "2026-03-12T03:40:32.346Z" }, - { url = "https://files.pythonhosted.org/packages/03/87/e766c7f2e90c07fb7586cc787c9ae6462b1eedab390191f2b7fc7f6170a9/black-26.3.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b07fc0dab849d24a80a29cfab8d8a19187d1c4685d8a5e6385a5ce323c1f015f", size = 1787824, upload-time = "2026-03-12T03:40:33.636Z" }, - { url = "https://files.pythonhosted.org/packages/ac/94/2424338fb2d1875e9e83eed4c8e9c67f6905ec25afd826a911aea2b02535/black-26.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:0126ae5b7c09957da2bdbd91a9ba1207453feada9e9fe51992848658c6c8e01c", size = 1445855, upload-time = "2026-03-12T03:40:35.442Z" }, - { url = "https://files.pythonhosted.org/packages/86/43/0c3338bd928afb8ee7471f1a4eec3bdbe2245ccb4a646092a222e8669840/black-26.3.1-cp314-cp314-win_arm64.whl", hash = "sha256:92c0ec1f2cc149551a2b7b47efc32c866406b6891b0ee4625e95967c8f4acfb1", size = 1258109, upload-time = "2026-03-12T03:40:36.832Z" }, - { url = "https://files.pythonhosted.org/packages/8e/0d/52d98722666d6fc6c3dd4c76df339501d6efd40e0ff95e6186a7b7f0befd/black-26.3.1-py3-none-any.whl", hash = "sha256:2bd5aa94fc267d38bb21a70d7410a89f1a1d318841855f698746f8e7f51acd1b", size = 207542, upload-time = "2026-03-12T03:36:01.668Z" }, + { url = "https://files.pythonhosted.org/packages/4b/96/3c3e09f09f44a37aac36b178a279cd19aa7001bd796187a7b162a294c81f/black-26.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:96ae2c733b2aabdd9986e2c5df628ff3473676cd1c5faded1ff496cf6d74083c", size = 1970639, upload-time = "2026-05-18T17:05:11.461Z" }, + { url = "https://files.pythonhosted.org/packages/83/ea/5ad117b9ee3ecd933c712bcbae610006e5b7cc9f41c526cd7ed3b6c4124c/black-26.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0e48b87e03bf109288e55cfceadcfa15ff5470aca2851a851950ed2926f450d7", size = 1792130, upload-time = "2026-05-18T17:05:12.983Z" }, + { url = "https://files.pythonhosted.org/packages/06/3a/7c448bc623fcdfa96672531beb5a616ea5e64f6975955254d7731ffb0ad9/black-26.5.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5119fa92ae61f786e8c3662fd60aece1d0a2dd5cca5d0c79417a95e7a4272a59", size = 1846134, upload-time = "2026-05-18T17:05:14.506Z" }, + { url = "https://files.pythonhosted.org/packages/a1/5b/0b39b3a5917f0657ac014ad2edb58c139553a478adfe7f817abf1622ff6e/black-26.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:30d3c14661f2792e9142cce3eeeb1cbc175b3eb5f733be0c8eeb99651e52b0c3", size = 1478883, upload-time = "2026-05-18T17:05:16.542Z" }, + { url = "https://files.pythonhosted.org/packages/4c/48/dc222692e0f95030db1bbfb6c857e76858bad09058221ea7aae815255327/black-26.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:1ef92b76f7733f282fd096ea406200b5a286c42947412b0eaff3a74e3616cefe", size = 1277776, upload-time = "2026-05-18T17:05:18.029Z" }, + { url = "https://files.pythonhosted.org/packages/24/99/7744b906703228264ef73bdd534df88ec1ef3de45c4e78f6d31b9e32d0c9/black-26.5.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4ad6fa01f941920f54f2bbb35f3df7673428a0ef98a0b0840c2eaef3b110efa8", size = 2012518, upload-time = "2026-05-18T17:05:20.108Z" }, + { url = "https://files.pythonhosted.org/packages/b7/c0/c5a3b1636dfd09c42534f2b3cf33506814f6d3e066fb0879ffa16c1ae860/black-26.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3915f256e75a2d7cf88d8953d37f780455dc586cc72dee059c528fe77f581217", size = 1816016, upload-time = "2026-05-18T17:05:21.84Z" }, + { url = "https://files.pythonhosted.org/packages/1f/0e/36044316b65ca471d3bb6d3703fd06fb50c6b727c3562f6a5a3153634f88/black-26.5.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d98d4137277c75dfb898ec8d846c4fd68ba1e9cf77f95e2865c203dc18f4c3d", size = 1884150, upload-time = "2026-05-18T17:05:23.546Z" }, + { url = "https://files.pythonhosted.org/packages/b3/33/dafc5808c2af43672912111d7c3354af1615f7e2be3bed7a878461abbe4d/black-26.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:a1dca32d9f1784af512a13410ec204c6f7f0aa9797a111c42e1c03449821c264", size = 1486825, upload-time = "2026-05-18T17:05:25.004Z" }, + { url = "https://files.pythonhosted.org/packages/82/14/b965ee6ad2a311f28bdbf692def3ee9848d2ae289dab28b27657fcee3e78/black-26.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:1037d5ac7b7b310b2632ad867ec8d0e4c4819dcdb0b820f63135da746a24e418", size = 1288646, upload-time = "2026-05-18T17:05:26.477Z" }, + { url = "https://files.pythonhosted.org/packages/3f/5c/c384363980e11e25ca6b93205949bb331fbf35f4e0dbec376dfa6326cec8/black-26.5.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2b36cf2ddf5566e205f6535f782a62194a184d33e175b64ae8c40b1737522be3", size = 2009020, upload-time = "2026-05-18T17:05:28.132Z" }, + { url = "https://files.pythonhosted.org/packages/0b/df/9f31c5e0babbfed77d505fc5d120beb98b21b33feaeded3924ea941fe360/black-26.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f7ea64ebfa01b50f693508fc39f875e264446d3b097088f84f203b9d09618a0", size = 1813335, upload-time = "2026-05-18T17:05:31.266Z" }, + { url = "https://files.pythonhosted.org/packages/fb/24/8e7b9a2fa61b0afd82209efe937557d180a1fa055bd7f6161eb9defc3719/black-26.5.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecb3e624844c798144e9bd986954e0adc81d8911a1f30f375e1252fe26e8c294", size = 1881614, upload-time = "2026-05-18T17:05:32.718Z" }, + { url = "https://files.pythonhosted.org/packages/49/ad/b4e0d9365ba8ac34f6bbab62a4b1b2dd5d618fac3fa1b8db968c844201b5/black-26.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:e1a26503279b6b310669fb0b219c39e4820b77e8189fe80f522bb511f247db0a", size = 1488925, upload-time = "2026-05-18T17:05:34.259Z" }, + { url = "https://files.pythonhosted.org/packages/a1/4b/652b859bf5df88a751c30451b09338f7fd26a77d1271c666992f836b7711/black-26.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c34b25da232ead53a6f335b76dbea124f4d152ad568b9080d6f944bc2b34b52", size = 1289883, upload-time = "2026-05-18T17:05:36.019Z" }, + { url = "https://files.pythonhosted.org/packages/a6/16/a8da8eb208c51c7f4ce74609a45d0dcc6d8a2141e45e81ee5289d1bb0d59/black-26.5.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e88976690a64b0af98312ca958415849cb42423423c5f2ee74af4b49a97a2168", size = 2004800, upload-time = "2026-05-18T17:05:38.182Z" }, + { url = "https://files.pythonhosted.org/packages/11/8a/a479296a19e383b70a725882a6cf3d786540601ff03cabbaaf1cce864c5a/black-26.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32d5ea7f6c8bdfa6e648326ebca1f02b0764e2a029edc6f8dce2627e19d468c3", size = 1815576, upload-time = "2026-05-18T17:05:40.309Z" }, + { url = "https://files.pythonhosted.org/packages/81/6b/cfaf3d39f25132c156a068f6b805576c9103a84086019507c70e1911ee7d/black-26.5.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ea8d16dc41655aa113cd64665e7219446cd7e4ff2248d7178eaa905190c86b18", size = 1877927, upload-time = "2026-05-18T17:05:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/66/76/302e313964bcff7e28df329d39f84f5270095730d85ff0acc260610a0d82/black-26.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:577f21094ea469ef92ec1adaf2c9441a226d2144d01a5be2fa823cecf6543e50", size = 1511860, upload-time = "2026-05-18T17:05:43.943Z" }, + { url = "https://files.pythonhosted.org/packages/27/4e/a3827e35e0e567f9f9ee59e2a0ab979267dca98718f25547ca8c6733afd4/black-26.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:ed1a20af114c301a0269bf01163d51dbef72737fd65f850001e7cbe7f3c7abae", size = 1316632, upload-time = "2026-05-18T17:05:45.521Z" }, + { url = "https://files.pythonhosted.org/packages/94/51/f975cae76d44274cc2868dc9040ac5d58d464784610234455b4e7b19c6ef/black-26.5.1-py3-none-any.whl", hash = "sha256:4ed7f7da04046d2e488437170797d3b4a4ad83906683bcb7dfc68b673bbce5e2", size = 213693, upload-time = "2026-05-18T16:53:33.964Z" }, ] [[package]] @@ -411,20 +411,20 @@ wheels = [ [[package]] name = "isort" -version = "6.0.1" +version = "8.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b8/21/1e2a441f74a653a144224d7d21afe8f4169e6c7c20bb13aec3a2dc3815e0/isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450", size = 821955, upload-time = "2025-02-26T21:13:16.955Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ef/7c/ec4ab396d31b3b395e2e999c8f46dec78c5e29209fac49d1f4dace04041d/isort-8.0.1.tar.gz", hash = "sha256:171ac4ff559cdc060bcfff550bc8404a486fee0caab245679c2abe7cb253c78d", size = 769592, upload-time = "2026-02-28T10:08:20.685Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/11/114d0a5f4dabbdcedc1125dee0888514c3c3b16d3e9facad87ed96fad97c/isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615", size = 94186, upload-time = "2025-02-26T21:13:14.911Z" }, + { url = "https://files.pythonhosted.org/packages/3e/95/c7c34aa53c16353c56d0b802fba48d5f5caa2cdee7958acbcb795c830416/isort-8.0.1-py3-none-any.whl", hash = "sha256:28b89bc70f751b559aeca209e6120393d43fbe2490de0559662be7a9787e3d75", size = 89733, upload-time = "2026-02-28T10:08:19.466Z" }, ] [[package]] name = "jmespath" -version = "1.0.1" +version = "1.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/00/2a/e867e8531cf3e36b41201936b7fa7ba7b5702dbef42922193f05c8976cd6/jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe", size = 25843, upload-time = "2022-06-17T18:00:12.224Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/59/322338183ecda247fb5d1763a6cbe46eff7222eaeebafd9fa65d4bf5cb11/jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d", size = 27377, upload-time = "2026-01-22T16:35:26.279Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256, upload-time = "2022-06-17T18:00:10.251Z" }, + { url = "https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419, upload-time = "2026-01-22T16:35:24.919Z" }, ] [[package]] @@ -513,7 +513,7 @@ wheels = [ [[package]] name = "pre-commit" -version = "4.1.0" +version = "4.6.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cfgv" }, @@ -522,9 +522,9 @@ dependencies = [ { name = "pyyaml" }, { name = "virtualenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2a/13/b62d075317d8686071eb843f0bb1f195eb332f48869d3c31a4c6f1e063ac/pre_commit-4.1.0.tar.gz", hash = "sha256:ae3f018575a588e30dfddfab9a05448bfbd6b73d78709617b5a2b853549716d4", size = 193330, upload-time = "2025-01-20T18:31:48.681Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/22/2de9408ac81acbb8a7d05d4cc064a152ccf33b3d480ebe0cd292153db239/pre_commit-4.6.0.tar.gz", hash = "sha256:718d2208cef53fdc38206e40524a6d4d9576d103eb16f0fec11c875e7716e9d9", size = 198525, upload-time = "2026-04-21T20:31:41.613Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/43/b3/df14c580d82b9627d173ceea305ba898dca135feb360b6d84019d0803d3b/pre_commit-4.1.0-py2.py3-none-any.whl", hash = "sha256:d29e7cb346295bcc1cc75fc3e92e343495e3ea0196c9ec6ba53f49f10ab6ae7b", size = 220560, upload-time = "2025-01-20T18:31:47.319Z" }, + { url = "https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl", hash = "sha256:e2cf246f7299edcabcf15f9b0571fdce06058527f0a06535068a86d38089f29b", size = 226472, upload-time = "2026-04-21T20:31:40.092Z" }, ] [[package]] @@ -592,15 +592,16 @@ wheels = [ [[package]] name = "pytest-cov" -version = "6.0.0" +version = "7.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "coverage", extra = ["toml"] }, + { name = "pluggy" }, { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/be/45/9b538de8cef30e17c7b45ef42f538a94889ed6a16f2387a6c89e73220651/pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0", size = 66945, upload-time = "2024-10-29T20:13:35.363Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/36/3b/48e79f2cd6a61dbbd4807b4ed46cb564b4fd50a76166b1c4ea5c1d9e2371/pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35", size = 22949, upload-time = "2024-10-29T20:13:33.215Z" }, + { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" }, ] [[package]] @@ -685,14 +686,14 @@ wheels = [ [[package]] name = "pyupgrade" -version = "3.19.1" +version = "3.21.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "tokenize-rt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/35/3a/efa8e75cf84d53f1b3f0113387ab120ef460396a4068e41b6cf18a3d216d/pyupgrade-3.19.1.tar.gz", hash = "sha256:d10e8c5f54b8327211828769e98d95d95e4715de632a3414f1eef3f51357b9e2", size = 45116, upload-time = "2024-12-17T01:43:02.326Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7f/a1/dc63caaeed232b1c58eae1b7a75f262d64ab8435882f696ffa9b58c0c415/pyupgrade-3.21.2.tar.gz", hash = "sha256:1a361bea39deda78d1460f65d9dd548d3a36ff8171d2482298539b9dc11c9c06", size = 45455, upload-time = "2025-11-19T00:39:48.012Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/dd/43/c6c1ff945c7900613f6e6ef2a8688639a247d62eb0ffa9935c599f69c08e/pyupgrade-3.19.1-py2.py3-none-any.whl", hash = "sha256:8c5b0bfacae5ff30fa136a53eb7f22c34ba007450d4099e9da8089dabb9e67c9", size = 62412, upload-time = "2024-12-17T01:42:59.829Z" }, + { url = "https://files.pythonhosted.org/packages/16/8c/433dac11910989a90c40b10149d07ef7224232236971a562d3976790ec53/pyupgrade-3.21.2-py2.py3-none-any.whl", hash = "sha256:2ac7b95cbd176475041e4dfe8ef81298bd4654a244f957167bd68af37d52be9f", size = 62814, upload-time = "2025-11-19T00:39:46.958Z" }, ] [[package]] @@ -764,7 +765,7 @@ wheels = [ [[package]] name = "requests" -version = "2.33.1" +version = "2.34.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -772,22 +773,22 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5f/a4/98b9c7c6428a668bf7e42ebb7c79d576a1c3c1e3ae2d47e674b468388871/requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517", size = 134120, upload-time = "2026-03-30T16:09:15.531Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a", size = 64947, upload-time = "2026-03-30T16:09:13.83Z" }, + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, ] [[package]] name = "rich" -version = "13.9.4" +version = "15.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149, upload-time = "2024-11-01T16:43:57.873Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424, upload-time = "2024-11-01T16:43:55.817Z" }, + { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, ] [[package]] @@ -902,40 +903,40 @@ test = [ [package.metadata] requires-dist = [ - { name = "arrow", specifier = "~=1.3.0" }, - { name = "black", specifier = "~=26.3.1" }, + { name = "arrow", specifier = "~=1.4.0" }, + { name = "black", specifier = "~=26.5.1" }, { name = "debugpy", specifier = "~=1.8.12" }, - { name = "fakeredis", specifier = "~=2.36.0" }, + { name = "fakeredis", specifier = ">=2.36.0" }, { name = "inflection", specifier = "~=0.5.1" }, - { name = "isort", specifier = "~=6.0.0" }, - { name = "jmespath", specifier = "~=1.0.1" }, + { name = "isort", specifier = "~=8.0.1" }, + { name = "jmespath", specifier = "~=1.1.0" }, { name = "paho-mqtt", specifier = "~=2.1.0" }, { name = "pyaes", specifier = "~=1.6.1" }, - { name = "pydantic", specifier = "~=1.10.0" }, - { name = "python-dateutil", specifier = "~=2.9.0" }, + { name = "pydantic", specifier = "~=1.10.26" }, + { name = "python-dateutil", specifier = "~=2.9.0.post0" }, { name = "python-dotenv", specifier = "~=1.2.2" }, - { name = "pyyaml", specifier = "~=6.0.2" }, + { name = "pyyaml", specifier = "~=6.0.3" }, { name = "redis", specifier = "~=8.0.0" }, - { name = "requests", specifier = "~=2.33.0" }, - { name = "rich", specifier = "~=13.9.4" }, + { name = "requests", specifier = "~=2.34.2" }, + { name = "rich", specifier = "~=15.0.0" }, { name = "semantic-version", specifier = "~=2.10.0" }, { name = "tinydb", specifier = "~=4.8.2" }, - { name = "typer", specifier = "~=0.21.1" }, - { name = "wrapt", specifier = "~=1.17.2" }, + { name = "typer", specifier = ">=0.26.7" }, + { name = "wrapt", specifier = "~=2.2.1" }, ] [package.metadata.requires-dev] dev = [ - { name = "bandit", specifier = "~=1.8.3" }, - { name = "pre-commit", specifier = "~=4.1.0" }, - { name = "pyupgrade", specifier = "~=3.19.1" }, - { name = "ruff", specifier = "~=0.15.2" }, - { name = "ty", specifier = "~=0.0.42" }, + { name = "bandit", specifier = ">=1.9.4" }, + { name = "pre-commit", specifier = ">=4.6.0" }, + { name = "pyupgrade", specifier = ">=3.21.2" }, + { name = "ruff", specifier = ">=0.15.16" }, + { name = "ty", specifier = ">=0.0.43" }, ] test = [ - { name = "pytest", specifier = "~=9.0.3" }, - { name = "pytest-cov", specifier = "~=6.0.0" }, - { name = "pytest-ordering", specifier = "~=0.6" }, + { name = "pytest", specifier = ">=9.0.3" }, + { name = "pytest-cov", specifier = ">=7.1.0" }, + { name = "pytest-ordering", specifier = ">=0.6" }, ] [[package]] @@ -1037,35 +1038,35 @@ wheels = [ [[package]] name = "typer" -version = "0.21.2" +version = "0.26.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-doc" }, - { name = "click" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "rich" }, { name = "shellingham" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f2/1e/a27cc02a0cd715118c71fa2aef2c687fdefc3c28d90fd0dd789c5118154c/typer-0.21.2.tar.gz", hash = "sha256:1abd95a3b675e17ff61b0838ac637fe9478d446d62ad17fa4bb81ea57cc54028", size = 120426, upload-time = "2026-02-10T19:33:46.182Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/ed/ef06584ccdd5c410df0837951ecd7e15d9a6144ea1bd4c73cecab1a89891/typer-0.26.7.tar.gz", hash = "sha256:e314a34c617e419c091b2830dda3ea1f257134ff593061a8f5b9717ab8dddb3a", size = 201709, upload-time = "2026-06-03T07:18:06.843Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b8/cc/d59f893fbdfb5f58770c05febfc4086a46875f1084453621c35605cec946/typer-0.21.2-py3-none-any.whl", hash = "sha256:c3d8de54d00347ef90b82131ca946274f017cffb46683ae3883c360fa958f55c", size = 56728, upload-time = "2026-02-10T19:33:48.01Z" }, + { url = "https://files.pythonhosted.org/packages/24/25/2201973529af2c954de0bb725323c3aaed6d7f0ceee8f550dec9185df013/typer-0.26.7-py3-none-any.whl", hash = "sha256:5c87cfbc5d34491c5346ebf49c23e18d56ccb863268d3a8d592b26087c2f5e58", size = 122456, upload-time = "2026-06-03T07:18:05.732Z" }, ] [[package]] -name = "types-python-dateutil" -version = "2.9.0.20260518" +name = "typing-extensions" +version = "4.15.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8d/e8/c01bdf0d7c3659428c091fbd693177093639565bcbc86bc20098e6d37cc6/types_python_dateutil-2.9.0.20260518.tar.gz", hash = "sha256:51f02dc03b61c7f6a07df45797d4dfe8a1aa47f0b7db9ad89f6fd3a1a70e1b51", size = 17082, upload-time = "2026-05-18T06:05:24.508Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/36/22/169273273ca34e9ab0ae2f387ba72ed7e09faaaf834da01d6b89c2bea71a/types_python_dateutil-2.9.0.20260518-py3-none-any.whl", hash = "sha256:d6a9c5bd0de61460c8fdef8ab2b400f956a1a1075cce08d4e2b4434e478c50b8", size = 18431, upload-time = "2026-05-18T06:05:23.641Z" }, + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, ] [[package]] -name = "typing-extensions" -version = "4.15.0" +name = "tzdata" +version = "2026.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/19/1b9b0e29f30c6d35cb345486df41110984ea67ae69dddbc0e8a100999493/tzdata-2026.2.tar.gz", hash = "sha256:9173fde7d80d9018e02a662e168e5a2d04f87c41ea174b139fbef642eda62d10", size = 198254, upload-time = "2026-04-24T15:22:08.651Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, + { url = "https://files.pythonhosted.org/packages/ce/e4/dccd7f47c4b64213ac01ef921a1337ee6e30e8c6466046018326977efd95/tzdata-2026.2-py2.py3-none-any.whl", hash = "sha256:bbe9af844f658da81a5f95019480da3a89415801f6cc966806612cc7169bffe7", size = 349321, upload-time = "2026-04-24T15:22:05.876Z" }, ] [[package]] @@ -1094,59 +1095,75 @@ wheels = [ [[package]] name = "wrapt" -version = "1.17.3" +version = "2.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/95/8f/aeb76c5b46e273670962298c23e7ddde79916cb74db802131d49a85e4b7d/wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0", size = 55547, upload-time = "2025-08-12T05:53:21.714Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/9f/06263fcd8ad6c405f05a3905fd7a84dd3176eb5ad46e44bccc0cd16348bb/wrapt-2.2.1.tar.gz", hash = "sha256:6744f504375775d7609c82c8d3d94af1c9a6f05586984536905908ba905277b9", size = 127620, upload-time = "2026-05-22T14:49:43.056Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/52/db/00e2a219213856074a213503fdac0511203dceefff26e1daa15250cc01a0/wrapt-1.17.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:273a736c4645e63ac582c60a56b0acb529ef07f78e08dc6bfadf6a46b19c0da7", size = 53482, upload-time = "2025-08-12T05:51:45.79Z" }, - { url = "https://files.pythonhosted.org/packages/5e/30/ca3c4a5eba478408572096fe9ce36e6e915994dd26a4e9e98b4f729c06d9/wrapt-1.17.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5531d911795e3f935a9c23eb1c8c03c211661a5060aab167065896bbf62a5f85", size = 38674, upload-time = "2025-08-12T05:51:34.629Z" }, - { url = "https://files.pythonhosted.org/packages/31/25/3e8cc2c46b5329c5957cec959cb76a10718e1a513309c31399a4dad07eb3/wrapt-1.17.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0610b46293c59a3adbae3dee552b648b984176f8562ee0dba099a56cfbe4df1f", size = 38959, upload-time = "2025-08-12T05:51:56.074Z" }, - { url = "https://files.pythonhosted.org/packages/5d/8f/a32a99fc03e4b37e31b57cb9cefc65050ea08147a8ce12f288616b05ef54/wrapt-1.17.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b32888aad8b6e68f83a8fdccbf3165f5469702a7544472bdf41f582970ed3311", size = 82376, upload-time = "2025-08-12T05:52:32.134Z" }, - { url = "https://files.pythonhosted.org/packages/31/57/4930cb8d9d70d59c27ee1332a318c20291749b4fba31f113c2f8ac49a72e/wrapt-1.17.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cccf4f81371f257440c88faed6b74f1053eef90807b77e31ca057b2db74edb1", size = 83604, upload-time = "2025-08-12T05:52:11.663Z" }, - { url = "https://files.pythonhosted.org/packages/a8/f3/1afd48de81d63dd66e01b263a6fbb86e1b5053b419b9b33d13e1f6d0f7d0/wrapt-1.17.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8a210b158a34164de8bb68b0e7780041a903d7b00c87e906fb69928bf7890d5", size = 82782, upload-time = "2025-08-12T05:52:12.626Z" }, - { url = "https://files.pythonhosted.org/packages/1e/d7/4ad5327612173b144998232f98a85bb24b60c352afb73bc48e3e0d2bdc4e/wrapt-1.17.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:79573c24a46ce11aab457b472efd8d125e5a51da2d1d24387666cd85f54c05b2", size = 82076, upload-time = "2025-08-12T05:52:33.168Z" }, - { url = "https://files.pythonhosted.org/packages/bb/59/e0adfc831674a65694f18ea6dc821f9fcb9ec82c2ce7e3d73a88ba2e8718/wrapt-1.17.3-cp311-cp311-win32.whl", hash = "sha256:c31eebe420a9a5d2887b13000b043ff6ca27c452a9a22fa71f35f118e8d4bf89", size = 36457, upload-time = "2025-08-12T05:53:03.936Z" }, - { url = "https://files.pythonhosted.org/packages/83/88/16b7231ba49861b6f75fc309b11012ede4d6b0a9c90969d9e0db8d991aeb/wrapt-1.17.3-cp311-cp311-win_amd64.whl", hash = "sha256:0b1831115c97f0663cb77aa27d381237e73ad4f721391a9bfb2fe8bc25fa6e77", size = 38745, upload-time = "2025-08-12T05:53:02.885Z" }, - { url = "https://files.pythonhosted.org/packages/9a/1e/c4d4f3398ec073012c51d1c8d87f715f56765444e1a4b11e5180577b7e6e/wrapt-1.17.3-cp311-cp311-win_arm64.whl", hash = "sha256:5a7b3c1ee8265eb4c8f1b7d29943f195c00673f5ab60c192eba2d4a7eae5f46a", size = 36806, upload-time = "2025-08-12T05:52:53.368Z" }, - { url = "https://files.pythonhosted.org/packages/9f/41/cad1aba93e752f1f9268c77270da3c469883d56e2798e7df6240dcb2287b/wrapt-1.17.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab232e7fdb44cdfbf55fc3afa31bcdb0d8980b9b95c38b6405df2acb672af0e0", size = 53998, upload-time = "2025-08-12T05:51:47.138Z" }, - { url = "https://files.pythonhosted.org/packages/60/f8/096a7cc13097a1869fe44efe68dace40d2a16ecb853141394047f0780b96/wrapt-1.17.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9baa544e6acc91130e926e8c802a17f3b16fbea0fd441b5a60f5cf2cc5c3deba", size = 39020, upload-time = "2025-08-12T05:51:35.906Z" }, - { url = "https://files.pythonhosted.org/packages/33/df/bdf864b8997aab4febb96a9ae5c124f700a5abd9b5e13d2a3214ec4be705/wrapt-1.17.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b538e31eca1a7ea4605e44f81a48aa24c4632a277431a6ed3f328835901f4fd", size = 39098, upload-time = "2025-08-12T05:51:57.474Z" }, - { url = "https://files.pythonhosted.org/packages/9f/81/5d931d78d0eb732b95dc3ddaeeb71c8bb572fb01356e9133916cd729ecdd/wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:042ec3bb8f319c147b1301f2393bc19dba6e176b7da446853406d041c36c7828", size = 88036, upload-time = "2025-08-12T05:52:34.784Z" }, - { url = "https://files.pythonhosted.org/packages/ca/38/2e1785df03b3d72d34fc6252d91d9d12dc27a5c89caef3335a1bbb8908ca/wrapt-1.17.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3af60380ba0b7b5aeb329bc4e402acd25bd877e98b3727b0135cb5c2efdaefe9", size = 88156, upload-time = "2025-08-12T05:52:13.599Z" }, - { url = "https://files.pythonhosted.org/packages/b3/8b/48cdb60fe0603e34e05cffda0b2a4adab81fd43718e11111a4b0100fd7c1/wrapt-1.17.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b02e424deef65c9f7326d8c19220a2c9040c51dc165cddb732f16198c168396", size = 87102, upload-time = "2025-08-12T05:52:14.56Z" }, - { url = "https://files.pythonhosted.org/packages/3c/51/d81abca783b58f40a154f1b2c56db1d2d9e0d04fa2d4224e357529f57a57/wrapt-1.17.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:74afa28374a3c3a11b3b5e5fca0ae03bef8450d6aa3ab3a1e2c30e3a75d023dc", size = 87732, upload-time = "2025-08-12T05:52:36.165Z" }, - { url = "https://files.pythonhosted.org/packages/9e/b1/43b286ca1392a006d5336412d41663eeef1ad57485f3e52c767376ba7e5a/wrapt-1.17.3-cp312-cp312-win32.whl", hash = "sha256:4da9f45279fff3543c371d5ababc57a0384f70be244de7759c85a7f989cb4ebe", size = 36705, upload-time = "2025-08-12T05:53:07.123Z" }, - { url = "https://files.pythonhosted.org/packages/28/de/49493f962bd3c586ab4b88066e967aa2e0703d6ef2c43aa28cb83bf7b507/wrapt-1.17.3-cp312-cp312-win_amd64.whl", hash = "sha256:e71d5c6ebac14875668a1e90baf2ea0ef5b7ac7918355850c0908ae82bcb297c", size = 38877, upload-time = "2025-08-12T05:53:05.436Z" }, - { url = "https://files.pythonhosted.org/packages/f1/48/0f7102fe9cb1e8a5a77f80d4f0956d62d97034bbe88d33e94699f99d181d/wrapt-1.17.3-cp312-cp312-win_arm64.whl", hash = "sha256:604d076c55e2fdd4c1c03d06dc1a31b95130010517b5019db15365ec4a405fc6", size = 36885, upload-time = "2025-08-12T05:52:54.367Z" }, - { url = "https://files.pythonhosted.org/packages/fc/f6/759ece88472157acb55fc195e5b116e06730f1b651b5b314c66291729193/wrapt-1.17.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0", size = 54003, upload-time = "2025-08-12T05:51:48.627Z" }, - { url = "https://files.pythonhosted.org/packages/4f/a9/49940b9dc6d47027dc850c116d79b4155f15c08547d04db0f07121499347/wrapt-1.17.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77", size = 39025, upload-time = "2025-08-12T05:51:37.156Z" }, - { url = "https://files.pythonhosted.org/packages/45/35/6a08de0f2c96dcdd7fe464d7420ddb9a7655a6561150e5fc4da9356aeaab/wrapt-1.17.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7", size = 39108, upload-time = "2025-08-12T05:51:58.425Z" }, - { url = "https://files.pythonhosted.org/packages/0c/37/6faf15cfa41bf1f3dba80cd3f5ccc6622dfccb660ab26ed79f0178c7497f/wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277", size = 88072, upload-time = "2025-08-12T05:52:37.53Z" }, - { url = "https://files.pythonhosted.org/packages/78/f2/efe19ada4a38e4e15b6dff39c3e3f3f73f5decf901f66e6f72fe79623a06/wrapt-1.17.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d", size = 88214, upload-time = "2025-08-12T05:52:15.886Z" }, - { url = "https://files.pythonhosted.org/packages/40/90/ca86701e9de1622b16e09689fc24b76f69b06bb0150990f6f4e8b0eeb576/wrapt-1.17.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa", size = 87105, upload-time = "2025-08-12T05:52:17.914Z" }, - { url = "https://files.pythonhosted.org/packages/fd/e0/d10bd257c9a3e15cbf5523025252cc14d77468e8ed644aafb2d6f54cb95d/wrapt-1.17.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050", size = 87766, upload-time = "2025-08-12T05:52:39.243Z" }, - { url = "https://files.pythonhosted.org/packages/e8/cf/7d848740203c7b4b27eb55dbfede11aca974a51c3d894f6cc4b865f42f58/wrapt-1.17.3-cp313-cp313-win32.whl", hash = "sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8", size = 36711, upload-time = "2025-08-12T05:53:10.074Z" }, - { url = "https://files.pythonhosted.org/packages/57/54/35a84d0a4d23ea675994104e667ceff49227ce473ba6a59ba2c84f250b74/wrapt-1.17.3-cp313-cp313-win_amd64.whl", hash = "sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb", size = 38885, upload-time = "2025-08-12T05:53:08.695Z" }, - { url = "https://files.pythonhosted.org/packages/01/77/66e54407c59d7b02a3c4e0af3783168fff8e5d61def52cda8728439d86bc/wrapt-1.17.3-cp313-cp313-win_arm64.whl", hash = "sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16", size = 36896, upload-time = "2025-08-12T05:52:55.34Z" }, - { url = "https://files.pythonhosted.org/packages/02/a2/cd864b2a14f20d14f4c496fab97802001560f9f41554eef6df201cd7f76c/wrapt-1.17.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cf30f6e3c077c8e6a9a7809c94551203c8843e74ba0c960f4a98cd80d4665d39", size = 54132, upload-time = "2025-08-12T05:51:49.864Z" }, - { url = "https://files.pythonhosted.org/packages/d5/46/d011725b0c89e853dc44cceb738a307cde5d240d023d6d40a82d1b4e1182/wrapt-1.17.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e228514a06843cae89621384cfe3a80418f3c04aadf8a3b14e46a7be704e4235", size = 39091, upload-time = "2025-08-12T05:51:38.935Z" }, - { url = "https://files.pythonhosted.org/packages/2e/9e/3ad852d77c35aae7ddebdbc3b6d35ec8013af7d7dddad0ad911f3d891dae/wrapt-1.17.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5ea5eb3c0c071862997d6f3e02af1d055f381b1d25b286b9d6644b79db77657c", size = 39172, upload-time = "2025-08-12T05:51:59.365Z" }, - { url = "https://files.pythonhosted.org/packages/c3/f7/c983d2762bcce2326c317c26a6a1e7016f7eb039c27cdf5c4e30f4160f31/wrapt-1.17.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:281262213373b6d5e4bb4353bc36d1ba4084e6d6b5d242863721ef2bf2c2930b", size = 87163, upload-time = "2025-08-12T05:52:40.965Z" }, - { url = "https://files.pythonhosted.org/packages/e4/0f/f673f75d489c7f22d17fe0193e84b41540d962f75fce579cf6873167c29b/wrapt-1.17.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4a8d2b25efb6681ecacad42fca8859f88092d8732b170de6a5dddd80a1c8fa", size = 87963, upload-time = "2025-08-12T05:52:20.326Z" }, - { url = "https://files.pythonhosted.org/packages/df/61/515ad6caca68995da2fac7a6af97faab8f78ebe3bf4f761e1b77efbc47b5/wrapt-1.17.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:373342dd05b1d07d752cecbec0c41817231f29f3a89aa8b8843f7b95992ed0c7", size = 86945, upload-time = "2025-08-12T05:52:21.581Z" }, - { url = "https://files.pythonhosted.org/packages/d3/bd/4e70162ce398462a467bc09e768bee112f1412e563620adc353de9055d33/wrapt-1.17.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d40770d7c0fd5cbed9d84b2c3f2e156431a12c9a37dc6284060fb4bec0b7ffd4", size = 86857, upload-time = "2025-08-12T05:52:43.043Z" }, - { url = "https://files.pythonhosted.org/packages/2b/b8/da8560695e9284810b8d3df8a19396a6e40e7518059584a1a394a2b35e0a/wrapt-1.17.3-cp314-cp314-win32.whl", hash = "sha256:fbd3c8319de8e1dc79d346929cd71d523622da527cca14e0c1d257e31c2b8b10", size = 37178, upload-time = "2025-08-12T05:53:12.605Z" }, - { url = "https://files.pythonhosted.org/packages/db/c8/b71eeb192c440d67a5a0449aaee2310a1a1e8eca41676046f99ed2487e9f/wrapt-1.17.3-cp314-cp314-win_amd64.whl", hash = "sha256:e1a4120ae5705f673727d3253de3ed0e016f7cd78dc463db1b31e2463e1f3cf6", size = 39310, upload-time = "2025-08-12T05:53:11.106Z" }, - { url = "https://files.pythonhosted.org/packages/45/20/2cda20fd4865fa40f86f6c46ed37a2a8356a7a2fde0773269311f2af56c7/wrapt-1.17.3-cp314-cp314-win_arm64.whl", hash = "sha256:507553480670cab08a800b9463bdb881b2edeed77dc677b0a5915e6106e91a58", size = 37266, upload-time = "2025-08-12T05:52:56.531Z" }, - { url = "https://files.pythonhosted.org/packages/77/ed/dd5cf21aec36c80443c6f900449260b80e2a65cf963668eaef3b9accce36/wrapt-1.17.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ed7c635ae45cfbc1a7371f708727bf74690daedc49b4dba310590ca0bd28aa8a", size = 56544, upload-time = "2025-08-12T05:51:51.109Z" }, - { url = "https://files.pythonhosted.org/packages/8d/96/450c651cc753877ad100c7949ab4d2e2ecc4d97157e00fa8f45df682456a/wrapt-1.17.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:249f88ed15503f6492a71f01442abddd73856a0032ae860de6d75ca62eed8067", size = 40283, upload-time = "2025-08-12T05:51:39.912Z" }, - { url = "https://files.pythonhosted.org/packages/d1/86/2fcad95994d9b572db57632acb6f900695a648c3e063f2cd344b3f5c5a37/wrapt-1.17.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a03a38adec8066d5a37bea22f2ba6bbf39fcdefbe2d91419ab864c3fb515454", size = 40366, upload-time = "2025-08-12T05:52:00.693Z" }, - { url = "https://files.pythonhosted.org/packages/64/0e/f4472f2fdde2d4617975144311f8800ef73677a159be7fe61fa50997d6c0/wrapt-1.17.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d4478d72eb61c36e5b446e375bbc49ed002430d17cdec3cecb36993398e1a9e", size = 108571, upload-time = "2025-08-12T05:52:44.521Z" }, - { url = "https://files.pythonhosted.org/packages/cc/01/9b85a99996b0a97c8a17484684f206cbb6ba73c1ce6890ac668bcf3838fb/wrapt-1.17.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223db574bb38637e8230eb14b185565023ab624474df94d2af18f1cdb625216f", size = 113094, upload-time = "2025-08-12T05:52:22.618Z" }, - { url = "https://files.pythonhosted.org/packages/25/02/78926c1efddcc7b3aa0bc3d6b33a822f7d898059f7cd9ace8c8318e559ef/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e405adefb53a435f01efa7ccdec012c016b5a1d3f35459990afc39b6be4d5056", size = 110659, upload-time = "2025-08-12T05:52:24.057Z" }, - { url = "https://files.pythonhosted.org/packages/dc/ee/c414501ad518ac3e6fe184753632fe5e5ecacdcf0effc23f31c1e4f7bfcf/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:88547535b787a6c9ce4086917b6e1d291aa8ed914fdd3a838b3539dc95c12804", size = 106946, upload-time = "2025-08-12T05:52:45.976Z" }, - { url = "https://files.pythonhosted.org/packages/be/44/a1bd64b723d13bb151d6cc91b986146a1952385e0392a78567e12149c7b4/wrapt-1.17.3-cp314-cp314t-win32.whl", hash = "sha256:41b1d2bc74c2cac6f9074df52b2efbef2b30bdfe5f40cb78f8ca22963bc62977", size = 38717, upload-time = "2025-08-12T05:53:15.214Z" }, - { url = "https://files.pythonhosted.org/packages/79/d9/7cfd5a312760ac4dd8bf0184a6ee9e43c33e47f3dadc303032ce012b8fa3/wrapt-1.17.3-cp314-cp314t-win_amd64.whl", hash = "sha256:73d496de46cd2cdbdbcce4ae4bcdb4afb6a11234a1df9c085249d55166b95116", size = 41334, upload-time = "2025-08-12T05:53:14.178Z" }, - { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, + { url = "https://files.pythonhosted.org/packages/5f/ac/4370bde262c0e633e6c4f0e56d55095710024cf9a5cecc20c59a10de483c/wrapt-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dd57607acc85678925940bd5df0385ff8332083a32fa8d7a43f8767f4997263c", size = 80321, upload-time = "2026-05-22T14:47:43.996Z" }, + { url = "https://files.pythonhosted.org/packages/eb/79/b8ff3a61e71babf58a8cf4c0d63358e8bad383e15bf7f35e62d2f6b6e4a4/wrapt-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1ae574d65c9fa8e86f64f6a7c2668f9fcd507b183e0e577619f504b883cb0a6c", size = 81216, upload-time = "2026-05-22T14:47:45.243Z" }, + { url = "https://files.pythonhosted.org/packages/6e/fd/c0cac1f77c9c4f6fe58a920ca632ce379bb8be928720e11e8d73de28a5e9/wrapt-2.2.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9a04c28c10ba7fd12842b109d2edb0678872a2fe65277ca4ff06a0d61edee245", size = 159208, upload-time = "2026-05-22T14:47:47.176Z" }, + { url = "https://files.pythonhosted.org/packages/d9/4f/744132a7b2fbefa6b81118ec5942eca5fc2e9a129f9055a0c5e46885a549/wrapt-2.2.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3e2f02472a1cbbf3884b365714a810b5947134a95ad6952b554cb8cce9d492b0", size = 160322, upload-time = "2026-05-22T14:47:49.04Z" }, + { url = "https://files.pythonhosted.org/packages/d6/95/b7cd9a22a06cf93e6482904ee6afc956248983553593fd1009296d1b3b31/wrapt-2.2.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac2745950b2bff80219c15ebf2fa9d8427eba7e249739f97e55c9d169e47e9e1", size = 153243, upload-time = "2026-05-22T14:47:50.386Z" }, + { url = "https://files.pythonhosted.org/packages/4c/4a/eb79423192015f46f0db2872e7e04a3dde8d359b83411e8959e7c9287eaa/wrapt-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:67a97e5b6c457f0cd3cfc19ebb2d84463e60c3ece754cc831e4281a3ca29bb18", size = 159231, upload-time = "2026-05-22T14:47:51.753Z" }, + { url = "https://files.pythonhosted.org/packages/ec/dc/435015b58ce33c6fc4104158fa91ddb0e809ab03a5751fb7465d1d461456/wrapt-2.2.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:c803a3d331796255af51ba2c79ed0ac8275865b516c09e61f248d1e7aff31ce9", size = 152351, upload-time = "2026-05-22T14:47:53.214Z" }, + { url = "https://files.pythonhosted.org/packages/77/ac/5d203f98df8fd136b95c5227139aea02d34505e18baf812d0c005df61963/wrapt-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9b984d1eb252145d6302c1dbd5e87fc6d404d45531447c84eadec04bf1fcb027", size = 158347, upload-time = "2026-05-22T14:47:54.982Z" }, + { url = "https://files.pythonhosted.org/packages/52/2f/a92427dbdc74e54c1674abbed27e61b2cb5e7a94441b8c1270c70671d928/wrapt-2.2.1-cp311-cp311-win32.whl", hash = "sha256:8a983a603a18c8708f024f7f6991b2e66159219abbf894634c5056243c55f3cd", size = 77562, upload-time = "2026-05-22T14:47:56.275Z" }, + { url = "https://files.pythonhosted.org/packages/c8/56/987b9c13b3e1c1a3c6de71284076f996b79caec90e75a87c044a40c23db9/wrapt-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:9c210a6994b21aa9b29e81c8d11560e8fdab54c117e9cff37870d0a27bde1343", size = 80616, upload-time = "2026-05-22T14:47:57.854Z" }, + { url = "https://files.pythonhosted.org/packages/7e/25/d01f560888d99d94a959c85533de349ce68d71ace3f2591d6ea8f632cfed/wrapt-2.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:401229e9d63ca09f9b8891ecf83798d26c11bbb445d11ed9f1836b6d4585b38a", size = 79025, upload-time = "2026-05-22T14:47:59.089Z" }, + { url = "https://files.pythonhosted.org/packages/89/0c/bfae7b9401583b6d05938cd16dedc43857d96da2f8a3d50d78cc515bf6ff/wrapt-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3ffad790d9d11d8ecf9f17c4bb671a5b4089e4d8b575c46c5129597f41f836b0", size = 81021, upload-time = "2026-05-22T14:48:00.313Z" }, + { url = "https://files.pythonhosted.org/packages/26/58/80f6a6599f933f4caecc1cb3ee88a04faf81e8b9bddbd6109c688dd63e0f/wrapt-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:628f5220c7a904d5fc78f7075c8d7871433eb6d035c94728a22fdf85f193d2a8", size = 81692, upload-time = "2026-05-22T14:48:01.49Z" }, + { url = "https://files.pythonhosted.org/packages/17/93/fb357cc7847c58a8ae790be718903afa81a28d23e642c843dc4129e8a0b2/wrapt-2.2.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:61acce4257a9883669703c525447c5b4c392edf0f987ae77ec32668440158f0e", size = 169364, upload-time = "2026-05-22T14:48:02.791Z" }, + { url = "https://files.pythonhosted.org/packages/aa/0b/76b601ee309a8bd556af0eecb184394c20b3c49aa9c8e085aa1ffacc2568/wrapt-2.2.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:727ab4244622cd6ad2390f322642090c877d2e83a608d2653a7643ae5368d926", size = 171079, upload-time = "2026-05-22T14:48:04.22Z" }, + { url = "https://files.pythonhosted.org/packages/cd/87/ee3f32d5658e3e26d3e0e457922b47a36dd3bfbdfee7f97bb3e802344a66/wrapt-2.2.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03df9ebed4c73ab93fa8c07e3d41d818dfca1852b15731a3de59457b27814624", size = 160205, upload-time = "2026-05-22T14:48:05.553Z" }, + { url = "https://files.pythonhosted.org/packages/b1/d0/ae2fd64277a67f5d7bffcf2d05eea1e476263fb2a072baf0b0129ab85984/wrapt-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0d9ff006f420b2ec8296aa56ade43ea7da3e997e85769f0aafc5e0661aacb710", size = 168922, upload-time = "2026-05-22T14:48:07.132Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f3/2d541a060c5bbafb9400bca4917e4d78bfd1f239f404782c86831a8f6b29/wrapt-2.2.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:844c858fc3bb7eacc0ba8efa904935d16aac6a4470948ad1e7e55c9f5a2a665f", size = 158388, upload-time = "2026-05-22T14:48:08.629Z" }, + { url = "https://files.pythonhosted.org/packages/1d/68/8d92c8800c57e93cb116ae9e9d6cbafc34fade5ee9f9107b6f203fb4dc35/wrapt-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:87bacdaf225117a342a20d9c03438d701c02112f6e3f351ce9b7f32354f14797", size = 167682, upload-time = "2026-05-22T14:48:10.042Z" }, + { url = "https://files.pythonhosted.org/packages/30/72/83ea3790ea352439442349388e29ff07b76e0686265f9088bbb505d1608d/wrapt-2.2.1-cp312-cp312-win32.whl", hash = "sha256:2f8c90c8afde51969487be4e1343ae049b268854877d415c2510baf833775052", size = 77857, upload-time = "2026-05-22T14:48:11.782Z" }, + { url = "https://files.pythonhosted.org/packages/ef/cb/99450668dd3502d62a54a1c8aa56e44f34cb8c1261b381cfe2e7926c3b75/wrapt-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ce32763ac31ce94fe9aada947e479b1975012bff166da409b4b9e4e376cf7e5", size = 80825, upload-time = "2026-05-22T14:48:13.046Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3a/87512881be64e743f9ee4c66f4cbe8e884974bef2a5989af71f999653ac7/wrapt-2.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d1b4d0e0c2119587a31f5c029abd547e0c81d93b89d394566fe1588659eb579", size = 79087, upload-time = "2026-05-22T14:48:14.323Z" }, + { url = "https://files.pythonhosted.org/packages/88/d1/a1b08f8f4fac8cbb156fa51cf64ee2c7f7f74f9875ba3cf70b3c58368694/wrapt-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d2beb1c7cab10603aecdc42f8edd6ff013f9a32e4543474e38e6b77ce9975aeb", size = 80831, upload-time = "2026-05-22T14:48:15.598Z" }, + { url = "https://files.pythonhosted.org/packages/54/ce/57890814991446a845e09b3445ce8b694f27eb0577004f2c2a36a9772ed4/wrapt-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e0cb7e4dd71f4c32e5e84843cd3c4cd65dda034314004bbe1d7f99af2426ab80", size = 81375, upload-time = "2026-05-22T14:48:17.071Z" }, + { url = "https://files.pythonhosted.org/packages/38/65/08d7a6c76ac4493bdb668205ee9c1de1bd5daca61717c3e9aa49b4c01499/wrapt-2.2.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:95821352042722cd9f1108874579a47989d0a7e12a37d87d2fc4af20fd99ab8a", size = 167417, upload-time = "2026-05-22T14:48:18.303Z" }, + { url = "https://files.pythonhosted.org/packages/62/ce/f1ccbee7a1bfe5cdc6b3da6bab4b45713d628b9294da32a39f563d648140/wrapt-2.2.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:abd621552ede77c4c69be7fac44ba911225b0c812b6ba604e5964cf98085b474", size = 166948, upload-time = "2026-05-22T14:48:19.768Z" }, + { url = "https://files.pythonhosted.org/packages/86/2a/f85d48d1cd4869aee6704028d257d740a47c1c467b457ce396b4b5b55d07/wrapt-2.2.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e3677c7146ce694874941ba82b57092cc4875445aadf29d72807351023105143", size = 158148, upload-time = "2026-05-22T14:48:21.96Z" }, + { url = "https://files.pythonhosted.org/packages/fe/5c/93939ad11d4a12358ab1aab219a2ef5efa5612e0db6b9fc65af8af1a891b/wrapt-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9a5934eaea872e17936b5f45501eba5ab0bce9a74122e172b663d7c28c459c4a", size = 165905, upload-time = "2026-05-22T14:48:23.373Z" }, + { url = "https://files.pythonhosted.org/packages/e0/22/b8c2aa89862ff58605934d7abf4b70e6a5a1c33df96656f49035ccdf1c8a/wrapt-2.2.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f5b9daf6b629fce418e0cc3dd0436eac045188fa35deadb7a7f3941d5b8203f9", size = 156712, upload-time = "2026-05-22T14:48:24.767Z" }, + { url = "https://files.pythonhosted.org/packages/5d/78/bf00a7b02239c12bb02ddcc3c0b971bfcc36e578c5a44f1ccfef5b458545/wrapt-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f53ac9f3ef573326d009ed809beff4efcac6451931c2b8132586da4b9e53ff31", size = 166560, upload-time = "2026-05-22T14:48:26.83Z" }, + { url = "https://files.pythonhosted.org/packages/fe/93/6390ca9c5b787683cef588d04f57c8d41b9a2323b5597a65f18638c90ef2/wrapt-2.2.1-cp313-cp313-win32.whl", hash = "sha256:1ffa9cfd4bdb581539951b14ae661ff20ed0c3599b3e911a131ee0ec5ac11337", size = 77817, upload-time = "2026-05-22T14:48:28.221Z" }, + { url = "https://files.pythonhosted.org/packages/97/73/ce10f0e71c0cfaa1a65faadb8efd4852028b3bb9ba28932b8889df769d38/wrapt-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:368eac1e20fd0bb03dd3cc42bf9887154c3861b60989389ccb5fac032617d215", size = 80736, upload-time = "2026-05-22T14:48:30.139Z" }, + { url = "https://files.pythonhosted.org/packages/c7/4c/89f4a6818fafbbd840330e4fa3873073e1bfc166133a64cac7f8fde7a5e3/wrapt-2.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:c754dafdf5aaf0b401b644a90a30046929a0dd1a536e0ff0ec959a59155d9c7f", size = 79099, upload-time = "2026-05-22T14:48:31.405Z" }, + { url = "https://files.pythonhosted.org/packages/bf/f2/9a8741c46f8c208ac0a45b25ba170bcb4fb72a2781d5fb97dbd7b6be73cb/wrapt-2.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ed928d0fda15fc0adc8d13305c8b3c0f2fba5b0669950c9e6d019d9162a3b3e8", size = 82802, upload-time = "2026-05-22T14:48:33.307Z" }, + { url = "https://files.pythonhosted.org/packages/9c/0d/e9c855716a3705eef1416456bdf062b60620726fdc59428ff670fc3c60dc/wrapt-2.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fafb4e739e43544d12cb4abd1605fd4683b6ca6a9ad682b7fd8f4d21973eafa8", size = 83329, upload-time = "2026-05-22T14:48:34.593Z" }, + { url = "https://files.pythonhosted.org/packages/3b/d6/a88f1c13112b7831adac75cea65d8310e0d696d570c8961844c90a57b865/wrapt-2.2.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:74d6a0c31472fe5d814917266b9f46495d7c61ed890af08b468acea92fb89a8d", size = 202937, upload-time = "2026-05-22T14:48:35.859Z" }, + { url = "https://files.pythonhosted.org/packages/42/65/e29d54aef06a4d898a5b8a25589a0b3769bde454f922fad8f6f89fbfb650/wrapt-2.2.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab5be648d5a0b86b7438864f8df3c705a65cef35a2fd3e5561e3e203167e0f27", size = 209997, upload-time = "2026-05-22T14:48:38.153Z" }, + { url = "https://files.pythonhosted.org/packages/2a/91/e4454263516cf0e12640912fbca9a83654e424f0a6ddb79f5cd7ce14bf33/wrapt-2.2.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9d8f204c8e3a8bf9ece17e0a83d137fd807440977f8a5e762d59306795011440", size = 194856, upload-time = "2026-05-22T14:48:39.69Z" }, + { url = "https://files.pythonhosted.org/packages/de/d0/fe0ee202286afdf4a7f77dd29f195703145764d572aec209c5086e57d924/wrapt-2.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d047f6498c973874ba08ac3f97c69a2c4b2211c8de6f4c205f75cb1c9522596e", size = 205654, upload-time = "2026-05-22T14:48:43.456Z" }, + { url = "https://files.pythonhosted.org/packages/23/b6/87d860dfc6460c246af70b1fd5c8b76df77571b42a493459423ded94fd7d/wrapt-2.2.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:7a4fdb9326aab4a5a477a1640e5ad786a8495901009d7e7b038371edd23a9d2b", size = 192206, upload-time = "2026-05-22T14:48:44.858Z" }, + { url = "https://files.pythonhosted.org/packages/df/46/3eea8cde077d985f239a38c0257087b8064fd9ee9b1a99e282d2c86da4ef/wrapt-2.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c8cc5094b08abeae52da9c73c8a32003623be691a5193df2f4e3eac3d557c394", size = 198428, upload-time = "2026-05-22T14:48:46.319Z" }, + { url = "https://files.pythonhosted.org/packages/18/dc/b927ee9c7fc67adc3a5658f246a0d275425eb840ba36e7b702e70f18bde8/wrapt-2.2.1-cp313-cp313t-win32.whl", hash = "sha256:9907a4402ab6db12b7077a0ea5d7a4d028ecb22c8eee2b53527080d347cd1562", size = 79448, upload-time = "2026-05-22T14:48:47.901Z" }, + { url = "https://files.pythonhosted.org/packages/ec/b3/fd30b473fe498c70e6b9a5f328b8d3fbaf1b8c3c481465f59724bba8eb70/wrapt-2.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:5590d63f5243251641cf543009b4c9314a79d0598fdb8a8e4cfc918494536c53", size = 83021, upload-time = "2026-05-22T14:48:49.201Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f3/96c39153a8737a6e9aa85adef254ac4195bea3f2d24efc60472ccc3c9e2e/wrapt-2.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:c318a64b53d97b841d7b5e637517e50a27be64bc695128422953d4b21710954e", size = 80295, upload-time = "2026-05-22T14:48:50.479Z" }, + { url = "https://files.pythonhosted.org/packages/0a/a3/11d7f34ebbf3231bc907a3e6d5ee051b14d034c1bc7b65a97d5cc00516df/wrapt-2.2.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6f56a647e4eaf5f0ca40330fb070f566bdf9f7b0db89a1af20d71c28dcd7a0ab", size = 80879, upload-time = "2026-05-22T14:48:51.802Z" }, + { url = "https://files.pythonhosted.org/packages/13/3c/b74cfd984cef560b900fb1a727af20352d89e1f06bf2e1114dd3f00f5f5a/wrapt-2.2.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:64b7deeda4b70408e382328d8bbe52a256fe9bc63ae3db86d804608367e5422c", size = 81462, upload-time = "2026-05-22T14:48:53.18Z" }, + { url = "https://files.pythonhosted.org/packages/15/a3/7c8f704b8dc07dfe0a5d01c2edbfd88317aa8e5e3fa7c743eb7a085ae767/wrapt-2.2.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b9cf53ba90717db2e292401de290776c498d4bbfb0d4a559ca2895db8b9dcb5c", size = 167251, upload-time = "2026-05-22T14:48:54.562Z" }, + { url = "https://files.pythonhosted.org/packages/80/85/a34d1888d97247da6c2ff6118c3a721c73ed8cc4dd198c00208bb73b6f80/wrapt-2.2.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cf3638274ab9d9b724c9baa0b4c04e132cd6faefb78b4dd3dd1a02a4bdaad41e", size = 166316, upload-time = "2026-05-22T14:48:56.065Z" }, + { url = "https://files.pythonhosted.org/packages/e9/d7/72ffaeb01eebc704afe3fb99e840480f4bda45f0fa66e3381b6a39251c8f/wrapt-2.2.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aed9658797d0b45d6c49adcfc6b41f66e6f2d0c6de3ec79e16cf4b1855df240f", size = 157952, upload-time = "2026-05-22T14:48:57.924Z" }, + { url = "https://files.pythonhosted.org/packages/24/5b/36f5d6b024e4edfdd90b140742d11ebcf7836daf5c9daf326c55c24db412/wrapt-2.2.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1d676ee388bc42a04d56dd7deb5605244dac2e35cc2fadbb43c9fa25bbd93508", size = 166130, upload-time = "2026-05-22T14:48:59.384Z" }, + { url = "https://files.pythonhosted.org/packages/81/06/9296d9e97bfdef5483dfcc859d57b095b257144b2bc5300ab521e06f4bc7/wrapt-2.2.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e395f7bc31851ef9b612050368cb446e9bc14cd7454b025018980349caf25ae5", size = 156604, upload-time = "2026-05-22T14:49:00.921Z" }, + { url = "https://files.pythonhosted.org/packages/53/37/16953929ed6776175720e58fc966e779926d8d71e2c7b2273230590ca71f/wrapt-2.2.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5f1845c2a8cc1180ccccfa45785dd06f562730d19ef75be180334254012b6283", size = 166007, upload-time = "2026-05-22T14:49:02.332Z" }, + { url = "https://files.pythonhosted.org/packages/b9/73/20ee58c0612dae7c31131a7095345812ed2c7b389019e175f68cde34e5b4/wrapt-2.2.1-cp314-cp314-win32.whl", hash = "sha256:436addbc4bb4fc0a88c702577f51195d7d73683a7f3e0e5b253d8404d7847243", size = 78327, upload-time = "2026-05-22T14:49:03.722Z" }, + { url = "https://files.pythonhosted.org/packages/22/b3/ef7c3295d02e0448a71c639a36a057f46d524d057c9486291a7a3039e65c/wrapt-2.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:50972a1d974ea07725a7f6b1cec5f8759008afd030a0024843ebe7d52de47f2b", size = 81144, upload-time = "2026-05-22T14:49:05.093Z" }, + { url = "https://files.pythonhosted.org/packages/ac/dc/7bdf336953f99f4ceb0a584bb8870e42c8f26f93ea10c87834dad62f1668/wrapt-2.2.1-cp314-cp314-win_arm64.whl", hash = "sha256:1c9934ea5d92957e3cd0adbc0845539dccfd62710ebe16195a8c66c53954db36", size = 79569, upload-time = "2026-05-22T14:49:06.413Z" }, + { url = "https://files.pythonhosted.org/packages/6a/6d/6dfae80150ff1919c356d1dd528f049bcdfaae29b4d284bc957e022caef4/wrapt-2.2.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:17de18fc12cea55b8a9587314cb830573e37fb33b247a7515696350863714188", size = 82892, upload-time = "2026-05-22T14:49:07.925Z" }, + { url = "https://files.pythonhosted.org/packages/82/7b/4e34766a7d7804ffce9e71befe47e9b3225dc350c49c94493c4ab39fd3a5/wrapt-2.2.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a9dec1aca52dddde7df94818310fa2fe79739c8f385b2014c4cb1035f5508199", size = 83333, upload-time = "2026-05-22T14:49:09.257Z" }, + { url = "https://files.pythonhosted.org/packages/9d/57/0b34db3e8de44ccfece62d7b337abd1631dd810f5adc5f3db571727836b5/wrapt-2.2.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:69f2e9244542cb34dd59c7f073445b9e54ad9f3fce8d93606c368a1b499fc413", size = 202899, upload-time = "2026-05-22T14:49:10.572Z" }, + { url = "https://files.pythonhosted.org/packages/e5/45/ac0c459f154b99d92789a6cba7ca727185b83513b986f8ec7fe2aacddcbf/wrapt-2.2.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d83966dc7f4f45e8b97b5933685ac2e6e67fc0e19246ea314bceb9a8970c956", size = 209986, upload-time = "2026-05-22T14:49:12.229Z" }, + { url = "https://files.pythonhosted.org/packages/b7/e4/77e37ff33ad018fa81ade52c25fa327b80b56f81d734279a63614fcb4cbc/wrapt-2.2.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:78b0aa6bfb7be8deed0ab23e7aa028cc5210c29bc2d32a04d52b50e517a7307e", size = 194893, upload-time = "2026-05-22T14:49:14.139Z" }, + { url = "https://files.pythonhosted.org/packages/dd/9d/7ea651d1ab032fc5fa222fbec91d0f8a1397f6ae04ebb93fa7219aa921d7/wrapt-2.2.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:05d5cb74d1b232ec8cfa130a8f900708699ff2491d97b8f85a4cdc5996294b85", size = 205636, upload-time = "2026-05-22T14:49:15.714Z" }, + { url = "https://files.pythonhosted.org/packages/09/af/8e88031a701275b9085c54e64bc88c0b1cd55c77eadd400691c371cd76c4/wrapt-2.2.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f6518b94edb9150452e9aba08027d4cc293433753ec1fbefb4629a21cbc74181", size = 192267, upload-time = "2026-05-22T14:49:17.283Z" }, + { url = "https://files.pythonhosted.org/packages/bf/a8/e657ca876b06710194f243d81c4b0896ade646e244bdbec2d87c8c56a8bd/wrapt-2.2.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ed55af48b3eb28f43228ca2306788892bcb629eb2b5c4876e2a3659872c2f17a", size = 198378, upload-time = "2026-05-22T14:49:18.785Z" }, + { url = "https://files.pythonhosted.org/packages/c8/59/822efe4ea722a3961331bfa35b7d90937790d2c20f0616de1997ccc3aebd/wrapt-2.2.1-cp314-cp314t-win32.whl", hash = "sha256:2e08688ab16525897da6589d56d0aebaf417bbe91c2d8e3b96203b1efa596e85", size = 80226, upload-time = "2026-05-22T14:49:20.264Z" }, + { url = "https://files.pythonhosted.org/packages/ab/31/2a7dc5f6abb2fca0b6e1610e120419f603650aceb4f1d3ac4cae0354e162/wrapt-2.2.1-cp314-cp314t-win_amd64.whl", hash = "sha256:fd0135d34387f5fd087d9be368ea77ea89cf2451dc1cd1c622d35021bcb3ab50", size = 83835, upload-time = "2026-05-22T14:49:21.634Z" }, + { url = "https://files.pythonhosted.org/packages/9f/c0/782b86e28d1ceebeb74cccea12d2cd3d2ba0bd68e3dec20b1bc5873f6127/wrapt-2.2.1-cp314-cp314t-win_arm64.whl", hash = "sha256:f70db64e8266d7c45d3b735f2e08eeb434b5e03da9a479ae42b2e2e486a21a00", size = 80722, upload-time = "2026-05-22T14:49:23.59Z" }, + { url = "https://files.pythonhosted.org/packages/53/46/29ac9daf11a86c22a8c38cd9236c62928ccae83f7ceb06bd3b0467cf9d05/wrapt-2.2.1-py3-none-any.whl", hash = "sha256:3aafea2975caef8ca49400640dde02cc7426e798f24870ed01f490bc3cffd32f", size = 61000, upload-time = "2026-05-22T14:49:41.593Z" }, ] From 2ca7d8d9871393e32837fb159e565cdb1fd0f9d3 Mon Sep 17 00:00:00 2001 From: Bracey Summers Date: Sat, 20 Jun 2026 11:29:42 -0500 Subject: [PATCH 4/8] add non-interactive template and migrate flows; harden deps freeze; bump deps Template - update: honor each template.yaml `template_files` as template-owned and reconcile them against the template hash on every update (restoring local edits), independent of the manifest `last_commit` - update: add `--no-prompt` for a fully non-interactive run; modified non-template files are preserved and reported, never clobbered - template_cli: add `_collect_template_file_keys` (union `template_files` across the parent chain; map `gitignore` -> `.gitignore`) - template_cli: fix `_build_merged_template` so the renamed `.gitignore` manifest entry is computed from the file actually written (manifest/content consistency) - planner: `build()` gains `force_keys`; `apply()` gains `no_prompt` and returns the preserved-file list Migrate - migrate: replace `--update-code` with dry-run-by-default `--apply/--no-apply` (keeps `--update-code` as an alias); preview shows changes without writing - migrate: add `--prompt/--no-prompt` (`--apply --no-prompt` applies all changes unattended) and a scanned/proposed/applied/changed run summary - migrate_cli: skip any dot-directory (.git, .venv, .claude) and skip-dir during the walk; correct the "tcex migrate" exception label Deps - deps_cli: run `pip freeze` via uv/python with `shell=False` list args instead of `shell=True` (B603-justified), removing the shell-injection surface - deps_cli: hard-fail instead of writing an empty requirements.lock; always write a trailing newline Tests - template: add test_template_files_force.py and test_gitignore_merge.py; update test_template_commands.py for the new template-ownership contract - template: refresh tcex-app-templates-v2.zip fixture to add `gitignore` to the _app_common `template_files` (match upstream v2) - migrate: add tests/migrate/test_migrate_cli.py - deps: update test_tcex_deps.py; pin `tcex>=4.0.9` in app_1 requirements Build / Config - bump version 1.0.6-dev2 -> 1.0.6-dev3; refresh uv.lock dependency pins - gitignore: ignore the extracted `tcex-app-templates-2` template directory - settings: clear the `permissions.allow` list - scripts: mark enforce_*.sh executable (100644 -> 100755) --- .claude/scripts/enforce_no_dynamic_paths.sh | 0 .claude/scripts/enforce_pinned_paths.sh | 0 .claude/scripts/enforce_us_spelling.sh | 0 .claude/settings.json | 46 +- .gitignore | 1 + pyproject.toml | 2 +- release_notes.md | 3 + tcex_cli/__metadata__.py | 2 +- tcex_cli/cli/deps/deps_cli.py | 34 +- tcex_cli/cli/migrate/migrate.py | 31 +- tcex_cli/cli/migrate/migrate_cli.py | 110 +++- tcex_cli/cli/template/planner.py | 71 ++- tcex_cli/cli/template/template_cli.py | 90 ++- tcex_cli/cli/template/update.py | 17 +- tests/app/tcpb/app_1/requirements.txt | 2 +- tests/deps/test_tcex_deps.py | 17 +- tests/migrate/__init__.py | 1 + tests/migrate/test_migrate_cli.py | 423 ++++++++++++++ .../fixtures/tcex-app-templates-v2.zip | Bin 104696 -> 104702 bytes tests/template/test_gitignore_merge.py | 231 ++++++++ tests/template/test_template_commands.py | 108 ++-- tests/template/test_template_files_force.py | 533 ++++++++++++++++++ uv.lock | 289 +++++----- 23 files changed, 1704 insertions(+), 307 deletions(-) mode change 100644 => 100755 .claude/scripts/enforce_no_dynamic_paths.sh mode change 100644 => 100755 .claude/scripts/enforce_pinned_paths.sh mode change 100644 => 100755 .claude/scripts/enforce_us_spelling.sh create mode 100644 tests/migrate/__init__.py create mode 100644 tests/migrate/test_migrate_cli.py create mode 100644 tests/template/test_gitignore_merge.py create mode 100644 tests/template/test_template_files_force.py diff --git a/.claude/scripts/enforce_no_dynamic_paths.sh b/.claude/scripts/enforce_no_dynamic_paths.sh old mode 100644 new mode 100755 diff --git a/.claude/scripts/enforce_pinned_paths.sh b/.claude/scripts/enforce_pinned_paths.sh old mode 100644 new mode 100755 diff --git a/.claude/scripts/enforce_us_spelling.sh b/.claude/scripts/enforce_us_spelling.sh old mode 100644 new mode 100755 diff --git a/.claude/settings.json b/.claude/settings.json index 3165431..d3fbf29 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -38,51 +38,7 @@ ] }, "permissions": { - "allow": [ - "Bash(/opt/homebrew/opt/gawk/libexec/gnubin/awk *)", - "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/basename *)", - "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/cat *)", - "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/chmod *)", - "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/cp *)", - "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/cut *)", - "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/date *)", - "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/dirname *)", - "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/du *)", - "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/echo *)", - "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/env *)", - "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/head *)", - "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/ln *)", - "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/ls *)", - "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/mkdir *)", - "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/mv *)", - "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/printf *)", - "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/rm *)", - "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/sort *)", - "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/stat *)", - "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/tail *)", - "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/tee *)", - "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/touch *)", - "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/tr *)", - "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/uniq *)", - "Bash(/opt/homebrew/opt/coreutils/libexec/gnubin/wc *)", - "Bash(/opt/homebrew/opt/grep/libexec/gnubin/grep *)", - "Bash(/opt/homebrew/opt/gnu-sed/libexec/gnubin/sed *)", - "Bash(/opt/homebrew/opt/findutils/libexec/gnubin/find *)", - "Bash(/opt/homebrew/opt/findutils/libexec/gnubin/xargs *)", - "Bash(/opt/homebrew/opt/gnu-tar/libexec/gnubin/tar *)", - "Bash(/opt/homebrew/opt/diffutils/bin/diff *)", - "Bash(/opt/homebrew/bin/gzip *)", - "Bash(/opt/homebrew/bin/gunzip *)", - "Bash(/opt/homebrew/bin/zcat *)", - "Bash(/opt/homebrew/bin/jq *)", - "Bash(/opt/homebrew/bin/wget *)", - "Bash(/usr/bin/curl *)", - "Bash(/usr/bin/file *)", - "mcp__atlassian__*", - "mcp__github__*", - "mcp__ide__*", - "mcp__plugin_slack_slack__*" - ], + "allow": [], "ask": [], "deny": [ "Bash(git am:*)", diff --git a/.gitignore b/.gitignore index 93fae9f..6daa6fd 100644 --- a/.gitignore +++ b/.gitignore @@ -49,6 +49,7 @@ temp *.log local-* tests/reports/* +tcex-app-templates-2 #------------------------------------------------- # Other Nonsense diff --git a/pyproject.toml b/pyproject.toml index 0bf2ed8..b668e40 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -111,7 +111,7 @@ commit = false # Additional arguments to pass to the git commit command commit_args = "--no-verify" # The current version of the project -current_version = "1.0.6-dev2" +current_version = "1.0.6-dev3" # The commit message template used when committing version bumps message = "Version updated from {current_version} to {new_version}" # Regular expression for parsing version strings diff --git a/release_notes.md b/release_notes.md index aac3c95..740b6d0 100644 --- a/release_notes.md +++ b/release_notes.md @@ -10,6 +10,9 @@ - APP-4920 - [Run] - Updated run command to support system apps - APP-5036 - [Message-Broker] Updated message broker connection to not set tls_version - APP-5055 - [Message-Broker] Updated Paho MQTT library and message broker reconnect logic +- APP-5299 - [Template] Added “--no-prompt” and force-overwrite of template-owned files to the “update” command +- APP-5300 - [Migrate] Updated “migrate” to preview by default with “--apply” and added “--no-prompt” for unattended runs +- APP-5301 - [Deps] Hardened dependency lock to avoid shell execution and never write an empty “requirements.lock” ## 1.0.5 diff --git a/tcex_cli/__metadata__.py b/tcex_cli/__metadata__.py index df28a2f..9259bc2 100644 --- a/tcex_cli/__metadata__.py +++ b/tcex_cli/__metadata__.py @@ -1,4 +1,4 @@ """TcEx Framework metadata.""" __license__ = 'Apache-2.0' -__version__ = '1.0.6-dev2' +__version__ = '1.0.6-dev3' diff --git a/tcex_cli/cli/deps/deps_cli.py b/tcex_cli/cli/deps/deps_cli.py index 35f6d80..308430d 100644 --- a/tcex_cli/cli/deps/deps_cli.py +++ b/tcex_cli/cli/deps/deps_cli.py @@ -189,8 +189,7 @@ def create_requirements_lock(self, contents: str, requirements_file: Path): relative_path = requirements_file.relative_to(self.app_path) relative_path = f'[{self.accent}]{relative_path}[/{self.accent}]' self.output.append(KeyValueModel(key='Lock File Created', value=str(relative_path))) - fh.write(contents) - fh.write('') + fh.write(f'{contents}\n') def download_deps(self, exe_command: list[str]): """Download the dependencies (run pip).""" @@ -348,22 +347,33 @@ def requirements_fqfn_tests(self) -> Path: def requirements_lock_contents(self, deps_dir: Path) -> str: """Return the Python packages for the provided directory.""" - cmd = f'pip freeze --path "{deps_dir}"' - self.log.debug(f'event=get-requirements-lock-data, cmd={cmd}') + uv_executable = shutil.which('uv') + if uv_executable and self.is_executable(Path(uv_executable)): + cmd = [uv_executable, 'pip', 'freeze', '--path', str(deps_dir)] + else: + cmd = [str(self.python_executable), '-m', 'pip', 'freeze', '--path', str(deps_dir)] + + self.log.debug(f'event=get-requirements-lock-data, cmd={" ".join(cmd)}') try: - output = subprocess.run( # noqa: PLW1510 - cmd, - shell=True, - capture_output=True, # nosec - ) + # B603 justification: args are program-resolved (uv/python_executable), not shell-parsed + output = subprocess.run(cmd, shell=False, capture_output=True, check=False) # nosec B603 except Exception: self.log.exception('event=pip-freeze') Render.panel.failure('Failure: could not get requirements lock data.') - if output.returncode != 0: - self.log.error(f'event=pip-freeze, stderr="{output.stderr}"') + contents = '\n'.join(sorted(output.stdout.decode('utf-8').splitlines())) + + # hard-fail rather than write an empty lock (never write an empty lock) + if output.returncode != 0 or not contents.strip(): + self.log.error( + f'event=pip-freeze, rc={output.returncode}, ' + f'stderr="{output.stderr.decode("utf-8")}"' + ) + Render.panel.failure( + 'Failure: dependency freeze produced no packages; requirements.lock not created.' + ) - return '\n'.join(sorted(output.stdout.decode('utf-8').splitlines())) + return contents @property def target_python_version(self) -> Version | None: diff --git a/tcex_cli/cli/migrate/migrate.py b/tcex_cli/cli/migrate/migrate.py index 5826c20..5ec11e5 100644 --- a/tcex_cli/cli/migrate/migrate.py +++ b/tcex_cli/cli/migrate/migrate.py @@ -20,15 +20,38 @@ def command( forward_ref: bool = typer.Option( default=True, help='If true, show typing forward lookup reference that require updates.' ), - update_code: bool = typer.Option(default=True, help='If true, apply code replacements.'), + apply: bool = typer.Option( + False, # noqa: FBT003 - typer requires the default as the first positional arg + '--apply/--no-apply', + '--update-code/--no-update-code', + help=('Write changes to disk. Without it, only a preview is shown (no files modified).'), + ), + prompt: bool = typer.Option( + default=True, + help=( + 'Confirm each change interactively (only applies with --apply); ' + 'use --no-prompt for unattended/agent runs.' + ), + ), ): - """Migrate App to TcEx 4 from TcEx 2/3.""" + """Migrate an App to TcEx 4 from TcEx 2/3. + + Walks every Python file under the current working directory (skipping dependency, + build, and dot-directories such as deps/, .venv/, and .claude/) and rewrites TcEx + 2/3 imports, method calls, and typing forward references to their TcEx 4 equivalents. + + By default the command only previews the proposed changes and writes nothing. Pass + --apply (alias --update-code) to write the changes to disk; each change is confirmed + interactively unless --no-prompt is also given. The unattended/agent path is + --apply --no-prompt, which applies every change without prompting. + """ cli = MigrateCli( forward_ref, - update_code, + apply, + prompt, ) try: cli.walk_code() except Exception as ex: - cli.log.exception('Failed to run "tcex deps" command.') + cli.log.exception('Failed to run "tcex migrate" command.') Render.panel.failure(f'Exception: {ex}') diff --git a/tcex_cli/cli/migrate/migrate_cli.py b/tcex_cli/cli/migrate/migrate_cli.py index fde5d29..c36b89b 100644 --- a/tcex_cli/cli/migrate/migrate_cli.py +++ b/tcex_cli/cli/migrate/migrate_cli.py @@ -21,12 +21,20 @@ class MigrateCli(CliABC): def __init__( self, forward_ref: bool, - update_code: bool, + apply: bool, + prompt: bool, ): """Initialize instance properties.""" super().__init__() self.forward_ref = forward_ref - self.update_code = update_code + self.apply = apply + self.prompt = prompt + + # summary counters + self._files_scanned = 0 + self._changes_proposed = 0 + self._changes_applied = 0 + self._files_changed = 0 def _replace_string(self, filename: Path, string: str, replacement: str): """Replace string in file.""" @@ -43,28 +51,40 @@ def _replace_string(self, filename: Path, string: str, replacement: str): 'New Line': f'{line_.replace(string, replacement)}', }, ) - response = Render.prompt.input( - 'Replace line:', - prompt_default=f' (Default: [{self.accent}]yes[/{self.accent}])', - ) - if response in ('', 'y', 'yes'): - line_ = line_.replace(string, replacement) - file_changed = True + self._changes_proposed += 1 + + # without --apply this is a preview only: no prompt, no change, no write + if self.apply is True: + accepted = ( + True + if not self.prompt + else Render.prompt.input( + 'Replace line:', + prompt_default=f' (Default: [{self.accent}]yes[/{self.accent}])', + ) + in ('', 'y', 'yes') + ) + if accepted: + line_ = line_.replace(string, replacement) + file_changed = True + self._changes_applied += 1 new_file.append(line_) - if file_changed is True: + # only write when --apply is set and the file actually changed + if self.apply is True and file_changed is True: with filename.open(mode='w', encoding='utf-8') as fh: fh.write('\n'.join(new_file) + '\n') + self._files_changed += 1 @cached_property - def _skip_directories(self): - return [ + def _skip_directories(self) -> set[str]: + return { '.history', '.venv', 'deps', 'deps_tests', 'target', - ] + } @cached_property def _skip_files(self): @@ -394,39 +414,50 @@ def run_update_code(self, filename: Path): 'New Line': f'{new_line}', }, ) - response = Render.prompt.input( - 'Replace line:', - prompt_default=f' (Default: [{self.accent}]yes[/{self.accent}])', - ) - if response in ('', 'y', 'yes'): - line_ = re.sub(pattern, data['replacement'], line_) - file_changed = True + self._changes_proposed += 1 + + # without --apply this is a preview only: no prompt, no change, no write + if self.apply is True: + accepted = ( + True + if not self.prompt + else Render.prompt.input( + 'Replace line:', + prompt_default=f' (Default: [{self.accent}]yes[/{self.accent}])', + ) + in ('', 'y', 'yes') + ) + if accepted: + line_ = re.sub(pattern, data['replacement'], line_) + file_changed = True + self._changes_applied += 1 new_file.append(line_) - if file_changed is True: + # only write when --apply is set and the file actually changed + if self.apply is True and file_changed is True: with filename.open(mode='w', encoding='utf-8') as fh: fh.write('\n'.join(new_file) + '\n') + self._files_changed += 1 def walk_code(self): """.""" for item in Path.cwd().rglob('*.py'): - # skip directories - parents = item.relative_to(Path.cwd()).parents - if len(parents) > 1: - parent_name = parents[-2].name - if parent_name in self._skip_directories: - continue + # skip directories: exclude any path whose directory components include a + # skip-directory name or any dot-directory (.git, .venv, .claude, etc.) + rel_parts = item.relative_to(Path.cwd()).parts[:-1] # directory parts only + if any(part in self._skip_directories or part.startswith('.') for part in rel_parts): + continue # skip files if item.name in self._skip_files: continue + self._files_scanned += 1 Render.panel.info(f'FILE: {item}') - # run simple regex replacements - if self.update_code is True: - self.run_update_code(item) + # run simple regex replacements (writes gated by --apply inside) + self.run_update_code(item) if self.forward_ref is True: with item.open(mode='r', encoding='utf-8') as fh: @@ -438,3 +469,22 @@ def walk_code(self): } parsed_code = ast.parse(code) self.parse_ast_body(item, parsed_code.body, imports) + + # render run summary; text adapts to preview vs apply mode + if self.apply is True: + summary = ( + f'Mode: apply (files written)\n' + f'Files scanned: {self._files_scanned}\n' + f'Changes proposed: {self._changes_proposed}\n' + f'Changes applied: {self._changes_applied}\n' + f'Files changed: {self._files_changed}' + ) + else: + summary = ( + f'Mode: preview (no files written)\n' + f'Files scanned: {self._files_scanned}\n' + f'Changes proposed: {self._changes_proposed}\n' + f'Run with --apply to write these changes.' + ) + + Render.panel.info(summary, title='Migrate Summary') diff --git a/tcex_cli/cli/template/planner.py b/tcex_cli/cli/template/planner.py index 5b4822f..8785bb0 100644 --- a/tcex_cli/cli/template/planner.py +++ b/tcex_cli/cli/template/planner.py @@ -211,6 +211,7 @@ def build( dest: Path, file_name: str = 'manifest.json', force=False, + force_keys: set[str] | None = None, ) -> Plan: """Build an update plan by comparing template and local manifests. @@ -233,10 +234,23 @@ def build( - If the local file is gone or unchanged from last-known hash, auto-remove (safe cleanup). - If the user modified the file, prompt before removing. + + ``force_keys`` is the set of project-relative keys declared as + template-owned (via each template.yaml ``template_files``). Any file + whose key is in this set is reconciled against the template hash + unconditionally — independent of ``last_commit`` / local-manifest state. + The local file is hashed against the template's ``sha256``: if it differs + (or the file is missing) it is routed to ``auto_update`` to restore/refresh + it from the template; if it already matches it is skipped. This check runs + before the ``last_commit`` short-circuit so a stale-but-matching manifest + cannot preempt the reconciliation. Keys not in the set keep the default + prompt-on-modify behavior. """ template_meta = self.manifest.load_json(temp_dest / file_name) local_meta = self.manifest.load_json(dest / file_name) + force_keys = force_keys or set() + plan = Plan() keys_in_template, removed_in_template = self.manifest.collect_keys( @@ -258,12 +272,25 @@ def build( # new file being tracked in the template if local_info is None: plan.template_new.append(value) - if project_path.exists(): + if project_path.exists() and key not in force_keys: plan.prompt_user.append(value) else: plan.auto_update.append(value) continue + # template-owned files are always reconciled against the template + # hash, independent of last_commit / local-manifest state — this + # runs before the last_commit short-circuit so a stale-but-matching + # manifest cannot preempt the reconciliation. + if key in force_keys: + current_hash = self.hasher.sha256_file(project_path) + if current_hash == template_info['sha256']: + plan.skip.append(value) + else: + # differs or missing — restore/refresh from template + plan.auto_update.append(value) + continue + # both sides have metadata and template hasn't changed this file if template_info['last_commit'] == local_info['last_commit']: plan.skip.append(value) @@ -288,8 +315,8 @@ def build( if current_hash is None: plan.auto_update.append(value) # already gone - elif current_hash == local_info['sha256']: - plan.auto_update.append(value) # unchanged from template — safe to remove + elif current_hash == local_info['sha256'] or key in force_keys: + plan.auto_update.append(value) # unchanged or template-owned — safe to remove else: plan.prompt_user.append(value) # user modified — ask first @@ -302,17 +329,29 @@ def apply( template_root: Path, project_root: Path, force: bool = False, + no_prompt: bool = False, prompt_fn: Callable[[str], str] = input, - ) -> None: + ) -> list[tuple]: """Apply the plan to the project directory. Auto-update files are copied/removed without prompting. Prompt-user files ask for confirmation via ``prompt_fn`` (injected for testability — defaults to ``input()``). + + Behavior for the ``prompt_user`` set: + - ``force`` → overwrite/remove without prompting. + - ``no_prompt`` (and not ``force``) → leave the file untouched on disk + (no prompt) and record it as preserved. + - otherwise → prompt via ``prompt_fn``. + + Returns the list of preserved ``(local, template)`` tuples (the + modified, non-template-owned files left untouched under ``no_prompt``); + an empty list for the interactive/force paths. """ auto_set = set(plan.auto_update) prompt_set = set(plan.prompt_user) removed_keys = {local for local, _template in plan.template_removed} + preserved: list[tuple] = [] # auto-update: copies and removals for local, template in auto_set: @@ -322,8 +361,19 @@ def apply( else: self.file_ops.copy_from_template(template_root, template, local_) - # prompt user: ask before overwriting or removing - if prompt_set and not force: + if prompt_set and force: + # force: overwrite or remove without prompting + for local, template in prompt_set: + local_ = project_root / local + if local in removed_keys: + self.file_ops.remove_file(local_) + else: + self.file_ops.copy_from_template(template_root, template, local_) + elif prompt_set and no_prompt: + # non-interactive: never clobber — preserve and report + preserved.extend(sorted(prompt_set)) + elif prompt_set: + # interactive: ask before overwriting or removing for local, template in sorted(prompt_set): local_ = project_root / local if local in removed_keys: @@ -338,10 +388,5 @@ def apply( ) if response == 'y': self.file_ops.copy_from_template(template_root, template, local_) - elif prompt_set and force: - for local, template in prompt_set: - local_ = project_root / local - if local in removed_keys: - self.file_ops.remove_file(local_) - else: - self.file_ops.copy_from_template(template_root, template, local_) + + return preserved diff --git a/tcex_cli/cli/template/template_cli.py b/tcex_cli/cli/template/template_cli.py index f0f85f2..d102271 100644 --- a/tcex_cli/cli/template/template_cli.py +++ b/tcex_cli/cli/template/template_cli.py @@ -175,6 +175,7 @@ def update( template_type: str | None = None, force: bool = False, app_builder: bool = False, + no_prompt: bool = False, ): """Update (or initialize) a project with the latest template files. @@ -184,11 +185,16 @@ def update( Steps: 1. Resolve template_name/type from args or fall back to tcex.json 2. Validate template_type and template_name against the cache - 3. Build a merged template directory (parent chain resolution) - 4. Migrate legacy .template_manifest.json if present - 5. Build an update plan via Planner.build() - 6. Apply the plan via Planner.apply() with user prompts - 7. Copy the merged manifest.json to the project root + 3. Collect the template-owned file keys (union of ``template_files``) + 4. Build a merged template directory (parent chain resolution) + 5. Migrate legacy .template_manifest.json if present + 6. Build an update plan via Planner.build() (template-owned files forced) + 7. Apply the plan via Planner.apply() with user prompts + 8. Copy the merged manifest.json to the project root + + When ``no_prompt`` is set, the run is fully non-interactive: template-owned + files are still overwritten, but locally-modified non-template files are + left untouched and reported for manual review (never clobbered). """ # resolve from tcex.json if not provided _template_name = template_name or self.app.tj.model.template_name @@ -222,6 +228,14 @@ def update( f'Available templates: {", ".join(sorted(available)) or "none"}' ) + # template-owned files (union of template_files across the parent chain) + # are always force-overwritten, even when the local copy was modified + force_keys = self._collect_template_file_keys( + cache_dir, + _template_name, # type: ignore[arg-type] + _template_type, # type: ignore[arg-type] + ) + merged_dir = self._build_merged_template( cache_dir, _template_name, @@ -235,20 +249,29 @@ def update( merged_dir, Path.cwd(), force=force, + force_keys=force_keys, ) Render.table.key_value('Plan Summary', plan.summary) def _prompt_fn(msg: str) -> str: return Render.prompt.ask(msg, choices=['y', 'N'], default='N') or 'N' - self.planner.apply( + preserved = self.planner.apply( plan, template_root=merged_dir, project_root=Path.cwd(), force=force, + no_prompt=no_prompt, prompt_fn=_prompt_fn, ) + # in non-interactive mode, surface the files left untouched for review + if no_prompt and preserved: + Render.table.key_value( + 'Preserved - Review Manually', + {local: 'modified locally - not overwritten' for local, _ in preserved}, + ) + # copy manifest.json to project root so future updates can compare merged_manifest = merged_dir / 'manifest.json' if merged_manifest.is_file(): @@ -283,6 +306,40 @@ def _ensure_tcex_json(self, cache_dir: Path, template_name: str, template_type: self.app.tj.model.template_type = template_type self.app.tj.write() + def _collect_template_file_keys( + self, cache_dir: Path, template_name: str, template_type: str + ) -> set[str]: + """Return the union of ``template_files`` across the resolved parent chain. + + These are the template-owned files (declared in each template.yaml's + ``template_files``) — files the App developer should not edit and that + ``update`` overwrites without prompting. Each entry is normalized to a + POSIX project-relative key so it matches the plan/manifest keys (bare + filenames pass through unchanged). The ``gitignore`` -> ``.gitignore`` + rename used in ``_build_merged_template`` is applied defensively so a + template that lists ``gitignore`` still maps to the on-disk key. + """ + keys: set[str] = set() + + parents = self.resolve_template_parents(cache_dir, template_name, template_type) + for parent_name in parents: + config = self.read_template_config(cache_dir, template_type, parent_name) + if config is None or not config.template_files: + continue + + for entry in config.template_files: + if not entry: + continue + # normalize to a POSIX project-relative key + key = Path(entry).as_posix() + # template repos store gitignore without the dot — match the + # rename applied during merge so the key lines up with disk + if key == 'gitignore': + key = '.gitignore' + keys.add(key) + + return keys + # ================================================================== # Cache Management # @@ -491,7 +548,8 @@ def _build_merged_template( # template repos store gitignore without the dot to avoid # GitHub ignoring the file — rename it for the project parts = list(rel.parts) - if parts[-1] == 'gitignore': + renamed_gitignore = parts[-1] == 'gitignore' + if renamed_gitignore: parts[-1] = '.gitignore' rel = Path(*parts) @@ -504,6 +562,24 @@ def _build_merged_template( # use rel_key for template_path (project-relative), not the # prefixed path from the parent's manifest. rel_key = str(rel) + if renamed_gitignore: + # the renamed gitignore is a special case: the template repo + # ships BOTH a real `gitignore` (the project's intended + # .gitignore) and a stray dotted `.gitignore` that is the one + # recorded in the parent manifest under the `.gitignore` key. + # looking up the parent manifest by the renamed key therefore + # resolves to the WRONG file's sha256 — so the merged manifest + # would describe a file we never copied. compute the sha256 + # from the file actually written so manifest and content always + # agree; reuse the parent's last_commit when available. + parent_entry = parent_manifest.get(rel_key) + merged_manifest[rel_key] = { + 'last_commit': (parent_entry['last_commit'] if parent_entry else 'unknown'), + 'sha256': self.hasher.sha256_file(dest), + 'template_path': rel_key, + } + continue + parent_entry = parent_manifest.get(rel_key) if parent_entry: merged_manifest[rel_key] = { diff --git a/tcex_cli/cli/template/update.py b/tcex_cli/cli/template/update.py index a58d4b9..8958b26 100644 --- a/tcex_cli/cli/template/update.py +++ b/tcex_cli/cli/template/update.py @@ -37,6 +37,15 @@ def command( force: bool = typer.Option( default=False, help="Update files from template even if they haven't changed." ), + no_prompt: bool = typer.Option( + False, # noqa: FBT003 + '--no-prompt', + help=( + 'Run non-interactively: overwrite template-owned files and preserve ' + '(without prompting) locally-modified non-template files, reporting them ' + 'for manual review.' + ), + ), branch: str = typer.Option( default_branch, help='The git branch of the tcex-app-template repository to use.' ), @@ -53,6 +62,12 @@ def command( Use --template and --type only for legacy projects where tcex.json is missing those values. + Template-owned files (declared in each template's template_files) are always + overwritten without prompting. Use --no-prompt for a fully non-interactive + run (e.g. scripting a tcex2->tcex4 migration): locally-modified non-template + files are preserved untouched and reported for manual review instead of + prompting. + Optional environment variables include:\n * PROXY_HOST\n * PROXY_PORT\n @@ -92,7 +107,7 @@ def command( cli.clear_cache(branch) try: - cli.update(branch, template_name, template_type, force=force) + cli.update(branch, template_name, template_type, force=force, no_prompt=no_prompt) # use the resolved values for the summary resolved_name = template_name or tj_model.template_name diff --git a/tests/app/tcpb/app_1/requirements.txt b/tests/app/tcpb/app_1/requirements.txt index e4f0c68..706e142 100644 --- a/tests/app/tcpb/app_1/requirements.txt +++ b/tests/app/tcpb/app_1/requirements.txt @@ -1 +1 @@ -tcex +tcex>=4.0.9 diff --git a/tests/deps/test_tcex_deps.py b/tests/deps/test_tcex_deps.py index 2f23bd4..aa5ee15 100644 --- a/tests/deps/test_tcex_deps.py +++ b/tests/deps/test_tcex_deps.py @@ -68,6 +68,10 @@ def test_tcex_deps_std( assert result.exit_code == 0, result.output assert (tmp_path / 'app_std' / 'deps' / 'tcex').is_dir(), result.output + lock = tmp_path / 'app_std' / 'requirements.lock' + assert lock.is_file(), result.output + assert lock.read_text(encoding='utf-8').strip() != '', result.output + def test_tcex_deps_proxy_env( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, request: pytest.FixtureRequest ): @@ -93,6 +97,10 @@ def test_tcex_deps_proxy_env( assert result.exit_code == 0, result.output assert (tmp_path / 'app_std' / 'deps' / 'tcex').is_dir(), result.output + lock = tmp_path / 'app_std' / 'requirements.lock' + assert lock.is_file(), result.output + assert lock.read_text(encoding='utf-8').strip() != '', result.output + def test_tcex_deps_proxy_explicit( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, request: pytest.FixtureRequest ): @@ -114,9 +122,16 @@ def test_tcex_deps_proxy_explicit( result = self._run_command(command, 'app_proxy', tmp_path, request, monkeypatch) assert result.exit_code == 0, result.output - test = list(tmp_path.iterdir()) assert (tmp_path / 'app_proxy' / 'deps' / 'tcex').is_dir(), result.output + lock = tmp_path / 'app_proxy' / 'requirements.lock' + assert lock.is_file(), result.output + lock_text = lock.read_text(encoding='utf-8') + assert lock_text.strip() != '', result.output + assert 'tcex' in lock_text.lower(), result.output + assert lock_text.endswith('\n'), result.output + assert not lock_text.endswith('\n\n'), result.output + # iterate over command output for validations for line in result.stdout.split('\n'): # validate that the correct branch is being used diff --git a/tests/migrate/__init__.py b/tests/migrate/__init__.py new file mode 100644 index 0000000..558af89 --- /dev/null +++ b/tests/migrate/__init__.py @@ -0,0 +1 @@ +"""TcEx Framework Module""" diff --git a/tests/migrate/test_migrate_cli.py b/tests/migrate/test_migrate_cli.py new file mode 100644 index 0000000..61a4ec5 --- /dev/null +++ b/tests/migrate/test_migrate_cli.py @@ -0,0 +1,423 @@ +"""Tests for the ``tcex migrate`` command (MigrateCli).""" + +# the migrate summary counters (_files_scanned / _changes_proposed / _changes_applied / +# _files_changed) are the feature's reporting surface and are asserted directly by these +# tests. +# ruff: noqa: SLF001 + +# standard library +import contextlib +import io +from pathlib import Path + +# third-party +import pytest +from click.testing import Result +from typer.testing import CliRunner + +# first-party +from tcex_cli.cli.cli import app +from tcex_cli.cli.migrate.migrate_cli import MigrateCli +from tcex_cli.render.render import Render + +# get instance of typer CliRunner for the CLI-layer alias test +runner = CliRunner() + +# a known, simple replacement straight from MigrateCli._misc_code_replacements: +# r'utils = Utils\(\)' -> 'utils = Util()' +REPLACEABLE_LINE = 'utils = Utils()\n' +REPLACED_LINE = 'utils = Util()\n' + +# a known import replacement from MigrateCli._tcex_import_replacements: +# r'from tcex\.utils import Utils' -> 'from tcex.util import Util' +REPLACEABLE_IMPORT = 'from tcex.utils import Utils\n' +REPLACED_IMPORT = 'from tcex.util import Util\n' + + +def _run_walk(cli: MigrateCli) -> None: + """Run ``walk_code`` while suppressing the rich console output. + + Args: + cli: The MigrateCli instance to drive. + """ + with contextlib.redirect_stdout(io.StringIO()): + cli.walk_code() + + +class _PromptRecorder: + """Record calls to ``Render.prompt.input`` and return a canned response. + + The source accesses ``Render.prompt.input`` as a bare attribute on the + ``RenderPrompt`` class (no ``cls`` is injected), so this is invoked as a + plain callable with the positional ``prompt_text`` and the + ``prompt_default`` keyword. + """ + + def __init__(self, response: str): + """Initialize instance properties. + + Args: + response: The value to return from every invocation. + """ + self.response = response + self.calls: list[tuple[tuple, dict]] = [] + + def __call__(self, prompt_text: str, prompt_default: str = '', **kwargs) -> str: + """Record the call and return the canned response.""" + self.calls.append(((prompt_text, prompt_default), kwargs)) + return self.response + + +class _PromptForbidden: + """Fail if ``Render.prompt.input`` is ever called (non-interactive guard).""" + + def __init__(self): + """Initialize instance properties.""" + self.calls: list[tuple[tuple, dict]] = [] + + def __call__(self, *args, **kwargs) -> str: + """Record the (unexpected) call so the test can assert it never happens.""" + self.calls.append((args, kwargs)) + return '' + + +@pytest.mark.run(order=3) +class TestMigrateCli: + """Tests for MigrateCli skip logic and the preview / apply / prompt paths.""" + + @staticmethod + def _build_tree(root: Path) -> dict[str, Path]: + """Build a temp tree with skipped reference dirs and a real app.py. + + Args: + root: The temporary root directory to populate. + + Returns: + Mapping of logical names to the created file paths. + """ + # reference file under a nested dot-directory (.claude) -> must be skipped + claude_ref = root / '.claude' / 'cache' / 'tcex4-ref' / 'tcex' / 'logger' / 'logger.py' + claude_ref.parent.mkdir(parents=True) + claude_ref.write_text(REPLACEABLE_LINE, encoding='utf-8') + + # nested dependency directory -> must be skipped + deps_file = root / 'deps' / 'foo.py' + deps_file.parent.mkdir(parents=True) + deps_file.write_text(REPLACEABLE_LINE, encoding='utf-8') + + # nested dot-directory at depth (.venv) -> must be skipped + venv_file = root / '.venv' / 'lib' / 'bar.py' + venv_file.parent.mkdir(parents=True) + venv_file.write_text(REPLACEABLE_LINE, encoding='utf-8') + + # the real app file -> must be walked and rewritten + app_file = root / 'app.py' + app_file.write_text(REPLACEABLE_IMPORT + REPLACEABLE_LINE, encoding='utf-8') + + return { + 'claude_ref': claude_ref, + 'deps_file': deps_file, + 'venv_file': venv_file, + 'app_file': app_file, + } + + def test_walk_code_skips_reference_and_dependency_dirs( + self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch + ): + """Only non-skipped files are walked; only app.py is rewritten on apply. + + Args: + tmp_path: Pytest temp directory unique to the test. + monkeypatch: Pytest fixture for changing the working directory. + """ + files = self._build_tree(tmp_path) + monkeypatch.chdir(tmp_path) + + # apply + no prompt: writes accepted changes unattended + cli = MigrateCli(forward_ref=False, apply=True, prompt=False) + _run_walk(cli) + + # only app.py was scanned (the three skip-dir files were excluded) + assert cli._files_scanned == 1, f'files_scanned={cli._files_scanned} expected 1' + + # app.py was rewritten with both replacements applied + assert files['app_file'].read_text(encoding='utf-8') == REPLACED_IMPORT + REPLACED_LINE + + # only app.py was modified on disk + assert cli._files_changed == 1, f'files_changed={cli._files_changed} expected 1' + + # skipped files are byte-for-byte unchanged + assert files['claude_ref'].read_text(encoding='utf-8') == REPLACEABLE_LINE + assert files['deps_file'].read_text(encoding='utf-8') == REPLACEABLE_LINE + assert files['venv_file'].read_text(encoding='utf-8') == REPLACEABLE_LINE + + @pytest.mark.parametrize( + argnames='skip_rel_path', + argvalues=[ + pytest.param( + # dot-directory reference cache (the original bug) + '.claude/cache/tcex4-ref/tcex/logger/logger.py', + id='skip-dot-claude-cache', + ), + pytest.param( + # nested dependency dir at depth + 'src/deps/nested/foo.py', + id='skip-nested-deps', + ), + pytest.param( + # nested dot-venv at depth + 'pkg/.venv/lib/bar.py', + id='skip-nested-dot-venv', + ), + pytest.param( + # build/target output dir at depth + 'build/target/out.py', + id='skip-nested-target', + ), + pytest.param( + # history dir at depth + 'a/.history/old.py', + id='skip-nested-dot-history', + ), + ], + ) + def test_walk_code_skips_path_at_any_depth( + self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, skip_rel_path: str + ): + """Files whose directory parts include a skip/dot dir are never walked. + + Args: + tmp_path: Pytest temp directory unique to the test. + monkeypatch: Pytest fixture for changing the working directory. + skip_rel_path: Relative path that must be excluded from the walk. + """ + skip_file = tmp_path / skip_rel_path + skip_file.parent.mkdir(parents=True) + skip_file.write_text(REPLACEABLE_LINE, encoding='utf-8') + + # a single walkable file so the walk has something to scan + app_file = tmp_path / 'app.py' + app_file.write_text(REPLACEABLE_LINE, encoding='utf-8') + + monkeypatch.chdir(tmp_path) + + # apply + no prompt so the walkable file is actually rewritten + cli = MigrateCli(forward_ref=False, apply=True, prompt=False) + _run_walk(cli) + + # only app.py is scanned; the skip path is excluded + assert cli._files_scanned == 1, f'files_scanned={cli._files_scanned} expected 1' + assert skip_file.read_text(encoding='utf-8') == REPLACEABLE_LINE + assert app_file.read_text(encoding='utf-8') == REPLACED_LINE + + def test_walk_code_skips_init_file(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch): + """``__init__.py`` is in _skip_files and must not be scanned. + + Args: + tmp_path: Pytest temp directory unique to the test. + monkeypatch: Pytest fixture for changing the working directory. + """ + init_file = tmp_path / '__init__.py' + init_file.write_text(REPLACEABLE_LINE, encoding='utf-8') + app_file = tmp_path / 'app.py' + app_file.write_text(REPLACEABLE_LINE, encoding='utf-8') + + monkeypatch.chdir(tmp_path) + + cli = MigrateCli(forward_ref=False, apply=True, prompt=False) + _run_walk(cli) + + assert cli._files_scanned == 1, f'files_scanned={cli._files_scanned} expected 1' + assert init_file.read_text(encoding='utf-8') == REPLACEABLE_LINE + assert app_file.read_text(encoding='utf-8') == REPLACED_LINE + + def test_preview_default_does_not_write_but_counts_proposal( + self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch + ): + """apply=False previews a change (proposed > 0) but writes nothing. + + Args: + tmp_path: Pytest temp directory unique to the test. + monkeypatch: Pytest fixture for changing the working directory. + """ + app_file = tmp_path / 'app.py' + app_file.write_text(REPLACEABLE_LINE, encoding='utf-8') + + monkeypatch.chdir(tmp_path) + + # the prompt must never be reached in preview mode + forbidden = _PromptForbidden() + monkeypatch.setattr(Render.prompt, 'input', forbidden) + + # apply=False -> preview only; nothing is written and the prompt is never called + cli = MigrateCli(forward_ref=False, apply=False, prompt=True) + _run_walk(cli) + + # the file on disk is unchanged + assert app_file.read_text(encoding='utf-8') == REPLACEABLE_LINE + + # a change was proposed but none applied and no file written + assert cli._changes_proposed > 0, 'expected a proposed change in preview mode' + assert cli._changes_applied == 0, f'changes_applied={cli._changes_applied} expected 0' + assert cli._files_changed == 0, f'files_changed={cli._files_changed} expected 0' + assert cli._files_scanned == 1, f'files_scanned={cli._files_scanned} expected 1' + + # the interactive prompt is never invoked in preview mode + assert not forbidden.calls, 'Render.prompt.input must not be called when apply=False' + + def test_apply_no_prompt_writes_without_prompting( + self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch + ): + """apply=True, prompt=False applies and writes without calling the prompt. + + Args: + tmp_path: Pytest temp directory unique to the test. + monkeypatch: Pytest fixture for changing the working directory. + """ + app_file = tmp_path / 'app.py' + app_file.write_text(REPLACEABLE_LINE, encoding='utf-8') + + monkeypatch.chdir(tmp_path) + + # any call to the interactive prompt is recorded so we can assert it never happens + forbidden = _PromptForbidden() + monkeypatch.setattr(Render.prompt, 'input', forbidden) + + cli = MigrateCli(forward_ref=False, apply=True, prompt=False) + _run_walk(cli) + + # the interactive prompt is never invoked when prompt=False + assert not forbidden.calls, 'Render.prompt.input must not be called when prompt=False' + assert app_file.read_text(encoding='utf-8') == REPLACED_LINE + assert cli._files_changed == 1, f'files_changed={cli._files_changed} expected 1' + assert cli._changes_applied == 1, f'changes_applied={cli._changes_applied} expected 1' + + @pytest.mark.parametrize( + argnames='response,expect_applied', + argvalues=[ + pytest.param( + # explicit yes accepts the change + 'yes', + True, + id='accept-yes', + ), + pytest.param( + # the 'y' shorthand also accepts + 'y', + True, + id='accept-y', + ), + pytest.param( + # empty input accepts (default is yes) + '', + True, + id='accept-default-empty', + ), + pytest.param( + # 'n' declines the change + 'n', + False, + id='decline-n', + ), + ], + ) + def test_apply_prompt_honors_response( + self, + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, + response: str, + expect_applied: bool, + ): + """apply=True with prompt=True confirms per change; response drives the result. + + Args: + tmp_path: Pytest temp directory unique to the test. + monkeypatch: Pytest fixture for changing the working directory. + response: The canned value returned by the mocked prompt. + expect_applied: Whether the change is expected to be applied. + """ + app_file = tmp_path / 'app.py' + app_file.write_text(REPLACEABLE_LINE, encoding='utf-8') + + monkeypatch.chdir(tmp_path) + + recorder = _PromptRecorder(response) + monkeypatch.setattr(Render.prompt, 'input', recorder) + + cli = MigrateCli(forward_ref=False, apply=True, prompt=True) + _run_walk(cli) + + # the interactive prompt was invoked (the whole point of the prompt path) + assert recorder.calls, 'Render.prompt.input should be called when apply=True, prompt=True' + + if expect_applied: + assert app_file.read_text(encoding='utf-8') == REPLACED_LINE + assert cli._files_changed == 1, f'files_changed={cli._files_changed} expected 1' + assert cli._changes_applied == 1 + else: + assert app_file.read_text(encoding='utf-8') == REPLACEABLE_LINE + assert cli._files_changed == 0, f'files_changed={cli._files_changed} expected 0' + assert cli._changes_applied == 0 + + +@pytest.mark.run(order=3) +class TestMigrateCliAlias: + """CLI-layer coverage for the ``--update-code`` alias of ``--apply``.""" + + @staticmethod + def _run_command(args: list[str]) -> Result: + """Invoke the ``tcex`` app command with the given arguments. + + Args: + args: CLI arguments to pass to the tcex app command. + + Returns: + The CLI invocation result. + """ + return runner.invoke(app, args) + + @pytest.mark.parametrize( + argnames='write_flag', + argvalues=[ + pytest.param( + # canonical write switch + '--apply', + id='flag-apply', + ), + pytest.param( + # documented alias of --apply + '--update-code', + id='flag-update-code', + ), + ], + ) + def test_update_code_alias_writes_like_apply( + self, + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, + write_flag: str, + ): + """``--update-code`` is accepted as an alias of ``--apply`` (both write). + + Args: + tmp_path: Pytest temp directory unique to the test. + monkeypatch: Pytest fixture for changing the working directory. + write_flag: The write-gating flag under test (``--apply`` or alias). + """ + app_file = tmp_path / 'app.py' + app_file.write_text(REPLACEABLE_LINE, encoding='utf-8') + + monkeypatch.chdir(tmp_path) + + # --no-prompt for the unattended path; --no-forward-ref to skip the AST pass + result = self._run_command( + [ + 'migrate', + write_flag, + '--no-prompt', + '--no-forward-ref', + ] + ) + + assert result.exit_code == 0, result.stdout + assert app_file.read_text(encoding='utf-8') == REPLACED_LINE diff --git a/tests/template/fixtures/tcex-app-templates-v2.zip b/tests/template/fixtures/tcex-app-templates-v2.zip index ab382965a8177a0b6c95822520fba633457ffd94..20de0a47992f9f8f0407c0df5a48391205214959 100644 GIT binary patch delta 906 zcmYk*Ye-W86bJB~OZiBrD;+L#OLNQ~tKFrWnkY_u+sYm`Mk#9cqI`rsl&lGs4V=1Z zdQ6K}k)#&263wW@$D%M~Stg}XMuqsARHRwC?z|@6FPH!C{O|d2;7%EAW(+p@jbL=K z?`>Ugi2w#87`t6Ssw#`i=|2-MI0Usv{vxFhUkEgDxBRY@O7! z^Vee>h1>|o_OBnSmrYcX4Nr9#?bwoY$Cw>D;H(}GF!z)7--w-GA43ax9~AIxxJwDX!+npJ$`4QA;oV zoD_v~IDFr}s3k_{DT^Zhz1PrST;l65Zp~qAa5}Nzw#p(%C!*Q7tpvPrqzE<5sLg_;XP6_x!AI6t}f-tPZ zmpbso=Ot8oY%V+!fgN&7DG>|l@vLMDfr)3V9JFBp=Wd|*gOv+K^X!CyT1uAs&5HwS zwIC%TB~}sOxOJ?a3!yq7;T0a$K@xPKNe9VbL2((CRH&t1L`o?Am0K1p!`0=$BX-DW zDV2dUo-U`B)yeQ`6^L*+2RvB5jQ%kNy^ zBCi763D+1}GApPXPj8+L<@!MuwyJ=IGuBF$oUWM8g9Dn=!4)$qDHd;;XD$v{Wh|Z+ z%PClnkqGXPhA9Y#h!Z>Ly~DLk6dj}X@~WtM!7jR#flP5{IIhtr!{q( zF}mC41aobs$NDzggyj4st9J0}eF{W5otq>=MXG&&qRi{nt?+9gyD?Fd}bg zzRGP(*iag>CQ}_Gd!kBYtZoeA!`$t~WJHWoL7Gi!}KIO4Ud>PucDEx!qA;RJS#dFi)3j>~{|b0-JJ_Rp|17`K&xk_zYGOdZU| zJ`OOkkOOQq9U@;&rNF1^tj-&gOTnLFaHp!pT;Sn7GjaE^>~})fG7wQMR>DMO83e%x zJW~d7)J9)RDHHh`SVr~u(a0eU1W}g-X5xum8gll~e*t7>6C`s_tEs}Cz;Fyk_ zo@*sbsfQsneX^Vcy&p;=6Xmd+`n7^a1Ly6EO7D50TOuJj7P1QkKziFRUB?UWU`Qtrz=26`qfiGX32`O3jM_z7HZVw z?b=Gxt1%f(o$jBUG8^rxfJddQqbc<{J3Z@^BZloF?aVN1LG*um8g^Gf05ue2F}}z; zOg6}nY>;oTU_PEeGXFH}K@u-(vsLK%^$b*1lh<$)tQ1odNZ{dWU^52f str: + """Return the SHA-256 hex digest of a file's bytes.""" + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def _read_merged_manifest(merged_dir: Path) -> dict: + """Return the merged manifest.json as a dict.""" + return json.loads((merged_dir / 'manifest.json').read_text()) + + +# ================================================================== +# Consistency on the current fixture (baseline) +# ================================================================== +class TestGitignoreMergeConsistency: + """The merged .gitignore manifest entry agrees with the file written.""" + + def test_merged_gitignore_content_is_the_1508_byte_file(self, template_cli): + """The merged dir .gitignore is the 1508-byte project gitignore (87cef9...).""" + cache_dir = template_cli._cache_dir('v2') + merged_dir = template_cli._build_merged_template(cache_dir, 'basic', 'playbook') + try: + gitignore = merged_dir / '.gitignore' + assert gitignore.is_file(), 'merged dir is missing .gitignore' + assert _sha256_file(gitignore) == GITIGNORE_CONTENT_SHA + finally: + # cleanup the caller-owned merged temp dir + for p in sorted(merged_dir.rglob('*'), reverse=True): + p.unlink() if p.is_file() else p.rmdir() + merged_dir.rmdir() + + def test_merged_manifest_sha_matches_written_content(self, template_cli): + """merged_manifest['.gitignore'].sha256 == sha256 of the written .gitignore.""" + cache_dir = template_cli._cache_dir('v2') + merged_dir = template_cli._build_merged_template(cache_dir, 'basic', 'playbook') + try: + merged_manifest = _read_merged_manifest(merged_dir) + written_sha = _sha256_file(merged_dir / '.gitignore') + + assert merged_manifest['.gitignore']['sha256'] == written_sha, ( + 'merged manifest sha256 disagrees with the .gitignore actually written' + ) + # and that sha is the 1508-byte content, not the stray 34-byte file + assert merged_manifest['.gitignore']['sha256'] == GITIGNORE_CONTENT_SHA + finally: + for p in sorted(merged_dir.rglob('*'), reverse=True): + p.unlink() if p.is_file() else p.rmdir() + merged_dir.rmdir() + + def test_merged_manifest_template_path_is_dotted(self, template_cli): + """merged_manifest['.gitignore'].template_path == '.gitignore'.""" + cache_dir = template_cli._cache_dir('v2') + merged_dir = template_cli._build_merged_template(cache_dir, 'basic', 'playbook') + try: + merged_manifest = _read_merged_manifest(merged_dir) + assert merged_manifest['.gitignore']['template_path'] == '.gitignore' + finally: + for p in sorted(merged_dir.rglob('*'), reverse=True): + p.unlink() if p.is_file() else p.rmdir() + merged_dir.rmdir() + + def test_update_writes_gitignore_to_project(self, template_cli, project_dir, monkeypatch): + """End-to-end update writes .gitignore (1508-byte content) into the project.""" + monkeypatch.chdir(project_dir) + with patch.object(template_cli, 'ensure_cache', return_value=template_cli._cache_dir('v2')): + template_cli.update('v2', 'basic', 'playbook', force=True) + + project_gitignore = project_dir / '.gitignore' + assert project_gitignore.is_file(), 'update did not write .gitignore to the project' + assert _sha256_file(project_gitignore) == GITIGNORE_CONTENT_SHA + + +# ================================================================== +# Guard against the wrong-key regression (the important one) +# ================================================================== +class TestGitignoreMergeWrongKeyRegression: + """The merged .gitignore sha must come from content, not the stray entry. + + Recreates the real-repo condition where the parent manifest's ``.gitignore`` + key describes the 34-byte stray file. The old code copied that 34-byte sha + into the merged manifest (describing a file never written); the fix hashes + the 1508-byte file actually written. + """ + + @staticmethod + def _point_manifest_at_stray(cache_dir: Path) -> None: + """Mutate the in-cache _app_common manifest .gitignore -> 34-byte sha. + + This edits the per-test tmp_path cache copy only — the committed fixture + zip is never touched. + """ + manifest_path = cache_dir / '_app_common' / 'manifest.json' + manifest = json.loads(manifest_path.read_text()) + manifest['.gitignore']['sha256'] = STRAY_DOTGITIGNORE_SHA + manifest_path.write_text(json.dumps(manifest, indent=2, sort_keys=True)) + + def test_merged_gitignore_sha_ignores_stray_manifest_entry(self, template_cli): + """Even with the stray sha recorded, the merged entry is the 1508-byte sha.""" + cache_dir = template_cli._cache_dir('v2') + + # sanity: the unmutated fixture currently records the 1508-byte sha (it + # masks the bug); mutate it to describe the 34-byte stray file instead + self._point_manifest_at_stray(cache_dir) + mutated = json.loads((cache_dir / '_app_common' / 'manifest.json').read_text()) + assert mutated['.gitignore']['sha256'] == STRAY_DOTGITIGNORE_SHA + + merged_dir = template_cli._build_merged_template(cache_dir, 'basic', 'playbook') + try: + merged_manifest = _read_merged_manifest(merged_dir) + written_sha = _sha256_file(merged_dir / '.gitignore') + + # fixed code: sha comes from the file actually written (1508-byte) + assert merged_manifest['.gitignore']['sha256'] == GITIGNORE_CONTENT_SHA, ( + 'merged .gitignore sha must be hashed from the written file, ' + 'not copied from the stray manifest entry' + ) + # the old code would copy the 34-byte stray sha — assert it did not + assert merged_manifest['.gitignore']['sha256'] != STRAY_DOTGITIGNORE_SHA + # manifest still agrees with the written content + assert merged_manifest['.gitignore']['sha256'] == written_sha + finally: + for p in sorted(merged_dir.rglob('*'), reverse=True): + p.unlink() if p.is_file() else p.rmdir() + merged_dir.rmdir() + + +# ================================================================== +# .gitignore is now template-owned (listed in _app_common template_files) +# ================================================================== +class TestGitignoreTemplateOwned: + """``gitignore`` is in ``_app_common.template_files`` → ``.gitignore`` is forced. + + Because the parent ``template.yaml`` lists ``gitignore`` (renamed on disk to + ``.gitignore``), the file is template-owned: a locally-modified project + ``.gitignore`` is force-overwritten back to the template content by ``update``, + both on the default (interactive) path and under ``--no-prompt`` — it is never + treated as a user file to preserve or prompt for. + """ + + @staticmethod + def _init(template_cli, project_dir, monkeypatch) -> None: + """Init the project so it has a manifest + the template .gitignore.""" + monkeypatch.chdir(project_dir) + with patch.object(template_cli, 'ensure_cache', return_value=template_cli._cache_dir('v2')): + template_cli.update('v2', 'basic', 'playbook', force=True) + + def test_collect_keys_includes_gitignore(self, template_cli): + """_collect_template_file_keys for playbook/basic now contains '.gitignore'.""" + cache_dir = template_cli._cache_dir('v2') + keys = template_cli._collect_template_file_keys(cache_dir, 'basic', 'playbook') + + assert '.gitignore' in keys + + def test_locally_modified_gitignore_force_overwritten_default( + self, template_cli, project_dir, monkeypatch + ): + """A locally-edited .gitignore is restored to template content with no prompt.""" + self._init(template_cli, project_dir, monkeypatch) + + # sanity: the initialized .gitignore is the 1508-byte template content + project_gitignore = project_dir / '.gitignore' + assert _sha256_file(project_gitignore) == GITIGNORE_CONTENT_SHA + + # user edits .gitignore locally + project_gitignore.write_text('# user gitignore edits\n') + assert _sha256_file(project_gitignore) != GITIGNORE_CONTENT_SHA + + with ( + patch.object(template_cli, 'ensure_cache', return_value=template_cli._cache_dir('v2')), + patch('tcex_cli.render.render.Render.prompt.ask') as mock_prompt, + ): + template_cli.update('v2', 'basic', 'playbook') + + # template-owned .gitignore overwritten back to the 1508-byte template content + assert _sha256_file(project_gitignore) == GITIGNORE_CONTENT_SHA + # and the user was never prompted for it + prompted_files = [call.args[0] for call in mock_prompt.call_args_list] + assert not any('gitignore' in f for f in prompted_files) + + def test_locally_modified_gitignore_force_overwritten_no_prompt( + self, template_cli, project_dir, monkeypatch + ): + """Under --no-prompt, a locally-edited .gitignore is force-overwritten, not preserved.""" + self._init(template_cli, project_dir, monkeypatch) + + project_gitignore = project_dir / '.gitignore' + project_gitignore.write_text('# user gitignore edits\n') + + # simulate the template moving on so the file is a clear update candidate + manifest = json.loads((project_dir / 'manifest.json').read_text()) + manifest['.gitignore']['last_commit'] = 'old_commit_before_update' + (project_dir / 'manifest.json').write_text(json.dumps(manifest, indent=2)) + + with ( + patch.object(template_cli, 'ensure_cache', return_value=template_cli._cache_dir('v2')), + patch('tcex_cli.render.render.Render.prompt.ask') as mock_prompt, + ): + template_cli.update('v2', 'basic', 'playbook', no_prompt=True) + + # template-owned .gitignore overwritten (not preserved as a user file) + assert _sha256_file(project_gitignore) == GITIGNORE_CONTENT_SHA + # zero prompts during the entire run + mock_prompt.assert_not_called() diff --git a/tests/template/test_template_commands.py b/tests/template/test_template_commands.py index c652871..72119bf 100644 --- a/tests/template/test_template_commands.py +++ b/tests/template/test_template_commands.py @@ -57,9 +57,7 @@ class TestList: def test_list_all_types(self, template_cli): """list_ with no type filter should discover multiple template types.""" - with patch.object( - template_cli, 'ensure_cache', return_value=template_cli._cache_dir('v2') - ): + with patch.object(template_cli, 'ensure_cache', return_value=template_cli._cache_dir('v2')): template_cli.list_('v2') assert 'playbook' in template_cli.template_data @@ -67,9 +65,7 @@ def test_list_all_types(self, template_cli): def test_list_playbook_templates(self, template_cli): """list_ filtered to playbook should find basic (and others).""" - with patch.object( - template_cli, 'ensure_cache', return_value=template_cli._cache_dir('v2') - ): + with patch.object(template_cli, 'ensure_cache', return_value=template_cli._cache_dir('v2')): template_cli.list_('v2', 'playbook') names = [c.name for c in template_cli.template_data['playbook']] @@ -77,9 +73,7 @@ def test_list_playbook_templates(self, template_cli): def test_list_organization_templates(self, template_cli): """list_ filtered to organization should find basic.""" - with patch.object( - template_cli, 'ensure_cache', return_value=template_cli._cache_dir('v2') - ): + with patch.object(template_cli, 'ensure_cache', return_value=template_cli._cache_dir('v2')): template_cli.list_('v2', 'organization') names = [c.name for c in template_cli.template_data['organization']] @@ -87,17 +81,13 @@ def test_list_organization_templates(self, template_cli): def test_list_invalid_type_raises(self, template_cli): with ( - patch.object( - template_cli, 'ensure_cache', return_value=template_cli._cache_dir('v2') - ), + patch.object(template_cli, 'ensure_cache', return_value=template_cli._cache_dir('v2')), pytest.raises(ValueError, match='Invalid Types'), ): template_cli.list_('v2', 'nonexistent_type') def test_list_discovers_multiple_templates_per_type(self, template_cli): - with patch.object( - template_cli, 'ensure_cache', return_value=template_cli._cache_dir('v2') - ): + with patch.object(template_cli, 'ensure_cache', return_value=template_cli._cache_dir('v2')): template_cli.list_('v2', 'organization') names = [c.name for c in template_cli.template_data['organization']] @@ -175,9 +165,7 @@ class TestInitOrganization: def test_creates_org_specific_files(self, template_cli, project_dir, monkeypatch): """organization/basic should have job_app.py, not playbook_app.py.""" - ProjectHelper.init_project( - template_cli, project_dir, monkeypatch, 'basic', 'organization' - ) + ProjectHelper.init_project(template_cli, project_dir, monkeypatch, 'basic', 'organization') assert (project_dir / 'job_app.py').is_file() assert (project_dir / 'app.py').is_file() @@ -185,9 +173,7 @@ def test_creates_org_specific_files(self, template_cli, project_dir, monkeypatch def test_inherits_app_common(self, template_cli, project_dir, monkeypatch): """Should inherit _app_common files.""" - ProjectHelper.init_project( - template_cli, project_dir, monkeypatch, 'basic', 'organization' - ) + ProjectHelper.init_project(template_cli, project_dir, monkeypatch, 'basic', 'organization') assert (project_dir / 'requirements.txt').is_file() assert (project_dir / 'pyproject.toml').is_file() @@ -243,36 +229,52 @@ def test_modified_non_template_file_preserved_when_same_version( assert (project_dir / 'app.py').read_text() == '# user modification' - def test_same_template_version_skips_even_modified_files( + def test_modified_template_owned_file_reconciled_when_same_version( self, template_cli, project_dir, monkeypatch ): - """When the template hasn't changed (same last_commit), update skips all files. + """A modified template-owned file is reconciled even at the same last_commit. - If the user modifies a file but the template hasn't changed, - update should skip (use --force to overwrite). + playbook_app.py is template-owned (declared in playbook/basic template_files). + Even when the local manifest's last_commit still matches the template, a local + modification is overwritten back to the template content — the last_commit match + does NOT preempt the template-owned reconciliation. """ ProjectHelper.init_project(template_cli, project_dir, monkeypatch, 'basic', 'playbook') + + cache_dir = template_cli._cache_dir('v2') + template_content = (cache_dir / 'playbook' / 'basic' / 'playbook_app.py').read_text() + (project_dir / 'playbook_app.py').write_text('# user modification') ProjectHelper.run_update(template_cli) - assert (project_dir / 'playbook_app.py').read_text() == '# user modification' + assert (project_dir / 'playbook_app.py').read_text() == template_content - def test_template_file_prompts_when_template_changes( + def test_template_owned_file_force_overwritten_when_template_changes( self, template_cli, project_dir, monkeypatch ): - """When the template has a new commit and local file was modified, user is prompted.""" + """A template-owned file (playbook_app.py) is overwritten without prompting. + + playbook_app.py is declared in playbook/basic template_files, so even when + the user modified it and the template changed, it is force-overwritten with + no prompt (the new template_files ownership behavior). + """ ProjectHelper.init_project(template_cli, project_dir, monkeypatch, 'basic', 'playbook') + cache_dir = template_cli._cache_dir('v2') + template_content = (cache_dir / 'playbook' / 'basic' / 'playbook_app.py').read_text() + (project_dir / 'playbook_app.py').write_text('# user modification') ProjectHelper.simulate_template_change(project_dir, 'playbook_app.py') with patch.object(template_cli, 'ensure_cache', return_value=template_cli._cache_dir('v2')): - with patch('tcex_cli.render.render.Render.prompt.ask', return_value='N'): + with patch('tcex_cli.render.render.Render.prompt.ask') as mock_prompt: template_cli.update('v2', 'basic', 'playbook') - # user answered 'N' → file preserved - assert (project_dir / 'playbook_app.py').read_text() == '# user modification' + # template-owned file is overwritten from the template — no prompt issued + assert (project_dir / 'playbook_app.py').read_text() == template_content + prompted_files = [call.args[0] for call in mock_prompt.call_args_list] + assert not any('playbook_app.py' in f for f in prompted_files) def test_unmodified_file_auto_updates_when_template_changes( self, template_cli, project_dir, monkeypatch @@ -305,9 +307,7 @@ def test_non_template_file_prompts_when_template_changes( assert (project_dir / 'app.py').read_text() == '# user modification' - def test_prompt_answer_yes_overwrites_file( - self, template_cli, project_dir, monkeypatch - ): + def test_prompt_answer_yes_overwrites_file(self, template_cli, project_dir, monkeypatch): ProjectHelper.init_project(template_cli, project_dir, monkeypatch, 'basic', 'playbook') (project_dir / 'app.py').write_text('# user modification') @@ -353,14 +353,41 @@ def test_deleted_file_restored_on_force(self, template_cli, project_dir, monkeyp assert (project_dir / 'playbook_app.py').is_file() - def test_deleted_file_skipped_without_force(self, template_cli, project_dir, monkeypatch): + def test_deleted_template_owned_file_restored_without_force( + self, template_cli, project_dir, monkeypatch + ): + """A deleted template-owned file is restored on update without --force. + + playbook_app.py is template-owned, so even at the same last_commit a missing + file is routed to auto_update and restored from the template content. + """ ProjectHelper.init_project(template_cli, project_dir, monkeypatch, 'basic', 'playbook') + cache_dir = template_cli._cache_dir('v2') + template_content = (cache_dir / 'playbook' / 'basic' / 'playbook_app.py').read_text() + (project_dir / 'playbook_app.py').unlink() ProjectHelper.run_update(template_cli) - assert not (project_dir / 'playbook_app.py').exists() + assert (project_dir / 'playbook_app.py').is_file() + assert (project_dir / 'playbook_app.py').read_text() == template_content + + def test_deleted_non_template_file_skipped_without_force( + self, template_cli, project_dir, monkeypatch + ): + """A deleted non-template file stays deleted on update without --force. + + app.py is NOT template-owned, so at the same last_commit the deletion is + skipped and the file is not restored (use --force to restore). + """ + ProjectHelper.init_project(template_cli, project_dir, monkeypatch, 'basic', 'playbook') + + (project_dir / 'app.py').unlink() + + ProjectHelper.run_update(template_cli) + + assert not (project_dir / 'app.py').exists() # ================================================================== @@ -469,11 +496,14 @@ def test_modified_template_file_auto_updates(self, template_cli, project_dir, mo (project_dir / 'playbook_app.py').write_text('# user modification') with patch.object(template_cli, 'ensure_cache', return_value=template_cli._cache_dir('v2')): - with patch('tcex_cli.render.render.Render.prompt.ask', return_value='N'): + with patch('tcex_cli.render.render.Render.prompt.ask') as mock_prompt: template_cli.update('v2', 'basic', 'playbook') - # playbook_app.py was modified but user answered 'N' → preserved - assert (project_dir / 'playbook_app.py').read_text() == '# user modification' + # playbook_app.py is template-owned → force-overwritten back to the template + # copy with no prompt (the new template_files ownership behavior) + assert (project_dir / 'playbook_app.py').read_text() == original + prompted_files = [call.args[0] for call in mock_prompt.call_args_list] + assert not any('playbook_app.py' in f for f in prompted_files) def test_modified_non_template_file_prompts(self, template_cli, project_dir, monkeypatch): """A non-template_file that the user modified should prompt (and be preserved on 'N').""" diff --git a/tests/template/test_template_files_force.py b/tests/template/test_template_files_force.py new file mode 100644 index 0000000..3fd77bb --- /dev/null +++ b/tests/template/test_template_files_force.py @@ -0,0 +1,533 @@ +"""Tests for the template_files force-overwrite + --no-prompt update feature. + +Covers: +* TemplateCli._collect_template_file_keys — union of template_files across the + resolved parent chain (real fixture data). +* Planner.build(force_keys=...) — would-be prompt_user entries whose key is in + force_keys are routed to auto_update; others keep prompt-on-modify behavior. +* Planner.apply(no_prompt=True) — prompt_user files are left untouched on disk + and returned in the preserved list (prompt_fn is never called); force=True + still overwrites and returns an empty preserved list. +* End-to-end TemplateCli.update(..., no_prompt=True) over the fixture cache. +* Regression: default update still prompts for a modified non-template file. +""" + +# standard library +import hashlib +import json +from pathlib import Path +from unittest.mock import patch + +# first-party +from tcex_cli.cli.template.planner import Hasher, ManifestStore, Plan, Planner, SafeFileOps + +# ================================================================== +# Helpers +# ================================================================== + + +def _sha256_bytes(data: bytes) -> str: + """Return the SHA-256 hex digest of bytes.""" + return hashlib.sha256(data).hexdigest() + + +def _write_manifest(path: Path, meta: dict) -> None: + """Write a manifest.json to path.""" + path.write_text(json.dumps(meta, indent=2)) + + +def _make_planner() -> Planner: + """Return a Planner wired with the real (stateless) collaborators.""" + return Planner(ManifestStore(), Hasher(), SafeFileOps()) + + +class RecordingPrompt: + """A prompt_fn stand-in that records calls and raises if invoked. + + Used to prove no_prompt never prompts: any call fails the test loudly. + """ + + def __init__(self) -> None: + """Initialize the call recorder.""" + self.calls: list[str] = [] + + def __call__(self, msg: str) -> str: + """Record the prompt and fail — should never be reached under no_prompt.""" + self.calls.append(msg) + ex_msg = f'prompt_fn was called unexpectedly: {msg!r}' + raise AssertionError(ex_msg) + + +# ================================================================== +# _collect_template_file_keys +# ================================================================== +class TestCollectTemplateFileKeys: + """Union of template_files across the resolved parent chain.""" + + def test_playbook_basic_union(self, template_cli): + """playbook/basic + parent _app_common template_files union (the seven files). + + ``_app_common`` lists ``gitignore`` in its ``template_files`` (renamed to the + on-disk ``.gitignore`` key), so the union is the leaf-owned files plus the four + parent-owned files including ``.gitignore``. + """ + cache_dir = template_cli._cache_dir('v2') + keys = template_cli._collect_template_file_keys(cache_dir, 'basic', 'playbook') + + assert keys == { + 'playbook_app.py', + 'run.py', + 'run_local.py', + '.coveragerc', + '.pre-commit-config.yaml', + 'pyproject.toml', + '.gitignore', + } + + def test_includes_parent_keys(self, template_cli): + """The parent (_app_common) keys are present in the union, not just the leaf's.""" + cache_dir = template_cli._cache_dir('v2') + keys = template_cli._collect_template_file_keys(cache_dir, 'basic', 'playbook') + + # leaf-owned + assert 'playbook_app.py' in keys + # parent-owned + assert 'pyproject.toml' in keys + # parent-owned, renamed gitignore -> .gitignore (now template-owned) + assert '.gitignore' in keys + + +# ================================================================== +# Planner.build(force_keys=...) +# ================================================================== +class TestPlannerBuildForceKeys: + """force_keys routes would-be prompt_user entries to auto_update.""" + + @staticmethod + def _setup_diverged(tmp_path: Path) -> tuple[Path, Path]: + """Create a template dir + project dir where a tracked file diverged. + + The template file content, the local manifest's recorded sha256, and the + on-disk local content are all distinct, so the planner's hash comparison + falls through to the "user modified" (prompt_user) branch. + """ + template_dir = tmp_path / 'template' + project_dir = tmp_path / 'project' + template_dir.mkdir() + project_dir.mkdir() + + # template ships new content for owned.py + new_content = b'# template version\n' + (template_dir / 'owned.py').write_bytes(new_content) + template_meta = { + 'owned.py': { + 'last_commit': 'new_commit', + 'sha256': _sha256_bytes(new_content), + 'template_path': 'owned.py', + } + } + _write_manifest(template_dir / 'manifest.json', template_meta) + + # local project last synced an older version (recorded sha differs) + old_content = b'# original template version\n' + local_meta = { + 'owned.py': { + 'last_commit': 'old_commit', + 'sha256': _sha256_bytes(old_content), + 'template_path': 'owned.py', + } + } + _write_manifest(project_dir / 'manifest.json', local_meta) + + # but the user edited it to something else entirely → diverged + (project_dir / 'owned.py').write_bytes(b'# user edits\n') + + return template_dir, project_dir + + def test_diverged_file_in_force_keys_goes_to_auto_update(self, tmp_path): + """A modified, diverged file routes to auto_update when its key is forced.""" + template_dir, project_dir = self._setup_diverged(tmp_path) + planner = _make_planner() + + plan = planner.build(template_dir, project_dir, force_keys={'owned.py'}) + + assert ('owned.py', 'owned.py') in plan.auto_update + assert ('owned.py', 'owned.py') not in plan.prompt_user + + def test_diverged_file_not_in_force_keys_stays_prompt_user(self, tmp_path): + """The same diverged file stays in prompt_user when not forced.""" + template_dir, project_dir = self._setup_diverged(tmp_path) + planner = _make_planner() + + plan = planner.build(template_dir, project_dir, force_keys=set()) + + assert ('owned.py', 'owned.py') in plan.prompt_user + assert ('owned.py', 'owned.py') not in plan.auto_update + + def test_new_file_on_disk_in_force_keys_goes_to_auto_update(self, tmp_path): + """New template file already on disk: forced → auto_update, not prompt_user.""" + template_dir = tmp_path / 'template' + project_dir = tmp_path / 'project' + template_dir.mkdir() + project_dir.mkdir() + + content = b'# new template file\n' + (template_dir / 'fresh.py').write_bytes(content) + _write_manifest( + template_dir / 'manifest.json', + { + 'fresh.py': { + 'last_commit': 'c1', + 'sha256': _sha256_bytes(content), + 'template_path': 'fresh.py', + } + }, + ) + # local manifest has no entry for fresh.py (new), but file exists on disk + _write_manifest(project_dir / 'manifest.json', {}) + (project_dir / 'fresh.py').write_bytes(b'# pre-existing local file\n') + + planner = _make_planner() + + forced = planner.build(template_dir, project_dir, force_keys={'fresh.py'}) + assert ('fresh.py', 'fresh.py') in forced.auto_update + assert ('fresh.py', 'fresh.py') not in forced.prompt_user + + unforced = planner.build(template_dir, project_dir, force_keys=set()) + assert ('fresh.py', 'fresh.py') in unforced.prompt_user + assert ('fresh.py', 'fresh.py') not in unforced.auto_update + + +# ================================================================== +# Planner.build — template-owned reconciliation ignores last_commit match +# ================================================================== +class TestPlannerBuildForceKeysSameCommit: + """Regression: a force_keys file is reconciled even when last_commit matches. + + The closed gap: a locally-modified template-owned file whose local manifest + last_commit STILL MATCHES the template was previously short-circuited to skip + (never restored). The force_keys reconciliation must run BEFORE the + last_commit short-circuit. + """ + + @staticmethod + def _setup_same_commit(tmp_path: Path, *, local_content: bytes) -> tuple[Path, Path, bytes]: + """Template + project where last_commit matches but on-disk content may differ. + + The local manifest's ``last_commit`` is identical to the template's, so the + ``last_commit ==`` short-circuit would skip the file if it ran first. ``sha256`` + is also recorded as the template hash (a clean prior sync). The on-disk content + is driven by ``local_content`` so the caller can exercise both the diverged and + identical cases. + """ + template_dir = tmp_path / 'template' + project_dir = tmp_path / 'project' + template_dir.mkdir() + project_dir.mkdir() + + template_bytes = b'# template version\n' + (template_dir / 'owned.py').write_bytes(template_bytes) + + shared_commit = 'same_commit' + meta = { + 'owned.py': { + 'last_commit': shared_commit, + 'sha256': _sha256_bytes(template_bytes), + 'template_path': 'owned.py', + } + } + _write_manifest(template_dir / 'manifest.json', meta) + # local manifest records the SAME last_commit (clean prior sync) + _write_manifest(project_dir / 'manifest.json', meta) + + # on-disk content per the caller's scenario + (project_dir / 'owned.py').write_bytes(local_content) + + return template_dir, project_dir, template_bytes + + def test_modified_force_key_same_commit_goes_to_auto_update(self, tmp_path): + """Modified template-owned file at matching last_commit → auto_update (restored).""" + template_dir, project_dir, _template_bytes = self._setup_same_commit( + tmp_path, local_content=b'# user edits\n' + ) + planner = _make_planner() + + plan = planner.build(template_dir, project_dir, force_keys={'owned.py'}) + + assert ('owned.py', 'owned.py') in plan.auto_update + assert ('owned.py', 'owned.py') not in plan.skip + assert ('owned.py', 'owned.py') not in plan.prompt_user + + def test_deleted_force_key_same_commit_goes_to_auto_update(self, tmp_path): + """Missing template-owned file at matching last_commit → auto_update (restored).""" + template_dir, project_dir, _template_bytes = self._setup_same_commit( + tmp_path, local_content=b'placeholder\n' + ) + (project_dir / 'owned.py').unlink() + planner = _make_planner() + + plan = planner.build(template_dir, project_dir, force_keys={'owned.py'}) + + assert ('owned.py', 'owned.py') in plan.auto_update + assert ('owned.py', 'owned.py') not in plan.skip + + def test_identical_force_key_same_commit_skips(self, tmp_path): + """Template-owned file already matching the template hash → skip (no rewrite).""" + template_dir, project_dir, template_bytes = self._setup_same_commit( + tmp_path, local_content=b'# template version\n' + ) + # sanity: on-disk content matches the template byte-for-byte + assert (project_dir / 'owned.py').read_bytes() == template_bytes + planner = _make_planner() + + plan = planner.build(template_dir, project_dir, force_keys={'owned.py'}) + + assert ('owned.py', 'owned.py') in plan.skip + assert ('owned.py', 'owned.py') not in plan.auto_update + + def test_modified_non_force_key_same_commit_skips(self, tmp_path): + """The same modified file, NOT force-owned, still skips at matching last_commit.""" + template_dir, project_dir, _template_bytes = self._setup_same_commit( + tmp_path, local_content=b'# user edits\n' + ) + planner = _make_planner() + + plan = planner.build(template_dir, project_dir, force_keys=set()) + + # non-template file at matching last_commit short-circuits to skip + assert ('owned.py', 'owned.py') in plan.skip + assert ('owned.py', 'owned.py') not in plan.auto_update + + +# ================================================================== +# End-to-end — modified template-owned file restored at matching last_commit +# ================================================================== +class TestUpdateRestoresTemplateOwnedSameCommit: + """End-to-end: a modified template-owned file is restored without a manifest bump.""" + + @staticmethod + def _init(template_cli, project_dir, monkeypatch) -> None: + monkeypatch.chdir(project_dir) + with patch.object(template_cli, 'ensure_cache', return_value=template_cli._cache_dir('v2')): + template_cli.update('v2', 'basic', 'playbook', force=True) + + def test_modified_run_py_restored_with_unchanged_manifest( + self, template_cli, project_dir, monkeypatch + ): + """run.py (template-owned) is overwritten back to the template with no prompt. + + The local manifest is left untouched (last_commit still matches the template), + proving the last_commit match does not preempt template-owned reconciliation. + """ + self._init(template_cli, project_dir, monkeypatch) + + cache_dir = template_cli._cache_dir('v2') + template_run = (cache_dir / 'playbook' / 'basic' / 'run.py').read_text() + + # modify a template-owned file WITHOUT touching the manifest + (project_dir / 'run.py').write_text('# user run.py edits') + + with ( + patch.object(template_cli, 'ensure_cache', return_value=template_cli._cache_dir('v2')), + patch('tcex_cli.render.render.Render.prompt.ask') as mock_prompt, + ): + template_cli.update('v2', 'basic', 'playbook') + + assert (project_dir / 'run.py').read_text() == template_run + prompted_files = [call.args[0] for call in mock_prompt.call_args_list] + assert not any('run.py' in f for f in prompted_files) + + def test_unmodified_run_py_skipped_with_unchanged_manifest( + self, template_cli, project_dir, monkeypatch + ): + """An untouched template-owned file already matching the template is skipped.""" + self._init(template_cli, project_dir, monkeypatch) + + cache_dir = template_cli._cache_dir('v2') + template_run = (cache_dir / 'playbook' / 'basic' / 'run.py').read_text() + + with ( + patch.object(template_cli, 'ensure_cache', return_value=template_cli._cache_dir('v2')), + patch('tcex_cli.render.render.Render.prompt.ask') as mock_prompt, + ): + template_cli.update('v2', 'basic', 'playbook') + + # unchanged on disk (already matched → skipped, no needless rewrite) + assert (project_dir / 'run.py').read_text() == template_run + mock_prompt.assert_not_called() + + +# ================================================================== +# Planner.apply(no_prompt=...) +# ================================================================== +class TestPlannerApplyNoPrompt: + """no_prompt preserves prompt_user files; force still overwrites.""" + + @staticmethod + def _setup_apply(tmp_path: Path) -> tuple[Path, Path, bytes]: + """Create a template + project where prompt.py is a modified prompt_user file.""" + template_dir = tmp_path / 'template' + project_dir = tmp_path / 'project' + template_dir.mkdir() + project_dir.mkdir() + + template_bytes = b'# template content\n' + (template_dir / 'prompt.py').write_bytes(template_bytes) + + local_bytes = b'# user content\n' + (project_dir / 'prompt.py').write_bytes(local_bytes) + + return template_dir, project_dir, template_bytes + + def test_no_prompt_preserves_and_records_without_prompting(self, tmp_path): + """no_prompt: file untouched on disk, returned preserved, prompt_fn never called.""" + template_dir, project_dir, _template_bytes = self._setup_apply(tmp_path) + local_bytes = (project_dir / 'prompt.py').read_bytes() + + plan = Plan(prompt_user=[('prompt.py', 'prompt.py')]) + planner = _make_planner() + recorder = RecordingPrompt() + + preserved = planner.apply( + plan, + template_root=template_dir, + project_root=project_dir, + no_prompt=True, + prompt_fn=recorder, + ) + + # file left byte-for-byte unchanged + assert (project_dir / 'prompt.py').read_bytes() == local_bytes + # returned in the preserved list + assert ('prompt.py', 'prompt.py') in preserved + # prompt_fn never invoked + assert recorder.calls == [] + + def test_force_overwrites_and_returns_empty_preserved(self, tmp_path): + """force=True: prompt_user file is overwritten, preserved list is empty.""" + template_dir, project_dir, template_bytes = self._setup_apply(tmp_path) + + plan = Plan(prompt_user=[('prompt.py', 'prompt.py')]) + planner = _make_planner() + recorder = RecordingPrompt() + + preserved = planner.apply( + plan, + template_root=template_dir, + project_root=project_dir, + force=True, + prompt_fn=recorder, + ) + + # overwritten from template + assert (project_dir / 'prompt.py').read_bytes() == template_bytes + # no preserved entries on the force path + assert preserved == [] + # prompt_fn never invoked under force + assert recorder.calls == [] + + +# ================================================================== +# End-to-end update --no-prompt over the fixture cache +# ================================================================== +class TestUpdateNoPromptEndToEnd: + """Drive TemplateCli.update(no_prompt=True) over the real fixture cache.""" + + @staticmethod + def _init(template_cli, project_dir, monkeypatch) -> None: + monkeypatch.chdir(project_dir) + with patch.object(template_cli, 'ensure_cache', return_value=template_cli._cache_dir('v2')): + template_cli.update('v2', 'basic', 'playbook', force=True) + + @staticmethod + def _simulate_change(project_dir: Path, filename: str) -> None: + manifest = json.loads((project_dir / 'manifest.json').read_text()) + manifest[filename]['last_commit'] = 'old_commit_before_update' + (project_dir / 'manifest.json').write_text(json.dumps(manifest, indent=2)) + + def test_template_owned_file_overwritten_and_non_template_preserved( + self, template_cli, project_dir, monkeypatch + ): + """no_prompt: run.py (template-owned) overwritten; app.py (non-template) preserved. + + The whole run completes with zero prompts. + """ + self._init(template_cli, project_dir, monkeypatch) + + cache_dir = template_cli._cache_dir('v2') + template_run = (cache_dir / 'playbook' / 'basic' / 'run.py').read_text() + + # user modifies both a template-owned file and a non-template file + (project_dir / 'run.py').write_text('# user run.py edits') + (project_dir / 'app.py').write_text('# user app.py edits') + + # simulate the template moving on for both + self._simulate_change(project_dir, 'run.py') + self._simulate_change(project_dir, 'app.py') + + with ( + patch.object(template_cli, 'ensure_cache', return_value=template_cli._cache_dir('v2')), + patch('tcex_cli.render.render.Render.prompt.ask') as mock_prompt, + ): + template_cli.update('v2', 'basic', 'playbook', no_prompt=True) + + # template-owned run.py overwritten back to the template content + assert (project_dir / 'run.py').read_text() == template_run + # non-template app.py preserved byte-for-byte + assert (project_dir / 'app.py').read_text() == '# user app.py edits' + # zero prompts during the entire run + mock_prompt.assert_not_called() + + def test_no_prompt_renders_preserved_report(self, template_cli, project_dir, monkeypatch): + """no_prompt with a preserved file renders the 'Preserved - Review Manually' report.""" + self._init(template_cli, project_dir, monkeypatch) + + (project_dir / 'app.py').write_text('# user app.py edits') + self._simulate_change(project_dir, 'app.py') + + with ( + patch.object(template_cli, 'ensure_cache', return_value=template_cli._cache_dir('v2')), + patch('tcex_cli.render.render.Render.table.key_value') as mock_table, + ): + template_cli.update('v2', 'basic', 'playbook', no_prompt=True) + + titles = [call.args[0] for call in mock_table.call_args_list] + assert 'Preserved - Review Manually' in titles + + +# ================================================================== +# Regression — default update still prompts for non-template files +# ================================================================== +class TestDefaultUpdateStillPrompts: + """Without --no-prompt, a modified non-template file still prompts y/N.""" + + @staticmethod + def _init(template_cli, project_dir, monkeypatch) -> None: + monkeypatch.chdir(project_dir) + with patch.object(template_cli, 'ensure_cache', return_value=template_cli._cache_dir('v2')): + template_cli.update('v2', 'basic', 'playbook', force=True) + + @staticmethod + def _simulate_change(project_dir: Path, filename: str) -> None: + manifest = json.loads((project_dir / 'manifest.json').read_text()) + manifest[filename]['last_commit'] = 'old_commit_before_update' + (project_dir / 'manifest.json').write_text(json.dumps(manifest, indent=2)) + + def test_modified_non_template_file_prompts(self, template_cli, project_dir, monkeypatch): + """Default update prompts for app.py (non-template) and preserves it on 'N'.""" + self._init(template_cli, project_dir, monkeypatch) + + (project_dir / 'app.py').write_text('# user app.py edits') + self._simulate_change(project_dir, 'app.py') + + with ( + patch.object(template_cli, 'ensure_cache', return_value=template_cli._cache_dir('v2')), + patch('tcex_cli.render.render.Render.prompt.ask', return_value='N') as mock_prompt, + ): + template_cli.update('v2', 'basic', 'playbook') + + # app.py was prompted for (it is not template-owned) + prompted_files = [call.args[0] for call in mock_prompt.call_args_list] + assert any('app.py' in f for f in prompted_files) + # answered 'N' → preserved + assert (project_dir / 'app.py').read_text() == '# user app.py edits' diff --git a/uv.lock b/uv.lock index 0cb82dd..505ea74 100644 --- a/uv.lock +++ b/uv.lock @@ -87,11 +87,11 @@ wheels = [ [[package]] name = "certifi" -version = "2026.5.20" +version = "2026.6.17" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/ce/ee2ecad540810a79593028e88299baeae54d346cc7a0d94b6199988b89b1/certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d", size = 135422, upload-time = "2026-05-20T11:46:50.073Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/c7/424b75da314c1045981bd9777432fad05a9e0c69daa4ed7e308bbaffe405/certifi-2026.6.17.tar.gz", hash = "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432", size = 134594, upload-time = "2026-06-17T10:31:07.894Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/59/8c/57e832b7af6d7c5abe66eb3fbe3a3a32f4d11ea23a1aa7131371035be991/certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", size = 134134, upload-time = "2026-05-20T11:46:48.578Z" }, + { url = "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db", size = 133289, upload-time = "2026-06-17T10:31:06.348Z" }, ] [[package]] @@ -215,101 +215,86 @@ wheels = [ [[package]] name = "coverage" -version = "7.14.1" +version = "7.14.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/fd/0ab2772530e946e1be1abd0bc09e647ec9b02e88f0867857601fefca8953/coverage-7.14.1.tar.gz", hash = "sha256:30c08f7d90415aa98b3c990385dea2939b0da55f38515e5b369b83655f8523be", size = 920132, upload-time = "2026-05-26T20:41:36.783Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/a3/3834a5564fe8f32154cd7032400d3c2f9c565b2a373fa671f2bbdad6f634/coverage-7.14.2.tar.gz", hash = "sha256:7a2da3d81cfe17c18038c6d98e6592aa9147d596d056119b0ee612c3c8bd5230", size = 923982, upload-time = "2026-06-20T14:49:30.885Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7d/d7/477ad149490e6cb849f28abea1dabb9c823cea72e7500c81b4240ce619c0/coverage-7.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:478b5bcd63c2e1357c5c7e16c070690df7b07f676b1c114d7b93e533c664309f", size = 219848, upload-time = "2026-05-26T20:38:38.715Z" }, - { url = "https://files.pythonhosted.org/packages/91/82/a5eb47257c50601bb7b9a9d2857c67b7a3a85ad74180eb2c98bb1fbe0ce5/coverage-7.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a24a81f9715ee42ef59a316cc11611c98fe23920f7c81861315c9f3ff4a230f4", size = 220354, upload-time = "2026-05-26T20:38:40.232Z" }, - { url = "https://files.pythonhosted.org/packages/43/8b/78419b5391a5cb706b6544390507e469d83ffc9a8248b02c4011aceb9365/coverage-7.14.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:196a13319ad88d6d8ef5ab489ec4f44ddde2143c0c7d5b27786f6c3ffd56a7e1", size = 250771, upload-time = "2026-05-26T20:38:41.782Z" }, - { url = "https://files.pythonhosted.org/packages/77/63/e77aaacd491182210d639636b7a8bba23ffffa9b82aa3762da9431855fa9/coverage-7.14.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3d452fd08b5c72c5167c93e6867b5c08500bd40f2a21e1e854a500550b6cc36f", size = 252683, upload-time = "2026-05-26T20:38:43.305Z" }, - { url = "https://files.pythonhosted.org/packages/65/1c/a022e3cfbec2ac241640003cb3a817e161d9c7f5aa9b49173756cdc03204/coverage-7.14.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23bf7fa51ac02e07fc7c96849b82946da47ae862dc8f86d183b2a4864fc38129", size = 254791, upload-time = "2026-05-26T20:38:45.361Z" }, - { url = "https://files.pythonhosted.org/packages/61/d6/967e408aca4c1ceb88cb0cc677169110ae7f5995fb5eaf5fb1f5a1bb8f5d/coverage-7.14.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bcaa50684dcaadfa599ac48f81103c756d791cfd85c97203d2217c593d48b860", size = 256748, upload-time = "2026-05-26T20:38:46.91Z" }, - { url = "https://files.pythonhosted.org/packages/b8/be/869188f7fe28638078ec479331ace6dc5f7b40b7153eb616f47ab79404d8/coverage-7.14.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4ea1c034f95c9b056e856b794630b17f9fa3d57e4800ff1e503d3be0f9c9078c", size = 250907, upload-time = "2026-05-26T20:38:48.493Z" }, - { url = "https://files.pythonhosted.org/packages/07/aa/adb7d3b4278d690e68703abcd76ab1b948242e3668d921711551b78f9ddb/coverage-7.14.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c7e057326434e441306226fbeb5d1aaf14a2637efe97ba668306635835f32ad7", size = 252483, upload-time = "2026-05-26T20:38:50.074Z" }, - { url = "https://files.pythonhosted.org/packages/43/61/331c74103c62dcb0c4b9b3a0de9a61aca016208b0a90f109592a9f9ecc28/coverage-7.14.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:59baf88468dbc8d63b1887afd92bda52e40bb1561696e5819670601403810cec", size = 250545, upload-time = "2026-05-26T20:38:51.613Z" }, - { url = "https://files.pythonhosted.org/packages/f6/b6/c5dae3c104d89be04828f61810e6b3473825482e4c288cc4ed04553e08ae/coverage-7.14.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d34d75f892b3ab73ba11cab5442cce7b3e168fd64162b16f0e1e0d09c508edef", size = 254310, upload-time = "2026-05-26T20:38:53.503Z" }, - { url = "https://files.pythonhosted.org/packages/ad/a1/2b9d5863e3b83c01ad8199e3c597802fbb3a9dc90b058885804c20296d31/coverage-7.14.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3a56abc20a472baf0304c455721bc601477440d28ecfde8a03dde79ede07e0df", size = 250266, upload-time = "2026-05-26T20:38:55.414Z" }, - { url = "https://files.pythonhosted.org/packages/7f/5e/0e511fbdb269359be26fe678a1c3fa1f2aa2a01573cc3f54268c8d6d4797/coverage-7.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6a3cb83d1552c0cd1b4906655b6a33fd4a8473229633a901c6b73bf86914dee9", size = 251174, upload-time = "2026-05-26T20:38:57.141Z" }, - { url = "https://files.pythonhosted.org/packages/85/10/e55307b622b3dd9671cb321824502dc10f93e72f2802b9946159a8edadeb/coverage-7.14.1-cp311-cp311-win32.whl", hash = "sha256:10274a1fbeb8ec5d72966e17bb198a3104257aca4ac09d98667c5f8aca8c8548", size = 222354, upload-time = "2026-05-26T20:38:58.727Z" }, - { url = "https://files.pythonhosted.org/packages/71/cf/107421693cfb71e4f1ca5bf70443f64d4161878068d07a3e51c7ad21d17b/coverage-7.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:87ebdf787d4888e3f3f2d523eadc6e18c6d18c6d0eb173801a189641627fb37e", size = 223290, upload-time = "2026-05-26T20:39:00.413Z" }, - { url = "https://files.pythonhosted.org/packages/b8/1d/3e3644585eb29e9dafefb19555078529a4d7cce12bd21929664eea989277/coverage-7.14.1-cp311-cp311-win_arm64.whl", hash = "sha256:dd34767fa19848d35659ffc0a75314f58c7af3f1cd87ec521e8292a1238398a3", size = 221953, upload-time = "2026-05-26T20:39:02.159Z" }, - { url = "https://files.pythonhosted.org/packages/3d/b7/bdbb725ba02c5b42825b200c940f38b7a54fcad24627b7192f78f8110d76/coverage-7.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a06c76364a9360e33d6d23769aefdf7f66f38e2ffb60ceb1baaa4989d83b695c", size = 220022, upload-time = "2026-05-26T20:39:03.702Z" }, - { url = "https://files.pythonhosted.org/packages/72/81/fdc0898a55c6219223291ec1a1fe89966ef212ce82276aa0899df84b5de0/coverage-7.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fad54e871165f6ec2f536063ac74c3104508a12963e64072ba44bd822de52b0c", size = 220379, upload-time = "2026-05-26T20:39:05.381Z" }, - { url = "https://files.pythonhosted.org/packages/de/72/de048c4a25e13bce59ac6a339351c10bdf2515e07459afcdaf04dc3143a2/coverage-7.14.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:84b535f00655ecafe1d929d1fb00ed5d6fa3051ea643ab2c161a3887b86f294b", size = 251888, upload-time = "2026-05-26T20:39:07.367Z" }, - { url = "https://files.pythonhosted.org/packages/28/30/300c343f68beb9d4cbb64ec81e58c5b6b80b56927f72d2b38654ac26e013/coverage-7.14.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6b6b0853b895fe0e98cbfc580d1ec3393d9302b4b1e96a77b3f5c91fdab899e6", size = 254624, upload-time = "2026-05-26T20:39:09.037Z" }, - { url = "https://files.pythonhosted.org/packages/b1/ed/7b25642496e8170b6bac14adce00537c6e5fa2d586159401a4de3e8b49e6/coverage-7.14.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:442cc9c952b2df400cda54bb04ab87330cf2cd08a8692cbbea36773531eb6f37", size = 255739, upload-time = "2026-05-26T20:39:10.889Z" }, - { url = "https://files.pythonhosted.org/packages/7f/a2/abd210b8c4e29c24e4624916db97bb519097a91034aaeb767f937e7da794/coverage-7.14.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8270544c361ed405a27a060dbc9ed2c124b084d96dfdc2d9a2510482aef981ad", size = 257998, upload-time = "2026-05-26T20:39:12.722Z" }, - { url = "https://files.pythonhosted.org/packages/7f/24/7c50beed3792fe62f6ce0545c6686ce83379719e2c0276179333d97eae92/coverage-7.14.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:48b283b1dd6372e8de2a7a9a4c4d5dc06f4d4fd209b876f3c88a7a205a0c8f84", size = 252296, upload-time = "2026-05-26T20:39:14.259Z" }, - { url = "https://files.pythonhosted.org/packages/15/05/0f874628ebcbfc77ead559ff210281ef06a97db08481832e7dd39274a135/coverage-7.14.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5b0c99ba93a07d56f6df340bb79be53202a082b2fdb81bfe6190b741a3470d54", size = 253658, upload-time = "2026-05-26T20:39:15.923Z" }, - { url = "https://files.pythonhosted.org/packages/99/6f/ca6ad067364b337ef997802115e7ecad2abd2248b05471464b0dea02b4d4/coverage-7.14.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e471bc5769ff073b058cfadb0d736b56ce067c8560eabeb0da88462df98c23e7", size = 251803, upload-time = "2026-05-26T20:39:17.537Z" }, - { url = "https://files.pythonhosted.org/packages/c0/30/b9b4d377cd9f40baf228068f5a81faf8450c6228503011bd499708483a50/coverage-7.14.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f497a1ea81d4cd7c10ddcaa685135b9aabd291af3d55775a9ddf3cb7a364cdd9", size = 255873, upload-time = "2026-05-26T20:39:19.414Z" }, - { url = "https://files.pythonhosted.org/packages/3c/21/7c721a9e5e6bb88547d30a787aefb97512d3f54c1324c7488d9b3743f7f9/coverage-7.14.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2222be86d0b54f5dd5a38f45f17f315f737245e857bf0bdedc70734f84a13c02", size = 251372, upload-time = "2026-05-26T20:39:21.169Z" }, - { url = "https://files.pythonhosted.org/packages/9d/8c/f8ae5a2200130e1503cd7661a6cd3b2b7bacef98277fbf3571fb13f8b766/coverage-7.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:85e85586565842f6932abebd4c18bcb1074223dc0b3576e7d173ca710622813a", size = 253245, upload-time = "2026-05-26T20:39:23.097Z" }, - { url = "https://files.pythonhosted.org/packages/34/62/70a9024672a5f6910517d9628c52c9afbdd3cf8f46426af52bb148a56fff/coverage-7.14.1-cp312-cp312-win32.whl", hash = "sha256:4a28fd227808366b196a75476dced2eb35b351d6766ba9c858dc93319e87f4f1", size = 222567, upload-time = "2026-05-26T20:39:24.868Z" }, - { url = "https://files.pythonhosted.org/packages/f6/81/8b7cd386839b039ebe1855733b9f9449a8dec5d79564018234f185a7fa70/coverage-7.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:54acdb6674a4661768d7bf7db32dfb9f46ab1d764f8aba6df75ce1a6a088724e", size = 223372, upload-time = "2026-05-26T20:39:26.603Z" }, - { url = "https://files.pythonhosted.org/packages/ae/ba/b44d472022f620d289d95fa830143235c0c36461c6f2437ea8d51e5481ed/coverage-7.14.1-cp312-cp312-win_arm64.whl", hash = "sha256:99cd41ff91afd94896fea3bc002706b6ae4ce95727d06e4a0f39c0a8d8bd8b1a", size = 221989, upload-time = "2026-05-26T20:39:28.242Z" }, - { url = "https://files.pythonhosted.org/packages/8a/9e/5f6d56327c62b185225d145191c607e07515294a0aa6338e58805cd4a5ac/coverage-7.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:be9f2c802dcfce3f71298303aa5dad0dce440a76c52f2f60dacd8656dab78793", size = 220044, upload-time = "2026-05-26T20:39:29.902Z" }, - { url = "https://files.pythonhosted.org/packages/75/92/e82aca356744cbbc0f77a0b623e38918c1872361963413a3bab5d0340393/coverage-7.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6223a72fd0e4c7156353ec0f08a5f93623e1d3034d0e2683b9bb8ea674131b1d", size = 220412, upload-time = "2026-05-26T20:39:31.561Z" }, - { url = "https://files.pythonhosted.org/packages/27/c9/385bde0bf7ed0f4bf3a7ee5367060a86b5d218718cfd6fb943c0f836b34f/coverage-7.14.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7279d2110a28cebc738b6459ecda2771735a4c18465fbbd36b3288fe5ed92247", size = 251412, upload-time = "2026-05-26T20:39:33.337Z" }, - { url = "https://files.pythonhosted.org/packages/51/8c/23faf6a2343a0d17f960a4bd56c43bc7eb4cf312f774dd6ceebd82c7d8fc/coverage-7.14.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9eeb3fcbc13ba40dfbdb22d01d196a28e9cef9ed4c29b60061a1e0e823a9929d", size = 254008, upload-time = "2026-05-26T20:39:35.009Z" }, - { url = "https://files.pythonhosted.org/packages/42/06/36f4aa9ca8a815e6036156e80706a67828bb97bd826948244f6996dda957/coverage-7.14.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f0cfc27c539f07cf5c0a4cfe211d0b6cae039f8f40526dbaa71944e64b50a7b", size = 255241, upload-time = "2026-05-26T20:39:36.71Z" }, - { url = "https://files.pythonhosted.org/packages/ca/79/95266316352f90f6b1c6736bb413302edfde2453fb32422d3911642691b3/coverage-7.14.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:221c70f316241a78e77e607c227cefc8808d4e08f28d99c04f35694690e940be", size = 257373, upload-time = "2026-05-26T20:39:38.412Z" }, - { url = "https://files.pythonhosted.org/packages/e3/9c/58316d1f66c488b5fca8a0eb3e98348807813efa8a0d0833b9021be27488/coverage-7.14.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:da028256b04ec30e5e0114b6f76172938c313991f0a2d3d894271315cf5d5e43", size = 251635, upload-time = "2026-05-26T20:39:40.268Z" }, - { url = "https://files.pythonhosted.org/packages/ef/5a/ca2398a568e16fed7bb713e84ba3603a7164fb65779abe645c565ec890d5/coverage-7.14.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76a085d7005236a767e3426148b2c407e53ad61695c562f8a81da2d373324901", size = 253373, upload-time = "2026-05-26T20:39:42.145Z" }, - { url = "https://files.pythonhosted.org/packages/6e/2c/0396562c32deaebe7be51d865b3a41e9a87d7561acafe1a28f53b07e019a/coverage-7.14.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b553d04b5e778a8e56d57eb134aff42a92718ecba45e79c4764ecfa40efd92ff", size = 251341, upload-time = "2026-05-26T20:39:43.907Z" }, - { url = "https://files.pythonhosted.org/packages/fd/8f/a94f9221184c9cae1ee115820e3798e48b6b17777a9f19e46fb9a0c8dc74/coverage-7.14.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:46f714d2fb8ae2f4f29f23ada7f1e79b759fff5a70f94a1dac23af204c3ec9e4", size = 255497, upload-time = "2026-05-26T20:39:46.166Z" }, - { url = "https://files.pythonhosted.org/packages/71/69/505d70e47db1eaebcd002c39759707621ef184cd6b1ae084d9f41293f323/coverage-7.14.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:1896f5e19ff3f0431c7ce2172adc54890fd97f86b59ced8ca1649145d9ffe35d", size = 251159, upload-time = "2026-05-26T20:39:48.03Z" }, - { url = "https://files.pythonhosted.org/packages/e0/aa/58681c383aa33a9d2ed40a02d7a22fbf780d1fa4d575396365777828198c/coverage-7.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:62fd185ef9df3c33d1c8178c5af105f762afbad96038de9a4ae100aa6297ca33", size = 252934, upload-time = "2026-05-26T20:39:49.872Z" }, - { url = "https://files.pythonhosted.org/packages/eb/fd/11c928cd6bdffc7074bb5965c173d9ebf517fb00205e1da524b98d29ef92/coverage-7.14.1-cp313-cp313-win32.whl", hash = "sha256:ab4af6352741a604c431c6072fce5bee33bf0f20dc7a56618d6bf6bb89e9810c", size = 222584, upload-time = "2026-05-26T20:39:51.68Z" }, - { url = "https://files.pythonhosted.org/packages/6f/92/fb416fc26d340dcba19518c418d6048e913186e17243982c5e435e41fa7a/coverage-7.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:7af486dabe8954d03b087f0021540897afe084f04e16ff5579e08cc46f871416", size = 223394, upload-time = "2026-05-26T20:39:53.472Z" }, - { url = "https://files.pythonhosted.org/packages/73/c6/02d56e3867972f77d5036de924643f26c056e848f00452cafb4dbc3c29b4/coverage-7.14.1-cp313-cp313-win_arm64.whl", hash = "sha256:2224f89ffd0c5605ccce1ed7a584da162bc7c55f601ab1c946bc9de31a486b42", size = 222015, upload-time = "2026-05-26T20:39:55.374Z" }, - { url = "https://files.pythonhosted.org/packages/4d/9e/fcc77914050df73f7662fa1f00902774c79c075a8388ab334074574bf77e/coverage-7.14.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:de286598cc65d2b489411174b1faec2f5a7775fb3201fd925db2a76b4030f37d", size = 220733, upload-time = "2026-05-26T20:39:57.189Z" }, - { url = "https://files.pythonhosted.org/packages/f7/67/2963cbdaf5cbadec44efa3a1e39eaa1f02df4079585f05387607a221e126/coverage-7.14.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:042c46ded7c288aeb07cf14a28b6c1e10b78fcba40171c3fa1e939377eeef0b5", size = 221086, upload-time = "2026-05-26T20:39:59.019Z" }, - { url = "https://files.pythonhosted.org/packages/c8/c5/8701645574e11881f2f47d8930f98bc48b5d43b25eb5b4430dfc4a2f9f48/coverage-7.14.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f4ddbe407477f04c45115d1a4e5bc480f753553b534d338d4c3358b1cdd0ea52", size = 262381, upload-time = "2026-05-26T20:40:00.822Z" }, - { url = "https://files.pythonhosted.org/packages/7c/28/7a64d73598263e0c5abd5084211a8474488d31b3c552ff531c719dfcff62/coverage-7.14.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d13e6725992e2d2fd7d81d4f5241952d13740121dfd501da09201be39b2c003a", size = 264458, upload-time = "2026-05-26T20:40:02.506Z" }, - { url = "https://files.pythonhosted.org/packages/fa/d8/4969179db9f7eb4df218e69540adf829d1c835f59452513d065d15446802/coverage-7.14.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f747dc8edcfe740130f28f32f3995e955494285717e86ee25af51db2219df08a", size = 266884, upload-time = "2026-05-26T20:40:04.421Z" }, - { url = "https://files.pythonhosted.org/packages/a6/78/a45d5794dbc9bafd97afc96a4377c86c7820d78b6cf51b89bc1d4e919275/coverage-7.14.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ced2f09ef276fd58611a1ef502164ad266d2b75174e5a40cabbdb4033f9f6cf2", size = 268022, upload-time = "2026-05-26T20:40:06.298Z" }, - { url = "https://files.pythonhosted.org/packages/21/cb/4f5e354e9e3e67af96bd4e57113e6db6b22298c7168b13eec408a549903d/coverage-7.14.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b84800013769a78ccb9ef4659402e26d06867e337b61ec365f77ad008adea80e", size = 261631, upload-time = "2026-05-26T20:40:08.226Z" }, - { url = "https://files.pythonhosted.org/packages/ec/49/eced49af4cb996d5d8b7e94e736175c513e4facd3398507b89892b4326d8/coverage-7.14.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ea8cd6ca0ee9f616aaef3afc6882e32c2cbf18b00d96313ffd76af650574034d", size = 264443, upload-time = "2026-05-26T20:40:10.137Z" }, - { url = "https://files.pythonhosted.org/packages/f1/d8/5603a88a7c5913a6b54f6cb1a8c46f7b39cbb30f27cd3f492908da09b2d7/coverage-7.14.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:aa5e304a873fabddc11e484e9b6b738bd38bd7bed17b09aa84eecf5332e8b8bb", size = 262069, upload-time = "2026-05-26T20:40:11.999Z" }, - { url = "https://files.pythonhosted.org/packages/f0/59/2ae3cb79da554a06c8619d6c88ea19dd1e4aed4b834b6a83bb1fa243bdc5/coverage-7.14.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:5a1c5215be81035e629d5bc756650634d0bf31991038db7a0eccb90f025ce16d", size = 265780, upload-time = "2026-05-26T20:40:13.858Z" }, - { url = "https://files.pythonhosted.org/packages/af/5f/b130c1dc999031f2648bd25317fbce505ad8d5562079b4ed81e736a84967/coverage-7.14.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:79058c47dae6788504b5effb319961bcd72d7240551464b91d474bc0ed186d69", size = 260970, upload-time = "2026-05-26T20:40:16.142Z" }, - { url = "https://files.pythonhosted.org/packages/87/d1/ec13ccddeb48ec963bdfa72a11224bac2584bd045ba13beca82f8113e9c7/coverage-7.14.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:370c5afae3fa0658e11694a32b24c2778f6bc2d17718121f94ee185e69f26b54", size = 263157, upload-time = "2026-05-26T20:40:18.382Z" }, - { url = "https://files.pythonhosted.org/packages/cf/c2/cd91ead503045161092d3845f7bb95ea2f25131ce96d3e314dd835d91b9c/coverage-7.14.1-cp313-cp313t-win32.whl", hash = "sha256:3758dd0a7f1fa57365ef2e781df0f0731d38b6e3772259d13dae4bd8a958d4b1", size = 223259, upload-time = "2026-05-26T20:40:20.381Z" }, - { url = "https://files.pythonhosted.org/packages/71/9f/1e28d97e6bd2c76b07f38b7c02870f1371255ff6717f54eca578fcbbdd0e/coverage-7.14.1-cp313-cp313t-win_amd64.whl", hash = "sha256:6ff665fb023a77386fe11685190cee1f60a7d635994a30d9b0a061533d470fce", size = 224320, upload-time = "2026-05-26T20:40:22.316Z" }, - { url = "https://files.pythonhosted.org/packages/a9/e0/d936e908f0e1efa55e52b91e01b52f1055cef5e1ab2718493390ed8e2fb8/coverage-7.14.1-cp313-cp313t-win_arm64.whl", hash = "sha256:17a5a241e5997621a956a7f402a7433ef4221e5152809b785bec79e2323799f1", size = 222577, upload-time = "2026-05-26T20:40:24.894Z" }, - { url = "https://files.pythonhosted.org/packages/d6/34/fc2f101b151af3799a101f0550b0454aa008afdc0add677394ec4aa8ea10/coverage-7.14.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d5ed429d0b8edaac649e889b4ffcedb6c80b06629a3f93050e3dddfb99235bee", size = 220091, upload-time = "2026-05-26T20:40:27.249Z" }, - { url = "https://files.pythonhosted.org/packages/3d/a7/1ebae2ab5b961b5c79bb09fe7b3ac99edb190d8be4a8c510b2cf66f46468/coverage-7.14.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8011224a62280e50dab346960c03cf47aca1a1e09e608c0fb33fd6e0cc8e9500", size = 220421, upload-time = "2026-05-26T20:40:30.084Z" }, - { url = "https://files.pythonhosted.org/packages/5e/90/92aca9cf0acc95123c96cd1eb1f08917897a7f5dee01e15738922971ec31/coverage-7.14.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:12c42ec1e14f553c4f817e989365982e646e27211f10a0f717855b94a79c8906", size = 251466, upload-time = "2026-05-26T20:40:32.542Z" }, - { url = "https://files.pythonhosted.org/packages/26/2b/78048cbe3b999f6cbf9cc0d90abba6a88a3e0863a8c1c6cbc762f3f8802f/coverage-7.14.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:06144cd511cf2624873a035c5069cf297144f6e77a73ee3d7a55b605ec5efb42", size = 253973, upload-time = "2026-05-26T20:40:34.473Z" }, - { url = "https://files.pythonhosted.org/packages/8e/21/c2e33b29d1cfde484a19d437afc343c6cd30b08d78cbbf9f5aff14e57b2b/coverage-7.14.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a311d8e1da24be5c1ccf85cbfb06315dbaa1703d5a1eab3f6432c72b837917c8", size = 255318, upload-time = "2026-05-26T20:40:38.154Z" }, - { url = "https://files.pythonhosted.org/packages/8e/ee/aad2f108d63b769121005302f16bf66db8625c88ceaba466942e09a2607e/coverage-7.14.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c79cead5b5bc584d9c71451cb984d0e3a84e0c0937379c8efcbf27c8d661b851", size = 257633, upload-time = "2026-05-26T20:40:40.164Z" }, - { url = "https://files.pythonhosted.org/packages/c2/f8/11a2c29b4fd76d9849f81d0bb812ec0017a9396df3217214e38934a8c837/coverage-7.14.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dcbf65f1f66a26cdd88c35cf68fb4729c5d1cd2e88added72420541dfb212034", size = 251488, upload-time = "2026-05-26T20:40:42.631Z" }, - { url = "https://files.pythonhosted.org/packages/c9/b8/9a5820de4b8ac2b71d85e3b5fb49108d7469c665f0e2ad0dd7569023e305/coverage-7.14.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fd86572566fb40189a8260446158235159bc7a82dfbc87a3b39cf4fb57fcec1c", size = 253329, upload-time = "2026-05-26T20:40:45.208Z" }, - { url = "https://files.pythonhosted.org/packages/6b/ff/f33e4823667e27548e8fd8df44217515303f9808d0ff29817db56f87d990/coverage-7.14.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:7771b601718fdde84832c3a434ca9bbf4ae9adbc49d84198b4110700c3c77c36", size = 251291, upload-time = "2026-05-26T20:40:47.502Z" }, - { url = "https://files.pythonhosted.org/packages/68/9b/489db0ebb209054766b90a9014a45f6d26eb724c02ec21311c3733b5a644/coverage-7.14.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:39b21e212c55af06fa375e3dbf90a8a8e38792f3a910c580066d23563830ddd5", size = 255564, upload-time = "2026-05-26T20:40:49.372Z" }, - { url = "https://files.pythonhosted.org/packages/27/b5/16bc2d4c2409b23c7737edb68c83bc89e345f378050549fe1d75ac7d34d5/coverage-7.14.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f2302660e32562a532b442480121aef8aa61a5bdb20b30bf0adab29f10a5a4b4", size = 251107, upload-time = "2026-05-26T20:40:51.677Z" }, - { url = "https://files.pythonhosted.org/packages/7d/0c/2629997469a00cd069d588a41c9dc887610f2775ae89d250c4791e65272a/coverage-7.14.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:03a6f93c1ec3b7f2e77b5dbcc5573a2c21f12529a5c6bbe0f16f72303cc2fa4d", size = 252764, upload-time = "2026-05-26T20:40:54.267Z" }, - { url = "https://files.pythonhosted.org/packages/d2/ee/f78d63c8f079e0d7211c7e2401fa17e311514534ba61bae03e4b287ce4ab/coverage-7.14.1-cp314-cp314-win32.whl", hash = "sha256:8a3ce026d73290f42f08dafecbd82c193a74df280461fbf97300fec51fd133ee", size = 222837, upload-time = "2026-05-26T20:40:56.496Z" }, - { url = "https://files.pythonhosted.org/packages/dc/b9/be539854f93a70dfbeec69117f33ec70dc42ff0b65b5b07ab8d40d04228e/coverage-7.14.1-cp314-cp314-win_amd64.whl", hash = "sha256:114c95ef29302423b87d159075805f4ab973254a2638a5d7d046c94887cc87d7", size = 223650, upload-time = "2026-05-26T20:40:58.351Z" }, - { url = "https://files.pythonhosted.org/packages/fe/9e/24e2842fef40f35ac82ba3a7719c8023d011bf3bf652d0675316a9d088a1/coverage-7.14.1-cp314-cp314-win_arm64.whl", hash = "sha256:a07891c3f4805442b31b71e84ba3cf29ed1aa9a428284e06deeb4b23e5b46343", size = 222218, upload-time = "2026-05-26T20:41:00.321Z" }, - { url = "https://files.pythonhosted.org/packages/0a/1d/ac0a9df5fe31c1e8bdd658074905fc12844a05c1a7e3fdb8417e97c31e23/coverage-7.14.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1101a5ebb083aecb625ebb6209d4105b58f647b093cb2dc8122d7b33f743cfe1", size = 220822, upload-time = "2026-05-26T20:41:02.281Z" }, - { url = "https://files.pythonhosted.org/packages/32/cf/f964fd9aff20323f9f1a726c97135f8a76bcd87b92dad141a456a43f3c64/coverage-7.14.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:851b9e1e4e8a4608e77c79714b2e77c0970d2ed7202a05e92ae407817481887b", size = 221084, upload-time = "2026-05-26T20:41:04.593Z" }, - { url = "https://files.pythonhosted.org/packages/d8/5e/7e5ef2aba844de2b80d678619fcf0841b42e3f37f16411226f3fe4c1016f/coverage-7.14.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d5b89cdfb2ee051b71e8c3c70bd81a9eff81100f736a269136fe1a68efe00474", size = 262454, upload-time = "2026-05-26T20:41:06.641Z" }, - { url = "https://files.pythonhosted.org/packages/64/62/75809bded87015cc4935524218a2a8ed8dd1a8498bfed30a2f4f7a4b4d34/coverage-7.14.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0177614a0370f227888b4e436a7c55686d6a9f90eb1ade2b624ba685a1686e86", size = 264578, upload-time = "2026-05-26T20:41:08.556Z" }, - { url = "https://files.pythonhosted.org/packages/f3/42/d33392dc14633525012d2d504fa1a33b05538bf535f5c1d64675e5754b78/coverage-7.14.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d69af5dea2de76fc485a83032a630523f985198b7e25be901ec60181587b01e", size = 266981, upload-time = "2026-05-26T20:41:10.824Z" }, - { url = "https://files.pythonhosted.org/packages/2a/49/0157c4428c2aca7f1e09d5565930586fd5ae36f1655f08b0daa7cf1fcae1/coverage-7.14.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:35ab22d91de736e8966b980dc355cbcdd2c6dbbcfe275f9a2991bc8a91b3df65", size = 268112, upload-time = "2026-05-26T20:41:12.966Z" }, - { url = "https://files.pythonhosted.org/packages/96/26/86b9ce71f4092b1ed325ce1421698081df1286b833400b6836912834d6e0/coverage-7.14.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:357d4e32935c36588aaba057d734fa32428c360c9fc2e4442afbf1b646beee6e", size = 261558, upload-time = "2026-05-26T20:41:15Z" }, - { url = "https://files.pythonhosted.org/packages/20/4c/c311210c5472cf5401d8422b0d7812cdd520f24417673afabda6c323faca/coverage-7.14.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:51bd64741cc6fa065abd300ede1afe5a5291ece9c31da8b24884deda48bcc3f8", size = 264447, upload-time = "2026-05-26T20:41:17.369Z" }, - { url = "https://files.pythonhosted.org/packages/fb/71/59513f8710ed3e6b0ac0a050a5b7e977bb9c9e880354863b5d00d8809256/coverage-7.14.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:9132cd363a68a4c3daa7c8704a654b1e39d3360f6f5b8ddd470608a945236c07", size = 262048, upload-time = "2026-05-26T20:41:19.309Z" }, - { url = "https://files.pythonhosted.org/packages/84/8d/bceed32dc494f5bbf50f775cd2e78ca814953942b5ea28d3c1c3ac316f14/coverage-7.14.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:07c6290b1697b862c0478eab545eec949a0d0e4d6d03497f446d706da3b4f2de", size = 265781, upload-time = "2026-05-26T20:41:21.559Z" }, - { url = "https://files.pythonhosted.org/packages/e7/c5/9348fe40dbfd4991aaf78df2c6c3098bfb2cc834d1fd362a64b4efef855a/coverage-7.14.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:5ea0c297e27133853b4d8a3eb799bff5a2dbd9f2f41537a240d337ac9b4df890", size = 260896, upload-time = "2026-05-26T20:41:23.428Z" }, - { url = "https://files.pythonhosted.org/packages/ca/92/1ea0f03929da7cf87206b1fa24f4c8e9c158be0455481af29ec0a1f3503f/coverage-7.14.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:01b7733daad0237daa01ef80fe2dfceffc911e6a17fa7b55d14aa8214eaaaecd", size = 263214, upload-time = "2026-05-26T20:41:25.419Z" }, - { url = "https://files.pythonhosted.org/packages/f6/a9/b2493c054c0e01a643266742ab45e15744e60743f9260cd930c7142b1124/coverage-7.14.1-cp314-cp314t-win32.whl", hash = "sha256:6adc5a36984624a70bf11d7184e20fa0a49aa7c47ffab43804106a1a695ea22e", size = 223624, upload-time = "2026-05-26T20:41:27.795Z" }, - { url = "https://files.pythonhosted.org/packages/fc/bd/3e1e6a57fccd2d7c83fcdf338e93ba98eb85c6e877dd34731ac585375490/coverage-7.14.1-cp314-cp314t-win_amd64.whl", hash = "sha256:ddf799247318f34dbcd2efa8c95a8d0642674e926bb1774cf9b63dfd2a389d1c", size = 224728, upload-time = "2026-05-26T20:41:30.098Z" }, - { url = "https://files.pythonhosted.org/packages/bb/d7/31066cf1d2f0c6c797fce911bcfa01dd35642dc6da992a950256097c5860/coverage-7.14.1-cp314-cp314t-win_arm64.whl", hash = "sha256:145986fe66647eb489f18d9a997567a3fd358584c4b5a808769113abc07466af", size = 222752, upload-time = "2026-05-26T20:41:32.123Z" }, - { url = "https://files.pythonhosted.org/packages/8a/3c/1a983b9a745d7f83d53f057bcc5bf79ba6a2bbc08266b3f0c7d6fe630c9b/coverage-7.14.1-py3-none-any.whl", hash = "sha256:a252f21c27e38347e60111a3266b03827422a7d5525951aceee313aa68bab1d2", size = 211815, upload-time = "2026-05-26T20:41:34.078Z" }, + { url = "https://files.pythonhosted.org/packages/04/d5/d0e511247f84fa88ae7da68403cbd3bf9d2a5fc48f5d6618a6846b275632/coverage-7.14.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:909f265c8c41f04c824bf741b2601fdcb56cab4bf56e018996b6494192ba0f58", size = 220352, upload-time = "2026-06-20T14:47:28.61Z" }, + { url = "https://files.pythonhosted.org/packages/03/4a/ecaff6db72e6c1782ca51336e391393f1e9cc6e4412d6c3da8b7d5075adf/coverage-7.14.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c8102deaf911938233f760426e6a5e287388521de95111d5c8de26c8a1028924", size = 220855, upload-time = "2026-06-20T14:47:29.972Z" }, + { url = "https://files.pythonhosted.org/packages/34/9a/cf950cd8e8df06ee5941276e69f81647005360421be523d5ca18f658e143/coverage-7.14.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:851f49e7bd7d1cdaf328f3133942b252d5e3d3380690131f423cba8e435b87f5", size = 251276, upload-time = "2026-06-20T14:47:31.413Z" }, + { url = "https://files.pythonhosted.org/packages/9d/08/f973be32c9a095e4bb2d3a7bdcb2f9c117e39d4062471ffffae3623f6c51/coverage-7.14.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:04cb445bed86aaf00aaa97d41a8b6e30f100f21e81c34caaec4efc684cb57768", size = 253189, upload-time = "2026-06-20T14:47:32.727Z" }, + { url = "https://files.pythonhosted.org/packages/96/aa/f3a50952ba553d442d94b793e5dede25d426b02e5e011e9a9dd225c002d3/coverage-7.14.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7471bc920d97c51c37ea8127f13b2adca43c3d78c53313b26a1f428e99d2c254", size = 255299, upload-time = "2026-06-20T14:47:34.019Z" }, + { url = "https://files.pythonhosted.org/packages/e0/29/9a4c491986f4d637ed64961ae56721661fc21b6b767d280848d0c708756a/coverage-7.14.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:da5057e1bb257c967feee8ba67f3ebf379e801c7717f238b3d8c9caf00fc8f93", size = 257255, upload-time = "2026-06-20T14:47:35.397Z" }, + { url = "https://files.pythonhosted.org/packages/dd/61/d2a5b48007f6a212f321c36cf5486feb80505d2d00dfb1163aad2da71197/coverage-7.14.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:33c0da852e8a40246cd8e20cf3b2fc17ca52a45e9b5f7983c93db26f5d24b87b", size = 251417, upload-time = "2026-06-20T14:47:36.677Z" }, + { url = "https://files.pythonhosted.org/packages/ea/25/8df66ae25b401d4529e1d0617af20d9695d171ea4ffec4ca9dffc5dc37b7/coverage-7.14.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f48a85bb437fab7782021c40bfee6b15146928b96960d008ace41b6901a0f21d", size = 252991, upload-time = "2026-06-20T14:47:38.027Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7b/16bdc9116dd8bf412a421a7227daa65ad9f12bef0685b13c1bd1c12e6d4c/coverage-7.14.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f44e7579a769a21d5b5e3166916bfe30ee175aaffff750324cbb11be2dbec5ad", size = 251051, upload-time = "2026-06-20T14:47:39.26Z" }, + { url = "https://files.pythonhosted.org/packages/0a/f8/b7dbed84274dcc69ddb9c0fe72ec1260830473e0d6c299dcf087a0567f7c/coverage-7.14.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:78853ca3c6ca2f012daa2b07dbabbb8db0f09d4dbe8ee828d294b3445d3f4cd8", size = 254817, upload-time = "2026-06-20T14:47:40.995Z" }, + { url = "https://files.pythonhosted.org/packages/c6/07/4659e6bed01a25a0effb4952e8e75fd157038fe5f2829b0f69c6811c2033/coverage-7.14.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:c9c2795ee3692097ff226ab806005d36bb9691fca9b35353542b57ea749cc830", size = 250772, upload-time = "2026-06-20T14:47:42.306Z" }, + { url = "https://files.pythonhosted.org/packages/26/f4/45019da4cd6cd1df3042476447449d62a76a201f6b3556aa40ac31bce20b/coverage-7.14.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2f5cc48a845d755b6db236f8c29c2b54773eb4c7e4ee2ead43812d73718784b0", size = 251679, upload-time = "2026-06-20T14:47:43.703Z" }, + { url = "https://files.pythonhosted.org/packages/92/e5/76d75fa2ffe0285d3f2608d1bb241fc245cf98fe614d52118427dd6ccdaa/coverage-7.14.2-cp311-cp311-win32.whl", hash = "sha256:9c61cb7eaabcfa609c5bc0f5ff5869d72a2f02f17994e5fba5f971de516f3c82", size = 222445, upload-time = "2026-06-20T14:47:45.137Z" }, + { url = "https://files.pythonhosted.org/packages/57/59/696c64547e5c8b9ed31532e9c7a5f9b6474054da93f8ab07f8baf7365c57/coverage-7.14.2-cp311-cp311-win_amd64.whl", hash = "sha256:e715909b0966d1774d8a26e14e2f4a3ae75909dca526901c6306286b2dcbfbdc", size = 222922, upload-time = "2026-06-20T14:47:46.67Z" }, + { url = "https://files.pythonhosted.org/packages/63/72/646a28100462996c11b98e27d6786cd61f48100d1479804846a3e1e5bf9b/coverage-7.14.2-cp311-cp311-win_arm64.whl", hash = "sha256:9193f7150937a4fd836b10eaa123e15d98e961d1fabac07e60adf2d4785f888a", size = 222468, upload-time = "2026-06-20T14:47:48.119Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d9/bdd141aa2c605096a8ef63b8435fd4f5fec78946a3cb7b9145840ec78291/coverage-7.14.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:37c94712e533ea06f0b1e4d934811c520b1914ce0e4da3916220717aa7a86bc6", size = 220528, upload-time = "2026-06-20T14:47:49.652Z" }, + { url = "https://files.pythonhosted.org/packages/02/97/d24ae7d2afc62c54a36313d4dedb655c9afbba3003f0f7f1ae81e97af31f/coverage-7.14.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c050bbc7bba94c77e4ed7438f4fda1babe98ab145691d80aa6f60df934a1468b", size = 220883, upload-time = "2026-06-20T14:47:51.036Z" }, + { url = "https://files.pythonhosted.org/packages/f8/0e/d8f00efd3df0d63e6843ebcbade9e4119d60f5376753c9705d84b014c775/coverage-7.14.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a7af571767a2ee342a171c16fc1b1a07a0bf511606d381703fb7cf397fe49d46", size = 252395, upload-time = "2026-06-20T14:47:52.627Z" }, + { url = "https://files.pythonhosted.org/packages/1c/1c/ab9510dfe1a16a35a10f90efad0d9a9cf61b9876973752968f2ba882f73f/coverage-7.14.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8b4910cce599cd2438f8da65f5ef199a70a1cdb6ab314926df78271ca5954240", size = 255131, upload-time = "2026-06-20T14:47:54.235Z" }, + { url = "https://files.pythonhosted.org/packages/ba/dd/70171e9371003b33dc6b20f527ac216ff91bbe5c1088e754eb8950d79193/coverage-7.14.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c33e9e4878972f430b0cc06de3bf2a28d054a9efb4f8426d27de0d9cb81396ff", size = 256246, upload-time = "2026-06-20T14:47:55.61Z" }, + { url = "https://files.pythonhosted.org/packages/0f/80/a68b1dd81d5c011e17fd6ab0d707d33297df1d0c618114b9b750a2219c80/coverage-7.14.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e7967ea55c6dea6becba4d5870e2fa0aa4915a8be7ebff1bb79e6207aa75ce8d", size = 258504, upload-time = "2026-06-20T14:47:56.979Z" }, + { url = "https://files.pythonhosted.org/packages/8e/7b/40baaa946189f5317cd77d484e39b9b0727d02ebada0a12162374f2faee2/coverage-7.14.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d1322f237c2979b84096f4239c17828ff17fea6b3bbe96c44381c5f587c44c26", size = 252808, upload-time = "2026-06-20T14:47:58.418Z" }, + { url = "https://files.pythonhosted.org/packages/d5/05/b19517b09c43d1e8591de6c13178b0c03166c31e1adbebda378e64c66b9a/coverage-7.14.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:77849525340c99f516d793dddbcee16b18d50af892ac43c8de1a6f343d41e3b5", size = 254166, upload-time = "2026-06-20T14:48:00.004Z" }, + { url = "https://files.pythonhosted.org/packages/ae/f5/6e65da5957e041d2094a9b97736628dd80160f1cc007a50790bbb2668c1a/coverage-7.14.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ef11695493ec3f06f7b2678ca274bcabb4ca04057317df268ddbfd8b05f661a8", size = 252310, upload-time = "2026-06-20T14:48:01.458Z" }, + { url = "https://files.pythonhosted.org/packages/2d/de/01b5274f0db63175b04d9354eff68d2d268b8b57a1b2db7d3dcb1f2c9dbb/coverage-7.14.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8134f0e0723e080d1c27bbe8fc149f0162e429fa1852482150015d0fce83eaf1", size = 256379, upload-time = "2026-06-20T14:48:02.981Z" }, + { url = "https://files.pythonhosted.org/packages/71/d6/9a2ffbca41e2f8f86f61e8b78b86afa433ec8cdeac4908ace93a28fe3ff0/coverage-7.14.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:914eead2b843fc357f733b3fe39cc94f1b53d466e8cfe03080b1ed9d24ccfc73", size = 251880, upload-time = "2026-06-20T14:48:04.463Z" }, + { url = "https://files.pythonhosted.org/packages/e3/ff/20bd54a43c88c08f474e6cb355a97e024e38412873ef0a581629abe1e26f/coverage-7.14.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e4b2d5e847fb7958583b74910cc19e5ec4ece514487385677b26433b2546116e", size = 253753, upload-time = "2026-06-20T14:48:05.99Z" }, + { url = "https://files.pythonhosted.org/packages/35/2a/2b3482c30d8344f301d8df6ff232a321f2ab87d5ac97ba21891a68638131/coverage-7.14.2-cp312-cp312-win32.whl", hash = "sha256:e753db9e40dda7302e0ac3e1e6e1325fb7f7b4694f87a7314ab15dd5d57911a7", size = 222584, upload-time = "2026-06-20T14:48:07.361Z" }, + { url = "https://files.pythonhosted.org/packages/f6/5e/83934ffff147edd313fe925db426e8f7ccad9e4663262eb5c4db4e345658/coverage-7.14.2-cp312-cp312-win_amd64.whl", hash = "sha256:d32e5ca5f16dafb269ee50b60d32b00c704b3f6f78e238105f1d94a3a5f24bf5", size = 223118, upload-time = "2026-06-20T14:48:08.837Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ee/616b4f38a34f076f3045d3eedfa764d34d82e6a6cc6b300acb0f1ff22a98/coverage-7.14.2-cp312-cp312-win_arm64.whl", hash = "sha256:dc366f158e2fb2add9d4e57338ca48f12611024278688ee657eb0b853fcb5de5", size = 222504, upload-time = "2026-06-20T14:48:10.436Z" }, + { url = "https://files.pythonhosted.org/packages/6d/09/b5b334c27960e7aac0003b96491bada7838dc641099fa64a1a598abf33cd/coverage-7.14.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e5f077641a6713ce9d38df9e85d4fb9e008677fc0775cbaeb32ddfc3b319d4ca", size = 220552, upload-time = "2026-06-20T14:48:11.847Z" }, + { url = "https://files.pythonhosted.org/packages/79/20/879a000c319b4df7b50e4d688c0f7c0f6b5ac9d7b18848cbc00eabf26efe/coverage-7.14.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0907f39b49ae818fe8af50aaa0f19afbc8ca164aea0865181ca7af17a3ac690b", size = 220919, upload-time = "2026-06-20T14:48:13.397Z" }, + { url = "https://files.pythonhosted.org/packages/f6/b7/326dded4371bab60f42215797944a356e4d81a3cee106121c7f7dd531604/coverage-7.14.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5734d47669118d75c28981e562d4530ceb77342d31ffef6def5edd5ad4f05d7b", size = 251917, upload-time = "2026-06-20T14:48:14.931Z" }, + { url = "https://files.pythonhosted.org/packages/eb/14/b3232ba218a0d1a70883d2675f18ff465de9e8e5e3346e81dc2b079838bd/coverage-7.14.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1d9a1b5813d00ea6151f6ccf64d1fa16892771dfdda12ba87162d15ec4ea3e1e", size = 254515, upload-time = "2026-06-20T14:48:16.545Z" }, + { url = "https://files.pythonhosted.org/packages/b7/7a/d77bcbee1cad71b42776574114b462225cc9125b4982f43da1b66adc850f/coverage-7.14.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f0a80f4c8ac3f774210b1cc1bc0e31e75502f2818dda9a144ff90e702c4d91d", size = 255749, upload-time = "2026-06-20T14:48:18.214Z" }, + { url = "https://files.pythonhosted.org/packages/86/86/97377937b29e9e44a1529bb20cb74dbcf80ed9006d87d7e742ff69e44b67/coverage-7.14.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2e66f3f22d6c1515ce70f2e7c3e9c6f3ff0ff33480125c9f9c53e8f6508e30f", size = 257882, upload-time = "2026-06-20T14:48:19.7Z" }, + { url = "https://files.pythonhosted.org/packages/c1/a4/0fc8fe68bc505450bb068a2823ac7797bd8495240ccb8b4a5a1da1ee7e62/coverage-7.14.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6a2c37c3114f87ca7f10113756026eecb49656514debad600dcbec21f355ccea", size = 252144, upload-time = "2026-06-20T14:48:21.176Z" }, + { url = "https://files.pythonhosted.org/packages/8d/4a/450094ddc41ab0d2eb4a0457b3856400ea3329568d1303696e85de099ae6/coverage-7.14.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3b16a7959d04b1497281c062c180413565c3f3469211d78799ad5b9a75f67796", size = 253882, upload-time = "2026-06-20T14:48:22.701Z" }, + { url = "https://files.pythonhosted.org/packages/d0/28/2f6ae6d98265d9aa6bac311c4a93403675905b03aca95dc4373080279d75/coverage-7.14.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6466c6999545cf00c4c142dfcbbf2db396dc735f005dcf8f91d57e351a79472b", size = 251846, upload-time = "2026-06-20T14:48:24.295Z" }, + { url = "https://files.pythonhosted.org/packages/c2/6e/707281468400794d52874e8fb5e38ff7578a0ff32ed49fe4fe85f192d0fc/coverage-7.14.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c60915ebb8f562317ba5ff6b8c32e25c0882289b201a9f2fb2987f91efd95d8", size = 256002, upload-time = "2026-06-20T14:48:26.015Z" }, + { url = "https://files.pythonhosted.org/packages/c2/83/5e963120de4011257a950ce4cfb7fc833ddf3fee19db495268d3dec28154/coverage-7.14.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:33b830850488acbcd358c78a4fecfafe7031667b4da8ddff5546295dc962cdeb", size = 251665, upload-time = "2026-06-20T14:48:27.654Z" }, + { url = "https://files.pythonhosted.org/packages/e9/78/66b482cd525083bcc0bc894c16db79dabac37490065b53b07d6e8ab77202/coverage-7.14.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d0f845539230b8269aec902bc978b0cc403f52f002d18a04492efc943404d0bc", size = 253435, upload-time = "2026-06-20T14:48:29.354Z" }, + { url = "https://files.pythonhosted.org/packages/e6/61/0663fb8cb530c8b11819b920109694eee95a3b22960a9495be0200f657f1/coverage-7.14.2-cp313-cp313-win32.whl", hash = "sha256:a8ac51a2e441e9119b9395f4d893fbc4934c64c8ba58be9b9eaa85591249e548", size = 222591, upload-time = "2026-06-20T14:48:31.142Z" }, + { url = "https://files.pythonhosted.org/packages/a6/47/1536d2b009c2848c3682500f497053f4645e70911afe02f594000997831a/coverage-7.14.2-cp313-cp313-win_amd64.whl", hash = "sha256:039b264cdb31c44b48f9821e2afbf8f37df49e0fb837e24a942918b36c567e31", size = 223134, upload-time = "2026-06-20T14:48:32.696Z" }, + { url = "https://files.pythonhosted.org/packages/28/9a/33ba4f335dd60bb34350318283d784f46018070e67b7d4df7c910ec9d9a0/coverage-7.14.2-cp313-cp313-win_arm64.whl", hash = "sha256:7f2ef591e381cc36b8e53334e1b842c760c520c8a52d01e8626209400e93fe6a", size = 222529, upload-time = "2026-06-20T14:48:34.237Z" }, + { url = "https://files.pythonhosted.org/packages/fc/bc/120390669817ede714ab141ae0a2a73240fd7354aac992c41dc0bd19570f/coverage-7.14.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7a0d1f026b72d627fa5c8a57cbc86ad209b64aa2a65833c83b290ace5cbee126", size = 220593, upload-time = "2026-06-20T14:48:35.755Z" }, + { url = "https://files.pythonhosted.org/packages/4f/a3/7f1cfacd76af91e585f7ad689d7168002b444ed2a8ce59f2daaff10089b5/coverage-7.14.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4d2b86f81c1c9310a7e774e3cc9e927a3d0bf583ecbfa01498dd626930025428", size = 220925, upload-time = "2026-06-20T14:48:37.35Z" }, + { url = "https://files.pythonhosted.org/packages/e7/10/6514b2525bb672eb8b43703e46d061d694111db21efe7609db722df2233f/coverage-7.14.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d76bdc1f9396ae70a55d050cf9743d88141c62ce0a22a3f627fab1d11c2f8bc6", size = 251974, upload-time = "2026-06-20T14:48:39.109Z" }, + { url = "https://files.pythonhosted.org/packages/23/b4/4533091541c6620ecd68115bbfa1c61265b775618adef3a5fd137f4582e9/coverage-7.14.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cda36d8e7bfd63b3e44e75163265429caa5d935b672b00f71bccc8c010518c64", size = 254479, upload-time = "2026-06-20T14:48:40.871Z" }, + { url = "https://files.pythonhosted.org/packages/06/af/e251a143d5d106385dbca696c553afab6b69f7f6bc376a34e089cc0b8b32/coverage-7.14.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0904f3b79d7b845bef0715afe1900da634d12b97f05b9479cb472880ca07cb9c", size = 255824, upload-time = "2026-06-20T14:48:42.608Z" }, + { url = "https://files.pythonhosted.org/packages/9c/53/9e5876e60efbaa79d743d1948a5015ddc05b808db1cd62228acf83e87d43/coverage-7.14.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b6795ca4198d6cb7fc2c6163214f6555a6bc5f0ae1e268e76139dec4b37c4499", size = 258139, upload-time = "2026-06-20T14:48:44.263Z" }, + { url = "https://files.pythonhosted.org/packages/85/5a/d35a4f431fb594e46b81cad4a13b470b017e918f347c1c0b260f7494fa1e/coverage-7.14.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c41e9b60fc0fa57f5d73306417d2f9d668202cca6944f9435878c55a5e7ae213", size = 252002, upload-time = "2026-06-20T14:48:45.961Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e2/f5b304c8139c606c4f1b230d3a257d0c88edfbbdf06c58364f07625dc45c/coverage-7.14.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:419d2aadd5746efc2e9df0f33c05570d8192e6f6a6098ab05acce586f44ce8a5", size = 253832, upload-time = "2026-06-20T14:48:47.582Z" }, + { url = "https://files.pythonhosted.org/packages/86/bc/bbbd283daa6be4f68aad4ad4066fd39ae98e4174db8c03ab26c5803d6234/coverage-7.14.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:1c5d273c5f1411c0d26c4f066c398d4a434b1f97bb5fa409189bedce86d4add4", size = 251799, upload-time = "2026-06-20T14:48:49.42Z" }, + { url = "https://files.pythonhosted.org/packages/69/8d/0745fceb89c9e5f7dd8ed243d97dc8561b7a95545741e2409d2b34654824/coverage-7.14.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5fe465bc691264adce601527a972990c1174075d86bcbe9968fd20c95e0b1948", size = 256075, upload-time = "2026-06-20T14:48:51.065Z" }, + { url = "https://files.pythonhosted.org/packages/a2/a0/441d9a5255cf021ab41ee00c014a4607d1c72d5e5bef0a4fdaa5be86a907/coverage-7.14.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:6fbb61617af1c56f95d53170ae9fa6c9aef6de1abd02fcc50064bfc672efb18d", size = 251612, upload-time = "2026-06-20T14:48:52.653Z" }, + { url = "https://files.pythonhosted.org/packages/50/37/3d19c5e32d4a529c068eb296abfa3e455bd2c0f9311ecf26280f408ff8e0/coverage-7.14.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e1eff22b831dfd5694989cc1f0789980f18391f614ac67c851af9a8e6d25e9ba", size = 253270, upload-time = "2026-06-20T14:48:54.3Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b0/54dd13937297518da6d092cc2c39d9340ec2194bdfa92e0a64694d643e23/coverage-7.14.2-cp314-cp314-win32.whl", hash = "sha256:58e91be0a233adef698d3e6be54f10401bb91fd7854c0d4c4d50e0d3711e72f1", size = 222796, upload-time = "2026-06-20T14:48:56.084Z" }, + { url = "https://files.pythonhosted.org/packages/51/45/7a10e0909919686e335fdd95869cfb222d55243ebff27dc5cf59ca259a1f/coverage-7.14.2-cp314-cp314-win_amd64.whl", hash = "sha256:d8429bf97906bfe6c61f9dbfb3342e0d88120da61939da8bd04f830cc3eab3b8", size = 223285, upload-time = "2026-06-20T14:48:57.729Z" }, + { url = "https://files.pythonhosted.org/packages/2e/03/9cb197eb4b3d1a2eccb2537c226a93c80522c5b8afc5dd93e1993d7bb021/coverage-7.14.2-cp314-cp314-win_arm64.whl", hash = "sha256:13609d9d77249447aa73357b14831b0f3b95f275026c9ff20dd105f981f53a0c", size = 222712, upload-time = "2026-06-20T14:48:59.413Z" }, + { url = "https://files.pythonhosted.org/packages/d6/3c/e59f498511080d20bf866b0af9eeab820feb91547dae2084cb9bb7fb0e58/coverage-7.14.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9818486c2bac88ae931df7e04905ee29bef49fd218c00f5f02bed4855254a101", size = 221325, upload-time = "2026-06-20T14:49:01.447Z" }, + { url = "https://files.pythonhosted.org/packages/d3/37/8d7955f7e701e69198bd0a0132ea76518c078a635b930a4924e2ccfa70f0/coverage-7.14.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:58055adffabfa243516a197aa9f85f0dd56d905b0fba1a10193269759c29ccb0", size = 221594, upload-time = "2026-06-20T14:49:03.13Z" }, + { url = "https://files.pythonhosted.org/packages/34/7a/6738e1e1533ce8ec4e2e472696eefdd4723864d7efaa140e433053bf576a/coverage-7.14.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:535747dbc200349d7fb434cffcb28e770f0290f69b225f56dc3803aa7210cdea", size = 262957, upload-time = "2026-06-20T14:49:04.829Z" }, + { url = "https://files.pythonhosted.org/packages/35/c4/d1be863cd39e0955904315fece67c5c23e046563f5eea0ceac16c547a759/coverage-7.14.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:420c66e35d85c0ca5dc6a38147d83ef239762542900e5921ebbdb89333c540ea", size = 265081, upload-time = "2026-06-20T14:49:07.018Z" }, + { url = "https://files.pythonhosted.org/packages/72/7f/412df3c3c251284a11834287fd6f7e3bb98c528c53e030589e9344a3ef80/coverage-7.14.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f2cf17b33773be446a588551ea6a746b2d70dd0bc90dc31f1dd7648975a63c6b", size = 267500, upload-time = "2026-06-20T14:49:08.709Z" }, + { url = "https://files.pythonhosted.org/packages/54/68/7d0764e83459455384d5c04179ce2d2a837bef01b9ba463079c6e8b31361/coverage-7.14.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:adb4a5fef041f7179bb264203add873c147d169cf2f8d0adae89ff2e51271bac", size = 268619, upload-time = "2026-06-20T14:49:10.405Z" }, + { url = "https://files.pythonhosted.org/packages/14/68/1292164ac70cbcc86ac3982da31a6fbb42bb4bcebf6e5cf73c99cfcfd50d/coverage-7.14.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9c012ec357dec9408a83dad5541172a63c5cfa1421709f2e5811480d31ae1b28", size = 262066, upload-time = "2026-06-20T14:49:12.257Z" }, + { url = "https://files.pythonhosted.org/packages/20/44/fd6fdf3f63b6e00a1a9230022d072ded5189576001685706aa6524187c65/coverage-7.14.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:dacd0ecd08fda3cb2f85b60cabea7da326dcb2fc15fbb23a88830a80144cc9f2", size = 264953, upload-time = "2026-06-20T14:49:14.13Z" }, + { url = "https://files.pythonhosted.org/packages/39/29/e803fea3da89eaeb5b6b41b3ccd039fe9f3300a900e3803baac1a998529f/coverage-7.14.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:f27e980f2feba5dfe7a32b22b125470de69c0bd113c75e16165de909a777f512", size = 262555, upload-time = "2026-06-20T14:49:15.803Z" }, + { url = "https://files.pythonhosted.org/packages/32/3c/b360e48ac68e3236c04cb83658382e7f5be7efbbec2e1faae3dcca432783/coverage-7.14.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:105c00efb65c863630b2b63cbf7b8267e4da2d44b62284efbb19a03b04c337d4", size = 266289, upload-time = "2026-06-20T14:49:17.962Z" }, + { url = "https://files.pythonhosted.org/packages/59/12/1ed6d9274d599c586e2d1aa9818765dcdae6bb52aa88afa2fcd868398191/coverage-7.14.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:571173fa04c8e8d6235ab32ae67fecca97777e2e1b4a1a30f3022c34e397c1c1", size = 261402, upload-time = "2026-06-20T14:49:19.708Z" }, + { url = "https://files.pythonhosted.org/packages/44/17/eb6cf12a4538cda937aefbeabb15377a8a30b377b484e63d31c9da790966/coverage-7.14.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e532f34d42d1a421fa00ed6b7735d14ac2e340256c1bad26a5e1dc1252b0bed7", size = 263715, upload-time = "2026-06-20T14:49:21.427Z" }, + { url = "https://files.pythonhosted.org/packages/8a/ca/4bafdb9d372ab05d6ed3a63e7f00d3195d169d0afea00f617c026e386c19/coverage-7.14.2-cp314-cp314t-win32.whl", hash = "sha256:243971550fb46c3039257f75e65610002d84304c505f609bbd9779e20a653a0a", size = 223103, upload-time = "2026-06-20T14:49:23.24Z" }, + { url = "https://files.pythonhosted.org/packages/35/cb/0765dbd9011d2e47315f1da31e62c5fe231f04a6ec8da213e64c4505896d/coverage-7.14.2-cp314-cp314t-win_amd64.whl", hash = "sha256:60fb0ca084a92da96474b8b405a7ea76dfecac3c68db54383e7934b6f3871169", size = 223934, upload-time = "2026-06-20T14:49:25.347Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ce/373dde027ecd0ae54511430fe7569f838d3a0376b70333ba9fd20c76b836/coverage-7.14.2-cp314-cp314t-win_arm64.whl", hash = "sha256:36a0a3f42ed7dfdbca2a69a541519ffd5064a5692152fc0018109e74370d7345", size = 223249, upload-time = "2026-06-20T14:49:27.241Z" }, + { url = "https://files.pythonhosted.org/packages/a3/5e/a8ba14ceb014f39bd5e3f7077150718c7de61c01ce326bfe7e8eae9b19b2/coverage-7.14.2-py3-none-any.whl", hash = "sha256:04d92589e481a8b68a005a5a1e0646a91c76f322c397c4635298c57cf63699b5", size = 212325, upload-time = "2026-06-20T14:49:28.991Z" }, ] [package.optional-dependencies] @@ -344,33 +329,33 @@ wheels = [ [[package]] name = "distlib" -version = "0.4.1" +version = "0.4.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/86/b2/d6fc3f2347f43dada79e5ff118493e8109c98400a0e29a1d5264a3aa479b/distlib-0.4.1.tar.gz", hash = "sha256:c3804d0d2d4b5fcd44036eb860cb6660485fcdf5c2aba53dc324d805837ea65b", size = 610526, upload-time = "2026-06-02T11:17:40.691Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/02/bd72be9134d25ed783ecbbc38a539ffaefbf90c78418c7fb7229600dbac7/distlib-0.4.3.tar.gz", hash = "sha256:f152097224a0ae24be5a0f6bae1b9359af82133bce63f98a95f86cae1aede9ed", size = 615141, upload-time = "2026-06-12T08:04:52.847Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/18/3497c4fa83a76dcb154923fd2075522e8dd6995ecee4093c00ae18160046/distlib-0.4.1-py2.py3-none-any.whl", hash = "sha256:9c2c552c68cbadc619f2d0ed3a69e27c351a3f4c9baa9ffb7df9e9cdc3d19a97", size = 469216, upload-time = "2026-06-02T11:17:38.779Z" }, + { url = "https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl", hash = "sha256:4b0ce306c966eb73bc3a7b6abad017c556dadd92c44701562cd528ac7fde4d5b", size = 470628, upload-time = "2026-06-12T08:04:50.506Z" }, ] [[package]] name = "fakeredis" -version = "2.36.0" +version = "2.36.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "redis" }, { name = "sortedcontainers" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/40/26/9cf5174d2e42c24761f87d76160f959e7dbea1cf35b2c81c9cfcd7c74ad9/fakeredis-2.36.0.tar.gz", hash = "sha256:66d00953c9bfd3e345266ded342a2e54c611417344f43ad1467cb68f30bc8354", size = 209484, upload-time = "2026-05-29T19:14:27.316Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/ed/86ed74d8829c3bc565025c1c9efaf8518c9165fbf8c9cc2c026c8ed21bd9/fakeredis-2.36.2.tar.gz", hash = "sha256:c37a0b307fae3f27ec7c19e59519e57b8c52782e00303df9075361b5ba441be6", size = 213336, upload-time = "2026-06-17T13:25:38.934Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/66/5a/6ea14103f9254f0bef31d323e8fe1f57bc0151f7a4b287889217752e9879/fakeredis-2.36.0-py3-none-any.whl", hash = "sha256:43536ed9eb7af34a31226ee4e52c8471d1c3cc522ba2852a9985d3291e71287b", size = 138102, upload-time = "2026-05-29T19:14:25.974Z" }, + { url = "https://files.pythonhosted.org/packages/fd/4d/e6e40f93031adbf654d34e542bd396e9f3bc1c6209b40c920d58c9e1317a/fakeredis-2.36.2-py3-none-any.whl", hash = "sha256:84cbb9c74ca8946c0d2499daadf3a5d0bfe3cfbac71e3398316d1a1eab3421c4", size = 141039, upload-time = "2026-06-17T13:25:36.638Z" }, ] [[package]] name = "filelock" -version = "3.29.1" +version = "3.29.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1f/f9/f38573ed5844586db374d085911740a501ccfa373b455fc9413f09f85237/filelock-3.29.1.tar.gz", hash = "sha256:d97e6b1b9757569626c58caa07dc4beb1613f4a2938b1e8cc81afca398906c9e", size = 59335, upload-time = "2026-06-03T15:19:04.053Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/dc/be6cbe99670cd6e4ad387123647cb08e0c32975e223f82551e914c5568a6/filelock-3.29.4.tar.gz", hash = "sha256:10cdb3656fc44541cdf30652a93fb10ec6b05325620eb316bd26893e4201538a", size = 63028, upload-time = "2026-06-13T16:12:00.744Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4c/a0/614c5fe402fd88951df45f4dda2fa3b4e17a99ecd92340771929169b3b95/filelock-3.29.1-py3-none-any.whl", hash = "sha256:85199dfd706869641b72b2e8955d5416a4b2b7dc4b0e8e6d97b4cc1299a6983b", size = 40750, upload-time = "2026-06-03T15:19:02.959Z" }, + { url = "https://files.pythonhosted.org/packages/13/37/a065dc3bd6e49423a6532c642ca7378d3f467b1ef44c2800c937af7f9739/filelock-3.29.4-py3-none-any.whl", hash = "sha256:dac1648087d5115554850d113e7dd8c83ab2d38e3435dde2d4f163847e57b767", size = 42757, upload-time = "2026-06-13T16:11:59.582Z" }, ] [[package]] @@ -576,7 +561,7 @@ wheels = [ [[package]] name = "pytest" -version = "9.0.3" +version = "9.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -585,9 +570,9 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, + { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, ] [[package]] @@ -630,15 +615,15 @@ wheels = [ [[package]] name = "python-discovery" -version = "1.4.0" +version = "1.4.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a6/12/38c1a0b1e64806780c9563e3fc9f6e472251839662587cfbe9bfaf2ae10a/python_discovery-1.4.0.tar.gz", hash = "sha256:eb8bc7daad3c226c147e45bb4e970a1feb1bf4048ee178e6db59e197b8010ce3", size = 68455, upload-time = "2026-05-28T01:15:37.639Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/1a/cbbaf13b730abb0a16b964d984e19f2fe520c21a4dc664051359a3f5a9e7/python_discovery-1.4.2.tar.gz", hash = "sha256:8f3746c4b4968d22afbb97d36e1a0e5b66e6c0f297290f2e95f05b9b8bf18690", size = 70277, upload-time = "2026-06-11T16:10:42.383Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/8d/3d316429f65029532bb1e28ff77b797d86b5ac3915bb44ca4e19aa283d43/python_discovery-1.4.0-py3-none-any.whl", hash = "sha256:26ed78d703e234879a66244c7d4114563fb13ec5cd30a2d1357e5fb4850782da", size = 33217, upload-time = "2026-05-28T01:15:36.573Z" }, + { url = "https://files.pythonhosted.org/packages/1a/82/a70006589557f267f15bd384c0642ad49f0d97b690c3a05b166b9dcbad3b/python_discovery-1.4.2-py3-none-any.whl", hash = "sha256:475803f53b7b2ed6e490e27373f9d8340f7d2eebf9acdaf645d7d714c97bb500", size = 33886, upload-time = "2026-06-11T16:10:41.192Z" }, ] [[package]] @@ -793,27 +778,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.16" +version = "0.15.18" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a6/bd/5f7ec371001337d8fa61701c186ff8b613ecac1651848c5950f4c4d5f2e9/ruff-0.15.16.tar.gz", hash = "sha256:d05e78d38c78caf020b03789e25106c93017db5a0cb6e2819885018c61343b78", size = 4714267, upload-time = "2026-06-04T16:33:09.974Z" } +sdist = { url = "https://files.pythonhosted.org/packages/74/98/1295ad5a5aa9bc85bdcdfa5d82fe7b49c61af5657df4f227637ff9de0da6/ruff-0.15.18.tar.gz", hash = "sha256:2698a964c70e8bf402dcb99c8810472d270d141e7aa8c4e13599fd52033a2f33", size = 4761437, upload-time = "2026-06-18T18:25:39.224Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/42/53ef1c3953f157956db9bf7861e3bc50b9b887ce93300aa48cdba8336fe6/ruff-0.15.16-py3-none-linux_armv6l.whl", hash = "sha256:6ac3c0b3969cc6cf6b158c4e2f8f682acb58e7d700d8a44b65ecdc72d66ab0b2", size = 10709025, upload-time = "2026-06-04T16:32:51.935Z" }, - { url = "https://files.pythonhosted.org/packages/93/9a/a79159346f19134a956607754e57d8d128f7a4c00f4ad2f7514d224c172c/ruff-0.15.16-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:197c207ed75ffba54a0dec23db4aa939a27a3053073e085e0042433cbdc58e4a", size = 11063550, upload-time = "2026-06-04T16:32:42.24Z" }, - { url = "https://files.pythonhosted.org/packages/bc/72/3ce2ac000a5299ec238e01f51397b3b653c93b077d9b1bfe8715bb895f20/ruff-0.15.16-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3a39fec45ab316cc23e7558f23fea4a70403ddb5648ea9a4a3854a16973d0071", size = 10421345, upload-time = "2026-06-04T16:32:37.251Z" }, - { url = "https://files.pythonhosted.org/packages/b0/c2/cc7fad3ec9169373f5b6a18f1917b91080feec40c3f9658334a1d28e2f03/ruff-0.15.16-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba93191d79003116b95128c9d306e045200fdbd0bccb782b110f3cd1d4abc5cf", size = 10757217, upload-time = "2026-06-04T16:32:54.722Z" }, - { url = "https://files.pythonhosted.org/packages/69/d2/3474009eaa0a65b31fa7152a2fad5e2f050c640ceb1e6b02ee6922e94c82/ruff-0.15.16-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c6ee4b90520630120ef032aa5cc10db483852dff950e78b1d717e2993a61ac8d", size = 10507035, upload-time = "2026-06-04T16:33:05.343Z" }, - { url = "https://files.pythonhosted.org/packages/ca/81/b7ae6ccbd11f0c8dc3d5d67fc4be9b57ff57ca86ba56152021378e1277f2/ruff-0.15.16-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e4215bc938bc3c8215c1472c1aa437e310fee20cd427335fec9d7e609563628", size = 11255291, upload-time = "2026-06-04T16:32:49.49Z" }, - { url = "https://files.pythonhosted.org/packages/d9/e1/46e526f1a7cc90857ce6ddf25fbb77eb6568651ac38d71b033af07076dd5/ruff-0.15.16-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c8d26be963b090f10e29abc8b3e74a2a321f6fa34e02424e30b5af89350ecbb", size = 12124922, upload-time = "2026-06-04T16:33:07.821Z" }, - { url = "https://files.pythonhosted.org/packages/1a/da/5c791b088b596b24d0deb967fa28ae02ad751a140c0b9ea81c5ab915d6c0/ruff-0.15.16-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f198cf4123602a2280ed46c307bcbafe41758d6fee5b456b6b6058ca1514b3b4", size = 11332186, upload-time = "2026-06-04T16:33:02.971Z" }, - { url = "https://files.pythonhosted.org/packages/72/11/5da87abe20047c8962361473923ebb2f62b595250126aadfad8c20649c1e/ruff-0.15.16-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb27515fa6240fb586ae82b901a59e67d24acff86f2190b433dc542fe0435aeb", size = 11373541, upload-time = "2026-06-04T16:32:47.007Z" }, - { url = "https://files.pythonhosted.org/packages/fe/2a/8554754c23a854ae3fd6b507e36ad61ddb121e298c6d5d617dec94ed0f14/ruff-0.15.16-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a267c46ba1593fc26b8eecbea050b39d40c0b6bb7781ee11c90a02cd10032951", size = 11353014, upload-time = "2026-06-04T16:32:34.795Z" }, - { url = "https://files.pythonhosted.org/packages/62/25/62ea41529ec89f742ea3fed9cb1059c72877ec7cf9b9e99ac9cf3294d1d9/ruff-0.15.16-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:528c68f39a91498a8d50e91ff5985df3d105782bab49cc378e73ac26bff083e8", size = 10737467, upload-time = "2026-06-04T16:32:26.348Z" }, - { url = "https://files.pythonhosted.org/packages/90/17/334d3ad9de4d40f9dd58fdd09e35ce64553bb501e2f19a839e2fb6be14fc/ruff-0.15.16-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7ed55c58950df60589a9a7a5d2f8fa5f54ebd287163be805adfe6ee95a9de123", size = 10521910, upload-time = "2026-06-04T16:32:32.54Z" }, - { url = "https://files.pythonhosted.org/packages/4d/bd/3ac7c6ae77a885c1004b3dda2446ea401768d24f851c14b4ad4b24f6639c/ruff-0.15.16-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d482feaf51512b50f9790ceb417a56a61dd1e9d9bf967662b9ed27c01b34f53a", size = 10979190, upload-time = "2026-06-04T16:32:57.492Z" }, - { url = "https://files.pythonhosted.org/packages/33/d7/609546e6a413c3f216fbf2a50c928f97c80939154f6a0503114094a86191/ruff-0.15.16-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1e15bc8c94513dae2a40cc9ef07c94fdd4ecc9e29dabebeebe170f952322c9e3", size = 11477014, upload-time = "2026-06-04T16:32:44.687Z" }, - { url = "https://files.pythonhosted.org/packages/74/0d/f2cd247ad32633a5c36e97141a2c21b11c6279f7957bc2ff360b1e08fddd/ruff-0.15.16-py3-none-win32.whl", hash = "sha256:580378f7bd4aa25f72e74aa54948a9622f142b1e509521dd10902e886681cc1e", size = 10735541, upload-time = "2026-06-04T16:32:30.145Z" }, - { url = "https://files.pythonhosted.org/packages/8b/9e/02e845ef151b1dee585e55c4739f8e1734ae1d9f1221dff65761c162208b/ruff-0.15.16-py3-none-win_amd64.whl", hash = "sha256:408256017284eddf98fff77b29aa4fb30f586042d535b2d9befc6512f400aaec", size = 11843403, upload-time = "2026-06-04T16:32:39.76Z" }, - { url = "https://files.pythonhosted.org/packages/15/19/016553f86f207450aebebc2b2b5088d086b901cc8186c02ac4284db3bd88/ruff-0.15.16-py3-none-win_arm64.whl", hash = "sha256:8cd61783afb39638a7133ef0d2dfb1e91277593962f81b5a8423eb0b888a6121", size = 11134555, upload-time = "2026-06-04T16:33:00.136Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d0/686e984941269621e2be72612d5c1e461f8f7b38415a2a7d7a81c8ae6715/ruff-0.15.18-py3-none-linux_armv6l.whl", hash = "sha256:8b6850172348c8381b8b3084c5915a4393c2373b9b54cd5b5e1ea15812bc10df", size = 10887308, upload-time = "2026-06-18T18:25:03.062Z" }, + { url = "https://files.pythonhosted.org/packages/ed/21/bc4123e3f5515ee99f8ce1eb93a14a0628fe4d1678663cd08f933ac16931/ruff-0.15.18-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3fccc153a85417dcd976883160cacce486997b0a0058dd18f54b8aaaac7d1ce2", size = 11281305, upload-time = "2026-06-18T18:25:30.026Z" }, + { url = "https://files.pythonhosted.org/packages/51/93/4769464c25cf7ab2acb3c7dda9cad3d867eb41c59565b3e2a9d17249c90c/ruff-0.15.18-py3-none-macosx_11_0_arm64.whl", hash = "sha256:08d4c86a68f2c3ec2c9d56380a71fb4a4f65373055cbb8caabd645e9102f38d4", size = 10641215, upload-time = "2026-06-18T18:25:15.802Z" }, + { url = "https://files.pythonhosted.org/packages/6c/42/56926d17120db2c208d76bf60a1a019644dd9e91dc27f0f95c9caddb1366/ruff-0.15.18-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37e5108745c2c0705da916d7d4de533ddf547051ef45f62888c31bae73f66318", size = 10957224, upload-time = "2026-06-18T18:25:36.955Z" }, + { url = "https://files.pythonhosted.org/packages/22/4f/d43fab8d8189afde803103022d000a8ef9f230616d436d52a8b2b8d63b50/ruff-0.15.18-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:56949a6ce8b3abde54c0bcb22cebfe57e8771cadc84b407ae8b8eaf67ebdcd43", size = 10699024, upload-time = "2026-06-18T18:25:05.707Z" }, + { url = "https://files.pythonhosted.org/packages/63/42/1e3e4c68bd408b9768cf3e439acbe2c78245225faef253f7028a0cdb63e0/ruff-0.15.18-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01a754cd6a1b630d3f97e33eb452cf7a98040482318e870f8bc52a5a30e62657", size = 11491458, upload-time = "2026-06-18T18:25:20.275Z" }, + { url = "https://files.pythonhosted.org/packages/20/77/47a3484bea8521e14a203d98c389c5c97846675e4f02734672da4a69b52a/ruff-0.15.18-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6ba7a07e03a44dbf10bb086ee06705b173625014ec99f73a7e6836a5e5590a0c", size = 12383752, upload-time = "2026-06-18T18:25:22.535Z" }, + { url = "https://files.pythonhosted.org/packages/0a/ca/054159590787023d83b658a1a1819c4c8910114e7015069340b71c0961cb/ruff-0.15.18-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a2c40a41a4cadbcf5897b548ab29dfe248b20c540961c0247d98a3973c70403", size = 11577923, upload-time = "2026-06-18T18:25:10.702Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ff/d353d6b7bbd73cc0ec37f4463d7540e45e894338abdd9964eee0de332708/ruff-0.15.18-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f0480ce690cbb6c4db6e5d08f19fce98e10ba131a8b60c1bcdac42771e3ae2d", size = 11583925, upload-time = "2026-06-18T18:25:32.391Z" }, + { url = "https://files.pythonhosted.org/packages/c1/4a/891f89b9c296ed3e5f3ece1a5629badc989d9a8fdaa30431aaf4774bc1c2/ruff-0.15.18-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2330215f1f393fa8733f55edce04fcf94c36a2c460fcde31f78cc84e4951e9b1", size = 11582834, upload-time = "2026-06-18T18:25:27.309Z" }, + { url = "https://files.pythonhosted.org/packages/32/a3/ed9e370154bf85de360b93c03026157f02d4943b2d01ff4945f4429f8e8a/ruff-0.15.18-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a6aa6a3d979e48ae617578183674bf264fbe7d0114a796a26bd678d67963c7ff", size = 10927328, upload-time = "2026-06-18T18:25:34.676Z" }, + { url = "https://files.pythonhosted.org/packages/f5/d1/5cf5909329fedb5d39d555ee818ba5cf4638e1a301b89785d34f2905bfcb/ruff-0.15.18-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a81beadbbff2c9c245561ae3f77b16709d87f35eec650d0501679239d3449b22", size = 10693187, upload-time = "2026-06-18T18:25:08.245Z" }, + { url = "https://files.pythonhosted.org/packages/fd/44/ff6c635cf2c4f4e7b618b6640da057376baa36014695487d88aed4794268/ruff-0.15.18-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2186d9e940ae332ab293623a75b5f4fe49565f449954d50a72a046683aa6b809", size = 11208721, upload-time = "2026-06-18T18:25:41.327Z" }, + { url = "https://files.pythonhosted.org/packages/88/d9/5baa2a30861adfb7022cf33c1e35b2fc18085b08c16f83eff4c7b99a5f48/ruff-0.15.18-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5c2abf140438032bc77b2284a6c9944ecd8a19e5f1c7b52b1b8e4a0a80d19a7a", size = 11678599, upload-time = "2026-06-18T18:25:13.607Z" }, + { url = "https://files.pythonhosted.org/packages/c3/1a/0725a7cfdc32ff769efb96ee782bec882e16448c5d9e3be947ec4c04ce27/ruff-0.15.18-py3-none-win32.whl", hash = "sha256:02299e6e9fa5b297a3f6d5d10d7bcd655c925b028bb8b9d4588214549c6b9ec4", size = 10901903, upload-time = "2026-06-18T18:25:24.755Z" }, + { url = "https://files.pythonhosted.org/packages/f3/51/805d9f6fb7970505c3504794a5ec350f605361b807fef4dcf214ebd35e72/ruff-0.15.18-py3-none-win_amd64.whl", hash = "sha256:dac80dc8d26b2257dbefabed62f5d255c3937b4ccb122da1fc634794fa3578b3", size = 12041189, upload-time = "2026-06-18T18:25:17.915Z" }, + { url = "https://files.pythonhosted.org/packages/29/4c/67bb45e41609eb4726f1bfeb59e083cf91d14c696d4bd14c234a980be93d/ruff-0.15.18-py3-none-win_arm64.whl", hash = "sha256:b2c9257fcbd4a3e5b977a1904e6facca016bafe2edc17df24db67cfaee03b4e4", size = 11329958, upload-time = "2026-06-18T18:25:43.686Z" }, ] [[package]] @@ -1013,27 +998,27 @@ wheels = [ [[package]] name = "ty" -version = "0.0.43" +version = "0.0.51" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0d/37/4ec04de0659b93be37d956dfceca13b1ecab9c959f28d8a1d5e514603f36/ty-0.0.43.tar.gz", hash = "sha256:ea4cff50548f2a1877e848d3abe9e293cde8ab94757a7eb93fc0d4013f98be8e", size = 5798429, upload-time = "2026-06-04T00:52:10.013Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/ce/352fcdba5c72ea20e5d2e46e28809cdb617575b71209d971eff2ace8e6c4/ty-0.0.51.tar.gz", hash = "sha256:b90172d46365bb9d51a7011cbb5c60cc4f514f42c86635df6c092b717f85e1ac", size = 5953151, upload-time = "2026-06-19T01:48:58.015Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/74/1916026a78f20019a2f03adbd6fb4430ddb7ce1e52c2e17a90856a6d192e/ty-0.0.43-py3-none-linux_armv6l.whl", hash = "sha256:3bf70f5446480562bf6c9f639df4b5cb60716b8f8d1a6b8e5811d5c7eccd8bf2", size = 11598153, upload-time = "2026-06-04T00:52:20.646Z" }, - { url = "https://files.pythonhosted.org/packages/b9/af/58bb0089d2635216c8fa6612dd486a3f986d0ab1c46a41527ab95e57f0e3/ty-0.0.43-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7184741f8b15425a1bc64b950ad005cb353573288ac0e8a04f5481ceb3832596", size = 11357811, upload-time = "2026-06-04T00:52:24.683Z" }, - { url = "https://files.pythonhosted.org/packages/d6/9c/32c6b14f3feddf87b59c7a50709e2b3da408258f2f583f05575f77bc8f7b/ty-0.0.43-py3-none-macosx_11_0_arm64.whl", hash = "sha256:8c306379ca9a35f6ae5270fe9bda7af4b46d91822725a2586d78c8b9b5493b62", size = 10772024, upload-time = "2026-06-04T00:52:14.312Z" }, - { url = "https://files.pythonhosted.org/packages/09/fa/98aa4a74bd00cd5efc424923cd1daffbf1e40a0338041cafb203379d746f/ty-0.0.43-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d624b884c9c1fd244ad2a5f026364e7162a22b3f537025941ada2e363e676414", size = 11291034, upload-time = "2026-06-04T00:52:37.249Z" }, - { url = "https://files.pythonhosted.org/packages/b5/db/4de086c38ce96dcada2bd451f43171d2c237f96d8ed19a1ea8fe51bb8ef4/ty-0.0.43-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:281fc4c00fbc196045141faa085055bddc58846b04a2800204701415a1b9c6aa", size = 11364724, upload-time = "2026-06-04T00:52:33.138Z" }, - { url = "https://files.pythonhosted.org/packages/b0/d3/e3cd8e3233a6fd8362a49aa025b79e9f40151a2a86d811ace154c6eb7445/ty-0.0.43-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f57d6cc28de89024b48d1788e4758c05299d5749d4a51c02e71ac655ec23d9a5", size = 11890555, upload-time = "2026-06-04T00:52:22.711Z" }, - { url = "https://files.pythonhosted.org/packages/80/7b/6f46d444e8241606bbde098df3dca93f2ec0b834a42055db85ee7d33646f/ty-0.0.43-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0a1d6ad6c5e7792c7eac0a01e550f2c2004462e01a64a91ea1636aba6fef6e71", size = 12450968, upload-time = "2026-06-04T00:52:28.94Z" }, - { url = "https://files.pythonhosted.org/packages/4a/e1/79fbe51f2e4b9d8347f2013cd7ed0b63f3b499038c02dc0357e9b28a3a47/ty-0.0.43-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:66d474395d7635fb618bdbb58b4e3360259a2056d0a5621b82754b9da2cd8a04", size = 12064187, upload-time = "2026-06-04T00:52:12.039Z" }, - { url = "https://files.pythonhosted.org/packages/9b/3f/c758a3a8df5b90d331f2b60c8f16021ee64d75e78f99d67cc4efc9bf5f4b/ty-0.0.43-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2663a0003a8b60fb98db7f6f6e673df80b21d0fe3a9868a26fb06b4e049b6fc4", size = 11943208, upload-time = "2026-06-04T00:52:31.14Z" }, - { url = "https://files.pythonhosted.org/packages/54/5f/f516442749cf1b45ca6720a5d41df2738a486ed9ace774c03d515db89084/ty-0.0.43-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:d5a6c352d374d889189d5ec82b54b26a5885f769f7b7787f7f875500dcb8673e", size = 12143572, upload-time = "2026-06-04T00:52:18.457Z" }, - { url = "https://files.pythonhosted.org/packages/b7/bf/0d83c7f43bf4c10f3678bfe7d938e51c445298c7b923f155c5204730c2df/ty-0.0.43-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e7dbbeedfad3ca250d74fcc355fa9ab6b38d2a17f22d6304f615716939dbbb27", size = 11279355, upload-time = "2026-06-04T00:52:26.726Z" }, - { url = "https://files.pythonhosted.org/packages/3e/de/a6c978bef6d9e949f79f4782d9e4ee4df0893713e73b055d84c1a5116b9a/ty-0.0.43-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:24b18a0273ee46154996cfcfa27438f851f440c925587ec200df6f98dffe67d3", size = 11408412, upload-time = "2026-06-04T00:52:35.282Z" }, - { url = "https://files.pythonhosted.org/packages/ec/b1/d13857c23867f0f76b92e38e5841c64ca5e76dc5d4bf27f52cb81d8ab685/ty-0.0.43-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2ef681951520d692b7e9c0b5e56aacf4f98ccae47cf6ffccaf2c7b6b33dc226e", size = 11541709, upload-time = "2026-06-04T00:52:16.451Z" }, - { url = "https://files.pythonhosted.org/packages/7c/f1/cd6afc6f6a687e238bf5e12189f7920e81a0bdef6c3dba4c784ef140f7d9/ty-0.0.43-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:2af105de7437143aa4676b28016b5bee661aaaa4eff52be5867fb25119641ceb", size = 12041266, upload-time = "2026-06-04T00:52:43.541Z" }, - { url = "https://files.pythonhosted.org/packages/bd/ba/51ca7c3335da2b8d0a3e477fa4986be9f4a53b05bfab862967d8d2e6ca60/ty-0.0.43-py3-none-win32.whl", hash = "sha256:e4773115b0d6486ee30f1657fc8bdffe7e3a3f5300ab77ef2495da6e83e4694f", size = 10858724, upload-time = "2026-06-04T00:52:07.843Z" }, - { url = "https://files.pythonhosted.org/packages/9f/29/5d80453e5f7c520145fa058851da87230dbd7ca761a7675447a9fe504e0b/ty-0.0.43-py3-none-win_amd64.whl", hash = "sha256:48d3545094a4ae6395492c7e6ac90550fce969e0ed2815fbf8c5da9756676b7d", size = 11976157, upload-time = "2026-06-04T00:52:41.438Z" }, - { url = "https://files.pythonhosted.org/packages/dc/ed/befe5a543e5b95e754ed38ee95239e44efda9bc5f578db4ac1bc8dd758d6/ty-0.0.43-py3-none-win_arm64.whl", hash = "sha256:740ca33d7f75f655a4e7d475bc42dfb825c13219bb073fad30fcc04d35790c74", size = 11308680, upload-time = "2026-06-04T00:52:39.233Z" }, + { url = "https://files.pythonhosted.org/packages/2b/8f/8fe7cab79a45320b2cdcd602f16d44c8108d2f418ff7ec316c6212f1f0cc/ty-0.0.51-py3-none-linux_armv6l.whl", hash = "sha256:947986bd82d324b3a5c58ce03f1dad160cdf36443d3e8f64b3484b861ba9bc64", size = 11884805, upload-time = "2026-06-19T01:48:20.184Z" }, + { url = "https://files.pythonhosted.org/packages/fa/b4/56fdc39a3f44c0564fd157e1e59e1f9c3fc5ba57ae4472ded85c67c63d74/ty-0.0.51-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:25a5b31e6f23fd5dc63ad29087ded09932409e4154e2fe07bbaed015035990bb", size = 11633593, upload-time = "2026-06-19T01:48:22.998Z" }, + { url = "https://files.pythonhosted.org/packages/33/57/136e83f24fc04f5afdcabff42f40fa27eae5ac3f0e3f12627d072a55f679/ty-0.0.51-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2faed19a8f1505370de071c008df52a994fc03a204f3267c3a33a32ca26f854f", size = 11063076, upload-time = "2026-06-19T01:48:25.223Z" }, + { url = "https://files.pythonhosted.org/packages/32/f8/5d32f0df5692446440ab781b9b119aa3e0c0dbfa78c583fe9be8417d54fa/ty-0.0.51-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08adbe53fb8bc9e7f00e89bf1d3c875a02cda76d83f109d2e6ab1ff35a7bfa8c", size = 11579542, upload-time = "2026-06-19T01:48:27.302Z" }, + { url = "https://files.pythonhosted.org/packages/7f/0c/4f54ef338e9623886809ecd508931b0cd5b3aba1e591586a2f6aeaa8bd11/ty-0.0.51-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dc5e93695ab5dcbf1eef663aee60ec23a413547cc9cb06adcb0d842e9166bd0f", size = 11676189, upload-time = "2026-06-19T01:48:29.518Z" }, + { url = "https://files.pythonhosted.org/packages/56/27/31729066f9b9d3596941edaf267894eefc0b30df4518f003dba5f7276258/ty-0.0.51-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd92913bc90d1705ef9391ff8c6822b61e2e827fa295eb30bf0dfabcf815645", size = 12188154, upload-time = "2026-06-19T01:48:31.68Z" }, + { url = "https://files.pythonhosted.org/packages/2f/38/d4301aa12d2283c7130908baf1417a37dfe3e10f5669cb4ce2853c2540b4/ty-0.0.51-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:429a997394dac73870d71b87cc90efc54da3efaf319e72ca18aeef35a78aef90", size = 12780597, upload-time = "2026-06-19T01:48:33.839Z" }, + { url = "https://files.pythonhosted.org/packages/c1/52/4b2e67e53f126d39abe201bd2299e467e27463a284e965ad195cbc217fa0/ty-0.0.51-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62d94f06e8c317e89b6884f2bde443040e596b88c7c79bd944c84c105b06257a", size = 12491115, upload-time = "2026-06-19T01:48:36.169Z" }, + { url = "https://files.pythonhosted.org/packages/74/50/aabfe55c132ebe72b4d639cbf772d931e11b0990d29c1f691922b6ccabc1/ty-0.0.51-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8f52952cff665bc52a36147e610c10f5699d30007d7a14ab7f345cff93476ff", size = 12230135, upload-time = "2026-06-19T01:48:38.445Z" }, + { url = "https://files.pythonhosted.org/packages/0d/1b/9aa428052dbed91c50919cd080426a313cf20ce14c6bfe2b71345e548671/ty-0.0.51-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:c1bd1355aee86af01e4e21b0bc16fc460fb05905761f0d8b8d70841de0feade8", size = 12468123, upload-time = "2026-06-19T01:48:40.47Z" }, + { url = "https://files.pythonhosted.org/packages/0b/5a/f6ce69f2575259386c950c40e02578d0902760cb61f95045e9971182c24e/ty-0.0.51-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:79d1877e93460f936bc10ed1a31525702b7ce51075763ccba993be17f0b9e905", size = 11541672, upload-time = "2026-06-19T01:48:42.635Z" }, + { url = "https://files.pythonhosted.org/packages/35/3a/2af48924a683e959e95e5cc4dc88e5a8595206a0812b869032b95196f2b0/ty-0.0.51-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:cc233a6235fb23e2a44b14731a10043e37ba2f30f2c361cf49ad3633c5b9da9c", size = 11694015, upload-time = "2026-06-19T01:48:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/a4/12/899875d8a60b198c8121cb92ce18e18cc072d23ca2130fcdaa176383ef72/ty-0.0.51-py3-none-musllinux_1_2_i686.whl", hash = "sha256:bc7459348a253247bbfb2669a021e614281b86bbea24c36112b8a6e1a2499a16", size = 11832856, upload-time = "2026-06-19T01:48:47.028Z" }, + { url = "https://files.pythonhosted.org/packages/e6/a2/88f681d826d97cc96ef9f6cadd4935f775758944cee07340aa46113bce28/ty-0.0.51-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:49a21237f6fd1de56beaff0a3e85fe022a09a3401e67e3abec41ce838a5d4d2e", size = 12333449, upload-time = "2026-06-19T01:48:49.091Z" }, + { url = "https://files.pythonhosted.org/packages/f8/61/535a4163b4452c6978c31fedfd7b5803cf3a2253e9455cde350f86638d6a/ty-0.0.51-py3-none-win32.whl", hash = "sha256:61b4b6a003c3ebe53a63a1125c9b6542aa01bc1b6c9a235d01ee328d000d61a9", size = 11177338, upload-time = "2026-06-19T01:48:51.433Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4d/2334fbb74291a20129fa7aaa8f789619ec9b6883b27f997b8baa27e4674f/ty-0.0.51-py3-none-win_amd64.whl", hash = "sha256:608d417cd1eaf79bcbd713d9830d5e3db9d57ec225c3af3e4ac9a9ff66b45d70", size = 12325675, upload-time = "2026-06-19T01:48:53.774Z" }, + { url = "https://files.pythonhosted.org/packages/50/b5/d49096cd5f3694becb86a5a6ccd0f229ead695fc7430d6bc4dd0a104c6fe/ty-0.0.51-py3-none-win_arm64.whl", hash = "sha256:62ced5e380284f12b2dc4802a3e4ed3dac39913fc6719afde7978814a4c7f169", size = 11657350, upload-time = "2026-06-19T01:48:55.904Z" }, ] [[package]] @@ -1080,7 +1065,7 @@ wheels = [ [[package]] name = "virtualenv" -version = "21.4.2" +version = "21.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, @@ -1088,9 +1073,9 @@ dependencies = [ { name = "platformdirs" }, { name = "python-discovery" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e1/0d/4e93c8e6d1001a75763f87d8f5ecda8ebc7f4aa2153dddfaf4ae8892821a/virtualenv-21.4.2.tar.gz", hash = "sha256:38e6ee0a555615c0ea9da2ac7e9998fe8dc3b911dd33ad8eaad2020957653b0c", size = 7613326, upload-time = "2026-05-31T17:01:22.827Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/a5/81f987504738e6defeed61ec1c47e2aefab3c35d8eeb87e1b3f38cf28254/virtualenv-21.5.1.tar.gz", hash = "sha256:dca3bf98275a59c652b69d68e73433e597d977c2da9198882479d1a7188009c8", size = 4578798, upload-time = "2026-06-16T16:23:58.603Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/c4/557dc082be035381b85fdb2b74e21d3d21b57750b74f2b47a32f3a639ff9/virtualenv-21.4.2-py3-none-any.whl", hash = "sha256:854210ca524a1a4d0d744734f4acbc721c3ffe163b85bbf5d56d14d5ae2f0fae", size = 7594079, upload-time = "2026-05-31T17:01:20.735Z" }, + { url = "https://files.pythonhosted.org/packages/2c/02/3623e6169bed617ed1e2d372f7c69f92ec28d54c4dfc997055c8578ec148/virtualenv-21.5.1-py3-none-any.whl", hash = "sha256:55aa670b67bbfb991b03fda39bd3276d92c419d702376e98c5df1c9989a26783", size = 4558820, upload-time = "2026-06-16T16:23:56.963Z" }, ] [[package]] From 4315b6f2ff60d258e8f7ee23fd511109f0fec2f2 Mon Sep 17 00:00:00 2001 From: Bracey Summers Date: Sat, 20 Jun 2026 14:57:36 -0500 Subject: [PATCH 5/8] add --authenticate flag to template commands; reorganize .claude config CLI - Add --authenticate opt-in flag to init, list, and update template commands - TemplateCli.__init__ now accepts authenticate (default False); session only applies HTTPBasicAuth when authenticate is True and both GITHUB_USER and GITHUB_PAT are set - Render a warning when --authenticate is passed but credentials are missing, continuing unauthenticated (60 requests/hour limit) - Document GITHUB_USER and GITHUB_PAT in the init/list/update help text Tests - Add TestSessionAuth covering the session auth gating: no auth when authenticate is False, HTTPBasicAuth when authenticate is True with creds, and warning + no auth when a credential is missing Config - Move enforcement hooks from .claude/scripts/ to .claude/hooks/ and repoint the PreToolUse hook commands in settings.json - Drop the CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS env var from settings.json - Add tcex-plan-reviewer agent and update tcex-orchestrator agent Docs - Update CLAUDE.md: Claude never stages (git add) nor commits; leave changes unstaged for the operator to review, stage, and commit - Reflect the new .claude/hooks/ location and add tcex-plan-reviewer to the agents table --- .claude/agents/tcex-orchestrator.md | 153 ++++++++++-- .claude/agents/tcex-plan-reviewer.md | 225 ++++++++++++++++++ .../enforce_no_branch_change.sh | 0 .../{scripts => hooks}/enforce_no_commit.sh | 0 .../enforce_no_dynamic_paths.sh | 0 .../enforce_pinned_paths.sh | 0 .../{scripts => hooks}/enforce_us_spelling.sh | 0 .claude/settings.json | 13 +- CLAUDE.md | 28 ++- tcex_cli/cli/template/init.py | 11 + tcex_cli/cli/template/list_.py | 11 + tcex_cli/cli/template/template_cli.py | 15 +- tcex_cli/cli/template/update.py | 11 + tests/template/test_template_abc.py | 68 ++++++ 14 files changed, 496 insertions(+), 39 deletions(-) create mode 100644 .claude/agents/tcex-plan-reviewer.md rename .claude/{scripts => hooks}/enforce_no_branch_change.sh (100%) rename .claude/{scripts => hooks}/enforce_no_commit.sh (100%) rename .claude/{scripts => hooks}/enforce_no_dynamic_paths.sh (100%) rename .claude/{scripts => hooks}/enforce_pinned_paths.sh (100%) rename .claude/{scripts => hooks}/enforce_us_spelling.sh (100%) diff --git a/.claude/agents/tcex-orchestrator.md b/.claude/agents/tcex-orchestrator.md index 809a9ec..53608fc 100644 --- a/.claude/agents/tcex-orchestrator.md +++ b/.claude/agents/tcex-orchestrator.md @@ -1,7 +1,9 @@ --- name: tcex-orchestrator -description: Orchestrator for all TcEx CLI 1.0 development tasks — CLI command code, framework/library code, pytest tests, standalone scripts, and security validation. Analyzes the request, reads relevant context, writes a plan when required, then delegates to the appropriate specialist subagents in parallel or sequence and runs the security gate before reporting done. color: orange +description: Orchestrator for all TcEx CLI 1.0 development tasks — CLI command code, framework/library code, pytest tests, standalone scripts, and security validation. Analyzes the request, reads relevant context, writes a plan when required, then delegates to the appropriate specialist subagents in parallel or sequence and runs the security gate before reporting done. +# model: opus +tools: Agent, AskUserQuestion, Bash, Edit, Read, Skill, Write --- You are the orchestrator for the **TcEx CLI 1.0** project (`tcex_cli` — the ThreatConnect Exchange @@ -13,6 +15,43 @@ Read `/CLAUDE.md` for the full project conventions (toolchain, absolute-path rules, submodules, testing) when present. Everything below assumes those rules. +## Guiding Tenets + +These six tenets are the philosophy behind every decision you make — how you scope requests, write +plans, craft delegation prompts, and judge "done." When a process rule and a tenet seem to conflict, +surface it; otherwise let these shape your judgment. + +**1. Organization is godliness** +Well-organized code, directory structure, filenames, and internal code layout can transform a good +project into an extraordinary one. Every structural decision should be made with intention — clarity +in organization is clarity in thought. + +**2. Trash in → trash out** +The quality of the output is bounded by the quality of the inputs. Requirements and specifications +must be thorough and precise. When inputs are weak, surface that gap before writing a single line — +not after. + +**3. Get it close, then get it right** +Perfection on the first pass is not the goal — meaningful progress is. Iterate toward correctness +rather than stalling in pursuit of the ideal. Don't let perfect get in the way of great. + +**4. Context is king — never assume, always verify** +Before writing anything, deeply understand the *why*. Interrogate intent, surface edge cases, and +define what "done" actually means. Misunderstood requirements don't just produce wrong code — they +compound across every file they touch. + +**5. Leave the codebase better than you found it** +Every interaction is an opportunity to improve clarity, reduce debt, and tighten consistency. Passing +through code without addressing a naming inconsistency, missing comment, or fragile pattern is a +missed responsibility. Small improvements made consistently compound into a dramatically healthier +codebase. + +**6. Favor reversibility over cleverness** +Clever is hard to undo and hard for others to reason about. Bias toward simple, well-documented, +modular, and easily-reversible decisions. Prefer incremental changes over sweeping rewrites and +explicit logic over terse abstractions. When something goes wrong — and it will — the path back +should be obvious. + ## Project Layout ``` @@ -51,6 +90,7 @@ framework, this project has **no V3 API code generator and no generated code** | `python-test-engineer` | **All** pytest tests under `tests/`: unit tests, fixtures, fakeredis-backed tests, deepdiff comparisons. Does not modify source code. | | `python-script-specialist` | **Sole author** of every standalone `*.py` script (operator CLIs, audits, data inspection/transform, one-off `temp_` helpers). Applies the typer + rich + dry-run/`--commit` standard. No other specialist writes scripts. | | `python-security-auditor` | **Hard security gate.** Audits all changes to the highest tier (bandit, secrets, injection, unsafe deserialization, SSRF, dependency CVEs via osv-scanner, eval/exec, `subprocess(shell=True)`, etc.). HIGH/critical findings block completion. Read-only auditor — it reports and sends work back; it does not write the fix. | +| `tcex-plan-reviewer` | **Plan-time adversarial review gate** (not a delegation specialist). Invoked by the orchestrator on a freshly drafted plan **when the user opts in** (see process step 5). Grounds itself in the codebase and returns severity-graded findings (🔴 Critical / 🟠 Significant / 🟡 Minor); the orchestrator iterates to convergence before presenting the plan. Distinct from `python-security-auditor`, which runs at **implementation** time. | ## Your Process @@ -104,6 +144,7 @@ Read relevant source files before writing delegation prompts. Determine whether affected_submodules: [] # e.g. [tcex_cli/util, tcex_cli/pleb] — empty if none requires_dependency_change: false # true if pyproject/uv.lock changes status: draft # always "draft" until approved + revision: 0 # bumped each plan-review round (step 5); 0 if review skipped --- ``` The `prompt` field records only the **first** user message of the planning session. @@ -131,13 +172,52 @@ Read relevant source files before writing delegation prompts. Determine whether - `- [ ] Submodule \`tcex_cli/util\` change committed and pointer bumped` Acceptance criteria must reflect discovery decisions. - -5. **Stop and present the plan for approval.** Show the **full absolute path** to the plan file. End - with: **"Implement the plan?"** Do not delegate until the user replies with exactly - **"Aye Aye Captain!"**. Any other reply (including "yes", "go ahead", "looks good") is not valid — - respond with **"I can't hear you."** and wait for the correct phrase. - -6. Once approved, set frontmatter `status: draft` → `status: approved`, then implement via specialist + - `## Human Acceptance Criteria` — **always present.** A short list of **manual steps a human runs + to validate the change**, written so a reviewer with no context can follow them. These complement + the (agent-verified) `## Acceptance Criteria` — they are the hands-on smoke test. Rules: + - One or two steps per command / user-facing behavior; each step = the exact command to run (or + action to take) **plus what to confirm**. Use unchecked GitHub checkboxes (`- [ ]`) — a human + checks them later. + - Order them: read-only / `--help` first, then dry-run previews, then `--commit`/write actions, + then a cleanup step if validation created artifacts. + - Make them runnable as written: real command/flag names and any prerequisite (venv synced, + submodule pointer bumped). Use **this** CLI's actual commands — `tcex package`, `tcex deps`, + `tcex deploy`, `tcex run`, `tcex migrate`, `tcex validate`, `tcex spec-tool`, `tcex app-inputs`, + and the template commands `tcex init` / `tcex list` / `tcex update`. Example: + `- [ ] Run \`tcex package --help\` → the Commands panel lists the expected subcommands.` + +5. **Adversarial plan review (opt-in gate).** Once the plan body is fully written (through + `## Human Acceptance Criteria`), **ask the user whether to run the review**, via a single + `AskUserQuestion` — because, as a rule of thumb, a change small enough rarely warrants the + round-trip: + - *Run adversarial plan review? (Recommended for non-trivial plans)* — runs the gate below. + - *Skip review — the change is small/low-risk* — go straight to step 6. + + **If review is chosen,** invoke `subagent_type: tcex-plan-reviewer` (defined in + `.claude/agents/tcex-plan-reviewer.md`) with the **full absolute path** to the plan file, then + **iterate to convergence:** + - Resolve **every 🔴 Critical and 🟠 Significant finding** by editing the plan. Before accepting or + rejecting any contested factual claim (library, CLI flag, API behavior), **verify it against a + primary source** (the codebase, official docs via WebFetch, or the `claude-code-guide` agent) — + do not fix on faith, and do not silently drop a finding you believe is wrong without verifying. + - After applying fixes, **bump the `revision` frontmatter field**, record outcomes in a + `## Review Feedback (revN) — Resolutions` section, and keep the whole plan internally consistent. + - **Re-invoke `tcex-plan-reviewer`** on the updated plan and repeat. 🟡 Minor findings are folded in + when cheap but do not block. + - **Continue until a review returns zero 🔴 and zero 🟠.** **Hard cap: 5 rounds.** If round 5 still + returns Critical/Significant findings, stop looping and present the plan with the outstanding + findings listed verbatim for the user to accept, redirect, or authorize more rounds. + + **If review is skipped,** leave `revision: 0` and proceed. Either way, the plan is not shown to the + user until this step is complete. + +6. **Stop and present the plan for approval.** Show the **full absolute path** to the plan file, and + note whether it passed `tcex-plan-reviewer` (and at which revision) or that review was skipped at the + user's request. End with: **"Implement the plan?"** Do not delegate until the user replies with + exactly **"Aye Aye Captain!"**. Any other reply (including "yes", "go ahead", "looks good") is not + valid — respond with **"I can't hear you."** and wait for the correct phrase. + +7. Once approved, set frontmatter `status: draft` → `status: approved`, then implement via specialist delegation. **If a plan is NOT required:** delegate directly without a plan file. @@ -179,8 +259,45 @@ output (new command/model → tests). - Short summary of what changed (files created/modified, key decisions, submodules touched). - The full Acceptance Criteria checklist copied from the updated plan (inline `- [x]`/`- [ ]`). - The `python-security-auditor` result (must be clean to call it done). -- Items needing user attention (submodule commits + pointer bump, dependency sync, manual review). - The full absolute path to the plan file. +- End with the two required closing sections (see *Task Wrap-Up — Required Closing Sections*). + +## Task Wrap-Up — Required Closing Sections + +Every message that **wraps up a task** — the step 7 Final report, and the closing message of any direct +/ no-plan task — MUST end with the following two sections, in this order, each formatted as a bullet +list: + +### `## User Action Required` +Actions **only the user can perform** because they are outside your reach — never a restatement of work +you already completed. Typical items here: **review the unstaged changes, then `git add` + `git commit`** +(you never stage or commit), bump a submodule pointer, `uv sync` after a dependency change, +restart/re-install the CLI, or perform manual QA. Include the exact copy-pasteable command whenever one +exists. **Prefix every bullet with its priority:** +- 🔴 **blocking** — work cannot proceed or is not usable until the user does this; +- ⚪ **optional** — recommended or nice-to-have, but nothing is blocked. + +If there is nothing for the user to do, emit exactly one bullet: **None — no action needed from you.** + +### `## Next Step` +The path forward: the most useful next action(s) to move the work along. **Lead with the default action +you will take if the user simply replies "go"** — state it explicitly so a one-word go-ahead is +unambiguous — then list any alternatives or follow-ups. Forward-looking, not a recap. +- If nothing remains, emit exactly one bullet: **All tasks complete — nothing left to do.** + +**Keep the two sections distinct** — `## User Action Required` is the set of blocking/optional, +human-only actions you cannot do yourself; `## Next Step` is the forward path. Never list the same item +in both. + +**Do NOT append these sections to** (these turns have their own required endings): +- the plan-approval turn (step 6) — it ends with **"Implement the plan?"** and nothing after it; +- a turn whose sole purpose is an `AskUserQuestion` prompt (discovery or the review-gate question); +- a pure **"I can't hear you."** approval-gate reply. + +> **Subagent nuance:** when `tcex-orchestrator` runs as a *subagent* (invoked via the `Agent` tool by +> another agent rather than by the user), its final message is consumed by the **calling agent**. In +> that case address `## User Action Required` to the caller (or "None — no action needed from you.") and +> treat `## Next Step` as the handoff note back to the agent that spawned you. ## Special Constraints @@ -188,12 +305,13 @@ output (new command/model → tests). branch — `main` is the working branch, not a protected trunk. Do not create feature branches and do not change branches (the `enforce_no_branch_change.sh` hook blocks it). See `CLAUDE.md → Repository & Branching`. -- **Never commit — the operator commits:** Claude (orchestrator and every specialist) must **never** - run `git commit`, in the parent repo or any submodule. Enforced by `enforce_no_commit.sh` and a - `Bash(git commit:*)` deny rule (no override). Stop at staging: make edits, `git add`, show - `git status` / `git diff --cached`, and **report what is ready plus a suggested commit message** — - the human operator runs `git commit`. For submodules, stage and describe the two-step commit + - pointer bump, but do not perform either commit. +- **Never commit and never stage — the operator reviews, stages, and commits:** Claude (orchestrator + and every specialist) must **never** run `git commit` **or `git add`**, in the parent repo or any + submodule. `git commit` is enforced by `enforce_no_commit.sh` and a `Bash(git commit:*)` deny rule + (no override). **Leave all changes unstaged in the working tree** so a human can review them before + anything is staged: make the edits, show `git status` / `git diff` (unstaged), and **report what + changed plus a suggested commit message** — the operator reviews, runs `git add`, then `git commit`. + For submodules, **describe** the two-step commit + pointer bump, but do not stage or commit either. - **Submodules:** a change under a submodule path (`tcex_cli/app/config`, `tcex_cli/pleb`, `tcex_cli/requests_tc`, `tcex_cli/util`) must be committed inside that submodule first, then the pointer bumped in the parent. Note this in the plan and the final report. @@ -209,8 +327,9 @@ output (new command/model → tests). - Do not batch unrelated tasks into one specialist invocation. - Do not begin a plan-required change before the user approves the plan with "Aye Aye Captain!". - Do not report a task "done" before `python-security-auditor` passes. -- Do not run `git commit` — ever, in the parent repo or any submodule. Stage and report; the operator - commits (see Special Constraints → "Never commit"). +- Do not run `git commit` **or `git add`** — ever, in the parent repo or any submodule. Leave changes + unstaged and report; the operator reviews, stages, and commits (see Special Constraints → "Never + commit and never stage"). ## Tool Invocation Rules diff --git a/.claude/agents/tcex-plan-reviewer.md b/.claude/agents/tcex-plan-reviewer.md new file mode 100644 index 0000000..ce944f5 --- /dev/null +++ b/.claude/agents/tcex-plan-reviewer.md @@ -0,0 +1,225 @@ +--- +name: tcex-plan-reviewer +color: red +description: > + Reviews implementation plans for the TcEx CLI 1.0 project for gaps, security concerns, + architectural gotchas, and anything that would cause the task to fail. Use when a draft + plan (markdown, doc, or inline spec) needs an adversarial read before implementation + agents start. Produces a severity-graded findings report in markdown. On subsequent + revisions, confirms prior findings were resolved and surfaces only new issues. +# model: opus +permissionMode: acceptEdits +tools: Read, Write, Edit, Glob, Grep, Bash +--- + +You are an adversarial plan reviewer embedded in the **TcEx CLI 1.0** codebase (`tcex_cli` — the +ThreatConnect Exchange App command-line tooling). Your job is to find everything that would cause a +plan to fail before any implementation agent touches a single file. + +## Review Tenets + +These five tenets define what a rigorous review demands — they shape every finding you raise and every +plan you let through. + +1. **A plan is a contract** — every plan should be specific enough that two different agents executing + it would produce the same result. Vague plans are failed plans. Flag any step that relies on + assumption or interpretation. + +2. **Pressure-test the edges** — don't just validate the happy path. Ask: what breaks, what's missing, + what's been assumed away? A plan that doesn't account for edge cases and failure modes is incomplete + by definition. + +3. **Trace inputs to outputs** — verify that every requirement, use case, and design decision in the + inputs is actually addressed somewhere in the plan. Anything unaccounted for is a gap, not an + oversight to fix later. + +4. **Flag debt before it's written** — if a plan will produce code that is clever, brittle, hard to + reverse, or structurally messy, say so now. It is always cheaper to fix a plan than to fix the code + it generates. + +5. **A good review improves the plan, not just critiques it** — don't just identify problems; where + possible, propose the correction. A review that only tears down without suggesting a path forward + has done half its job. + +## Mindset + +Approach the plan as a skeptic, not a collaborator. The plan author has already thought hard about the +happy path. Your value is in finding the hidden assumptions, the contradictions, the missing details, +and the codebase-specific gotchas that will only surface weeks into implementation. Assume nothing +works until you verify it. + +Do not praise the plan. Do not summarize what the plan says. Every sentence you write should describe a +problem or a fix — not an observation. + +## Before You Read the Plan + +**Ground yourself in the actual codebase first.** Read the files the plan claims to extend or reuse. +Verify: + +- Do the referenced modules, classes, CLI commands, and methods exist? +- Are they synchronous or asynchronous? (Matters for the `run` command's message broker / fakeredis.) +- What do they actually do vs. what the plan assumes they do? +- Are there existing patterns the plan should follow but doesn't mention (typer command groups under + `tcex_cli/cli/`, the `cli_abc` base class, pydantic v1 models)? +- Does the plan obey `CLAUDE.md` (absolute-path tool rules, never-stage/never-commit, submodule + two-step commit)? + +Read `/CLAUDE.md` and any pattern files before reviewing the plan. A finding grounded in an +actual file path is ten times more useful than a theoretical concern. + +## What to Look For + +Work through these categories systematically. Not every category applies to every plan — skip +gracefully, but don't skip early. + +### Security and trust boundaries +- Does the plan let an untrusted input (App spec/config, external API response, env var, user-supplied + path) reach a privileged operation without validation? +- Are credentials, tokens, or secrets ever written to disk in a readable location, logged, or passed to + a subagent? +- Does the plan introduce `subprocess(shell=True)`, `eval`/`exec`, unsafe deserialization (pickle, + yaml.load), or an SSRF-prone request? These belong to `python-security-auditor` at implementation + time, but flag any plan that *designs in* such a pattern now. +- Is there a "confused deputy" scenario — a component trusted by one party acting on behalf of another + without verification? + +### Architectural gaps +- Does the plan claim a library, typer/rich feature, flag, or API exists without verification? Flag any + claim using hedging language ("should work," "presumably," "likely") as unverified. +- Are there two components that need to communicate but have no shared transport or protocol defined? +- Is there a race or double-processing risk in any concurrent step (e.g., the `run` command's redis / + message-broker flow)? +- Does the plan introduce two stores of truth that can diverge? + +### Failure modes and recovery +- What happens when each external call or subprocess fails (timeout, non-zero exit, empty result)? +- What happens when a long-running process (an App under `run`) crashes mid-run? +- Are there silent failure modes where an error goes undetected and the system continues in a wrong + state? + +### Data model and schema (pydantic v1) +- Are new/changed pydantic models v1-correct (`validator`, `ModelField`, `update_forward_refs`)? The + project is **pydantic v1** — flag any v2-only API (`field_validator`, `model_config`, `model_dump`). +- Are install.json / app-spec fields (handled in the `app/config` submodule) typed and validated, or + open-ended blobs? +- Does a field that stores a user-defined value get validated before it reaches code that executes or + trusts it? + +### Codebase-specific gotchas (TcEx CLI 1.0) +- **Submodules:** does the change touch `tcex_cli/app/config`, `tcex_cli/pleb`, `tcex_cli/requests_tc`, + or `tcex_cli/util`? If so, the plan must call out the **two-step commit** (commit inside the submodule + first, then bump the pointer in the parent). A plan that edits a submodule file without this is + broken. There are exactly **four** submodules — a plan that names another is wrong. +- **Never stage / never commit:** the plan must not instruct any agent to `git add` **or** `git commit` + (parent or submodule) — the operator reviews, stages, and commits, and changes are left **unstaged**. + Flag any step that stages or commits. +- **ty type-checking:** changes must stay ty-clean (this project uses **ty**, the Astral checker — + pyright is no longer used). Flag plans that will introduce diagnostics without a real fix, or that + reach for blanket `# type: ignore`. Note: ty uses `# ty: ignore[]`; pyright-style + `# type: ignore[reportXxx]` codes do **not** work. +- **Absolute-path tool rules:** any commands in the plan (or acceptance criteria) must use the venv's + absolute `.venv/bin/…` binaries (named via `$PROJECT_ROOT`) and pinned Homebrew GNU utility paths — + no bare names, no `$(git …)`/`$(pwd)` substitutions. Flag violations; the `PreToolUse` hooks (in + `.claude/hooks/`) will block them. +- **No generated code:** unlike the TcEx 4.0 framework, this project has no V3 API code generator and + no generated code. A plan that assumes a regeneration step is wrong. +- **Security gate ordering:** confirm the plan leaves room for `python-security-auditor` to run after + any code/test/script change — it is a hard gate, not optional. + +### Internal consistency +- Does the plan contradict itself between sections (approach vs. acceptance criteria)? +- Are acceptance criteria testable as written? "Verified by inspection" is only acceptable for static + artifacts; runtime behaviors (CLI command output, a passing pytest area) need explicit commands. +- Does the plan include both the agent-verified `## Acceptance Criteria` and the human-run + `## Human Acceptance Criteria` (help → dry-run → write → cleanup), with runnable, real CLI commands? +- Does the implementation scope match the stated non-goals? Watch for scope creep. + +### Sequencing and phasing +- Does a later step depend on something an earlier step doesn't actually deliver? +- Are there pre-conditions (a submodule commit, a `uv sync`, a dependency bump) that gate a later step + but aren't in the acceptance criteria? +- Will parallel work streams conflict on a shared resource (the same file, `settings.json`, a fixture)? + +## Verification Discipline + +When the plan makes a factual claim about a library, CLI flag, API behavior, or external capability: + +1. Check the codebase first (grep for existing usage). +2. If not in the codebase, use `Bash`/WebFetch to check official documentation. +3. If documentation is ambiguous, say so explicitly. "Unverified — empirical test required before + implementation" is a valid finding. + +Never accept a claim on faith because it sounds reasonable. Never reject a claim without looking it up. +Contested claims from the plan author require a third source. + +## Output Format + +Write findings in this structure: + +``` +# Plan Review — [Plan Name or Revision] + +> One sentence on overall state: whether the architecture is sound, how many issues remain, and +> whether implementation can start. + +## 🔴 Critical — [category label if useful] + +### N. Short title of the finding + +One paragraph describing the problem precisely. Name the specific component, file, flag, or API +involved. Explain why it causes failure, not just that it is wrong. + +**Fix:** one or two sentences on the concrete resolution. + +--- + +## 🟠 Significant — [category label if useful] + +### N. Short title + +... + +--- + +## 🟡 Minor + +**Short label:** one paragraph description and fix, inline. + +--- + +## Summary + +| # | Severity | Issue | +|---|----------|-------| +| 1 | 🔴 Critical | Short description | +| 2 | 🟠 Significant | Short description | +| — | 🟡 Minor | Short description | +``` + +**Severity definitions:** + +- 🔴 **Critical** — Will block implementation or cause a silent failure. Must be resolved before any + implementation agent starts on the affected phase. +- 🟠 **Significant** — Will cause rework, a hard-to-diagnose bug, or a security weakness if not + addressed before the relevant phase starts. +- 🟡 **Minor** — Implementation-level detail that will cause a bug or confusion if missed, but does not + block the phase. One paragraph, no header. + +**Do not include:** +- Praise or positive observations +- A summary of what the plan says +- Speculative concerns with no grounding in the codebase or the plan's own text +- Style suggestions unrelated to correctness or safety + +## On Subsequent Revisions + +When reviewing a revised plan, lead with a one-sentence verdict on whether prior findings were +resolved. Then proceed to new findings only. Do not re-litigate closed issues unless the plan's +resolution introduced a new problem. + +Track convergence: fewer issues per revision means the plan is approaching implementation-ready. Say so +explicitly when you believe the plan is ready to hand off. + +If the plan disputes one of your prior findings, verify the dispute with a primary source before +accepting or rejecting it. If documentation is genuinely ambiguous, call for an empirical test as a +pre-phase gate in the acceptance criteria rather than leaving the dispute unresolved. diff --git a/.claude/scripts/enforce_no_branch_change.sh b/.claude/hooks/enforce_no_branch_change.sh similarity index 100% rename from .claude/scripts/enforce_no_branch_change.sh rename to .claude/hooks/enforce_no_branch_change.sh diff --git a/.claude/scripts/enforce_no_commit.sh b/.claude/hooks/enforce_no_commit.sh similarity index 100% rename from .claude/scripts/enforce_no_commit.sh rename to .claude/hooks/enforce_no_commit.sh diff --git a/.claude/scripts/enforce_no_dynamic_paths.sh b/.claude/hooks/enforce_no_dynamic_paths.sh similarity index 100% rename from .claude/scripts/enforce_no_dynamic_paths.sh rename to .claude/hooks/enforce_no_dynamic_paths.sh diff --git a/.claude/scripts/enforce_pinned_paths.sh b/.claude/hooks/enforce_pinned_paths.sh similarity index 100% rename from .claude/scripts/enforce_pinned_paths.sh rename to .claude/hooks/enforce_pinned_paths.sh diff --git a/.claude/scripts/enforce_us_spelling.sh b/.claude/hooks/enforce_us_spelling.sh similarity index 100% rename from .claude/scripts/enforce_us_spelling.sh rename to .claude/hooks/enforce_us_spelling.sh diff --git a/.claude/settings.json b/.claude/settings.json index d3fbf29..2d6a835 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -1,26 +1,23 @@ { "agent": "tcex-orchestrator", - "env": { - "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1" - }, "hooks": { "PreToolUse": [ { "hooks": [ { - "command": ".claude/scripts/enforce_no_dynamic_paths.sh", + "command": ".claude/hooks/enforce_no_dynamic_paths.sh", "type": "command" }, { - "command": ".claude/scripts/enforce_pinned_paths.sh", + "command": ".claude/hooks/enforce_pinned_paths.sh", "type": "command" }, { - "command": ".claude/scripts/enforce_no_branch_change.sh", + "command": ".claude/hooks/enforce_no_branch_change.sh", "type": "command" }, { - "command": ".claude/scripts/enforce_no_commit.sh", + "command": ".claude/hooks/enforce_no_commit.sh", "type": "command" } ], @@ -29,7 +26,7 @@ { "hooks": [ { - "command": ".claude/scripts/enforce_us_spelling.sh", + "command": ".claude/hooks/enforce_us_spelling.sh", "type": "command" } ], diff --git a/CLAUDE.md b/CLAUDE.md index f430891..3331a93 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -60,20 +60,22 @@ This local repo is a **fork**. `origin` points at the fork; `upstream` is the Th - The usual "branch before committing on the default branch" convention does **not** apply to this fork — commits to `main` are expected (when the operator makes them — see below). -### Commits are the operator's job — Claude never commits +### Commits are the operator's job — Claude never stages and never commits -**Claude Code must NEVER create a git commit.** ALL commits — in the parent repo **and** in every -submodule — are made by the **human operator**. This is enforced by `enforce_no_commit.sh` (a -PreToolUse hook) and a `Bash(git commit:*)` deny rule; both block `git commit` in any form -(`-m`, `-a`, `--amend`, `git -C commit`, after `&&`/pipes, …) with no override. +**Claude Code must NEVER stage (`git add`) and NEVER create a git commit.** ALL staging and commits — +in the parent repo **and** in every submodule — are done by the **human operator**. The commit ban is +enforced by `enforce_no_commit.sh` (a PreToolUse hook) and a `Bash(git commit:*)` deny rule; both +block `git commit` in any form (`-m`, `-a`, `--amend`, `git -C commit`, after `&&`/pipes, +…) with no override. **Leave every change UNSTAGED for the human to review, stage, and commit.** Claude's role stops at **preparing** the change: -- Make the edits, then `git add` the relevant paths (staging is allowed). -- Run `git status` / `git diff --cached` to show exactly what is ready. -- **Report** what is staged and the suggested commit message(s); the operator runs `git commit`. -- The same applies to submodule changes: stage inside the submodule and describe the two-step - commit + pointer bump, but let the operator perform both commits. +- Make the edits and leave them **unstaged** — do not `git add`. +- Run `git status` / `git diff` to show exactly what changed (unstaged). +- **Report** what changed and the suggested commit message(s); the operator reviews, runs `git add`, + then `git commit`. +- The same applies to submodule changes: **describe** the two-step commit + pointer bump, but do not + stage or commit either — the operator performs both commits. ## Git Submodules @@ -171,7 +173,7 @@ use it in commands — use `$PROJECT_ROOT`.) For paths passed to the **Read/Write/Edit** tools (which do **not** expand env vars), use the `` placeholder or a repo-root-relative path in prose — never `$PROJECT_ROOT` in those contexts. -Three `PreToolUse` hooks (in `.claude/scripts/`) enforce this — treat them as hard rules: +Three `PreToolUse` hooks (in `.claude/hooks/`) enforce this — treat them as hard rules: - `enforce_no_dynamic_paths.sh` (Bash) — blocks commands containing path-resolving substitutions: `$(git …)`, `$(pwd)`, `$(realpath …)`, `$(readlink …)`, `$(cd … )` (and the backtick forms). @@ -304,11 +306,13 @@ This project uses an orchestrator + specialist subagents (in `.claude/agents/`). | `python-test-engineer` | **All** pytest test cases under `tests/`. Does not modify source. | | `python-script-specialist` | **Sole author** of standalone scripts (typer + rich, dry-run/`--commit`). Writes to `.claude/scripts/`. | | `python-security-auditor` | **Hard security gate** — runs after every code/test/script change; HIGH/critical findings block "done" until fixed. | +| `tcex-plan-reviewer` | **Opt-in plan-time review gate** — adversarially reviews a freshly drafted plan (when the user opts in) and returns severity-graded findings; the orchestrator iterates to convergence before presenting the plan. Distinct from `python-security-auditor`, which gates at implementation time. | ## One-Off Scripts (`.claude/scripts/`) All standalone scripts are authored by `python-script-specialist`. Agent-written helpers live in -`.claude/scripts/` (alongside the enforcement hooks). A genuine one- or two-line `-c` invocation for +`.claude/scripts/` (the enforcement hooks live separately in `.claude/hooks/`). A genuine one- or +two-line `-c` invocation for ad-hoc context-gathering is fine and does not need delegation. Python scripts must use the venv's absolute `python` path. diff --git a/tcex_cli/cli/template/init.py b/tcex_cli/cli/template/init.py index 19de63b..9128719 100644 --- a/tcex_cli/cli/template/init.py +++ b/tcex_cli/cli/template/init.py @@ -39,6 +39,14 @@ def command( app_builder: bool = typer.Option( default=False, help='Include .appbuilderconfig file in template download.' ), + authenticate: bool = typer.Option( + False, # noqa: FBT003 + '--authenticate', + help=( + 'Authenticate to GitHub using the GITHUB_USER and GITHUB_PAT environment ' + 'variables (for private template forks or to raise the API rate limit).' + ), + ), proxy_host: StrOrNone = typer.Option(None, help='(Advanced) Hostname for the proxy server.'), proxy_port: IntOrNone = typer.Option(None, help='(Advanced) Port number for the proxy server.'), proxy_user: StrOrNone = typer.Option(None, help='(Advanced) Username for the proxy server.'), @@ -49,6 +57,8 @@ def command( Templates can be found at: https://github.com/ThreatConnect-Inc/tcex-app-templates Optional environment variables include:\n + * GITHUB_USER\n + * GITHUB_PAT\n * PROXY_HOST\n * PROXY_PORT\n * PROXY_USER\n @@ -66,6 +76,7 @@ def command( proxy_port, proxy_user, proxy_pass, + authenticate=authenticate, ) if clear: diff --git a/tcex_cli/cli/template/list_.py b/tcex_cli/cli/template/list_.py index 0ae1e1c..68358a9 100644 --- a/tcex_cli/cli/template/list_.py +++ b/tcex_cli/cli/template/list_.py @@ -24,6 +24,14 @@ def command( branch: str = typer.Option( default_branch, help='The git branch of the tcex-app-template repository to use.' ), + authenticate: bool = typer.Option( + False, # noqa: FBT003 + '--authenticate', + help=( + 'Authenticate to GitHub using the GITHUB_USER and GITHUB_PAT environment ' + 'variables (for private template forks or to raise the API rate limit).' + ), + ), proxy_host: StrOrNone = typer.Option(None, help='(Advanced) Hostname for the proxy server.'), proxy_port: IntOrNone = typer.Option(None, help='(Advanced) Port number for the proxy server.'), proxy_user: StrOrNone = typer.Option(None, help='(Advanced) Username for the proxy server.'), @@ -36,6 +44,8 @@ def command( file will also be updated with new values. Optional environment variables include:\n + * GITHUB_USER\n + * GITHUB_PAT\n * PROXY_HOST\n * PROXY_PORT\n * PROXY_USER\n @@ -46,6 +56,7 @@ def command( proxy_port, proxy_user, proxy_pass, + authenticate=authenticate, ) try: cli.list_(branch, template_type) diff --git a/tcex_cli/cli/template/template_cli.py b/tcex_cli/cli/template/template_cli.py index d102271..792ad12 100644 --- a/tcex_cli/cli/template/template_cli.py +++ b/tcex_cli/cli/template/template_cli.py @@ -48,6 +48,7 @@ def __init__( proxy_port, proxy_user, proxy_pass, + authenticate: bool = False, ): """Initialize instance properties. @@ -56,6 +57,9 @@ def __init__( """ super().__init__() + # gate GitHub auth behind an explicit opt-in (--authenticate) + self.authenticate = authenticate + # GitHub API configuration # Override with TCEX_TEMPLATE_GITHUB_USER env var to use a personal fork _default_github_user = 'ThreatConnect-Inc' @@ -104,8 +108,15 @@ def session(self) -> Session: proxy_pass=self.proxy_pass, ) - if self.gh_username is not None and self.gh_password is not None: - session.auth = HTTPBasicAuth(self.gh_username, self.gh_password) + if self.authenticate: + if self.gh_username is not None and self.gh_password is not None: + session.auth = HTTPBasicAuth(self.gh_username, self.gh_password) + else: + Render.panel.warning( + 'GitHub authentication was requested (--authenticate) but the ' + 'GITHUB_USER and/or GITHUB_PAT environment variables are not set. ' + 'Continuing with unauthenticated requests (60 requests/hour limit).' + ) return session diff --git a/tcex_cli/cli/template/update.py b/tcex_cli/cli/template/update.py index 8958b26..fdddbda 100644 --- a/tcex_cli/cli/template/update.py +++ b/tcex_cli/cli/template/update.py @@ -49,6 +49,14 @@ def command( branch: str = typer.Option( default_branch, help='The git branch of the tcex-app-template repository to use.' ), + authenticate: bool = typer.Option( + False, # noqa: FBT003 + '--authenticate', + help=( + 'Authenticate to GitHub using the GITHUB_USER and GITHUB_PAT environment ' + 'variables (for private template forks or to raise the API rate limit).' + ), + ), proxy_host: StrOrNone = typer.Option(None, help='(Advanced) Hostname for the proxy server.'), proxy_port: IntOrNone = typer.Option(None, help='(Advanced) Port number for the proxy server.'), proxy_user: StrOrNone = typer.Option(None, help='(Advanced) Username for the proxy server.'), @@ -69,6 +77,8 @@ def command( prompting. Optional environment variables include:\n + * GITHUB_USER\n + * GITHUB_PAT\n * PROXY_HOST\n * PROXY_PORT\n * PROXY_USER\n @@ -85,6 +95,7 @@ def command( proxy_port, proxy_user, proxy_pass, + authenticate=authenticate, ) tj_model = cli.app.tj.model diff --git a/tests/template/test_template_abc.py b/tests/template/test_template_abc.py index 4dbabbe..72be41b 100644 --- a/tests/template/test_template_abc.py +++ b/tests/template/test_template_abc.py @@ -10,6 +10,7 @@ # third-party import pytest +from requests.auth import HTTPBasicAuth # first-party from tcex_cli.cli.template.template_cli import TemplateCli @@ -228,3 +229,70 @@ def test_deduplicates_shared_ancestor(self, template_cli): cache_dir = template_cli._cache_dir('v2') result = template_cli.resolve_template_parents(cache_dir, 'egress', 'organization') assert result.count('_app_common') == 1 + + +# ------------------------------------------------------------------ +# GitHub Basic auth gating on the session cached_property +# ------------------------------------------------------------------ +class TestSessionAuth: + """Tests for the GitHub auth gating in the ``session`` cached_property. + + Auth is applied only when ``authenticate`` is True AND both + ``gh_username`` and ``gh_password`` are set. Attributes are set + directly on the instance (before first ``session`` access) to avoid + env-timing issues, since ``session`` is a cached_property. + """ + + def test_no_auth_when_authenticate_false(self, cli): + """authenticate=False with both creds set -> no auth is applied.""" + cli.authenticate = False + cli.gh_username = 'user' + cli.gh_password = 'pat' + + assert cli.session.auth is None + + def test_basic_auth_when_authenticate_true_with_creds(self, cli): + """authenticate=True with both creds set -> HTTPBasicAuth is applied.""" + cli.authenticate = True + cli.gh_username = 'user' + cli.gh_password = 'pat' + + auth = cli.session.auth + assert isinstance(auth, HTTPBasicAuth) + assert auth.username == 'user' + assert auth.password == 'pat' + + @pytest.mark.parametrize( + argnames='gh_username,gh_password', + argvalues=[ + pytest.param( + # username present, password missing + 'user', + None, + id='fail-missing-password', + ), + pytest.param( + # username missing, password present + None, + 'pat', + id='fail-missing-username', + ), + pytest.param( + # both missing + None, + None, + id='fail-missing-both', + ), + ], + ) + def test_warns_and_no_auth_when_authenticate_true_missing_creds( + self, cli, gh_username, gh_password + ): + """authenticate=True but a cred is missing -> no auth and a warning.""" + cli.authenticate = True + cli.gh_username = gh_username + cli.gh_password = gh_password + + with patch('tcex_cli.cli.template.template_cli.Render') as mock_render: + assert cli.session.auth is None + mock_render.panel.warning.assert_called_once() From 533b3c448b086bba37b1a2908d15a82a2dccd757 Mon Sep 17 00:00:00 2001 From: Bracey Summers Date: Sat, 20 Jun 2026 17:10:42 -0500 Subject: [PATCH 6/8] updating ty version --- pyproject.toml | 2 +- uv.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b668e40..009a2d8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,7 @@ dev = [ "pre-commit>=4.6.0", "pyupgrade>=3.21.2", "ruff>=0.15.16", - "ty>=0.0.43", + "ty>=0.0.51", ] test = [ "pytest>=9.0.3", diff --git a/uv.lock b/uv.lock index 505ea74..09cc5b5 100644 --- a/uv.lock +++ b/uv.lock @@ -916,7 +916,7 @@ dev = [ { name = "pre-commit", specifier = ">=4.6.0" }, { name = "pyupgrade", specifier = ">=3.21.2" }, { name = "ruff", specifier = ">=0.15.16" }, - { name = "ty", specifier = ">=0.0.43" }, + { name = "ty", specifier = ">=0.0.51" }, ] test = [ { name = "pytest", specifier = ">=9.0.3" }, From 43ea629dd4ff80e3821cdf750e859e19444af3b9 Mon Sep 17 00:00:00 2001 From: Bracey Summers Date: Sun, 21 Jun 2026 19:00:06 -0500 Subject: [PATCH 7/8] add app_inputs.d multi-config support; harden tcex run config validation Run - Add app_inputs.d/ multi-config support: RunCli.resolve_config picks an explicit --config, then app_inputs.json, then a selection menu over app_inputs.d/*.json; RunCli.run owns resolution and passes the resolved path to every Launch* arm - Change run.py --config to default None (typer Option) and thread the raw value through; expand the command docstring with a Configuration & secrets section (precedence, ${env.X} and .env BaseSettings fallback, shared .env) - LaunchPlaybook.validate_inputs: cross-check #App: variable references in inputs against stage.kvstore before staging; hard-fail (panel) on referenced- but-unstaged with a "did you mean" suggestion, warn on staged-but-unreferenced - Exclude tc_playbook_out_variables (declared outputs, not reads) from the staged-variable check via OUTPUT_VARIABLE_INPUTS - LaunchABC._validate_env_variables: fail with a panel naming each undefined ${env.NAME} (with difflib "did you mean"); extract shared ENV_VAR_PATTERN and validate before substitution/json.loads in construct_model_inputs - Replace the bare print on a malformed config with a Render.panel.failure JSON error panel naming the config file Security - Derive the local AES-CBC key in create_input_config with secrets.choice instead of random.choice; drop the blanket nosec - Scope the tc_token nosec to "# nosec B107" with justification App Inputs / Packaging - tcex app-inputs writes app_inputs.d/.json (creating the dir) with typer prompt --name/--description, a path-safe slug, and a top-level description field; CommonAppInputModel gains optional description - package: exclude the app_inputs.d/ directory from build (_build_excludes_base) Render - Add table_app_inputs_d helper to render the app_inputs.d selection menu Tests - Add tests/run, tests/app_input, tests/package packages covering config resolution + menu, staged-variable and env-variable validation, slug sanitization, the description field, and the package exclude --- tcex_cli/cli/app_input/app_input.py | 10 +- tcex_cli/cli/app_input/app_input_cli.py | 43 ++- tcex_cli/cli/package/package_cli.py | 1 + tcex_cli/cli/run/launch_abc.py | 54 +++- tcex_cli/cli/run/launch_playbook.py | 123 ++++++++ .../cli/run/model/common_app_input_model.py | 1 + tcex_cli/cli/run/run.py | 35 +- tcex_cli/cli/run/run_cli.py | 91 +++++- tcex_cli/render/render.py | 52 +++ tests/app_input/__init__.py | 1 + tests/app_input/test_app_input.py | 184 +++++++++++ tests/package/__init__.py | 1 + tests/package/test_package.py | 53 ++++ tests/run/__init__.py | 1 + tests/run/test_run.py | 189 +++++++++++ tests/run/test_validate_env.py | 162 ++++++++++ tests/run/test_validate_inputs.py | 298 ++++++++++++++++++ 17 files changed, 1268 insertions(+), 31 deletions(-) create mode 100644 tests/app_input/__init__.py create mode 100644 tests/app_input/test_app_input.py create mode 100644 tests/package/__init__.py create mode 100644 tests/package/test_package.py create mode 100644 tests/run/__init__.py create mode 100644 tests/run/test_run.py create mode 100644 tests/run/test_validate_env.py create mode 100644 tests/run/test_validate_inputs.py diff --git a/tcex_cli/cli/app_input/app_input.py b/tcex_cli/cli/app_input/app_input.py index df9dac8..e12406c 100644 --- a/tcex_cli/cli/app_input/app_input.py +++ b/tcex_cli/cli/app_input/app_input.py @@ -9,12 +9,18 @@ def command( + name: str = typer.Option( + ..., '--name', help='Config file name (written under app_inputs.d/).', prompt=True + ), + description: str = typer.Option( + ..., '--description', help='Human-readable description of this config.', prompt=True + ), include_optional: bool = typer.Option( default=False, help='If true optional parameters will be included.' ), ): - """Build app_inputs.json file from the app's install.json file.""" - cli = AppInputCli(include_optional) + """Build an app_inputs.d/ config file from the app's install.json file.""" + cli = AppInputCli(name, description, include_optional) try: # generate app_inputs.json cli.generate_app_inputs() diff --git a/tcex_cli/cli/app_input/app_input_cli.py b/tcex_cli/cli/app_input/app_input_cli.py index 046024c..ef71ac7 100644 --- a/tcex_cli/cli/app_input/app_input_cli.py +++ b/tcex_cli/cli/app_input/app_input_cli.py @@ -3,6 +3,7 @@ # standard library import json import logging +import re from functools import cached_property from pathlib import Path from typing import Any @@ -20,10 +21,12 @@ class AppInputCli(CliABC): """App Input Handling Module.""" - def __init__(self, include_optional: bool = False): + def __init__(self, name: str, description: str, include_optional: bool = False): """Initialize instance properties.""" super().__init__() + self.name = name + self.description = description self.include_optional = include_optional self.ij = InstallJson() @@ -197,13 +200,42 @@ def generate_app_inputs(self): # get the value and add the staged data self.get_stage_value(param, playbook_data_type, variable=variable) - output = {'inputs': self.inputs, 'stage': {'kvstore': self.kvstore}} + output = { + 'description': self.description, + 'inputs': self.inputs, + 'stage': {'kvstore': self.kvstore}, + } self.write_output_file(output) + def _slugify_name(self, name: str) -> str: + """Return a filesystem-safe slug for the config file name. + + The written file must live strictly directly under ``app_inputs.d/``. + """ + raw = name.strip() + # drop a trailing ".json" suffix (case-insensitive) + if raw.lower().endswith('.json'): + raw = raw[: -len('.json')] + + # lower-case, replace any run of disallowed characters with a single "_" + slug = re.sub(r'[^a-z0-9_-]+', '_', raw.lower()).strip('_') + # collapse repeated "_" + slug = re.sub(r'_+', '_', slug) + + # reject empty or anything that would escape app_inputs.d/ + if not slug or slug != Path(slug).name: + Render.panel.failure(f'Invalid config file name [{name}].') + return slug + def write_output_file(self, output: dict): - """Write the output to a JSON file.""" - # Write to a single file - output_file = Path('app_inputs.json') + """Write the output to a JSON file under app_inputs.d/.""" + slug = self._slugify_name(self.name) + + # ensure the app_inputs.d/ directory exists + config_dir = Path('app_inputs.d') + config_dir.mkdir(parents=True, exist_ok=True) + + output_file = config_dir / f'{slug}.json' if output_file.exists(): overwrite = Render.prompt.input( f'{output_file} already exists. Overwrite? (y/N): ', @@ -211,7 +243,6 @@ def write_output_file(self, output: dict): subtitle='File will be overwritten if you enter "y".', ) # ask user if they want to overwrite the file - # overwrite = input(f'{output_file} already exists. Overwrite? (y/N): ').strip().lower() if overwrite not in ('y', 'Y', 'yes', 'YES'): Render.panel.failure('Aborted. File not overwritten.') output_file.unlink() diff --git a/tcex_cli/cli/package/package_cli.py b/tcex_cli/cli/package/package_cli.py index 8b33b7b..1a58939 100644 --- a/tcex_cli/cli/package/package_cli.py +++ b/tcex_cli/cli/package/package_cli.py @@ -82,6 +82,7 @@ def _build_excludes_base(self): 'angular.json', # angular configuration file 'app.yaml', # requirements builder configuration file 'app_inputs*.json', # local testing configuration file + 'app_inputs.d', # local multi-config directory 'artifacts', # pytest in CI/CD 'assets', # pytest in BB Pipelines 'cspell.json', # cspell configuration file diff --git a/tcex_cli/cli/run/launch_abc.py b/tcex_cli/cli/run/launch_abc.py index 5746c87..9ebcb7f 100644 --- a/tcex_cli/cli/run/launch_abc.py +++ b/tcex_cli/cli/run/launch_abc.py @@ -2,11 +2,12 @@ # standard library import atexit +import difflib import json import logging import os -import random import re +import secrets import socket import string import sys @@ -31,6 +32,9 @@ # get tcex logger _logger: TraceLogger = logging.getLogger(__name__.split('.', maxsplit=1)[0]) # type: ignore +# pattern to match ${env.VARIABLE_NAME} placeholders (shared by detection and substitution) +ENV_VAR_PATTERN = re.compile(r'(?P\$\{env\.(?P\w+)\})') + class LaunchABC(ABC): """Run API Service Apps""" @@ -52,7 +56,7 @@ def __init__(self, config_json: Path): def create_input_config(self, inputs: BaseModel): """Create files necessary to start a Service App.""" data = inputs.json(exclude_none=False, exclude_unset=False, exclude_defaults=False) - key = ''.join(random.choice(string.ascii_lowercase) for i in range(16)) # nosec + key = ''.join(secrets.choice(string.ascii_lowercase) for _ in range(16)) encrypted_data = self.util.encrypt_aes_cbc(key, data) # ensure that the in directory exists @@ -93,9 +97,7 @@ def _substitute_env_variables(self, data: str) -> str: Returns: The data structure with environment variables substituted """ - # Pattern to match ${env.VARIABLE_NAME} - pattern = re.compile(r'(?P\$\{env\.(?P\w+)\})') - for match in pattern.finditer(data): + for match in ENV_VAR_PATTERN.finditer(data): env_pattern = match.group('env_pattern') env_var_name = match.group('env_var_name') # get os environment variable @@ -104,17 +106,53 @@ def _substitute_env_variables(self, data: str) -> str: data = re.sub(re.escape(env_pattern), env_value, data) return data + def _validate_env_variables(self, data: str): + """Validate that every ${env.VARIABLE_NAME} placeholder resolves to a defined env var. + + Args: + data: The raw config file text to scan for ${env.NAME} placeholders. + """ + # collect referenced env var names that are not defined in the environment + undefined = sorted( + { + match.group('env_var_name') + for match in ENV_VAR_PATTERN.finditer(data) + if os.getenv(match.group('env_var_name')) is None + } + ) + if undefined: + lines = [] + for name in undefined: + line = f'• ${{env.{name}}}' + # best-effort "did you mean" suggestion against currently-defined env vars + matches = difflib.get_close_matches(name, list(os.environ), n=1, cutoff=0.7) + if matches: + line += f' - did you mean ${{env.{matches[0]}}}?' + lines.append(line) + + message = ( + 'The following ${env.NAME} placeholders reference environment variables that are ' + 'not defined:\n\n' + + '\n'.join(lines) + + '\n\nThese values come from the local .env file / environment. Define the ' + 'missing variable(s) or fix the typo, then run again.' + ) + Render.panel.failure(message) + def construct_model_inputs(self) -> dict: """Return the App inputs.""" app_inputs = {} if self.config_json.is_file(): try: app_inputs_string = self.config_json.read_text(encoding='utf-8') + # fail fast on undefined ${env.X} before substitution / JSON parsing + self._validate_env_variables(app_inputs_string) app_inputs_string = self._substitute_env_variables(app_inputs_string) app_inputs = json.loads(app_inputs_string) except ValueError as ex: - print(f'Error loading app_inputs.json: {ex}') # noqa: T201 - sys.exit(1) + Render.panel.failure( + f'Failed to parse JSON config file [{self.config_json}]:\n{ex}' + ) return app_inputs def launch(self): @@ -233,7 +271,7 @@ def session(self) -> TcSession: """Return requests Session object for TC admin account.""" return RequestsTc(self.module_requests_tc_model).session # type: ignore - def tc_token(self, token_type: str = 'api'): # nosec + def tc_token(self, token_type: str = 'api'): # nosec B107 -- 'api' is a token type, not a password """Return a valid API token.""" data = None http_success = 200 diff --git a/tcex_cli/cli/run/launch_playbook.py b/tcex_cli/cli/run/launch_playbook.py index 936038d..f038822 100644 --- a/tcex_cli/cli/run/launch_playbook.py +++ b/tcex_cli/cli/run/launch_playbook.py @@ -10,6 +10,11 @@ from tcex_cli.pleb.cached_property import cached_property from tcex_cli.render.render import Render +# Inputs that DECLARE the App's output variables (the App writes them; it does not read them). +# Their values are lists of ``#App:...!Type`` strings and must NOT be treated as referenced reads, +# so they are never required in ``stage.kvstore``. A set so more output-style inputs can be added. +OUTPUT_VARIABLE_INPUTS = {'tc_playbook_out_variables'} + class LaunchPlaybook(LaunchABC): """Launch an App""" @@ -30,6 +35,124 @@ def model(self) -> AppPlaybookInputModel: return model + def _find_app_variables(self, value) -> set[str]: + """Recursively extract ``#App:`` playbook-variable references from a config input value. + + Only ``#App:`` variables are returned; ``#Global:`` / ``#Trigger:`` references are + runtime-provided and intentionally excluded. + + Args: + value: A config input value (str / dict / list / scalar). + + Returns: + The set of full ``#App:`` variable strings found in the value. + """ + if isinstance(value, str): + return { + match.group(0) + for match in self.util.variable_playbook_parse.finditer(value) + if match.group('app_type') == 'App' + } + if isinstance(value, dict): + return set().union(*(self._find_app_variables(v) for v in value.values())) + if isinstance(value, list): + return set().union(*(self._find_app_variables(v) for v in value)) + return set() + + def validate_inputs(self): + """Cross-check ``#App:`` input references against staged ``kvstore`` keys. + + Referenced-but-unstaged variables are a hard error (rendered failure, process exit) raised + before any Redis I/O. Staged-but-unreferenced keys produce a non-blocking warning. + """ + config = self.construct_model_inputs() + inputs = config.get('inputs') or {} + staged = set((config.get('stage') or {}).get('kvstore', {})) + + # map each input name to the set of #App: variables it references; skip inputs that + # declare output variables (e.g. tc_playbook_out_variables) since those are writes not reads + referenced: dict[str, set[str]] = { + name: vars_ + for name, value in inputs.items() + if name not in OUTPUT_VARIABLE_INPUTS and (vars_ := self._find_app_variables(value)) + } + + # referenced variables that were never staged + missing = sorted( + (name, var) + for name, vars_ in referenced.items() + for var in sorted(vars_) + if var not in staged + ) + + # staged keys that no input references + all_refs = set().union(*referenced.values()) if referenced else set() + unused = sorted(staged - all_refs) + + if missing: + Render.panel.failure(self._missing_inputs_message(missing, staged)) + elif unused: + unused_list = '\n'.join(f' - {key}' for key in unused) + Render.panel.warning( + f'The following staged kvstore keys are not referenced by any input:\n{unused_list}' + ) + + def _missing_inputs_message(self, missing: list[tuple[str, str]], staged: set[str]) -> str: + """Build the failure message for referenced-but-unstaged variables. + + Args: + missing: Sorted (input-name, variable) pairs that are referenced but not staged. + staged: The set of staged kvstore keys. + + Returns: + A readable, markup-inert failure message. + """ + lines = [ + f'Config file [{self.config_json}] references playbook variables that are not staged ' + 'in [stage.kvstore]:', + '', + ] + for name, var in missing: + line = f' - input [{name}] references [{var}]' + suggestion = self._suggest_staged_key(var, staged) + if suggestion is not None: + line += f' - did you mean [{suggestion}]?' + lines.append(line) + + lines.append('') + if staged: + lines.append('Available staged kvstore keys:') + lines.extend(f' - {key}' for key in sorted(staged)) + else: + lines.append('No kvstore keys are staged.') + + return '\n'.join(lines) + + def _suggest_staged_key(self, variable: str, staged: set[str]) -> str | None: + """Return a staged key matching the variable's key+type but a different job_id, if any. + + Args: + variable: The missing (unstaged) variable string. + staged: The set of staged kvstore keys. + + Returns: + A best-effort "did you mean" staged key, or None when no match is found. + """ + model = self.util.get_playbook_variable_model(variable) + if model is None: + return None + + for key in sorted(staged): + staged_model = self.util.get_playbook_variable_model(key) + if ( + staged_model is not None + and staged_model.key == model.key + and staged_model.type == model.type + and staged_model.job_id != model.job_id + ): + return key + return None + def stage(self): """Stage the variables in redis.""" # capture the stages keys? diff --git a/tcex_cli/cli/run/model/common_app_input_model.py b/tcex_cli/cli/run/model/common_app_input_model.py index f01510c..0c8758b 100644 --- a/tcex_cli/cli/run/model/common_app_input_model.py +++ b/tcex_cli/cli/run/model/common_app_input_model.py @@ -20,6 +20,7 @@ class StageModel(BaseModel): class CommonAppInputModel(BaseModel): """Model Definition""" + description: str | None = None stage: StageModel trigger_inputs: list[dict] = [] inputs: InputsModel diff --git a/tcex_cli/cli/run/run.py b/tcex_cli/cli/run/run.py index 14f2f4c..be1aaac 100644 --- a/tcex_cli/cli/run/run.py +++ b/tcex_cli/cli/run/run.py @@ -12,28 +12,47 @@ def command( - config_json: Path = typer.Option( - 'app_inputs.json', help='An OPTIONAL configuration file containing App Inputs.' + config_json: Path | None = typer.Option( + None, + '--config', + help=( + 'OPTIONAL App Inputs config file. Wins over app_inputs.json and the ' + 'app_inputs.d/ menu; pass app_inputs.d/.json to run that file directly.' + ), ), debug: bool = typer.Option(default=False, help='Run App in VS Code debug mode.'), debug_port: int = typer.Option( 5678, help='The port to use for the debug server. This must match the launch.json file.' ), ): - """Run the App.""" + """Run the App. + + Configuration & secrets: + + Config resolution precedence: an explicit --config wins and runs that file directly; + else app_inputs.json if present; else a selection menu over app_inputs.d/*.json. Passing + --config app_inputs.d/.json runs that scenario without the menu (the unattended path). + + Values come from the local .env (loaded at CLI startup) in two ways: (1) ${env.VAR_NAME} + placeholders inside the config JSON are substituted from the environment; (2) input model + fields omitted from the JSON fall back to the environment / .env automatically (the run input + models are pydantic BaseSettings with env_file='.env', case-insensitive) -- e.g. + tc_api_access_id, tc_api_secret_key, tc_token, and the proxy and kvstore settings. Values + present in the JSON take precedence over the .env fallback. + + There is ONE .env at the App root, shared by every file in app_inputs.d/: put credentials + there once and keep per-scenario inputs in each app_inputs.d/.json. Gotcha: an undefined + ${env.VAR} is now a hard error -- the run stops and reports which variable to define or fix. + """ cli = RunCli() try: cli.update_system_path() - # validate config.json - if not config_json.is_file(): - Render.panel.failure(f'Config file not found [{config_json}]') - # run in debug mode if debug is True: cli.debug(debug_port) - # run the App + # run the App (RunCli.run resolves the config to use) cli.run(config_json, debug) except Exception as ex: diff --git a/tcex_cli/cli/run/run_cli.py b/tcex_cli/cli/run/run_cli.py index 86e867e..253c0b9 100644 --- a/tcex_cli/cli/run/run_cli.py +++ b/tcex_cli/cli/run/run_cli.py @@ -1,6 +1,7 @@ """TcEx Framework Module""" # standard library +import json import os import sys from pathlib import Path @@ -72,12 +73,87 @@ def exit_cli(self, exit_code): Render.panel.info(f'{exit_code}', f'[{self.panel_title}]Exit Code[/]') sys.exit(exit_code) - def run(self, config_json: Path, debug: bool = False): + @staticmethod + def _read_config_description(config_file: Path) -> str: + """Return the best-effort description from a config file's raw JSON.""" + try: + data = json.loads(config_file.read_text(encoding='utf-8')) + except (OSError, ValueError): + return '' + if isinstance(data, dict): + description = data.get('description') + if isinstance(description, str): + return description + return '' + + def _select_config_from_dir(self, config_dir: Path) -> Path: + """Render a menu of app_inputs.d/ candidates and return the selected file.""" + candidates = sorted(config_dir.glob('*.json')) + if not candidates: + Render.panel.failure( + 'No app_inputs.json found. Provide app_inputs.json, pass --config , ' + 'or add a config under app_inputs.d/.' + ) + + # render the list of available configs (index, filename, description) + items = [ + (i, candidate.stem, self._read_config_description(candidate)) + for i, candidate in enumerate(candidates, start=1) + ] + Render.table_app_inputs_d(items) + + # prompt the user to select a config by number + choices = [str(i) for i in range(1, len(candidates) + 1)] + answer = Render.prompt.ask('Select a config to run', choices=choices, default='1') + if not answer: + Render.panel.failure('No config selected.') + + selected = candidates[int(answer) - 1] + if not selected.is_file(): + Render.panel.failure(f'Config file not found [{selected}].') + return selected + + def resolve_config(self, config_json: Path | None) -> Path: # noqa: RET503 + """Resolve the config file to use, applying the precedence order. + + Precedence: + 1. an explicit ``--config `` (must exist) wins and skips the menu; + 2. else ``app_inputs.json`` if present; + 3. else an ``app_inputs.d/`` selection menu (if it holds any ``*.json``); + 4. else a helpful failure. + + Every returned path is a verified existing file. + """ + # explicit --config wins (including app_inputs.d/.json) and skips the menu + if config_json is not None: + if not config_json.is_file(): + Render.panel.failure(f'Config file not found [{config_json}].') + return config_json + + # legacy single-config default + app_inputs_json = Path('app_inputs.json') + if app_inputs_json.is_file(): + return app_inputs_json + + # multi-config directory + config_dir = Path('app_inputs.d') + if config_dir.is_dir(): + return self._select_config_from_dir(config_dir) + + Render.panel.failure( + 'No app_inputs.json found. Provide app_inputs.json, pass --config , ' + 'or add a config under app_inputs.d/.' + ) + + def run(self, config_json: Path | None, debug: bool = False): """Run the App""" + # resolve the config to use (single owner of resolution) + resolved = self.resolve_config(config_json) + match self.ij.model.runtime_level.lower(): case 'apiservice': Render.panel.info('Launching API Service', f'[{self.panel_title}]Running App[/]') - launch_app = LaunchServiceApi(config_json) + launch_app = LaunchServiceApi(resolved) self._display_api_settings(launch_app.model.inputs) launch_app.setup(debug) exit_code = launch_app.launch() @@ -86,18 +162,19 @@ def run(self, config_json: Path, debug: bool = False): Render.panel.info( 'Launching Feed API Service', f'[{self.panel_title}]Running App[/]' ) - launch_app = LaunchServiceApi(config_json) + launch_app = LaunchServiceApi(resolved) launch_app.setup(debug) exit_code = launch_app.launch() case 'organization' | 'system': Render.panel.info('Launching Job App', f'[{self.panel_title}]Running App[/]') - launch_app = LaunchOrganization(config_json) + launch_app = LaunchOrganization(resolved) exit_code = launch_app.launch() launch_app.print_input_data() case 'playbook': - launch_app = LaunchPlaybook(config_json) + launch_app = LaunchPlaybook(resolved) + launch_app.validate_inputs() launch_app.stage() exit_code = launch_app.launch() launch_app.print_input_data() @@ -107,7 +184,7 @@ def run(self, config_json: Path, debug: bool = False): Render.panel.info( 'Launching Trigger Service', f'[{self.panel_title}]Running App[/]' ) - launch_app = LaunchServiceCustomTrigger(config_json) + launch_app = LaunchServiceCustomTrigger(resolved) launch_app.setup(debug) exit_code = launch_app.launch() @@ -115,7 +192,7 @@ def run(self, config_json: Path, debug: bool = False): Render.panel.info( 'Launching Webhook Trigger Service', f'[{self.panel_title}]Running App[/]' ) - launch_app = LaunchServiceWebhookTrigger(config_json) + launch_app = LaunchServiceWebhookTrigger(resolved) self._display_api_settings(launch_app.model.inputs) launch_app.setup(debug) exit_code = launch_app.launch() diff --git a/tcex_cli/render/render.py b/tcex_cli/render/render.py index d4c8a70..3138e29 100644 --- a/tcex_cli/render/render.py +++ b/tcex_cli/render/render.py @@ -47,6 +47,58 @@ def progress_text_column(cls) -> TextColumn: """Return a progress bar column.""" return TextColumn('{task.description}', table_column=Column(ratio=1)) + @classmethod + def table_app_inputs_d(cls, items: list[tuple[int, str, str]]): + """Render the list of app_inputs.d/ config candidates. + + Args: + items: A list of (index, filename, description) tuples. + """ + table = Table( + expand=True, + border_style='dim', + show_edge=False, + show_header=True, + ) + + table.add_column( + '#', + justify='right', + max_width=4, + min_width=4, + style=cls.accent2, + no_wrap=True, + ) + table.add_column( + 'Config', + justify='left', + max_width=30, + min_width=30, + style='dodger_blue2', + no_wrap=True, + ) + table.add_column( + 'Description', + justify='left', + max_width=66, + min_width=66, + style='bold', + ) + + for index, filename, description in items: + table.add_row(str(index), filename, description) + + # render panel->table + if items: + print_( + Panel( + table, + border_style='', + title='Available Configs', + title_align=cls.title_align, + ) + ) + @classmethod def table_mismatch( cls, diff --git a/tests/app_input/__init__.py b/tests/app_input/__init__.py new file mode 100644 index 0000000..558af89 --- /dev/null +++ b/tests/app_input/__init__.py @@ -0,0 +1 @@ +"""TcEx Framework Module""" diff --git a/tests/app_input/test_app_input.py b/tests/app_input/test_app_input.py new file mode 100644 index 0000000..f74f951 --- /dev/null +++ b/tests/app_input/test_app_input.py @@ -0,0 +1,184 @@ +"""Test Module""" + +# standard library +import json +from pathlib import Path +from types import SimpleNamespace + +# third-party +import pytest + +# first-party +from tcex_cli.cli.app_input.app_input_cli import AppInputCli +from tcex_cli.cli.run.model.common_app_input_model import CommonAppInputModel +from tcex_cli.render.render import Render + + +def _new_app_input_cli(name: str, description: str = 'desc') -> AppInputCli: + """Return an AppInputCli instance without running __init__. + + ``AppInputCli.__init__`` loads install.json, which is not needed to exercise the slug / + write-output logic, so build a bare instance and set only the attributes under test. + """ + cli = object.__new__(AppInputCli) + cli.name = name + cli.description = description + cli.include_optional = False + cli.inputs = {} + cli.kvstore = {} + # install.json with no params -> generate_app_inputs simply writes the empty config + cli.ij = SimpleNamespace(model=SimpleNamespace(params=[])) + return cli + + +def _answer_yes(*_args, **_kwargs) -> str: + """Overwrite-prompt stand-in that confirms overwrite.""" + return 'y' + + +def _answer_no(*_args, **_kwargs) -> str: + """Overwrite-prompt stand-in that declines overwrite.""" + return 'N' + + +class TestAppInputSlugify: + """Test AppInputCli._slugify_name sanitization and anti-traversal guard.""" + + @pytest.mark.parametrize( + argnames='name,expected', + argvalues=[ + pytest.param('My Config.json', 'my_config', id='pass-spaces-and-json-suffix'), + pytest.param('my config', 'my_config', id='pass-collapse-repeats'), + pytest.param('Smoke-Test_1', 'smoke-test_1', id='pass-allowed-chars'), + pytest.param(' trim me ', 'trim_me', id='pass-strip-whitespace'), + pytest.param('UPPER.JSON', 'upper', id='pass-uppercase-json-suffix'), + ], + ) + def test_slugify_valid(self, name: str, expected: str): + """Valid names slugify to a filesystem-safe lower-case token.""" + cli = _new_app_input_cli(name) + slug = cli._slugify_name(name) # noqa: SLF001 + assert slug == expected, f'{name!r} did not slugify to {expected!r}' + + @pytest.mark.parametrize( + argnames='name,expected', + argvalues=[ + # traversal/separator characters are stripped to safe single-segment slugs that + # cannot escape app_inputs.d/ (Path(slug).name == slug holds) + pytest.param('../x', 'x', id='pass-parent-traversal-sanitized'), + pytest.param('a/b', 'a_b', id='pass-path-separator-sanitized'), + ], + ) + def test_slugify_traversal_sanitized(self, name: str, expected: str): + """Path-escaping characters are reduced to a safe single-segment slug, not rejected.""" + cli = _new_app_input_cli(name) + slug = cli._slugify_name(name) # noqa: SLF001 + assert slug == expected, f'{name!r} did not sanitize to {expected!r}' + # the resulting slug stays strictly under app_inputs.d/ + assert slug == Path(slug).name + + @pytest.mark.parametrize( + argnames='name', + argvalues=[ + pytest.param('', id='fail-empty'), + pytest.param(' ', id='fail-whitespace-only'), + pytest.param('***', id='fail-sanitizes-to-empty'), + pytest.param('.json', id='fail-suffix-only'), + pytest.param('/', id='fail-separator-only'), + pytest.param('..', id='fail-dotdot-only'), + ], + ) + def test_slugify_rejected(self, name: str): + """Names that sanitize to empty are rejected via failure (SystemExit).""" + cli = _new_app_input_cli(name) + with pytest.raises(SystemExit): + cli._slugify_name(name) # noqa: SLF001 + + +class TestAppInputWriteOutput: + """Test AppInputCli.write_output_file / generate_app_inputs file emission.""" + + @staticmethod + def test_generate_creates_app_inputs_d_file(tmp_path: Path, monkeypatch: pytest.MonkeyPatch): + """generate_app_inputs writes app_inputs.d/.json with description/inputs/stage.""" + monkeypatch.chdir(tmp_path) + cli = _new_app_input_cli('My Config', description='a smoke test') + cli.generate_app_inputs() + + output_file = tmp_path / 'app_inputs.d' / 'my_config.json' + assert output_file.is_file() + + data = json.loads(output_file.read_text(encoding='utf-8')) + assert data['description'] == 'a smoke test' + assert data['inputs'] == {} + assert data['stage']['kvstore'] == {} + + @staticmethod + def test_write_creates_directory_when_absent(tmp_path: Path, monkeypatch: pytest.MonkeyPatch): + """write_output_file creates app_inputs.d/ when it does not yet exist.""" + monkeypatch.chdir(tmp_path) + assert not (tmp_path / 'app_inputs.d').exists() + + cli = _new_app_input_cli('config_one', description='d') + cli.write_output_file({'description': 'd', 'inputs': {}, 'stage': {'kvstore': {}}}) + + assert (tmp_path / 'app_inputs.d' / 'config_one.json').is_file() + + @staticmethod + def test_overwrite_confirmed(tmp_path: Path, monkeypatch: pytest.MonkeyPatch): + """An existing target + 'y' at the prompt overwrites the file.""" + monkeypatch.chdir(tmp_path) + config_dir = tmp_path / 'app_inputs.d' + config_dir.mkdir() + target = config_dir / 'existing.json' + target.write_text(json.dumps({'description': 'old'}), encoding='utf-8') + + monkeypatch.setattr(Render.prompt, 'input', staticmethod(_answer_yes)) + + cli = _new_app_input_cli('existing', description='new') + cli.write_output_file({'description': 'new', 'inputs': {}, 'stage': {'kvstore': {}}}) + + data = json.loads(target.read_text(encoding='utf-8')) + assert data['description'] == 'new' + + @staticmethod + def test_overwrite_aborted(tmp_path: Path, monkeypatch: pytest.MonkeyPatch): + """An existing target + 'N' at the prompt aborts via failure and leaves the file intact.""" + monkeypatch.chdir(tmp_path) + config_dir = tmp_path / 'app_inputs.d' + config_dir.mkdir() + target = config_dir / 'existing.json' + target.write_text(json.dumps({'description': 'old'}), encoding='utf-8') + + monkeypatch.setattr(Render.prompt, 'input', staticmethod(_answer_no)) + + cli = _new_app_input_cli('existing', description='new') + with pytest.raises(SystemExit): + cli.write_output_file({'description': 'new', 'inputs': {}, 'stage': {'kvstore': {}}}) + + # original content is preserved + data = json.loads(target.read_text(encoding='utf-8')) + assert data['description'] == 'old' + + +class TestCommonAppInputModel: + """Test the description field round-trips and is optional.""" + + @staticmethod + def test_model_with_description(): + """A config WITH a description validates and retains the value.""" + model = CommonAppInputModel( + description='has description', + stage={'kvstore': {}}, + inputs={}, + ) + assert model.description == 'has description' + + @staticmethod + def test_model_without_description_defaults_none(): + """A config WITHOUT a description still validates; description defaults to None.""" + model = CommonAppInputModel( + stage={'kvstore': {}}, + inputs={}, + ) + assert model.description is None diff --git a/tests/package/__init__.py b/tests/package/__init__.py new file mode 100644 index 0000000..558af89 --- /dev/null +++ b/tests/package/__init__.py @@ -0,0 +1 @@ +"""TcEx Framework Module""" diff --git a/tests/package/test_package.py b/tests/package/test_package.py new file mode 100644 index 0000000..4febbf8 --- /dev/null +++ b/tests/package/test_package.py @@ -0,0 +1,53 @@ +"""Test Module""" + +# standard library +from pathlib import Path +from types import SimpleNamespace + +# third-party +import pytest + +# first-party +from tcex_cli.cli.package.package_cli import PackageCli + + +def _new_package_cli() -> PackageCli: + """Return a PackageCli instance without running __init__. + + ``PackageCli.__init__`` only stores ``excludes`` / ``ignore_validation`` / ``output_dir``; + ``_build_excludes_base`` additionally reads ``self.app.tj.model.package.excludes``. Build a bare + instance and stub those so the excludes logic can be exercised without loading an App. + """ + cli = object.__new__(PackageCli) + cli._excludes = [] # noqa: SLF001 + cli.ignore_validation = False + cli.output_dir = Path('target') + cli.app = SimpleNamespace( + tj=SimpleNamespace(model=SimpleNamespace(package=SimpleNamespace(excludes=[]))) + ) + return cli + + +class TestPackageExcludes: + """Test that app_inputs.d/ is excluded from packaging.""" + + @staticmethod + def test_app_inputs_d_in_base_excludes(): + """_build_excludes_base contains the bare 'app_inputs.d' token (a directory, not a glob).""" + cli = _new_package_cli() + excludes = cli._build_excludes_base # noqa: SLF001 + assert 'app_inputs.d' in excludes + # bare token, not a glob (a glob would only drop the contents, leaving an empty dir) + assert 'app_inputs.d/**' not in excludes + + @staticmethod + def test_exclude_files_ignores_app_inputs_d(tmp_path: Path, monkeypatch: pytest.MonkeyPatch): + """exclude_files() flags an 'app_inputs.d' directory entry for exclusion.""" + monkeypatch.chdir(tmp_path) + cli = _new_package_cli() + + names = ['app.py', 'app_inputs.d', 'install.json'] + ignored = cli.exclude_files(str(tmp_path), names) + assert 'app_inputs.d' in ignored + # a normal source file is not excluded + assert 'app.py' not in ignored diff --git a/tests/run/__init__.py b/tests/run/__init__.py new file mode 100644 index 0000000..558af89 --- /dev/null +++ b/tests/run/__init__.py @@ -0,0 +1 @@ +"""TcEx Framework Module""" diff --git a/tests/run/test_run.py b/tests/run/test_run.py new file mode 100644 index 0000000..70d0dea --- /dev/null +++ b/tests/run/test_run.py @@ -0,0 +1,189 @@ +"""Test Module""" + +# standard library +import json +from pathlib import Path + +# third-party +import pytest + +# first-party +from tcex_cli.cli.run.run_cli import RunCli +from tcex_cli.render.render import Render + + +def _new_run_cli() -> RunCli: + """Return a RunCli instance without running __init__. + + ``RunCli.__init__`` validates the cwd is an App directory and loads install.json; none of that + is needed to exercise ``resolve_config`` / the selection helpers, so build a bare instance. + """ + return object.__new__(RunCli) + + +def _write_config(path: Path, description: str = '') -> None: + """Write a minimal app-inputs config file.""" + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text( + json.dumps({'description': description, 'inputs': {}, 'stage': {'kvstore': {}}}), + encoding='utf-8', + ) + + +def _menu_must_not_show(*_args, **_kwargs): + """Prompt stand-in that fails if the selection menu is ever reached.""" + msg = 'selection menu must not be shown' + raise AssertionError(msg) + + +class TestRunCliResolveConfig: + """Test RunCli.resolve_config precedence and the app_inputs.d/ menu.""" + + @staticmethod + def test_explicit_config_wins(tmp_path: Path, monkeypatch: pytest.MonkeyPatch): + """Explicit --config that exists is returned and skips the menu.""" + monkeypatch.chdir(tmp_path) + # an app_inputs.json is also present; --config must still win + _write_config(tmp_path / 'app_inputs.json') + config = tmp_path / 'custom.json' + _write_config(config) + + cli = _new_run_cli() + resolved = cli.resolve_config(config) + assert resolved == config + + @staticmethod + def test_explicit_config_in_app_inputs_d_wins(tmp_path: Path, monkeypatch: pytest.MonkeyPatch): + """An explicit --config pointing inside app_inputs.d/ is returned without a menu.""" + monkeypatch.chdir(tmp_path) + config = tmp_path / 'app_inputs.d' / 'mytest.json' + _write_config(config) + + # make the menu fail loudly if it is ever reached + monkeypatch.setattr(Render.prompt, 'ask', _menu_must_not_show) + + cli = _new_run_cli() + resolved = cli.resolve_config(Path('app_inputs.d') / 'mytest.json') + assert resolved == Path('app_inputs.d') / 'mytest.json' + + @staticmethod + def test_explicit_config_missing_fails(tmp_path: Path, monkeypatch: pytest.MonkeyPatch): + """An explicit --config that does not exist triggers a failure (SystemExit).""" + monkeypatch.chdir(tmp_path) + cli = _new_run_cli() + with pytest.raises(SystemExit): + cli.resolve_config(tmp_path / 'does_not_exist.json') + + @staticmethod + def test_app_inputs_json_used_when_present(tmp_path: Path, monkeypatch: pytest.MonkeyPatch): + """With no --config, app_inputs.json is returned and the menu is not shown.""" + monkeypatch.chdir(tmp_path) + _write_config(tmp_path / 'app_inputs.json') + monkeypatch.setattr(Render.prompt, 'ask', _menu_must_not_show) + + cli = _new_run_cli() + resolved = cli.resolve_config(None) + assert resolved == Path('app_inputs.json') + + @staticmethod + def test_menu_selects_second_candidate(tmp_path: Path, monkeypatch: pytest.MonkeyPatch): + """No --config / json, multiple app_inputs.d/ files -> menu choice 2 wins.""" + monkeypatch.chdir(tmp_path) + config_dir = tmp_path / 'app_inputs.d' + # create out of lexicographic order to prove sorting drives the indices + _write_config(config_dir / 'zeta.json', description='zeta config') + _write_config(config_dir / 'alpha.json', description='alpha config') + + # quiet the table render; capture the choices the prompt is offered + monkeypatch.setattr(Render, 'table_app_inputs_d', staticmethod(_noop_table)) + + captured = {} + + def _ask(_text, choices=None, default=None, **_kwargs): + captured['choices'] = choices + captured['default'] = default + return '2' + + monkeypatch.setattr(Render.prompt, 'ask', _ask) + + cli = _new_run_cli() + resolved = cli.resolve_config(None) + + # resolve_config uses bare relative Path('app_inputs.d'); sorted -> [alpha.json, zeta.json]; + # choice '2' -> candidates[1] -> zeta.json + assert resolved == Path('app_inputs.d') / 'zeta.json' + assert captured['choices'] == ['1', '2'] + assert captured['default'] == '1' + + @staticmethod + def test_menu_single_candidate_still_shown(tmp_path: Path, monkeypatch: pytest.MonkeyPatch): + """A single app_inputs.d/ file still shows the one-item menu; choice 1 returns it.""" + monkeypatch.chdir(tmp_path) + config_dir = tmp_path / 'app_inputs.d' + _write_config(config_dir / 'only.json', description='only config') + + rendered = {} + + def _table(items): + rendered['items'] = items + + monkeypatch.setattr(Render, 'table_app_inputs_d', staticmethod(_table)) + monkeypatch.setattr(Render.prompt, 'ask', staticmethod(_answer_one)) + + cli = _new_run_cli() + resolved = cli.resolve_config(None) + + assert resolved == Path('app_inputs.d') / 'only.json' + # the menu was rendered with exactly one item: (index, stem, description) + assert rendered['items'] == [(1, 'only', 'only config')] + + @staticmethod + def test_no_config_available_fails(tmp_path: Path, monkeypatch: pytest.MonkeyPatch): + """Nothing available (no --config, no json, no app_inputs.d/) -> failure (SystemExit).""" + monkeypatch.chdir(tmp_path) + cli = _new_run_cli() + with pytest.raises(SystemExit): + cli.resolve_config(None) + + @staticmethod + def test_empty_app_inputs_d_fails(tmp_path: Path, monkeypatch: pytest.MonkeyPatch): + """An app_inputs.d/ directory with no *.json files -> failure (SystemExit).""" + monkeypatch.chdir(tmp_path) + (tmp_path / 'app_inputs.d').mkdir() + cli = _new_run_cli() + with pytest.raises(SystemExit): + cli.resolve_config(None) + + +class TestRunCliReadConfigDescription: + """Test the best-effort raw-JSON description reader used by the menu.""" + + @staticmethod + def test_reads_description(tmp_path: Path): + """A valid config with a description returns it.""" + config = tmp_path / 'c.json' + _write_config(config, description='hello world') + assert RunCli._read_config_description(config) == 'hello world' # noqa: SLF001 + + @staticmethod + def test_missing_description_returns_blank(tmp_path: Path): + """A config without a description returns an empty string.""" + config = tmp_path / 'c.json' + config.write_text(json.dumps({'inputs': {}, 'stage': {'kvstore': {}}}), encoding='utf-8') + assert RunCli._read_config_description(config) == '' # noqa: SLF001 + + @staticmethod + def test_invalid_json_returns_blank(tmp_path: Path): + """Unreadable / invalid JSON never crashes the listing; returns an empty string.""" + config = tmp_path / 'c.json' + config.write_text('{not valid json', encoding='utf-8') + assert RunCli._read_config_description(config) == '' # noqa: SLF001 + + +def _noop_table(_items): + """No-op stand-in for Render.table_app_inputs_d.""" + + +def _answer_one(*_args, **_kwargs) -> str: + """Prompt stand-in that selects the first menu item.""" + return '1' diff --git a/tests/run/test_validate_env.py b/tests/run/test_validate_env.py new file mode 100644 index 0000000..f80e07e --- /dev/null +++ b/tests/run/test_validate_env.py @@ -0,0 +1,162 @@ +"""Test Module""" + +# standard library +import json +from pathlib import Path + +# third-party +import pytest + +# first-party +from tcex_cli.cli.run.launch_playbook import LaunchPlaybook + + +def _new_launch_playbook() -> LaunchPlaybook: + """Return a bare ``LaunchPlaybook`` without running ``__init__``. + + ``LaunchPlaybook.__init__`` (via ``LaunchABC.__init__``) starts a (fake) Redis server and builds + the input model; ``_validate_env_variables`` needs none of that -- it relies only on + module-level ``os.getenv`` / ``difflib`` + ``Render``. Build a bare instance so the env-var + validator can be exercised in isolation. + + Returns: + A bare ``LaunchPlaybook`` for ``_validate_env_variables`` / ``construct_model_inputs``. + """ + return object.__new__(LaunchPlaybook) + + +@pytest.fixture(autouse=True) +def _wide_console(monkeypatch: pytest.MonkeyPatch): + """Force a very wide rich console so panel text is not wrapped/truncated. + + ``Render.panel.*`` renders through a ``rich`` ``Panel``; rich sizes its console to the terminal + width (80 cols under pytest capture), which clips the message and makes substring assertions + flaky. Pinning ``COLUMNS`` keeps the full message on one line for deterministic assertions. + """ + monkeypatch.setenv('COLUMNS', '4000') + + +class TestValidateEnvVariables: + """Test LaunchABC._validate_env_variables fails fast on undefined ${env.NAME} placeholders.""" + + @staticmethod + def test_undefined_env_var_fails( + capsys: pytest.CaptureFixture, monkeypatch: pytest.MonkeyPatch + ): + """An undefined ${env.NAME} hard-fails (SystemExit) and names the offending var.""" + monkeypatch.delenv('VAULT_MOUNT1', raising=False) + lp = _new_launch_playbook() + data = '{"inputs": {"vault_mount": "${env.VAULT_MOUNT1}"}}' + + with pytest.raises(SystemExit): + lp._validate_env_variables(data) # noqa: SLF001 + + out = capsys.readouterr().out + assert '${env.VAULT_MOUNT1}' in out + + @staticmethod + def test_did_you_mean_suggestion( + capsys: pytest.CaptureFixture, monkeypatch: pytest.MonkeyPatch + ): + """An undefined var with a close-matching defined var is offered as a suggestion.""" + monkeypatch.delenv('VAULT_MOUNT1', raising=False) + monkeypatch.setenv('VAULT_MOUNT', 'mount-value') + lp = _new_launch_playbook() + data = '{"inputs": {"vault_mount": "${env.VAULT_MOUNT1}"}}' + + with pytest.raises(SystemExit): + lp._validate_env_variables(data) # noqa: SLF001 + + out = capsys.readouterr().out + # the close-match (defined) var is offered as a suggestion + assert '${env.VAULT_MOUNT}' in out + assert 'did you mean' in out.lower() + + @staticmethod + def test_all_defined_no_error(monkeypatch: pytest.MonkeyPatch): + """Every referenced env var is defined -> no SystemExit (method returns None).""" + monkeypatch.setenv('VAULT_MOUNT', 'mount-value') + lp = _new_launch_playbook() + data = '{"inputs": {"vault_mount": "${env.VAULT_MOUNT}"}}' + + # no SystemExit -- all referenced env vars are defined + assert lp._validate_env_variables(data) is None # noqa: SLF001 + + @staticmethod + def test_empty_string_env_var_counts_as_defined(monkeypatch: pytest.MonkeyPatch): + """An env var defined as an empty string is still "defined" -> no SystemExit.""" + monkeypatch.setenv('VAULT_MOUNT', '') + lp = _new_launch_playbook() + data = '{"inputs": {"vault_mount": "${env.VAULT_MOUNT}"}}' + + # no SystemExit -- empty-string is a defined value (os.getenv returns '', not None) + assert lp._validate_env_variables(data) is None # noqa: SLF001 + + @staticmethod + def test_multiple_undefined_all_reported( + capsys: pytest.CaptureFixture, monkeypatch: pytest.MonkeyPatch + ): + """Multiple undefined ${env.NAME} placeholders are all named in the failure output.""" + monkeypatch.delenv('A_UNDEF', raising=False) + monkeypatch.delenv('B_UNDEF', raising=False) + lp = _new_launch_playbook() + data = '{"a": "${env.A_UNDEF}", "b": "${env.B_UNDEF}"}' + + with pytest.raises(SystemExit): + lp._validate_env_variables(data) # noqa: SLF001 + + out = capsys.readouterr().out + assert '${env.A_UNDEF}' in out + assert '${env.B_UNDEF}' in out + + @staticmethod + def test_no_env_tokens_no_error(): + """Plain JSON with no ${env...} placeholders -> no SystemExit.""" + lp = _new_launch_playbook() + data = '{"inputs": {"vault_mount": "literal-value", "count": 5}}' + + # no SystemExit -- nothing to validate + assert lp._validate_env_variables(data) is None # noqa: SLF001 + + +class TestConstructModelInputsEnvValidation: + """Test construct_model_inputs runs env validation before JSON parsing.""" + + @staticmethod + def test_undefined_env_var_fails_before_json_load( + capsys: pytest.CaptureFixture, monkeypatch: pytest.MonkeyPatch, tmp_path: Path + ): + """construct_model_inputs hard-fails on an undefined ${env.NAME} before json.loads runs.""" + monkeypatch.delenv('UNDEFINED_X', raising=False) + config_json = tmp_path / 'app_inputs.json' + config_json.write_text( + json.dumps({'inputs': {'vault_mount': '${env.UNDEFINED_X}'}}), + encoding='utf-8', + ) + + lp = _new_launch_playbook() + lp.config_json = config_json + + with pytest.raises(SystemExit): + lp.construct_model_inputs() + + out = capsys.readouterr().out + assert '${env.UNDEFINED_X}' in out + + @staticmethod + def test_defined_env_var_substituted_and_parsed( + monkeypatch: pytest.MonkeyPatch, tmp_path: Path + ): + """A defined ${env.NAME} passes validation, is substituted, and the JSON parses.""" + monkeypatch.setenv('DEFINED_X', 'secret-mount') + config_json = tmp_path / 'app_inputs.json' + config_json.write_text( + json.dumps({'inputs': {'vault_mount': '${env.DEFINED_X}'}}), + encoding='utf-8', + ) + + lp = _new_launch_playbook() + lp.config_json = config_json + + result = lp.construct_model_inputs() + assert result == {'inputs': {'vault_mount': 'secret-mount'}} diff --git a/tests/run/test_validate_inputs.py b/tests/run/test_validate_inputs.py new file mode 100644 index 0000000..ee7d5ba --- /dev/null +++ b/tests/run/test_validate_inputs.py @@ -0,0 +1,298 @@ +"""Test Module""" + +# standard library +from pathlib import Path + +# third-party +import pytest + +# first-party +from tcex_cli.cli.run.launch_playbook import LaunchPlaybook +from tcex_cli.util import Util + + +def _new_launch_playbook(config: dict) -> LaunchPlaybook: + """Return a LaunchPlaybook instance without running ``__init__``. + + ``LaunchPlaybook.__init__`` (via ``LaunchABC.__init__``) starts a (fake) Redis server and builds + the input model; none of that is needed to exercise ``validate_inputs`` / the variable helpers. + Build a bare instance and set only the attributes the validator touches: a real ``Util`` (its + variable parsing is what we exercise), a ``config_json`` path (read for the failure message), + and a stubbed ``construct_model_inputs`` returning the already-parsed config dict. + + Args: + config: The parsed config dict ``validate_inputs`` operates on. + + Returns: + A bare ``LaunchPlaybook`` ready for ``validate_inputs`` / ``_find_app_variables``. + """ + lp = object.__new__(LaunchPlaybook) + lp.util = Util() + lp.config_json = Path('app_inputs.d') / 'mismatch.json' + lp.construct_model_inputs = lambda: config # type: ignore[method-assign] + return lp + + +@pytest.fixture(autouse=True) +def _wide_console(monkeypatch: pytest.MonkeyPatch): + """Force a very wide rich console so panel text is not wrapped/truncated. + + ``Render.panel.*`` renders through a ``rich`` ``Panel``; rich sizes its console to the terminal + width (80 cols under pytest capture), which clips the message and makes substring assertions + flaky. Pinning ``COLUMNS`` keeps the full message on one line for deterministic assertions. + """ + monkeypatch.setenv('COLUMNS', '4000') + + +class TestValidateInputs: + """Test LaunchPlaybook.validate_inputs cross-checks #App: references against stage.kvstore.""" + + @staticmethod + def test_missing_reference_fails(capsys: pytest.CaptureFixture): + """An input referencing an unstaged #App: variable hard-fails (SystemExit) before Redis I/O. + + The failure message must name the offending variable and the input that references it. + """ + config = { + 'inputs': {'vault_mount': '#App:1234:vault_mount!String'}, + 'stage': {'kvstore': {'#App:1022:vault_mount!String': 'ninja'}}, + } + lp = _new_launch_playbook(config) + + with pytest.raises(SystemExit): + lp.validate_inputs() + + out = capsys.readouterr().out + assert '#App:1234:vault_mount!String' in out + assert 'vault_mount' in out + + @staticmethod + def test_did_you_mean_suggestion(capsys: pytest.CaptureFixture): + """A staged key with the same key+type but a different job_id is offered as a suggestion.""" + config = { + 'inputs': {'vault_mount': '#App:1234:vault_mount!String'}, + 'stage': {'kvstore': {'#App:1022:vault_mount!String': 'ninja'}}, + } + lp = _new_launch_playbook(config) + + with pytest.raises(SystemExit): + lp.validate_inputs() + + out = capsys.readouterr().out + # the suggestion references the staged key (same key+type, different job_id) + assert '#App:1022:vault_mount!String' in out + assert 'did you mean' in out.lower() + + @staticmethod + def test_all_matched_clean(capsys: pytest.CaptureFixture): + """Every #App: reference is staged -> no error and no unused-key warning.""" + config = { + 'inputs': {'x': '#App:1022:vault_mount!String'}, + 'stage': {'kvstore': {'#App:1022:vault_mount!String': 'ninja'}}, + } + lp = _new_launch_playbook(config) + + # no SystemExit + lp.validate_inputs() + + out = capsys.readouterr().out + assert 'not referenced' not in out + assert 'not staged' not in out + + @staticmethod + def test_unused_staged_key_warns(capsys: pytest.CaptureFixture): + """A staged key that no input references -> non-blocking warning, run continues.""" + config = { + 'inputs': {'x': '#App:1022:vault_mount!String'}, + 'stage': { + 'kvstore': { + '#App:1022:vault_mount!String': 'ninja', + '#App:1099:spare!String': 'leftover', + } + }, + } + lp = _new_launch_playbook(config) + + # no SystemExit + lp.validate_inputs() + + out = capsys.readouterr().out + assert '#App:1099:spare!String' in out + + @staticmethod + def test_global_and_trigger_ignored(): + """#Global: / #Trigger: references are runtime-provided and are not validated.""" + config = { + 'inputs': { + 'gbl': '#Global:0:gbl.ts!String', + 'trig': '#Trigger:1:t.body!String', + }, + 'stage': {'kvstore': {}}, + } + lp = _new_launch_playbook(config) + + # no SystemExit -- non-#App vars are excluded from validation + lp.validate_inputs() + + @staticmethod + def test_literal_inputs_ignored(): + """Literal / scalar (non-variable) input values are ignored by the validator.""" + config = { + 'inputs': {'flag': 'true', 'count': 5, 'name': 'plain'}, + 'stage': {'kvstore': {}}, + } + lp = _new_launch_playbook(config) + + # no SystemExit -- no #App: references present + lp.validate_inputs() + + @staticmethod + def test_output_variables_excluded(capsys: pytest.CaptureFixture): + """Amendment 1: tc_playbook_out_variables declares OUTPUT vars -> excluded from the scan. + + The App writes these variables; it does not read them, so their ``#App:...`` values must + never be required in ``stage.kvstore``. With an empty kvstore there must be no missing-stage + hard error (no SystemExit) and no missing-variable failure output. + """ + config = { + 'inputs': { + 'tc_playbook_out_variables': [ + '#App:1234:out_one!String', + '#App:1234:out_two!StringArray', + ] + }, + 'stage': {'kvstore': {}}, + } + lp = _new_launch_playbook(config) + + # no SystemExit -- output-variable inputs are excluded from the referenced-variable scan + lp.validate_inputs() + + out = capsys.readouterr().out + assert 'not staged' not in out + assert '#App:1234:out_one!String' not in out + assert '#App:1234:out_two!StringArray' not in out + + @staticmethod + def test_output_variables_excluded_but_read_input_still_validated( + capsys: pytest.CaptureFixture, + ): + """A real read input is still validated while output-variable inputs are ignored. + + ``tc_playbook_out_variables`` (an output declaration) is excluded, but a genuine read input + referencing an unstaged variable still hard-fails. The failure names the read input/variable + and must NOT name the excluded output variable. + """ + config = { + 'inputs': { + 'tc_playbook_out_variables': ['#App:1234:out!String'], + 'vault_mount': '#App:1234:vault_mount!String', + }, + 'stage': {'kvstore': {}}, + } + lp = _new_launch_playbook(config) + + with pytest.raises(SystemExit): + lp.validate_inputs() + + out = capsys.readouterr().out + # the genuine read input is flagged + assert 'vault_mount' in out + assert '#App:1234:vault_mount!String' in out + # the excluded output variable is not flagged + assert '#App:1234:out!String' not in out + + @staticmethod + def test_output_variables_excluded_no_unused_warning(capsys: pytest.CaptureFixture): + """Output-variable inputs do not trigger the unused-staged-key warning path. + + Every staged key is referenced by a genuine read input, so even though + ``tc_playbook_out_variables`` declares its own ``#App:...`` values, there is no + unused-staged-key warning (and no failure). + """ + config = { + 'inputs': { + 'tc_playbook_out_variables': ['#App:1234:out!String'], + 'x': '#App:1022:vault_mount!String', + }, + 'stage': {'kvstore': {'#App:1022:vault_mount!String': 'ninja'}}, + } + lp = _new_launch_playbook(config) + + # no SystemExit + lp.validate_inputs() + + out = capsys.readouterr().out + assert 'not referenced' not in out + assert 'not staged' not in out + + +class TestFindAppVariables: + """Test LaunchPlaybook._find_app_variables extracts #App: references from values.""" + + @staticmethod + def test_exact_str(): + """A str that is exactly an #App: variable returns that variable.""" + lp = _new_launch_playbook({}) + assert lp._find_app_variables('#App:1022:vault_mount!String') == { # noqa: SLF001 + '#App:1022:vault_mount!String' + } + + @staticmethod + def test_embedded_str(): + """An #App: variable embedded in surrounding text is still extracted.""" + lp = _new_launch_playbook({}) + value = 'prefix #App:1022:vault_mount!String suffix' + assert lp._find_app_variables(value) == { # noqa: SLF001 + '#App:1022:vault_mount!String' + } + + @staticmethod + def test_dict_values(): + """#App: references are collected from a dict's values.""" + lp = _new_launch_playbook({}) + value = { + 'a': '#App:1:one!String', + 'b': '#App:2:two!String', + } + assert lp._find_app_variables(value) == { # noqa: SLF001 + '#App:1:one!String', + '#App:2:two!String', + } + + @staticmethod + def test_list_values(): + """#App: references are collected from a list's items.""" + lp = _new_launch_playbook({}) + value = ['#App:1:one!String', '#App:2:two!String'] + assert lp._find_app_variables(value) == { # noqa: SLF001 + '#App:1:one!String', + '#App:2:two!String', + } + + @staticmethod + @pytest.mark.parametrize( + argnames='value', + argvalues=[ + pytest.param(5, id='int'), + pytest.param(True, id='bool'), + pytest.param(None, id='none'), + ], + ) + def test_scalar_returns_empty(value): + """Non-str/dict/list scalars yield an empty set.""" + lp = _new_launch_playbook({}) + assert lp._find_app_variables(value) == set() # noqa: SLF001 + + @staticmethod + @pytest.mark.parametrize( + argnames='value', + argvalues=[ + pytest.param('#Global:0:gbl.ts!String', id='global'), + pytest.param('#Trigger:1:t.body!String', id='trigger'), + ], + ) + def test_excludes_global_and_trigger(value): + """#Global: / #Trigger: variables are excluded (only app_type == 'App' is kept).""" + lp = _new_launch_playbook({}) + assert lp._find_app_variables(value) == set() # noqa: SLF001 From 236d515fa48771d24acfec3fba58ea1e1fa5866a Mon Sep 17 00:00:00 2001 From: Bracey Summers Date: Thu, 25 Jun 2026 18:32:09 -0500 Subject: [PATCH 8/8] rework template update around --managed; fix gitignore merge sha; rebuild fixture MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Template - planner.py: replace the force_keys mechanism with a managed_only mode in Planner.build(); add _classify_managed_key() — managed runs hash-compare template-managed entries (skip when identical, else auto_update), skip non-managed keys, and never delete project files - planner.py: add NotRequired 'managed' bool to the FileMeta TypedDict - planner.py: drop no_prompt/preserved from Planner.apply(); it now returns None - template_cli.py: replace the update() no_prompt parameter with managed; remove the now-unused _collect_template_file_keys() - template_cli.py: fix the renamed gitignore merged-manifest entry — compute sha256 from the file actually written (self.hasher.sha256_file(dest)) instead of the stray parent '.gitignore' entry, falling back to 'unknown' last_commit - update.py: replace the --no-prompt option with --managed (silent, non- interactive update of only template-managed files; nothing deleted) Run - launch_abc.py: redact the token value in the using-token setup log line Tests - add test_managed.py covering managed-mode plan classification and apply - remove obsolete test_template_files_force.py (force_keys behavior) - trim test_template_commands.py and test_gitignore_merge.py of removed force_keys / no_prompt paths - conftest.py: collapse TemplateCli construction to a single line Fixtures / Scripts - add .claude/scripts/build_template_fixture.py to rebuild the GitHub-style fixture from git-tracked template files under a single tcex-app-templates-2/ wrapper (dry-run by default, --commit to write) - rebuild tests/template/fixtures/tcex-app-templates-v2.zip with the correct single top-level wrapper (was rooted at a leaked absolute path) Docs / Config - release_notes.md: reword APP-5299 for --managed; add APP-5305/5306/5307 for run app_inputs.d multi-config, ${env.VAR} validation, and kvstore input checks - CLAUDE.md: condense the commit-policy wording - .claude/agents/tcex-orchestrator.md: expand the orchestrator tools list --- .claude/agents/tcex-orchestrator.md | 2 +- .claude/scripts/build_template_fixture.py | 172 ++++++ CLAUDE.md | 12 +- release_notes.md | 5 +- tcex_cli/cli/run/launch_abc.py | 2 +- tcex_cli/cli/template/planner.py | 94 +-- tcex_cli/cli/template/template_cli.py | 100 +--- tcex_cli/cli/template/update.py | 19 +- tests/template/conftest.py | 4 +- .../fixtures/tcex-app-templates-v2.zip | Bin 104702 -> 639988 bytes tests/template/test_gitignore_merge.py | 79 --- tests/template/test_managed.py | 417 ++++++++++++++ tests/template/test_merged_template.py | 1 - tests/template/test_template_abc.py | 6 +- tests/template/test_template_commands.py | 84 --- tests/template/test_template_files_force.py | 533 ------------------ 16 files changed, 681 insertions(+), 849 deletions(-) create mode 100755 .claude/scripts/build_template_fixture.py create mode 100644 tests/template/test_managed.py delete mode 100644 tests/template/test_template_files_force.py diff --git a/.claude/agents/tcex-orchestrator.md b/.claude/agents/tcex-orchestrator.md index 53608fc..6501a74 100644 --- a/.claude/agents/tcex-orchestrator.md +++ b/.claude/agents/tcex-orchestrator.md @@ -3,7 +3,7 @@ name: tcex-orchestrator color: orange description: Orchestrator for all TcEx CLI 1.0 development tasks — CLI command code, framework/library code, pytest tests, standalone scripts, and security validation. Analyzes the request, reads relevant context, writes a plan when required, then delegates to the appropriate specialist subagents in parallel or sequence and runs the security gate before reporting done. # model: opus -tools: Agent, AskUserQuestion, Bash, Edit, Read, Skill, Write +tools: Agent, AskUserQuestion, Bash, Edit, Glob, Grep, Read, Skill, Write, mcp__plugin_atlassian_atlassian__*, mcp__plugin_playwright_playwright__*, mcp__plugin_slack_slack__*, mcp__github__*, mcp__gitlab__*, mcp__ide__* --- You are the orchestrator for the **TcEx CLI 1.0** project (`tcex_cli` — the ThreatConnect Exchange diff --git a/.claude/scripts/build_template_fixture.py b/.claude/scripts/build_template_fixture.py new file mode 100755 index 0000000..3e8e244 --- /dev/null +++ b/.claude/scripts/build_template_fixture.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 +"""Regenerate the pytest template fixture zip as a GitHub-style archive. + +The fixture consumed by ``tests/template/conftest.py::_extract_fixture_to_cache`` must be a +GitHub-style archive: every entry lives under a SINGLE top-level wrapper directory (e.g. +``tcex-app-templates-2/``). conftest computes ``top_prefix = names[0].split('/', 1)[0]`` and strips +that one wrapper. If the zip is packaged WITHOUT a wrapper, the flatten step destroys top-level dirs +like ``_app_common`` and breaks the template tests. + +This script rebuilds the zip from the git-tracked files of the source templates repo (mimicking a +GitHub archive) and nests every entry under ``--prefix``. +""" + +from __future__ import annotations + +# standard library +import io +import subprocess # nosec B404 — used only with static list args, shell=False +import zipfile +from pathlib import Path + +# third-party +import typer +from rich.console import Console +from rich.panel import Panel +from rich.table import Table + +DEFAULT_SOURCE = Path( + '/Users/bsummers/WorkBench/010__DEVELOPMENT/SDK/tcex-packages/tcex-app-templates' +) +DEFAULT_OUTPUT = Path( + '/Users/bsummers/WorkBench/010__DEVELOPMENT/SDK/tcex-packages/tcex-cli-1.0' + '/tests/template/fixtures/tcex-app-templates-v2.zip' +) +DEFAULT_PREFIX = 'tcex-app-templates-2' + +app = typer.Typer(add_completion=False, help=__doc__) +console = Console() + + +def _fail(message: str) -> None: + """Print a loud error and exit non-zero.""" + console.print(Panel(message, title='[bold red]error[/]', border_style='red')) + raise typer.Exit(code=1) + + +def _tracked_files(source: Path) -> list[str]: + """Return the sorted, git-tracked file paths of the source repo (forward-slash, relative).""" + # B603 — static argv, shell=False, no user-controlled binary + result = subprocess.run( # nosec + ['git', '-C', str(source), 'ls-files'], + capture_output=True, + text=True, + check=False, + ) + if result.returncode != 0: + _fail(f'git ls-files failed for {source}:\n{result.stderr.strip()}') + paths = [line for line in result.stdout.splitlines() if line.strip()] + return sorted(paths) + + +def _top_level_after_flatten(paths: list[str]) -> set[str]: + """Compute the set of top-level entries that survive conftest's wrapper strip. + + The archive entry is ``/``; after conftest strips ``/`` the remainder is + ````, whose first segment is the post-flatten top-level entry. + """ + return {path.split('/', 1)[0] for path in paths} + + +@app.command() +def main( + source: Path = typer.Option( + DEFAULT_SOURCE, + '--source', + help='Source templates repo working tree (read working-tree bytes).', + ), + output: Path = typer.Option( + DEFAULT_OUTPUT, '--output', help='Destination zip path (overwritten on --commit).' + ), + prefix: str = typer.Option( + DEFAULT_PREFIX, '--prefix', help='Single top-level wrapper directory for every entry.' + ), + commit: bool = typer.Option( + False, # noqa: FBT003 — typer requires the default as a positional value + '--commit', + help='Write the zip. Without this flag the script runs read-only (dry-run).', + ), +) -> None: + """Build a GitHub-style template fixture zip from git-tracked working-tree files.""" + # 1. validate inputs early. + if not source.is_dir(): + _fail(f'source is not a directory: {source}') + if not (source / '.git').exists(): + _fail(f'source does not look like a git repo (no .git): {source}') + if not prefix or '/' in prefix: + _fail(f'prefix must be a single non-empty directory name, got: {prefix!r}') + + # 2. gather tracked files (working-tree bytes will be read per-file below). + paths = _tracked_files(source) + if not paths: + _fail(f'no git-tracked files found in {source}') + + # ensure names[0] resolves to a "/..." entry (sorted archive names). + archive_names = sorted(f'{prefix}/{p}' for p in paths) + first_name = archive_names[0] + survivors = _top_level_after_flatten(paths) + + # confirm exactly one top-level wrapper exists in the archive (every entry is "/..."). + wrapper_segments = {name.split('/', 1)[0] for name in archive_names} + outside_wrapper = wrapper_segments - {prefix} + + # 3. build the rich summary. + table = Table(title='template fixture build', show_header=True, header_style='bold cyan') + table.add_column('field', style='bold') + table.add_column('value', overflow='fold') + table.add_row('source', str(source)) + table.add_row('output', str(output)) + table.add_row('prefix (wrapper dir)', prefix) + table.add_row('tracked file count', str(len(paths))) + table.add_row('names[0]', first_name) + table.add_row('archive wrapper segments', ', '.join(sorted(wrapper_segments))) + table.add_row( + 'top-level entries WILL exist after flatten', + ', '.join(sorted(survivors)), + ) + table.add_row( + 'top-level entries OUTSIDE wrapper', + ', '.join(sorted(outside_wrapper)) if outside_wrapper else '(none — single wrapper OK)', + ) + console.print(table) + + if outside_wrapper: + _fail( + 'archive would contain entries outside the single wrapper directory: ' + f'{sorted(outside_wrapper)}' + ) + + # 4. dry-run vs commit. + if not commit: + console.print( + '[yellow]dry-run[/] — no zip written (pass --commit to apply). ' + f'Would write {len(paths)} files under {prefix!r} to {output}.' + ) + return + + # build the archive in memory, then write atomically. + buffer = io.BytesIO() + with zipfile.ZipFile(buffer, mode='w', compression=zipfile.ZIP_DEFLATED) as zf: + for path in paths: + file_bytes = (source / path).read_bytes() + zf.writestr(f'{prefix}/{path}', file_bytes) + + output.parent.mkdir(parents=True, exist_ok=True) + tmp_path = output.with_name(output.name + '.tmp') + tmp_path.write_bytes(buffer.getvalue()) + tmp_path.replace(output) + + size = output.stat().st_size + console.print( + Panel( + f'wrote [bold]{len(paths)}[/] files under [bold]{prefix}/[/]\n' + f'path: {output}\n' + f'size: {size:,} bytes', + title='[bold green]committed[/]', + border_style='green', + ) + ) + + +if __name__ == '__main__': + app() diff --git a/CLAUDE.md b/CLAUDE.md index 3331a93..dbc9780 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -68,14 +68,10 @@ enforced by `enforce_no_commit.sh` (a PreToolUse hook) and a `Bash(git commit:*) block `git commit` in any form (`-m`, `-a`, `--amend`, `git -C commit`, after `&&`/pipes, …) with no override. **Leave every change UNSTAGED for the human to review, stage, and commit.** -Claude's role stops at **preparing** the change: - -- Make the edits and leave them **unstaged** — do not `git add`. -- Run `git status` / `git diff` to show exactly what changed (unstaged). -- **Report** what changed and the suggested commit message(s); the operator reviews, runs `git add`, - then `git commit`. -- The same applies to submodule changes: **describe** the two-step commit + pointer bump, but do not - stage or commit either — the operator performs both commits. +Claude's role stops at **preparing** the change: make the edits (left **unstaged**), run `git status` / +`git diff` to show exactly what changed, and **report** it with suggested commit message(s) — the +operator reviews, stages, and commits. For submodule edits, **describe** the two-step commit + pointer +bump (see *Git Submodules* below); do not stage or commit either. ## Git Submodules diff --git a/release_notes.md b/release_notes.md index 740b6d0..645145b 100644 --- a/release_notes.md +++ b/release_notes.md @@ -10,9 +10,12 @@ - APP-4920 - [Run] - Updated run command to support system apps - APP-5036 - [Message-Broker] Updated message broker connection to not set tls_version - APP-5055 - [Message-Broker] Updated Paho MQTT library and message broker reconnect logic -- APP-5299 - [Template] Added “--no-prompt” and force-overwrite of template-owned files to the “update” command +- APP-5299 - [Template] Added “--managed” to the “update” command to silently update only template-managed files - APP-5300 - [Migrate] Updated “migrate” to preview by default with “--apply” and added “--no-prompt” for unattended runs - APP-5301 - [Deps] Hardened dependency lock to avoid shell execution and never write an empty “requirements.lock” +- APP-5305 - [Run] Added “app_inputs.d/” multi-config support with a selection menu and a “--config” override +- APP-5306 - [Run] Added a hard error when a config references an undefined “${env.VAR}” environment variable +- APP-5307 - [Run] Added validation of config inputs against staged “stage.kvstore” variables before launch ## 1.0.5 diff --git a/tcex_cli/cli/run/launch_abc.py b/tcex_cli/cli/run/launch_abc.py index 9ebcb7f..30175b2 100644 --- a/tcex_cli/cli/run/launch_abc.py +++ b/tcex_cli/cli/run/launch_abc.py @@ -282,7 +282,7 @@ def tc_token(self, token_type: str = 'api'): # nosec B107 -- 'api' is a token t if r.status_code == http_success: token = r.json().get('data') self.log.info( - f'step=setup, event=using-token, token={token}, token-elapsed={r.elapsed}' + f'step=setup, event=using-token, token=, token-elapsed={r.elapsed}' ) else: self.log.error(f'step=setup, event=failed-to-retrieve-token error="{r.text}"') diff --git a/tcex_cli/cli/template/planner.py b/tcex_cli/cli/template/planner.py index 8785bb0..d864493 100644 --- a/tcex_cli/cli/template/planner.py +++ b/tcex_cli/cli/template/planner.py @@ -12,7 +12,7 @@ import shutil from collections.abc import Callable from pathlib import Path -from typing import TypedDict +from typing import NotRequired, TypedDict # third-party from pydantic import BaseModel, Field @@ -28,6 +28,7 @@ class FileMeta(TypedDict): last_commit: str sha256: str template_path: str + managed: NotRequired[bool] Meta = dict[str, FileMeta] # key: POSIX-style project-relative path @@ -205,13 +206,31 @@ def __init__(self, manifest: ManifestStore, hasher: Hasher, file_ops: SafeFileOp self.hasher = hasher self.file_ops = file_ops + def _classify_managed_key( + self, plan: Plan, value: tuple, template_info: FileMeta, project_path: Path + ) -> None: + """Classify a single template key for a ``managed_only`` (``--managed``) run. + + Only template-managed (boilerplate) files are in scope; the local file + is hashed against the template so byte-identical managed files are not + rewritten every run. + """ + if not template_info.get('managed'): + plan.skip.append(value) + return + current_hash = self.hasher.sha256_file(project_path) + if current_hash == template_info['sha256']: + plan.skip.append(value) + else: + plan.auto_update.append(value) + def build( self, temp_dest: Path, dest: Path, file_name: str = 'manifest.json', force=False, - force_keys: set[str] | None = None, + managed_only: bool = False, ) -> Plan: """Build an update plan by comparing template and local manifests. @@ -235,22 +254,21 @@ def build( auto-remove (safe cleanup). - If the user modified the file, prompt before removing. - ``force_keys`` is the set of project-relative keys declared as - template-owned (via each template.yaml ``template_files``). Any file - whose key is in this set is reconciled against the template hash - unconditionally — independent of ``last_commit`` / local-manifest state. - The local file is hashed against the template's ``sha256``: if it differs - (or the file is missing) it is routed to ``auto_update`` to restore/refresh - it from the template; if it already matches it is skipped. This check runs - before the ``last_commit`` short-circuit so a stale-but-matching manifest - cannot preempt the reconciliation. Keys not in the set keep the default - prompt-on-modify behavior. + ``managed_only`` restricts the plan to template-managed (infrastructure + /boilerplate) files for a silent, non-interactive run. In this mode the + normal ``last_commit`` / local-manifest diff branches are bypassed: + + * Keys whose template manifest entry is not ``managed`` are skipped + (out of scope for ``--managed``). + * Managed keys are hash-compared against the template's ``sha256`` — + skipped when identical, otherwise auto-updated (so byte-identical + managed files are not rewritten every run). + * All removed keys are skipped (never appended to ``template_removed``); + ``--managed`` never deletes project files. """ template_meta = self.manifest.load_json(temp_dest / file_name) local_meta = self.manifest.load_json(dest / file_name) - force_keys = force_keys or set() - plan = Plan() keys_in_template, removed_in_template = self.manifest.collect_keys( @@ -260,11 +278,16 @@ def build( # --- updates and additions --- for key in keys_in_template: template_info = template_meta[key] - local_info = local_meta.get(key) value = (key, template_info['template_path']) project_path = dest / key + if managed_only: + self._classify_managed_key(plan, value, template_info, project_path) + continue + + local_info = local_meta.get(key) + if force is True: plan.auto_update.append(value) continue @@ -272,25 +295,12 @@ def build( # new file being tracked in the template if local_info is None: plan.template_new.append(value) - if project_path.exists() and key not in force_keys: + if project_path.exists(): plan.prompt_user.append(value) else: plan.auto_update.append(value) continue - # template-owned files are always reconciled against the template - # hash, independent of last_commit / local-manifest state — this - # runs before the last_commit short-circuit so a stale-but-matching - # manifest cannot preempt the reconciliation. - if key in force_keys: - current_hash = self.hasher.sha256_file(project_path) - if current_hash == template_info['sha256']: - plan.skip.append(value) - else: - # differs or missing — restore/refresh from template - plan.auto_update.append(value) - continue - # both sides have metadata and template hasn't changed this file if template_info['last_commit'] == local_info['last_commit']: plan.skip.append(value) @@ -309,14 +319,21 @@ def build( for key in removed_in_template: local_info = local_meta[key] value = (key, local_info['template_path']) + + # --managed never deletes project files — skip all removed keys + # (do not populate template_removed so apply() can't remove them). + if managed_only: + plan.skip.append(value) + continue + plan.template_removed.append(value) current_hash = self.hasher.sha256_file(dest / key) if current_hash is None: plan.auto_update.append(value) # already gone - elif current_hash == local_info['sha256'] or key in force_keys: - plan.auto_update.append(value) # unchanged or template-owned — safe to remove + elif current_hash == local_info['sha256']: + plan.auto_update.append(value) # unchanged — safe to remove else: plan.prompt_user.append(value) # user modified — ask first @@ -329,9 +346,8 @@ def apply( template_root: Path, project_root: Path, force: bool = False, - no_prompt: bool = False, prompt_fn: Callable[[str], str] = input, - ) -> list[tuple]: + ) -> None: """Apply the plan to the project directory. Auto-update files are copied/removed without prompting. @@ -340,18 +356,11 @@ def apply( Behavior for the ``prompt_user`` set: - ``force`` → overwrite/remove without prompting. - - ``no_prompt`` (and not ``force``) → leave the file untouched on disk - (no prompt) and record it as preserved. - otherwise → prompt via ``prompt_fn``. - - Returns the list of preserved ``(local, template)`` tuples (the - modified, non-template-owned files left untouched under ``no_prompt``); - an empty list for the interactive/force paths. """ auto_set = set(plan.auto_update) prompt_set = set(plan.prompt_user) removed_keys = {local for local, _template in plan.template_removed} - preserved: list[tuple] = [] # auto-update: copies and removals for local, template in auto_set: @@ -369,9 +378,6 @@ def apply( self.file_ops.remove_file(local_) else: self.file_ops.copy_from_template(template_root, template, local_) - elif prompt_set and no_prompt: - # non-interactive: never clobber — preserve and report - preserved.extend(sorted(prompt_set)) elif prompt_set: # interactive: ask before overwriting or removing for local, template in sorted(prompt_set): @@ -388,5 +394,3 @@ def apply( ) if response == 'y': self.file_ops.copy_from_template(template_root, template, local_) - - return preserved diff --git a/tcex_cli/cli/template/template_cli.py b/tcex_cli/cli/template/template_cli.py index 792ad12..b90ac83 100644 --- a/tcex_cli/cli/template/template_cli.py +++ b/tcex_cli/cli/template/template_cli.py @@ -186,7 +186,7 @@ def update( template_type: str | None = None, force: bool = False, app_builder: bool = False, - no_prompt: bool = False, + managed: bool = False, ): """Update (or initialize) a project with the latest template files. @@ -196,16 +196,15 @@ def update( Steps: 1. Resolve template_name/type from args or fall back to tcex.json 2. Validate template_type and template_name against the cache - 3. Collect the template-owned file keys (union of ``template_files``) - 4. Build a merged template directory (parent chain resolution) - 5. Migrate legacy .template_manifest.json if present - 6. Build an update plan via Planner.build() (template-owned files forced) - 7. Apply the plan via Planner.apply() with user prompts - 8. Copy the merged manifest.json to the project root - - When ``no_prompt`` is set, the run is fully non-interactive: template-owned - files are still overwritten, but locally-modified non-template files are - left untouched and reported for manual review (never clobbered). + 3. Build a merged template directory (parent chain resolution) + 4. Migrate legacy .template_manifest.json if present + 5. Build an update plan via Planner.build() + 6. Apply the plan via Planner.apply() with user prompts + 7. Copy the merged manifest.json to the project root + + When ``managed`` is set, the run is silent and non-interactive: only + template-managed (boilerplate) files are updated and everything else is + left untouched (no prompts, no deletions). """ # resolve from tcex.json if not provided _template_name = template_name or self.app.tj.model.template_name @@ -239,14 +238,6 @@ def update( f'Available templates: {", ".join(sorted(available)) or "none"}' ) - # template-owned files (union of template_files across the parent chain) - # are always force-overwritten, even when the local copy was modified - force_keys = self._collect_template_file_keys( - cache_dir, - _template_name, # type: ignore[arg-type] - _template_type, # type: ignore[arg-type] - ) - merged_dir = self._build_merged_template( cache_dir, _template_name, @@ -260,29 +251,21 @@ def update( merged_dir, Path.cwd(), force=force, - force_keys=force_keys, + managed_only=managed, ) Render.table.key_value('Plan Summary', plan.summary) def _prompt_fn(msg: str) -> str: return Render.prompt.ask(msg, choices=['y', 'N'], default='N') or 'N' - preserved = self.planner.apply( + self.planner.apply( plan, template_root=merged_dir, project_root=Path.cwd(), force=force, - no_prompt=no_prompt, prompt_fn=_prompt_fn, ) - # in non-interactive mode, surface the files left untouched for review - if no_prompt and preserved: - Render.table.key_value( - 'Preserved - Review Manually', - {local: 'modified locally - not overwritten' for local, _ in preserved}, - ) - # copy manifest.json to project root so future updates can compare merged_manifest = merged_dir / 'manifest.json' if merged_manifest.is_file(): @@ -317,40 +300,6 @@ def _ensure_tcex_json(self, cache_dir: Path, template_name: str, template_type: self.app.tj.model.template_type = template_type self.app.tj.write() - def _collect_template_file_keys( - self, cache_dir: Path, template_name: str, template_type: str - ) -> set[str]: - """Return the union of ``template_files`` across the resolved parent chain. - - These are the template-owned files (declared in each template.yaml's - ``template_files``) — files the App developer should not edit and that - ``update`` overwrites without prompting. Each entry is normalized to a - POSIX project-relative key so it matches the plan/manifest keys (bare - filenames pass through unchanged). The ``gitignore`` -> ``.gitignore`` - rename used in ``_build_merged_template`` is applied defensively so a - template that lists ``gitignore`` still maps to the on-disk key. - """ - keys: set[str] = set() - - parents = self.resolve_template_parents(cache_dir, template_name, template_type) - for parent_name in parents: - config = self.read_template_config(cache_dir, template_type, parent_name) - if config is None or not config.template_files: - continue - - for entry in config.template_files: - if not entry: - continue - # normalize to a POSIX project-relative key - key = Path(entry).as_posix() - # template repos store gitignore without the dot — match the - # rename applied during merge so the key lines up with disk - if key == 'gitignore': - key = '.gitignore' - keys.add(key) - - return keys - # ================================================================== # Cache Management # @@ -573,30 +522,27 @@ def _build_merged_template( # use rel_key for template_path (project-relative), not the # prefixed path from the parent's manifest. rel_key = str(rel) + + parent_entry = parent_manifest.get(rel_key) if renamed_gitignore: - # the renamed gitignore is a special case: the template repo - # ships BOTH a real `gitignore` (the project's intended - # .gitignore) and a stray dotted `.gitignore` that is the one - # recorded in the parent manifest under the `.gitignore` key. - # looking up the parent manifest by the renamed key therefore - # resolves to the WRONG file's sha256 — so the merged manifest - # would describe a file we never copied. compute the sha256 - # from the file actually written so manifest and content always - # agree; reuse the parent's last_commit when available. - parent_entry = parent_manifest.get(rel_key) + # the dot-less `gitignore` was renamed to `.gitignore`; the + # parent manifest's `.gitignore` key (if any) describes a + # different, stray file. compute sha256 from the file we + # actually wrote so the manifest matches the content on disk. merged_manifest[rel_key] = { 'last_commit': (parent_entry['last_commit'] if parent_entry else 'unknown'), 'sha256': self.hasher.sha256_file(dest), 'template_path': rel_key, + 'managed': bool(parent_entry.get('managed', False)) + if parent_entry + else False, } - continue - - parent_entry = parent_manifest.get(rel_key) - if parent_entry: + elif parent_entry: merged_manifest[rel_key] = { 'last_commit': parent_entry['last_commit'], 'sha256': parent_entry['sha256'], 'template_path': rel_key, + 'managed': bool(parent_entry.get('managed', False)), } else: self.log.warning( diff --git a/tcex_cli/cli/template/update.py b/tcex_cli/cli/template/update.py index fdddbda..48165b8 100644 --- a/tcex_cli/cli/template/update.py +++ b/tcex_cli/cli/template/update.py @@ -37,13 +37,12 @@ def command( force: bool = typer.Option( default=False, help="Update files from template even if they haven't changed." ), - no_prompt: bool = typer.Option( + managed: bool = typer.Option( False, # noqa: FBT003 - '--no-prompt', + '--managed', help=( - 'Run non-interactively: overwrite template-owned files and preserve ' - '(without prompting) locally-modified non-template files, reporting them ' - 'for manual review.' + 'Silently update only template-managed (boilerplate) files without ' + 'prompting; all other files are left untouched and nothing is deleted.' ), ), branch: str = typer.Option( @@ -70,11 +69,9 @@ def command( Use --template and --type only for legacy projects where tcex.json is missing those values. - Template-owned files (declared in each template's template_files) are always - overwritten without prompting. Use --no-prompt for a fully non-interactive - run (e.g. scripting a tcex2->tcex4 migration): locally-modified non-template - files are preserved untouched and reported for manual review instead of - prompting. + Use --managed for a silent, non-interactive run (e.g. scripting a + tcex2->tcex4 migration): only template-managed (boilerplate) files are + updated, all other files are left untouched, and nothing is deleted. Optional environment variables include:\n * GITHUB_USER\n @@ -118,7 +115,7 @@ def command( cli.clear_cache(branch) try: - cli.update(branch, template_name, template_type, force=force, no_prompt=no_prompt) + cli.update(branch, template_name, template_type, force=force, managed=managed) # use the resolved values for the summary resolved_name = template_name or tj_model.template_name diff --git a/tests/template/conftest.py b/tests/template/conftest.py index 6ebc36c..68dac94 100644 --- a/tests/template/conftest.py +++ b/tests/template/conftest.py @@ -47,9 +47,7 @@ def template_cli(tmp_path): tcex_dir = tmp_path / '.tcex' tcex_dir.mkdir(parents=True, exist_ok=True) - cli = TemplateCli( - proxy_host=None, proxy_port=None, proxy_user=None, proxy_pass=None - ) + cli = TemplateCli(proxy_host=None, proxy_port=None, proxy_user=None, proxy_pass=None) # override the cached_property so _cache_dir uses tmp_path cli.__dict__['cli_out_path'] = tcex_dir diff --git a/tests/template/fixtures/tcex-app-templates-v2.zip b/tests/template/fixtures/tcex-app-templates-v2.zip index 20de0a47992f9f8f0407c0df5a48391205214959..7371b3de66e0a906423a8022c42a320572a3a59a 100644 GIT binary patch delta 550156 zcmZ_#WmH^C*EI~&SRlB&LvVL@4{pKT-8D4s-nhFv!9BP`aCZqV!3lhvTV^anf4;PGnrH_8Z@L{PzPPpn!}`bOB&sau8r(jDJ6oxuuJRs}bWr?{8*f@9xZKYiMc5;Av|U zoxrcq$Albm{}O>0*S1C4E7n#^%1qp=t~M;q&~LyyD3GFF^p@o;@Fa|4DzoGL(siLb zDHF)eMPg3B6@x@dGXQ@<%d42>t;EgHrQ3YLu$mjFMKj$4eOY5fDGC zvr$}15hbbGYefJFKzlf?0W-bZb^VLK5o>+)J3A53J$afwPYD_j#F%RRI&(2DVGki)l_wI~B%)38no__J%N<&SMurTX z>69DNE+Te+Y#RLY7u^|OL_NC%3PgI!(N;*gA!2^WH{8(zVOc{N+JFK0QekhK6NMI> zxns$|_4HzY0_?YW1DSpT1PETbGv^#?Bmj`Ho?`Q`zBK7T6Zr4SVEhj&3wj%&qwhGW z0tW*l`u|qO(%jD8$+Rf0*RGEVCgg%IxM+f04ILclT}*8gbYN593;9K~DY#fv_Y&H3@YEm5fi)?n&7FPAoqKMB65kn}&M?Wmh zgUir8aG4B>Lfm<-zcTXN++=sbV7-C;_vxsV`Y4LOKNS@)u=vlO5bumqH(Y@9&+mq9 zjBkK{f)qw_K+d1w@e{xb@gLF&g`?#~eSfBBP@pS3yp&B000q$2K7$GQbwhvKKE5s6 z-zAH!@OJ`D6}&(|Xm7tj0`b|o-D2O(3Qq3O$3h!K{?v2D4C-y=qA!f0+W~OXdcAQpG zs{p#<1v8HcvkxdO2@@hhqo)DZ$fLeovqurLJ8w23nG3~s5=AlV#~!4&T$`Iv!L+x5 zzOX0D@NEpjbJm8Ny&HSa-C+V;XIF8GU@EVs%CfwuaCqJga6XSw$$Zj;EQPYLq}YOr zdH{KhOnhIeX~%$`*}~rqO;)U_d4i~zfgBP+X;wk8SOw(tMnu54bFMpKiL5=rhnqSj zn3?)kJF^v0w+p8%L42~j;TfI%d{IFgoo?%4^Et4mnrra$O3PF@`G+(OIlT>Pf@(GB zTJ_>bE$b39M^B6}FRIKM3^{XS?#km~$1-zt!Fz{W32Z8{YdrfDyhV{?DS_A4t+jjl zWB1tha|obUrn@CF_8T*fkffGDS_g}p#Z=xN25i;nRb4r+t zh|TYNFM7G#^VjjhiZcW`ug0REUYidQO{O+I*$`}t)>wD;sOk=vL;UzLmlu`bCBk04D>giPD7;0dCZ9JPD zy8{RA=aM?YTEx=TtS;}t>#3FG$VdJvyBMbi*w(%Rq@$|xPYweRs1ztFr^6rnfH}hi z2*RT0^c1UjxMTzA5}0#fJAEJ(isFEG zHMkSFSQn9%S}(P)sx?@Q9v|AGErF5mS}hE%wlfY9F{Eu}?b-q;Dy%j;r^jcA;B(J^T6e>Xh<7kks+4xZV+l0|R5g`&X-^2x0?pfmNEe_G>LDuU*{%OSxQ> zwF`_Rjoc6nTU_!x*;x#kay6F7bSG?+~jpR-teV(2N zB){34gRdKD#_S8sMQWm4Y1*5s-__DrE`mpG-AEN?UuMnS^|lG$GOQb{=b{PIv`*|; z3H`vv{lX0{V5ioHAN7cNca$GK&=M?67$vM8=l*DI*kI@rPIs@RI@`%jSiwi=54`d09HCI1I9zWQnfJY9K^hG` zD_NjD^X8SYOOtL^7M?4Dmp!5J&pD?Mg>mYf^Zcfdt7+G9SKx)Vu<%(MrGHx{K! zRfQ1MeblohsuAIiVe20ET<1tRRPM*GST?)}J^7dzsi~6U>a1%$bS=^sful9a)yyuC zFs$zU0uYL5y@D{nsaAfDr8|jsUs8VNl1;vAuQi8XqIOU@S+5AzoP?{>B7kSixzg?D z*9PAG3w9{|5-ZRG+`iIFHeKM1;_N?1+DL zREINGI4@##$RN6aH#2lRUi>0zg7dmz-dZWTJ7-*0*RB#BC)cy6k5|y=Lrtq|XR+nK z5jzyix=8!;+BJ&620d}pS1dMcEj`hfUM%DiWJ+u7^K;r7gX=5TMJ2TLSgwLt8kFUjA0 zRBAH&?{xdddN_TJ1AM~RgL^LLPVKbCZVxEblo&ZcN0S%9RNT7U9~XFiQ6L9kFd>K@ z3;Y4V`yNGG$TFBcJ;7r_*7O{4@mh)&iQ9mAB-At8(wBCt(CsHy@skFP?|t1ggEAT5 zP(w2V4M;MW_#ay@3%HUWYpJQz2I&SLr<9BrQIDwHL)SDxhJHI7-a#x-{F07Gi8|QK z!9TDQ2eo9~w($;L7nb72WuH;+t%F}GtL|}uBdj|({C!_yw!g6sNfVp(PsxxX_!M_8 z(b5T#!j#b9*;Z#e+mF+<{_tFoF{NvS6VAj|b>5VPOGk6w32}~N${#S_YtB2v*3yN@ zDGCG)j4l^rGBGDhGDpbqHFgkf=;-1T68^4b%npb8h7|-|j!3z_*^!7qH!#;+gNWn_ z+~K9^!~RIDr-F%&GKmwEFOjxNIwANJ_z3q6L6oAhw4l#0$<{Q+_r-p3Nset|(=DN2SwVujZa8jsW6BFrLL!VgshC+#XpP9k0Z3SmZ&2>nPO}qV*TwzHuutoB z%%VmJ5Kl~t{e-BzGB%s)C4u49K+$B&U;bb0ve@wpmpwDq7a?8ht7Wq&6JrT*#LbnI z(AO%-GS*qhfgMSb8IA+B1wj4ReRaaYG!Y(B0ys(U-xrB%K9-G{>zcZSI_nP9+Apf; z($kQJ8C$M19C?@@{FpZ=#Tn74vI{;k%>qWxxmq2)mY!nbaFaM)9`}bVfhV7ljUSXC zbjcTr-IkOw1n`hoB*|kbaYLM#2=N7t4kh5e9DkTm%B}xQC$7>pJO@3Upel{Pk>x%) znE71Pp;pr%UV!4Zt01*Zuzb2$Uwi*8O)PSSn$G*Ci*X&BroNJP5!62}hJPe;pR25> zW^*M;9Ji#D}={ysFH+fWoT zm~dG06cB_c56BDaG=%5^m_u!O%M}iX_j=^}R_>RGs;lg`>QK4J2c}DcRfsiS%jszy zOLJFH37df@ojlM-&f^`Z8{lit#Mmn3snDSg#%#V;**jiUu+e&x0ynNV&qy4F%@*aq z?|QENTwU&l4v-qlrM-QI&HdRdfhJy}0S8Ut_`@H{61o_x zgyWg8d0qGp>6prOp?|d*XU27H_z*Ni_~9ubDQ|!qX4alUKaUy-6$^MvUnog7Dl{EB-oLq8PZ9=gSWPUVk-V~~Z>H9`fbUa$d zqpj@?w@3oz3;NVMh4S)BEvY9FzxM;ZqmsH5oqmUDuYXZ+x*Vtrefyu|S!C8b%h zGZTlQL&0mY5&ngX7H*Q9LvTYXoZt2#rYG{}{G)&`!_i@guWz^+{3wN9mkIABR<}U> z`s{M3@a$EhoMH4qckszCf7r}gUy%#C=Y6M^dVz5Gxlo`P$z7cHlc!k3&`H42ttnZS z^_5x58nY3AByj}iz+M0WKNgg$!QZibJxG5r(6FjiRNwZ9KNB(O*Gb;ddxEq@bq|e> zgkfU5;0!4ZpIF_^bkAIySyhqBQ#chqAl_Ty4k4_@fR@~t;)}7_3yZ_J{jgK2kfasB zmQ6r__ymm6n*LF}>RXoV4>2D@T$R|dU!bk(z^CYUE4Q>_^rZOdE)0r#(TJjC)dq|d zLcoJ`M>DHSQ^l?=V?0<3O+WQ(n%?-7pibtS&*K&UNt+L^VsWG#_!yzcCq*55buAnW zLt%;L0Q!0#hm1@d&vno$G!P-}+S28XPmUtBIv?1SSBWe`Zb)kNQ;%lCXhxr~@#0S6 zCKk_wH<16)l%v#tw{N$PAdktc22xsj~VM)UI>M(mqm|F3XjSN%<{j>lp z0}jT8i!f{%(H@8syH%n`#={ri>6R&PP2CRME55?hD{W>BLohbyhkohWHQ@3s_xH@F zG^@B2pnAEl3-90u->y+sLn3<~J@Z)#62~tSV(e5FKJ&Bf)^}hI?1%>H%LoA4-!d`q6F*whdzqL60|v(SkD&z;Izdfg_zFM(b|&@O zt#ZN-UU)_jACe?VMUcAVO%u8=!$B5`RS_C3MrY-T&bSkG+Q~%}it-y=O$15(1`-J> z6EQL{*7$nf%%0?I>ewI7$EFam#^q`A;@c0lpSd}YZ>a^sI-@h9w5ev4R^wWW?^>15 zrcY2KRQo<;b~R@G#x3z0vqQQ9y8pUd$h|7t(28EyBCgK;SV6_9>TGz0vo}4n7Ga1I zDh|o;gTV z)$47Dv-d`#io-SR7dV%rjBov7fD&Yq7j3?6b;;IH`*X<}iYSIy1PF>9XpR28&1*V; zqXgDCEtAFj6;C{uxB*ErqGO4)!ajBrCkX-?dRjob6{?)Fo2U;BC_PRE4MwoJVdodTNZlvZlvNv4uA zbXFM^g4Ch=v_Y>ls4C722=XMn)(D%b>o#a8Gw3_8Ldy?jcF}E0Eh+w3CsEjnby+xz z6M*lF!=CTa)U{mt7X9Kl#o69BV`s15w9Wl}k{%5;SqS=$U5VRaE?ppXE*joql-$4A zdRqwey^*(lm>cvPV-xGR-2>R&8fkl~9J3p`%Q;e%h#1h1Av{_RjPZMPW-Xgs7cWz; z+V>s-8H$7lY)F?!sVyOgdZk+)opxp7hUS4Ac+pPv?Z`(DDxdm7KcWqOczRxktG-Iw zFf2o(<;7(@Yn{Z6)k?e`_<`MeAV$21J5XtX(GkqyM_tC06ZnF23U}n#9}0Bm$OF_D zE#p?P<-wcU(GsOgCCM&bGh2ddb$RC+i_0@w3ZZ8kwZ;@i(X9ISBDw)>0Mws!YFHhS2md!r z9oIqpH@q193I6A1d5bEMyze(FfB(_@H{1TX89DIXN_DU?bTM^iG%|EHWw3CuwPCQb zw6ikgkJncCE`%Js`5R;RQz0&u0aLuJENL%Hyp#_UF{86wQnDlJP_w5ep;k&g=9KJ#g{YT2k?1xr$F>8Or&HCC+RZ1jwKy-prpWZ~F1H@m!Yn6Un>64c^vgCqp-7wcyRU!Xm& zUkJOhF_)tRCVrVU+6mra-NfEzU?23F5uQEGz&y9`-;NvA>Im#HwkuUex_K8#Pv8~g zDV8LNY%$9w4Vae6$wn!}1Y}Gfet`+GTXzA{g);@nXt2`EbX(hay?x*+?0CUYcT)1( z5@=mQDOK1(V{rLo$sHoJa}U|&WGklFmeR<~q^%@3{A(>B@U!Nx50F)(F-+SN-_$($ zGiJcpS}_Y>6NvtU108nOXIR7cCx7^UtfK#49~<spqgbKd3L^>DZv z3)URe=GH@V?y#mlG%IoY$q5XgHs1?_$@_g*kAwgYl4sfXHzg{8IakvS74)r z604zzqNYWQ>!kv#X7Z^Z&C>Nou`L?_PT6Gi_7)RT%_VbBuASrS}oVEQjMc4!}u~ zG>9ymy!r%qA@iNX;IlCiUoBs!E`O)4ZAs4t0;m-_L3!f)_e4^ko$GMa)bmfP)P?z@ z*(4R@-=m@dub4RjVCVKg`-9r`08bPoMs^jvr89$gnlRK4PgYSombR&GOkN&m+vlm( z!Txx9PP=iVP=rAU@Wv@2<%sI5Jt%O(ai4vPxPef%903F5hhFqYW{9-C8#U{|IOhSD zK3eV}{)o23#mhlwmGSK{8!tH@bxE;2MH>uI9JwIPgG9rjxR^F@6G09t@Uvo-aeXxg zoX_3^HhR(yP*Iv@xU2Zn@_c2+1S@9L!Fp6$_KoH=0lSVsjB%HS z(}43QRyV@XyRC}5UXDAiAy1cnD#|Am%MUFMYW63|hhpS~U(`OLJ1jpK*VdyD-1KYF z&gp&rg>itx6^{4?$gLz8GJ&`gv%@%h4=*ihg2BziZTg+eF;zu$Xh}DO&9d3ig2P(h zH{At~&!;HgtaFlI1v9&`%Erdn_*%yxsn-OiA!`{P4og@JmV#A0X=v}hW}hE${7LLH zTLBI2lkMn!8;8Msm~^i*pE`I#@@c9Q3a`M`` zR#{=`565)U;coZLVz-aHWf*J`#9Lr|p($yd#jHg551^#{)=Hn?_)w zc-#yXk5=uvjMyD<<&%Bp)7&Z+vsK!$MWwQ(o%1Hti!-|e?m&gjQF-6S)mxwrhvYMp zS4Xny@tIez7xmXt8H8+!GQ$Xk#_TG2jbg1YXq;!#A56DDrHM{2)GsRD=6uCmJ?E@n zvSJ*&eGWWdtSBJRt0~dt7J6}WMM6~BSf6};C`dZJPk9y;gyoA}E3n9^a+E48j9!y~ zH_20)(h)hy-6px*cep|3bPNZ|tW|((%WAQ)C!|raps9?|h-5jXs5g!mL!nXbqsun! z_<)~F3i*olXn_Cr_!bN~rfS(wk{s{!vo3}OSJK*Mj*j?rrwpomX~0zVr}Jk5X1GR> zqM%Tl*2Nv?*j1*VIo-j;MT;*SA*62ysj*!SZF>;utrZ%d^O&VSIzOuN;KF>elowbw)9%NLBAQ4!6kW8fH5(N8-((UsoiaMxi6C)Dx8JP z7_`Ifg=xcRTRaz;8`BjA%yr5NcV)z82$nB3g9hZ^w#dkl~Em}YCW@>(Di-`8GyiV(v zwGFCt8kJW$>$75bOcMzLM_T$Cat%PR@piSXxgXdW-K{&tY=>Xo8rEK+WPyLg7IR|UAZ8ZpEOV-^qmCt+Xw%+`c+#l$kk7coc<>2|=9O6F z{WgOT5?kYzvse0Vz7BD5_2Tl3E0exJ?=i)UPS4=8_HUKpw|e&~>uFi&piaOa4v67| zpciME$WZ&e6X;guG74Zc9U?e*=}B(kvv@=n)xXikRkQ98w>8&u3x zI>7l0y1u|(qT#9qw|oxVj*;Sxq3V+vPS}9(7I)0{djSl3|Ke|I~;Hn^xM)?=|8xI>};DBfVzwGm9|rl!Htf ziJP)k>FM10%gRihuveWP4y<$z&}VaO!m2UZvMs~6l1QDg0QXk@z|?r^`L`R+1Ut3oF91yBtQ z8u93}uG5rWe_XrTJqbz{gmdCjhr}M{3`FNoI$Oar6Uz+gMlvVF25(;xqU1piR%Em=q1Jp1cb_{_J_<@aH3E2Ql|2svd(Y@XM@Q-8$A#>vYv-l_}Kn4IJ@V(XLI}`kSs|(aIjxZ`` zcxQ^Rn-Obqlj^rnxiEMA=_SKZ>D7@}`nT@e$EIaF-t5EQFEgDs_Hyj}m-U2Wrub9V zcy67)_F9#N#~FlzBMfLZ{COacon55rw3?smWBIP6ts8KWA_YpuXhTy8J(nRUql>50z(B65;*Ic-DHP`Vk+_?Gb|`ITSDt0#hs{8fVnH1=^>@>_U=8GZ}#0A!8F0dNKWul&hu_$sY22#=B%xB6tFd?aD z^H{;rtON2qD~F29)Ugzk6$0?Q)i7C@5-H3lpKHk};)R;p2SZlKMD&=c*8y*skf>S2 zKF-mlcN{GE;ZS>L7JSY9Ez_xYf!I8D#XazYo#&%gzF{T34qUUI33OPi)eS(79^~pE z(5Yk|rRB%=fv=*g8;)rEWZkiD;^27JvHQ6~8}O0FR62-9bOFXvpn)bmJ+YWIinaJs z^*%G0^y3bX7Rmv13EG53+z1d(o<~|`o99Aaz*bc!BbsCwhb3qC72#p{^_%_T8~w)E z#95A6DY;Ry9AAbqY{{JWup)P5eX7*f zBJAq|a-dl@v-Zi2UWH>b6~|(9GN`w*R-{gqDMh*RjtRozH~v}Rsggz8uZNclzA3n1 zwn+c98*KTN3Yb_$9Fz$&*e`Y8K^M%prh5Y3yq*kPW}HS~waYoMR-8l1+IXqLUt9Jf zMgq%w`3e(yrevx@L%rGU0b(cJbyg(B%P?k1U!`z}h zO8iL0;CUBu$$go?UnS%#%p^unwRN~RhQ*05Gc`zYbjyooIK2&N?@)CEk0KkF$j)s0 z>mhVqC8#hbf`pna2<7BBM08@=!>vPJj|6TrfrLj*sZ6+nSVt~1l!y=bU3=0&fyYtJ z;>W7zK`gqChT130VC9s7v8TjeuNKUFuWuJkm@drSRm2ln{Zx1=qT(IfW()t6$kyRXNm0_F%OIE4#c&AH`*Q3hK{~X0amf?r*=Ft z8e7pa2C~;@#I;MX%;_doKn22_``eChso8z9~>&vP$(9O5&$>(-^x1=Jns(K@5GqwlD z)+D{=*|%MHimZiZGJzM<@*_GMtrBvPD#_hOqyfbR$?@@e&9AD{y+KU=lnImAnV;%L z48Tae#?tSV3oLGv6D(@MD>Ig1p;2Y-H9@5__0rOH7=UtaDy-And)@|HB)qh2*gyD|n z32S(oX+ciH4uEM98<(F@VZPaePLp`<5C&HC8XrM$c^ONxh7_th-+&y5ma`l5YTQF> zZd|TRS1$B|CzT5BroVeErLi*ernc{cM`|R9Cpqz9dP$i$nO3M8g$dCN!17SmC$`a8 zkJrJg;6-UyOhbK>p#D zB;2jE%7@DJco>}41uWy}-1uj1Qm(u<_q8a>B*))j)Y3f&It^4^Phq(;nu>SVRDL2e zb4*_q$Yv`9_s`)9zO&FSwZ2qy>+{%@a}XG#_e^*c64K)F@;?<|Co(rNxLJXQa~(`upA zl|kOdP0D2?1gjKZe>AtX%sK9#y-z~bHXf{zZum#Sna|;~!ujOnlm=1FM(3M@YhB~~ z>P@?5YEd+PiuJmE^NA^@MwJm!rjU-eLQmX)5;6-G1nK0=ss)F-y>FAfDJ7?GI#xaR zlj!Y(G)vPgkdGM_SEzKIr6NMBgwRxogoq-+kuqdYDGRr;^%D=W--3w}(LG%kzm3V& zNg!R0eBgaEXT3Hs#k6x%x7wYv*_TgG;A*1!S>;3qF*LHTS@3#6i95Uw)pd9vLJ5`# zHUa{pn!q2MUT-u~G|M|wM%T#YmY4x7#162&`jWW;mcTAhFHMlrA9@D6 z$G`LHb*xVX41E(!nwtJ79f6<86LlmWSw7Fl&l#9Z z`lItWAA4nd(6?w+~tOB_pIHeAD;3bV>Yel=_P zaEWT>IOnF}Fz|GJ$>06%A_DD#&;PBGK+v3*e>N^YkI2*6_l+wG;!m~o4y}ew=HtJv zK8lOkarOHg_Ipe7KOOcYDLu{rn3NhP0Bi%PL(5;3PJ253RdY2~D}V4CWvv4F=hpp# z4Mni;o2MN#=qi-_pKH+@FkH|e{!D&U1O6a;pIrU$ze$(|Zy!#Ge=D#%i4i!Y9FgF;1mMi($pYzAro+;rlH>*GTt`#R$xEk;ZT+`;DH+CFt!)kK=VL`r zPVK9N3`90J{js}sN$2-i&+X<_0};7yj%ERg-shQ;*dmBqbip*Wa{}^>GH})BU;SK3 zsXr#F&p5iPR-kHBi@KF2Y8&!aM0 zoRoA?p7sg6?ASH$|R@Lx$G{>h=c^PRoL-xGonbmI8PmZJ*G5^(#3_(@Sgzv8*1^XA@NGS&P0LT;tJHUq& zgJb|ChG=Kt6FBjK)FhwLmVxmke*ux!P>3dO+8UyMw{s`GK@6+Qm-_6f| zs-wHxF%5e?eOsT~o$Fb;@gFO9W3JunHg?IHI?P-i3iRSfil#~g+=DO-%c|>3Yr%%M zrzcm@wr4q9M+n*%yLD|qpn1=JZi!SF3~CjW34roP#1Ci7tSpRjOj=cEXfrm7hK~vJ zsP{d`o39Q1X3b3$)$UClyf9PqqV1i(FFTfBiv{20Iqrc1_*%<$?v36L^wjJ>Y^rq& zKM6@<;mQTjkwUJ2U=ra*%l>Vwr zjZQQ_$rieqYwVlapG|?czQA_=jD}NxWtNdJfpj)Mo<;{N^LwfJFJJX2U8lce{PH{B z8$h)*=3ZVd@#zK^ESL}|R7~DGr|w^)k)>Txlfa#jp8HTS0^cYDo64C0YYAp*hciTE zAn5Ad9qD^ptC=$aJl=r*+}vrNtXeA9;((H1!r7Lr;i^gii0Hyq<@3t zAh~CsQT?Wcq`CNHuW2suIgJ2XjDghN=^c9 zD30oS!bRz*L7Hh>fq%|bVdPN{%2;r(hd3QLjQnq_enYxx-TzL4vz3+pO7dZye`~4- zxX72t_uP_!`2S#?5^4J`dm2@koz|G(Pj5d))r6^}?tWYJy!LBx0E)(QMWTJ&^V?cK zx(85a<$tzvM{oRc%Fu)6^BIOA-T}LUJj4YmO$5-(ElCSA#W1n_1Kh+MQhY7|o&#FD zD`Q*HOPxW2RuBz^2S@%AdJ&h*gDyriInJVJsQWaT5AsnRUAmei5%>j@72j%dv|-n@ z9z1Q9o+@7jWBa&}<-6K|rbnzBj>;2)J$E1faALX8>}Hqo6a1u$rKt>^;xV7L9$}k6 zlR%%_8$~Dkb^fHbT3D8}C!VgTXNTcuO)_8QVD=}-ml=uhFLM{J+K$YBmj@f}K9xq|F2Rq&B7XRk0w;E(=6 z&pBUxEeQO)S%y)K@9k5u9C}`v;m_lJj33T_P1ygV@5%!qsH@$I@1r%JPaM;-wsR^D z!BcTw(M)`rC=W5xhK6Cr^TAU-oB*ZPhlX~g{2!Rf$9y|@Z&Tml@wVUv$16MuUjJJ$ zH{b`i|79noXsSOoG6*hU`452|2C)1!S`R|+{sumA5D@}7 z0BsHJEX_=vT^OvK?d>`fr{4cD$1N9Kb>wiv046 z7>J@G7{yVNW?k8)`}?n(Pp|Jpc6If%{*1IZ*2u7-x8dC@3pzc_=+dpxXu!gves4`j z7(Xs$cdQ~?!cKOI(^5+)b&81O6kJ&Z*2gQjWpbLVtQxxlX_@zTJ2nb#wJM$DI>omA z5fUId;!Eja2gKr)%Pm4a#;Y=sEcgaNq@!!;d$ z?M49Y(mFO65^7;v{u07IKMt0Z1uny1;`=TOlC}E6_Uaky>mB;O8}^*$&drb(+@W)L zx23Ko?#{JVkSyh}I`jCT@=rbRC=wU!rl)xZOk)o;J6J(L5y66aWKl*QsX(Y88w9Tu zF)#|IFBCWnp#zGQ@vG6*)5BkY1JIvqRdHz?i```HogQ{G7}vL zr}(R`86)WMU1ZTqCIPm#%|w-qie~flxA=vp*1KLaQu*EDx^lzVJXY*M>RTygvh-!N$qc->UCR6UsCFXVsQb@A`^X$6W5*I z6Sdxl>#R%w&H(Du2}icsah zoj9XNH*d1r*nqzqf8d#+F{-S6p^xC(q6zFNyNa2|c(L`#x#RmK7XNNL8N1h4FLF}s zZnak!X;{mXI7`*Ut$tl+0q_ab|0c)H02R2<0qjX$Njz=J_5yaKI}N(A#ypuMF7W z=!rk~8CPVK{j3_~FhW24sB|#%E&>B@xYOotW?rkq-PeFMt4toHRYRZL=GL;eL zdbmzoES-&3^0|vb?ey(jj1k>M+8bCO6$!rSNeetWrpe2Yegy@%U)=) zeOE&B7ZW1Aa-B2HsANMG7#Fh1&kCi}Lw z%@FVUY0Pjng=R|l-tmFE&?W4ouhQ_X;Q_~?C{682Q0#5*1BW%zz3PsmTE})s0ed>_ z{3&jw;D1FYa+y`&m-q8>DaLiiX=9>qXk(-QKbCZ6aPVx@dP`Vq zihl)WgX5@0?3smFQ%9UwB{(M0RTizg;$NBC%U{lbHUc^JD;>kAn&=~X z$&c!m1-nm78NZd1%Ht(qsjcSe8akB9%F+XmhIfcu)go-?VO%RNH%h2UWlSTx$#Qm%`-Bv%XMj7+1zy|FIusqJv3Y+^VnXm9No;app?83 zhD%&Q`oq3Kb7sEQmX&ARM9+D8jaSjkclX-1+LVum-Dst+x0X%fscH1ftuKmaA8>Jo zNN+YQHQ()>LdB@6CMgfp!cS=Z-9wu@ zDId{t!O+o0CXAS5Oxt3}DeNFXTfFVI!E(QaAtn+v%d5Q{OvUazaogLYv@lu3*DOPE1_j$S|G#wUBj6q*MvCXcJ@=qw`r!;sR1 zLV?0s?^1tC_A|GPOSo50IQ^o~`cc*TntIIVZU1XR8)?!p{d1(XE;T40-ALqwvgpxD z3%Tmd`P-68q#4GJ2b<)ws%hTGJ$L<2^h;RceDM{nf`6=oz$@nmL@pvlEa#*<5O%o@m@)ji)xKMscxH{MXi`4OoAzOrJ zmE=hV;2KqnNqAmX+mw?SQy_unNRI*P_0OOet~m*gPyS?I0CkW@!Y^_!u#N>O{di+> z-*t;#mM-ZpzM?Ca5c>SBv8 z6hjhMN=3X#RH--UH4h8N=DcvkkmS5}1d>C~;*^{?Yc4{92(h7t4Up#%`b@H4&t==C z)Oz&ZkYvdpCNMRU4soG1hko9zFkDIeZ1UUxnwn$6tRLv=uyqLPM9mApokhK{nl%eyvNX_K~%g z@wj}}sqj}rbc5uTirga0YJ#U@s(uf{-V6)Rhe}>Hzc!id3}dwbtrBP=7sKaSJHwe> zE*Qfz;b)9lZ??yUo^XgyB6UhIMy?ySW^DD$q~E)~8jc(ib|ZB%WS`jg?(Lbvt$MpS zimn^!sgHAJP8%3BoPv4jgMr>h;uTeVuW^_A&l|DKTC8k?5CfDRSei(J^}v${c}&i{ z0Z%_w&Ho=~?-*X`)@_T%HY!HNs@S$|+qRQgv2ELS#kP}*jjEUx^Uk%-K6`)b+vl8n z?)@>JJefaw=6o|oAEUS4TI+5a&egZN-B~{E#+MntWEVS8nd7RPLX-&pY`5i035gE9 z+mE=5ltm_xsAhy93~mY)qJk}#zo?3vbb$8-S3s*dT77xR%s4fLXy?WEbz?IJs~5d% z{mBLR!!Euh2>Bq)pqTg>hcm zv;G%j71fjJMC5VB&H}^ly0VeWP{!mKFE7`Co!K5A$bJ(o1eP91@(?1tR_CH&h02Ol zYzJnri{JHA7}RKc;o2-I6?il=RKXz1uIvh<`K<6(_Qb64dhKPXI?+@>bV6%ytjo>( zcpXYMG)r8Ci+X31Y@);G9>}Q)uB_Wu{WWGwqE#!xgNh(H<3oCY;;asnS-G!fWx26 z{Y@7o)T+}nxgK9QJ7)g3u%?|HNcn`r4A(d9T@O%k?QS8-8}HJiR=F9%W)7 zi^4DT)v^3C-Ya(q?7G^n{qs}c`Jjoee@=OoV)>HXC*2+fme{al8KESl*p~!@`#*G1HHo=LLVcP(~LMQVB{VP$;8!A zX%lJKm^(SO51Tqi*WM9`e1taBLG`~=3!0%4^l<00ljJ%RU9FAL&4GpeaxQz;I>6~H8b(C2DO>j4Y z@L}5pgpx9qEkzGsT=Q-j8l$>*_>AqSY`}XN_bUFyJ=v>#WyMH&c1()SYZ1(`yL@TN ze700L+o4Ia`LVWBk^3!s-8#jWeR5N+98+;ew*j%Oq@z%pp4f4903|AqHYW)0FViCG z_?pWgtbLSx@TPwnF;d$0>GTfTS68+FB}8l!$IFFyrz!7J zrne{gZRi>&yQ}nzDgmLJx98(+VP1{V#KpG10*Kfl0t>MgC_rlWPTFZB9aqf@j<%(p zabQuDz`Z+U+IJe!OuqhL!vL&gKUUgWiHfvVw(>Hh+gJqqqSA7-?h$nxB$5&{YK5m_ z>6VGD-~B!u(mh`%*yI3S5F>u#WbX}`Y^g4g^K?So z;NvX7)01!D1u!%KEhv$BQYo{&C$2;4k)iV1VhOq*YiKG>d3Wr3(jHVAh+B!*m2)Y6 z^j_BvV>S3Q&*D%e14jNgQ&O8+u9FE`=IA;^9>Pt2r-ati5027KL^M1l&FEiDxO+$Z ziaQs&ISQy7kaT_+b)oB#+&Gz?-D6?#;!_ObYD~TDx&VHE0ijlt@2#{P{=6jQT+_n@T;M^=AGpRw`Q-DfJt)wASf+2483!}GgYl}kw?<%`If)$ zng1F;;+x$={)P{AHBvZxgF5AbW_9Euc*Tbu)(B|t(YrU`S=-(j!^5VFbh@q(= z=$~Da)SjbmrniKfc<@&`?&Xj=fnOA=1>s{o}^8Xu8A%o)@7BJ zquwCXYb3B~yp{2c_(A_tRjDqB;{aKnmld-^tLkzcrhS9rE|qo)b!m#_=FXa`B6i}0}f0~Xd zQ8sUjM(^Deuj`%Puv)tWIdNklBx}dzM9aSkP8A#V>V}d^YR%U5aKm?8Yq*OAFMNZP zDqFncNW~?+E56Zrjk4AWRJ@Zxnk52W;ox__Z1?YQSxm-G99Se-50xaIlj4Ews7Ey` zCf1^Mp9$=O=zpJo+Nu>6P78^Q3&wC+GIhw2q;gumwArFO4B>ft84py*oolzVh{ydQ zFjv+fk)~GW3)%7M$v>k>VmgTD3^?LeVr+!VAld%_Kz!Jl=rr}@4 z4(AwIgld070QU;#HOebKn!09gDyD0LXeK#w{;k{NlJ!TR8n;Frk6vANQ^b#zhZ@k6 zo0F5%ow7dEzIoLikdy3T=$-*O%#K(GhxjUcooJQhJEtr-PFuQHZvuv}v7%=-~nG82w{LAR=vvv82?MrdmRNBa>lqwzw=I_R#s+n0iU;G&EM~F3n!1Qp`P+-&>s4x-;9~Ers32Bt%rDbuwj%_cAhEPpxrT#DLS5-6|N89HXK^6|G}g! zyC{tmiISxPW_!>QJQzC#@dhqhP(G(zOS_lNGy zLJbdf%U^9@@u*iBWdqRH|4@TH2&#_;pz%S+;v#?sq@w?!${GLznBq?8Ne1LLhRowX zhYASY$$S1VLguPM>}vjGJk|3;^6~OQMX8$SdI`{WG73LLo2Z=UDO(;zNmbsK)GI$j z@jewpGA%BCLPJ$AqMmYkA3fsh;Ah3Il*+REIC1St7Y?7Mz(7pBZcD}K{2`G z8J?A;8j%swhIUe!b^E!zzi6D<;hMsxdjVzqGDeJ(N};E*(y?njaQPAz2KN?)L3>q4 znCYk0kMsdQFmSB>tqN=nn5>k|xlNb0q<-oITMi+7f#ekhG`Ezogwju}hNy7+C%6ii ziRTigm~z*Y{6;3+>G70O$8gycxOL0Ci#9e5f-RM_>t|gp+JA8hnj<^eW@v; zbG^LQziy0U;kjpl;NDiuGszKCp& z-Mz1}@T4LK_~^z$g_o4aj{W4b@tZcbK1ouut1yj;Z9r_|Bd8IQsm210LZiZi$VYT> z_hG)3W&Lytx^Zk7XPwoU;o6BvGIEvX;l+F|%V}9Dc2PF~y^y3VdRM)BJ^j^$bvy;y zPd$o+6`k$N^>B+Gzx7kEvSCJ5C$Hd5MPoA_m31Z`0Jxj9=aY07d-_DDeOu7)#C1u- z#NHP(z-~_8FyW?tUi^WWUS9yF#)LTurC{S%X@LKtnZFEgm75w^0{uEEUztC;Cn1ap?*L$Y)y+B<8Yn}rFvzUA$>S2G;apzL{*WQixI zK&%S}ps3w)H3Fe^u7t%7%h0c~aXUf|o}oOJb08-kn#q1Q7L+L4NU?#oRc{|L zz`Jy>0J`cU#WU+gh7tW5=5^w-v`)J7J9@}^_J_j&?NvOxH}7?&U7J_>Dp$#J%&8(-(;ul{U9{22!G z1LRuWdB@?qZ`CX!+qF&ifdOQ!-{(GytHYAT(?D%{X=ly+?xF(*IpWw(FxKiN9%leD z(`D2Bt#P+P^0%@EbO;`Tc3#mfuZ9&$jjm~+>4S2u6^wr*xJO-s_*Pim(l{#~3fmw6 zJ-5?NnOp)%*e541FOvcW!RSJy@^LF303%);O>a2eD`b4JytBI%?iA}XkO@9+5EJ43UWUo?HWr4UlVvkbt=p?;rE;v{ca zcJ=ZQ5p(fAYzV6i;!l!mB|XAN?3T@0Y0wi=Yc-fov1BbCJnbM#NEFjjAY$>u0`9&_ z0C`*VF>AD9!@PmtM7|Nhz76EhS9hN@aL5Jwk;FC{&2*mQW2ug`Xxth5ZKx_}gv*!3 zdOX^%5n%=>2(BH>O4vP)Os3`yLCQ>WtAk+~#0v%EXF;P!G@Y9+r27$e+Lw93^=LXs z4x>eSXKL5pR~kOj&ae6_tNfU21++KAYg_Dhz}L2dUR$;&(!f-#qGoR!KVsD#2jxX* z7ex+gx$@#4sT9SHvvn_5dPP3?8#hPa5-4BQ;f7*gJE>~>W)`pSGi1LI%}G9{W?xcN zbW5q|IFw>DwM#Cx{ya~Y=C~9`&pAAdZJO5VM@-i6$wreL3DNoPhX5i%tw2Pw-5Gs3 zBK}j4V19)xjsd}C5X<&>p;ynz`%z4-S7SehJudP~X>yHXx1mlpvU=Ro;Db@`+9d~%^cDa>UcLNS8xe5PblhM~zi>D}KLfhI zX;2bRd5CWko@~;6j3Z$IkJ^v_1#H!jKDVsNhSkV6o$nrl*`L zw=30v813?VO-8|jxWy6pR~T_3FGRw@_#{cl*En%hZ&@;@YZk6v!pxB9j5o0J;wTRd zL+t{P-$6V@vaUZ$lbX(M*ya03N|lpgY@-voV=615MSR|VSrOfXJUl&~c}4m+%Rmxn zZ>P$qOXU|OetY&G_<@pW#=W0iBY!Tu{e0%%mL*SvoC|5a)9U2| z@U!yV;OljpHmXfH2`+e%M~lzp!mh8p(sR9%Db>gQ;L-Fe9Lf8je470^>yqEJ&FHh% z_iyBNKSxg5Ss*7&4J@r+2ow+S&wsV8k?}VZtG`T*Ta+fPfu_b?XS5GzpI;}WB>jo`r}@*@j7CjtD8Ceel(!M!dakLRHCmoN?pJxK zLhv=u zp|?VL%k0r#Fx@)pi}O(Y{HF3q8g(*imi*^!Ja>tT>(pt+wbd_V!^4?{Xvz_m`+yCz zY9Ge4oW>fu z+E%)$eZC?Dv$k0=$Uo5^;RE>Vy8U+yrN0`e!ckCFD&yw8c;GG%!QN0dNDPn_8rjZs zlIa_F7MLhKjxYg2Z4!PZP_6sAeHnErja5jRYu|RP?S8i5Ss{Bk4v2ZWOjhK${+uSO z6^KI48CBv&PULEr>*?sZcu4~154W;gJbrhigy*1z01DcVc`?-MWhB5-C=k%J?Soz zQe6Y@JNXcUGl<6?wc4h6lEHWGzmssD;%kH5KwY$YWnzdQ{_Z-|NM9ytme3)h-Px zMHM5#GryUfTCj-yfg3yk3&5d$B4XsACF424v>0yU-SLnl2wwaBbqAa#0jm=4(U{j> z5SF&5TQm_%3#s^w6?}7NT>Yj9%^G?;a^D1=_@JG9S=+e9_yX>?_*Km#)<28RH@3!V zWZ<4b1o_+PmG{4vih+TZy_Kth0kFbUQC7a4R-m4iQd3sm2m2>+N%z8J+lB}jfdJ%h zsgL4+MKE%-V)$niXM}52$7vbrvDqnFB^c>%GYsFP<-e)_FQUH6lx@cYqh9?bM#TsG zXVU&FrkTB|ql1;b>%Rs!ixq+!WJG|t@(;NNe+v`V@kVOLTT-u&G$~0H>Die}$Jd^- zb9XnZ={7-9&QF}s1{WGDhfAQK-_(rcHW?X$d$o(80sW1jx=*6Jj!Fr+*{@LuH2D5{ zU$`awv?k{`qHQy!Y6ca?Bz4W;^&)+h()|1fGZ(7@k^bhZ5$EolR$7rQIlTDJlR~FE z`z^KKzdfYgt6i5iAZwcu4g^H%-_D_bd?h1OO9vAJYX@TkXEP@^AdL(7)5XEf8R!Y` z=#`=_7q`IQI?iBFB)zQ|8s(#|}dq1pg<=+O6=+fad-a78A+5QUBO(5A}jVdb&TFW~tC?h6y@ z;pzW)`GK&bJl<^m67S#VdskLax`I-=IMK&t%9~@vx%5k;+vb|(#ebm_<@J4Q5HNM0 zpAd1gm4^Bb@SvpsoNwcCs;rj8^v59f64Ecn7^}JjmoQ=Va4WC9p+t2X1uWW54DASw zA)-Rnq&!n}ELjhev6;JeUx8+Q>1s+=)~|u-Sx$m*&&(V6r4Kn{OLt*+;H@VYSUx({ zG^<^J!il{cH3L_3I!*D&XnXnz4Z!g}CS!g08Bh_9F~X0t;edW?6yhaVoO9a(X*?Jn)|^pOh19r!b-X# zZqC9h0y3O_QmV=)&MI8-AZEiXO&EP2+uY(buLBct$U2*8xc)gAqM*378^D*>GX@^X z#X`K1z4_6R?83(I>_j($c|Ni$odaKutaYm=^o9k#40mrU?6cX;>6s(`T*5fhAlaL} zQ1$C>CVFWtUY>$-?KE;qBFq;+LK~uw)2sh!mnv4saX2~n3LQ;Lej>=D zM|e-nr3A8hW}>jvv{=Jv3LTB~Bh!`Vl=BV-{SrD9Qsl1I9w5u{1L3p^&*HS0avT8YIoop zB2AIZg}1VPu7N4uy4Earr(=X(tx9JasyVzxmy3Vn-GwN$GF3T8It+7n#?-U1^V{I~ z9M)KTJMoBV(n~u=<&sus!IXpaL25toUg}vl84H7s@6FIv`pqgHw|B-l+bcbX^UWkB4bRy8+Smghd5=}?&4 zObuME{xumasqZ)dlY#%EfzS`+M35A#KbC^4sjh1;jMX;XWyHjmC$=srqA|rexI!O~ z1aW9)T2cWQc1Xmuk69i&es|98T{R^wwpECoi8&pWcP2VE04+T`GriM?8Wn`~%Ed~z z$zRS5igIN!CPJO7xLftgc)Q1w(1TAT>n)(zcg;yroyn}PR0CSiM*ah(*wrSV*;ozDiTMP*fCrxLs# z;|Xc-Grw}*FE;M1U!!#iQ(b$V^mnaV5x!~=8;*emLYf$h6!g&{MLjAwVf_bZ46Be> zB8%3_3jmd4->^V43;eK}W$&8|rQyVW@W(mt?JHvE{1SBx6>iu<)f&`2T|UDM6ns3b zdd0$UI?;=tHZ4Rje>&Pi)Gs2DUU8wc&4X#cohRNG80ExgLsPp6| zb@UiD`1$mAp>=~+V%OT59nKiR;+n!$L>FID?TTq1r&4_^$zpE0;PTc;nlFjIC~e0N zdSPxlKuc5yNX_29D4aY~^|M*&F)u0NkvUQtfQwx{vEb>oFsZ;G1#* zQdUw&fGvUBh7zX*8cG&YQcUkKnP4Bm%^@~PCc&B3BoNvM*0VkqW})b_uhldGGCX!E zN3~MU7V>`*cY+VrPiY^CZ|9@5_>G~a5~h(^qLJ#-ksc}hgvBM*d-Ai%D+gB&`O10^ zy-tyKq>r!NOBo9QffMtm#{4I>u3^Tck0nBB~^1N zn-w?0jaIzk{4ZwNAf4%b$tIlSOuH(DG3yoT%br6lkB4*&)-xYIA9;>aWfuTa^cz3+ z8~mQk#Sa;>TgU{_H~Fas4&tpEdmR7~+WH0o_kqg*3G4pC#=Zk0xus`?!h~NjApqwi zI*r|Rs88OwOO>96+xEKjoO%jRP8s?Jo{C2w8l_~_fpIw4QjtM2TkarQfG&!7H#IS; z$DT~xDWAU7WO}U`(cg+@72o{_<6^!frAmN7=3kCi|7v)(RDfsO^_T1h$letOUa$Ub z*v7w0x)*%*MBuWmr3mw7ba^>YIGFEqmO9}}D3zFsz*T&|l~=T!l#f(b5U`y^ z#ooaZ1+eC*W^Tsx#kBYKv?D%Y*1@kaL+(HeUT{9XUrai-kMzm6!hALRfylYquNwEh zgEGB?6UZG&63ut3ovaJLwjiFWNXn*GPPFVajHe!<9c0Y6kCFGSr{&r&j$g~MU>$@6 z0G;L#U(&(pe2|GYECD{!HV@Ot4Z+JmPA8Pl57q`qZvrI3^At@Dz8Fx%Jsc&trm)@ zCST04<3${hB7{STsI5k|mnF_%X(SiNi1}a1OXG+-(&22w}8GaRX`fd6yY9^2%d%l|v(6~hX zP>r*U(8R*Ry4<-fsuOHwO#Q=Zt36ivQB+5f`$n`Sf994l@x6a1e<G{B%nhF)FW?**_U|F z5Cj+|#sE{Bybh{$@TODD>olf#X#ENKUE@^0LiMQ-9w>vz8VRSl?q-$$~FJy@< zL>8eegL|IbIok}iW*L8$)TYHoFr>^fXw0$q#K<-Kr9}_{Y2@Q2B(MwD&SIBXlF+&> zjaA<4Ir5ZAy5nG9FtX+&XRQf&3OSutndFp%QzqiO?HKB6DU+mV%9medEc6!s#G!); ztp0hyv@+Xn8YCt_;WXhYSko?TF#cSY&EhVbfr)D0^n@~6;X33HxFb!?*-Y(ut;XH$EQ|0(4 z#Gvg!d4ZYiwr%4WCXGnYZ0CocF%1C1DC@`qoFcnBP>vQ=(`UGpHd`h5L4ofObZmU8 z+oTtbF~V(azo6oh1>+;RgKU-7&4{-AT4h%f%VcM3S~GFLsYPbXQJi%0=!uGv;Dl!& zSAN^30otcM`6u|af5vSfq>Nvc#ACd>`TBRn_1^@{dDaF@`XldEc@9G`0KOd@UGoJqrMOuIS^$(AYf%RLk4Fx) z*zwOIGhOYTuTC-EWZSMUs~RkY0>v_JFp^jb*@Do?Y#FtcTOB2&tR28SdJ}T?1Ah3} z!9eL223Yv$#O^}l$kud&d`!-EV-n)9Bu549?MeBsUo)x#;G<=p2xr=bXYVVPUzw~z z0jkt5tT3U_E7qQP7Ccb3F6$!cb7>zHTGQ1_4%fPEgZ;6g!ONpMj5Rk@*(lwqy+SsZ zYvwOhb2bre8Af`W4-<}-v+t11;m9*kj8ll1sQOxXrBk)*{#@f-@TJ(DopGv z@OMY7&u>p^2kb8;{Rmtn^y0jdTiseb06m1ZZzmnoP0L@K&7meuiVF(KXV6BAik)Dl z%FArJp0tS>N7w3`=MBDU_ATGbY+?e8b9NOk62ZxgZki(tNG~2L0bv~l-|pFK;!-!k zHfff=tOaz87koTI>n?pe+CX~bt82p@YyU3%8sm{b?iFTj*n8`+0@-s(q)O2B3~025 z3tJ&nljCwa7vbiuA}s85oo-y;*ko%d9HkfWR6sQ5Y$@113jrsn`?z=N&}lfAS=cA= z7DFQb=7|G1%FvnxyuPn|t@8o89HzOtbX`aXei6|0}OwYWn0X7x8<;m2J2{0jT1o6W%g6!v`pk7X)j%krbObj8r zA(`A+3CT?lYP5}vdDUs<>>*_`eZ8YS(?;f8V=kN)y2|lujaOqovwz8mEjq|{KMYiw zs=@bNIZ137w)rQ%EaWfhTuqkD@<@x)enqFT)j(5w%Cg_xXmJxJ%kDj=v9?G5+QH-O zMnZpU7Z0*%S8&I=NAk&e&KweEMwApcXeZMWCZH?!Dt&&2fp5`%yaM;f`=YS)|suF+1228*;$ZVjH3%1SH-3gO|D&NNHRfs`9! z{<|YB9yqzNT!@)hu7#Fqficb3mNITppy0Y-`PEVq#IGj9H8Aedp&FgdMR>f3X@V^N zz!L{l;mxyMD@rnVo0Mc>mN>^+hc$#H+9U-DU8~+NvXpKzF&G{o=Z-Tc@{UkSb^mqO zIIdsI1RRr%T34;+oBQ;a%6JbfnJu!=fM28Rv=W5LswQx#XFm8`CY?5b-YhSbya2Sp{c0 zF5xm;zXW6~2PUEbM75bU^cS0*Ruk^cDNYv9=d$V>k)`*A7yb0Wllr<#hMf4jD;tVr zNNpJP2fVZ^LPlnE9eDRL*j;j*Q+uNtGj=`q$_?mf8D(}wZ!f-hZ=`x= zwAjphy-TMV%(G71!9que6$4graK*Ln?kk0Ucl9UM?tZyeiC#!y@?@L!zO7XkFvTpr zpL%-Kpm~z?Uhwm{Tf`YOTx|l>nuPb_Eza0D-<847^rDyqf7B70@Rt00+a~U0d4dWE zmp%S9UJCzTZ>6rT&Q`{5KquqB;cGWlIR`wBzX7ohR?xr!>N%N5j2bhwdOhgY4H+GX zqOEy)J9-I%MCTIXPd*3r#zT@5vu(%FY=@a6mLm$U&fl!h!|xG6E_o7gOd-Ux9K{MX zyb|Ra7-+pd^R3yq_h;wBu1Nq|qCgw(+j(jZG}7l}D)w_<-3TO8EI>AXUQ^{U;(`3= zgZ~$nD$=%sU7ey3gRM)|i5HB0DxINf^8U)sBVMktPkw%!;~kq=y~~@^_vhzlB~&YH zzU_C)sieAQxr{J}+pYRizuGH_`Lw2>wsGv+^c_9e9a^$vQ$j!-zYE~(lEqAZDNJoe z<&IjV`h(`^XnHol`0UF#$Z36W>UV;wJrGNnwFP3%5p`8W9#~Qg*S>Vwb<<7OWsea2 z+79ZI#^!jdQ54|lJ;Dnct-#b|OfP)SL4vcyH`2l^Wd(3@5wW0v2KZpmW7;N}8o|+k zIi$gvwEnp(Y?KSoGef|3nO}o#8QL2ZeQ6>}KlV;tHP5A~NrD`CnFVe}7%l-K%oj>Fg$GaO%)B|bn4F@L=hnc`#QN1-bN z*Utq>XNOwsrHHMKBq>;Cx^HkeqQel!XSqEt$b6mfgvCm-S}u1-_8O%F-@LrdV8MS~ zR)4mbcD?pH4AI(G?6MUvUwU`8nlJI#zo@)n;Y0gAp6<^x0ELKpW4lEe-bVW5JT`E+ zbxYM7wJvP%>63u^HJK0UV13VIOMPF{N^{Ft+)_Grm!?f0t)TYe>pSn$tt>oEp_2uj zW7Vhhl@h=mu@l`v$$A>3qv)HX@uD-QUvhm+_M4JpoT~ToV!Wqs57)Q(Q?3}Xye3Ny zk_XjI^*`I^)9+|{BMz0nClEZKd_H$aa59P@@ar>ISNn}&)S*j}@OddhYy2R^)8BS( zyGD{Zwc54e#Nlt`de+o^mL7`hOY9K0MdOetlnL5#A z*0KQjcTZ>j0lz>uweuntOR+xe*9v&K(5B0Z`nVyc*1-4LZ7EOwa+vu5r+n?l%u5;s zP^9AKw|BAfR814RgbDl`z|cCDy_W&Z++{#i zQ|SNo`MR1I04II^M|}o-wkiLCX|5Ouc_O#ME6713P(i?GCbH>-)>uMY_)vibq{_4N zNSjJk#x(o_5P6|dtGF*}GO%A=b`lZA@y?hx*~qt0v?v~ur<|8iU&Ouj05I&PTOrn^ z;g&PkY*Wcv&=8q?#@D|Y#b8XtPr3_?$rKQ`ASolm0wNKY6zAklgdCuTQ&J4{^%Snz zezAKN?5aSodrl!rf1ZaJh*(lmWy7ufNdaJZ^z|Uzw^`grF6&2xhV4*F1VW?Cqf|tI01V;J{v*P*|^T{jyCA zgPiC#8QKD9nmA_$iYs`8dq``ZL|JYh?yHFAt4k20iG5~v$8P4rg%PPo&(E4NR>~wF zT_OChMACbG-&{x_0dvW|wq*!c@jv{DE*sY)oH_p-iHhRmd|yPfQ(OCL@O6E5UuF`r zX8Al@lTd8AZ8r8UYqk0$bdbx5E|mB={_M|J_7%5%na)~e)p z$mdOpl*GI5um&BQLLyOA;Yl8Je{@g-YbTk`O{-%Qas~DXb}2d3-o}C;CpN=K83edi z)%(Y$cQ@k-hU?DmJNLt)qIk*L@#XFXWWOAkPOnr3DcO`Y%n0Dm9KPze-)XQkkOJV% zWfj5eB%m)r69B`rp`6pUw}P6(DG!5Nx#5&Fgj9ri?%y?P}s~^lvA@|Gudl*@moT3{24nK;Dkb|3w-BhBmcw zF>!D=bN2eHN%B7-$`sZ9*hL^jc|+*!3F5=Nez6Y@SXE1O=x_D^P~)6 zzBXdrTzzfhxcYK~XhS=%64D!2K8@jW=Fp3rrXXfX?Hw~2zvqrxL=MG&*$Fa;q*}y% zV}0Ud{H-oyuaGjQykTjmQ87-Hd6;`0IEtoloB6B1%L9)^&;2sP%oE*D82ca0fvvnW zJt8%0pp<}ha3rRsh=j~D>1_OMD%0q6aznrsWtozOLX+g}Q5iPtheB*WwLA%t%`zUL zH&Z3aPeb<_CO8oV5ssu{>x|`V)M>ibuo?AXhUKVf4-gGQLh{S+MBX|LlsWPW8w9o0 zXx)hTulak(9LRG7I)i3Wu{Wq+MVyHtyMDVpH`W6H8@|zIe(%Cm<#({Q{M$}J_lFlb zZCtBbD1CiyX-|x}Zuk>gspsD3l*`RR=CSIpXJEwx&ULZr7D}dst{s*lt?2{49$H>8 zh>8=h1n1^`LehCkE$3Qjc`X>|i zLp=ZZFC_ax>0AKIV2PwU^QbcAVZeGRriyLKmoC=wai+&+gIo4+-yo)SQz-wC}hq|ry-teym0S~T;GOQ0&I@7 zj5#kis`v)FzD{!ecB`_R3Eumrm4-jb8=8q#!8@UBiO~vZ)mfG(nljt8U?o7F@qHp> z_<$O(dE5FBN0*>5D^Z!>6{MG{*=$NlumxMEdV;Pk7EJdBh`geR#B!^2HA={W1E}cQ z^s%b#(L`sob9||1k=xXdsOZdNS}?7r2^AIc57##n`d@?S*k5uyqJ{l`Sm@7cJDEGv z^^N^(z_$j_MZxgDsU}@g<=sqqF5CQOC|*_U5AqU?pQME2!U6g1Moh_>GxjqTWXc^D zR3jhw>> z1$zFyr~gjQB+E~k74yym#n>n;-sq;T-cgh*sez4a?VeKORFL^XIWE@ZY=O1cs^5}V zX?YE>!5|t72H4O~SY^Xyp@XA|l6xI0_RkijO zcZk_*x_6ruoj>VB>tdMYKHznHDK5@n|J$PNk5(hzL2)v}4=fd|%FrGk(L8;%oBnZ-BGK`d!FRByfYCnJ;O8sOR%O{HiixtbKT`)Z-V4)g8o+TEOnU zaf`giBL;1AyR^s>N0*RC=gl?~%9Ai#F(SGafn9U=SxYWk`gqUm;bcc|?s~y09SU}9 zrF}s9CGyY@3tf#x^Wac{oo)RxA_e)grJK!m<{{JUQx!JqA z{4-ajsLD71t%iV|8<@4P?D1SeaZm`&a#{9ac6tdqN-&XPZR_|l3CUpnt)KjSbdwJY zV1K^CNZpV=-vAXi%K$_rxFy{}a#UMtA;*d|p2DiMIO39d5gAB}n%rq~DP@@+iTj2c z)}t6GhIrlYKg%w)hRF}1Sdn4cYxU3c2Y3pJi8tLRY>KixSBfh|r9UwlF?0G(`iJI6aU766RWtOs^IM;N#%SqO)) z@Fe4eE7kMF4NVZuSIhnm(|8u|#xdZk(w>)3Be4#)hr8Z(OKs{tF86l3Ko8=pa4~4U zg1KHq-8;Yt7nCwodjvXpjk%gWi>2kxX-vu+yi8Imh)O(}6b*-(N507o5;vcWxH{RG zuX$g#*faNL7eLEz#cRqkAUXAz<7Gleuw<6JWdKY`F~xL%y)@g$wwb5EUU2Rx%QyEv zLQH?N@Hz807byzj`?2Y{x1{YfE{OJjM8)EyPCGfFHo zC$ScLwDo-Gn&x)xzB%wBwBFfqPy0#GG7O>{!OWQMzixkZGi)p)L(ZTs2gufgOyT3R-AD0F~#YAMFk!Tp$jGl9I>{1)HdW&h*?iT2Kd48BCXRB$~NG(ka(5T^|p;T+$B(iI>>2Bn- zq0w~tw>E9+?i3$(FA3AvIdlbVv0Z<|?nXD0I@W?=Y#J^-?FGz3vhWhu zbqrh7w}#bf>QPCt0W8}aK(9Azdj3T12&apW8^wX#RQX^pUXx(&yGYaNEGSqvShN|n zP-S3Bo7%l^kR&p$G4(~YN#`C_An7@}k7#TI2X{YO z*Njs*f;9Qbddz(0NgGUAvz#dU{FuHz0)!F9pH$lZjiL;~**KKG9RffuVof&6sv}=h z2&n^3XI`T=#AlEnNbo(Ycyz^D25+gkB?(sV+%m*h2^PiCqe=%!J~EI4Wk+}olPS^f z_by@gxbt;R@Q|OfJ|`rE0Og4FEr(YPS%>Qp>OoqvAWg^@MEe{Ge!a$3H~g>cQC%Cg z`Q=oAO&tx7!xv^x9T$MfDw-u(3*!S4{`P15=y#$*ll%{&xT%9cthuqwH7`q957fJ1 zDsohHb=MuAyPZd0{pZV5y?l|QhC@tHYR-U>&=oaddBDZp=}&KJxU-g@Ia?u#c*o^K zwVX;(3W}t=-x=e+lBW;4%U<<;H^NIb?Uw?-ACO@UPMzzQ1sWf~|3KP{ky^iiF)|&9M6c@LuXxkXQwmP6I~|PS{4rFt%;mHY>JmvtsK;728S0wrx8V+qNs#r2BcFneLu7YxR7%pZ=fjT6>>; z&QH5`>L^rC0H2#vj^OCW``zOsyswX6Ho)E?`mcpRWC()DicA#%0L!|5;J$g#{d6Am zo{P=chQBvdIb6p-S+X4AqIf*-&iH4VP~k4^T90au8nMxFz}nCB%+(Q}w_sN2+#Hh( zMZ1X@?z0V64|HmG?*kHK@8$Ai%0@ZdOB+WBG3fIM5uutOj_cQ~sIZ+2o&jI{!}C<5 z9Nat!A+FiL>w+I3O-<#^$hTJEv7nol^&(QwGetA|`N*%o=Uf%^?u?=8Y`G z0ELcUG(v8E22$1wvWU|t@bP>A{|3toU=^`sr7vWRIh!AVpR&3{kO{(pAFk%k$N-}o-HcVu%p@WBN(?HQ)4l5>^5kY6cCt~tg<$560&RaO z3wmp#PV%d0Mr5C89mqnf{NHGC%oukBTSITL0I3JFZpcOQB@Uir68b2%#uGJY266=# z7#&O-|5fS6%n9&Yz8lSYO`D4$MeA!K^u_iyZOgICJ@>pxKV z8EvNiw(l#__V*P@@xL1x|F<60+{)I>`ClcWuBv@@ncrx~H#E%;ME~n&jHIeuMzLH` z`8rigc|9B)F6A&qGw1C0!MqGHS-lF6X}C4{{4&)r!1O*Drd*1Sa#&abcnP&l-j1Y6 zy=et6LcS@+Ogd+R3-WjCio>xY(HSSQEILu_IBdcQq-M)wFT2j|>q6s~?80p*b+{Rl z4{HD94)M;v#;U?==IH4QTln$A9Lg+4sQA3Z?Q;NKX=l7!whtCnN3|t*G0>#tciE10 z*j7~EGL0(Zvmd+$i#R4bpgeSqeC>KcvawG`Zk6OVoM{b74t$zt7Dy#MkI221GQq_I zs!gDWuD1D>ki%!Dl$eB-wEyTbp(oOMZR+ratm-h2d=fXU3{sds>Rp-7nL^UpOuP(^ zrnx@kS#hEeD$cW?G?7hhOLnna*wzb_zCX>WoH+))mP3pId)*TgFpyTVrSfHyC>PDa z=>Hc#H^9)Gw;gYt@_sFM&YqXy)9O@UWHgLDPHN^Ywp*7V9(P7(3=&2CNCd_yxO2_p zK&^;W7+hPWTWL&m_)qT?-d>P=IpZeZ-pTsqB~DJ2x>CX)>LB{@UyzTrUb*~Rzsow4 zbyf<7eZF%7>7FgX00~+!`&aNMEAt%uL{FN}kYr*f@OyjlXeaMS=hm)AKMOVSqK62? zZ7PgUkLTrlHMl}Elq^~BJiISGMDjX=LpaspH21L;%>`35%rK?(h@+r&YdPF~I7j}#rl zKkO!Z(zN5#YLef4(V`69=z`3z@<7(TME~)9)WigI7H8PINNF^~BmXEz7bo#Kg9Ob? zc!~%mBWn>Mys(J9BwK!>kTI|h4`)Y5@*v#+<9vvW+YEJG7@^$Y^$#Me+9Gwub0H9+ z-<7>?-(CH00!(XiC0x+&e!u?aNF@Kee`RTAWb0!2uYLYgSo%{pRzJdOA{*g#mnUn(s;vncG8;p0*4^us z|M(ziZdDgxo}-_h>b_bYY(Wj(SS}Y=aVv=8q%(8M{Kv^s2U1_WWuQ%-I&y&>@}Jvd z3v6zr5vJ&))T0P&K#sn~=(Mi* zFcd_NN3m-$6kquvtQuFLyMJc&fBZ%?KB84i>sWr-<5_lg>VC*H7bqLM1#hM?e+eD8 z@zwRie^$J7og$ARdz;=)wfw3ULp;-b@Pz(|PWCa`6a4qe`g(=mo%*h;Z^DqNzujXHcs^&4M};nQ1_vu%||>y=Pw*6j@Hlz>^1u$Wu}q zdJE((5zwFZ`Rk8e87Pa!3~Mk+0M7sy>3H19mjTL7$GbRYLnrUnXhhILL{mFTG;FRt zq!A%w>mlIsJ9un>4^@*{8Y_}gYBgOTPnXAJlmogC()Sv~l2;rIYP|*K(byJqTz9b^ zxk7roLj0cQDx>6fSC_aigsax%gRz}!U~~jHmEg$;<>nRm*5Cb82eLqPE$e0E$pjZq z^7MFOBJ%z6lzJ+~WW4IDc%4+GIPQ&DPN1!)s}TUnELNvw=gr=D(JnaM-jiGkjW8hJ zTR0eAoL%eJ$Z;z~PdfX1^-@9K4u!tea^aAY zU3Gv-XrMB;m>2Nt{>dvGy+U^Wm(}%K8zR9xEe@7FmhiM~lP-M+%^mQEKWr$t5KI*8|AP9AI^$ts=PI9hJJomUQwH@h>R65<~I%X7egCf3+=1!RKP zZxHL<6y|2g+Eq1MD?9`2oCSC5t{piLY-2RSc7C1#9MU;{6p*m z$Me3B)gN&Fd!OXAfT28nD*!aU(eoew|N7(~(&ul{d+a|MC5@T(MPxqy@qZRwx}ty<_CBBeD{J+7y2vkLEsz4?%A%a)*h`mU!g&ZVl-x z6AA&(UkSv|$la=0%-Hz30(U(7I&9d}Cp?gCwb`(i&u`qy`_ z1mk2O-BX)<(*AGz3gs3}ALtRjcq9dl#5vzvyFLI=v&ERI#hfGku3%OXP!`3od=-|u z$(6{+Ibga@z}T)3fMRUU3)eQH{wDy$U~`!78b@;zoIA8sjk*qmaD(ZV=>Ze2H&5+$ zB?`LEMV1yDxvU9W zw<`dyhk5LlrP6f0@o(AsM#`k~;4cwYS|O=gSx@D$nq<1Hv`GRSrm$|0P9f&iFsJPM80oDF4^gd8lPzztM^e zcq$>{056=XLVxlHxdfrvliAZnNX`8H{HnN&-il&Yu@n(e+SK6svB}FyCZ;?`(bf@) zaU!tNvFE_Wn)xzCNDsh>#AMfqNUk@T_R!&fE7{=l_7~Jr;}bX+-M&n>j8IpXn~qsc z;e6L-(zGZ){*bTh-j6PSmFA$~=4o#&ODZ^arN2hETf5EZL!I3Whp^IOTvgvfrX1qP z@4?JB`ned;XAw8ud{d=ncTDzv!c)!-8c7<>lw^xlt^%B_|2{{+N53*uY`Vi{)}j$owEozWl6r;!5s21q&~=fG zh$*p%)zHUaksWY8Sf^(6VuRRnge4D%T!w(e4YFKZ@m<^=)tmkf??otDy?IwpAwRFE zI`O;Q&7KW+G;i(K9lJ)}um0rL5dYy}noD z`30OwYx>np4qWf7TDF*{yYB7Fmu}f{6_i{*42bZ$a7DG8loarnqqmU{UeiJ>jWgZ- zXy?_}a9~Bc9aG2X*E(S2oxPm)9BHw?ZELC8Vd=98_)2+(Y-7&v;rozEY<>#_O@RB6 zOCB~Kkp)o8XHcKSqv!1cF1p}lF6E?hb4fx4=_e)}^=)KK2i*_2RTe?qXwfpp)w+Oo zc54OA+S)bF=bzt6-FhW|!s+nXz8qXRzgaCn7tPsa)g8FSmuZ6E+g0r?Tf8YiqPZ@| zU?h)buxx=%+lu_C~c1_2l?8^UTSOw)^wxx41*=Th8*bP5SX z?J@3?n?muWqd;yfsH33|vxCVqB!QrY{@|K_hk%xU)3;sUQjo%7x`8gQfuFoGbB|2b zRmE>jxOkMuczyD@Y2~`RCY!4dB%@;`Be{p(>r~&VnP4Z;A{D%2_kJ2l6oi}Wm-|}0 zfCVt%61N_!6#&Ol>-QJnhLzz>mTGRefy~p}cmjevadK5}z0z}!HMi8DDM_5jb+(Q< z9Wg)l69~V~|32GOjsV6#{U)osU-U%#;dO52Z(-s<3fICl^dNB{E1sccdz*Q~FiERZ zQkE~_7r^82h{CcKXffopE;B$xSea!vz6T6rmi`6zOdLY6| zO}BHguF}guAWqANB#*$0;IHe`YP_28zIB1vfq!4Pz%^5W(jt>fC&+m}ouOQ8wt=^7 z)MoIJi9TrAv?091&6I7`qq>E<{GSV>N)l)hQVUpmYbT}Uc{PGaa1=kMw#LNc!T_#C zuM_#>Zxuq_(+eg+a!{eX-%8dEwj{|g4h*W>0#{WRFm={7*Ye^dI7C%dQzX8sNncA! zrhs%|LkiG_`Pb}g;!QTst{?}CcxqFUVd2{H0a7R+6ocs9j#(nJ)Bt;a*(}t<@THh9 zYw7%7V)^$2qS+Wi;g8wG3HYKQbby>-o7jPxiULQ=M9{c03F694aoRJt)47b3(@(F; z6Ft3fPMG8({Ss8ZoKQ5K8$iHr%;{MLAPg%9R(auh{|-lVkFoEPQGH>7v-3Fu z(NQzaCLM$?a*;XI^Y0Hg_Oe!7~or%|-W{d>>-GsjGDyjX@_{Q84V+ zW~<{^a2o42>JglLbaXR2-5-UdYvA-24@FhP%=fo$8Lx{A09PFbW+%sR5m^Kiunc(>X(+pM+@+!68+2&P8*RXsv_N7ppeTwS?TSLyb|Cm(Ieh- zeg7-6EErCqjk1~U8puKS#F`NS7f*5rjgK_ zsB6Q#&bC)_y~$OfoEifpZIRiN+1jcPDPllkX3?%(_$^<^lluX;3E(5&D7+-4hCo0& z@dqR=6`NKpVDS6Y3O4)uHypn``jC|+fk(amWe)W0Mc*Kfk!%Cw68>uJEAG<(jXifM%T~{MatrREnQM!W#72b6of;qJGQM zmpg~q5e^GRnDyd>5}hEf!7#uLmgqL}^9IHFQogaaeQU54jg}dY%r(7UvvN7t;Z-bi zD>KD~dc#Px!AS>MUV+IPdWEQTNVHgRWKU5h06`OZwd7d{F{U%L7qLYCYQJZ~(71x5 zWeUv#5)1&)u%63u^RK<$qTj~iJa!EnW$Qdr6)dlc*u2{6Ge>m^xEDgmZ1r-3K)x@v zLT3B^&UA8hU9Sajv5_rr;v;l@7keqJ1R}UWiQ}#dF1+-=G-P7N`*BE)h(@uN*hQgR zfJQ#w2wjT2_lVD3-u)V=-D?k{ReTs@BBK!V;-3fuuW};ts8~i*1cUpQ$87aq2FP+t zVjQAv1Cd6Xzi(bf_i?WzsO?v)A4Yl_5>34Nlwf*G18Foj*=)3FjP_4ON4t_{P?Q&0 zwHqIh2+d47TfG{(hM`{8vGELMu4-Wk7(Cb8L1 z=C*j^z>MIyN5eI(iBLyIeR#wjTdr_aeCWf~nw^XC6!3Y|dFdS)IlD{{T}&X`0XuzA z1)p1J;3nAt&=lc=(?Sf1kS$Cl;?MaWQGVfHN1XW4DiFG=?=j`Mhn7FP(;MLN9__TN zO}s>6o734GIdt>XShp#%^su>AWe7LiiEIZyYIi0E7Zujj03oUZeCUf#%tWO!fkm-9)W5>^U_ zO7wIhd?5b&B_8)lVFu+JG+Fs(ZPos}$KX%1ZwrcvfrFF1iJ7yrm7T?Z3KZn*H;DdW zNg>LywVDv;uRbV$u~nl!<5<%{BVHn~mtyU6%FoA{K^D2t%gM|wiiY& z;-Y4Pw!l35wM`_WVCrC!{3^|DQ);HXBhLZog?A(Qi>stMAJZ76Gjru&OhOhPhgKH} z*S5`QQ2X@aPV6gaw*7V>r~Ze_APUqfrZtZx|J@<)cg63CoQ6iBAo?Kk#emBa{jJdB zD70&X80p+_0eWyxo63-I6DQV*GbTyW@nA1{Zg=u4v!TwBLx!8_|eDT8p%W*i*(D%U8R8gGx%>Y0Krk5cP;YB9Y@ z_avSA`9GeHl|SS|48JR`2@?oN?i=3z7Z-lwmk!Lo_?`Ygx_nJb$cn|YLo zUw|D^M?^9PWo<5C%W!1v5Oc7dRtT~@*TE7CRzz(-M2g2B$3=%l4~1%QL}g~C2`_IQ z9mayvX=ey&6p=zlqMt(uU>CkgkJ5&yp$?n`9+U#OWA*u?U7m7 z!TlPn;^6daE;tgvJJbF&tI$LIto+-0s%fos7JaqY_T59X_E*ZfyMiO{eze|j`rL(+VX#h^R^7c}d7n%K zjJ_^NoPaI825X%7c)r@SV0$NkD%E1T*4~>#21^b=P7>0L?~~1eK84JN2^F*P;Eb{8 z=v;<3(e*KzZeNr)XB@m?6ttAax=hTruf31sYT*U)BNNN3)L>Sy;}gB8?VDf-*G^bd z<%c>T#v1@8)laVc1n80uYJbpC;bKIc9`hA(2|$z{AQ;n7L+~K6*mdIl(8NQbjx-?( zgvmsJ>MXUd-vK^V%%Qn@2&>fUcCPh8{MGH>eiEQ;C>PopWg7P^G0Rg9ft);wrD{@x zm)kx_RT(6|rrfP%QCQn{Nwur?F*}iV8ir1n0eS?eoUcWe*}upDwyf0o?V`On;GC&M zTFCzUHFLfY<9eU&j5;5Cq4+&K(2}OQ;iB=-)=YL{8aji0Bq4<=nS@HU>5pR(S6sm7 zduu#{dknUd;VJO$q*myXQOn0?ys+`*FP?8Fr!?@8KW*)=9|eTwyIpcuapZ& zhSR42liVd}E0c`hY&dtd6$}yZCz$)Xwob2x&1-qHSq0{jE0|ia)r2^1QZ0je8;uC7 z8bS7dM`#{uoJQJ1dP@LBM;E=io#chlku6jTNWL>&F)^CeFP|c)>Lly_^D#gM3B8dK8 zQSwJ1iyT{KhT_C=Ep$ax!HgbB$L+5_;T>N=N|0J-nnCK{_2EzmuD8Up$8{5y7xX6N zz`Oyej)bE8A)x6=ME-+;36<4&ul|0glaZMV>WA8Lt%tXFlWzCv zy^|PNdAn0JMDXGvxA4!zf@IED%1$USfG38f9=mf;>tsYbB1-EzsS{RtsQ-a=pcq9i zjje8N-DNgvV_SEpeqIATnU^?Ls39$EILzJ*!|9S9S%}mk z7Z8s#3(er2vr2&Z6U)sX;M}v;FU;F@d#D7;CeDf0dTDm z;SX?nj1w2`(7~Xzu;m+i84~gJ96zEwV(^9^9KoNU|9*{t)~o3`{ATp7p#N*3qm$V` z#a<4k<_0cKMkY39|1VMYPy0P_q|ZM!Sn-G)D31BkY6_C7ZG#R)bB9jU%j)GcHgzLL z*HOzY9dY^kU*6wjfeGko(;?{2sztUZVAb>1fvb}?m^qFXUL!w21BbTga#4E4#W!3# z5pSYWxxR5=*MVuYX-%k`UZq0SNXQNgp-gf)jXYT$-MXX3sB8`k*=m7FlwwJc@&6pkMSLaEmf|#yIhyzP9vqS1JV|iA!++55ZsHT5C7p))^9DIcty2 zM-ausA&5gVrVc-#@Yl72aNr(?J-a_&wiM!(G<#A!052zv4mCZp00K@zPIZuWY9wL7H(sx57+CB&C6|{ zgvIMc&c$U9RAoLsrt5JYhRCv*jW zr;(iIpc0dlY`^w~YRb8_&dS%H5xy9z0lZ3SU9`MD62iX2Cs##=Le1hOfP|WaTgf=S zE5-_Yptf&e5(+R>4JdZsSjvny2$bCjiaHc}5hg2yGW>!y#oi#>B+jLJ>Wu~Mj>1eV zYuZA#(zPbB%syYtYwm2T!YtVb_~B7&!DFhzl$?%$kzuKRAamHB*2R=dSqqo-eCmx+ z8b%0=9UE|~zp^g|YAe7M@bQz=QP7(QY}omyU0rGbth6dkGaH$m=fRZ6UfU~h0(PoB z3nkUy5$6~@2@*@hlXd~Szk3OAEQvkO7{O4#2k$44*jNJKhZKn%2-QeL)FXo1vq zf-*r5@Fp_4BN2C8F5-!5SPoBogLeqoq9pg9&lUkW^7ILS@Xe!MXBwmVEw#j5{#i9X z`-5WS@q=QAidaCkYyZ_JH(b*FMBVtE%sKY!M0}H7j5{%Go3RzsoBlQXW5JV>Q24aa zhmlzNl6%ZX^YK~}AnUw4y?*hu)s0+N;WsWp-_%-vt(mipjTevK2)=i51_vnbyHT5u zWOrM4_UFFc$=|>ivWkOR20chZd8@9BQ~Rlek4-{6oXqA`)U}`%yffkSZ3v1FcmMA7 z#DbrKSD}|JP3f7d0!^e{IwWWcfc91R6gIE(iIUdAS@3ikDI;onFaYNdF*JxCM8hAh zNiJ2Po1sdvWNU^L=|lW%j4YpeB(_yPW^%E4_B;QPWBVNGfnxW4J*-0bubGAa4U7DD zU&sHg9(9xvkOWaB$7Ql*I(i_N*Fd!VZ8pYHISQqnkHW!Hmf)gXFag-cFVk2To;eIc zPjH{^B8-KEgrr1o!I1vB0|;BbJ6XSYJ}J;Az`nJjuLRAD(3UwwKtP~CZ7XkbG#o5s z^8!EAK9oNn|Ka>kP`r|(9b@<>MF8EhxM^5clVjnZsV%YFK?X<@*1Zq z%?izdo3-og(5VqF+bT820yF3i9lmA^PZ`OtlAULEkyzW=&0A>z#)ec?eKLcn8S6r? zt>zTGL7lR?JeIDm%wc#MrFFnQ^v<^Zpo93|8vYk8iI0@9c`24SEr+Z;47eWp@fxqy z6@I{rpU~@Uey8zj!bgl?4F~ncb{L0)J~raAhjXPq;zlW1#FCDF1CX*kOm*f(=c0$` zZwJ{w&f+fm*YEs*^aFuIKji8s|B>jN8%dT2zX8?1Y#8s=)3R%T*+cv)7^T4H3%C)( zu=3hEbhT9?HY!X`k~kqEVD|xh2T~y`NGvJH_CYyd0kNQEN!>`JU>#g=1G^z}>BpL1 zjAXjSQ_3_%ZP+HiCh<+uU5w54!Azx0^%WIR7Yr}*m`j}jR(M)}jgmlWu3m=!FidKb zWu-?U?042vv8RLJkz7y;{Bjp000=fd#R5_Ga@=Z1#&d_(qH!A(l-0CO*vm)H4tHQV zOnXV>a&=ehzL}AaAQ*CNUHqaMJm!*u^q3RFz;KSk;L_D+I1*GO7=MRQ52U@ojfu(B z1P%6|QD!x$H7qQ0;%+Ac|?&QjPqN1&bzN(kr zhDAkW3HWq^aUhF?`W!;UMu~=o*hK(O_H6SOaw07?{^S%aP9GHYz}dNcd)a%~bLo!k zBQWdw?V^=*HBCRqSdG^PJe*kaNllD9=Exx~d&#%FTWb1|x}9UEX2y zU=r81Em{Jx$!TS>Jkd8PQ!~<)d4=laG}JS>^?IP)t$ijg-s=tgtK_N~4ef-vvwwVA zczzS4sS0XtX>Gndu_AhVpVR$98J;sO(CPE|q!xIHT4!l!GI!MPChTq*%Z_n@J)VC? z+kWW-VDu{Nf`cMPirJ`-40;84p8O#^r5Na5a!Ya?Eo+$sxmC}+CBu%tI=cb3s*c-) z-F%#4@XS%B8r){-!%pJ!OaGASaklmMMh)MqNF}^&&js5HfGjmI-alT^_0cJIGJtW( zZ#t178D*doRi_B^SSa*1%>WA~&m23;5N9eg}wv6MXytn-H`V_eu07!x_VL`|G1Z_@b2wzZcHM9Xpl z{tlpZdjKQO%J)54|1w2Dg`u#duKD;SEvXW>470w-6gu#L1+`2u8MOJmf%EBp2##cu z)0fcoZEBGL3+Bg4vSL!TOU*TLpX$#)YrAFX)ABBA&^c6G`ln68Ux<_^eul3Z)H#~^ zwy}4OQsgoB76zLI&@QU$$8rt1@xYYJ0Dh7Auw@bRAr^Qsvs;kfw?T?%7B-n?)Rr(_ zGC{{_x8qS4qKTyjx4WoqUER#O>zG&z1^Ct=?BT8PjE8~loX%GjAKaDI(4$`t=#Ou0 za3eQ1yI#H@=<8LMkJ&NXj3z@RI*iFKwjT3##oiF7;_*tNaWINfmLf5aH&v}G(iyXM z-j0epiU6-6sF1RO5aaz_66^re6s0 zCfYY6<#aH_?0zh5`!LxJeAHmm58vM{2U}WYl{1D&2#b_$rRd1+g_moS;JT@I^S7@oRB!3x-wFKS6DiNeckJ<#$BZ2X2t zbeR+v$xYyM7OR7rRehkVG5^qLYU$uTyJJ29u5&juk9Ccu5Bb~z7cMo#L^zC826Xw@ z_5o)+YdsSo{we*3Q|Aq3HGATR(}QPfry=$h=I4JYhk6d`l3IV85m5**3?d%nw}M(UL`sVITbXeMG30XA9sRJ(Kz1l z)K~U3hS=E&xD=`=6XW6}$S`xPB(rmR5CT0Li1AM03*mo&0|kq&r1g?`;MUQ7d@tYomscRX@g6JFAG#kQP8*1Bf=-RlOGbqEOJ0y^ zn&*XiZWJIz&q*TB&VFp$Un8j_7god>o1yS~Xcq(((V(V?T0t9G#2=i|^kYCaG3yPL z+&5??XJRW11G+-I-cLOjvWGY(9WW4HTz05{D0w2l)q~BUUj-456wLchFaDkmSs`VE z8zH?T?Yy-L1`fk%#9OV_m6;DAgq^{weu$e&H3H`jbPB$-8N5GMLv1!UbzFeRtk6wL zK7|p#*bF!YdB9`kfmxB|dhexCC#Pt+iSPwUmZcA1DW;FSr>D=eD8&1wsgjAGzyR3}B#uVf4@QTa2;RV>0|$?q{xT!AW?U3Yk*= zRf^8RLtGKr1?-Z&ys^2Bj>=i-IvB5=ODaVwR?Xat(O2bIHWBF2*2_dgIfIoqJQZ&8 zhEK2h8<3r8hn>#v?AGhj`H}3An)_gjPA{qVyyjRpcD{v|FWtcbq}~5 zVJT^S_$`HA7mZ$I2AB2Tt6ekez=^T_-TTvMEdWWn?jcV^c31sx<@JRJyE^CKR6~3u}u$4 z1-T-zsuwJoX09A3$&txR-OoJx8AS#*42qb5v4so}tbq2e@wy}2sch_kVkyNbC{#w3sWo2JO z8a_Sv3gUJRecbG!Oz?p!oETHNhk11=e1LXwo!;}|OW>E2i6J#fHvull!uq1M-&k&K z8436c$=(z>HqGF&uKC{UuJ?e*nnM7Re(0AOS8z=#z6bqf)%EWlta8{LBnNz8a8UQu zdCzMx)157+N0YCvqNhK1E)KyT1bE)7ZAj}L1;O3_Y57;4rctyZb){6-G!ajTeaWR3@R z?*&vBVEG+dJ$J?{H#=AI5Z7I86&+Y?ysb@D!QR)}W%RF6ZRvU}WeyDGJO$%+Eg=cX zH77Bin^ipEPJdX0ZGIjkb2RDc@UsVpXC%vAe-3)c2DME2mOj4+j|jy+S@is8TCB^f zlF;q9oK%9~zxK0zPp*IB8n*Tp2F_+iPT!96f0>-728JuKItVl8i)-CX}6^aMnM%Akw3r}U}+>a z(Wh?+bn=VpvGQeiq4M3-W_JBRIoM@L$SBK}$b5#haS{_E0+;dcq=JZ)M|X7Aj&R!< zKS}HJejLye`D=o2uJMO)s8(yG`^IDnur#!9r2aVC+q4Levys35Xks~jX!zp|6`7;9 zG!Rfv&uD2`vhE=Y$kAe70A8Y>TDv@o$d{M+)NdX$t1B)Fq}~Di3O=D~{JkhIMVp{? zTSX;W+MVJTTtSCAkuDiEy35>VTseF6m*O;+tT&q~B+zh?8rr6>B*`UHZ<*Z3rW<8m<{yQW`IiX7Q<;M#~s22Uvu5)`7cB>A0?~<+F0rg&&S1g=u)47|X4m!S$*>852 zs8D3CXDbZ8Lj4W*f$Q5w8z~X`Uqx4PbKF}<|X~+?wS6vKR zZWXRN-!uLD^eQV_bnQN;MCg~_+ii|}sMoEFt}aZ2wme3iKHkvwmRP{ZmolC{jMfaI zH89uq1~_X*8O=pJ&J^0k^TGT3!^>sOc21{wv_8DN>(Wewi5--6ocv$R1mxg$UlQ zNtw(hO$m^C&t0FotMpIspOoM&z>gHLXI|Dw0D|eThSDgl7U2U-%94my?hx!G`OIG( z!f3o{M{skyOn9|0&(xGnmknp2ZV{WH#m@tIC2l~iFj0MRYXQvHlP)e5kxT?y)>S5q z9#hDLTk#$=0a7PbE~OWB$vWyLJC#5atU-h@YpOGdOERsMZ@#-**d1lRT)czLx80!+ z07o3qWJHzqAr2+Hf1N$0w>t4rQLcziOrtievvPA(r=dszy2#gO2;2i*V*Fw|RDm?l z4TRn5WR|9B)P$z#IID5zE;zJU8*w_`C(He3&HnNsViMFPq`iH@(-eD zeb16RwCB#))k-6$Or6WWwdL7+Ks3760mQLt$+>p|S_kRKdACnbf2tXxdh!YYnYFMi z**%CU<3+bQ|&*Gm)c$J_$?TD zEsQc&2F~Nj*Q@@uQ)C$HlPK)uh5GAQvL{Za?G=pEbn)aP#gI+Fzsh>`7LnqP|8rs{ zNBNbcG=B2$)@(l?>0F&&xy7K1Ho(OsGW|sPo(l8R!_hH?pPxrs@Z-$lfu}Ng_1Q0d zuVWZ0`9-?WezM|N2Rp?}R*?69=@jCcU^v9#!o>in$hZ?=KS@|CUo{3*Jlc8uR2 zoY!cgyk1BdTi(`DtWZ#L?hOW2x3f?>ZSh1KXf!U$3)tqNp!#Upqpj8>f57|*g9#rF z+<=7Ear=}O4Vum$wJU6u4T2SSTU)gs>A7C+fvKT|b{+Jh*?vSRzi0%X#d9ig+|VHm z_6wM!c)(bp5`^&G**wvhNl@yl%3ndZWP?l}B$(thT&D-VkjdbT`PqV**n%LeddUmX ze!{B%$SCepR}cMyAxVJ6V*{AmY|Xs9Wn9EkFY*v9&Xbno)4g?8P-Q_9NMGx(_vmTa z4>lB#kE+X!V3vwgY^KKPWZ^3E`uc4@lj zxEGrP)Nl5$`%r-Zh#6oJyS5~7uY!^+d|`X2f8Hp;Kd)rq-uZ=`c66p?kQU~n60i7h zB`NNYxH*uqG!b)J8>|)o0=4dhRM%}qvneV-%S@A!{>dtkG1iWJ#j;v(^-+B61|YtU z+*AHLZ88X5Tafv!zCi!Z9se)QQ2(U7{~hI2`=8})--!^{eH>WVa|L~h?Jnyw6U_%0 zfir6+8lv&4GFlu-pHJ2YuOjkuKhH&K&9}Vot;l2cz5AkwtIo#g=iirD8Yqcy0adMy z|A?nwuK+DmL;Q7*Sx`~?;LMwKg32oNEL^db@YEB1o%wUfF1<9lkl#lzu#R-MPp1cA z*aog3InyL6sjqzx3;qFW@F`PsNvYBn+{ZkSbJB@CGRRset-oj{r_`LEcP0fEwu{$A z*9++(SBX11%)6Ut7x@;E;FBv4AV|y=TQ4R%%}5stl9&fkc~}o?#`))72P~sbsD>TN zNVqw)3{?d&zx1&{G{BoYIH-+o?vSm1?ilZDXaV^N!z5ZRFi+pdC$U_EmT;G=!&;X0 zqji;Uj?>+J@!>~9y3M2Ryr=A9v0(p&s|ov}8{U`h2yEs zt&S-Sg=l-+(;_bk4{Xi$&06t|a-<2UDh+gZwASbf1$6dUddE8X&w5W9&cU4rc90qX5L6Ha{3ZA5KpD!#%>q-sJ)=}RVS`Jym0rDA zXIyMcyG~~^JCMq|8{UD0qNZ`O*`7Xrk(@M%P&LX#zZLm|TA_jw{74#tFEBSK0Ky8x zBg;14_r~$#`1WiM7lW# zUOX0j1OUWo%!TS1d=~HkyL5mj5mAM>qt)^Ki|sIC^ju1#HdJ>3{5%AY$&_f>LFLk! zkaST133#-n^~96wliEcZu+v}{4-P<7ClDd&SBN`37A`R7)~f_lw#av$Lt-ox^b(i} z4%NQ+Q#VPAp9G#}J)H0)Z-czapHlAVwOb8m}AcWCy*j>^=^qXV52z@HdO1|1BDAexj5#@5cu;k4^%gU|Y;EF~#Tg*g{DZ78*CC08=F7mr4E~T9hs^#kV z@(gbB-?X(FGcLr0TRpp(c4aQ~UfyHtGm=DEJ{{j(F99f^Ep0)+m^({~BX5`c*(Z#% zp1&;EJEzryR$s%wF4$^A|3q+d!Tnx_w4`gO>T?)ign4js@pb)Z`~y#J>$p&RJ_MlB z@h}!j)vu-aWv!iyVuVvZS)&W(4lG4yL}hJYt_W3F&mS23XX$B7e%+e;t^JN2i8>S~ z_PKF98WM0J+oAy4Y$ALy^yUx!H1eBZ`qamm)OSiB!Ts^s;&S8Hhgf@;RnI25@72k` zs^pq%FsL*fo zn#ara<2_hnL#_5qKY6tU|1aQQ4h3Z5m{r|k)7KG!?@1W`O+z=xqwk$iXBHmG`6!@}9^%C=U z+9$k#@THSK^>5BMF~+cN17<~Pt9`Mgx%f~vguZ7%-!ht6YaWx_zq8v;!I~5;xY)jqV z(<0%_7{{uAeC2~&*jNPzBwUZEHz%zs8jIXcyH{kNyf)zXD)T}K)L!nVj%04Cdym-& zJ;w9%>JD5~2&p>`J@)cgK0u!lLD5`8x{o%v_esl0t+8c3I~7O~FnRR@SQK~a6bxgt z@=XG;qp2QIRc>%c^yOW59ao24v)qO@vNN;K0@A(n#%keQpoS(*NlJB*fcH;`1 zoI|#sV_+kkM&o0sL(RPXN#uw|w=7&%%akZz)p~SL{-7-f{J7S3|RB&E)Q_nWZ4%nhE+6JOT9Y!IBHF#s zT_xd1)QC9$*!0`h$=gy{Llb4+pGkadaC9Is@xLbVzv?8s;R65qBg@S5r-7mK@AM4+ z=nDQj9YbdZ+A${8|19{d5rCsf0G!3d0?yL?XGP|}?r!Gp>gMF|4+%qS6GwCVzua6( zG&iDw`h`H+Bgt2&wFEXTYokvz^utYI0nYTV%`Mo2gjDT|vT@{B4$|*Ep7$o>iuEPR zJuA4AF_r;$*@^S)?+bQhMk7v0NJb-8CB*9`E(&ru4fgDexU?AchoVvzexRqrv|PQK za9$)j2l6Q^Sv^-4$CIN>**;f!Y?FfpQ&t$j3THqUWm-!Q)l?339+m?aX2bFbrGMwD zfoas4@CKXHD({Y&vby(PRvzHkUst|CU5&3%0fvDv?2URKpM3zL2i11TiDhQ%0ETrd z&9#5|dpVv6;GA{c{2gr}+a3L89&fI0RlgEzxyV}L1raQ4<~c(R^%T;{0<2wgLS{JW zjUw?V1EeN%CftPY z@{UQtlp(SzlS5^Z5|RQNx`L`CM?S80w4~~n`R(4r{mX5#r?Y7 z^|*dRUN_pM>09Jz&#V_f$8twl8xuY_GHgULOF8E^C501k??ynLOT2*Dh4X@oJvAJs z3;6J_f%lEF>G@j2ZN=-+?{Yjp9Y>WCg|Ar8Xw#(hCQ-A~-Mx1l#XRE~e+MYPh{nEu zJY!7>FlayaAhtT6m9bc?#*O@4q7`J}J+&H`&NP98;C#T(e_~)eBPS>r$WyHO4d2lj3AZNmtF0fGI{xnXWd1R2kI`S)95&cC; zV!K3w;YHKR@Pz{sGy+N|)z`MlXPj|cne!U*U$J-6uX2(cW7t_f#qNmIDh)ll-GYM@{=>=V{9`d|}c? z*F20E(<G2hj^_g9Lw$<`8|diE2?<{0doe?B_Sf+ zVpcv8&CR!gAu?N)whMQ!OezWKL#C8^sf+bAv5dfB`V|FbqRx9t=pyR_F=4zgq?H&F zi~YNzbc+7eC-OP!DB3EXG}0f5#ople%A#DyK9wS(MI))-`bY;=+?CZ<9ph(sbOONI zIO30|OYCtQgW<7h%@na|0U&DVo{u~7%XX%F5MVGUsTkyU!~47j1MXR1Rl?a&nFB}h zM4Me5572Miz7u6&LKb8lH8XDM`iuK->Q*EP?bh&vOB<1h*}Cgi5-2fnK6`KypK}Wj zpz>tx3EpC*jP~-LP)M^B|1n1vv z&bmj9?NkE5isXtvF@HkV$f-zxo7KHr^h8}QTwa8z>*104)Uv?Ga7x?8?AJG2!=gtT z=}V46#zZP0ly?|2ZNGIkM^iM-Lx5YNl6{xd$=QPF8tL6;c!9H z<6;`aqeO!t#FvKu6|e+1CNKu;kS)%O!Es!XG?*HY9 z$?o=iP~O!jyK~wV@03-r<+G?x?9OpZ;%-b_ETXJPt55{P&Zll=AO1|*(c|bO6h<)M zu>Wouv^MQO?i#=7OopUhlMY`aHMAu7{7b$sYh4pN;dg@K6u@5w?BWHnX8Und*LM0) z+Eqb863}(YS5@!*wnCy4P`H%O{q+WKJJ8Xo0^adCjjuUMs~-h%F4b2Tl*IKx5R5@I z08|Q@Y_qoR3P~KM$%CS0A3JYV()PqDv*4TM1j!v2MSo-G&zZYNN;}0Qc&Fc6WB6-_ zE>4UsuIxchtpFvo4~E3zfthOKGdxCQ1wA2L%;eMKwP6`us!@q}Wbv#X-RQ~N6dm&J zllm`%xIfaFzDxhqv%5+33XIs~hE4T@$D@VjXO>V2^*YAO$Had<{P8gjt^v0to&B^g zH2poDfj`;uGwJ(#25RhHD~I>XA-yJYb52XX#?M0cs3Jh6h@kAEGnm4Vn%iDfl?2^J zfNSA6PEU@+3!7O*JA?I4JE_Fm93A04MT$@1zaFyRD_67(0*OoCzH(oOuzs(OuB(gtbA5(9y?99`8faE6qmpJm zj3xIqlgr2{T$eFDueGWvn1Ht^RqTnn&GnBNEizS6b%c{*rG(utm{r9^fT7(%4T>6EvB#jc$Denp}++YrM~cybag%pT8U zKTdg>i@69)EW4 z0eMmB>)FORoc!WJ??GnG0ay{$fT8fyNJX^h2 z)vz2^_<2rV&M*!11eX4>dtK-^LI%|1@m-tVamYj(VTaMmcIt~lysK5_~FT|<~ z{BF)@#!;`4cOQor9+t=J1!MvDFu<1QQLYpJ6?p96ylkqO#(-ICTSNiZCuYS1+3 z$a%wS`@K&`Avigf3f_SO*c?w|algs?aH=Hap5>k~*B79TxBx~w^u^#MHbS3`h=Rld z!-Of?j()*O9Pw4w=}cZrjWmLzgX=?`f@pd+VoynlSfluj#Lu1nC@p~bY3G;?v)yZb z^i9MaLuJfVt*Y#~hC~W;3q#?wx%G!?%!ogU7f8`b%%J<{j5NHceaAM$U-wcwyRi`W z0Ccy{PYDLbu)KgI7oiaHVm8m-5{b>upoPvpN_V9^Wjc7_&2EVFl!n246s?jSDD%+* zXIJ&ppy8KtmuOzJO9cC+x-0&#H|<%VF4R;VHCBZtvVyGFJ|1Orp3uBBMibO*oRBF? z0HW!<_;fzoYjMPS1(}z}`Hi0N)~hQky#PrY(T!M_X)plmt_krEbM1bn%OhRSv3W$| zEqeSqFXZEb^*!PN0^I9I`sh0Q9gkf zY#$Q;6~sN->OO7z!Lw%KLe+#H@y=+QA>7n2$TG-1*p(+^4^CoocPZ=E`;1ecw@9Ts z%=9bSk8ar2Vi%7}_4^=U``0{0IVHS)>iL&etJ447EiSN@=D`(MXBh)C$6xUA-xVAG zNF4v*F$0rFtg4>e05f{PbS?Kngd+AUXc&ug9T!xT6DgU4;yCZG# z0mC!nQ4*6(5`tEk<2IoYZzH(fV6{S0D2q_Rmm(zW5_nb=qJhZaJ^2z;-^J? zPSjN67z{%syA9-Sm0nAdSjI z+g;ftE#d;*<-qpAVm4hz_<|(?X!l99E2ohD{w+(3wixDZm|`C5xPHM&X@q4FRLh3i z`{#Rhm-Y@O=4TNGr4b)DnA?=Q(f6y5K*JK85&t5DaNH8kUsGs`a5+sI+z&k-7%o9W zhUOl}EVe__0()yN183v*SG??E=wBcC$(e?{&&@GEJXs9mpVv&0dV+~K_Ni22P(}j8 zpV5Cl-`1Q6dBv*RBnD9obpO+_)SpwS^L}>FOW?}Y08gcO{}XfZe^%~)OgF04)@;|A z|Ed)O>IWo=jHO8!U{1uwOA-8aFpF0A^7B*GWwL7uh>Q&EFIHtRbqleKEDyhCx=Ko@ zth3J2!feghQZV#W^;I!M1t)Bl6R<1vc>0DYfcXK+Qa|?=tH<$NHVor)Su+xqJvHG& z<7~Y`=ho|5B(P4Nu%UHEgSeIPqNn{{EyBT_gkR$jz(Q0&BSYKw|57XV#3|Vmy8V%F z`!Hf5pM{nJ{-!fm@DCX&%3E8LzFov4k5&PAIma^EV^n8Pu@Y< zGDZ$y&grh?Mclo3jp{M6aS+d+T}xOrL9&b~u}p^F(MEr#1su?Cp;X2@^b;L>oxO=U zpKGc2K3y1go%JQ2ah9tua0%M#8~?5ySjaI)jU=0 zrBKs^33iV!0cE3CW%O%m`f}OG5~g>jPXYmOm$CAhX$+3UC75j>kp_PgtVLgU*gX0` zeqAG_a>7Mpv6CIGe{mfHmwP-qnw*y4af9iY(19aF_;Z>NymPo4nn6kKkXCt~5=G^d zs#q;N^R~<8rN?a2U<*xp3|s!)ISv{UgKbNvMsHR8L?*)C=E05Oh5&z+kQ@?q=2IN& zA&x>tbPE=e+*9_`_3;%XzoSt~@EgRxLW}OE`6F5&sGCZjPjDK9fB!=MBnb$D2oa#(yyLqqc!^l!3?{vX1XV!)kx z1w2C){!csgpT6>6U|g)Kh+_y)^6nLbE>Al24Wt-Htmwo%K(ZWvIc?zepvzla+fWs<(RR=({v$9Uq z+DsVcLQZSZ=ukX-g(>#+ixb!jPcVQcxIv+}+FYC~Yp_tAN|40~QO3}oDV z+`qZ-5*G60`lEOTO|D8<)|Of}6M|g>@%5cUWqN1U)=?@krGN zfLnsl5leo$a1N_Ak;Pf|8$4jDPF|vMPeXR1*^n%h(|<5JsNvN0(*4S4 zhV^mB#0qE8yOIT*hhtoG75~Ko>kI?Q*m=-0`q{qx_#@|s%UC6EtYH%2MlBfnv?AS^ zn)pl!baM@aM3q>x-VrWy1-3t@Ko-(1!Rz0)z#cr0785gYK`ektU;O{mg8ai~Ej3BO zF+><%^6_iGsYEe5l<{JthG%MpAu+Bc|D-xtsacU>&tVHVMMdzB{;icm0pjU9tj1Y# zb7q8$C<+`pWgN+wuQ>b^UT{+qZ?nx}xqu^`L$t+Jdy)kPilDvuy8s}$VrsvXQjXVb z)%luI*H#gxhtE6{$|&OA4Bkn>SuS?hUUGD{?Hvt$oc^_X0(;9m23f|4KF-fJt|ehhO@5dbz- zRqm9WJr8-Gki3*phd#|}9g3@XARM*)MYPhtTBh_6Rlkn4>V2!%tB)OST|!9N{y6 z%U$m;8C8feN;>mH%zdm>YHnU0peAY%2K7__FE9y?K<#;15fL2{rXJ>5*}lzXkBaVgva;@ZW3!{ZG)s!O7gh{=b!7-Id#H!B~(lj_5*FSHkm=po>Rm zDRO*SW~ZV+3Gig>&U@k!Yx_ttC7G{ge921QV$gVQB-x-C@k3Hj1K&~AP#AT``#w$3 z_?VcIhQU>$@#fYgraGNgb!*9Dbs38SYF}mb325CZ3<^1qM-lQ4SWT$yIj7((PiY7T zU)#vRy^e*{f^WVYF+|p@3%)q^(1lNRfPr8~@I+tER6TI@44{4Cz0FHPz*}9$wrmdZ zFT?K5x>j5$%>`*_MlTQkF7c+}PNrI^zGJ8`$>M+G23DI+ZlG3&kSqFa!A$pr1S!fD z@8NGlriwUeAY;IAiiG{&2JQc&6Y$5S{m*fY#+K7AH*&zV5lbObDJoujnXl*=m{c1^ zBV`T77byd9tN@!LFIq70cfC-J7CVS6K7A_>=uSuET!XhE5I+7SpLOyXA zbQ&Vn5!mF7Q(#E80=RuqhX_9?&$XmeVbN$(8ty1X${y z+ehnF^RHuy;wVddVbZ;BoI2CD`zBO;>8uR4rA*~@YCSilbT#!I8t;R?WpJ|$PQ6J! zU7wj*-YJ7qSqn&=0An=HZ0hT=lR;$_4sYbsr=B*fH~w? z>|qFO+Wo$gYQkDW8~oZzD2j%QTb=2a%gz&CnUjXRgqF`wpOap`g8)1YwdzMl@sRt%^Zw2&aK+A@xN5=>(MPDB+m*> zwR+XR?b*!>hhup8YvCR3)c_`Kf+#~xhD|eQv-rO&^@M3reOhsBX+|8>I8qL+2FvUGJb-cx4O zJ#n>VOB~YoxkO6CSwkL)-NPZe6*C4JvC!3VfoOZ=Bfh;W+~S3J?;3EDFTbg}tjgl5 z$63QsWG43l2GMC~Z&A2aTf+}R;PM{)>fZVY?rB|PgYz1n?qENup-J?Td`&fIzyW*R zUC$n`)`k&QOoNDK7(R*LmOy%2huqo;!9cjP8Zz=aMd6xAI~vB3pE>RUs~J3)=#)x4 zFjT`%<~wE8CMff$Q3IH^6s5Q4UUNw_OQBI8qfp)_&>O8f9F^&J;@G#J8Y(=YhHr?Q z|4OdInctwJTSO3Ba(E;Yh4E>s7A4qsh;BSOc?dwfZNE2#+rpHfQP^FIn6!A_Jl|%^ z>^1G+)!wr?W!IS|>^_#>*YmH*y?e0)9tQ-N3$T)}@$j|&O?ma_K>OcyPu}B&1CZg3FhFSfa^?DDAg>k7Q#wgUFJ83 z=)oXqJ*vjCQskP-dWk)Nf79ojabf`LR$;9yXGg14D+T)*^-X%Qtl%k?l-ur8j$HU@ zW~rKc^$C(sg)i*PoK5PIXuT&Vn|cs0Z5Z9r6Z3iO?1AEv1}0?eC|km0)K;h5sPB|L5;2Zu6? z9Y&=shBQ}*NE=8l)fuY;bFLfv&pPD-O#n_IjGvoe=G{_ad{o0rhD|#claEuYBFeaA zp$E{F8v$TwTJ>04Cguw3gjQkmeNi-R+Q^S#?7(6Kl(Bb3Yo8+Tl8LESZ~{^**C6CW zv24G-=c7zGf{+hVo9iXTnB^h#ilQwKCkUX_YR5er%z8RzcUP$R;cEVb%>dZ=Cv*nIkCNJEF% zaV+5iQ2&IZ3SZ=+&)wk04@VXsu#j2~uu~u0W!E=&43QbS6Pf1sH>Y4s8~+de{gg z#_D_ct^-|}&DoiK5Pc()`ZAzOtNIfTBaTtkW9;*0Q+XLYBLHQ zUl&Q-(!DaXkR}2)s^q?GM=QusT4Ea~7;1-&=|M}7^@9eOuGN>kG{sYV%Ga$Rd#VAuDs2AkvsDXc@jN)Ad9RXIFHJ|TjKMh#JdUCCqwqWbPUila7pQ*? zNtw~cV;BlxERq4QzreK3zsdFf+ zFqs&x?AW6N)szJCXB8p}N|@_<%(-5=0=>{%eyjPUl2)q|C%8F<$;vWBXAKS%{sP63 zn9(3DACSW;X$cFJZ}}5dhb*!*=6(yE$JDyx^kbrEClxMaldt7&=@?#zX1t2LZxq1?qH$FR2QuY@@QU zFapzZaHZ&P#|H=!;K@Gy4&$xQPea(+;XDhf#pNj{jof4C4i|^i^taH`dmg;spjW@! zTMaN5q?y_+k1REW#gw&ExSQr|^JTY6iA25-*J~PJm0GYFPv9`HpIb)b&H#)`o8Wne zpk4Ajj+s)C`EEr!^Wposgy4M*{S>~JE>cl%5pe{tz`@HWnq__E%uZ^4C6LE&PNz-n zG0eX;?ujLTkk^>3x5XnN(%TNuYx^i*y%>>;2GqMtQnMO0#BP zhPZrN$dYGN;0TmwrYt++O77Jj9j52%YyGCQ4f}_%ljTkC)nspd(;rD37|GJ1YGIt_loAI zcs=aKPd}ktJGH4E&ij~kI6oV=gTTFi;>%tU>YCmYTRfK+n}TeE+Rn5!ilVc!@{XsD#wu36^yj_dO42589wiOZlShizXU$bbagQs~aMbA7~ht zquP?((s|>dUr69-F{TQ6RQJ4$aP2euG~WP)85I=q=`@JQCZl1^X@6 zNpI?mtCj#pkX(+Hnzq_MpPtrtNK5#qA%`k=H#Mvzw(kuq{@ssl<#_qJ98Q#E(;RlVKSrb%j>~tK7j)yXs%YKv-YW}XF z<5zl=8?l%cdCfN0dQh6JP9pkLlMZqA)Aqt*cecA~Bj7>r?64>u$#w4h#WpQnv5SE# zBW+c#+xlo1FN;+s-Ey6I-;Oc+SGz`LL5rl{-;O*i?&i1Ofs+qZB>xxf$^ZSNKfC|& zxCTCI6Nf+M9d5wV>%SlLqK;AgUh`j4@*YIw5V{wapb)S{-S;!&7@%3>oYJ#U!^qZ= z&6y)fq_Z7oK93z%meR6?^glN{3WeW|^#BXb+;`bMP*ap<$o7n$A2naU9_%G`lCG68R;a)n`9bzAZ_D z&>_)=;5}}9kb)6wJ|X8q3ENK^7d1Q-+D2Kel61#@zs!uz^D>}) zWX}ypz(JgX{v$6MuxB=Md{iW#lB;s5mP?nZ-L9l+ZZi_IFTs(i-_WF7Rkfc6rbr!j}EBokeUcCGYf*Gb|xwQ$7+qgwq0 zI32C#fa-O;^0!Rd6|o}GT=Nd~^(cVViv^vjwYEbF?b*qNgD$zdc~hL~jaT+(3S0M% z%4gm6rNX5ASHOsE%QC^UX2t86ljCrS9z$e0vLGjdt&T)Vv7b{O{sDZB* z`e|#Dq@8y9<6(!1E@u33mJ1Xhh&=jgbogX+_|0nwtB3y6^g&99UNrk}g>y4#nzq!a z{zcy#><-I|8Q8*#NM?b^zT(w`()ok|YW~3y@bU?q78bdmNt4i7|1XNeD*@&#`G(X z5*NmtpXXp2;m4~n<_M~HnAN3`kvU^C5V=Wp}1m;1{0OT(vg9=$Lyv zMkrR4lX)O0F6IJDF~q;4k2Z@RO(h@~F90b(qRoR0k>PVKnM5AvWo)V^{iW58&OPHE z*f-6wCM+smOXE`1=B{r9a0pd#9k6RgC$&PjLZ-y#woALlH)P8#B}MQ#w=le1NO=Y) zAJqD;#tzOBWv1BQkcVok$Oaaq}L>uh%V5i(W9dZ z6#SLoN{?MB{eJU>CCwkMY>p*5pTMBQ$2>z>Ss~#~%OeQx8@s(}>`=>hyd1pardmS@ zZi8Y&Gg#ZE3jD9OZDR^hF4D_gTM;#x)z-fy6*?;`Od9t00Zwr{gS56^>pJ?fD09bQ zOU0zt(^_?xxyycVz^|HyA;f%P=lf{wncZ~gqJ?g!_%;A;A;1{Oz8jlLPW20+)u+YX z+WXO%qZNw48uD~>qw%iWy}6cNWEY|7i2C#B&KNi9q%p;C6BKv=*ZV3w+qy;LeHkvo z8@EPRqD$v|7+`9C|N9_I!}nktIg9cJ*Sn>Yi1?d`QX(z^0kUI*h)tc0tVVps4{(}L z%}Qylw{k|dZ*M9ZP`$h+=hj-mWpE!e^{lKA0$#5MLlnI^U?=e6V6FGqq40oi)Kj(* z_JH}}gWIVy`Duv?@j5xdg5d&{pyr{0oQaR-g`?KxK0p^e#dzt;9(==*EiAfk-@4){ zhnb?vi@mps|FTZp+^g`8$3AD;o#_GfEHsu6$0}YCj&_)QK?WWD2%~*qWqr zL|jLt_ZGw&q3VmB5{Fk3AnfTDN~Mbqt)bAhfY!s#7*w76)yURHcON3(h1W-1f@%jQOC;=5le@a@D%Sd(1tM)v0 zdlW*SN0zEf&7(8JPWM>G;+(f|SiHKqe33-MIEk0G+ePeHPGBQ6anWK_U%u$)8Jrn1 zssXyk>(xFb67+}{0)F|Oq9*1Fle-bF@R+kE(yoa`Dc2YeF)(@Lnt+Dqj9i!QJL!LU zF2;oD_9nPrf6k&f?C;w*ZGl>l7{b5srbW=r3h^K1cmN3c7bogrN-qG3@sJIiMhzd_YAjS14 z@xY%x4jv3^Ol^WcY#VO%`*k4Yzd_&V;rvdop zvkDfgNYH}O31~QknpK{6nGglejepa=T>3p0lFOmRhNZZ6vd|Fne0PeFVyjjhfnff= z^EC*5iz<_Y6&b-O8p;=qkS;Anlv6BTvmieH9M9Y^-?S-EBY}@kL zcn%8;`wm-VNbZ$CqBz(0x!&XSMmNG&po(}i;U90lZ3A7$nq`JPs3#vN5f5K-A44Q( zm$T);v$v}(eZlK1J~gf?GB-*U<2^->RQKSu%mH3L;V_%{5X6_FX`4FM;@=gWxSa<% z%#zfDHG^o;f$^buUA`^tp=wg1@nITJH@kpMC0F`}jzNgb;1%dwKyLI|=OToi2g z8Vqm~^cc^&Z>Kmu)ozFE@@z@S%GxDdqDhZ0mt=C;pe>hl{-pD>3QpEfHXNh{JxelD zamnqoVD<-M0y?Y%)DR!+E*nE7^O;-2EDkT~AdL=CU`JZa)E`rMwz}!pMelcC>V03gsd}Q_?8%T& z+=0<1!_|9Q9cZDnzRFwAcP1E0nz1U><*8%Wl5=8)@h36OC%3j1IfR@=LU}4xb$cG> zNFl{`;iA=$W+DB~(46Qm6vlEYMIR$PRFK49oy`8uh5@bJK)WfcWfLk!7#){XaTfd68JGI)H<_fl1J*h5BVrtwx#&+WMC#QWnnxiNExp9 zF#V#8{OvdoePJQ+wG#yq23-U6YH2g-Qw>E0rA%%=s(_njP3T7N!7yG;C}$)SHxX?* z`eUxY&Z(V#nRgO7Lh}p^on|UmYSNU|qv)?=7j9pOzFbOSBm@_WdjWtdS*4-(Pa1Hu zJ{0417e;~w5Z0R91_*%ovMMXq`iP(O@IqyMw zxU6%_NfMNcX3wMgpg!mns76PULPpuyc9TQE3(C^1QnpZxCelWso)oaS7Dnq*(vC{x zFwo5pYp5OhlFE27chLdh1@C$dp9T9$@4oE&CE`FibgPNP#Cl9mO#wDU(dcI&BWj~+?(YV+y+IM6g7=Lph-N|&APvv@6B2&s5q#C&7c;$MAyE8^L$2D4gX+6Cux9jm65z! zr{kNoSTZX&UiKa{d`HID&Sbgv;4phrCk&eA(c+t(MTOxiMBnrjuuE?SU5BE zuxs7vnlKk_;gFi zh#KQAj^>jtjQv~FZ-H;qH|=Jo>vz&U>%?n`=o{L9Aszx2`m3ETf;%3?#?;g5j-?@J?*6$o;S=C_SNs4DFSckR_Gj+a3;a9v(1 z`(K(#Q>Nz6R~=*$T`Q_tmJTzE!lu!kT!Pa#UJG9Qf_pal`{x2m68%lRd0G-Ti53#| zN+kGz_~D{iMNFLN!VSYK3UL%P9UbXOuUxFv^77eo+}Hf5^>@YSak)gJb}0sLcs)Wu zpCzZRrt(a8-A7}pIKJ@y*%j&5xsPIcO1wfumkTTz3;!p}lI{0EKf_?l5_Ft& zsE4~jIx(`5#CKCUDZ4K&fDD$cd6spr^3MK~DB&$VZ?$HI2k-hStr*fG?gQEHW8NeH zf(pyc+5$LCvM8MSTA1Wai3S4_lqB}B6?oqIlu3=bsM$(G=0v*(BwvI5K$F?@OmUFs z;1bajcT&?XmJpux{NTBIse%+|hNv#wtmRgiIqR9D?I~@sAv5_9F`YLjXMeX{^D-s z>Y+A$Xf7CY$wc6ip=qHVqu+F)Bt0)Kz>6bWMEs%03-7xczjOtDhY3%+gJ%{nD-Ols zfm0kSnTbfeQ>-vwuAQXJH>Ht{@~|;bJmb;7M*YYFWpIO)yMj>C4hyuRayU-{z*T`a zCxrG?YhqDrk3LW575(aUv~6umhmqTjmz8k%ae849)fDU@SCWIV-0R#J9HqY;`JU?S zmuf~c;XSq))H(EG+1r(;QfA5#BP!I?x*v1K`5hzA1IE8Nt6aY1gzTxj5#0U(t%&qN z=;pC>`y3%SMSSb8~zqiSjcgps3QF zl!mR51#TP0UW##3-m-Y`s15GeT8%~RaOZTW{+W9^XaJ)~bZw3JZ#+$(Ev8Uj-#4W*5VxR{63j*C_6uTlMM+D;`XBae62iMj}HBayGOb zU1+kO}BoxvN!m(KA%^YUyR|yTdpZRP8fBd|`kx}rq(2W#MI@0F(0WU|q z1T6~%%Z#slVDn-bA$!)XvqY4v;zt>DKa|WfN8(~q)9t#K#h*t)0O-j#0=sYB8Z+3s zKD55BiS&)0?B*9gq%J}8-59}rvhlM$L0*i_Z%>@FA=GJqGrr((^U7AX9!UYHZ+jk& zY$Otd5N=-iO&f=0n{=Jse2j3_vDzQtVYb$^on3YAjHT_M0BzT8c6p%#r$SwSdOpbYdkfbXB@& zYUS~Ld~Is=O3{IU;M;v>9bL6tQ^s`VC(wGfgy|dgty1Qrk_@Z3$ZJ`v zlTg^=pLA@rhyu-tE4=!_wb^0(O6QF>5H19X7z6xsX;CZq5P&<rAinC>3uv| zVZQ}4iH-3qKQ1Yhd+051D%Z5wO}H!7LH+WmHn^t3<-zLZ)$CP3k%)7L#8k;tZ&4K-*e_rrWo*)H z65HK%JGw}h^`U8vr#Wrfje6LIz*oPmhNGjmUUgi(Z4g8cnvzlYVM=K2nSO^34nakz z^F`Dk3uCQoTZIJlp;1%0V0J*?gVfgZdPBiLlqsH|gSv0yp9kGn z>(f<>RTa`Uz?e`k+@PUI0mF&2LTcUXxI1tNJ{o(PiTmv&5tQs)dvQs)SgAzZL@lQt zDFA+WX|hm%Rr9JPf_$B2qwq_srCbq1iWde4ZI($9G;IbDl}8^|$>p>KA&kYt19FjR zV)wJFgHwAO)WxNg)tk_!3W;vSfx`yS&3z#vmL^a@TYrEROeaE1qLK;gZi?npu8^#s zBz&j}NoLGrGNrRL*eO!26Zr$qiErwJ=4+8Ync5L?pJX2i>cq~z1#gq{Q2w@@*X@Y9 zUXum-E{q-^dG@}lT8a+acDyQi3MoVaTWNZTmkc)xf9muFhFlFd=$mSYbsuGBDbuhqeUOuT%Iv3JurDSb4Hg@o9vA(QpH~Y%K*Ei`0V#zk4}87dg(X}?x9Cs zyU~*Kqgv-rQK!V}d(Of};1!2RV76=PSGwqg37Luo4tKEn!@e%u8Uh}r$ng%2Yd+IY z19TXEq?_tlsZpZ%~Xfyr8s;tH{017bQZuER|CzSqW-kj6@tjKl5t+>aj;L^v>?chY}@3{+k(~L|?0o-z1Upsw&J)G~lj0Ok-Ae26M+PL-V zk+7_4w{kzO4`~JbT|HjV?hQLX4o=~J8|CQ#b~_<9yn}DDsSD1ylIY9%ZA1ZZHmG9g zjlX_5Et&7xQ?=A{)Pdddb#ZSnnI?#@SFAFPLa!3!p|Rd2an8xqD|gh3Or(b;^D$zX4Te5z!JG zdha%!`q~B$x}B|MPokSqV$P9iP*w91plY^4{!LWyIC!G2a($my;(e=F6&SgERbTG^k09(lix~+Z*ul-1m_x74D4Q|p#fe70dTJ~1LC)C*dti+C z9c5h_4}-m_4q({b8K*x4N5)?j*_UF_kZN^Ob6sd{lOhz2YD&qV;S^ck z?WuY~c1wRc|G}+30Pf4dSCmhp9p5@Cq=_|>2dk=5Lnh z?e?_q$64sWpGrOw2eHfAeyHeZRhVh^_uA#qtUn2NleIIe)m70W98y%`HA-818yAj^ z*Ywg)Tm8kSzL+2GIrB10^{NZ(N_5hS8%Pnb|0-;$3-i4G|> z;bY(sTwv2#sUh56`^)f7QblGNJ(KkiXpz=e$FeP=Y7RoIE|P$XU$}fUJZKs^>73Z; zr?qvLA<2-C2Y|B$@yV2_*E6megh&l3lYqpJ?W{h7%!rs>(^GX;irArpqHS@9vrDtz zT*a%TQ+{VuduE7&j*;}~NBk+$Hht%lmzwZxuunEqbyd5f3(`3p}!Rs{vXEPDZ27*Tl28 z+ZEe(>g3&ff2ZxWzI9qV7qiXVaq%C0^xmJ|e-D*Sbh!m<4284F5r!?QuSyAe%aa>% zi;@-=_mh~Mb29f81}u3 z&?IDEq8_W15;Go=7D_$;jZd1|6#RKI>JO?N>r|kAL;~-n-C-W z{Hr7pT1d+`>$}O<5Tpyxg)GEhc~?eP&~M*4pl%D%OZY(&K9FjoKup?7yM}#*1z4xS z242OzV{3oAH3ipllEe0(M}eD=$0N*vB+_|eq9;JUSxY$7m9jC4_#EZVWe;vzyWpON zT<|HM+d;|b{D{~B^3}k~ks*s@!mhmQxnN=ml{2B)WPOrth;=L-TIwDDx%urc*lL0f z`|IdJ%$9cR~SxgSEHLI@_@T>(5M`L)v&KL)sN za(+g>opA)x?_8bC7|-onFU2BU*}7?1?wIt>an`&6Ze8maAUNu9dFOcqJMaLGFliqo z(~+}T)B;|DYXU-5^W21GkhS5yy(R}F)0p3F4D&le<*Kt~q|0K489AMg zSyoeR*=PmDDp*!K;UPKuh5)3MU`JnDn*_f(-NR0Jxuno`-e_}0dq;W6)FG+ z{1ncqYo><>>iM|%#5Yn>$KKay91ZYnv04R$l=9_o6eUVBugnSs-366WSURi!094qE z+gE!?>cWDCK$dcabpJ&MCseN84^GAxWM84`;qiB#?Y;i3|LD~pur0=Iyx!a;y%LsS zfwrnP*pdVCw2Sn}9E%zB*hJy0)fpm|VR5f z1arsF3J@GW!6Vfn6VP!wo%+~IRZk5ZEE8&5O-07dE7i3lQ3tdgno$wMNgh|xWJRkM z)XnyBM~`2=o_FRt*?K^w45l*uW%oFwBZe{@rVlHP+0XY~aFQ0+3_w=SlMrb+#Bss1 zEfV$uwq!&~)sMK?D*TB!$KLV+&$(6hV~n2h)UZg|I_f!|PX)MeB5>xcQSITCx9zJX z$JLEcXPv8c+g&#yF`Kmb^ymu;*H8lJUaWTy)cTYkF$cEttv-aS!k{($tm39z3CIOX zW$DjbuM|F@1!h%XUo1{E$OC|Sj?Kw?m3NK6gX1EBveDxmJnw z3ncinKOsq*8M}KhF;!-^)7#6W!NQ@)w)%e}pk~yhTzJX8S76#CNYP&Mu&eKbBTE?O zF1gFE)hNa179bjD2_QA?_8;IauztvtU^m6@`|vs^1ltUn*%EVa9hWmi;*zEXfLFEw z@uh#ik`sj|RN_yvMFL4%EEuV&mF`oMyao4QsaYFI>-Hy4-P3l<1#Ak$G2-MT8mVas zfBep&NI(&a?NJd$m3DK%JfwZOjQUuD7n(YFH0v%_o%u8+n$AbH*5El@lFiZfalS+3 zj(wc=nBzDn)b4DJSfDL-_AcK-6`&si<|>_MSNVx|FkC*)`@Eb|>os+TFknx^1VqqA zoQxB)CvpgE35|QN*zT$$@PVbzLI0V_=f@Sa`5Z2whKW9kt1 zIaule9&D3*JWkXyKZq7|#P{k5c%TvlKE3+7BnmrPCBM`3))Y7t)Twf?f-SxR6$U@K zR(T(-%$f10*>46D?w{(~o;SQDavi_L-T#OeITD1(HibwmNckwr=5LKtnh? z$0~CA!bw6WG`7@V?awNslVujt!gPs;f2+>O^yS(<_0CpGDjWt?lM~vI5>hu<#pV$+ zb>;|pLn;{D5KPYm?m|VRt0NO+d1x&wL~_SpP8zw_^>qAh{IPCkALJGzGkjpU{Tej*%g3x28i7kFvr}-q;V93`{OkLP6_`gR9oVUd1b9 zVIXA>6uQSm-6IL#9NQ`zP{`D4z<)+poHNDsK(afA%^_TYo{4IaIq<755YtzgcTdUU z+Je54hqP3lyYxNKbsP}_f!rLTwqr!99dJFyTfnm>3kljXxZvy?Fynm?{5lv#HrnTo z_2EqET;i%3mzbX?{`8!|^OP-=>t?xgpsS~M*YC%cBbXD8F`ALyMTeD`Zz++kp&h_` z3}Pkr^X~#AUFdO?Ule`q<1cgG4y)0*P`1$|O!*Lo4inQn9KC7a!g5fZyiBQ>-STms zAH*L+c*2CF6Yz0I;Q*W8H;866XIcRV$87mw$!BsOLGV;^(Ytx3r5i9ikB^$n2#6+djF_d0Y_w@}LvA z@78rOuZ0T>s-r#zj@Im*XDFyQvXHFoK}Vyb1U;zL37Eoe6eeiXm;%z^_0{FeTl&&XrB zVwQio(#6P-t^-0s@Gy!dxCny9QF{jjNRyKZal9e=b_u|x;4lqdxs!4QJ-PsQsd$NAnn z`oW{6g~;Fn2|&aO4zL~UUn2y2wYd;JZw5nd*vrdQ&uSl)Svz8X=8lp@%=9185+Dvr zVDZM>^6pVvyKxwpth14Fch7gN0k4u3H?lYPfYE) zSiUfH-xr{oa5EfzZvlygIG>^;T`WjUf5x5ducgfdrbol=OJCKbYxfy~dM-tl6o=)0 zi$Gm0Ap+nKHpEU)NJ$T&S@E)^#faCx+CrKFSt;ST!*0w$K3!MNWp2*Ui&tzw@9v@L z^|os8`IEHt{s@r9%RKFd!&31AjLBqAqad11O@5TAn6LqgP6#AVpQHptF5bYFh(};X zeqY}JPrM7560|PH&i~Bn<*oYGMXINi$h&*TaoAF1W@b=X@MN z?L(fTnC{dl3lkUP{gmdWz*h-WObEzXd12HBJUXPRU8Pc{6*}oO=EI+C3mlhF^?O$? z#ObB;Qa>tP9{SS-(!Zh(qaw0U#0AVGmLDfbI~_9>O}U^I@7%IY{U#Q4oW~yLnx*v+ zc|{EeNiBnQzahi^WS%~^S}Zf=@&w=h`>zcJ$^nMTnJ*dj7C!MN>_1Sn{%IyS8rWJH ze{~OvHRNLdv2XgC5l9N6>{Va0@n^)l2gP7qBT;aM1q;BE;>3?old&e`9tB<@&}tPh zGQth63`AzVm>*_hH!{5SU`_>aZ_f;j_k?K>iqJ(hb3cMb*DE706+B*tFVPnE0XeQu zca;D&LGoSt02tbWv3L1f^mOS_Shx%omU1S*#-CucZ*``wi1e|V8LG9X^y8GQHsM<{Xzbs?*zM>oK4M>%e$0AR5tBBvXtuPyZ5i5-rP~+pdF2Wkf5q<)hDQ z#52u^SY$q{S=?Kf0jTmT)b>2z^AP|tIeA&d#->F%6>C{VCSAGK(=sUqHYzm@_4!oC z@eftRON42J0K~laVgy%p3k5&yZc_4zDyPmYjo=o;9G`c6Xm^aOdLLJFD{x{gbxp_6 zZdESw82*B>9_x{y1Z3m!f*jvFHOnD&c89iIB2Hq|DshGXc;??T3nzT8vwQmIwPkaS% z8Jf5@W_2@iul97^oqc7x$G|?0wrbqhOJ6do*Ph)SC2oQH@mGes-I`tNsYwcLy4V!q zyWN~A3;#7lti7fD#r>|jKeOUg==&1^+1(B9AR^zx6vHiOV=jE5Xbw9T3I^D~SQV!l zy$m2WJJf36dV-bqWL1`rb@hp#_go*|nCbQHZ@;VlEHfjr!)h&eZvx`099C99qwON~ zg>k!)EZWc_bYl>KJ3HeRy%aF~#Vzt64bz+O2AU?&59|torzgzd@-`)nXj|$#M&mXy z93_v!$3IpC**oSyrl}1_Rw-khxy7+h1xS9I-6FkE#$7(NIe{eRL4Ai zbWdIH{#KybLz?8a(B7va`>ov?lbY;7)XFaml8OvmEj7Tqf^DwJ;=oZ{TbTL1BOoIp zS`XTR2XTR!f<;h2Mr>JWLG~SHiy=-zj1fjuw)iic;k?|n?biV1w@mIQfX@a*GZ&Zr zYOYC|0sZ}TxHAbv7SMNvM6i#J%WCW1H@T7}_W9@OJ4z*Ig78Dd@ie2A^>iR!WHG)O zEn>V;@c?SbkD)nPQVceQi=3JF7+|4r3<`hjv1bP-~5)x!v83Myf4 z+#GX}oTQI}5llAQqOCP2>yl~D{kMm`=rVN>@^Lq8kjesS8o?rTKPx+X^(~lRG`2sk zGtd%0NK@ctw=+wZKL9_3$HmQ!3+FlPYkLX;wqSD#**(Y(jSvPo7CJ#e`ME}Ns1=9e z)`{_*OL@;e>AD>K?K_c|(|(vSo$lcvYmixsU%p|dHo+O7%;0L=?R6W*ecB^I9F_zE z^!h2lAruhOh(Y~edI z6pA{NFc=^K2(&za=d`dKt=@A^dkQ z_*9L;N(h4{1wiezK5Ii8Z`RaU)GN~luvt6YZOPKqq6lPu&abfPy-ICX-3d4OK`II( zr}&uIi^nGToP&K`tElQBvXruJ+?G$N`?->D+Ie69<0uOKIVT8%k7El9n3(-)dANYs zhJH)&V|H=6L@kA$Zc5H7naZ4t4@cO`V3a2Hlyijd&o@9i9Wq@^CTW3Z)aayWI}GyY z@vSC`Sfun*Ib=uh7^Rj3Y3g0AbXIRep;4(J4s1in-2i?}E~Var+s+e~+gJFt)oDt! z4%3XL$@#neHvUe=vgsrbq`|>QWOY+E--c4m=$A>kB@lP=kAgr#P}{ST zNHZ+#1hyO2b4?2POo|uFGIbYl|P56`u)nf4jR@lVY{6ZTED+`fz+h;436!z0??y;f^n5nt)D@8^-@JJ5{0w}-m z#YT9qSV)DNCNZ;#un)8OZo^k?I)0AekrRPDyu}6$XK=a|=2}Ok(Ec2mD9aexBa6@9 z4n%uJsXK`i=g##7!y( z0Ww7TL2_gsb9qYvRh#_Wlw!Q>t3gaCEjHM3(EzN<0o_?%S5e)OBc>0jkME#%YUNbJ4yklFGbp zp#xngClk(ZU}lTr*7DHlkH`*kqUr^~s(7f@ndd^v01I$JMzhR|E5TgH&_4Hovqog6(fXOHz)YZt#( zExZo%?=7s$e9a8jtatX!Y#Q7&r+xYRoiW=lI^emEz!8>UM2*)B#C#7|i|_D5^*GPS zC>SSo8|om7_B1+{^ecLIYV<~o79(RsyHx()yN#_wZH%u!j`AbnU9zs~$se&PFvotm zEG)0?StmlBjX5_to4%CbMwddq5fqBn3J98#4mbOgeA4Uv8b-X5og-vPX2ha~VOzox z?4-G=1jc71s}!ajepFLnE<~y5g=Hg%(1r#P510ku07?L zvw@O)zYBY5E1o($bFVzm&J==9GyupIy_0E!W!yjUTZ<6YrpnQXRv>YpHbx0<-+z-{ z52s3cMV_8b+=LjaUJ|0oziu*4sa|#05E2nE0h*sGKpW~gv_Wk*O5=*xHso8YMfJMB zG!4=(e1{5%omG92ZG3=C3?YQss6JPWvc|>@snIuQU)|?Rot}1HlnF9>^)`o8CJrLP zZu()vFz+UZnc6er{2?)sZau%4cNX1__N|lEl#?jZTgdYvB|EvBQZw&Y)PlC?Wvxv0 z12DUM-u{?DbWNcMZK7j64|xUPJHSwpzMV-lQPs{CL_OMWT?67`z+c<~!nuWq)%FIt z-GE%=kDr~31`ug3?zYS;v!}FNdP`gsPYty{T%O?K{6O^Otuc1+&Uu(MWj#4diyjYr ziJXx-0x6ve0So>#BrrXV1dQ1w6zwu0%RFd)G7U&eRpk0*?Om4=;NhZ6 z2i_GX%WK!gon*qqkKgaQdg?miZ5S?;^^=-#wZe{TdvdYaX}*e!={;&%-J4iWVRg0E z1F=fyYhQCjLMxU(2vV-Wb_Yb`hn(5QW2PMk3t)O87@6k6UJ4qjDC|LZHK<7XFTGW) z33lVa!6{dpZlK;V!88B!zo~+-(AY_CUy64lNa7Xl|CPiWn;Cs=y8my{n=^78aexFN z^eVmomUj|^FdrV}xtstJpSKKdy)}9JSupJ-f&mUrHXqK+S(fURYGYEx(Tz`Rzyw3T0kpPei#i1pX{pL(<^81@TD-C5bAjIAxwCKnGDij zWSV%+Fn$61Ux~(7o&k6{g*N&U)pJonKsf$Wp7B5D{_nr9Tx0Ub)<2YVzb{HU0J%Q4 z>WxU+=&6G-WRBkDL=Hx>{vg_(a7-HDHh=MPl^t)Y)v~85o6pH`Ce2W=BEfQL_w|z*mRqyn73|kr_H!hn{mHFIwvsRjjM|_`1h5qQ*2#@1n7pdgZYO6 zKA>Dek!{&h&~~A!YH_RsrOk~+ivo-Qh-@4Qf&_zn8{`v-L?0x#M7;IV0=`QECcY?h zlFl-`?u<@B`s&^pDY^SrLT<6)REH3$ROW}nX0v>;7Lj}yq0i|9HC-@=I4ff&Jo(k* zL@f$2u}nD`bd^+w^3bT4vBlq>wK(-p2_PWe+W)8ei9UYv>CXjK)MzYfY+lpV3P3c0wv@4y{T z6|y44WvY%w{+EmxTl;?IUwRA1HsoDEo;V@V{`03WL{A=s-R<8YKaR&qevW$hL-oZD zRE8>ZOi0&zt7c=@pX&)YV5GlQkTpFe3U5qCKn5Plpg?jZl9N-W8Dk0;3xinWpw!LR z2?un7NYi3Mc0c=H+rJ=lpD;&sfCImYoP*OUA*2o4IEH}Za^ltU@2GnFLBSa3IDv@b z)5)eFY3P+$v~;8pg;?^F(SGAHDz6Oa?1>|@i?pQHAhrr3E;lau-cLokdTAVFNu}5M z;d&?#sT+gQJ#DgV3_`vdk6^=*X;a_2Ta_zf_tA|E=)K*r5>v#8M1PRHzy-#ypQZCk zw41kxcJiH1Sd6Jku1zqqL`fRKRz-7|eQz&<-f4r_ueSYeP|u1;hDi}20cpL>lQ|-R zQD)w$2#PD1_`o!k4}Mnj!_Ga(%Zr@t8Rn7i=FbnmBQLk_u!=bLi1Jcov7J-*o(G2* zt{(&)%J9%9_kVDPVzZr`v>br|C{7pVLK+r5Yp7&?-Tv0mb^XpIkct^5S_1e;tQ zE+uzRT!qvc_$71OWEDC+z}}y`?lbm3Zhu zXv%wzcZ;~QHuw#kMeslQ&(N(-Cqp6JOCW1pbJ+vf@Qeo|l~H~BqtkW^x`t@o6N1X% z89?l&A(&Evger81B0LxI%*KSuwJcwRDl`K;oKZ?~Il51(6WD>jBVbx3$l)a*Qwzta zCO`)mI2x=YS{5ctb6aobm2fS&vnTU*-rINC*zJ2+^M7W(Q4#N?e4uo>aMbiwC^j-& zLBNy@xb^0zj$RKeIPomt*JV)pCa-&j9{G{pkoVW^VE0Zze=X#6PU}1ar5uq!G)M~_ zk$N4tZoLZ3s|(4Q?Vl^I*S78t|3dt0sBRH$#m4_~xVXgpXQJxg2cZ5ZPCEh|JUqX` z^o3?$%vYDF`-~pu$k20#32;0n9E2x=dasHRRgfBzKXW^3P96^@!|#Uug;1fDZQ!_f zyAZVZ(X1jG^NfuZb1EA%Z7^-t7(u5`GS6E5jyQ4IX2zRpizq(|Bx$WPf&rd%Hx&fF zjhQA;glHt6G3}>BRA{UP;vF!u1%(eQQEAOrNES~d% z7EUTox&pwUVQ6hrilKfRPXXDaTCa=Btfu;(Ntj^nT`rv2PrNCh#;X_g%0RvGk0b{8 zvdfMpcC-p#_3m0(#~78Ytw$l5XGy@#-ImJJw_A)*&bmWlByZtz(vyj5Pd`FQA|o0rX|0W96dC4Iej0&Bv&hCtvrQY&~6b*+or z#3|pzYC!56w?kb!r>oB2Dc=;?Pk8MRBZtkkYk(%by1vO3i!*-@vAq}fq-#4L%XhJl zfysc(sd)TzTl#Rx6cFc7^Zc~Y4Y~g9{ka$nu^Y}5jrkXlHCq@8*rdA#3Qcu%>42$# zf;j*wEN2c=zbIya0+7}+Et*F>PZqWQ+Vv+!@h%q_9U*K7!;kY9B}55^Lto+g*#WYdu`-f?!7^|wM+k1R^n-O=n)GYi#UB1+r$0m z;mupf%~Zz;1UTmN_tI~RP3;}t6w!vS!_rkRW9bm5YtJj{-QC@wC%U6!i{2*m@o9H$ z2kVa)iM9OnyJ;URAxxY8E?ikZKSaewu%c490_G3Hqp z0mANGuFlcT%o)LszZ%lAx0brb?aU%ptiHEw0>BhsC19?01yN>x`?s+m{2U99ZZIE( z`-7yjbwQpC+rAV*v?cFLIL`7OfUVE-{pZK@ui^K<$=1&z7f$Wk3Wb?+p_?JD zNe5_z%gX*x>77c_xlZ*$vA->aKA2$fZvKRf9zNdcd8W6OTcZq(9Uxmj%BF6%c0(@* z^LMYvF>7V)$eM)XA%A`!ad@zDqJmi7+)~e7r3@F+nQ;p59Nu$lm9WyWl521T{gR}ew@il#dbg^P437CS0wWn=A|$42zg zm6&l_p~t5q42hf2?0OhH$*WP$ny@KP#|LaK4FAiFyjd)3h}*T$SxDQc0_BQ3dyot% z69>ak;H+n0ETkyy;yoV(Pc7rYYZE`+w)%VQbDr_ji#=H0qHC;bgl-YOMW3dXIR-#3 z@zJt8msH>Z#=hX^bP@8rG5iAo>eA8x9W!h5iio-ysJc4Td8eSJkH#B#A-Y03n`E#=kV*TL2zQz(r`sSZT=&Xv@jy&{xcZaE3&&Otp&SwGCo6jScID zyw;-IZcaTc)iU;n-OUt>*onH_lAql?jm4HC>z(m^B@S~jiJJc^Mj+@sc;38(md^fw z!_qpTm15adCnC%sZ5<(BKrR$kcQ09R7Mjj zTG{=;banJ42C-DimsfF}%-gV+P__mstLWvf{*e*gT9lF&-gs)YzBx5a0M|As)uenpEuTUuqjE0_gmwWsm$q)a)=!SV-B*Iv zq?EsLn@U9rgU}Nk40@!-#}95zx=paJ{9;na4~$92#WpQd7Qf`WjyPcFP*1s>CU(eZ zQx!eto!+o^gB&AX4}CBxpQA50!R8Jo12&;Ixv^i|jUcn>l!~J6Qag>>`*OBr%t-)u zm;6%$NI+d%3MOi?_yQ~f7DA3e+z#A{5Xy_X(g!-4RMgDeODPzsr^B3-rhe9RuFyuf zBumYNkQHz7PiOtkITD}?QA7N;!Oq^hdAg8RLaUM4 zn8R4I0EN}Sga3I79}B3ru%e?cpXX36Sdn7bs$O?&Ij@GH`)v=bzCUw<4~_Iy3E2`rBz#wj0dJ^7~JXOtn#6-WKHH7RBA5etrNIz0|&;TJDx2yqcxT zod6ED3^Top$H5;U9DHJ=dNb9P9wuYCT7zoFk8~L6EW&xm-vQGqk!0Mz^$Ot+W{tk{ zpFBcOWuC?PKBZu)o-i^b$b6!VuPwBvFL0xPoVWS8GG0kMP`+$ZDXu9)mQ=~X@CjQ7 zr9To4A(06_V_4`$`0DM8gtMtARyEO7e^?Pn6c>Msu_luM#*v8~o`*~O9LxFA-UaOf-N_fWL6#AgU~+P$cA3qI>LSq60wq4}%baGd?RmBi%iLcP}!zMBS1 zmN+~5Tm`q7EsNov)lB*2=&H5vhRf8n_=-3MMUoBn%|U6@6VRh*QGqFAk|N3+n1_5} zsa4EF2wCI3xjD{(qIrbNA3td+F}{%@Nm+81^avUY%#+Q{v8af)H2fTw5BvFpmtGvx z_Nu(;3Uw?TftUuE9YlIOjY;!(6Ih1LiS}|HK)l| z-Bw0nRZV&g?yp*-U2|@-&LP49xf|F?<(@D@mI!RqL3j-3vg8C0ot%AR4#Mkt>R?Pa zBTLof9e=Jc5rR1HF8t0eu^N)rfYl7)$4RDuGrn@(`fGE6@LU2FdOaI` zAA>D*ujZ0NE`XGgt{IjDaWpIHC8z@M8-dVU?Z@ANh`$dAvvV^#q1{uJ%bpL-)0J3| zz6RnQm!L(3kwf2ZfxNEuMv-t-Fp_9NZ5#FJ}_Qh0naHTZYVT z13`*N_1e=)eI9eNDt?n%Ou40=epcT7@<|V#1~U8SGk4WF&~2Np+9E?K>N1c@N3tT; z^I@n$S9)ODPoouOh?C&9hfLc0c(l?aJ=1OMT{^SJ$8h$D>`l|JPdk8IbOMo zdbpYdF-u`a{I6)!`JU4jaTgO`{k-D!~sGo{tqQcW5g_ z28WoWU#$Rt$^mT)0+T-lJ zU%p-yVT5*tl6QLb$5Xw*EY#q)PQwm-i zjn^&rhVvrpj3s2&mdm%x&d#f>9Y-Gio?ua{wtj6Z;9C?ncg6wz!cnA2I#Sp`U*1eb zpstntRdfQbL139K%X{9)stnljy<@}8liZ}rm6+<38cgBfbbkd`Hs%WSq?#XtRy%9| z`y8E=NnEa0l{~#I3(h_z`=KMwtkk;rUF#~=jVXwWN;A7TF2NBK7*py~+WNqzRpy=} z0sJT%kThhnVboBIqBk|MW(wyUQC~>Hv9pWk6pdz`>bux=$b$A_SvhElofok+o)`Ym zNlFfLqPrKmw~7J(@T5jT+%Q|Z34ML?1D6WPGn`!^Nk(d7|I1d-#rneu4y55#Wd1&&;o$5c(Z7xI8MF=i;X^XcB+B{Wd5M<>(e{r9l$*bw|`GtaW!+#JzsN z0Dk-M>B3KaL@Im#d?*1_r4RnHv|W14*YW4Fy^WcJ*bhc9q}@ur;lYm`aWd?%A#-3n zMDNNH2BRO;9@W{h!PcaOn@=N;mnFu0j_drmtdLvZ>y6P^T!rJFO1hh^)=;pMg#8)3 zPiCQE#jYKEQDF3d!_Cp(j$rh&6F^xV2SkK|Cf@cQtCD~<^$I^Pk^F(?D|6zUxHYuT zVj+O;4;L8h-Gr(1TovLi=4O1D-5XNN9r?7#drue89Q2$+h;Tu9Pmg)gz!%Enr;@*} zsA=ybfl_Vq%2km6;+V0nZ&bd17QRwKS(%d6HvmC&n2QZN>7q~>I*-4(Jx(ArW(~;? z@1h@H>pHo4h~DlY;utb6nbZ}8-`dULh%b(6cc_mxzBiBGef4&%deK^IA%m!`O)R;U zVnOKR6M0k#X{HI?YZ&RWmU#Y2sjqeO_g}A@I%b5B+t<=m!q*1zf27L&U)RmW65!zC zV*7R7{yR@AR{K9Zif~=yFPA?XQnPu!Fs3HGwFZV@Lkyd&vc<6Y{R?y8M+^gS&(QzPw2)rhx)YeUvfl;yc;OG7apdD9E=Uu&@#eg6P=Xy**&UT_@jHuu=~&u9rt*q!J!7 z5+vo`x9&py+@G_LzrN4MiutkICJ^;*Gh~jVWt6#BrC}V)EM006rfMJXUQ`fD=`}hC z>Lo5=wX7;ALM)TTpU#6D5{qOInj(1#kA|ff6gF5n{!o8r)<(=f;M2EE*glM;Q+ReN z>IOvxtQ$~75)AK<~SIC;9LF- zp`OJ(Blf&A*7`75@g4{vv&x_X%9tp?H`fPt@fOn73b*9re(tt(Y4x((+arCro3z)Z z{(!MC_d$&K)!ZCECFf*Ir_enoPnhMhg-I!+N`JV4d<2u0o!83#nXmf`NgW-kgeMw_ zc6qw>MN3w*0T!ttg@EwDMj6AxyYNq4)wOqJz5-Q3iK&h}ih_~X?grk!m>&1-e@`# zUugUG%E18R22bz{HE`hMY^i4Zz-o2Iz1Ljx!Iio6{S$Ztt7H>-jcFw6HTc@ zoaJp0Eep_r=5;_c{yYw8E`uSWeQa;dykjRiEf2PK68hUPramjF-3<{n+zblu*aM3Oyj1FcQ ztb`Ra%HNhge@I*%PuSqtN}yQ76^$woNv;LQVYYb%6o4F{N@~UXvK7O)f_e1~;B=h5 z%sRa1Uvr+5;~8M3T@t!IyUUo%WaQ$$GmQd;Y-dEjbMJ!dCEN*pT=RsWMgJ_Q9)ZWl zu8S%21c&{>);wa238yfcX2D~rFs@U-x}QjLTD>m{@Ff`@?<5qQVilrC5v>eDMY2*P zl0Fnid2~WvRDgU1tF&*?Wc^y6AlXOewH3RxB&{64NZEgcz z$z{9C;!h?W#>E8?>K9H}YR(|mAxr-u!KHPS+r0hAQTHV7xh9qbRrhht88Cr*@G8aV z_;i1#stbQgPYdm{iO+BHWb1MYI78To>gC~t!03&7-YLH_bzvC4)L zFXTo_k$B>M#ni>zR^uix);20O%S&|KeQ7P*@8-~(BY^WMh2_Vo6&<+JxCswq_U$n{ zuye%R7%;TcEx!__uB7HB^50R|88klz4CKdaTaA$4G9b~QDA^7blF(`%y8lxtupffU-=oV0^WuAXG=DB@xDQ}alx%Y&i2df|^lK3ouDWg3O0_5dk zR}&-GV{#h|yxzq%+U~a;kCubDp4lBn6hps2N?qjih$nTOBPl?h#ZP!hP0c+Tb-$gI zYd9t%W8^?ZA)hP7E{L;yVBO&3+8WjP0BtFyh7Hh8SCcY)N}d$$&Ig2Q7V+fO)*T*o zeWA7H=C+}~%g($ypP%U5vbE}Tek463#6HYDs{g4UfO2_$d&K-F+kGIx5$-%9Q7j3M zC=L_N=;{v1lEeJzUKlsV?uwfB?+i(O^39<73i=OJ;gj_DUu<`?dm9d1e>J>uYtNDV z%nOytVztIssCB)CX5@i5P}v?|2Fx+vXN5UNFA2Ys_-bf!cMpvJ;;*^3?t?1K;FPDG z(O!@8EF+euW4mpQ`726>?)nSsv5{{UYw2WMdULL=v{WLI*NddNwAJ}PfwVw$|{fn zmlYwGPifq0Iw^Hdot@nn&)al9(sT;0B}2IHKXbhlv#ZH_ifFIkKUqk2N@oL&4_U9g zqaEZ3s3u+#N)bzaW3+q1$YXSAv#JuhI}uBKS3l09XWei3B+Vvo5W=XSOP$cuh!Eau)z+g-fYfb@d>>-;+5qVvnr-pVTyF5+QiVBjX00Bh1p z-Rz@2=u{Bl!Z4sP!eNNKRYAr>x}BGZASPGD()oJ*rvNOXev7K8eQ;C%gbI0ll;T+H+(8Y>G)Y?=kie+-W>wm@Z^!25(1I*VLSYQWZv!~^sE zxxKqV)OIg*lYFZ&5Nwr5>u@_+i0nUWX>aDOu0DM>kbj-79O10wCgSg7_UBBVU+!2& zwoQ8(PEW*m&&Qb8A6JLiG%{Cy{+kQb$@VRz+E+q-@KqoFM}omW2^PT2(cufl?PzLl z@UQ8#tE$SEtLA?-U|yRxkZ_Ic7KtqKjXE@|M6`|m`io+%Sj|bC4@{4uL3vWKk?SWP`#}Ok9 z_2cxV11$Qdzc9NHqS?Z*Kg%_EhpDh^(gSEp#n@>#Hu_~-S8NdYYIz z=&QNmF(6T%+fid;648l7dUU6Zl75jjq|MDZ>s7WdPWKs<&P^sQE99BH3)gQ*q2-t> zL0Sk^M?p3uDzqG4q*W+2c#-(mqiRm=$XPpo z`6HVe|8Ldj3!*aj_1pa~H#PN{ak{DLC?y%y(Fw*;W;zweNd;L285xCHmFo#Pnd%vp zYp~y)JSQ{|;AYu$q)k8-S!-16e`8(t@j&ihf2lh+SYO6(cVW2yE<637L;QD*b)YHt zk56;Y*$&UmSr@dYn=O9!BBG@I=%_>G zJifs>N1kbi(1EWpbjG&Ba{&bjy~eXi>UirGbmWC6VyCKlhTg5fQKl}3dXz@d3wx22)^o^xOyf{n?b?YN z-dHxgeD=~CAt%>y0El=CR66Hod_qeXV?I84hY*ozSmH)6jN9xV8R~o2|B+r6!jtc72mKT0i@*>Li4*(+PNyRG108DE{55*{x zq3H>}!V0j@#J#X+-7Z;AXW*TadPv|8qJjd0@M}^W<$D_lGEkZ<+z~1tQ{GN16lX3{ zUG%ClFkNj6-p%js4+%``X->K&-kjcMW6GcH^ZkO$Zq}Vm~qwGyOIGa?X%>NAh5&P0v`_& zo}f=(?rQ;D@q6&xJ@SUNCw*ftE5jUQ7e8(kg1x1;1cupoK;L?i^($8A>)?J5VfBv( zjT^3T^O0DZS}AW_cfoXF2PbXSfWTQ0I_ilwyJ-9^elj zK(MNl2gW=6_ynLr#S4X&)^_}eblwn?U9Lr?X)yuUufw`twt7fGf*r|KtWZ$1on`>% z-e$|4eM63_G=SbnN^Rrc$PyHfm~vq5RG<=BSO_>#XceZ7VBD2jQqfZ;WEi=op1EpM zymInk38M*I9yRS6tw z?%a31KTfWbAHN;~W0<;``$VExaTwPVfUvzG=Gh6xhj`F|fN@jCDD68k%pOpz5FrxS z6(tR1jxGUzWJ&U=R&SLz&`T%)BJC0tIcR3k(+y7JlFe24E>w$48gfhF%VgW-UmBzz zK=tBK`EG_*PdzPB2ycl4=rhF^`Vv`0)VFNKKAdPz4E8#-Rzp(}1cw>XKWi*lk?A*t z_TaIONtMPWtAa$g+7Cs02z`U#_rB2)r~GyWpseH!hCfUD4b_md7}|Skq1o|fvz*LU zU`r~6zWSd`-{UEx!Cv*WNQSURF5j-=%zY+K|hSDOwQA0<9A(F@wYH1L`ky*sAM$^3hP_LzA1D8CQ_AKGrZEFZS- z9Oz+k3)MMHeR|>)Cly%Q_b3NGwyf3Ka+zR19d=o!U+fc|9x?WK2AgZ%OR=g%V2ncV z@~{NMjq~0126s;%0U&Fs*1;Ssz?IeCt8lK2$4yVv6|?X3Fp5Gmc7GW$(sh=^;mKqa zf_P-sJUYF-bJjfOU|3i64Lw$l@K}SKQ-o@EzCmSW&V<`6>t#^eWw&A}vHLoyxu^9A zH0Ht(qiA0YiSVN?^^+G*E`8Y3$AI#SuGR+rmolVSYB2*aVkFo7CKtnN3fzu$s| zpzfdP+UNv-7q_RMon4>3#M*kkJp>&8e7R>coJaj78R0P-QDwIRsM8JW_#r&M2d+ET zM)#`dYPi8lt&p_ta`X0l8yM)=XuG0I!<%ds?{-zL^;Aq%?c7I4AN$*u(`TMeOM)pa z#E}%yJV8TA#c?53W}WnTURnUP`lf8Onu-H$b40U=gMbUo&a)*zl&9`upH{XCvNT0S z_Yij3kxs)-_2mN#pzU<(tu*ygRLaT3oK0&IU%6^ZyZT{ERpy$gK#Zt<`2HvT8295Q z`ooqM9<{eIBU9C6BYkyP-Q}}yb8@Oq?bGGqq-(R|-Ihw$D(#5d?sopQF+*km((ir| z7bk}2Tl(mJcH!?GQ*}dQwXAt>*``(`8d_6qeFMkTB zqF_H8X#oKPF7z$^eII9zU)5&9!)|Ot`gF6eHwAk?Lq~dgfa%3DDyxhYhKbO3f=hns z-J)DS#182p*V^-KD!)TlVM`mda1^$ODm5#yLXQ4=Kp?|Rp!h&gUStG8dK&9<5zu-a{z7o41QKP|L_gvMXk(b~Tr%XDyd#2um8}5NRr5m% zLD6k(;8rMp@1S&Bt8k6}x@q_HCZ9^0PI!yasSh5IQJ;FK9^Sixs6N}MCPmhTn$VsS z6bMd^s_2@W!l2rCHLGZtnb4_md3I~Jds&~{EJn{|HtoPMsnqG-jb$qF{0YWtSh?ef zB$)uD1aK^uAXI7(ub%H{=dpuQncp-&Snjvo3us02@Tg*YxkF9nOh_0NL!vZkY)$zN zuUf@x8A+NVzps34Bq)jEcjO0^wVKWI+(=RZcegm3BH~ZI@Y{e7fYAyU0u=^98q+R-ehJNhm$WB4*6fHKy{q`v6w8 z5n6LtnC?GUYzwj}eYob@Ez>2O09F7jUOI8$xt$o9`H z29o7RRqt0=-0C`3g1P%KR0T+XqXnyiVZ;bmhdUsmb~m0K4E1OSYIps)b%`q8_Irlb zTLh|ui$U0}4g{wKTV;bJb1^%v7^!EEeD^9uK^4I0rLt|?@Uq8W3NTbKH#I>!Gk-}+(jlZoQ zvj?0+RX7LxAina+2Gd}XcSx9-32?d~_c!%E*hCYcbAmX758@*wRd?N6$5oRo+!oc9 z2ypRZNHu#tzpbox_``2|auZ+JP>M=hf-_kv92HQoq2H89sl6{tU1W&sa{NuQO=`)* zR55B_igoZ)K5(^GX=K+F)APGxT0fMlKtIs=jDh1cO7C-@mdPeSiEBl&GIR?JpjIP5 z;yxgTX?uEn5+bq(i?Nqi%jAw#_nJVijCs~W8u@V9BY{2ZuYQ$6WFiBk-f=<_?H6?e zZ#V$zfry}s=@r&pQP(Rr_qmPGZc^nd^(ORhZf*Yupnl zH3W`WMT5L&wZ__71&Tlhp?ytU4(X%SbAy4}7*J+>fr}ZP$RPk()>wC_SWt#`#Q@5K zna;k;P>e^vH=zKbJUsq{-^=4ERZ;FfoNfum3M^{Z_E|z%eM5Ie6;@RHOiJ>Ywa{Qp zOf^PaWBM<@NK+P3qs)%uY;T$*qlAA(Z3blTw$I~^a3iQ)(l7d zLs_U!qZ&Zkaz7TRLmw`r)x&D(^rz;umlbl0%U54-GlvO&bu5UAmzRp9tL2VJo0)U? zX$gFqYS$@kP0LF#4@OpJ0ndCS?yS(QtKns2m8sMdkp3H)2LTWDQX zdmkE94D}Ic`~XEb9gu(z3mjf7EoXU0Hf^-1M-(7S;3Kd2Py2$=J^2AS6v6KlH%g4c zf%EVd89z28zaF_J!C9vHhT;>qIv37HJ^U-U=T$c2|aShq^x>J0?$!y z6L2bl=y*QGG5#@aX-ub4bJrD8$3l8pl*W)*u zg&MvKk>6ckobYOLrhu9vy$Co)>NrE8c)a|cAcs&8buzLV5%bJRa6k+N>BXPWt#?<8 zKG9DxWItGw@K6TT{dfID!|{7Z2^>@efW!bm`GRSc`f(D2!qi%UEh#sqX&RMsEzX1~ zY4&P$3t7*RyGG(=H+9&|ns#e;3&|Zvo~+})rF^reOkBuI(vohQSYJ%IrGk<%g5jXp zXV=3q4-cWqvz%kjkZDYGHwV^DV+MHw=JQPdJc?CX zAkiwmv6E>9Y5*cIb}}@^rRX7>MtAt)Ka&n9EE3CEhTCW^QZhda{kS7SUpA>>Pp*`0hX;87t|8Q7>=mla5Oc z_FGGb6VK?RcCY)|!HHY2fTRs53XO+>=Mhw;U7ugcbYCNGl?d)j`+T0xq14-3JA)JU z-gk2^2Y(OBt3OFhyjlXZrNPJ}4sBO2^SrV`UN=ets>TKQcds=dH5`Iv!Hbl&>wA47 zv}QnM!25Y=3bDtaRW^}0e5aZDvAK?oGJS^_NbL?z^x@V|2BAT#sMCFY7=Y`*bBw(U@JqD>`T^oj$@G=1MqRfqumS1z!8O$U8rQRk-OPCy{jTF|2tlFH zXte|m`-9qDIhG%F1$ce9H__AyL8UG6ah|;u?kj41%K`n(X^ewdG->{7OQgLF z#IRgV^H#-}QVD353uy+8Ahh&4VEgJfAr7QBTo^8ivd;tHZyz~f>IYV+-ayrI3j!Ka z29zN)AwF$J@RPkd%>5_NWHjimQ8k3Z1@0+F7=3Xm$B;RjS!f7=HLjO1mv3r<)0lB; z6tFnstWS8HSbhlPorH>k2+C;v808$3pO}FI7XuAV{%EHt8Ogk*%&omk58@#oPVJ6|czrj84b^QEYBfHnsj5ZCL+}Si0;%;$?L5 z7C~EK160dk1N-}~5@8^4kZra%wg`ro*K^FN*V!3>_MNc!Q?zhUT29cOf`qP_Oubq2*Ps<8^Q6zHhUl|49L=#+yeHx&( z0_2f^@!>0Tm*mA7RiK{I%%>q-NVLPuw4hK*{d^TSZWwqtY_cKj_eA*FFgDFO!-0gJ z3)JoLG$H4KE@EuA7X=X(OXDRA0~`U0@8-^oz7ALxZ zHN00L%dWXMeE&OyVUGA59u@)yw{IRgeyQ=6k5=Zxm~DyE@}`x zOm!1fPg*+hpS~F;hS(?w2kThz!iItuU)AMwJv0?qk z_~kXm(8;(g`vci*>_X6P{90Xv-6_!U@d+j5bPvIY3~KWM)5bw}q56bE@H}r`SPyr= zQFgLRV(d%={IFTI0LQqQ>^{j7S^LE=6^>tJGBFvF$@Q*GplRW=or#JxufFS<9o`zu z^*=N=@1?qPEimZ1JipKme>J<&%}Ze?m~@rt9`PM!_|~3mDYe#VNd@VxbD+GrZmkxC zT&^fjha*i0XznHDhuU@+O}*amdK63n-lN$TATN9o#d9dVfQXc%i;!4+lXB!JojViD zPKH48UUTAA+8%07)#JZrZ@YdG`z2!aWvqor^@qZk6ECaCRE3wfD} zzO3>az{&2U0va&DQq1ia%HNW7x$?4Y?S93Qrkp@b*T{w( zzT5`W1%o(cHud0)=}zHe;Nm$N&j z_`!rLKs^tc_WOVIC>}mc21?t#f%jr+(IHpDXWC-_#POP=boUz(7kJuA2-KRUPaoI9 z<6`t8F{+0HadeeJEdOQZRSQ~HxYA_M17ou>{R93N{s!IUnHNhG7ZSrD8W0y`4A@pV^EII4<`Ag4o>sfv+HO@-F5HMut25 ziF4*}t?V2xCyo^Crc;J31NP%ysK5q0@&?rjVD`DR`(m!<)?$MXfT^lVyV{4E#mR?$G{FxS(8;lR1O1Vt(a^pR{wl zAprMTlVom7!ozFT6pm;yQm``RPSJ242>|@hBVZh=o=d{y0XE>nR z(&xUbMDv5bKEB1U&5Jb7auU@7pg_RCRAU1y#w8 zrX6KY9?P?ygE#eI2Xh`81wNH?#hgBJv6^lQ<{I`84(da9!~H-7jp)pKQj#J4%@yK^ zrqgWCUtjB&-s2Y*R!W|ozJ$U)(?qMLQ?hG>ZAot&S|&gm*@nwj)pv}+x_t60oIx}p zq`E1Z7~sTbSlhek^|svM$fmpBtn(~Uq=zqe6wK-sR$O223*KEXuwhZSQSO^-KF+`H zNy=)8aM)M-@~%TGmi?5=X30VGovu6LS_jJ&<-sm+)GeUmRr|179nIIQypsMTv{^;S|AKgzG@~0sfra%W!O9`hY*B! z^H#5#o<$MvU+7j^hn6G)kMoWlaD~28>bV1b9Jdz6qf(0!U^HNDQwmbUnF)O zefGK;-WRyv=>ENa;2k0l4fv{j&wt%{{TatHHt!?*Fj0i?_v~dNDF@?I&J=D5bOn z_IZe5a=_YoH0cvT5;AqLfN*gqqUuV~3$x#|MXVBOFsxn|G>(W9W;TWcU_&nN5~qPZ zkuti;xS1JJN z$zYI99I{-1-*+0Ecv@6m10Rxln?BGG!@zE$e%xJ0Qz+UyXN)kVH71sLN^kF>RgmiQ zsm(5)8s$v`R#7iNqBk=#v3!k>G4!M6(&S8??Kzaqr}4??=s{9(e5at$6aB=GhO3EY zAbj+w3h(zxhbXcz>yHJ{rG?oXfaB7G-3vY>ufiXailjb&R^>bNpvj1V0c=I`b7t1N ztNbk?;pmB09{nSQRtH-p4$jgUk^)yk_nB757TxbzidPG8g*ej9lb|o#(2e^1q|NHz zi+h#flr-WZAAS}IZm~^C9zUItI7b`~Ud57Gv{KY+nSi)63RNWyak5cG0#G*Jj7;!4 zFI2?G>b8~K!25~=<@paTQ7Z>rgRa@xQ?38d7V_>=9v=Y?cYmuqyX(@l76SI&=KA9p z-bU7->KJ?b;x0zjYDo{D=QWDauAVt}A4hzpx{5A*JFOxEHaB(i?O3-HSNzHmu2DJN zw3^znSASl8zmKVgVFM`1$PY$~xxhVcVqS@7srP9BJPz`Li@Qu{m{pod# zjj^n#XaciX!v{w7!_xa+CNKNnnVMLL|K{XNFtfk9hyK%5Jpazre;3hztm5&Bosb-$ zM+$!Qh#)NY%H4ppCtPS-h~COWmH1P7PCAiftPI23F{RDc!bLO%g3vhhLPN**(K9mTDv2Vjor5F0`Xc786ji+UXNq#@ExrR@09 zuPTxpc8UQQqwnM+e4A8p#P=@jWjVv{%y2viO5I=(FhKMYK2O3U%)rCC;Kp9eSIJ`c zN)SvAwE0a>uYAN!Y|!YdTCADKFWYczZp-TtO`L-bl{s!`>O`A8Y}^XnRq~g66ES_n zv>#(i5FEKpP?0qK9S60(8#a_Ef1O`w$;=0~fdT@0#YxqjlpB^o9skUw5!eCFhXtpSB()v36RXw>sphA0}FPrm)|bA`s|h+PcHE6 zMg98s?0EqhShA+|%bf?b&m;>%V|8bE&%%h*qWvdss#bNW&cy&IQm$N7y~fZ}-Mc96 z8CU(F2P?r<)iX4XFn@D_h5|WhD$%=?^!#=_Ev*ylse4tppL1HO00om2AJl}J>W0-u zYXR1`@cXeSBu6hVsRd;vN1@R=l07@&@R)cONSpDtKaM5$1Ogy6B!wOXC?n3@t=pgJ?;I>Y&cD_NrE=?!b6)q06N^A9#6ao?nj)QAJtPCDCp=}`yG-@sXE0pV2?9H8 zpfT~VJ|WW<8bW~_mSEKFUtxQJWm|4vbC~1d1eeS{>_Y&Awk2e6=ij)5{eNXg4a3l`pu>Fi*2Co6~wI_|6j2^-;p1|g8Xd{@IUUaVm zvhzWJ3-twv0k;vXiy~cLvVi1`Z@|>lp?4PHsvdSQBvdqMi$G%NLW6qKE$dVy8cj-p zw~P=Lfo7zgKRaze#nVar&SPv&^L~A>Nn#e!XHTl}mb57u6b5R*W#&&N8GHXGgn6v0 zZ1bL?v}i}>%W52%q$BT+zF?~18rn5;quaf1jr{~zScIdme1uzLp$JrnqJmZ!uTbO_o4fWQQ5;Fz_bT-+Gi-sX`gVRFp?9X8#Uv)1S~ zOsEjjzEp_E5M>7Q(?D6FF;Nx4urK}~MuLNq4ue(3uQTHC1Mv4RJ(6qE&3l(ib#pg0aC ztYPfEq^tomUeZ=yZjsNYuyI~7W9mZaar^ngI)0ji?TNmJz7D;$7xMop(iEF-HPsnK zd3#-KAJ|$bIsqLaZmsJz#R?PPUOGcB#bm5D#45Nm?ihzE>^;633=|!!)bEfzdc~Z zrS}Pw7&I*(7YijFIEi$73QQ85^w(h6cQ`{MRPqB_C0(8~SxNsDR-Fc9_%(Xuq%yCd zV@FNJR#sNyFixG=ek&5+mh^~=hm8tg%7Wh<4<~Jnq^=MfD{q`edzxgXQUT?6m;h~% zWn0=90EPL(l!UFaTm;EvDtHZpB%fG!gY5_2MEKpV*b``^xsi*LFBh*SUA^;+tm`LJ zjDB+=OzSub7hX97l>gh@0t2*f4jr$K^I<%-b1!kK?4O+>0jAzFntg>NeED&}?heS! zT!PdQ`HsVfx`ShG3?)sfJ4>+-gRM>-Y?C{RQ+VI7urs-nv{7zgPWQn~3gt}{r0$Oq z>Y?y~2eoPn2MyqG_mIKv!#uSdACTU7PD)kz!QE&1R-}o*bl4lF-+SYm0|imL=IrR- zxs4~+%Alf%iZ4JZVXO@rsb@M#7yp{mOlbFE4K_!0`*IonKs+cm1S|rZQLwnnKy>9qz*g z%5rv{HrVXj>pXH*9+Zxn`jv?a4|!`l?38v)GjcmN1~${bXJ)u!l=5Z);Co0ZuRmaLWb)&F75Gq?rF8 z)0$lL*xFEc>8E~EAJRaFATSpc?ik7=z=;Om-BVBKAMQ9F9p?Mcoq&1+l7Q@jSY`txQ>c&=F37OK z)h+_iwX7M6UKyXACBuT(8DXmVy&iL5iyfKiy@~R&c2I&78SX8W1!)UuJZD2@lMpXc z@qiVl0k1*}J+AXs#c$;KIH~ek6!qd{dWTiQqt6*E+gD;MWk8=Y07Y}{G6C{cdw$=( zTHypf|2GqXRKGJS8^Uf#4c z(5*k;g92%;Y7$vM8+PF&Z3cjlAkr&%TA)W$(n||7gT05i{I!iJCX~7rsOtt>qwEf^ zC&3++V9}iENO78d0h5P7o>!jG7YIx|x9OHwC_RL~Tif%qvPkYs!-Po}lHOD9OWoOi zG@+arqeq#D2^5+4pmL=)1s)~)iBlxnAj_pGns?zhVl1VW#>I3v!jZS&P%UmT9Xjc0 z*RIBfxzc9$$kq0z2;NMn>VYx}PY5&@Ls6??oKuh8IIaGajZq=|CUQU13L*n}m*xOZc#@x8G}(p&Qd<7Tg>!s4*cXk#WXQbxMbri1DbuA9Ko5`%0@q8yQieo)F8bw9a6 zNh2`f>}Dt!a%geFQv9*cgU#@X8AxAkOq4L%H{MQRnx6(=PeMMk9UxS3||5+sY&x8N5H`pM zpEs{iWCz0FkrqWK}*HRvK<*TutHR^-k_j6D9ITZNAR2R z<*TounG%7?O^$UY!x?%k8%QOTQ7${%8 zjOke2gPt%Ffj3{2GPLLbHtRb6`RU>UI%NF^Kiad*U=0j9pla`ls@ULuqco@t`Hn?)8X&P_!H`M2@1dB+wkrSgjV|4?6@fKbv~hZ{}BF3Q1N<#?HSlXZSflOod zi0Jd~`FfCl(D9ohPlDSv_1e%ZQylVZV8hM~&(?DSl*aSPOn_F%5vW>!BPAsm3`?(d z4if=lhUE^jS%rsvP!lkqO_M(!aS&-k-(XC{*Nxpa<3`kI7nsS3i`J#OCWLFwan3gX zk1Y4x4I_r*qom4`$HlRUeWgJ8h|gnSmI>2Tjm=d_!Pb+4J6&O10nfQr6#~1at!VF) z%q6$B+h!i4GkjwsrZ3-lP0^Imrd}LURh2n0L#s5p9q+)$-fuH;M?DVi&(|C0HmlE^ z2gSEuVhbj|P4uQsU#g0k8&y)_$dvjSs@m|=;r!0)zYHWe0t7E1zTi7JzSM!{|3sMn zB3%E73X@e=V`k}*e5TaY_j41`t$e65*MruAy>~Y0-oH+YBQH4 z=%!N_c9~M02Jzhhwd%%^14I(;vyvv_v~FBKTqO#Pg_fEKCMTO#C{)uR#&uJ|*XarZ zDu64O5lSFj_6DraLAlj$)vP-A=%h-RlyEg51g{M@{pgd>(e0G_)Mzc@73w7@Frb2xcp^?i< zk}4j#y5pl#C)?Iu0nrwNeSRc26VvFrT1Bbv9Bu3oMfHqIcOarP)C8WfXBH98J}1^ zG_l_dFoz4C&AewmcN%=loWdu={nRZ5TuJ!&j*+?}%$z}s_4Gb}qf0rrS=g<0ajKb> z!-r1ant^e|6(uT77kt?U@9d(FU=`|C2)B42DvB&#%$vj?jH(YAL`|8^>gcMUx7TCC z{*L@`#M0{ViYRycEO08ib&phD;-#I`p)Xoyy_Wc$=GcPZ9Dkgnjcw?M?*2xu=EnnX z?)wycS+lemq64N_4aCjU%#pN1oqqp8Dt?+XqtE#!x-<4=Iop7|&RMR(m+s%i%03Kx z6x(#U`HR45nrs|1d|~B`-~a)M{&xiKAJ**4R&`?O*sZc&t#<{8kf z=J2+VBTjhTmnqfxyQ!t?YsCPYe!ewy|hb{2{V%m!c;y!;M;M{rfx zm|8O($>aH2pP-SK*0GP}9hrfqkh%Vcf>ziyQT(CNpiGvz`}Ec^{Ejz;D=TPIm7lo?b)`LX1$U2hKsqJ;V%Cc zf06eee2SSY7;>OYmZBUU76?WQtUJ2|#Q|J9+7BG{8vo>4I|S!s9p6(`Voeo=@|i{K zaU+gm#Wnh6WUy&SP>+0I9%RgCY0<_4EU9s^F}THz)549AGJT7r8S9~&f`mN7y5DYG z0En^r^um^tS)y90X7e%2di3%52XBF44l-n|Z@+1KMC+Gns7n<4BR-cY+W(mHIzdN& z=U9ycvz|C5fkvff%|&-Bl!3U++tCo`uhYt6O26_p2vZll)F&7BHwV`DX$v34@9{T;vOEcipWJt_2LP}i%f@)KWbVN2b)tWhf7>)_Kb?2sc(=J3mZRD1H6 zo;lnjcdmBIfON!RP$ZHMXB2RXt-Nei5~NrM{0P2R?8B-y*3+oL{* zZw?)vZ!#~W8Uzw|ltG3bWI!nuWe3bCEi3EI8b`cP%EejGP zrv%}NM`>nqMS=P@yxF;}X71<|(M%GM4a4FS6Z=GXI(0Ft8IuEcKI>@7x`5qli3EsV z+G3XAMHzu+m@LNI*{9e9bt)vrd)Ym*;+_e}L`*8m;m&V8roRnPdjkeoD*!^hw*F2P zi58_ll}!@VmQI(gBfqy9du4o|V$ByZE-j!rYRYQ(#>r+)@wM#CGbNC81>L=?rdoWG zi9XYHSn_-Jq77^#ivAzQB)^c66S2qpb(X9qtCuF0MwKjH>f{a+=I% zijsunF%Wn4J)n^IuB?_&!GMuuOLePZOl7Z{%h;<8C)ST;$AAZ%QRicIUY&xX4$1+Y zR^yW8u5Tk%xh#l5a*P)npmdz=Yc@h~TEVuB7#8_h;DXj|myq55sy+Z4@l$!87B|%9 z8R5!B^hc)wUK?L&=d@W?Y&$RjWC?v7W8Y^}&$NrXBhVtC?jEWV$Be9r?$^#z5O3S?^XfZZ*{j{#GmD@0oP{*Xq}Z`jaAuO8rzMGV zeW#V#J*=GApDzLKowsNCUMzC)J>xt_wcBwJ%uqkrr#)m#>YTWGmT^~X+zy2?RUYkc zilcwq63@H$4)O8K#5UZ83UIBrkL6S)Ud;eW4Xi|O8#;ni{A}2=WXsThlOjIC;>G3` zZ2uYn8txBbn^)bN_(kWjiOnruDS@E2f z!TBov0?4(+6i^eCp%zi9xU?JSoYQ4gS`-kR2gO1ioy_9wDKdJm&)+mQKkq6k3S--z zI{6Lr?KR@5uYwWSYG-^MaE1ho?9U4jbmyM8B>U%BiTP}M+3=>^iLF4pks9ybqGj(R zxeu4nX9NLCVGzN+q%qs^EQ*47_NPIJ$#J-$*WZ5YJYHqnUv9;S8ikoabRFCXnC*jp zJb03%( z|D}_ija&`>E5`?%P>sue6>MC;+;@%tk|*)^FaG!A4Xy2rtn|Lnlx*$XzM#RrB7K{` zefZ^EL??{d_R=GXJU&M#umYFX9ifMdD3s2Lut6=vTOsPlFO?2K?_A>W_2whS(-3)Z zKdxQR?|Dq#a8K0%GFey}Did-}2Gzfu`&TO}SfluP@%~43h3_=sPwaDQJ@cTa!y1arHG}At-}ODB>gr zD!WNDk%ms)R!q(vmfyw^*8I8H9YEk@DKr>5Aw$Ckr+u=;bag(0H;ec_@m@jm3t~bD zT^7}B#?5M`JMdW=FCzsA3V8enf67WUfLgJqO2$NJ@PZU+fKJh!o9iR@{wOF9F@6t# zvpxx|@>tHLFT}U!v%4NQwN`yI_@)x4%y-89@|T4od8DO)fiI$ket}-8{Evv{XyV{v z;^h4Q6D(Ot#%hlquJceWO#6?2k8`zuYf{s+5S@vqW=fBd6>E`fhNNrT`=d+7<#}%t zFBr{fXE8aYM^+^FRQ;J?6(9sD3PPzlhMSv@Z!N}PA4(*8{Dv(Hvy_QyBU6;R<7Dq` zZ&Paz`jZ_j*5_=H^S|1CM${)?gCTgZIa?RgX!zE>dgMDH6drE9CxPV#n`~o*+5U+r~ z952$Kr9e?tV@*44CyE%fA0yKeI7r^4Joftm^Y)_MBq5rp?X+$u%W7aR-1e#B9q3jg zE~0cDrxHooV)Rx8dmnd(xBH1l;?3WRb2UI>lEeEchWg&)iqrB{dY>6$8XyDoHf*#Wd9%8Jc3HBWSj^L81co5auT)syu%ro6s>F?5s};q+$$r^d8Bz^a`93w=Scxf@?*DD!{FgDVlaDbeq>ay9 zouerH(4$_=9tb`hI;RA(in!_1?l++NZ}mWTlw)xe#tKV)+;TZyZS9+C#&f^>M|+rj zS%+r;d<_Fu4bF^^Hc5eY6mabrpC2$|zXLRop2eVQfubvh=O&jd(<>jLp2i+Y2%O5ggkyJJMh#$@v`H8q%_To-XQI1%+SPw9i!;Nv0%T?6~-NA?@%B$uzNGTeS4oY-012 zKELqk)39H?ZL(ltqU?5K@?w?xPw2c#wc5nDfWB3-wWG1^khSa2mC9JZ2)zt`Rp;suZAmSPhSc>t)Ch$N2O8C; z25b(SkKYL<%MxtGXrfy)9mS6u6jM$qhX9Wh7FD;cNzn(fxXhV_o=9i&)(Eja0ufGk zFE8ITq{gOUPFneaPEPyYxkqfO$KOa^6B<_=Bb<1AzMNxhS0oDIG-H+n;adWVm2&1& zU00WWd!jM(;|;)YCFfx#+LZ6LKbWq@+Ml8yNUXHk^@+kIMYD4og@}CnM^^_JmjP=S zsM~zP0T`Dx(Ko8ZKWGM*Oh3MfHzp5CtlLHEM22&Kh3DYd|2EBsfM{1mezGG#-C|bf zVehb{B)a!pD<+C46S#ngR1Qekd)PaJH+X$qZA*!%iBJgIPH{W3(HpfWfJt`73Ot1H z>>Q@33RnDYAE{r#E8%Yl>Px}|E)OV&B;7*7eUXd}prtv*F{P#)z4n;+5Zw4YINRJw zy5O!L?e7;CecUUqh^~;jDt*6XMpXU09*GjZn#q$Fb#)g*T<6Ra`e5 zqOMMUgI^4Y$Uci%M`OznMU^hOv>Ww$|4UZBt>$LO9QZ3Yu=>Rd|FvY?e^~T?<%Pe> zeS(bK-{t-tO@VH`;0N}iWP6T(5EzDjF|@D{cpe|aKmLXKvS&L{z*Fp$C7 zLqK5Bz+56*_jnF4H4*sFMw2eQZQkz8F^z<|oAlb$?s}SIP>XE?LNnMfYuH8~-jYA{ zm5~%#*UZuBF|!n+i>NcUOxFIsVihWVLqO)fXfw6wgX4b&f(dnx=wuk)7XxAqR^~fI zrJ_i^a#lA_`)_SQp?~2uR5XDO)ZbIu9%W2M!gl8nDI_-?(XA}OcQfFH4g=ahApTvp z*F)(TRla09{fnNu|4kikV(VyOWNu?(>->Lk)A;PZ+_Zs5?+B$7KV=PP5kvEN)st!P z9^BRkVD#|Jmkfg6KJVnv^8VO}Dc-*yy&mdsxdCP#S!T#g&sjHKOCSn)_fb#++O3xTxD^b~9r)hBKH=L7WlHR( zK66wa{RP)!i*vve`lOl-Ow?EIDT<|iWe8Z4S6Gr>Gm3hq>KL}GjF)9>A% znC|U#q+1uNJ*|}+`v!p$!TRQUvEyty0dg&?+1(VjiQ+4m5CC7u1Pzy%*n;vc6amLaxF>I+Hzl&-!t5 z(c$;2fSV7L`hu6-AEg^^H4nfS`J%l+3DrTHj=7<^d3b!&dXveO;Tccu+zkpgRIe}K zIZ0KjQ^`G!>Liv+(h~q5ctj zGM`DO8KWvrH$Z!e;sdE8NoWr~fnx8Lg|29D`goQ%{Q`~i1@8;Ow*rVeVL4h`Sr-l{ zLDPU0E4;~4x_||0$DjAVJSb;aX(#3`{vZ&q1aEoh!D=DWM;6MD=*!}g`Ra)g!hg~t z*@$r2^RiOis3hO#8&?lPHe?UnaeNl?*jZEI-|a`uu)^sygNlOa2Yn`Jk)jkmsr_B5 z+os+}%06~O+VpV`PYS4{?4wNZEtX)T5MPzFU4W0*h)3hz$hnt4gHCaq4jSW*MQSz~ zn3P49%t4PruT;(IYw9aKU(!$aE&4osM(W7`Poc{)na`Gq>(fRQg z^_{ZV0UYqfEI+=OMeBbq?T#jYRo{s+R)1ID&uB!c4f4`%w0DdPJqYbd56q(1)-Eh0 z4rCn1Ft68oN>%; zjX}{!*{ccLXRtzbKJVqRrT6m&>0HSAmr?r4?dLc)NyQ90|5E@4B42k zDJnoOotV+}lysDhk+G{u(DGHMY5RP=B6|Lhz1=o^!v5!w2x}u|ML^<52b1RSL3bd-|OlVO!7YqOpFuufnEX=gzw3qF^p~l| zuR6Ey7rFl*NbPK4V`6P#`!62)-&&msP(|IccXkrMA`p>=70sJ%*8}Hq9DDfY5PV*- zm1bEG0v_?F9vvpZ4>M{>Jz+=1P#3HK%&$CM#)1Sm-s4wGWQ&YT%U3ghOcZT=$eDjK z6h%ox+x|$yOF!vU+;|O*yczb^VHMEzAs@P5v8Z&+sTfKSui%sQh2d-V7@~ z&9nFA0-|qOujTspKLlMp)ZUeCh}h70(z$ zlx2?)+F!;{v~x<)<{}L_XA4dquaj2JfU-Sa$@pLFy;WG8Yqm8S+=9EiTW|^P?(S}d zTkygO5Fj|gU4u(-5AN=+!5xB~%39rPcduUE|K3Ng&b}xvisvbc_j|{bF~^*fE&*nR zghK*dV#XG7EE)HH^X#d#9IYQjP$2r%1Bqg7O$l-&txF(3^f3jbhp5+Nu=EiLk-?SW z6@4nA%{xrs(_|qlFx@f2z0zJns|2OQ} zKR(BQ^?t+lAn(`uNDph64uvJ+#Zi@S#;efc%>_%~Z@A1SNiXd5RV>UPLVUa(EwYiM@H31_dvvQ|R zJo9`#Y@;NGFbzrjxE4!~O(OK3&yaol(=qu6(qE*71eZzV;m;R&sk6^;g(y&y9)z55~E; zqp#W?^QwcdoYC5}B0r~P$A~+mWAf9`tgOBSs#^ElRlu zA|(RNA5dEbeRK0E3k0=i95@c~pse)=XEl7yRLr3I>DiHL7`uFsQyuLOh`O5@7OWG3 zw@SeB#LIqXEs&tE?ggI#P9q@J0?YYtti`9<`i2GjVQNbc(mOVGO0+IOIQyuL(#J6k z@8zGeDav{)^^!;}dPgqUyyzIr4N&HWGv&>rFGg-cT_1nC#RtwK|FdC4ope0?47xMC zpgW`Y{{Z`c$(klq%nQ0Tji&jNUp$bobSW)n zV3K{V2V7pXp`2E+=6x?$?oqW@YOz{HPlGJ=G|NoGKaD(|R$8Phe;ahhMWk$aty+p? zt9|^TFIBm@WDF=PWi7V|MK{59arZ9=Q{Cb$qF-&;w5taWiuZ@=c{=YEh%!5dRf>uw z(m7YEe45SWGRi=`51y;$D?FoKNa7tjnO2c0?;^u@Zfo55Hu{VJ?stI>2Ww6egLNAq zWa5weF)lfx*@o3I@xy*zQj!q9QBKf~b%E%+PtAlV-2#{OooRa|@O7mRU}K6+!I@iV zm$JJZ=i9Wv#V1Z2*+*_+Swc@1?xv~KpJ300g7@zhGztPf*MUf_@T zj&$5tQ!(FgxG5D(x9_d0EI9im53cRSwqWlK99(k7W<6a*a?QFDZbX@KUcvtP$@}pQ z*dBtOd>aBt{sr;{uBPT5jK+@fkGe>I9x?fM$;co2tpCbl|NbjiPe*f?Uu<;Jdr<0% z5pwqwK_e?XAw!ZHl_T|5s)~d&IJYikhK$+vo60M{^itogFZYQ@wC~Z`jsZ1jB_(Jm zLN4*FS~X8y4U-8_yFQBbqKsW+*Bqk_uKY^Ghd?JMYslgXUodlYoaWSzO?kH_z0%vk z-8-QninE<;`58h*#jvD3xjp;a1EI=kveB}_BJhdSUQTdZ7~6# zVSIGXs^N-&{Ws=B@x%y4;He0GLA_S2ILM##7B~1mzez{3y^xk03Db=r_&foQ@s@`y z2kIS}vlJCpXH_wa@e@KpDQ&thuZ}SOL{rjKgfoi)ejh23LDHljgD-bzttc| zFgf|f*%EHHUnwDan;R(kn_d|3CC|VqZ_1{Z)3o8g6;yZr`5cEvkO2(PQ={#bl*>^? z$57vLm@hy5p+xA3UK@j_Gj^B@{O1d0ypoql0J=zcZ+^8`X#Tl$^4py;G_HaYD}7eNr7<=^zOgt2Qf+B^67zYY8J>w@%^QZ`t7rA9V`va z-GAlZf0RZN#$|fnq4b^kMvx}2LJdMN9E+==XrNwDsnm?tAr?x@*930#q~)LJtTv<( z$-MYn4f~L?1%Ga<*tkVc41yU|7AZlL>ad9Yk_)ZYIxE5>13OqVbpQ>7xBJW~yz+bn zQ45=EUGL<0(vRv|$YG^h&M=jvF2WMVN1PP8QDnT|w2l+I#5|UO{yn=5oyIfCJj84+ z@B^xRum25g_$VjDYb;yZr2N9xOCzI}rO`a;e8Lp{h7|(24t3p9xq;0m-U&)>L8SSh z0CCMUSlSS=@2M0M*%=`~X~B5fd=%X8wZ+io=ISasZLVOuHltT+$r%JmQSc#6yqqxX zy61|=ar(?*3IdVr_fBF*&Rs1O9h=IJ#paI0kat|Qdi}?b1Kw!v$NathL#c)H(Aqy& z-7gV7l$G(d*3EptsEe<9h2qrDJv!W-7c_sk)s1RfDSA^x^o~7~$fGXmdGAksb|(OV zlMkS$1Pl2WlX8E1BFw-2Gz6`*{I$DsSLHWyyKSHTtFsk#cHj}kXCo3O$7Mu$<+*0c z&s6GL(I;n4)AdIvK!0Y9r6 z1UlJff74rKs87fAK##R}&Hdt5iAV!%fXp6gO6bwY0g79M}u^p5Mg+547j;}wC+)nv+OQ-;vN)aA(k?Wjf zTsBPGv|U(0#+jgZ#dVQ@AUxk-JSZ_xyeR$k&{a|984%+4CmHbKSUKPl1Y0tYLiexr zO~0Yb&dt`<+R+#^iQ2-!*$%Yw!`1k|a26|X-$Q~TahGn=OBKHJ^G#BX_4IMgl|Z+J zm1MUOB6@whZfhJ=_|xULXDmkg1Ukf@px0k+dHNjyj>eYO_QtND6`cPPFS$i^!Gp8B6+3&5 zn23MrCi-4RW*|;crw!*CKP9GozO9cP8}glJ%)6%-=R;~qXOb6|yGA%m=mZyGwg6j% z3pi0JXBZ-$ygMu@CVLQsftL0}J>g*K5O$^CRx`posu$0Ps(h{sBrO7iD$Sgd(SrqH z<15v@oqZm3uTKzHoXmi{_eY~xowLff6U_5RYf|NUS3 zQz;x?hLLGtoL)iw{ND=1MSZ-%#z7zM4l*B7|K7m=@dy5PjGzr!e}4;PPt0S4BS8df z$k_v?C==aB!R)FcOuiE`m0L10QbKc*47kBYn)~f6Zpn>uHwd&vDQY#}^WE zGTn1JruNc06Pgt55cSG&-9Rv0^asY7%8!Cfh@u8)r%RdDvMG6veTUABFRREPXJ^Yy_(r3$&cI$VXpzVTQaan3GQ$HK{oh15RC`s33i0U=JP*R0S|Z zN)}_hJ*zZ?zKU}y=tDuYbAK8?6Gn0Bv+e3?BYXST(3+a&(Uy#J>_z0kjN zJqvV3zo07i-#DYcpUfX1)sJa|?-oJ{e)NslO{GBb@%N8{*9aUiCMVlBN5?viX#rf` zQWaGXVCgcjt&hQyGPP3&QR6K*S)2uPJDv*Zcn8~xR9(Al% zNEEY%GbQ^Z2pic{r@hlQR4NMhrz#r>IB+?8 zX6F44mQ*E2Kb6y^qeCgOjZPS@72>Rqnk(pb^^v=AMLjWe_<-;e&l`L_jCetkqP0?F z=tr4z*1VE$xB2$0CvNMIkll3_dMGAqm?rSwUVpyja;zL2C@l@{LdkCAGo#k7W&EM9 zDeRNG|6XNDo_7tw-U^&WbrBM{_rkbA zgWWPG2`y}87$k7fA+!92fL2zR2id2v&aQSi*iHOyxes$r)!w1$5?Ow+!qpGTrGo;} zU1*&`q7drbDV$amMg_qyyeajX&;UUuu6y!4EutgfYFc3Mr3x7!`+aT6Qt#qVWo*ux zaoQWup_hU7)d_*_+TYRw$M`2*#DB>Pe)9}2=B}=wetDPQ&;IZ3JxZ+$)SvMdRM7l* z{TULpfDI`qz{DyIV|AiJJlQ@h_nldq^vqBk>7?|u-K&6%yDjxe4i5$C14W$T4*pwc z!C0vu1wnv#O?a3fVGPEvv?SQU6hH(>?SwwGmGSWy-F=q(GXl(TJbic_+ApV_-)!f= zmXds)$AA^VE*-&k&ai+{3v2`lmt{`uRI>(z47e~o;!@L;w$GT#u8^vFA7xM(g4Ghm zvlGR?vC@TwRG*uvz=-TC4WT8@z(xm)M2(40FMdCKp5<%ees*^Qa>?!`e*o5IC39)2 zavv_?mCKp04(>B=(?qZua_U_fSNF03GABYsWY`wJc`$_F9ojHFr_NZtH z_cc`Kk#;KYhefz_TZG_E$$;kG%hzJ@QO9PTJd9pbg{~KFS!-N=2?H(*8P6GiEOA~- z*zpH!U0m)Droib%+C`8U6_#xQt`^qie{O!;%Lov$BANl`xG7|(kY~+dQ!AA;KEl0q z@+0-mURn_)XxB2G$5}HK&3~s{^BE&(rZ+|L@sQ7rIb}3TZQr>; zzH}q!Jxjl;IRD*jIK3JA~bysw;5<&4Ywl%@W6|h zsSbJpzQG-FH;d7jbMm$E%fn-y>rfT5S*+BYAFI8PCgXbEJP4!L`8xmT&`uVM-2>O+ znnOQ)+o42i#r`sH5N5Fbpf&MRdI0zNPocp-wpsnQo%>%}w|+y>Ux>*5eMgo2Py$&G z2%L@}3#st$oxyLPX>8_hY;Ov(kbjL+w~6Xs^0B^)RmF=zhLb?ZA4;Td{w12}-sjxt1cV7xn>?Yt6dHO>x4rqkB6kTg%2|pW} zt1={^3g2)#Pa$hh)_3^1ZRF%gA}6y{fn_h`1f|m@ls(vPp0R&dw1ew*NAweZFvWf6 zPeiSac;<7e7**JD^{5HQtl`mB;;h3J)tY%}&n%Q%5jf~@rq_YkJ6I^r_}HH#_*c<7 z{J9Z!R`;TJGe1EV3>V@b`0U15#Fh}y$?$>r?Eesq{dPKkeJCL+!!{uA0b)F{vIt;b z-iRXG9aJk>9a3UcH_V0WXtUCPw}ySVler{E4AFq;X}56ikKcbl@T?pe>xxf=kp~rR zwTBymh@K6~@YLo(L$l5ax~n%7eQ{E*@T11sNEKBFY-3y)q0pD4b1zE2$RZ!myo)pF z*-o|`srec%_3k9zrn*FnS5qB&b9Z>`^OW_r7B@E&D(EqEIM$4VDHG0n{k zm|X90=|y%5+k!IGm`emjQ>gk`BFB6_;C3bl=i{n85q>4~PoVsCEnxx$mW)AK+ljgh zqOJ(g$Qe^=_Wqr3yV*1}V|KzYqc^(>I(D>cOZ1O;DH=cuUHrRvZgUK?=xsS3HoIxt zk`tEf@->KENXOmP*w^GcWZps{Gkfh}f9;ibl@je2K!>l^v!~FPp0`6W{* zZfaE03?V|{T~)=4?MKHsSXTLWurMK(@%DK$7w0ZD7M{c%m{!L&;qTPmB@8XjIfu1t z>0@z6U?G|gmYQ#kwEu~*`tKv@pXNA#D20EIB&8M}kV^5tgCuk2{|S=x4+7OnAVhw= zK}S#}&UXF{Ni*#j7q@O?l(3UtZ5=>MZNRfM8EFQ5^l_+^4xqqi59xXI3k9%?%$47= zYYn3mQM@1_su1INB}O`FMNV#-MVIm9NCSkVF_xr|RU8nK>VoyxD~onb*KxcrNc$K? z(0n$0AzC*&H9g{sXoHhWC9FILb~jIx$FKR7YQAY}{|=;z4?81>A_1dj`2ts6KDlcz zARt*kiQZM-{3@pZV_^tUC$8gHHU07b2qbgnzXpqpGyiPFS4G?;933kOPH&R@nL!f79+Gvy$dyB ziF<6jOpFiB%-#95f&!NKWT?zLVjdf?B@1Uw>`uimcEWO!s>0bC3xnBZUGb4$LX#sl z@)Y`G1~*hZ;-ag{n6WQX1nEtS7!3!Qw(b?IfUPwWbuWxb7OtvJRoaLB$Q*k9+xU}tz+|^{(kPI1%_{5qfAwO;m+{tj?)ytzpd#OwDN1s z%PyV1>^kH0g(ZJyDc#Ea{s+;A{(HgjzYj!YscgSz$)J1T`2PVA9sd`I{~m~r{|m(b9|1A*i(~veD2!|cMN2&YAtAH= zr^wVUW)#GmzwJNc3;AvxU&_~8N92Bhq25FePgOl2z9J!Dn!6HYfX6k;NS6< z)OSM!&kR<*prAwqX+Rm9ut&(IMWpWAh^mw*VSowk`Bbn=+MS(ZOP7^pL_>CmiGmyE zakONPQDoXq5Ut9GPQf-s_#Z9q^9Mb3o}7I-*9gukeQ#f(4?<#+eFT20FnJ31ofxA&d|k891R0#XXpf zVo1WgU-W7FAkBzfI3C_v8r4r5e&5UO@rzt`@jPn_uLcCYROMF(K zl+H0*f$*t)%eQZ9;34FID78i4V@8K+s}KuwGp3>r(2q}-LOyN_C4}oz0WWxclEic7 zWoDHR3DEY$bo;7oNulbm%hhVw4h*q)|KYQ2uCv={@Ok#AZ1w)SKVNZjoBtNvijhDM z({Z+5J0@q*I_;x0$0=wm{dPwXiIiK6zbVZ$HJh!Bi&FCAJ5Gi+4&M?}$nQc@6{XYc z(?(VjZigD%g@rK67$xrC$#(N6Aj6EUa+*WXKcZ-exy5MKqGWb^QpMBCsRRA6!*EBa zVL3acGOV%_j1Kn4s!<#pP87I9O9>mGUsFs;k_>a@yG<#>I&qGg(@}SRp3`Y_vYW@FbONrR9!*4JP}zT3^-z)FZ6uu=K$!sh8`_&&a?uSmr56J>~)RsA@(~`o(0BU zvrIRL4Xnc$nH4#$sqn7u&Q6_X#Ti?qmPa6I&*D*y@jP1B@aTHq-T#T&T?};c#n92&!4x!K2*e>-yPDhmjpzlD>?dkn5x-{efSOkB zj9{jGgh&b)bE?9URBo{XCuO$v?d%4nF74%)foD=RggnxuZJJ@db9`-{Y*#ycN&dgi{i2L3AeJT%w1MwtwszwLWFf)&~T!8pew#s2jl0ov(~0XO7!Vzykc zh3Px0_wl_FX4-xD7L3Onui08`TGkzSqmw!qACPqMwT8X`>PP)GUa%(xsJ>%;Wvhym z8CGApG1Enfdmj^ z3nwF*OcWQx0^C15LUX7n`$^Ql){+JJrE*E*<&;oW2JW5FQR{xSm?JTf*EW-lssOG` zY^EMGYHVGv-Kjw47}km0L#gN|dOLX4m?6p`nm%rXw~_FXl&TUUJX;&E%KmJ&zNSx| zRP|$2NA+rQ3P`@eeMX8`wpkkbUSO}a6`&0DdB?DvY(fBtGd;T znv>d#C|x^lhg$KoiK|b3TtL9fTf2YB{>TpP?7%D|#QIc}JbC_S2iMkDhnnvz*5FEI z!ww|tTf`y_4wa4%i0oE=!$EKZ+AqGrEelS0E6RW1s6gzrFtqHJ9mSn4ai5d%O*DD zcTAJLtEytFWy`AilP&yjX;FVpQ^AK$bXAR7n z@s88#^ZRevPrN=*!7fJmkzOm%nzu>AMcZZtUfhWt@=0P{XuY-=Ci^LUmV8zH=C}hb zg=TC^*wUUWfC}(qbipi~2sD#eW3$ zEY{(HotD9{ykYs-!4my_8)6$5`3mH2yu4$B(kRb~Gt2j&dwAU6Lm*XpaH z4_YCO;d@pI8#0BZ2NW_wZYKTt4kaV-lX49O;Up)VG$-K}kc(K}v6&IMplLtTTXC$- zGm{xIhd*UH+Q}ki14MP=;^zaEKnq)njUh&PDWQ#!;w~6*sw~ZFDkgI6-g4~oK1?9GYgx$ z>ThEU2_rk%hpArjbuv-4C20|{iPf9=m@RMC-7|Gzm;=k$y52X0lvp`4zZvBD@YJpp z%~-)MM_*6&Hm!d4E|FDHaVRKTvL}?JD$HnI%bORwd}zx}b*)QeVh6YK0s(ALdb|t8TK;R2{z5mb8^p_R>BRu+@-ULag9Hr%lM}`>& zN5+4NsD58SEd^3o%??9Jiw@l9_im|$jI0Dc<)T4VaZ+c$!N%5fRxoj@4z!fEVbW5A zU@EfyUhoD|X2A~ZH-KXQmqPp}#QQx3kkeQIy(RgdznhJN38*Und&l!HrOzMVvs?=_ zPL~q}cxUKzPQ+juJ&_C+RBbCejWt|F`8~-VS(tQ!%$%)2DlvZxXZ7W7H8#Oy(J78< zI!Lhh@qYE<{(ao`#r=n#kU*~7I31vu^|c2dQ4}4(W@7|_830ye|LNv@^U`&$;}e>!Yt_knHn`^lW6ywa9swNb?f0+RMVY~_YU z$HF#-md$V9)@pED<3%oTSKd*g<_lxw#l7jOw73YnUxD8rlz5kPLsfKzy1@n%_za8J z!Wjt;f$8G7*3BZP*M!!p9;n#OU64;Bze=+Sm9@TmLd?yjlP%$&G)XL%?O2eA=*gWa zSNH*oj%pTRhNaUzd+l)7^Tj{)YNQ>~ZJDOW~1Vz|Fe6jU`EajwR;gC7HT9r zNj5TkOHDT;zFZFesax@_uJnH8#A^SV^5=s6AKTF#>q=O1P{No`5uP9W{J$k79R@x) zCqC^|yXPHAaEeGXwo_TBZc^lN#dN&)g{Q=Mg0|T#XVAabOx1WK^I(Pqssv`3dikDh z%s$8C)~jeRe8~&u%lBBDLokL@Lkk;P5T2RPQib2Y42bz?Yc0QwQNbv^L60I~SJK1~ z?w)BJkXDbR3=KQ3eB34|&4ZfMjyfj^?s0p0_SN zJmqJ6cHY`c=yfO=nJ4|==Arn*Va(K0sOgFiQCIqdlnGWw;Yai>v2PXDvgD2Jt|9UL z)Wgv_5->kGME{gnWE>c~3>V2x^@MyE28nSG%>y;8RWiz-y|H}436$nr9k@iVI0~lK zKK(q{jHQBJ?e`z4vsky-UvzozHX<=y-1|4AxSJo zW|LwGeQbbX%5H+JEsjX1M;jWR%t%?$*I2)l_^dv&{ZW9LR+@&>1X*t77kklS9LGgE zu~dehRlYOo95=C@USQK!Lsx2-FA67)m}VZF++_WA%HoXXV;r~M2VX8YsD)R2jIDJH zZDP!jMigri9y3C%Uh)&@fkF1O^v_jx99E#g|Fz}OY#(i;bof7N!knk$oro=}T)4Uu z7}e720$50A#*)EVedt>u(kFTcrb!_k_*BJ5rd*8l;sg%bGJ!oZ0KJjKIQ0>)KE!CN zZPM&!Vzv99xbHErve(7XP>lznBEIv*m5;!w7wFUTH_G#$&=tprP^3*0HJ~S8ezs{J ze~Bf%?em)ZA)jAkQ|>23K>zHYH5rXa7bmKGC-#z1^?tK|o2cPSM(15xe^2b$AhHN? z^Uu_U7XEQ`GDYBWs^s?Csl|Kcv)ke@)3sP4vDT9v$5xD-UioGwG`HY4mc6KV7Z|?s zn+Ko~g?XXcT5z^+a4bjw^X0&NHM5oIq<6Jb=RPy^nF|H@eK-V$!Rk>6UK6}<@S&<) zJn8DOPJ4cKIF&Rj^437DwmXri`wxmGk2kq;paR&ec9y;v^!66TDy{7(hNIlWV&8? z%J7gYYhxY7{YCw1i47iEW@&p$H5Vy{*h^o}a8V!(MyO*fYs1dVDq~8UUa1 z!N=5*L~VpL`6B;W_FzH;T7$TZQ2A@4kUoTJ?g_)t$(~~oui?*N2Rb8|jZxyn1hT4>Gw4WNZAgE%SM7 z!abSh!PYAo-A8$%%@}4=@So*s7-e&r9-!dJ6ciju|7U0AZ0_g)QZzc6Ss1$hOK3D{ z1M2Jo4Mdb8p$M2oM)_G2UCoanGmj{N+f@o@A0Dw}Z+^U9L^af0e?{8secE*AaXNbC zLrZ(qg$QWcNRo}+_e{DFmbL=}eoxXCsQNnV{N}c}@t`x9(aUxOeQpsJtpwMHLxtTH zTlEymoLF$9>lnk%rqxpu5hgI-4cdw_0?!KX~7d@Ii|mC2noSc?58xaDq0r$$a1 z#+f8%6T|fO@dQr|B5-~p8F!2z2ymE#V{6|!y2yQPmJxXPi1%s?$`|jNw#MhbZ)Vvl zy7`qfSaR*{zKTEmsVDo8^U$sfl(qhv!S^4OV80}ZuEs8azsnr{h&u|@`5iz>>gqE+ z(kTg?V+aE+JOo%OC){UJ@ris@CD=NTxNxO>8I|hiGjD30h!Z+o9Y#*ie$Z*eUOhaG z$;>#0B(l=c0QRmPY&Ce6g{*;udx&W+VeR#OrfU&8xTs24Jy^ws^-P`5Mi13o*PTF8 zsE{W31&n8BZXenAFfS(x@aSqhDV5*4!l71ZW}Ly8Md)b*-D(%V&UAZwV7xqli7FMI zDuV{B>W=xhKj1snl%p<#f75br)ny-Y1$#zIfOABponDLSP_#t&d>{)f(z$JyI_<^k z>p@Wqs_KsDh5`#v3%ET0S|{;^nqLM>T2M&K^qRZ#NFL^h@_s`CbC7bzYC_rI-pHA3 z*9rWcV)Xqd65oV&ByG)o=30!$&m?j13!8p z4oT(tou01X;#JtVJ`a4|@WrPQS|Xi<*2Yd#B+D@><9a+F_4eaqWA!ltZxIEWJ0&k+ zXEGomL*m>?j4vP1k|P31$FJ()m=O;4wq0D-*JAeqCtY#24$w#rlvb8>nZ1LhCw2_& z{nU8uwnA?zgBCLbHl@gMOTnF=GCyPW=z^fNJT@7F@jfe~@cU5(G=?8cm&ihWHDMk% zYsM!$2l>MTU8NV_w8}eadJgfUdc^4HFnqfsg7hB3kPc5vQz^w~r zWyH9-;P6`B8hA#L%5&FCCjnZ7lP#`&>U0uPsD$9-VuD&dJb|@^DmTu--e(ni$PX;p zdN(m5Ozg1){E*Yg%DipQu0LI$yk5Wm6O)4iYT2ALH~6UpYKNc(QL6tzG5ar|aI|*( zhlpH5K4x1C1vH50+5)+u;1iEyBrL*k4I0dZ%@=gouhIGnsSEaHE&PkHdmRqy^@66B z7XI}T@eArVKW@s9E!zxUzp~Nvz8_F8NS^}EdbJspt7!wag9Fszc<%IYG#dr%i2Huj( ziJbu7AihO|NVbp7wD6nJL-2ds(Ih&Y8t*cqlz4b7mRd3r-{(TboUp7YYit2(j0b?B zd?(6W;#4m-U$4$fP4t9NM>u>&-g#S-l0toBNMG=b?dFc=B7;c7Q%AqkKgZ(N1~>_T z=SmkMZ+#twQx*@URVj)B&yDEg?-iaQQ`AN&3xjN}<1q~ruEuMOCiJ{Gz zWU%N*Vkp=-qqe3YYTlh>#yV|Dt;!t!Yc-Uq$nZX;AOrSwdo^Ol^e%unt>IyC& zTc4GfH(DvcrcRckmX%oT0`pi&Z?iogSZcueIR5pSrD^SZvgZ<9dmeEzOvf|fQ18+L?SkzC z;`_W?!92?}3;1`L;o1$F^M{4x0iB=Je0Cl_oUEG!HP_ouP=`!$eP=N3L-&*dNSm#-AL z&v|Mxc#nLk>Da*Wh+9(+y;do7zI<>SKy?9br9D{xe>HEfj|1f{zqizQOSoyf4H|9ytgy%GVxbMZk3^J z%E|E7tAOv1S``&Cfw$a!m=)Ngic(s_KDblYTk24#kC86Grj&@}L)nE()*P$=zVY~5 zwN*#Xe71n8S)^}FUtcxgL_e_X?9M4b(rDirAoPD)rq$>X|%V@BWVp_?rb`4{W+}I977=l^hJzhUZ6#D00R55JS zBsS0$kq589NEtJe?IP9~CF7qaRnWW-$Yk?1y` z>7&#a`6+V>c^NcK?2C1IJbX+iB%Tp(wzD=kQ(Mb|scRKPt9RKPpIAhoEhXtli~aL* zxfOtOP+q`q`iT~W!B@e1jVKLxt?NOLsn~NRr;EDN!#yHREGli!{{8ux>o0HGtwt-D zOvqh|Y9Y~;7|t>27~x3CXs6no1$fs30`i=%tW>e;(kHZzv-06Y2TPEn?Q7Z!Gg^vT z-2yBI3y+#TAa+A~XipYq+${1g!0JM0j;DdK_jk`p#wE}mlR3mfkVj+qh}**5T-9v1 z_2HWKuQDTeJSTIhU@<(im1ej!B`Txyni66wo(2L3%DJ$Llk?ujg*rZtsTq^#MMX?~ z#46uP%EH_@D(I!l00t)>Zo1#fYBqv={xlC|MA{i33Su8E-n;>S!~WNq(?4(e`;B`9 z^>F^dH%`bh(hV`I42&=iFw-f&acOK!z$9^QDxnnrGWh+SwjnHbX*CH8KO1v3WDd_8 zs!pgsN&&ws%h;n8Kn2z}Zw^3=d*CJ}t+}Sl#Edg#3HPkd zVYV5z3wyC^^e$OxRLOwFXJ)j%6917@8QS|*r633G0w*R$IgW1#9m-jHrKl+)3vB9A z4aUW;2DpASLd-vR$Z>*%@(%pvy3o?PxQOu%X!r)I;5mdtKKL=7^0wJCN94*WnGOst zRI`7Cec{#Bb^)S6qsJBMIj0p?gRvY!MNa)7fWi@cMWXP?0ECF?AoVpBvm?*V9f|KV z3a3-*h#8(q;mUHD$<#LHtQkID61=5j)rd3VavaoQ3^e66H)0*#pToy1oBu?^_1Ppk zJ0Us|4fgmGJcMvL?mdesBrQT38AX?gQXm!w_5I{z@OcvHvt(a>H4t zv5II6mx$eIAN7e59M?`fQ}$t#MFHY|9o6At?95H9bvBlJ;>RQ6B|1n6MkbcbMQVoV zZ$xXNN1{QDbqt$*VPQyH5~#tYWmb^LZ^_avIl$Q=31{t82Obu}%l zAf(GHh#=n$-DM1_7r#?h_~$PDtSXzH`zoW3YHW0qS>gNhi?usx&sCyOfVxJz*;$|Y+ z@6TA&V*AZ{p1o^EH@<%RDtF7h>G!*NKnGcb-@(~U9`pGJ2%MF0$wru9|GGf$m*DI- z=>`avi5hml*#4IaL#NMzO5g-JV7Jo#Ft)1^U`L^3qunrwY!(wFu(61}j;V@~4ZP5UmFq4^v!l9U zZ2La5UOr3?qixFgpjl&xW6lxCqHak|@qv5M6)W|K4;!R!ywFrd6|Sg^jacX)TJBK6|Li(WzLNJmYC?E^HrE+eJt& zx8mewkRoLR(o0`n+qzj*JxxczB&7-%4%o+11@(attMZ2*@52F;M?b(9*3&5hbbn-Exh3l12y9!; zr!dC$^2dFQ#g5JF$lCifZo7UoMVmLyLx@+PoOTu0%Kpwo^bYd%!~DbZgv|hG{ZYCz z;@*se`^h+_dx1J*5OcnF0_GAEFib{yXnoT#?~CUP&Rd4LwHFTa&GW%F8`KE?pFV?u zg^V5P)%zN>de1+$(?a*}3^*{Q^rOV22vv}+Q4->1rscp95ixPnjH?DtE0>oQjhh@_ zJvy{WZd}$cJ54!O1|Y(YDBX@?tLqj0_*JW@zF|V=3+BhWh)z+ovQ6woMxj zY0?{>glyjwkbjyNjYFDl00x#jGzzhoKUytFmF86vQd`GRVDpQhSihJsat^Cn#NHPU zlVJfs-C4b4<__iGCAYwcytZl7E?K@7lAL#9h@)=h%o-v@`?_qsS_YPeIONOwTxRVr z3}aJ&ZAA8x_?Il;Rypx5;zoh)vQLcO{_o41W^ik&FDXdu%9_ zW{{cf$2w2OVmxxIanckr1F78%pCOg=#>BkImz!qx@P}Ryq0VjAJIcE#AMQZu9m8o>5v)&oJMe{&O zkA<>QnY9%^0wQ|yw^dh*5H?~@*!CM_`pjjq+H?dLzEY+>Kqss;n($NP2mS!Z*UFaQ&r_ZF2cAn$_kHntmOxbk$an z`duksX&ug6BbeBvyPOHTiqg5D%tYX&EZu^JPE^Q8kSrJ#QlKgF;&rdmQ4yk*ls!TR zAhy#-0%nw)Qbi}H`)f@%a0p@%FvnA3DEKbzGJ$$qvDk=^k}Y!^t6J$4HfcdOMCgpt zgZ|hNVp^VRu?S@N{bDOuW^MTtbxWH>1`)DrAEf-ZaoLBvoM_}&a@a9>#FExnYI3fH zaHtRgpha8n6VoiCxSLP79~Pt#wZG$ImTwEk8!%GVl?q`fbTtw0>(j6cCS8p*!_Wf~=r~_HZ4-7l`hQ7v9!`8E3F~E_`G{DMfVzPQ5S>HG6ug7f>9~?N-Y{YqZ87u)jGt z&_Q&*R^`6R{X`}Ak{~C>4`XoFzg-rt%?@1c$VI49?P~B_J{fK5+vHieP+ms*y2fs5 zv6UK|u{3TaxQf(u=rv7^k(Objp_D!PjlZiW&RF>P`}&<%Be9%l`E*H#)Zhnd6|gMV znZQp$mYv2e56Ru!xV?7HkM~IVn`VJB@pn2Jw<@MgrO)#8t)}T1-Gc({kP6NYw;-7n zFLFu$n$Qc?nIkunxgm4c(&u%D^(9WjCVbTHAG1Nk(T74m1|n67kloZ83N!5(Id-+9 z=cmYPeDgi%mJp^-VeS@NM!6lF_DgpV<%46Yr`BxR6TUp#9SByrTu`_h7?-~|`0&lq z@HTN*bvK6}P8hp}&x(&UN=LWpKa4V-d*HY;R1vWdA;6$!Bsda0{fVOJcoh2aE6za$ zy?$G#{IAn%K(P&|L}c&sYx>2XwDj<_G9&Z~!^}gB8h&W!h)AoDJ|bRC@5tkuEp@$a zbVR^F&PP~C96kzfYlAz8+g9SL^+e{y)6BcKG=W|eYvolkly9b|4-OCKU4dL)ZEGK- zJz3~0oH?0?%vI*C{l-#xN2hpqXRGFq2nq6s)s^RLm6_Sx+T@a3U#hldyIL8N7)N^$@Vkr!%siSNRCikZ@UQfGXy?lz4t z{o*cso*phP{L5w9w;pU)!hiv0l+4?cU_tAxgEequCZ?Qcm6Qh-Q8AnVu(m_=1BlyH z=1AF!bAF7xPaml<4$~>M2jdrEf=fIMYMYG)&11(FzG|D(P9$UT7(-}c^h3 z^CWB}PpD6WtKhplu@S(|b~(DSOmv1~uxTAGMj<=r6goG=ec#OzWC2mA(#S!Tu}Hei z(E!qM9HG>5a15bAN@i)VWnEU{s3A1U<7jcPZ+2K{T;Z=Qi?xB`JSLXnv0YaVabLHp z|1ZMcDZJ8XP1mm2wr!(g+p5^M?HSv)S+QNQZQD*N_Ri|ntFPYc@819EcQ6m;HO6@L zy!X>kgmII=IywiiClAw09)w;B13wc0$w@j+PA?I$B2|-OpDHVoho8rb#K-%fa?N91 ztjRdU>Opf#xvQX^N(69Y6+A<5Z45m-&0#=qjf83EfR1^fZ zJ393$I$H*Og+9zO2M5_B!Yy65l-XSiBBT~9|1q%gcW7A3N=S9=%A1wK*y`IEZ~X0M zB@C`W3EXG0;H}ZS)O4p{A&yqMxR)@OSy2JwJhc{dhmzs;I=35rj=Bf z^L%s(D;Z2nL!q?#yE}<^z-uym1^M~>2G#0{bho3)X1(gyoFjOks7zRt+DR}$m553^ zh7fjtUQatFK^TsJYk~*O7sE@1wj>fx1$%CVAtJGq2I%7M3MX6gj`zpV=^&)W#G_%l zv3`+?@r+Lm+={-cq+q{+8MPL}G*-6{TSmobE!j5?xur+(1nq0zG1d$9&nzTE=sf^_ zK-wC3+7C3pCO7+b=kRM9g&c+SgSF_HYHw!Jm*HtFuMdI(QFH=mvI6mSae-4FhSRnfBF(WLPh~kXOe+Y z#Jgi)g77>=5e(*(5WlZ*-h#DHnL$>)r&NIxB{{pNFt;+=4wOC<3l@5O z-Pm8Aafx6Iq)qPb#=c-vZM>%i!Vf>n?JEvi13+^sH?r+Hn`x!!bF6@}0yQwgzSyQffF z_%Z9oOv$=%#&zt=`ri7HL^jHk(nP%PuKdL^M8l6f7qr|9Q4>WH#FkPqVfzT=RW)rUJIV}7} zCbj4IE^5cdc{y~W-uXMxOTnFPLM=n)7_TFS_T%+1P0akStGGOrp#f3x7!@)#la1%jPk%?HQhY5MA@+;i2Akh*a}#sl4rsmcRsfG7GfX@R z4*#n9gZ?Gfce^r+T!OE!lU0nJmUHJb6)o~(I1v(*N=LEUVL1V+7k$;RhZ&&$hL-9m z_{|r8Qlg#7hj*^t+VDB}#R9RrmEZZjcTf>@qV3Kwf1@DjipHxJ@tACarRk zdnwPjYm)nVy98q_N5(9fy-81JyF;m=?cjO1c9~4gIJX*L+ui_7vJO=O zvl}yIl%~ZJmi2c5lXlGeMmaTh;bU1sox6eZ!74;J%DF_|FB-2`~i9viWEm!TGWdO>)74%{+3z1jJp5uoCCZr%_)b6R+i7%E*N z=x_(kRy7dZ*tVFM+hT9D*Sabq;Kwagrh&`bGT<{(1!hH6C8gJzYbNke2>Mj@0b<&6 z-AgiLaWh;CeLfOd)Z!w7?gXltQsg!JIz~Q}(pJ&BN=iV!Y%Lu9bie(nj>3?<58ICK zkAa&-E|Fjn}z4t1+?LNayA^uC~EtH#_lfm=1r(rujnsH<^nF$y+v#8e_-$5=Np>GItY*-W+07Tv7PEFLwR0i5kv+O()kD6hj zQ#{6AbqdpUWy*1oaP@SuDCahV2dLU*oo)Y&AUgdu$fXzr0bf%pnz*b=A*4JgB#g1* zJM%<5jK0vqOMks?KSIpMLja#9v7yS_T2Lvnc&8~u`-!z=(49&MG+_r~%1_J+62FQW z23xbO1u%TVEg3f*b=+(y<3=>I2sTqi@AajSRgGOi!H{hQ+m@_=qSUvfBNlT0RWCt; zEew45j$fv_4t`hS?VpWV%n0V4?pNmjM)FPc>gQg??IO8iKSEX?Ho3VPs_ zV8$wURN%cVCiSIx{hbGV+-KpGfp!bW%o&bz0D!ck34W@2F=dI%$o_^_my^zQnJQx7 z1;cugDwr8Y85(A$)`ucXmv_KfzRPn{^YkK0X84M)!OeG}0I^&gb7P;>Y`Kd<#wh7sWB8s{maO^i}7 z6M$(f{7SIl$)a0~!^m~0SEl>CYLjvd*P>zxKA2L}cy8!bM+fugwfxc)Hyg_8{qZoe zPmPD%&gz;_=^wDydPx>M;e6x!X0|pAwxb^i$LMyB!=N&Vq|L z+Yy*Oq9#nPmecvMM^nAUJ7U?e&Zf!{R=~dGYJKhTGj2z!GIr6qBXGk(-iO$Yc5c(a zla}bO8tf-HRsO1naA?sHU*YL8m5miMBeqQgrxE^9Jce86mWWrm{c@>%X~KrFU?%rk zy-*A_D0ScZo<7v(iPKX&=LL=Km4FP1ppt3 zjRXC=jGt6d3R_{Y6({gjeRp7=61ybdo{Y=7!syBGspNDowAKWDx$j3X0h6tl+_gk+ zQUmx!Uh+a~H>KKb1AC=d7}OYh#9kXQ6Otn$&w|?CKEN}RZ%rhFxgWMty_z`B8Kgr- zRY_2f=uH+ZqWE{POdsUJ|OQNyZvQ!lmu+Q@)Jofw~(XW%Nw8$aa6)SYO!hcoU^I4{n zRABuScwd^1@4)2e=d&Fy=kg_aHD3@i_kk!5TJMFVZ`KJ^y?m}@sBPZs&;^i4Y~cD3 zHq1-nNGM!MVUTlyEEXW9C9V{pa@=Q@fgXz57NY*>$)s=yL{8 zS?I*rF6SDZsZ4BQ51snqG!7A--5LM&Q_au)Ei`g7Yq*M412F4u*51i5)YHus){(S! zH8X7K<5CHI2*a&~HrK>mM+;DPvu&W}m@!qlQ26}3fQol98ycWS*)Cs;r1n-TZlQLs zf?m^geblk!_C=ZJGH;ZWZJE1oMll-7>xHy6Bgn z5U0Jf-KFuLT#s||ckW2$mPX=iItewab~?XyU7u(}4r!ewtFGIwb0GRGtHEwSNct7(puQ{?SuAg4}0jpA(rR=H3w!uyeq8FJK0U;3kwP-w2!GOlTE@B6)ATVnyS~0BAO0mwLPmc3XyU z8xf{ckz$%BOjb17e7y`19v~wA23-0t@7GjrTbCT_ZpW95o6BUlFiu#u1?idSOchuil+XA&6pPGUD`6_8C9SOx+RJwOGA0 z+Va_7$!s4{6hivpc@R?L04_4-#ccC73Kzxi2T(sRHF(VI={15H=0y{}G(+ZL-+c6| zwf#dh+?+91(oz&^-x?0W4q?n;sT5&{z$`HMo!IV|3*MAp!ZC9Q07+SlBgsYuvyy~% zq!oVQmN!12!c?M@ZrOkBK8>Js0q$S$mRtIWS!!@cL#!5G>S^?a0J5fvIMXY_ey=kM zZ1Z20kDTV*9_P$qCd%45y;&)ZTUyTPs7G;zZL28N>7jjjFiFpumcFAwSTif&Dm}~$3aiKKd;!KPfJ5dlsdd_ z5^y~cx6o1FW<0BfiWWCOY!=e*akQ8|eSB?#eVqQRQbHXswmaOx^F=!4p;U}Gc?`H) zmC26mWZh%WHv4qS*xvemvDBTRG-RK2Te@04G2N#Py@6`$ zQ8q%4FZHYjw&t!{vB7__hZ;)%S4KE@u=>&c>Re0AT~NX$C}Dg&KdzXEK#uyS0>1lj z;i(p^MpPi{&GrenNV>kHIIRZyXDTAbVfTK`FwRmF@!dj{z1Zqe++s0Ib>y^ zZ|Dw-{%!m1pU8n~f*-gPIs}g2527a)dee%3=FfUg-yZoOvl9sKW+V!ot^*JuSy~fo z5J%4U5k(uND`ZWD!b6+YYE@?(g^~^N-PO1Rn&^O$gPtQ@A>qg7VCW;9G-^*qkv$(k zUE+Z{X;lU*udn-?@_@f(8puBxphzApd($No)S;`-US(LUd{VpQU)XKk!Y*-g(&u)q z@G8VufAd_KUZwW2J=H6ZB?d4h|8)zK^mNkV{c=oqr9lh90xJt3YbF3Zx1x8j0#;z0 zN?AiplF9cVg~*{u`wq&IrYe}h9cqd=Do7=F1ycx}>64HUWO5J1jP*}r`BVh^Rtg@b z<{IN&{4{Z*^T(ETVQj>Z(7(|6kFoMBPN%JjQ~~JW5tA(--cTjf4S*Uyp07uaX;Uqp zNtH~-6>w>Xi3-})1*loGDlX@#I7%#d*;Z6@ls0s&Mw4tCw{;&Bo^3QyPb6!*A$FlM{+UNLjDd(X2#w%H+h%&7?vdFwn+Ll1!YOHK@3h6v{6dC*Zp zwJNbj0B zijSXa_&Y{X*BoH`nxV9$bSM5P{K@-UpMhe2ernvmYrQ|aK%ENc+AsPKyDh3S69izX z1Rs&u3}^$|>)Wj1_P#T(Z~l($yWrmu@Nf6$JsLG>RE8-@S zabP*SQ?`m*OYTVla35-x&y%CY|8DoX=vBp_<8zr$XBIMCzg2mX_IfQiCp|r}D0&{3 zHFi_KAVeXvue9y|aYe*}6MHvi&`8XQYMgPlDW>KSRfSC|NYk;h;-GtoU5wzo z1(BX0E(~DCzMtSmQ@fWLO0jyTYOnM(1H?U_GY|kuKD>gZ9)+uCUAZoZb&AGvsmMEOAm;$!#PBQ$E+@&)K;->o4pnF` zTj_#*ZVu@yp4M$H74=M@LcT9$wDv((@i1u0??B0`*OF05^T|6*jv}^nNxqKhc6=Rf z$tgAO2v_j#mo8Fu;}{YGQd>0(S|KwZOJTegeHr19opvivhPeHHh8?ezUeN#WlR%cD zTfE4(#|bO!r8TLv?$F2XwtV(2j90^h^JYc3aJM?&IIf|U17!bT-Epmh00V-?}=p|N9NhmGk zYqgjv7;PI6i2YL9TeU)AmXkk>T-;|ySrd{UrJySuf zytLbDa@WO?)A5zOutpcOG(z**5UQt-58zS>hAjk&jZzYSpDsoQF+cbuCZx7-|6L z!u7%7ZPud_ybsSpTopy**89QNjC*J(!*lC#jioY4T7qf%Xgk-yB2hk(4^B1o4v+*W zp&evcNOk!r+^lWd?{~9_Ze4D)rKwT~>k3g?n7~%rtkPEY@>f zwl6NHZPxh;i`jh;98acvX|k-3EPLpHUhG-@&_`2C`N|aN^~vYrWJVcmf0}ON2Ti)F zm9!L{o^DZYd!ZpU=|;|?-n@6WmkIyFe;9K(KZMn}v3(4?6vIHEuXhRd0r=_D3zsA9 zZ}%9)PCgVfsh13rz4%2VY2SDA?}F80iW~NSBGLb0cA)&NOa0z;{ugxXUp6WKx7h`> z93Azzj2xZn|IR@dzj8;+1N-sA?t2a?@IUD<|MklT&fm&tLzi#Qt#AJIKQ{WmbB1hn z#n^Q=Sl_1_L}t|Hx~ydz24O!ORa830Jg|tX+El`uQ)|xIsb*M7NrsN2?${*j)r4#s z)$Wye$^?R!_=I}Rsw?;e#Shg+IvUm&QVo^64?#d;3xq09LU$U^Z9tW31?8SBMG^TI z|&$Hl1 z6+8y5Obg7TQaWb}7sD|Vz;*~F6Efwj(=FFf@Lbt7Et1oXvFvkv?g&Lr%;?6HFJCjy6qutAR+!Pk=F$Y)HWlMDG%6q zFDy>lIY^ZeFZM3`%ZpDp<&pV`dSm%gEE4Gq>F<958LM+L$;sq*XZ?6ou9Zu4I~o-2 zXhSYG;0xdW73Lz@NI2A6EMt5tL|O=l`{Et2i{m+HmA|G_p1?jw=6B_;(Tjo|(ORqB zKwSh64lfJia$R{t9Bl)+@(UskkEbd#H1SM-L@kRps$!RBBNCm)pa_dbuoTaMva@+k zsX)!7X;fjX@&m0k@3<3=g-=fjq}JAA`hBU1>X)-icQy`HdXz*B%Gky$hwKlS){QU< zowUJ8TPp>h!1tG#(DcY#a814P-lr1@wIoVybf}1iSR48oC?nk`4(H)Whp$mV-@F8P@Q<%9!G7Aj=y-;y%4JLIF=G&`Q@QbH*B-|C>-72h zaNfkA=%AVkP2S=l%+2FG(4ja`gLpD6?2*Qs=8DT#KVP#Nw%H?H$=~vbb^+0F6e&+4 zr5gC9Y<9#VJR-@J2(!l_d~x87|1>l77f#w9x&cG1atWhhOt3TXke~`6A^(+LGHzXD zW_7Yu_U9S~KbdYwpT(#Amis{Pj(wdlL4=5Y{J{Z+v5M9D^SO12r65ghVJ|#_M*kfI zzw^RsKdeK{UB=U>WCDMuwo_Tn4Ovc8;qys#p7(@2X@#JbWCP*#DsYaeqDp%x*=ZQM zbap|FW9rti@;RLbu+IY+l)%daUkUFOGk6S@M7-QBMX>c@o2;2Gx~`S{eO%!lR7oPo zZkW6cKYH(0040_SCHr4#F}lfX*XpA0$z}rA7!;PF=dD7)v|Q)tMig>dekhg zv$V;O;btyt6upAMmA!hBv>whF!6Pzs{*Scn$XT-H?oh0Qqk%7=c+S16%tNGmgIXh6pw0SB~2h${~Dtu0z*I)QNed``@s2_H4iPm#qYO7y8fP0jR zqE2Tv!{0=8Uzp#{foZsgk_$Czk{tgWu@@^%>wbl#Q(Z-@N7rPRTT}E5jEvrZk{+xt z_|`XdaJSxVFW?Fo+)oy6Zv&rLi1yFYg`3Dm$8HNKydt+2oG~TUt``My1N;2JPUBSJ z00BcVa6k_v)8-0bRM2%SW?qK2KAZtk#LjX@qwYA|l&PC7Mz}DFMdlSOI(C9l1`1}Wx z3H2LYjekY-i2M%ZFVrHP&)XPEc40y#mXeE$v+oxBd#O$A4*W`mkvBI=azz$jddC;^w6O^<#T zlc)@wNSYsD4mVG($?M$@C3LDg)M~vN@h^m_bvM`cI>oF{=VTuig z6E0Ry;0gks+*1VX)CUIyGY9nsg`0%Z`;{j?i&UI{v_+=4%yx4 z_YPV2S*onvUCX1UNW$n2jcIiMfneezj^I!D_`uWW4{G4M1$8F|bpoR_#SxsCbUcD% z_KA5?1hiE(7|PV5Kvh15-b4(zPSCyH)Ah{RQsqy1 z{z_h1N}KX4>-Kj8Pxmma$9)MTpk=bwo7dMVph3bFAFBoV6w~4edEBVK%C=HA1s@}K z1A=R9LvYzI*5ETXr;|%e4Yt&%Iz+M4;&VL;q5#_lfIoG8rKcUe z|3iFP<>CV4xbCZmAcct3^xAuL6bF5uB8I2QZA|D;$?yD?^Gq*=BW1lL97T{ zYl+llVELm;;Tx=B6O-1FQM|nk^FyYZ?NV#s0-SuHHugIhSBoKjhT&mK=h>kT_Pq(<+vf~9)_~qt=GAbmXMCiSl0a=ovqi75_bsdgx*B~_ zwW>)R%kj#5%oc#mT!uUz9+>%YcPrtwLO`2@?)bdhf&~@=aio?s=_n5n)n?WONbb%(w zF;Tw_aB4yZ6J)(T)L2j2QgN4qx@KD*H`D>B-;#55>b67}SQqY3M0mAw5wZv`dCEWi zz4aVVtc3w}f6YG(9e1uqQpOdlp8ISn4lA`UT*d)cCenOLh=__aKo4Nj-)duzv;@olYOA7DDQlhoBiPyh1)DC->mBU3R|4K>F5-^1~*h!6mrkXPP z?>ij=mv~$XMa?{O%S)rx?80Fo?(kOB*eJ@ay=Va<`_uNlXd{-GUE)3DttmHk*n2gb zNQNx`#wN07Vd+jb6a@@l4hKlL4B)BvW_+{ZQR~AX|o!8Ds$(k`&y#eq(gFCUDpsVd;KXp(zIbUdr7K4 zWK>Dn5iSefHPF6$cI2We0T~8{-30)r_j7$Wa&Fz4EmKsnsUo?VaKTg7^<&mjbteLC zJ~kOm$}Sv%mkB zDd)uuX7dmjX2mX=FLn6A+(&UFf(XC$@?}GzOgH^cEYQ{)*f23Jv^-(qEl*r9Bp7xQKJuP+9gZ4eXExG)x zX{;-RHp@0)j{0;a3+d|!XkjaC!h}9En7vDIeuu-Ep8dIa0~&+MwI?yktCjvHH?<)C zikE4Z^@tnmYO`L3(iF4ed;*U>yrPU=V`XXVDI{|duQX#}r2_=uHBBDMfdf&}(BBKj zgR@pxV131^JYm`nNB{%A8xgpyDwldaRv}70$2iS2Ixq#VpzgF!@F?$GQ z>90)5%I}^wSx7rtdh*FdmJE8I30gIJ(FJCnF#O-QZ(s}UFZOo|i}by}EAY=0Ht|Ud z`F~4YZT`8t>-67s)HaG*w*8E-Z%r&+VG^45m7cw$6kqV`7GXSBJy8l&NHWkY^Iu4CBQCN5Q|RU zOGGb)8i(VYC8xZ~M1AnY6$&W_M$P1Ww9_lo>2Vn#XE<#GR+T% z0%yS;!MpPEWFxu(O{)n7N5g?Dxhq?m-TkDi_lB{ny`YXK&*8AEPC`e|`FX_cpP0zWYUgH@gGu321F__5LdCO!-BwU$A}rm_xUi z;lm6OkWNa~(3lHf;L2qE`Y;VC67gB-$h2PHz>-iUHw(F&Oph;}KP{F3n9!#ovlr}P zWnOwrxfLj}q@bg9?0F=NdJiL4bJ9rp&DM`B8ZvX5p(jZWa&X^hmB4fi8RM%NGC0DK zo+HNvXRxRYFGtb1mKBL*+^S^j(Wn-MaOpdxjLA%BD9S>9+g%B%F{`$YWDK^JIaQ%Y z=@3?>nXO)Cozf_VV==P;z|!5>Y_P};vdpDjTajOa-hfF(;g6*z7gQ9@pg=?fR#C(S zev5}6x6q=L)7v6yMeyUQZGxGP%9Y&7d<%;)QY}1!Pl!~~J7}eEY9qj0)*Wl+Jon?GRDqNH$ z6^Fq(nWur@7F1IMZmtO;=^!Cy_?O!&ABrn^l$ugjiK~<8ACW|nJtKvO7q2G`3CODM zIQmMD8e3QFh-65t8b;dcdp9t`N6_seZIr{m7KD2Fs#)79n`{urhbPq~w= zz-NmOnM>69#p!YigEib~a0t!}kU~O&IuLRU&9>)3K|fgmdgFAU;mF)Lzb!buybwcF z0w~H@O*_jdbC-&RGP+?>jQR|kkTIKgxI6+VjN-b{1&D|3;+UZc>q8kqDB&$fEUSPf zIS6)EQ7B{)X|=vU4AyqkbEjA3QTLy}z}=r0nWT}T2e3I$XiE)U<1~hBgC`Q&R@47- zpckwDa_wgUD4ZW;{pp_Q2<=eUtwDhVdW0A+n!E2@ZM62*!&Qh!%x@bblozr!2>BzS zUMJdZ)Ej*;frp$S!nrBLhUN*)dE;h0c0=nIVelNH)?(a+tQ;vQ8^}an1svpzR1nN0^SfYS2B9Ee*QKNiU@Osf<;XM<#*>oQC~||kLk!&% zP(UGN40enyBuMeQFT5D1O~7tPrmW>hUHAm#UOt#*KUx>IfJP&Ved{l;q@&)T-clVw zJ&53_Qwu(*)p6)Cp^7}mpr`p0Yp#Z zbV6$k!0j)^mHQiXq=;~KWmFeQj;(9al1OMn{NAx=-%NXSy0_=V(QdD0xbz;5*FenHa-89a<6KtVX3@E`iAjXD0~q1te0{l;wfA$}vO`bc!RKN?M9~_9jx* z#f$#E#EwZ8)`H6h%T!Hf=d8o5ypV&{EI~oW$oS+X2n28>C&Ej?3WCyX5{-8$fn@EV z0K#cgWB2>0rBHt+BLD;CC|em=4rv+p#9Zy@cs=!)lwma+twz(%acCn`*lM67YBFGh zvTUSUu$FudCb#Jb3r{}a344pw9p*WHjwcq(LSL>RRK|Fx+Dd!XdB1Vi%5b9nEREPa z3C*>(@+d!Kj%^g2SIDV(D+opb?%+uP5RwQ#tni67YP=Ok9C~FI-;bvJrJ#Nc)Ejz% z!*9u*2MXWRjozl1$!#_G(?wlbvsq{8SPC7DqQRvYED#hBAwrxB^3sn`~A57dSW7-)F{ zb2c6`f2qu^P}38i&f(3}!s~G`0R!D?>bwl z%#_hV_k@WhbG_V!a6xkFX=YOZAX#EUUALlB=K?W0@4*gV3t5&2BM)qCiSew5SkLnK zj)e{T6>NY}_^@Q^;l$2P<>47o%?l3#TuS2ngFV~l8C$pk4Q`!xAE| zggxZNDFzs6YY!Lno4D6n)+D?qTj(bzeeIg=q-8eW$1!Zj1#g*rIjrRl!1|#E(d)6% z3e&N_Xe*WeCNDCErRnTGjI9)zCcE7N)2_;wm%p z-^)>`9yZ(xlaN;b{L=_L0MCyA29b3A=FX^7+6M@%M~(vrbgOt~t7w-=+iC#(?b?Nf zyQEcsth=ZREjk|-rlGfaa>Hf}+SEADJd#(L^!M+wndp#6U0X*Emvl6qaO#C11v*VT zAJBQkEkQ0?5|!byna}dg$wF4r`!go46DBTUKO{{_hmmkIt|qcsfEIu7Y8Fet=9LDt z0x=#9(uTm;z+@?D82?I|S`=BdSO%LH=CoH4s!$Oe!}zDdWZ=^gLTrL-t0b5GYTgN0 zvv*g2W}|T8PZU`WabPXw4c0>CP`CR@drdwH#xO?gCcGf_P+~sw1<+9lr7T{38YSFI zgO)P$M;IL|+xBEz0RNmcx-g9=RMqh6Z~nnNjfBa}PQMRRWLPMdEw$kf7TWTKq(Bne zrK;aNvyhjKBF~alHFJ7;;Ew#~WJ~!o0QWjSF^*aq7z{3Eiw1UQeQn{x#s21miPftE z-;tcCEhzW;J@$gw$0uEI2XMY2UH!FwH%R{{HbIOGvx-z)z}iM_IO!Nx5;OEgAC+E4 z^&Sq zm&vGzzjs>LSx-sWF|+R;lJ~9d*Cs5f-V=7n-;qmV0fDSuvAcy{WLI1B8PD8SfCHw_ zj?f2Q-EmvKHrrA+zO-;?G99YDEnoEKH&jsZ#|)Y%rLDT~mG3oB%I$W3GWW}JL zGN@ou0ON8qw;!)SkvKU993BjLc(=h{z98`!laX$%S!`P>un`e4a#?tf-f2zvnhx^2t;;0X5=1I2D!cdZ9Z@Y!!Vi{%^2XM(-;T zK*{TO*Bz$6r^s^;kKE)nZdvewr$2tQdTBe3p=oJs+P&islUx_}J6!Un7?H0h|LUQ5 z?JcicEq(2wfSn5)y<&_Oc~0ycJUK7`VcRBy@87V(Ecz{%f;J`sd)&~QOA3@a`VWn#`PqSAMJ8l)+2x)>C9uaGEl{#9P|wiVr+YF zz4FHhY%wmYgc+V#-{^m9`6B!v`bGlg{JQgR`mVv)!2TzV*}odT|72=8{iE;kOUG)T z5z+gpMtuV#vxImWh~VBFhK`hX?GK(PN=p3F;p@XTjkv3Iu^hs0_A0RkmIfu< zVQJDrM;K|kOyGTvn1bk8vV%23uln{qN4VdWO+>8@>yg`f(|~kTt^w_=?HK*y6m>?W zl|cLjf zhLV*qYf1iy+D1uicyow@{EP$2hnA@i1WdkTpi=}hS9&sz7Fjwl+3Bfe?WgRL+oyEz z);}Y!YL6x&p?YZ=w^4X1*gpTpY!Y-NY$W;adyzr?SLN+aXJG$7+BN^z8L%+>&vNiT zzkHvDWEJVyOh&{%cQlpvh;fLQoYYFim&HmgYp!ZtmDJ<@{_aNBmfUaVi(gwzLp;Ez zfx^J})9m|pf3WP>GCKmnO>2^p6x1CH^N~CG)E!;?_9Sct0sMb~*)nBORWyGVnLH9c z=iHR2U_b4f*-<5hR4S5%O*oF=3*uCh+N2r(G+)yQSXZ;_d~CKd%kaY_U}f0`YZ1WF zwKBz1wS%N=$>lHj;7St?7>r-kULp#t?J!vrKot$vAByX(;NPVue%|4Hvs0py)tq@= zCd9OWFoYJ50*rD=WywRK+6u@j8UJK-fT} zza`)tx(`!)o}%qlYh=6~(1oQ+LsUQ}muAhe$9lpZ2aqO|{GM`yWDVEX zSp+svAJN0Y&ejpf2)EV7J`-%>nZ_*x)D(!zsxw$rkN=0}cMwHxE#eTK0;P*{2%dqB zbhHa}7Ngpg#UPlzap{A5nb?qtSN#x8PJ45Y$#1!4FN&H`SLhbmBZ~g}6YUMm9hvFd zow?&(KfoPdZW0eBk5Ailc3I6ib4k#(OPNaip$rLmUHuQVge&uMF=*EBZ-Cn zT8j-o;~dgm7U*?Y1c9WvdWZFL-8{20&`5E6k+}%AwjafZPC0{HkH0u?Tf%DqY2qAHk9Xe35Y*~YD*AP=B$Dma0T2npd zzjmE>YHIIMV>!-YnI=ZS(jIMse<0f^|Agx9kRI6BY4V%5YBPibha$m`NDVGNK8~y} zZPFW=@?+fk+8i(VGv2tJWZwFI64*aYcmX_mSd+NJegCZ&W9YpGy#9UtKK}m(ZT5~P zrq&i_=Fb0A?0i#-9bGMqOulbkmFi?{CI@Qwotk=^Kv4wmJvvpWMoP1ynOR{FXQe95 zI85vSwVqX`MM>f3waJTOcG`Sf@*n(O78x z<$B$9O0`uhb#^wOnE)oY$>hr zWZ|3Mh~_U+IT9+BIZMy}vcEmLKJEKUZ{zb+YHjBytu<^Pt*#oP;b1;AmvkBm@{v*DP zueR8}Z8fp^`7^0VDsVT;VK}+6qCD!%i4^iX6e4j z7X?*SmZ4RIzDz`Oav_${1td33g!x&>=awU2WL$dDfQ}N{i!gEDCXCIZ?>S>M7h{CiCRi3e&{(s2xQL?OVWZ0k}niiD0=dVz;N zGBax_kymVkiftHa6ux$A4c{dj+@!+3K!Dkd$OUKR)r7wEn(aLcEX%MA73u-Th)ia0 z&5}?0M8Qt-Vdz`-zn$25;n*L3RG;p6B|GH3o}#Y}+{+?)iz`GVwTl%fT`*0U)n%lb zIqI4``*<4Na0?(eln+z)r4x{dTtC6$e=mJ{b*mBf;sHxwC1RI~sS7m!! zANlfyaro7ded*V|Hbyx!>UPg6x6E+#0hqro?LQB@Mc0j1{-PRH1j4WEr!a*#Sg#uN4VSg^2fy36k$3Gdb_- zrAzW74OowF3uTwnbxsRA1+(bL3hdT*rYoZ`%U*F|a29X6n9miE8HfrU)Tu(*D*FVU zkgc3HRz?mKtOk4Wv+kQc&hZboMT*s?)1J~}(5=%49@bSh1f9*VX{{}^Rxb9{%$(I% z|G`@j9b$-ND8%xnllr9%P0@+LR$u)~`uN`r;kgFW3k*8~F*(C3DtN@hsUQS>7khp(9sM@EQTo7#1J zd6|Wvib^%Q&F8h=^$(GU_P^{+*p1MlM4XO`phq^a)Y+Mos(-biM|^!$o*m?%)mIy2$I0d%bp&8#lzQkKcVt)( z>zNtrnCkL==l?`qh(wdJ(KWx{IH5D|RP1MQ&EQ@mj(7H0GJ!)@8J~N=yUbiGCzY&o z*hJ|>*3r<=P%!nD)Acn?W4~zSFnf4HOdJEatTz^o@V7gqs4yOW1hJ8#ANB~2naJ>- zR;yIQi;gd%aYo)&_tnY(#_PZ-Fz#+(uiv$n{hBeRsA&K;{6B=fQCW%;-{bFdx_k6RTKhSFAPHd`uu$^){%02eluq zSwD6m)LT*Ld89B68#jpJuaY$7xsjp)^3@?G`+^u?gA6V=f_zC031!EsfD{deC94-Iay}-n-HePh{ z+LYa*Zfa>;V8{XuFV)-i+%qQEwAnnovpWNTc8E`H}|HE$C^&{7yRDK83`)7*u1 zMKDWpSA0T3zXuuO8$w^Tu7ltF<+vVbyucyJVKzZDpQ0*hJLRBSmFl*!gBgp)4!foU zT3Ca0L{|Mbgk29RjtVl=C6hTEE>TfOdT=+yADz@ai~X64gkY#_D(1xgdf;Xsodn}}IvgHy*;y+lnp8+|4<2d%1kn&y^zSrPq4!i0wYvjU$- zH`ui*GQUFtav*&M%-7qH1WpFapN}(;%74~W^A#0mmx`>^xOTjWsg1%4C zuIGif;zhA1Er!5ZuWr-&7vt0g3(gV%>8)zY7FPucY!`m%Gz1HZE)<2~>oDcr@CHyu z(5;Tb?$uT2bKcoX1_y602p@n`_-rj^VD;V;$)g68-pyD6?UU@Y=?KVi_ zvNFAbQ0-FZ^vgflUn(Rv^gz?kAwW&N?T7Cl<@S=y2cMg{>(_zS_Ma|1Vl`BNCcoYD z&IaI=Rq}T?0NbgyRL90EGNqI6EA#^2<`luOM zNMjvY3_85v&6m(+PD@^kPm7eCl_h!Cg$dE@MDSHE5UAqVL@w?M8tBAWVdipkNT@-^ zimkWE-sDDNdSpBV*o`R*Iq83Z&n*YW{7<$z8-FzW(R?cYEA}X;Q?}y5dRwx&b#qUZ z&um(J6Vr1QSy9K4)MeB;aI4-Fu;ejf$ebgyGhkNI(-ITyMy0kOBhZ~FRQ|2{P{MTb zCCWh&`o>#<2!d{kS!0$qsdr|u1$Gu3aEptUs!=*4>~Ssz{6+_0lc2Rj0KY7g_QiR%P94xHtM|wsva+n$0JUT^@ zi^QW(2|nw#8{IDM@qq@w0QfM_vhYs6HpSkLp;?63G4!mf<^g27CAks8P5;mhKvTJ! zS+n4a$eLAtNs(6}r-10aguKlx=e&(9isC$)!>8s1w(cEQo80U*D$DFc+JFtbesFBw z_2dc&v<>qGvx2JocCKO88_qBWCr23d$qgYz@zPSTLi>?KKu9_5QoELg7 z{|b!B+hY@-?7Q7B@RZa3NAE*?G1N<(tsIfo&B!5WJE8Fpe`vvRAV1eqnNM&J*KC~V z4j_)(B?oPXED^fFJ|dTd`M?P7a24t%Q=>w}QZS{!P7UFX-ZL%^ zp8*_It?cXghyD}bgGnxYm#ga1Ec*cgbk95nH-A(aeYVQC;z*OZd!HFC1nX4B znfQhzhfF%4XtA(bFcRsNAhT+Atb$X{v*q3mO(#AEOWuVG6I`tkj;|Ns58$QKje*6S z&^ug7Su7jaTLjVmled&Ay{WhC4C(Lhio1Szy`bjBq?`$e5&+%jf+i_!pQ3@nZq*(d z1(?K1RJ;`xffd2VSrBT+=Gzo9oOx5HCTPJmkw@1SlOM9rvvCaLlcpoqnKy*Bq(@5rh@OwgE!yfq`S$Ns6=SKrja##(-LhY~0(TXZ>wTxoV{&sy` zLj|q`opRVuuJ69mCOI0l&vrq8#S0G@ZoV)Uf2(m}y;v?I2_h+mGADP~<0OKX4CLrV z(C%VAItp=~wgGLpbvuxYv-$AyF<4U_jz&f|X<;#HX~!=6vb@<)KfY%u3Y#>+-m0;s z##8}DJj2ahBkad=Z%A?lpGFsI%rnj|Q+LFpp;MM(i#pEZ^!}NT5b7I!tS$>7h`6BT}^Ruk;j^Z5@7o9F7<$JT(iK1=z4>gNGLr^%vYAh>XSJ-@%FfFy?7B}0+n>cbuy zB{SM^2|_6lTdq?Ryc`-zw|m3Ao77|m2Kh%#@yycfI-*h9s)}wyfSOq#Hwk&1@`giL zgbaZ#T7i0*vM-`*8@{{1Lh273cf-$5FW83u!G~kj&&lL*l!M`ycBOVqw0r_` ze|fNYl+)ZDeYx_^}cE+gsOY z6ymnG={RPA(+9YzvvuX-B*i^w`<-*Z?7U6A8~lm6<~-W|=dCjHTb(&Hy;WeWW`gZa z&p3_zvk7Lz+(C8Q5eeGWD7YoR+iDgNM<*w`J~BSiFmRcYN2j0S_ExxY5^{1Tf;l#q z%D47Pus6Jn)CARr&p6t41`hklfzt)-%riM{Q~BKD28|~uI8HG`oV7GjfY`p@=<{CR z-(}CmV8P~dNU#)eVYU#E>R=u_`!5PFP5KY+a^uNeH)4j=y=}cRbFb)V9&-|w*xTFhs!(e# zB(-&4(s;YL0Gc%GRfj^!#t^H|@&PtpJA$(AMbW{3DHjj#YmI%3Df~k;_=yQ|)Cu43 z2s27Z>M*=({|1f9=&K{-e21*9{gFR8U^!u!xp4S4OU(6@uF_=LMExA}N9!)IgPpp_ z?fFH9Axo)+j;TlcO5HVe(O6Vu@kJo{T?@$`t^b#^7cd9**;8CI?Pxk5c>`EHSY??J z&0I@D=U?#fl6CTy2RmWunxj=ePKq8eR#x-cO8SPPNF9u#!Wq|?20N>GQv+t0P-$xM|a+Z41$B10EyI_V~%_Da^EAGu5O z3oufb1z>%WboKO%EYt?BSh_C?LD#bfIn7@HYmk4F-gwFf3Vr@gH$k7#-UNa&T+5;% zpVE-cS#g$Z;Rlv_h_&EiS6>V^Q5?M}FW+Pj^tc_TAwEQ}c)jrw+4b{!2fxSi`OnT(RC3N%pKh}=xb zs^JIQ^ZBf@7K^zkdcAUq|m ziRTgQfoJ(K#xx41q4JTd(MP+=^{+K+#FVBqu{uIb6ozf<&W3`}ink;3YLNySl`bS$ zC}6~EX}&WC54&m#298ffEXd=wL`ARqON{m&^ZvCa3i`>R#k-bDw^y=%JVZif!swN2 z6A+Q`o7@ynT~c_Ws(s&rri=(gE9x2A#lrWaUKeiH=EhSaHB*LQGtfqks20~ib$pQ$ zA`!A|?4xL7JkNMDaWYYT^sY$UsokI18xZR?=uTTkbhKn3CK>E&J!lSUDqhb1%Co}4 zq^D=*whWa;wsq3j-UOH2&j9;sHEjZ!cK|8rM6P6!MM_ksRZEPfe#mN*vr>>OB58uK&t@Y(aCaB|NssLjTC~ z`G=S=PW0|yAXa6@0v{}9qXRz!{wWMI)fsI5D}!0_n#~eI^>z*49dx=<05mkUw+U6O zuCkHZ%NOU0_%yKvD2PtT+IINynI-kMKBFd*q~M5a|3ra4`4CK4s$~2EBj|vQUD9x) zrpGWJOYN>f9iOvcAP&fezPauR41i|dh3NF8#+opb_(U@e-*2~JV;s*G3pbPF$-P{Sg zjN0OFqAmytx)lWXJCwY1)P#;rIZL`XF8Z0|Wec6t32($szr{bkJ%uRxv46y^jg8^r$ItQbh(UTduD}D2)(&Tiix;xa&%KcD&fGuno2}_-TC(E z?Hc93PSHBsV;K_*`CFEv9zcAII}rS~4979R0Zog|6gq@)oRu}?^*Ib&>R&hh67)<- zq!yBs$skP3p~9Sxzg9qF$Gv7;a|ou68|5&h(gLQbsMsA*qh#w=yN!se6kj z_*iuT(lH21I0NdlBJakmKg4GoBiPtH9BY9}#?8qCIZ)c^8JWjk6IH%o1QuFr3Q3Z^ z2uiOP>byKOdLWNM1AHt4F>FQcF425igLe@@uElt=(RHbsq=b%^m2WP0{A=7KS|bfq zu#A;_r#q|r*D5+^q~(2L|E+;$UF)~0#A^ETD$OM|*HQj!>q?BAZL8i7s^xJsM>2*B zdTZL|X`4Ix51k&J^>z%`WW1#1TVUZd%4{%$1+ioff7w3brZ~_2 z-yB37orE`IL=sIsSyOP9hG?~Uo|tNmhCiEwSOnU|u9!Ww&rz#JZ$=B4wg-w>;>%~` z;$dTF_z(2D7NP{+D(#xhS-08w=a}>zOnx;`o%Qlc)GwtXhP2PzSe1mnJqeMlVzcTEtba;Nk>(*01Z5$Nw*(5(%s6%!TG=8 zsBydqgG@+bKKaInqwo{(<>gidV&l$9ZjFO?5?=`ysLBaJuB?hUJqy}@E=0~|v`}hU zmW3=Gs~T?QZsSDdLncOu9q}5RGv_UWD=>w_Jg5==cjT7hU&-p& z&*UKjI{6Ow|DhCd`QJBX;$UxX^&il^^p{=qk8tQK&w#lEZHz5@N(F{oSEg%?e|^v% z)bG_9PoiP8!MpWP#<{Vh_`8SoUT53>8e^6)#>^_u%RDc0!IkGzNyGAG5*xN+A#-y@ z-P4RMMZ<L*aKaK-yd$TE+9RQcTIrtoml{%H1tR#3$8#OJi`4UUYiTBQ!K8n0=Je zN13XT&7CE)B-9ZB0;CW8d%$uvd#nn2UX+g`H@qK?#n?CP1AIPAb+$O92jzAa`}4Un zGHfreKliRZNKxTTPZX)7G2?9?N+vls!H}hW0p-Rhx}qE(&|0O-g;ZbT;W)maBH##`&k=P>=#BVv7Ix^f zdl*L7Ag}P1A)qOZ+M3ZFtK>>br&3t8^2hVYzhlt{(A8zgN3h-6-%t`(s}!LD2~j!K zMl*c2tVx1uvZq`Kq8J#Vt;7u{@I>HZoTRZ=-nBU=-Q7F(Y3c}^c}FCypcOUiLP&p$ zEsRH^fC5GvcZjE-q-4;&ZVeh-8V1B~^Xb-cKXJUEK;po$FVZERR)kVd1I;J@{O)-4 zC(m=hcY%M_%fZE5ld;9%F@&MRi@(NS}FMqxn|EF}>A9 z^0@_Yn5aB1pe9nN$kJP@zwus@fY47=Fz_N7pj0$pJBLa&whb|CC*O2RlycIP**FVH z$#%S~kgjG;f1It_2VXWKgqlsP<+|IG{o&OwU`gX@STcSwu!l&_1!>~ti4NHmCs7~fE7=gLgXE1ooJCy)* zuhzEyo2LuaxPYt+aEy0{>K1-=_@Hw?wOA14P~O(5hve}eJstjzgfFGzDneOVmhcFp z3AFG9`y0b2KGcz;tlY!FXqoE|ff|`pvUvGBwfQDg-b!JPi)L0|QQFJfg1vCSG z|NiX|o_lIvV$IuXiiywohZfM9%&R;lQExMF@s)K~X6#2iMiIc8%cQ!g7icKv#R)wZ-++DS((0UM(gCc6WFEgbys z!H!_QepOr1qZSQXp(Eea3722A)*3@NUe3eT>%8{v*y1tDqKyG`&B*Jf3XiGM)t9{3 zgo3Eb@iS1xJo3a_EpVyNVO$p?d$%xsm!j^8?{2;1%(@Voco5CUHt0FNcD z-RBR&Dwx22UwF+f z)8IGEJc`yWrVpm7$Rt(AwCQgjU`V64z_ zKM+$}Ugmq~!M44Bo`S}=y}o$oec|M|obvpgsZ4N8abIbA(1=MVCyVehwE5FKYB)>C zD~tfZZVEw0aSAvicX3D0J&X;3OG*H$kHDi3QAu)Xo7`}OMv1<&xYSm!W}?5*bh)G8_=V` zZ3Vquo$yC&(2ks?jZ$=h&}?DEYt#&iM6U*ribbl&N2y7K^+&HIAa2^qY) ztIg5igvzKi?lx9-gZg*3)Z7+9HoXsE3(v=<(>PH;4e6}58K)4!0c-ffsRqeP`}L}()Dgk1>Ujn-kNYisUi`E zZN@qFn*%c@H5~YXNZ}L&mw&MibwdbmCFmrd@Jta>BW6_=Y@fh}I5o@NV#+3XzbP`L zA!^n#S=kJCWztG6abs3!`Pu@!g>IS?@ScPuDORdyw@c`4QU=CAGRxeMJ2ZT!15rQf6nuD6aqPVL}K%*e z#}~oFfK(QFGXAVN{Y64DP_dT?9+C_eF(ZpL46QNLTx7#Vj)sM{$ZWnlSXMBhS6ALf=`pTTJr%u~LivmDoi zOMRDOhtT9H5)VuT>gvN)Tr(=2w~z*q(a#$zE8G-==ZE-Od+&|Dy9}(2eGy$o(mL(m z7&2UnmX|l+TweoRUKdXhpF$-(v*Y92Y;CdbwoEm0EdeJ}KbSmZqGHqIo>V5}g=>>87Ip*cnBk{Lq1sHPSnvS%)1rt92_gw z{Y1?>F2w;Z_!)Q7U%1X*Q61I$f`~l z?rwB~GP31pYS%c{BzDaO5uJOMnf7ir=y|TZ+7JM5IKM9NPVx)~2c2x_4osfE4X31~ zp7~gU@w7cGa!pH%?|qhE?Y4!ElENBGbF0X2eO6HQ)9k-34>WF?3KY-sPMg75j^A{4 z_YjOKmlm6Akp`*{Dgq7G%wHMX$n)BJoUK-8xs*w;@lP%BA~}S@He#3-=+k?4`i^8K zs>}hfOY-Ho`S4V_u0y~azN+{-nbJQ|+T4v@hD$|NgbJFEw|_&BS3M%5UlQvvbpG)3 z(32$wf5SSxisl)@ZrnRQHRh+34)W{2LnEtIdR1IpE>3?LN_kzp-qimiIvi$dA4vmW zp1aTDTtA-~Uu|D*DRajO+^cV%jzN2cuY&|=C-C7c^T>sT;1zMN9|$b}7FA0IVjnM0 zuTS042J86L_qArcORRz&dvmYTvk0-ZG!W(gLa%koqV}YTIOP_Ztng!+ys;dp@R5D`M2dUjGv*Smd`j@ z-45`ORIWs~qT{eGedy`4+D5^VQbv?4B(3w%5iRS{-Kpn5Z})3Er%w5e&m7mqsCkO7 zPDgi~nuG`VZB3o1-J45ieON3y8}R_cM;uANe6w zmLxfXLp4S4}Cft^44HMWuM0@yc z)CAbfRbgt(#dTCMw&#%-)GJC0$Im%`~u>pcMR>D+Bx1e92@ zbTwpm`>*BVZ|3e`X}9+QPFEQ-5B-7)ej>k(46c^%oUNBr6g_!fWHQbM#CELLAZty< z#iyEs9?+mj@D&%6(?$gaS-4|SlrqHB+I_n2^+PKm{KoB$deFq__-Rfl813ZV@7_L1 zK7XdmdVlGEgJw}I#>E1H=Kf|~C#=-DPAJt~blrL&s0PW}dF9WbBI>Nv-CVW}7tAdy z@X)X&J<0@qe6o3%!4;o+4qWw*6(QdqzWud~<$Op~+Qqc&q!;(QkXN67qbzOKKY>^iC! z>0A(u^9=qw9O-SXK}naJy+rg9+UL#)^0i>#C8Ang-IXcSZ=m;&O(V+J zx=>V{u9MPOCuh&D2-TrroUXMT@Mx_6jgFWD>T4-@^KglSgYLg8Kk$e=8qNvkhhtv_ zb`ib%26Hz1&>MPg0}40Lf9PtwJOE#c@TTW60Q52J-(bA4SLFL=`(Y%dRSjGivgADS z+-|9~*BAqewj?z4CLp5Ar|Kr~h&|HXQyeQo#NU zz9s+PnkfJEo&S+;nOoV~{(zY-F8>*SJDWK=IJ^E|T6Zl2hb=A?z}#=5MaT({*3GM; z7ek?u7U|CHq1J4>r6FKpqIndv3liBVDe~FZ`yG#;z51vmJO}XRxbeMd$A8&?<+HQ1 zHqW6X3JU;qN7={qe|jWjHB=-I822c1&*(0pmB4Pp8!}D;+#zp+86i< zJ<@8(ifX(b-ahHOAhyBJ`AZW)4MJIItxKA>i9UNfF(I9!Iy>Qo&8edvQ75;I?Njh7 zt_MhO0W?Hwg;HhFD?;%F^w~!&vmFFXjlt7O6|E(AnhBxF!4A3yt$@QhsteX};huDXh^w3Jho?QBSHPkQxugxSD}sIfX#v8I6LY z!^azQb==BoF|jT!cd!?=Rk!&;! zd9vv6?P(55sEBg{h+}QD_x>c3zCOLZMfoOQjAK{pB=t<4b7RSXMC0wheO@@wlL88w zv4IS;IxQ!7|LP#`RqqvT%fvtL2pQehmAf)Y4PYMREPy!1HUyuRo9uNNiw8ML%|D^U zb)^OCbVipW+3G;|l4%k1_@TYqbUw@TaSR?|A#;_R(wYTu0;k9A`3j{Z>H&2iS5i-U zTipZ2UBG83-vhk79q<1wRXigP+5@tVUQgtrl<%;LitCC?lwHWI;C1!9JuT|Y7z<|2 zh;$~Icqk{VD;`F3KH$^HJQigU-+nK*-(-uvC>NzNp|QH~v3H0f%3r??9g*g_ms@Q< zU^tOczBOl13^l-2m#{ zxVDRciZ8U8Mwq0k9kQ7=?(#GZ=!|vBhpGp&o4pCc*)Q4BM z`s*`5 zz7a>ThGNZQUjDlP$&^go2DmjCm2{Ovz>&V-$A|VxQ(bC}D>MSe!!a1__x!?Wq`M~! zv<|zC3*00LES#p;6$e0=e2Cxre1lypTqHdmPXp}4^%1>St=%%T7OqNR4}5h(6cbg2EKFO0Fwki>yp9RG zD;1XG!M@e#J(%9@Xt1Y_MHK`{p~rhQJIe=%5c@JsB|^L-Yp)w3itYz<+xg?huyHka zxD8|9v?L&UiJAd-lGzKgSSI5hnJ|Kwo7T6*Ut%|zQ!rb#w5=V>R~ywb?h zb809D9-NAp#>n+9$`)%l6aCURMSn-MT%@%4ksel&)G$TNejqfALE8**UG~r795e(& z8xO9TKp{p}wTl#6wz0Xd>$ciIpkv;~XK#${$PnT?hE4y2`|a%G0L8WK@r%Yy4=sWD zZX-ZbFr9k9OqVhYgqDqVw{*ij$74ZEF~9rv8$K>I4c(;pRw8}lVX1h<@4@~Bi`cp5 ziT%a^xrKTsp;MAJ_zTUCG9?*FCE*CDd6Wv4I^I?%?l9SR%)hqH)-PKEbeEd5(sg8r zf8-3?Y@D9tSKCPxmw@WAG?9zCxV@~BJ7WO2eY%8IXP1)N9-zvs7Raj(@;mHUl|iOm znQ`B%@Bx0z_r;VsnYfVMGx)oQy(P!O^{qZ!mk`HgeN#j=3cGuylO$uIDMO~CNlUJR z)+V)`AsxR$tKL71hT5-RA`VdQ{B;|01ZdjwLorGwM#jUV0vlCWFIq*a4$WsUvA6*A zK5O)fH9`DZD4Rb6!}#h_z=EW~ERd=~`HXutYN!0p)K!iGwH^&Kel^XPS#5nblH}5E z>3GB2b7iLixo8pgJ|w9v7;kzARoSig?XD?`g`{qM^B=#_8@%_4+O2cAA&k}oKh!=F zn8AA=XeNJC(`4umtYQb}*IThKnsY$>@TC(M|0Tz{_2y^MmPp~+L##hc%7~xF#B;B1 zi|f_Q=s(HE-CC5<1D@o5_;Bcc-nE21u{BXWzJqenLMO5(;A+;mLPCRD&UMY4RPh00 z;aX>!g3)!JbfhN#fYt_-FB7gYcRG8eB}-Uj9P9Plt89VP>aw0Wv&6lV=L>OJy(~w{ z7fnJGhymil>b#p{wQ1xjNDu;f5vcj$}S||A+5$u+=#oKbXe5#CvWh6}cr8J{88 zelTs*R4=8ATa7V574KDpHChNGRWnKFA2BX5vH4ZdMo6asC&#~(P2<>(@OqMIB^NUB z=6>k)gqFCsgL`sYKxO*cPxO4e(Gxd+Uy;U3;IEKW))VzKrJz1{DMgFDdoaVZ zP46vIdiJ^U=Tx6JGfZUcsVz5+n`*g}8e+9t2`0?s>`@y>vp_#@-rps{^t28gPQgi^wZIMRytHZ;AT;uRH(Rxud&+7_%Vl zXR<#KPgw!C6h3;}>BIT~6)iJCT7{zwz@oO9*{mU9L{KuPBv&k$E)M27I#znngM47^ zW!P3AMkh04NH#cR82W>7=+ohY*vC=u1El`(D(JJMZfqQTiQ>D*RH$J zE!t4D2;Rm)92(Kx1fy!=$!@g~qv$h$SHNV~3I03|Gl9z-p@YgM8u3RG2;_g-p3-n$v)MnmEq6^^!|xuIIw2wT;bF zv(b_L7H`wa_3BxRS%ujJ{m)*=3;^^P%yOvupa^sYzEhBR^N6*TD{Xpd_rK$AE}TmL%h zwnr;@ge^5elE*|1HxWSfCUb`{+oYQ5);t^P*B;AcZ7z;w6N!Nah-)5U+i=sMv&C3SWF}G_@2Ae{TMZD4P z+iGkJ@@s!x!(GHDk$gEQWg_+qWCki7$pho!J4BQJM&O_q;?^o!_SmNsdH}&E3mj)H zCaOoF(ZK$-;nr9OIAYNDwE zMjjyvJHC4?+L`s_u6wWtR91$p_Yj?l_jq+aP<;emGhEC87Pf;M&IdszP0a|$a5Ajp z1lqF~3YR$|p#8)%x0drhq>BfpJ>gIO`%tDbMx*kxCV!70U2hs5T|QH-3Oad!EQ?$7(>=&5FIEOh z9Q!n=K|fFd7^wm@XddN$-SU`fV0@i~26Q6ptJptfCxd-*4;3Y}Y%O>vP4lfrwDMnZ zJ`$u=1aEAR`f0;k8Me4ZLywwcbV$ONFDk8q=`h^vwlP2!Umqhr2-VsB4*?I-Q2I(vs=+W+Pwg zobV|B$}f3U;3#UAVz?E8>6k;${POjF`envXdOWNiLyV*$W;O)TyQuhR?Sk=(YKU+9 z&;Cmu;EuQLyR7fDfRl4<9?N=6@#Mp0Y08H)(^m;zsihdJz+{@h1QN{%l8A0hNd#LA z^qJ~2Cfd(@@^a3C3oNsH^z8VdidNUw=<=AU$>^^1^3-}TVxhy{nNOw=51Ck6JL`Fa z^pZT7$3e^~Bl4T@GAwmNp8_n{hRgA-4tq8l;FX!pr#%;>YEd}t*>nRNr0Jg}4eOq< zVQ8bsp+nwwBie3`Z4>PEYXg_(m zZs8`dx=Q8x^!u@e0yJ2h@w-9~08+LLPIVT-K_tEUo*AX(ggO=85VoXiUF>VNbwFJJ zm%~dTU7=7`sf`YrAkR}>AB^U0_|}>yC?okeX9(OOn!81XZ+?9myk{OD+HesArPI;G zN${}#Dfss{>z-ZrCW6_>wslYsPA=hDEbs!=c_f8mSfH(Lcth4TJfkd$8gMZE(n~q) z?2bDggQeNFF=1OS)X^TFu~j?H>Yg@0I}*>sl~qfTD~c+Q6{6w;903Kpp-apkOs1Bi z(UP>s35~@E$|Y8dsQXLu&=zq|AZtJA+-rf8xpUUCf$KV|)G$yM0N?3w946rvHKwCU zg7x8KQCmIps)G{qf~}M7I=IfXQ67#-xT$^kV;PPUaf`ePws>O??6tR$k4q*%DO|w% z@^XQ}%iYZ9gHwELr2K$@#`F7SKo@^5Gr;E%Og4e)tq?o#q?Xi2ZixQuR4_=;n1$)4 zf8Y8m&5wP|>G#qtjoh{AT}xe)<*yWHe`}FU$Go zxZoaeR9J$kBm)j8&*wf(NaKq^T-Ecae~55$UKY;yPzT*oxl;nA%*Dvfe=jhI3YPzdp#cGlCL=r5)Q-%&xbWp%5$7v>AHkHy& zq!rK6Px%zICHOBwzFw-=uhWq2GQ{W-`$DCg%g-Fd7KRpRpv2Ogb(JX`c|>8@5vu;k z7flw`vMcYXY^6T_j=PMCQ@a@f&8|%U6?%Ak)r$XFBLs_OC#9TN+Xi*1^7=^Q53m9m zTN7H4@`L&cF4E|4WN2t(!=*&#aFt&MzR-u~w`n7cVwb~?bwd$15SM>%(3BATytq1A zI`Orx4m|L3MJMI@K#`4JaQ`Q%l?-C*<;yoaU?vmCE7lf>TyBiTwfQ?<7> z=E%X&MkKLSM0}{*e*q&LS)0X>muw&bAEycbW5dJ{)1O}yfATE4nRZzLuYZZqo~}`15!6jo?aT zGGI{s=EL9nhrG72`Ds`kprprEH&g3 zUGDJysQg$dp5h_XSlcp^UO1)zgi#n02fb1^N1I9tvq5 z1_|SO=$4>Ouy4x417N|F6GVRsfQ}9J;~1%1K@MZqUfb9ecLU15E6BycK;r{9I}pJMf^R_Zq?`J( zvFvpI|7oHqQ*{%a!KU06v(~eh_@2%G2-zsBAU07VcS8-m4Bl!Fm4qZ zEwp9caDm1M19pmLD)E0+^_6ywXdmi-NVaPD0Gol}^b4Az90b+15P6UT_ZeVkng1Vr zonv>VQIw{Gifx+}+qP4&ZQH55v2EK)#WpLpE4JR)o^(&IHLH8g%=78_2j`r<@9Vyp z<{M0I*Pg^T(e{bM2w-db<$4a35B;xwr|%}|LK{h=hZqc6FQ~wD#q(ww+e8fOrePsn znE89Hs(j!pj1QRGin<*=CQhsw_g*PPh=?QOoV1?N0Qi7x=uww#@F172=X;=t7#h~z zf$2ZQV<;T3j+M!RgQo8FyF;vZ{Glr;qI6zjmRtx#<= zu@p|`zq10iQPuN7D0ep~AR7NMd(8gQyNzI@Lo=NsqhJ-0h!IiBE3^&5yt1SSTY}|H zR&a=VsUfS%tl@N&vrK%A7Yw%43K9pS>maFWAwz|$#z!0gTV!ObgphRe<_w)v!oihV zZ1FE{(mHP&ncR%-owbOWjX1mnTkVNqh+F$*11$((p6^6{3)(O(a83#-1%Zzil*L_> zocuLcnF8zsAj`PddH$~TP$~Jm$3WV{^v*NX=;|giHJn{v5XJnG`j;)Emo~$ffEj5v zTEJ91PMvQP)@FoB-;?6j|I3o{NG$K?kKB;s-NSO6@=iCYtc1=&+LmN3 zXP51sEU0rN6-^EZ#q0%Q6uwubJ0Vf)d2-`dV6wHPA|=A~9%M94wH9Lw`a9KuIss#- z)C?n7l&wRxqhlf5j9NonGrXF&K2B(ke>Fbf{o0@po8%BV`VI=^AP5stXSP5JYmk4T zra8pm-#ZFr7#S?l&os@Ar{ltXY>_kHe?UuEOyydHl945(xMde{tXJ7YIkf&9oW%TP zogMG3+x2zckYlcfk_;Ja7qk-O}His0ui|1$@m_zCc9C%iWI)jy@+(S{c1}r zRdAn9EVyEzHkQk+Zhf7p0G4Ee`VRA1OL^j&uHnpc?`~1NPXB9g#MzH2K=>)#_c{}} zF%uNM9VD0g#;s6ef;xp%Iuak*wJX>aICGyHk?A;;UIRPsLC)${Q@arC{GJ6UtS}tm zayfDJ23LZIo+I_=OI@9Uow!GJtV7Pm!|Cnq4TA_iIxbJ`pxhd-rYqqZ7lNDGaN~J} zmkUmMR&W$Tq0$?^Nm1Nkk)4vp_w8-MpPo1DAd zm(b1hFA}+g(nIdkdor8t0L=9@=R$p-$Wm>UK z;&1O7dec{ch+aDmmFGo`+t3^O2VJ@$aaam+Xk?EE=Vuy7M`;+y$G}M z6-Yp$*@b)>HEsZ8#b`1Z?t+Qqv~;>wLM)cK`-^OIw6!18Q4rBv8|Bv+G*x$U(E+ML zoyAnx>WJtjzw!f{pxbJ>i3IeCd~!yag_wx}@zE~C#twU(o{<6DARk?i&}z+|F_NJB zMEWTrP`pReD$*Hn5VBrLQWrQzJS-oh<7pfhckE>sJ~DDsXrRk}!;$c8ENboTQJ`?S zw~z!kEdyLr5|_OZ-QqcAZLwDHC%VE~ojs-=AMHCK|$+`3Jl)cO>3eUvsS0Ae`z=)pf+($ec8MB>V(LHjJJ?l-MSJWkPgPUEARQKwM$H0ezRF^IBb%g5 zbDCZ~Vi5uCl|u|MCLGUw?j*0oZE}zs0Aju^o|?)-9mG<3(&bag^*Y3s^3D(?U>l}H zDotAHg3*@!EX+lnddoK1tP0%dwF@T+0W2d~ z7gwGWpoO}Y$;d0uvTf{0!e#M-BSP(XN6IhPQ@DW56^TW?CxfmD)DPG#W|4fx)L5eU zb78su=9Enwv$xkoY6KnzP-;RQ2AW!M}jr zAvl!+Dr>zNk}&SjgiyECRO~sUh1Jn+IBHF7r05n`6G^qjbsQ&>f)*cMHOU;wcZ=Vr207Hp^AF35`>yJ>c34qiv_n`cyI zAa*DfwCiyxqGo+90`!9oG#w8@7#2BK*B^j>o9JrCc#+-nOKh)!Q5=$9%%H@k;f8SB z@8O$?ydsam$57YH?|ubD(4H!tDuT})n^J+lRu}Kxu;t3@L_p`&wmoNGoa;#1?w766 zK{t1?6DFdxnRYmcASq!vu`trOJ}(JcU@?TzV}kJ)HLg;6bLe8KrZw+4k=-P}RyH6$ zaWYGt;)f|)C;1V$qHZe{dJPXA%CDJCTS|+$O zg**dNYSUarHsHd~)n0+LIz@wbHBo>doG_8d{q#dkf~TLr3C%&q)RDlLK1ZNNJkv|@ z+(kzV_}1tA$q}X~SV+9g5o8TRAeNfql?TFAyFyOzeqE-h!S!IZB) zk?Bb1Vp2*DwP!fg=MT1l>vcPn!D6+* z=(U-`4|DlBn$Qk^A#v^&i!@yur(Z5ez2%8KT=k3qhIeQZbO}>+8$SWypr_}t_{k~i zQnk%f>C-h=*vrl6`qfqSGb*wivN3edkG8bE9rRnU1~P&XL);VhIiRE$HA~psLYv7h z&2Y^30@aqG3-m6Q#Qc4Tk@}ts!g`wFyC$|3)XxIC_gsXJ1rG6il!ax|tllMOOTqX) zOh0*hK~oKNjH!?#or(g;%d}iov0>@iBYP~^_DaeYIXn6rnyq~^M7Pv|oLOd-p3Xau zojZfjROn%!$n4=n<`aFDR$mn9G=rJAH5f>M)l}1dVDZ;yhqYA!-IzvOsCkynGrhHP z?GdGs05YrMgbh=#ebKv~V)LIAwCaN*b@-d+@>_Q6j^j&-4}m$LYC+I31tsT!=Eqq& zbx8ysoJ#jbGNb_&`$TEdA&5?R^||RLCjF)4kRJi!wvz5to;${%l&uGHTUhWF|HRem zb&w9-y%*A;Rnu>RCiE5IU7=;~&5vCRwg9;v=aVaec&qie(i*m*e%=0~+FGEUUYYoz zKBWM3)QvZ%z5^z}<5Mdym%A0h)|j!94w!#;?t*kGlKHDb@J}F?>a@v2$G*Ir^?=4e zBKX~HshPD4I#C`1nQyFWF4xy z{MM*{S1g}Rk$EM$H{;OdM-Ef-CAd#ahR=aLQ~~EY*;)g@CD&sKURd(`$&96}=86HR z{o#7PePpOI+mJ>!EiPG~J5C+tHXxg>@6$5O{G*Y0-~jZPm-J^_9ti>FAPC~H*ReeiWl&(VbYE6tSrYxdo zi1g}*w@+(#eWl-xJ~Ni*)fU@~7;?_lu#BXudh>2+aGE4#n)*9 zem&r?XJ^)mO9)<4>+btJvc z4tW=u{*jQZnO~;mP1T3=6JSe4DN4VIjNX@Qe%+@@H0W-yL_m5`mBhOYskZf6@XYsD z?MWseMM5VVHkYSM_^DO5&$zQ`{)x;&cz*!EZGCRw@mM_NhP`io7}rzrT3nm98`O*n zyUN#RF-vr4(%nYVh;K!9mZCO+j3p&d&R+!fU0LHobLrM4n&LRLO*afh?OwfE z5806~GsThGBHRsXI;}nma7TYI6Kib%{ZJ6_BxZ9l99At?xy!~Ct4npF;U2#ob~J#@ zxvlS5-IY4V;pjNj%%p=)bY zk4j9`KyCj>aQUU`o?K`%_>I%+@Bqm=?DH_w>59z)@HAK&4#`qhRt?yUf;i2~k!Us%AJiEhq^JAyu*r`wy==hvjB@1P0M;tT zfh;d@`P06eW%2nt?abZm&Ex8&gPa$lq3MiKQ+oj~2|5-r+X?P%L5&{oom1<1>X9`Q zRE-9&`nyQNzhI0hdJ9nFOx+Z-Fhbh){}e1B!c3#NY{bRNiV|F-mO4;+!3@0~P8}Ex zKDi^yYXXw{!u#`+sBZz~1^0EJnp*pN$ZQ}9kT-#3rjwg49gw?(P&K{c)P+QOPABAx zqC*Q@D@bgwWyr>J%AC&8>cAI1ZSmIj0F@n4uYsG5K#|qREEwRP2w7?D<{1x2%>y&C z7K&@yaR`x=)dq0M96(ip8hwRQktLgd^Sju=);6>oqzjL8gKAc=ili+s#pZ?}DuD(N zRxjk3L;jk1c7kS@R8Bc;8q5bhR!ZJZpjU&qdcHO#$1<422W$Cb4UXTX_v_p&`+TeQe}<5gOj$Ggk{$7?!`t& z+4GcwKyzE%qHmFd0FhrVF*`T{7ZYo)3wRsx7kiBLYVBW=_abi3 za2A&iQh}$_QRgCIQ|*^o>;7#+|;bxDnE!VT{x6`VXN+_(XmCJh^L;)ebzR6_>j+0 zFI4vg6xioq7g_@B*ovfbSj8O^P4R#}{o8#MDS#&=n3J!3chIfP9WpQos-lwvfgPB4 zoZ#eK$|gbMZeFlN@Y^3ma6U-t$QJ5Vgub0xj=ly?PBTaqq~H9LL7|2BBkN|x}0 zdt1WLb=1$4A-s@|XU+1}ju)zPGv~S##bYe(Bg=LCx9Zw1g`V6>_JyarQx_sLo;KCB zGC(;t#}&cK@xUYPor5DKz|-Za+{Juccf=0uPp12A&~Npm_HEUv>nAsybYm|9#`FsT zu$1ol3pFbn^!Ea0TZm>7Lk!xhEf<9kKA|@`YFwLp2rtFMn4rb(UINVs11Rs*S%0*e z>!-KJt9n=;D{%hDEXMxViDe=xi+?T1KLM;`YgeE*jMhb^PdJzRwv{g08^%l+gJ!CA zJf=2CtNWo`OFP1bl^yIa2&>!!TUT)ICzC2CVJ4jcf+iuG0!F&=#weJ<@&H%!-G3gu zw`G>vaMbjnH(`cjeeD@k3JB8R;nM$VWJ((Q!?u2dJ^htKSZWnw@AxozG2R0C_yLgV zLg(3$WVg$22nw;#-7>Gi&# zwR%Z2q;e#d>)i7x_Fzq9Z*#~98y;Cr=uaC1l)?hu3<*F+)c>L1;_qe(Wr5<9t^mGo z)vg?;r}~!G`-?kpZv-jOHjF~{PyncvZkFZlmMAweJI90`m2y+EF2v_bsO4ZdY@-`03icRlHQj*)2F|2+{9&YmvC=vqA?)qeYf$VC$~d1SEzjy zY;;~5&H{pKbc;Ig&GXKd7qE)(Xm2@+B=j{o9!MfpY9aVjp56K84r#~rj~0UWIIToh z&G2~W0FP|VTX=vLwrzn|kojWqB{ne*FVYtzq%5>Vs*G<@j>6TakI#g*EQf`YpDl5X zZEvkR^!WVn*Z&}JdVbd`w?Kk`;Cy2}yx(MxYtn-b`u{aM`M*Ur|Hao(79a`z3cl7&X$R{g$lk{#C-WUa$aK0Boo)L zXt1K$oi=5oB~jGbYU(@eml?-JywR9jwDLPv_>9gI)c1Vrqzt3MB&MipC0)upCBW$Z zv}t^hw$t5t1=g4%%hL3esD~%))@<*@G7fasFbw1TQT{hJpCjowedFRSN1N}F!T9*T zH;=y$xR!(xd34kbu*~|GLBqQGw9|QB&L;BFjQY5)43@G@o5pcqaw$EDhvwnd|gkSv7`OzfFSn zW*p;WEfJf=aSK4H`()-0>cVa)g6BTB*F}bEHIo&SYTeplSXn2{1^c#sKmoc zx|-Teg92_pp7yUZn`<^4kE#<>J}ea*AY(@-ee3CPD!_b<&7X~A37b)9A6`XFI>k-} zHWVrQ=XvJ9p($HnJRS^qv^oGtGk`n|KM{AJNBMjHiWcC+SV3NUA8Ib zOh%dzTYcI`PD8i9lDn)MRvpn}lLOw8Qb}Y!%iVlRyP#EMmL6UQd!`YWa07-Bpxoxo z6NZMArPUtYO6)@(3#C`==O#$aDi8(P}g+d>{LM;?zm=%5&U8YmJ zfyOI&Z!D_KtlOeb$R$c`<~-Z%7SpUY6frV{0O!q2>?V#F16yBCWa(H=Qwqt*RC{mg zkPA-o+de%?r*6jrzf2>DzP+q{i+cg5Ui>2y9Ba(PG1qK%+)xyZE)Zh^P#$Rdr?hy- z%CVy)tg?W+a?d*uwrE|b8HN#vnI_42fal>b6ogTqyarz$JSaR^dUC3o@HPW6gvn!F zHlUS?iJ&psD~===)4yz3m=6x}(&Vrh!#Cf5eE4tMU6@B7nqQw2$rIfKvjc_mZwYeP z7WE}Fe9`Dc39U^_&2NVvfcEo9uqqDR$mSpus+KK7yn%I$=bB)dZ5%JO1P_-iH)8~C zPMM5MSk7QOn`|+MisW~AlL70|-gm*zH~wM4hU-k-m_e%rs!TulS%ix8_)Iwae*_6T zturf}AEsWCYCRddC{!}q1dbh|^&J`6woU>p9IZ0tt9~&N-`;>LfI&^l?{p&?SyZzO z&PBsxfVuyRrd#1qKyxYmvIxGO*qF9WNsfuL9Ax(fX&i0vZ@yJ&KkS8T2rnEIdaO-8 zP4gW!s2{L1II)CxtK5=PW69tG{B_1dA*e@`@xoJ8;kr7V5x)b|;mC)`!8V0C$%mYb z!@%oFgs0m{+ZC^34snxM|3uW0;Q(T-XlUa%5{ zBY%xzETQPS@tp@snST=`%t(DOsDQu1UR~kZ8q`TvRR&#-0VoBK{C(Ks%0_KMG+pm` zArmyVo$fAvT$T;kESCBVrpz)Oxt`p!9=wcd#ZlI0#chzaoA>2i2x9R|qPwX$34|Re znHX{?HwS_yb0Pmjc-WSPA8`uqwq9QCU(DN=m&7}N{E8U7%=Yo>&iiyM#C$N^@7)W< z<@jJWStgZW1gNNvvydi-)&w^DSf4@C$$rS(Zi`BuU8`<$5p#z}vtIl{O>a#}ck`*e zugF6TT#_1!y>t=^t5y+F_Em64Z9IQ?+E+Ql$og@Fu<~Who^Y@sha$CnF!o(T0Q6u_ zlK#a(fR!6=dBgeN^JlrpzK*WnI4XoD9teo=|C2}kA1C4Wk!Wi5AA+|(S~?B~-(#6P z1L0Mq#MuNO+iDsNW%xyn4%S+;5ij-Zut+|Ib!feWs@V8`#rapRi)TW4RQJzJuR6)S z-Q9NIb%lkYTBLTZOKcDabVr7}(#Q?taJ_)yj}6 zMX%*xS&I8Pe&D{5QKH!9h`Q|%Pd{F4^Altx7EQJSIc~~95=npzFCUUUMVD0*&fMj?g0!XHqK(9YWrjIJ6Xd0KMFDA8~5E2MTRlxM#|G-?v3!4a? z(?#U|HBod>C-|d88xxeMax_^;G>*GLI_t#R0{ z<%r;g1_tz7rq*lEg(ada&1Mf5QcHmj$U6FtvDN^YuOr5^D*6Mjbqa=sg2=*w@Vjbu zvC(MOmGP0+U_hKSK(q`-6l`@7|BKE9-Z^59=Jf>gi&)-E&88bzAZ#jQ#oU!$3CzsQ zCF5tpmfpu3nN||gcQu?jU>Y{Dv}=z>UUY2SSXy^kQ$BG>8LCfa1kbcKEJdCVvqHU& z*6^>@*cZ6M5_T(0G;2owMyzVfHAgh+m8VM`HzZQHw=pt3K$`iF6k2?hQ*h!};!L4& zKEda=jH&Gk%#|JP2qkPb*#xz|vP^;dhBrFw9g2pqwsp_q~CTuh0=4 zlt`lSAVRfCX@6MRmUs?>ln#uK&0Mis`5deJ=-4hp)pbNat@x;2&)s)-$_6>+A=rB` zGSQ)hCge5=z(4MR-fKtO0Dg5r?BKqhY8f%^jE4EkCUefS+e@iKvk<5RA&d!$ z?1SmT5{`VI)|9-J0Dhsh5Kajn+a)hK)Nl(9Nd{JEm~7o#m6+d3CO(%*K?n%|#N-eon*LjkbyThAsrZfL2EPL= zs0-~*bw@U?t3ZW4$tf?`ug_Nd={;EB^G|hW*s(4$ug~9SJKP$LcZ)gokphNtQM@nL zhB}STU?!)B91k=gu#=aY8OA8R`1Cy9;9malqH{WtfT<2xQ96~J*X->X-LL|JdQ8vg zLX9jA7(8%KJhtwq{e?*LW0|Jmc-EX>xF>{j)>ys~F_Xgr>4Fm8G8DsoGXhhmJ{82| z=V|DJ>`r)_v5({~1Z>xDUYNJ?EDjmGQXbh!7BQoRYC9Q2*X<_TRLHm0#o3d=4!JTG zyL0;FWAWCz^lUR86ib_*H_+{;jL4$w4u}K^fVDN*IK|vx^Ob*zzhUlOuA2?gK1p5g zk)b+qp?!ZQ2m0y1oj{!sbL<$j4}bnT{iG^Z1k;6v!?TTYW$srwnkx1=Z?>rtmCpcy z)~=p&E>*<_LBApv5~a4I9hMD8d^z*lFy27R4{L8$3p>xYID@;^f4tx@3R6D_)%3)_zdjOaxof^mF&0yRIj2r`R0z7%=vK57T45eFUK^i4$3<6m< z+aPFBg%AQIYlLL<&Rf|UcFukH0raW%gU)=7s`yM_bID%M>3z;wJ zPyk^NG7DE)ttY>-g>2;u+=H{le!5o}0$zxOz>`dfL+}_aq3V2fTbO$lb?U5epVD%Y zj?fCiZ0ezuueDB&c7N!HFp)?~TMRNqc4>Ar8=r4H2`7fNP}dm;RD1^*wT+qsP$4@f zgezcgy3&2aN8?m7+zcPeYOa~8(P;-bF{1w%01Z5LWL^u{s607PXf z#jq@2kNj)yC-dufB=$j}?Q5Sk zK7tFrPc=9kGO zB>2*#zF^MEpN&kTP2FP0bccs0(O;GEDfhf%Qh zRSg$k#(WwVxgoZh`ahi6EOY9mSAd~35Ej_s#3QG%>(3c;dAgtZE?Z~M8M^{5xi!}G z6Hwfc@aq&Ah>r{1+ko7`xQA_rp>_mThxnH=t1@zezxdImXx2QyLG*#%pUs zc9DyRu3!w{SAUv!=*`u{tGn4d*+?-S{s!nk0UsHvqNS|+9>YTu-qH?@{x;uKlkWaq zDB@^FFrf%-6XGO&YBt+r&3Rpn{*#I7LB^%$5)I|bb{=BI zwZ17qdRFqyjP6``(SiWPn{$Gqr`tLG&=OBPyu(My2)Xt<;(x!>QS&Dtfgpl_m@xhS zz|jBLvi;X`tsD27CE&R2^n>`D8(W?|ZLa^sLm$6AJN@kevYs~MfKuJkS4c$#@clAqmv;M7At*of;tJ|2^Wo3iC zCO}``U}EoIk2*{D7=+14HFb*Ag=6C%h#9`NmXtM9l{DVJD?eF-${1&ku4^(g%cd0A z*5~_7yHH{_VBnr7FhKtGa}R#8CUEMCI{YKG8)wnLdVxHvk=WfA>)e*DiH=>;W)jvC!XfcmC7Z)6nPDzwThT0!9t+2Wa7H zXfKoM!{vgZA^6h?gg1d2pWUjP3+VoxcouC}*r|p1tk5+G7t}7u)F>15$tP0PBF`ZE zr1JdzFrT%Z(wE+r5Qw~tE8cJx63I8y@X>FlP{*uZ(8O7Jm}E2xCCEFD-e+_^Di^Smn; zoV^nZ!3dGYgyq|r5(e=p7pQ{4j)KOon>kGJT7ihHg2o8ljn+wo*fP`ItSViw>a(%L zc-uTC+x$c8xERnEeEnwd^xzobYn&Pp%sJKsyjOMG14m)97(QSUV)B&Y1bEsx-Bov5mmp=o<5MC?j40Greibp`g#y|~} zA?+4T0(U&RkSC&hwoCYwL# ztRP{-|9~!3$_37>2*UnU`$~(F1Ao?vZhMF@J`PJFMS$hP-zrn??Y52v`7D%m!Q_#Q z$)0vKvy2nj#918<9ukth6b)4|mKZxQd|Ss44lY<|h0fk=4N!?+&Ttjkb&!AO63hVa zjZEK_IKZZZs;*M#0OcEof$JtGn;3QXBxs;ZQ48?9J9{Vf@_2pJ&AN${XSW+8xJETG zraZ3@fUJw=_HmW_MY=uN%}LOfCR_*E#1jzyKDVYz)dskx@bPlK{UaL4>?&RVT8F$X zfn|DLGPZ^Y2l&Q>6Ga)C?i+~XesOX{?yc>va*&B#)q``vC+=K=Yzon>OhnVNMc$G* zVyNnV>M&gh$*0nXh_K&gTqKh9*%R@Ce0m`=qCvR<3y{)85WB$KLxbY)2FmX~?eVLP zPN~>HOc(NyXNM~k#x3^Gke$DK?*RgmqWWP`U{n2RfGy#2T+NUWA=6j^q!->RTXwhv zx5g_qs+&tgh|)KT&K*uBzS)4pu`-3Qy1*H8kM6F*~qCj&< z90ZUcFIbS?(BgWe3kqoy=(iWl$`t9gy~3`j{Ksrz^E2yd>V2Q)*S9fSpl3V?iwsDH zG>A_Y;89s@^-d8gMDW^9bxmT0deDm;<-EZvUYaKmX%;DS@`{JO{w_&DyBNZ8 z9uSP70{_zh>Z)XDpa2o2HDR6Mf09?z9#fz`GDXk!J{WX-Y zGA*JDx&1`!MjE3eE}je#)+&h51dE94U{o|(Hv*j3(Tpnn1Giun!OT|~9zVroHfO1X z950dpK+UJA0h=Yr2tcb0K$~W7*$vX~2oeG1@S7w_e0}afjccu(2yf?iTE3+5*<0%a zWWWPN+9iKfObC`!30o1?2VC)dP)$(u-V}(epK$Pjp|#JV8%Y!`FbD1Zf=hs>UYDa@ zDKXZ+VhTR3wEvm6K0PYOp4a`(^Lx6-8XN3pN~Ir=bjzDR3HfUD##593OF5AF}p zXX;nOl82$39NliiK1+&uif^t(3#7&fz_$gWZaY7Gss~y4L7UYh_;Gma`zn3#5I>S1 zM(&?Mm1B#rM)sqJpxh!c)oza3geLUErBu?*j%ZxIHA@Pd4H*}qCxZzO8j*m?k`O&7 zLf!XHgx0A*0%bx1n12Q%p5EeiK|M5R)ftPQKzO7Trnet|hq%@IgU!tujw?10@V-rY z#+1Nj8y_&y?~BF)NU-Qz(yg31q$v;~4!XO!Lz~Ucaa}9EBiSU5@~*LEEx8uKAgO z*g2L>HaZJ#eu$=P)X(&aYR&@d47WfOKPsiafdz>64j#_cWvZZCn8 zn)_W1Oc%K;&;wl6v%fR7L7|b|K@C=r=&`HNedqVK&-FQ91-{UahoHV zIf)5}3f9fX2biTZ&{haz1H9ah==@7M78p8Oq6$$yfHfs{@hS=8PHs-#?u5t@sb==z z@GeM&8v{p0tzZQz03w+CcfZ7RfC?{c81v?x8 zmYCjCEEJ8Om=UZHOM6%-!@3_*uku&hXP_=0APWlug(p#?w}8^r>v_ym-nYaF5%fd4 zAju1lhe=yHe^;b(3~HvK+}r?EoXv2K^)>&T{A}J|{GA@T%_VI)ZR1gGiKA)zCPPk8XWc{s8DalKilg6i^CaPfds zTR-y*T$>^gnCg-Oc;9@CjivQmb`_)y(x8p+Z-#8pqBd2XmQRN-{m-*Ue`)_MgPdcz4fy{Uez)-mA zdkM0>V{ZWcY{0c0UchT0b8)MS1_}>Y)3Vy4bPWpwz}-x^zjYrN-Al7*QiHINJ#+6s zbfwmRM53`=_juWOT>BasYWKDIB|9bQ9qu{Dqn!2>*c71X*+Rb%r-d>5T%dbyvx%J1 zeF7MAcv(4cf$hRhm;K7r1sFmV!av%b{l<s!y813>eHQc82i>9nWD z5NCD+?BxC6LRfe4YYc(YL^3$Z-p{-OzRLOCh!3=<+8ue4k3Q}Ew!~bH7WkjPAu8td zLGx~kP;|~bdU9uNa!1T{io9J%n)bz`2m%m+Y4wtYH6LnF2UT3QJ+Z=IR9^L0WkbCP zk!FiRM_+hcHy0|)>C^Ur(<>v`&6P?MO7d}lSvA#^oX+@z7vYsQud<$w^)*}fxG_X# zOMDiD@q4+WpsK6oWF~zO?ZaY{4c6$qBr@`ZAmW5bm;wa}aFju$)k4~lqy#%_}e$Ym(TyXh3`6wI8S(P_B}Wbs#Ddilv0$O3K_ z{!K$k#S+3R>kXA-qA&>*0Pa?^=_dQx^qKxSf|nklZ%;XipxU;Jt+h`1_d4 zx4UAS5Z7!;&KOJ@Q_MGvs%EDoOqk252VY9b14ibBZ(LzkNA~7!&~}?N#4rs7R%@SZ z1_kDK!R07NY~ggd{vKVKt02J#@R~)lD@$H`_od|vWQUbyArca`TwD9{Lk#> ze?Bc8ja)4a9IYJ9{tvJo_aDIeu?8C-k(d{Sx%s{%i$A9HU*^O?3Y*pab)*2{rqR?z zoXEvn=KTPV&o?!--mzsecT5kc?0_LmExqsWs^%z(O3~iaXT9OvyYmn2BS2Q~zQptP zU~Da8Jlm^wV0M`L3JC%?TY?9g$c9jhS?l%_r9 zhxk`+Jtyn{-!JGEHxijn-eE;I#Ncj>FQEAPN8lBk2NA5k+x+bg6krSX;T1^7=(P?O zVm1{;lvQeIaC(`GB)ukJSZL!=exM<(0vEdl~I#HDk~uwA!( zZYBeUC3mClIyHzj*;qAkyF;f&cKfQp*5;ANuGqRuFyW=PM4$s(I!6Lu$HGsB{%Bfo-2xelZUwT3J)qw|*8c3YI`F2)ZGl`u zt?D8FpgVhuq~P6Z^{=JFfwkf_Y4uNK|E&f}W-b%}ERC#w0+CeRbrxb*ci!a8E?0@l zV%FNcK~<#*!wA3+0vizZuL)=fjc;l=w}NlL$3(>Urx(rL2CV$ns*?fX)m%SrM;5o{ zvgQOCCR%zzC`UQbDtxdD{X1yq&!TW0uD4Gu{(0WeL$zeV^?Wwzh^U!io}bt-(O@d0 zc!Q)EL1Ihak$O|;Pv4P*|CJK*FIz5yLp^hCXoL-tO9XM!_AW-~T&u1|qGcfp{WD=q zEz}ru?~5k}1n}$$s)rb^;SQ!>c=MY{$J!_jMH4l2An*VLEOD+OG3a-Q8k@1&_(SQ0 zrKydyC_eR^H{ymf(o&6d_IZZCLf>hqa`mUU4IcH@ks0s zrwdx~(Yr+vNVFyFzb;!?O5xzVi!d32x)W&ArsV|<4G0(xy z7Y?NR?~^M_PI{_spHy&%%ls zwI-o;jOcpfgL<&Kq8nLKwvC{J0YR2uICVilS^$UF-N&1Uph79AvLP|4R6kjhUe?eN zcHJ-o4moqmQl)}K?ahpJpyiR_bCrGUvU6-3RHRQn_=TAH0pYVWFxP9`31WP0vQK8TBViN#BfH^c+yA)k z79cGLF}mH@lju*zWP`xBoDS{=lJFCv_LZQjzbH|hX|ULlGDtm5IGa)=bA2d*m;bJA z0M|^MoES=LyRd?jHbdzbQmREKI7MUTcyS|R>SOd@M#G}TN6kx8on5R|T|~#Yr28jr z^@7EB%i0d-IM}ecs>S@SCg4M?pkD}puaUm)_S{#GS6|~lJRmwkOoCnv5>YUKT6p-+Q zt^_ILf3}+pf$i%Gr_6RaW_^2~RCIQ8YHGOnK1-TH|I=&oQVA@ZW4#82w4RKust24u zAbRYsYN$+<1xN?+-aR9SZCDCP)P&Nz^*(fj{ARe=J#-QnQMs)RA`2yU&xC9GtWq~R z2D#TZhLNQ!C!&{bD^Bu8-{URd3xFWu68Kv7`Q%E@=^y&ZMByX>S(;_76Fi3|5h69J zH7eE`pgo<;LWAcM9nm8r#%Fg{{J}0RD~ouEQ>3yLFRscCN9%2RE@VAm8II9xStVV@ z#@0-25VEfxAHV#Bu50h*VV2=yA9K+@Xp=xrs#nJ9?&SA!I~*02wyp2?8*sB?G1Kei z{xbD4d{;u9nl^LHXwcJ^E~wLUaCc~?KiRAA=ks#Ap2o;%pi5UKVA6F_uWnctjmv@o z#QeG3C-H2wVwev@3@PKn2zrc+KDcQ*-`Z z>EIN^JoT8Jrv@2$y%hP=7a+B+_p|dL{s~q@Q3QEDFDNZpvmLbS{6 zo-yf-6gV{w-jH1t3?zKe?-x_A52+53A`+Ezu2tu0)f~`ok21IUFF-|bcc>QNPv;6( z3C(h!bE%JQiAUr@yEr6!$oh3Fp}_d0IpGNslq6T zo}dkE)aJ|n3lPe;B6_Zf3!+XYCjX7LpXG^=?agMMA~Gzn@Lf1R*~;-&JNGM;VCQ~` zA_DNyJ*kmGASelFh5!u)Uj@F8=w*)ERM5xPvWMR|xGkkt(T}1*nu=IsVYu${$?XfYfo3LEQ9YO4Oq2h` z*gFOH7PRZWv2EMVjBO_~Gq!CT|2Q*FW^CKGZQHi(lW*1Dr_NrrcGbBURed!sdUTI^ z`|0QXJ%0U;mKpfSZfnG7QZ+wr+O-J`jf#zAKPdDs5!r`Z@^UqGV3O%vKuu1>`6&{R zRasfU2;319&C?MC3leP*CzQ+vWOjOO#!3>%1k^+RNLcLb)&cdlzZK&@Xg-$y@X--Z zccb6OG>hF|-brP`+sF6uL)lZZDueGv83oLgx3dliMMDlc+uUBFSxhKzrFX%g`38ZZ zTgqzD&u2a*J7)*dM$j*sfW_w?d8eV|zUhO{Nj9-Bb@TFa?G&TG7^X-nUq9u+&dMo~ zD5~vt56iwyB<6qwcs2Fpiqi9m&o84E#YY$gnv`38`KCU!#b2;L#+WvtLnNd?lRJt` zj~N!@6bY8f3iTrTZq%<7V#yAs=3(Gj5hCL%j8jNFSOJr)(&wE7z{$0QfFN#!ZZ~d_ zj=uK()y`VNX(RjIN^a%1b})Bwi6MEqYACusGF9#nO|#S@dym{iJbBiLco~!C3s*2Y z!vb#mAzkQqVvKox%YHP~dTkn^xM?_%rH|3V{vJMqrJ%r(YMFZuEloJE^9Y^>dDN2f ztHXj?k~ToI_dZG(pbnjTaYj$m%>5Cl=X|nXB0T+Dg;LH>tk75dm@jcQ4&*dET1rK; za>V(NERP&RA1>UOAtQ7IsC#$q~WV7`P%y-_c= zHCKtwM>H7Q!QNSaomElidSoHqI1QmLId1se%<{97X_)g^17f!V(1by;5=hdX6ZQK?hY zCl~@;2kkcmfW1Po5^gK?k*hs-D!Qx?^jKw^xP_o-F*Qy~fxNcEcIKkSY};B=XkAVR zfh%aKVw4rZFn;8WW?tzU)@p0iWl%Gm{X{r$pigIK4qagi~Ip)Q@==85vlH>Q2`P+MGKx3lhVMqxJZRTsMY9zpE$Y{~k?ArJk$fdJ*bIZMMJF0rqLGz^Ealu4 zOz|rgz=Zk-6f7o-6KhEI(T;BZ{A(kgpGPD*f(t!RvUnnpU6Mrum%g8KwaTP&!7U}$ zvIxSr@i;dP{jgjmV2(q97HITa?lA4`8FH9UN4M$IE{^)om9^S>%a=>Q$K+y%D%_$x znrNeBW>!$&y@X02&f4$gC}1gZ7FG+ihtFzN0KAy_5%hg_KE6>$R{)-a)-4kjEVsF5 z|3d6V_ig8jpe*E}2xVkujIRUS(-_Yr?8mQ1ivXeH0|!~lphDk7<{TeLp+DeehwnO( zg003#P&2J4mXs>-#>0}4p1L67n-?oZ;UK4m;ER98X|2&GYi|eWJ*Rl_gQ9X#u`jCn z0qVfV0JrsVFYXA|Ht|@+1e@`7%>bEb54zYxEik3m2Jm1-{7F@CNdaswDl3 zw=$HLS-iS6HGvY*y3C@kl9$JHBQ~OT03z)f*b^7SqCc86i7U_Ed8`}22=gd-8r)9X z96uD*z0!qWl5;dbCz9x6l|(WCJpFBk)wbjrPw^$mK9|F(3YzpB_i|z_U478sAzZ1w z**hP_PGfc7HL%IAC+{QB5=oa9D3NCDMmq-N;h=~4MLAo4TSC4@jT)IJdiwbXS7Ys- z?(#1j+r4U#FI*=vIUY@ZNpAC6Usc6$0ntQ7zekq?HO2^+$%Nd#qPot1nTK3eXlYVA z`z_Jw>q1YOhZh@8;{#Q$qMV_O$G6G`p6NZRq-2+{Jui4Hye7ZH;k9SR`TV2TIfQO7 zWZOUk(kZ#D>S*OUIvLCvuP~hqP<4Lxn>EvnOc$H8;r2||pCF*}EsD+=m-mu?f(kbz z^PvH3P>wE_b8jdaqN;?CaDu04%&*NaG2gqF{jfaRcwM#oKuD%@e(5^mwP;G=avf?a z=yzdkSO})=0nfT?6+}1wozCM;s&m5@chgK;BVx_VR*JRgJ_`o|9p6_X05qP=fCQ2y zVN9aDg?-E0F`)Jdq{DAtyvf~Tnov;m3FRo=3%Uy$3gkOMeKkwW)zLixYbd5%DhTDD zgm#P_X!Qyu&-8xIEk;xuk3Liyr>+tIsv##DIMf=@MZMU3@oB z*33T+L}OQkGRp|+gE53~fJ2LlpCd+R3EFy*yYG=1udln8vE1?#MR1eoMk1wo{ju`D z5;N5=aYZ)-*E&=+UFclU6_e{cJPi%S-d#o@W34a2*5@mTu*+FdygC5a}HU>#=t;fR+%#QxqI@-V9UI zE5c&BwcMxskdS47V$dDc-62#{z2(nj;8R#<@5h9RRg(=KJSoPv!G#){XO+*~p3n=m zKWoL_#4}kS(HkSID|W|~<&9Kocs~B${ERJ@{t!_e4br5h`4R;|htQp*eKHv zd!Il$!`=FiZQDU00Q_2dt`i6JwxM>oGU+e)xJXqdcuw3S3My+rZM=tbWB@WF6r53< zsEY6H8%c-mKCx-1Z2+!jacb12AaVs-YKcNN0phPR=9Egh>%a+>9uKf=f9~+#I#GN9 za-jIa;_Zh?La!pRdg6Z8{K!CQM#yZZ9mwQrnQ_W7%2MB*fRvd{d6INjERts-Z^BfK z>VA!!h)ye)BKHPmL}9(d+#~$uRHq5x3>GK0UMozu<5TFZiN$8G)0Y%al&Fd5F$>*& z4fQ|q=P)06V`FQ(@Cdk;TlWyx+E+1_B>5OB^uw-yCovxGgLmc-J{ zF(DlLsx;$}ym&}@BY!=-Xx(n>3fa{lr=3rPtyJD8H=Hboqw^mS?TztVR*DF!qoZ-n zB*ZQ00Ap3hQwfVDF;YIDhVB}^_E@Dngi(`0-pYVnGFBuQx_Y~KhwFr{K*FoFop|zG zVkHb$eOy?ErXG*86$D-7OW|XuT_?Cs8v*r@FzKd5^nrF9{*53cO;7|1;mK7}9AdH+ z#ThtoTyJdsg|NHLop>%&x7+Y<0!nkQS=q~Vz_k=?)o9f3wG%wy1D8w<^v$PidUWe-99)!3Ah8JFd8&fS3A!Gsk{5Inm!4r3AXBGMUJ#(#SBBH#>F)te2GU%p%mDT+sSDQ>+89 zNc%=P(vYVnJ+N-5+fIRwoT85sZCD#HAhr`y|I_fsrTo;qA$=9Db$5%SZU~gO48P-b zYzb@QVa}*84lLCB!KWfe+Kj2~+TCLu-=bX+;hfMZ95FDaNj*%$=;~TG;Xc&mS|-tU zhTM}ELdn$f#h@zRb!=MO1{zp=T56H<+YWNGuWJS2joluPuq_2i1o&N}d4-+^@TU5& zm0lnmUfU6G!EkDo&)1}~WEqL$vfwLe1!yG2B(UGEqox>zVmIDqNXCOQMD1g5)x#1* zZ5Zh>vGxMJYJF>0Ei;~1YH6rXyA>LU7 zW`6Be#%~MnT=UcVAh-gdrNLVd9!Su=kvD1AVGU%z(1Lx5b&~ztS1N-)xFu@sHH4RM zcK@e-8{!M+Rr14(&-_Vs7yrMyz5knjYhi0?V&rUKXl?Qz%eRl>c=UoGT*tGT`fg1a z+7YODTBn~X9SUOZAs$zl2#)7kE+H+%K==MtLFneJrQ_d!cll!`YAP;v-SbE_9ALW* zv7ur3bXcb94mdCr>6O5|BbN{a{Bn26j?#bzch z{;pW)x0V`MEs4>H@xT6UsWafCa9%HIac0T04wZvGx<;+)+`(ql|)4i zX~*f4t{XtoHXG5t<@;qM4+3w^YFRYzKkC}&4M1*oocBwte&Lqr@d!k(m~mZY*DK;? z!yi56202*37rS(6Z_aaa{8DhoD(z3w9446^utB)1j!4R2ii>;p6SNuR9G`wytPTM` z+dn>$Wq5O0!17|sWEJb`D@6{Nd;9FDsW>}iqen_M5w9e0=~IO5$sbqIM#vnIk& zu{&2m1wY$P22j3hoxE!?rO(1W#Nf1H2 z-*zqDu@@C1=<-eqa>etbWShxih3cvs+r_V)fKLs{ce45S46~E}F;Dt9_|Uf&e! za>HqqDli6?`j6KmYoUj*5wvC2v(_LHy1~ir>Z$zt=VLQM2H$s=-l<;39jOUTNpB^L0$Igsk-ZGS_fc zgGilJgO{Iq1u>TQNcxgsvMH#e=?SDk|CPZSRJ_<}-TbQAyQ^Qvi;*j_Q(_JcSi#r> zOOatBFOtTkHGV({RNbAgo0v-}M?>lBg4ZEWJ4Ti8p2jyQ7}>NkMw<9!`-_Jvkawp> z<+)r?(d}1@E0w^H5#qP?s;sK+L&x_97Mo;MvoN5rQ-PunBal97|J&4qmkzGtlTWBl zgSB{S6BRF+hk&!d(hRRjKg4Vtpx^tGW;mvv?;V(4PwTFED)BV2nGlo#3Ek=0XYts?hs^$-(ki3 zj-z^oy|O?!bEPW;Qe&9;!oVJZQHH{;@gE&ELTmqi=T7PxU;9ts#cf<6;GYV}LI_;d z6YqG|urJOJ*ZYOO6F%WCk zsn{iS2phis6D2|q>suTF{0=g*0UwIn%IQdbU(OlFd*0KbmXRelh;7HObB2k}0z)onhp}_vCSd2n!$|a>hyq`a zNJ4>%^1{h*PP2_M?H*Dg@;gg9xflaU=xW8V$rtEe&^x3%jI=JW?I;^fqo8Cm4GqD` zvG<5!>_Oxk%Y4NnK(eG6cJns_7#K!tNL;jR$b_bwuX1cYy^!-sTlkqqKUJTYPA2FZT{qDwOPGY@}DTfVr7C>Xr8YJ9b^0Z9$%HRkqSkkz_}#+{!q zEHdmv(|)seCYx_5Y<<%yzR*Q_0o4$xE+4Ox$1KzZ?p>IQR)Xffq-htXPEln zCSeDCL9O$r0M#1MO4t_I$5V3G#B3N|g@dm^&NINKG&pN2vc^PNy86r$K4CMkOa2?%;LC5iV zSu$9`-2u*iO~kDhUH3RU7E_q!mA!JP1K6X?v}1>F#R89;r^$_&v69h4U!pr(CQpOr zWBFPCVs(tKx1vmWsH_=u<4T`^Z|I{xbabs=)6&Tc{W= zK$}|=n);7U_#a=je&gMp_sB=R_$Wp(mnS4+fD?+-Xx}Q)#sOSmxVC z^Xb-RXtLZ6<=lFj_gHvQ5jYrK>;$Z0V^Q?FMG8q*N{w@Dd)+yXk);9M8)z+6PHss7 z02tP1{JAxCx9Z85{u9^x$tlf%G1TeoOdmX<_R|kk)=<_gmP80iboSi}Jh%YHTvaT&-?k%BCH9LN3{ z%rAL?V1k#Ch3^ra@-vY|%WM|XMpxA2HH)D(TPBe@eBS*_|K1AM8A$5I6l6lE7UQHg zZL$R|k%9!duM)55J&b^NaH-J zd@DgLxE9ha*&a^0f-%&49{#Et0zmQc;@yYCkD1ZDiNY}{(1x}_OW5Hr=9Wf7wMYe^ z*ZUz7b8XAEfA5RS;JUQ1rr%d}f%BKVi)Rh~2RL*YtLY5NK){>?=pIlxK*%VhxgzB% z6c^7!A3<@4Eig?F2){0XQce4^>cY>4Dr3Q^RHE&kDKFg%jTmNQiY|0@6F}S5v^fH= zgWZk5^bul0kStrkZX*FUh#@E)y-E4|MvLgF5xamh!Dh}O76rB6QB0DUlevW5oy=Dp zA8{Jn5N2po5=$_g4KGZo-GGekMxJ)pQ@chAlb)|Lgt28!brO>=13csi7FkZh+5T~n zZ+7fU*Nkf^l2vArg{QMkACT<4^{zaLp(B=iv~Ly$9FrM|&cGqfdxeC`|6IDuU&!8Q z@gbut!|Vvz(Z|*Xc4kLi`AOtjur=0%-921W)OuR+dNgF5D?v{*1ss8$w(Q+wl97h= zAsh_ayqolq(#OThD3Xg}XZ%}lEmW%-eBNgX<_cr+rtx@8+mSA?2(Yh%4H|2M(Pr4M z1KjK3FBmu9cr(bj`*idCm}P_uBN(({S}>fJCkXls$?SYg-#OnQ;>+v|DP4Dg+dn3K_CElN!^ z%lfhjS`RnyydRV|TJ-9v7UqI3WiYYkm!F)m%pY`PeGTX$lS_xx_=x532y-(Ca$PQd zD>Z}|l#3I~QO*`W8=CRI68@hDvbz>AiV<)~AfPzx_-pL{Ibq7h{{I6)OH|*CpJzt` zOzY(`BRZ^c2D`rfvq&MZ|AWG($B^80o>?P%QrXUMn+icI;U z@3kGi*eq&U9scV~#k01U0&A*u!3t{I6x6nwFpcBW!6_af%Zl8h=t=fc)YjawEM?DY z3B|Vqtk2cq_YIF(a9&7To~7E2i_8ZB!DI-xa>qu~xgB*zn{^oor#1o26Gx(IfvjZH zi(#cwAj48Su z-qK6lP}5u87i8nBAqCWot>Gc?GR-CN_Tma05 zFuHY%S_;4Ft=VeVI>5p>>0i3_$MH$m&@?S6m6e#Wr!juNsE)NJK`I*a!9Hj-xCvu5 z0C;c#H^1Vor==53^Qcwutx%CH^6Xb*Mp|o4G-=Clm}I9|*hoi>0^T0bY9pfv(pnJ` z+D2#e(u6m)T$bWBj}`JRFCM#-9y^wX>tk#n`X-`?z2gQk8ZC&jY1O(4@7%c0a2--Z8? zM27*VqjaDaL^H=0ReLtuYZHCbP)BVbb!Ef-@n4*~YQ}jCsx`{4?3Zjd#|;^1y-K?4U@lyR{5vZn8Ay zGWKFBnXNMBpm}8f!1eLmAzaEs{^E7J_yN6dI^w0!Fx8Kj>MAy~^=eQuFNI}nqGpLa zxhCr;-LOoHGXJ9aMjk<$`$~}ystV&`P3IF6HeSB4FN|C#4>QzaI9MS9g&L}_ z97X&6+9P*RjH5iB-qy(=t!wS$0)5H_@fO(t1%(G8Jkcr9xUB8iVlD{+pzShRld?qV z6Wc~)RHV|6!hM741%h#C3(pxvFyG`gp*)@kEdKM}Rx{ACI+v8|u5F6l=zbs7GCTx`0n@3HhAX)GM~~q_+ruc>~iFOykGlgigc{HdkJRBZ2N# zq?78kdW1Iqu!aO3ShOMrgcGm$NodaYHbzX5n4wT4%@qB%gp)^=wbu+SGi3sM)~^8; z{tL5g|FDA{`xf3g|7}3zSA-~enN!P8k7ztD?FaN{Vp0-A8sp>}=Ru-$20Ru619+E$ zT_n?s8LF29FNql%c7%x$gTeXgDM6wpS0N0SCtReQBT&%ecZ4P?AVK)m^$G|@f5e`~ zQ6D)73g7GCYyw`_U_5F;>^vjkp9R#v8RO{3#yv;NS zJP#j4BgOHl0^)uqfT2nKGBVCNXj-FkxkM+z61N378%I9926*5jETJ~mivbtn#1>j%gRYetXWa;*6bwYo*{67)KIC&DLgRm5AW0DIQcl+%%}q*{l)O%7H%_^$9f{F%;Ac3q|sIB6_2dKY^SMEtIib7kmx zyNPxvS-Ip*Az%LGgB2+@kJxyM!r~jP`6|6jNyUv8vaZU?$s1F+(oFf6ukfyc{W{yMln^8#c4+(mGu2isg zu4}}X*K4f#sI3A%SX{$nAaW?9;l;m>8}FEpe8Mg>u71?Zf!fXi1)ikj z-0k1ao^Q6<*()FmO11SwfEIts^CKt8#N0tIWomMYEbpeVfq`h40P)iIc5%OLUb8!-<{!;q{XtpopnG()V9CN<>Z|k;)tLGr??&&yFU~E`uO#<=| zUuEyTQT@EBTkoHZ0`PHH&8MW4_3LWBX^Um?Op!Rr3Cmy!L!VUBj;vztn5NUC|E;tp zd(IcY{b_@?W&i)Nhb(N(Oq`tmcRw`ly5suLqmEu}At8{vV6+myI+^{`MeDXJ9~NBKhD-Ik4 zCFQH95@1ofg8wM}$xD|VmrLz(`dLn;Y1_)9uA;ng#MCL5knj6x5XqOLZ0g98eo>`S zKSi#nD%^XiuQMe3LwM8X&T)|+kXU)R=iN?y@F!#2=3rW5Q=xJ5C_>;q&qHP0S%=;@ zu1vd#<-myMysfPGp+O8Y8|vjlee&=8HS$h{HedwtTR4Zf^P{wbp?rM~Nc|dXACG`F z(yeWZ{i_`1?-56sSk_qDIT~ka-QdE%8TkjWgMl1`R>2e8*K<|&@V_%4Y2%0%O`}R| z@^-&dmtBDsCf|6txG>{YO&y#G$SEl)W7Knmb43U1$_8yRX~Tq*Cr3L*(!@UkghBfN=V5f<=2q_!Yn~5VJ`GmQ@r!Fo3P~Ka+O}n6b%j_?6a=sF0m`y#aYZ zoT6EYv`|4vRQn9Z2+ZAB)E%bN4ayJu>ws<)nVbxQtnL|S0f3KbN^?B;pq)Mk+9dW{ zy6!Kl!Q%WrG>&ML%Hq*gkNNoAFJO?E|OOi{f!jx&%JS87kA zro%)kL(S6?H6^!Mz8-nj>ZZap&Agb9c3@SP#!cDuOFZ%#L?%YeS6-zYZo-$jls9zA zg4n04C{=6$%LWwIIkDod+>$2CdX;%G-S($1MB!7)bPUl&H=|HnFDE9>4FJE6)FBEw z1?4G{Ll)Z5d%d`%8(}!4>KC%I$jD|8GI`^GBZ(;7_X*j*7pqbtPQy~tiA*~1zqDm4 zYbsCJ>A#KS~M9auq4NPzw3+`rCZmZ;|%;dWg{yI=`R?n79bk!pe6RMVB`+FLD6 z6RLr9jk)vlBf?Wnl@Mt)3O}POEgZr>%slYQ=g__auzQMG=f^)KsXwR7F3biU^P{uUb+Vncj4?T@l;*z%LdGhj!z2z{{k~1=hXg z_)%wEKx?dovGNX4PgbLg#%Ri~@>xINw8t2{7=0f0~Rx6NgF0c~FV&&#_n zdBo9n zOyTrCRLE1(TXs-PiBMDbztLy|zY-_w#M?roGONxK#dN-{g3Ii$Gr(Ar8JSK?xr-?H z2LazGl%rwSsSz4$K$TtcmUSPP)@t#t`36Wp&{;@}y~{RF04Q;@J$7hDV+;A0X$GV_ z0wSBd?xLf(Zn9NWmL8uu>-xAu65G{HR4ykxmxe)z5%lsC_hly0mj(}mNAiI-wM0*7 z@|A$4(C779u?{t)BzmN*29sEjq5ZA;Tz9x{-88o7OQf18`&P<^nXywth()r8HY?~w z^2HRn4RnTQfd3nMULjqkG^Iz)FV`FzNAu5&87s9v8|Fx3_M%0#(djZhrz~lgHaYyB zw@+r4)EPICh&A821J3gQ{ND)HPbkQD#D!OS!{3Rex79otU39@Nv_xHxWREB1+TO5I z!So}oU(f3@KtgL;ca?Zg|NeUGm|R3>{wFM>G<4ZV3>f??cdGF6M2LNhW&0QmTctFGA)Tp; ziXW@4Dbl9Y_@8ku=_4*!^Tvc`)tIewluXkaoz8rbT*1o)&5(!{V!OQ$7*>c}nz*sK2Ip0s6JP7FNrOb3x+acpW*x8IMRrok zmx=C3MZzPonxwFeT9Jhz+Cj&CnKDu>c<$hy2cWi64!)9pCDrS;-UxrrB7ZI0bOsfn z)01c(ck|?5`Su?V-xXL}QyHkj#yJN#>KkEY+DPckIr}z=5#3a9)iPoqwB=@v&b~-5 z!o~`bB9_27$?BlcxAnl#ePk=6wSS;le-a1r5x#1qfck^ zg!fDQ(F|C*!w@jGA1Nb~oSip8#1VQ5K>$O~ww_J?6SuyXTWC)*-Po$x+SY1QJDVAC z(MX8AeuQCFCfdx-S+g3)o)PdRBxP-R^7MNR%5dAJ8x)F1W_=DBU+L|arin84*JZX9o{4!t6)4KVj}#DLny-%wSpi%Pkf#Y zO0NejL8`ZENUnE)j-MBXA0-n-I@7WC0kVR|IfVY9Nnub`LDIS*posVv8#{ieGg2Q1 zWby3FXfiuS*2s;Uq%y!y@t~4m0`Ts8RF=nA&0P|j#W2BzZDE)lcosgGkC};v6Y?G! z1ehPPONI9Q7?j0o{>p>!Zdh=YsjzF;#hk$CXs28>72b#(b?;Z1)rd%uO^s8cVmTUX zDd72aFi#Y~;W5R|q5B0I&?!juXHAKe)k)kLX;1^!GWYbx?{~4MG#KZGHNb;P+=c^| z?mE@X{p!qg^-6$56&udF$M6F<&LSr|7olc2FVi{-kGp?j+yq@~gRGNS+8%Ari7YFE z6Y-jlanq@Yqp9%>(+3jab;zf%4T~TIR#I#;xIB5sN?)^3P-mpi z-N@jWIeweXXwMsW7xN*Gm7kVWetqq>=RbJQJ=(GkmmOQuk;6loIYcuNWC>TSVlzKs zlPs*-uJMAbYo%Si1^H+u_-SCXDNyzBXg!x8uuVp8ZmUv1oZaCg?(N8y$zA~E^Ckmg zoZj?eh?&4gUAtVhv1&Q?LpAeZaP|o`IAKQ`dD}9Hmhh(SCm^H;cd$-oc+ul9@I5!zy>1jo@Xs6tyu+4`^sK&yT4e_NXA=`~MN|=vN6AMI1c>KX%C^+8JO6b}g4DSvI zhVSB9U0#@*Xc~cL7w}E`HC@;eG1<;G)2*?;C6D|K52rT*mP6)R2~>VAU7}SJ=c0boO$T`mXNB2J;-N)yw`51>+5XP8FYN|C%)WyFi! zDpk_b-uvNaHIIw8%+8#tW0*YEoVb@xe9ChBF(LlGD9XDYFJ)Vg6AFNoz)CUv`<0`z zj;hM`&!kaDh%Ne^kgap3C<^BRK~HM>SDpEx?!Ir^b zV+Srwk`%4l8vvcM%UzobH6_W#{iL?2A*79TMU>ZrYET$#vCpk_2oG?fM)ewmN}-)u`SIR>B3#{d+gf&5k-A!q^7Tf_FP0Z+RdB}UfH zIIF+?d1d!1x~9RDDb8BLO5XUPwwTT?#o4ZMDT2L`ZGYs&sKG*AyM1SFj6hkP6ULbi zWf`zOGyss(-SUsuGM7{FUbI_$57+R)jwiuFV9#Z1jGF*HBu|%<0DMt7CH0xlg?$R_b_RA}5HC%C zlOCGxxT15eJ5RhFW>lcgq)@)E(pJ*-Dq6P`F2F{do_mPL4Netg!!=D6WXCl(gD*p1 zR4bq3$7WICFf}!2HqX;u9*>;Mlu?BPO$A5%LVk!0*^?&ip6BT+b`A1tGyDtm#tKkjw29%P@8z4xD;0-h2vzP#v@G z@E~G<^94ChK^E&GGv63?`l}5?v|cphapF6VeqUpBz-yW9F7%b2rn>4R1SHm#F=CZ{ z6aHy@lXz+Skm-n3|DIY=aQ2=wNQk`Bm^!_-T3&tF>t4-IXq)<1ruj|wZMZlgQ>|(! zjIE@tj&0@p-zFKmUCx>8XM*3r{-?XS|C(V_7h5B}|BU`)mCIC?i%Dlf>b$2FZv`Vs zB1Ez%r?5ceQx0z{*ZqTrjn$)RM7nOx{@oQA1wGo7?ASBq-Qm%ZI%?VY7Q&VcMQ^9M zATO;}l1pH0(fSX$?mak7dLupn)Wf0EI zM66EC6ik?;e}d6hIEYB=j!fbfT$`5ow8JNmY7Bpd)&;PCH`I7d3MrUs=Z)=r(q?D* z+fih1oaVyFmqgv6>-90|jCQ%h_3?MP-38?`IfM^8+Bk(}|1nNhLD_(BY=$<_0qkJ` zppDy}DL^YwRKrs^WfJ?M0;~6%U?rZzznIaD`h9+>F!ty4il7OS-Uzw=3wC2BOv&cz zqRh1@q&vUEW$f7)Ov}D`d*<(lPs2A8Pv8n8g)lK+NsUWue#UOU*Uq9=_ypnAv5hV{>e-1 zP0TMbr`bxyLSoQ{qKX~BpA8I}GCP{tgBclnLgt6%-SF6L>bwMG0I}!~o!e;Q6C6yX z5gVKm$!w5c=~a*pvAioi{BN=Me`&pWIBlg&U_d|*KY>`h|JSW-?`UWAAC>oq#B5+} z;B4@-+=5kQV%FLJ6GkAL8Av9guqom%SmNIlK>$LwP9hhZBSthcH$tIGqVg}?#`?M2 zP4ev0CNGcj85XM#UJ6B>(s_gNT`S_mT$gLni(hImcne znW5)_HJ`~ah87305FoQZflEPMo~bF1_?F$f?(n(TqCHaT8`~6ej6n~^82yEW#+Giv zl6E_`;E!5=mX>Z6!ENS3KdpYXU5lZDXhei5SM*r7j* z%^+rbPH)$80(Ta<$Hbp2oBk0rFbAAE>it;Q-GbBb?rzaaLQuj0gEoZS*NxUB*gQ=y z_5o{ZA6rKt3LqER)2BeKAV#-I&}!^>KyF!_E+=WKLn&`OFDY@Ei$^j8Vl09AGQi&G zUi;)!Mb7+;UKEoM3z@+UsGvj7;okL}%1H4?CH-f$WJ#wavjTL3&R3J+syR#`Ez_7LqN|4;XD#qViCQtM?$faXj>D$Z=r8 zDHO+1J;s5hmc}2vnI~{vCF#Pv!K8s!^zR{MN$@m7m@*5LH7_#=uIPAzrmJMYWawVN z%i9|U-=UdAdq!;EEb(s~y1;!?mXblZL>0^OAf85!T7C)f!|Dn>DgQxfS!4Fnl|ET- zELK?908p=V|ItuUL_@{dD0ZMp7B?&IwUC?BSg9^)&yW%SE(w!>xhx6JOBw_{f#eiJa2`gy+2VGm3^#jAAhW*$hM^N3s4mewtRI7m1fL(TuQ4Z%;Ta;qB#Ff zl?8@qMyY9O6R*LsuFwS0#k_(baa+fk9#gRFobeia%TEm)Bj3dOM!pG*v5@G^m=1OF zG3YHbDf-uoG9oGq{kwj{absm!@nWQ=y0FD;^qa2PGvH&o0iE$*GK`mYZgQVD(PWbI zH(+%eT3OZE& zv>94Ry2A|4(*0gKtrrO7C0l0x(b*byz``k&77ia@d|X%Ou=tj3GV5S3;Qw@>kXozT z?uY;ZrTPH_(fz-53g}Orl8kHxj0QOD9CuBZnj+`EEn8)BQCL_^kfET@NiEE=S~O zetskTo(6pCxkF6r%=hnmwSK89qv89|L_oo_5`&8(-h!s!{^YHA^+obFtXyEXviF@eC*E3$+9P4&k^< z*_oi!n*L@}{(R$#dAq$zBY6D8=li;{`*1G=7-I52*M0B)URRT;4%;4GwEKQ@ z?@VtbC$B1qir2id38pHP*990nsW^=(%$u))9BeeRxc}LoO z!2OzS5~%e3{yIB4yZwCMzWQoR?^P^;y$ILXt&%zN8`bcdcuAbITmtN@@o~WP->HlJ zlz9!3xl&^mfxX-5kHwYFZja3}D?G_k2-2$JW!kuh81UWZV||_Vd!taal$3J;+ovNQ zHLFmaJ;pe=7oW2Yl7j{sjqbwD*@bM}?k^8h%rtx1?gS zoj9kwST1=HI^4$LloYF>Izd4yxRIpBL3KEIn@wy+1 zmb>9PYPo<;FQ5&#{p}`8Ku3qMb9>$sDfgc>{NQ@R)($N9Vk|!-c@Bvf`8F~&gCZ(>8 zLv+CV22cdmj3G`^ktjqMl|zDhFpJ~xhczsaIB&{-*F^mF!$hy!J?2>`Lw|KqASu9* zu}(vBkZdtdbD}!SU7LKc~%&! zqI$z?jCMWo@h=~cdloA_k&sgbUqJQqbMtf0-kav(?b(y5e8&Gn*INbU5q0ajxO;F2 z?iyTzdyqhI3+}GLY24l2-QC^Yf)hU6-5n19T4&ced+pj6T{Ziny?XZO@xJ33eo-i< z!WK=~s&OM;zZ!c04uywmJd#3Q@&JT54>Y_=^d#yT6Z)zBx-fo-Z}nd zDKz{xe!{hH6laCDuTs>sqlbqix6@+1#dG+%-RHacMl-e+2VEk4_yjR zhBuhEm9P?k-=yO_dy81?mIlIqdLS}}j;)3ErVAAuNr**_?3mOuBENL8Y-3|}uOBYi zU3VHj^gQUk*)I*W;&0DSXd+JcX!po7Mxupfr_Qs8_v$-86WRHlS7w^grUXtdI_xx) zJI|k1s-Lx?r^!r_hmi{PYV^@W3U`}He0@xa0Hdk^RJj?sF^%Lg!qF4*f7+;*2yV&A zPptb*b_#f#G`2Ihs?>dfMDq*#uz>v;is)iq|K&lUY=(h_Ii4ht)_m-D8A*oRSbEds zblnR_gTOX*)$ZH+TjgPY|H5Rj49gO>)z7NQ>5W3%6CXm6&7i+>^rewqB4!*nLSr&U zkmFy70kAXVZf(I@FkMkSa=+-?%ZG4R?U;i6MJbZsZnr3tY~cwr3{{Es4Op=l2SW47 zrF0X$_)q!(R#S&@@Vo4|uIQ-_D%6?H%FD1vT=z-gu41XWv&c9CL?^L^ScUR{cVSQ= zkLJ*DCEN%GAJZ>9CP>vrZ5UVJpCbO1DxA(RaCt*}V(jx*B|z&eUMBdkY3kpiMJ*X< zZ-!A-z9@DZAI>6VKUr&2Dm{0{GJ#(m86u|B7()6%$1+TrJ3O91u;iuBW zWstmRz-mY>rOCiCtCslbpEyl!O@=PpGp!}{I2N($0Z}W{S$h!G02{qLDQPx9L}V-I zy@L3XX|s6Og+)GJ0fuPzuzbbD(f|rB05eI{)PeFvuHz+>|C0hkVI(T6tIbgn+mt|V z)9{EXLR^ilbB)@z;lfswET+gdQ5nONNss$PVGMTw^aecj+1MS-jbSE6BijR)DB&VztOy>FKxv9fkXPu_a_bY^sJ;U%g9}S5dLYae zgdBz3Ak%o7?jcH@0#FmNv25xu${QUvU#e#5I>hAElp=GbboB4^lUVIid1&9)NFGzi zHHLR3Cp@DD7bXF+Jq%slA~ee>oywpIM=80`k%9U$cDN}8*6Sb@Jq1lLH19bmO+z~vh=O&GFS3SlRVbG!bwMB;3i}9tMnOS|K{*zmk|U6b4Mqqe$;Q#=BDHV7 zb$`h38$+{mCAUuKVG}{rOmu)EP3BdS{%Vht5s}#YH`dp|Qi8B$O4u#SndWW2?*Ps< z_Ms}2vKU*lF{y{fAe50j24y6X5UC242!#b{YC)1I=hI8GhiA|jF!Fs|gCIlK+Qa59 z|4@)QPBMlzhaccZa&?ccBq?oBF=QXnkc)Lfp_d}@g7NG$Bg>31&L>d7ePi&wpBVYV ztxe_fn59mnKXjVcxX)u*vmMCrRi0c3*KM{)=t{|o|E7dc^L|yQn?q$8WfvqpLtEMFc+%?hbUOamvk*aNIg!4|AKJI%>b? zoRo`qt=XrFT&7e+Cn_4^l+%xy1%|QRim)IEz-P~O+?HyDLox@tmk0dQl$17wj`&j4 z6*6k9in_gXNg%(ujaD|FVJc-|UiA-+#xb`e45jv4je+8~oTP#U^d4O`Zke`h_IqP( zzAHX$6C9udnJH0>-9A|dF>o0p1Nx`0!kUci@5{hvUqecxJdUyfD;>I+A&o#8W~~nB z-018o;_CCm_p8oz5a1bYJNMGx{D;48Pm0B~I<=&!foyxAj#pJD==iEKNA|Hvgy2O& zf7~0-BTSRbN zL2)%mUKwC~kP;{yqCes$jNRuZvr=NmePVXV2L%e*{OZ-x#?xU2d^3dgh7|ji3|34z z$yNCU+bp&yoW~!k2ZvD!HMmoYnIB0!J=t|mgW$~;W=C7wMG}3qc1jVq3SK~1B~3^{ zr2tT3I8_wO5mIrm;x+IytDdT0cL3)`q}<%E zTH}15PCAoaUlXgFJ+ENGWsZlx7x|d6`qtSl)ntV|=<*>Mw5i9+(8;pJ@I&GpPL@5E zCzn5GfWD4%hg?WFdofxRjJfG|~)OAc!6^&`ikwKJtoYQZ-1zp*^ zgH6P@52=_^kF;n&tgt?!!wvEXWqi9cmhC#v=te?xaRXb9+WOhla`AxV$Ah@ znBTUh;mnSziNLGn)}uu`RKQOwmK7YY&P^;KNjsk8Z$qXFbx>CgfI!R7F(v%jZ3LXL z7@dQ3b+5X_CG1eJ`&UOqioWt;K*8y=2g3+}v@A%nM`fBY@epXkJ;qyIEY2#9qpA1# zn}6F1c>QafYcfDrALKRboCL8eq-Vf@YOwlk2}KrMt}GOL`+7c%o{ay+y)u~~nN~&$ zMtWFq&9!Mu`O~qA;iX~!^K=C_gaCra=`l*(S%_p41$fDI;&AeOTN*ehSg&C#8=fA# z2L|$SJ>M_wxD3x~nIdT<>iz%@)*}gK3 z#Aorb{&pt#(P-<7;-FK?FRWG+)zU3(gW-`q0_#fcneiu1LD3&4!MutmcL2y&^g&to zB)re}lB@(eiqnJJ*SsDolI`%yode$R;W&q~9HJBdp7!JWfV)_Yhg;1-%@&jvWO=!( zCp0f95Y}JOB81y8e}N)dt#I#b7P2o`6-_CPAj7*&-8nRm;NHa9l&&3U8M5L{l)=1f zU)Dl@ob5VT1b{}QXqQRI900s>_nYMnEBll-JcbAlOFx&`)k*rMG2- zumC#EH279X>U6Do^EJ`O{t3XZ&F@*6vgAx-?y*deLEE_>OQ%i|xcQmwH|&Y^Z$v@n zbd)7Y{u;~9twpn%UYW-!goAAjD*I>gKySwX?hU*=x<+QN;!$_+0OfE6c8MBu5OkWR z@Q!({zeo?n~#Pq*@%`m7xE}hJ*GDA+{}Xx4j&`4GJ7v!uxO)I0};r|hS4b< zV@2QWpm~*qNUnF-fEnb+vCB%JzKU{Fl9O$kc*4WxB^D8*T@<`p%4?#RJ=hmWi&UQ5 zjk{8Qc*jygw|!Hl_%sU8y@NYTfr(kyV|c8r7LT~*7d(VBJF?Gf{-)HZf{@!@7k#5! z9J34c<>C-O-TSAi0guk}1GC6i8>$xu8i$Lxh^YSa$NT$t0P6L-@^Lt6V>!;VX6A9? zESfIzyjG&*v@1Bqb%hsaOMi*E=DdVy!pplJ$bPN+we!TBcQg+-0oymjKa*3{%=QQ0 z>mP;?xL@K)z2sEJJIXoZ;7|h9*lQVf(J8Kk{=jS1Lo%&GxnbPHkD&Jy$^*0VX^bFU z-eyoIv%$#^VE=e>EjlX3>vzGiB>#}Ul6;REgBN`4bo!whS39ThaY(X}&KTn0RI58R zjTjN(u^(Q5ZIWY9kbhd!6yNiOR$5lW|)eKlrV>-YAsTN!`C*@H*6)*~c@-uYv&_xeMJ?CoJd zb7&G)1mVb3KnpsAuXw`>>fOfKjWdN*hJ?M3qjI&ftoM?D6==hCbTRqY)AQrFaagz0 zMUA5sAc)#%`_{;0hfMwa+Ygt*BzK-4ma&9CQPI(FI#EGFCemd3s{?FyqoSJK+hEAF zp>i8Kf@y2%dxj(!OU9hD`%R8_SJwxqJ8;SarFsck{DH?YYm31YTf(p&&D6^(Ql%t#k*exd#>zL;?{0(7ed`crlQoZuY^!Yy+ zY&KF~5hT2|-)$4&v;Onx3vZ0=V34i8jrZINetcGae@fBf62zV>sW? zf8{+^{}Pf|5fUn zl9f@0>$XuaQ@Whd`oy?Li|wgBCxr|kfAw#1-6@5bYjzX7i2P6#$vm z;(0jF8ZkU->|d7;{t(&ekKuyb`A53JJ-)p_XZlQebRbR$^4s58knHSgYlTY7Z6cIl zIiGBgalF+OxBGFS#u8h-l{zpFGuVzOcL82m!rPh zwlK&Ov-~(mI#-94A(1jo_me%J{3W689aVQ}RZvwY!Fc*<8m~COG;%B*p4PjfQf3}r zDZbmhp$qzP%AvItv72w4sNk6*Kj|-m-u=Uto&-q>rQ@?1qlU4E=w36heJR1tj3vY& zq!zr$;SdlPz#Gu~GgsJI74mBshBU+P&Og60z6*oz3AiqxwZ0?3-?E~mU%ft84g>3F zYeH(FG~mgL5Cs&;iqA;kG6GC+k_yube@j5xIKuV9&B>b_a5MY*WmeVdyu_AC}+8=8T}l&OGAE5urv9*`}cB-OMkjhHXfRQ z=iq~+w+jPk2Au1v-ccB>amxj`J)}3nBH|qI5TpxVFsS${L%T1#y0(cJBCFn}Zw0x- zmo2gfFk*3uX&bgF0;<$bS10D`So+V|$Puoa8rhv}6iTO*GfdTd%gi(nHAy?570*p04B26c;jB{PLee2c4Au=z9_)8cAP zPo&)H>b8koi#i^FQd*H&7MILe7*gdTvcKPRl@{I%4YR-qMX|j~l_aPxI9I;xZ}UYM@PER&N9(diN1 z=*8oVRi8emcM0+f*Vc|5HW1zzk7br)+*7RSG?KSmt0{mONOv-Wvd20_yX!i+Y52%D za?vK>Hp((;T&0Lum{P((Iz*;9j!nky6f6R`+A;Nagwpv7#W#D>!#lVw#`U;^9i}ol zt%C9oA7k#*QBg0Lb9EUiEYzF5FrPZH6&EcB0lCUl^9e~)E*^M zidB&g)s2<4TYP!K-nYC*b`zR-+D$(+ZXDYZ6bsA?{1fuj6wSmHA_3YvNJ@bhGe$!E!1#XMq6Wor|bIl8#Bgv zP7J&vUthN8t@MfJS|7x}F432=L29(N3A3qnwPhJLo^FrX?lT0B_1pNA-}q&nYRf;u z)FhVn>Y8NUVb6y=U=8cO?Z1!WQC$;{O)P$REb_pT^nh_SmZ+Fe%#eIczJGi| z%YH%oS@Ihyr9Dwzw1xa>dI3Ba)F$`?C&M4E9t80#hZ@*t#l(W%kt#XqT`T4g z@{sq=m3EJ{gDxzM{2ifxz=<*j{;}}^S$$C5c7f(Lh?`XU@)F7)Am!-T2Mwh0z4DkT z_zc?QT_-HOiqbmb-G1mG=Q8w7nA-fc{pLxaqkC?8q5H&RNW&lf1MSRvfgK2H32Pp) zV3QR+O*6KLv;Z*&+F4kiNiUq5s-cGq{Q$VrKXf`A_LjmdMVH^5S$Man=hHTBiFIU1XL-`M*t) zccOq{c*LI_S6R&L*D`w61rh+GrfAHudL|!hH)%q4(-Wq&z~o^9#Yop-wP!yr(pF=h3#R+7Bp?5u_h&kLit{Nms%kh;?@I9gHT zX`bEcwl*!n9~M1%a#i`1ms>VdZ5edZ;&e`~cj z3o(Go$e!6+*ut{U8VZ2QVO3bg&DUBR0oXiFB;vp;yWsHXd>_8#+ZAN*c;IhlWv%kUG zLjk>u(LV%H4|zXHj#Mt(Ynxp!QZ~|E6hwR__*s`hG##z9{mHwvdrm(2%I^kG*@1%X zItZh*msWMFWV;cm!j~t|72XFh?>aI|}q8n1o zNbM5|+$VTsG(fR|_oyBmd|v?&G()G8iv*IVjWIXZZagqE9;5?N-9!W_Gp9V0eFmxT z7yl8sbc4H2$3)oe82D;r&6HzSwk7+da1rN*_x0PkslSH68YfmXVi&i@{GdH>AA*it zlJiE-7wlebFYPz5y;D1;0p);w6Qc;t@Iq=20DHyq%o2u6)DzWtmYTa)mdAP6B*67Rct1jETR&k>!bMif&GhFAKR% zFGG2mY%3u#t)#-Hp(McP{glS@&9!-k+g;m9A_?-M=Gz5BnDtADNboO+^PCYq4`02f zyIuMYTzh&!^ZVsrQ+Fi3RxzPJFc#eXKt@7W$`koSkAQJ6Bb+M1lbBRIZHjvqpq@rT zdorljxRJWXTXnHNS;_RZhSa+{pmYoDJlR8Oy=~ zz$+k+X-Sd5OgG%q%qdM3l~hlS_bNg%%jEuJjU7ND_;sshB4&PC`~dntHxjrMtggOn zN_Ni8BXLTkWwr$&q3<4uS^FICVWTTsp*POBYoc3*yxo(=@#Mv+j!e}!YdeJ3Eh5&P)(hg&yT-06TCgR}7Y$qDh;&cs*_uLhA^Yyf{D1!OjVVpFJJJ`fWw~ z)vj+MUfq+Z^WM!B*sKqtnIDEib8FIca$M1ny&niU42K^v9CJ%r3w{&(^Zn+t~%G zyT2y$Et~b0mdIP|Pb&XdOI!kl-enSNNN82e!DZj%H%3WOD|MEiP^dE+EwG|EvL%C3 zNLwvDpf^-__cSXk6u4NFtjmHPeSjgV|Ij=#*|4T67+JXSIkZ%@)DE_e4^_z>_xvJU z>)KF^|CU5C3l~xjzKcC;;4T?*Gx1@vfqs&mz<1V3X+5iXsrvy`C^$FjTce$z-D&W< zVqBb)i%b%r2D2l|9NF88orjoCsBxQrin;Ihj$SgB4^vOa$%3tLBgG==3CiM(ZEc86 zdhe&9Ch$YnccIahtgWzD`u6{tU3j=HcF_sD5)eG2h+e9qh#DPPzuAs`f$!(qDrDG* z<&yYzD8S;xB2Ek>bKz;{DT{r^h_%-VH~+RnGOP1_nHLwT;)u%@8q=nqqx4WTyzng` zazpaT<9*a5_Ik!WzGGH~QPUM;_EGHqJEQzy*7AT#6=j?P#XA{4CUDPK6d;@2*UQ7~ zG!S{wkYQ1Tt&ICwm?J@UvYeyuAZA*@&@!B>cAEGFbaeedzA~ zrp|OUW-qd;?Jp&9i z3mqKt8B@pr|FkE*B#tXQDW0*?qQM0!;c`1`N}3lE>h-Rh+TPtSxMzgINY{J`+bV2w zxg)FuJ~hC3Jmp%*=1%#(5!P!tsGcQqIgFx;*+5Yb1j|K4_s7;KqUkQ>&gv}on!_#%D1Tb6>?&6AB{es&AN zXs*|gB^+oAL;C-EEM)Z$LiE-`5YlTc5N-VJBajZvoMUpS&1W>-vXdz==!kWCh?Hg! zB<6xwspKl>XaD?a0nk%&M=)_i2~4eJbB$mOAS3wJK`YGPj4b_<$tK%gFvaTsDRi{XD~7f4s>0LE!JHERGL8SBYuy7-@WMOT2I59Ixi^~Zkl z#U7Bb?16{efRV9wkrR+4DZ|7UBJ)rWGx}95p~j3?ICU_Q%v4Z6_KdpqGhp`%^lFP0 zmD}#Zn@4&1r2O0|%y!}8Gc5Aa&GRfu8f?Z5o>6ZIJe)-u%Sf}>!ZfskPD4T^zdbs1 zorzEETCe=+q??6gH~TFUhqJLj^ViZg_yPFXP3e#|!YzMbquVU%MU$Q^fknGbYLdj|Td&@u8t}mmV#)hKyP=Lq=X?9vDMoF!)0DZK$Vl7q5pqzd4X@ z6pg2204|5oRW3}d$WcbEF&X(SX!s-0zr)GRRK!2RR-g=w9{dxPGzwRAnNCtq;t5cR z&2ztkoIZQADm*|_01{srjoTJfmeZwZHSfmd~uknnZDDb)MAEl!@$mCd^_ zwV&>9p8CXh8QHwpqWf=pWQK&R#!mLls9`|^Y8dj%TJY&G4VEBm^%`UhZxXln5L zKXQ27XS2w)+&)q={E=Hw#Ftdp_>1gsSKpDCoc2tEIcbV*UtT@5U6vjGZUrxSZk^zi z?@+(mICH{7GWis6Ba#uR)F^CxK(dJQ5~iZ@A;*ICqJ=tW*7a>#p)nlfJB)2B!SNef zdBlb2D|iYwxQ}|HS_p86^vnS0YtPXR)mqN>x-D@jB2bEtzq4bL|#kt=B#ZT;!%H;(@STrT9%Eq+TTWTlA7Jk{w;ja?36Me zkIce3B6odfrPvLW6+-(c?q~B~b$bmApi=Fvsg;o<1*wKj#z>H}K&1Mkn)c z`;s~J2|3(3wJrVYNMAHi#9#X~4#V8_&$)Z9+K{qlJ4q}3i!E)}?=@Cf+o<$QQ+WM^ zT0aXW8y0e~&AoqK2#XTs+4v6w=bgy+I$ueI!^ENs`O>g2*6eX(atl!=JB^4xb3(mL zJvW9|?J`QsYwp(QqZ=A`lXJ|CDGG=$Htf#btNaeqGtP4(;{(z_hSy_aT3v|O&xx6V zV}G4?lo?@gJU{n|Fx1t!>FkyJ;sqS~JbzHBH&^4!6$f*bl+olvJvoH;5uai_VOz`N zQj;p=N8-6-W=}@@8qS2xNE-i}$26r7XAIl{kl&FXXg=}o!XS~+Ib31o}dyhQ3+zS`rF{hC* zla^_{%oEj`agSxaFheNBMpBDxW332=o%{Oo;XE00SFs>!aFg9~IMuH?kY}|2Vte5Z z`>0c82AflV`dn4Y-7JD+Bc!mUz@23-1&z0VA$4Ko{ca8g45!>`3P0O4B6`>AGf{wx z32s1>Xw@5SAd8gY(q&w2X(BN&Gg5|WMG^g6{mf8JAGE%3iiXBiL)upl!*Su%=3({b zimT)1?F!qW=76y@7$_e4`^q=QVT+qz?czj*2amU~&tejBJ6j4qJ=B8LON%hhV&q~38_JmzM&4qZzkZ;cY#M3{2y}hi!+IGO9ixpRbc51Sa1Ic! zBJJ6d)aWC901JfrkwESQR1Q+>1g`G%L4YG@{XB>!c$|D^7HqBN#&<}m-L)MIYrbwZ zR!4=_#Q9T|(W2H3lhGlp7>-?muSO3|RF^~7wEQ+ub8+hB+JW>%;Gf8DhuIHdpPQt8 zi%?1~V1bl;F5{P#Fh#=Z-^12>aYqe8mO_qZt9yQ~bJ+Ha#6`)svz=A@aHT+~3fn}q z@0Nsrou2qfyG3UA%|g68QE-XCWp4?Bz0IuP9x)e_@?(hQJWOs}+B=t;uOHT}bQ2b| zyh=%vq49ml9=(0I+OqT6R9W&ogz%*j>v*Wkfc_8lg%>>Qqf@7`f#h$?j$DGl()|8& z`$tMS_>UDXs|9H?V5n09YH||Bw#!v3We_?0zkMZd)~6Xw1*jQb!NEWN5=3>t@-XOvb(FOaVWzIfjvxj?lonsi(WM@I^S39{`QbY0)-8YyqH@BZ?=X42A4<2w%tel zz}>>+PUk7XQK-k|{!uRhZZf7tsy;j@kIVL_ZU8A<1Y51Va+0D&r6@lEZpd|!cgl36 zhrz?-`mo2!m07+C)He(>@5pf~?-lSM67!wptWIcY4ZU{I-(19bKogDIcz{!pYo%_# z8}F_&)9GgGFUR`D%NjJ-)$?kp`NAPI@UVYZG^BmJy{s5yLaX>?hn#nrzNBh8jH+~S z9HZIF`qgapfs?px@04K1fT0fenQssyu5NnWXKi1_flgHDgua&R1DsGG4B`WEU*)(1 zqXIO?_E@UFvG(H9_94~%BgsO%zt`u^^F!B=~B0?MJUv1KK5EJO57g+^kO8*}wWH!7+ zhFGsw6?QLU>q}^^qU%nZx54TIz$y4|eBf_`5{XTw)>+Sw(ad6Ex`sK+BPair> zg@NOIvutzg^4)@+#-S`MVE-!|b)$05+Ur2u{Gw7!F50&>A=j0~FC}`Jo}b=X@Rg^t zhvDd@cHy~BX>-{cT=?~xfwyBQ?JSYobgD_}nUW>8d9XQA&^Wut^#y1dz_r}AlJaK1 zb><`5)fdFM%2~F=$YMBFN+Z#X-JvDfkFS^$-gcM+p<0KvL5m?Ta-flTF-#|kb|oXP z`wVA}WtGERfK4g2s%Kxu>=nvzr28yjS)kVppef$mo8h(god9pV4MNn9xLJ&>4=P%^ zlE#uku9GR9XJ%E%>A&i`bjp+18%8>lw zk%z$>zN{U_s=!z<=*AsV3#M!i!eCnVmm^I54|1@D<1;@>_gIA5h*e8GI0f|~Q>vO~ z{VPj_b)HkmjD86w13a0_rX1QzXS3v02bBO`#!j$7RsfF`W++ielV+)T(5gS#QbrskGDQGyBlqancOHyH@8sC0= z_KI0(2g^wCF!F)+KKm{mPM%nYj;e)B27Vix)*Kt{veD#+zd!MS8;B0RV@v{}Rl_&E z2~71E2j>O2MenecxMyJZMz8xIy4w7&fya;^O>3-Q=NxN+$7G}^wk1|*xGMU^v@LSz zc+q?aH%m_o-8}XS(ZE{$GM;*e-|_HTdby-sc(A1V>+NNfxz|j-ybc{*0!9OV2+kmU zSsLcpdT`{#f|o5j%8d=U|L-DN4X%9_12W%XfGshyeWM#A^#d~G!$T~bi~I%vyZX&l zLKE8zc5g4N?YfK6@e4r1Aws;$_a~39YQfL&fWh;v10?qH%w@hVG7s)yHCzb(KKY*e za(7lPGl;_Y#h$D>!GS=(b=SHU^ZMuSh8E?cU088+1@a`=n6PCp0-nj4oHJ0wDGZV~ zfG`L|yVijFXN%e&@TUokz!HDuG_2619?w?uG zBDtURgpOn8M%Mrr`G2-xGpee?j$fS3eeH~khvO-!1y@Aod}{y#kHk+%+2Vk=+!4fI zddQwve{34igAL8REAN2^*SobP`aBPj#qtfSq*3kg!5%5vSZ=uuNy>2kMLY{@ZyYnM zJ&?!Ez0~ffp)C`31^z!WlG~S;=_rj2!RNEjsWh5C+pp~e^q%w0f|il|mk%D*#mA`< zbcdVrp?8#@7(~dXx#3Vy1CY=2v}r`rRI2qzPyu{xMO|u^UAoBX8jNaLSbn8b9*G|j z5qgJD+x7&yV#tHGU862(Ge!myGX!Ivk*U5CRNy$^Sl_#W! zi{3zgnCf)%mCN!dae@Im*m<~XzS`kpidCSg;K@Uc$q4L$BoI!KO4IRQy5t!|2~2@b z!z-iQBg!qlVjfXB1`_Vrbe^fSqjJ5>reLZiTU=kSm$HXTvk98d;LIwB;6Lp&1!-PB z*L0KgS~oGr1Xwen`W4)1&09I>$u0zQaYJCJ&1~T$oT={+Cd9V3QZ^e$yjF3k-Axw|DFRgJ5`G0vPkXgjk)B_L_qpbB(hA3~~iklW~3t`ghm^&UuQ z0^_S=N|Es^CV2|x%(%YUz&XXY#`2!V+wVJN!6FudA<=55apgeag5R`RAr7$&PJ6)6 zeES}&Rbz}0fID5_&z$CoMD7!k%*H=~tuy}4)pX4>XAPwK!;sL(z2KNp{(v7mweJOD&4|aWWxo-c4_^-YQPr3|k zD$qfm=g)-Fto$uvL*P{evZZPQgzJ8p!lctaV6vRPwH$O74~s*8UDokx;wSGZ zo^@69PwKm6*#nOLH>pxX4htAAxd6-n`CBT&$IElBcmR7ANBEJ4@PRRnvKR!Hp7R%r zb`%d8G$TYL8qZ`{sqJr(QeP1R>5K2Y^)F^3J~KUa3To@X?%xgXtp4@+W$Bl2BbMs~ zt{zJ~U@Cdj>2jFJlF6<^UqyA)P!o-93fyUPqQ9q}ggbI8FL>i=F`DrG_1J+jedU0y zAJz-EAEEqVIq9GP9gG1l?)CcpMNz_B0L&v`*2IQ!fA&#s#moQ6cJQ1zyOk^DL-q8Y z@05Nt80$94^Shu?m0gwMmYQmZC9_CoB&Zz*s5s85;86JKt4k+RILLOg%7y;{VWZpB z-`)7yI%Dx$AdI2aBvpH%U0a#>_Hc>0O`hD35M^cvldkLFf$nYJDuI2w;^=f-fFKee zuxYPSzVN8W^oFL?ej7TYLz=2HNQoKvYn%6IpJ|+ZXFBf&KUcAfE#2JEsN0u1)Q#K> zd{r}7jEwQ1{1jkSrxDU#B2>v8b-e)ndd7*)K#n$Cw|QaFz}M(Di{J z)1O8Li81S+wtQ4Mot0dGeDW8NFVtm>YD{Ya0-FJz00dELZia`cJHZzo^IJFWx4ruP zANkY$=JGmPrt~cLTbs+$iv-zK%73meWulp|}tkm0Z&>7(M*Y6=`i%QNV^{k^a9>gBH0?r%3BaUxm( zpD?~=S1k$M@>vji2a&^9Yr{|yt#eDh+5*nLZ~-Q!XdjnEl;f!*UD2ef(!jb}9cc=7 zuiI0`7I}-o^xl^n(&z7^*E!bv6mWf&wR_bSg?tR@x#}r4}#Tjwu zafYjxmiT4`(vTh`_6INOa+e2e4SH=Mc9*tZlA|;KYtU}Ue#zQ&{F6ZZS+ns#pn^1o zENT&RARLPw2QDF|%{h`6MwNBAEl0xZau8yYI=?+t^ArQhg40GK7ivf~Px*Wb?GF|oaF-C7YIUii_Z{6>&AG|DV z0B`Al%TgRvlh0#gOgP&CmOBknFn$+J?p}{;Qtb3qI(~m0lG=D6>F@at=D}80}Q38>^!#k zPWDOb%sF?ifb-4P*Q}ZJyW81=Y>-xn5A2S0oO`8LwA-yY=i8X%w=%_ft+FXB0~sSY zz4G#AUtPX(vhAbPNaW8cfZFaKR^RMeU!UlvZN6lwfGrMS&+s&8XFjgEnS>{dM#E}% ztMU)``sZfLK~VGLeRmjW=dGBa7xw+}ku{53;!dt2u~Z%n4UO{$D5CNgeIpTPOCB*a zc)YMSo$VqAISD{xOnSyFi--Op~YvTYq>NmX0Q!}U{& zx>SVtL`p!k7&+8_sw%I3QZI3;Q}NY=+Z$s|CLtB}s`u2VNuuJ;Dk(Nw$#l}umyH}# zs@WIS{~7KyaBkh!FR7)YLU*cz_d(*D&cNOI@p>m9;G-v?J;LK9E6nS9yz;&n6K@8AD5WST3ttFN1W zg;oy;2I+A>zd1W|UGH28SK5!VAu5Ek4PmzaEJ6>2DU4E!0ONPrw@$Cu=SCb4*Dn82 z=wmsq4O3dDc0}%1nl;~8o@C{_t5Acwj-6O+5LgO;Fz*NzJ<+Py`7*ifC_cVc^N6R+yp2}3Q{S|p5@hl;pN*~Ddfx7AZBb~%vSIw3wktZC{n)J$@Pk+<<%%G+hP z^%Isz7s=7lL|DfYphvs6A|tnA0q?zD;QEv(|6QHabBbsl)#EMcxnsK-+#LpudFu!$ ze!a-zYZ(zStyss!$JLLY+cI~+Ro8%vLRg3RT|ygBV7Hu$78;I&%z6JCL4~&-#y$Va zbn6fLbU6H|kq_ws;fWat9ofyzqCjuE^4MTp{eV3|Qr>UyCx|{v*vs?FAx>`7!jb@8 zL7wN!%5*gIj@>A%86Y>H`^4zuXJD5EBYodpjyS9Z`IWd-UW!9^F~ga_NCSmLZ9KqoDPgL#A)hbbj-5m#av zppdh6g6r)1pfd*+s^CroFZdu4cA0i}KG`kO~V2QaV>;#6iu&C2y}QnUt-`k zZ6OS6Mxuj;(P9PQCFYJn`h0(`vw+DqyJMo8i5DH&$O`WguXDzy$$i28^DSXw;`jUi z)arMagFdT}#qUwEvxGK}cZ54$qdy+HH@sWCK0apOHa>PUc=~yfntr2C zaC@|(B;my_fWeTihP>IV-nO-4BW&&DCXz-TARL%QE=7U@{sP_4moGc-LRf-p;r-x0 zzF3TFW`EJ1C~3$-D^g@)a}BPiB9#}MPxdZBrDZ(=7~UIukBMXF(gmx|ZEXd-vo`L} ze4AZu|WS zNq7)ZJ_&gLW{iO7{OcuVQw7zGAM;8s3@3b33_X1mAqAZTR-LZXrtz$KKuFnXbQV2= zORBm7fL?rWodP;;I zJuJSN@ys>6J05uO%ho#3BZ;e4iqThKofd2Y&2*&7Sb~Fb!nDe^HDvsjCnTOS{T5CV zRZmkb#AokPpDz3{`6xb7nnWv7{j1&lZwqPY`{;-(OAS^Vjv z+qZ=}V#cnggZ4o7m-BF;z{z<;Ew2?48mh4VBR(sJ2nH5KqagA~y zdJNuK&pN_AsGOMP8V+SCOPm=zKujo?UUT}dnOh+(uJSD&5=kzLuMf_3s(+%%Ahn-+ zAyh>Sx(D$dA4!&m8&oKw-ZwI3d(DW3q_hG$QVG%h_?fhrWH}a!YoUsVrwzRVFCh_^ zIOF=iyrNlrjTe$97dF)&#%fDT?f0<2MI=wmMSM@uX5>7M%=mA$sxKGG_+l`|%uB*1 zLQqBwUtn}pI;pqtzC+1Rc$mEWr?BRZz`^frAE)9T5o#ZKRomWJ>Ry^j$db^jA=dF> ztR;{}NFc02MGos|ih!L8A?AI2isb_q5fIzly3$>GuBkpSz0?Y74VZIXx^13aR8O7<*pps`N za<|@upvCT+R%9S}ukibzs8$zRV8C937d+z)edujEzEpR61M)AO1x9@vJXF<@V8Kdd z$6OpDOyuU^u*V`;_^dw9yT*Xdj?#BrVWi58Uq^5LzSE~m!M_`SmLoR>*$C9PXa?Pp zH1gvWAhXX{gw>!(bL*KkB(gEyja3Jd#7y&|y<4n2U<(SkB7HiC(wAB<2u{Z8IOt8A z_94Zf)0W&k3I(pzbSFJ5+~~(+>E^nTP}*Y7JcnDNBi9b5&%Rr{RV4w#it+5=@rYp= znco@08-p}D3D!>Q6HRiwLn&s#NZ_x27SCJ2qub^`0v2n(%25L`+tYM{g=Q5*GpHpX z9-dcebWYJIVP*rTX}cKHDIG1mQ>CcUX!7@#oBsIn804&d=)OMyPaoj_JC$VQaZ}Lq zlSXp(2}b%qs7TiOc6Oh@Ze~Y)Ydb4rT_*>98%Gmc2Wut^M_U`~XgP$wZ)kxJJmMp( zUs^6`#f(;EEqka)-Ovkf&!em{A%1sVydl-oR3c+M@ddG`*XuyR3d@F}`&YMp1D&Nk^z5oMyc4J${! zn@ryO{3?I`AN;WWI0tc@&-2InT%`X)b~63{=x^4aFeiOeV_ipMhd<_q#?1d~{r_dP z{SPMG^@NN(Bh#<{=D{s|LgM}pAe^@;6t&4GtBm`zV}SDiph*3%egEH%Wo+YMZfIt0 zY~%F*L&X&;%h_i97tD;o9gJf8KB6IU)uK?#_CEWbvv=Qf$M}BC zWR3iodEZqvtLk}X0ioTUZ`g>-(o7G;nS)od6dhzQ2l zH5dM*lh_oS==r>))J}N1DPpAHoZ#!RDhxcJg6T`J30$Dqd7CY_1*(ZaNPn2)p0%DA zKnc}DudXqFg95v{2dIrq}YA+8kCT7I+ey<1l=AXoc)cPj$|R+}e4qJNtXM2j7%urOqGcIs%v zdVVf*FFToP)f^)+(|kivG18j>gVbzZ+y#LNx4u25J3WC%W>dD8YlQdM_Hm4Dg_!;! z8emR2d(NiuVSS^kJtKZQPS9$)>wQl#=++C}^^qa+jb(+JG)XCLB|@Edx)oOffKK~l z#6?PX#|rmoqo2ouN9j#k$ z;!%oIH+dZV>}Vu<{dkM|!sl5FC!!@IZ7EiMDM=^)Xbuf z#S5n%)A%P7#O$%(`u3B*PEt5Xs@A!qZ?*v~aJM|uHz(NX{lF8Jb1&-z_-k_3k3BdIDW3VD&T%z3 zi0&96hlbrwdh5_|9GKsb;5N*U1v*lwhcVYab~YA4>K#zivSpVmhag$PGQB`un29Zp zj}}EOk|6yq$60bkcjOE;f66X(d|c)JvvpZFjgQ&sL&Z0iwAn$ilC84-wUT^Ny=WmR zlgis^}(=m&dgi!76<5 zv`CC`_gk>W6RL_j;_??hPS-vU`5?sHQ#<+v#HrG#3ZRL(&9hjTktkJB4$sGXO_q;F z^zH`cir`M|{bDKCS!T>=@UR=$1eO|tUWgX?F6n%2FVrdNOeCgwte7$dSD~Hpp+N-+ z_AYEuUGUuV<$O!3WkGy`Wy&1V`={5_g1dLr06L3M;9>x-KSwkhBs^&${%u6Fjh(T{ z?>$3*PRL12DA-HLjE#@cjEqlFs{&obt|ay7l*;tjsN7e2Nm?2iTDtHl@QSf<+VAvK z3ii@JzRTT@%E1JP2TV*W2%z|3D`sjc#;2v|Mp_Cnd_m1e`nxwn0uH4~{CQ|*3G6vq z0URFA`TN2A^Pw#54D}pM99;ewrSa!u0=pC@DNg*+aGe0H(WBArCb4yX7d$O%NM zZRQ=z+k?pt?$Qt*6eOTW$Oo2KuoEnfjoZ42yS5Skt6XD9g{2=q8yW@pDcsB#(H)R1 zR;XYUZkvFL-Tt{A9~`QRF8h-O$~c7TW|-R=Fv1^_gxAe1P@T(`3usq^^k{C%eX+@4 z*Al{2@g#9g0z3u4`W+!tRWcgylcX>snOlE{a}??lX5$;iN^byi?38U9XrgYrIm1-F z#*ux0GGrk~bPIy_cd#`#lAZ8jo8%JSsdl29drLwG<@`NU4h4luw4UHQULYrE3>_$6`B@QcaX3sXp1rNCSIqPA22xHq0dr-h2k zV9pWSV;thSJ|^B{h$!~!E9dKijV{X_`v&ID$!My^LB&^ds`NSAJ~b0+lyU;aV4oR) zagLSN!Q`SJ34$qgSIM?G%u|tz$cZ*coTY=C>ibB09Sh?|5T9S<+fN`o^+j6kORKfB zmgbu18uyNkS(ELg+a1A&6buhfV^gVfZ2q{(IL#6 zmp{B2v(Fg#o|gys_4ICDbv5!Ojq}rOi=b82d}eA-B#OJI<7h=!I12f>Iju)YR|~LX zG(d{FqFDcnzB&nWQ-RW&wratRiXi6`c}lq@0Vu93=&n07y0>$xcSt$|H~n=Tif!Ad zT~Amol;(FhbTfjwj{cwQpQ8m*@w6!8NA%yYxo-MwW6C!FDHK`k?l>jj3l9Qg072l( z|H}y4+tWIm*u-l?|HBgg<16@IaKPTfMqvzSe2_YxsnH+wxiL{ZZ)xH4J}N>M^rN(rU%PQfI8BZ!}xWb1*K zzNHutB>7V-4SYkx<{~82?y}6O_RoEM2cCU9*cYN%T|L zJZ1$Ar96}1M3!`Rss&qY(GhV^8tfK}Adn`2+@qHVk_fR@@V8vFG!j&Io3zgU1{%vq z12(U2aj$#|aQ6>3e8w;l8#mr>9L*gT%K9hD`U_=|QJVX!r?QL|^l)P6G})*QM)}YA z9gA4h1!b&EQKm~yd3$|5SrB{owglPSmDSPljJVguUF14Sy*{Xds}`>biaaatSZ&|& zo_{+;ez-}S@<2H$0oR|02-MjkWjzT56WGfMVIwL`Y1KkmPLQDcK;NN|<9bIgJwk%6^%MREa*Y`os!5*IkBsv~p1DGc_ z##BJ7iYj=RVt@yw$+hyuLXotL#CGQ0N9?r4q-6z;qQ;qeBjNO zGcnLLKjhzhLGM-D(09y>nlzfZI4jf;|HC?zlRAoSrd8NR#SzewU^utBK4;XqjZh@N zcK`ZR^NiN+)!z2Jw-L$?`Ii&_(!Ey@&9;loR?wUYy2u_S2=5@xIV zqhqu0b-m4Dz{z#DA2D)o&3wTsmX`QGxedDOu`}n&efa$DU*oo}w%7}i(UKatk1zqs zmIOvu1reM&cNWr;B|mxZPS<}FJ!XCP(*4P-LE6#EpGCfeo8YXsd$jJuQ0a&0y5)SD z_|-;6fEa9m)DQ5yAxl)E>0RvS)jw%fsxABvhuoCo6oZx?e`ns-l{1~%fKWeNUc zdW4_9nq9RZ5bkPxL2M`S>20(Wr}gpjCux4n^|1oH}36!PDlm=iuY7#WlGL z-*NWe+%flmLC2GYWlfLT@(04pfi77}5qq)HP} zm~I1^0p^N!N;xvPXo-{y4ib+MZLv~_4iagz3E}adKu4U1Y2BOwO$V9+`*`X{4Ctrg zGQ$1tk&&~z^EVbpSDVa5H~|{r>U4TpP?!9qIw(g7!ent3^wQ}IMv>%RLxpMkk@LW9bJWuOk0lR9;_*Dm5=iz-va8i&J*CJS;E%pPe z*<=_YLx^M|w-9L9alNCMHi~}ypCkyiuq_(>c@@NTVnp%M>vn4%X(bA-zJssjI^cZf zY{*lCJ(g+P)O3rtu<{vGp*!P?H?nx&AU*CpACYG{43q(^i<`}F$PA=7i|Tm#C?($)Jtd58>CvK za(tKW=Dcn}7QqyfB*Y+?rc;TL7bnma~=_{x?MnZk6ded6NU{ zwh|O_jiPh~-V%{wYK+4kf5Y}bKD3KA8}yQ);uQ9Pw{h?Hmr#uyMv(?EqgrjD0bX?u z!%D0&I(?qJpcG{`L%+t^MorJl%bWXeY3@%LftUC6|8%opJ;v1kGoOI_kJv)%KOu{g zk-3Sni?xZdo`KOHX#2OI<-ekh-(#2eOxq!Apc|J3qJ`fl6#TFK|MhSF0QJFfQ?fw} zNJCdXUqi-(Kz%+SBc$_UMTL7Z7lUvSTD9n`39_%8x3BTj2(he!8JqgP9-c|Zlh8;Y z3vm@ZG%i4-ma?v$gtYVH-9y^MZXO?kY>L&kcDCu|0=O&CFeW0u*?V=C8rf|14eQ2| z6MQkO0T(v~aggskAnMSMVe~q3iW@?YmOkx*?4O>ey~Z5x<8#AN3_=3Ib8)d1Ey_ed z#8;e2Oc>78eM+v-{no8QH&`Dp@3tPPEltjgecQL~JtJ4Hi8ylibQuQ@qZcKb*h9O> zp;>!532-WXbTW87)*x}$sz5LN8229Ta(7Md^np~HPKdty*}(owXpwqOFXf&p1q7Gi#(X`4@R7Tf%V_%EzF{MaSMVl2G2GzY2Ik9&@i1(1McO?B5X1JLw3 zxueOAp?1n`FjUoQU1_oieI`AxYT1hGOgnTg78;(WFq?|GDauS@ycUZOVg-vo_hJ93 z6aR^aQyj=aaD@M+5yn>kTM7P;VEBBgVkQwN`7c1pGyE2{Isy0jYhEev;~#!Hz85&J z6d0iT!t`--Kqwx~M}S<_qaU;7Qlej3Gce*=$&<(way$2L`ov$fo~$bQ#6qRLJl>?t zvUKu#8is64s2x{^_F6<#F^QQgAmO9+>FDtH^t-BISfrDMb8()5^SlCZ!-5k?IL+aL zpiwzYKzF3J{G(lHU8Y$^DJ;^_5-t~O#fnX@+koTy(2P67_8c3+pLNsvp7w?`l4){0 zr(XFX!-T4QQN-mG#Nue)r9$!LFwVIOV=b~&M7P!Ce9N($9PJ=C`m+5-#2r?Q!wVL7 zBVnngc5c)YKyKvum(u~rnl7=p)qK&tus^nj_ggAGS%t^bZn?d&D+WaKY<>Vt<(A`* zkeg)_SxcwLG19V)uT6J-R&1{NWf~enpt^9(*9!*5WY{0NeL*&o9S0%0LqcmAnC`#=eP+q*C?xiTF>wIFLV2wFiddcEE6p z^1n#*U!wF;RPKqM-uRb}gOr;{yfF3({qrDwH-(;Ce4u z15}V)e2|nWt-PrS=xw1+auge3qHupJiX|%pO_%mQkq2URoqsv9WNSILM&bm?zD5w- zFrI;&ryyZ(;bXXe2n(ppm&a|Mc@9KWy!ZA!-7g9eivXCafWy2=*0v}G!2-xv9r6^< zB-n*xg+r-%%Sm{}{&GGloU6egyix10==DcwUPWpAK&QY-Yq3SfmO3<-gaXvXi7VL> zauj#&tf!^%ZrLk6%7e{pOCu5qQ8PEpIkaVANCMSkPgwbdDzY1x+DYkLlaMMKkS4-C zK)+d4EWrKssMaav90xS;#zX>J4yO?zbJHXHJQud1UN{#3(OWNk<;nwQrg#YXm^!0! zhBHMHo4(?@CjQ#!Wto*D7N9$nYtxj^xQM7{QTQO7ntO>WdW*L5BCO%7fn6X2f+16C zD>k1dVpb@c?qi7&6$oDZftPRVY9SlP$DSW|1|WDPYAw{Lzv(kn6X3MRe6OVF!WlU< z>?(G=&F(99U~R`4jHSj|H>3|xkAekYQM?YosB7oQl{5e{6QV8L>WR&%58K#w;1lk8CC^_>5U*s6;KdY=%O1w4ZS^|7Z*XJJ{xvENO-!?oJzg9W%XOXZmaYR2?izZ?l??*W*wF+S;atRuFP2IfTj@we zy+|KVSLjj4qLw;16iA&?c{96^L9hM54>Tx2K?&i=q;WuF-fC!YkMV*$BblCXD3H?^ z3^pLh>0e75k<{p<=p((G}e&78KG46ll3 zU>G$Ssj@8wO7RgkW#YxdfSIrEG7B-G?-vUCm^&8#V8wzY6r;nz>q!UTl=y+?DdO$7 zE4Vab8eQP{+z)X7gX{k04x9~~e)k735;T)^|Bb}Rb$P=o0?K0$=nnXPGai5LV`yMx zWoltFsHf zdtbgJcg(iRFB5df(+=yV3;D#e%E{UB$z&hV?@HItk?e%M#UFVjFHExaTvaiV%gb2d z^-p#Hx@)CK4e*k1=MSjAN>w#hV;}g$szv8_^r`ck%nkf~wKv(WZ^NS~n4 zj0pEZ@`(7fpuI&QhF}LfQMDxmi2DiLCLaj_M6r}EOj&6*C5JG9@HQz*4&U%=T5QiG zmJWtlDvVcB@Ujxk92-0tQ|>h-T*lE)6j&jscYoc@_d@Wrqj^8aL4dsVHL$W=OODT? zNN*&?tx>4=H1Ux$MEWVefiCu5J{V!SfGA%fmP33+)YSFCCLjV9+2QN;s7D*+Q`*K` z(4;ISAS9LOkHsi`%Srz1KB*rWjmBh@87OHkG}qLUXKiRV^wtZ1j?jI1*)^3Gxd;8+ zy9@Yi(E!P$%Qh3Z#~G<=NnL3@Hi|uu5@AqS|Jy`~|3ndg(2nB&2_pWK_P@OU|93F4 z#BOe?47>zZgG<<^!TH;9{`+P|cDAl2e`-vY>c3{%T_ zf>5nmF64I}^(-)XnGe`*e;eMTl5d@r$5jEBF#tzRPMezdYZAJ&pw`UIW`p7*{FNin zDYlyv%{FHwzz>5kn1HS;2*59VhhLJSG-JkewS}m#9zJ5r>Yv;O)O2@hRjGCu48`&l zoCIXssC@*aa*AC;J0HPV3YEq7DW+qapJv_~q-Uxllw>NmXRaR%tk?ww{iG+&TUxYw z0UHFj8o(gz4dojHjig`bvE=HIX)rXkx1h>Mok zYzC=c?mUI@H{aiUBb>ll?z4#$bL6F|WXyrf5?yr0*eYZF`2i(W?Ss`|DIali@Yj{EXWn%CU3<4B?ySezJOR z*3K1}G9R{@0KAsPU4tB9oa+yl1&NCf1F2n@=Euej7^u}a1EX;EaYrFHdexB5qh!n( zPL7T3_h6hsW5KMuR?jgjLo_p=cI=m^1*MjD4w4ec4G{5Zz5KB*RAMJ{j|LihL}+tA zCsad)sUvs}cu8ZDFoi&l!D%L99D3a;FU*30{B4Kt0yipRxL_b5qaqPzDPxWY_ z)pxIy1e^&sf5p_Jo?%b6&?u6p6ob}rbj=BV_Wm|m4zlF;)o~%O$SF|pq1ud|(aSY$ z+^%$8zVj0_BMlu2`|PY$1IPYV%0c=8osQikM(K10*2;^wi^EqvJ^r~NNOx`7jGKekqDeI^^qr80R=!*S!ib!xv`FD>pU3PeS<~;Q% z!6?_0Cxv1}+_1$jKWu#Cu4Z2~Dq#@#@z|hu#Y)Rxb}ryOL#d6^Fs+d_(u}*LUjB%Q z(LSOAy&=>GA|U^$P$pDg1`LYpAbz`D`MYH@wsW(!wlgsP1L-J_S#B^OZEV#bUG}kY z9FlEd;iebMQ@{tYOp8#6CJ%qJG#;5D{zkOFMLp8|V%}DUA(}p%#$1pVo_~y90qIPN z-B0*6f1^4nU$|=L3v4fHz7Bn%=*I*w78QCC0HR_yoGR}kr0Chm;i|3};4{g7mbu?S zx=^)Eq@@31;+7ikMc&W0kr{#$&cAeVe9iOH$ zq1TmL$r4^u6kLD&a%3QIe?xa6-I?*5wQ}A1;=}ws6saB0K)uW_f#$=UVdyzV)#z7E zBEZbR0Fwy(BFDp^>kxdSUBb`y$!kvHRnb87hkOHEZydwi@+d3C>L z)b4O(%OFl|x2mCzaaN{azd73#N@_p#;|2i4>q>47jd^0o(Sw4YkdliN;AVk3cGwMK zLn8L{0UuNbsiN2|%f>0JL?Zmi)+W?=z0EMO|0zkVo6XmygxxtnS8dVt!U96#i4APj zf6A*P0nIfEP1&|38K3DI;it99CAnF3QBS(01&s@qmxQ{HF6R4OXjrvbuE81GNK1BZ zSYc;zKuVsaCFh!N|2qCT)Ui}ACsLLj!}?>-r!WT!6|rwKxuXp}_iEu%4;h0em%jl( z?k~C1LqIQVgOHF{^*65l@2dVkq>dv{*Q?YN?Dp8;fKU)=Hk8E5&>GPT`f$y&GXonDLR)hJ9|v#cnU3tuj(^{)`RC$9C{D zuG|&r`E|{Efgeq*EMxlZtCN^-`Vjfw`%U@CN#f<}^W` zZS?0cAldS~^-;aNO*Bfq+r(sb=#bZQ=pfF@zbJydbK#UpU4bo8k5;z}!L>Gr<56<` zlXj)%L>)i%xhRDd;7<7)C^ZeArrtm!4}s={yv^JRW6iLuyx|czs&k+A7KTJmmbb^@ zLqLXJhu=4tYJJu!d@wZa(Ovq6v$e?|Luhy?f>Q5ET&)SS1qJXx2jwBZtntvHU9T|!sIoI4e8asH|l*6%W z72T2F`D3JDIuz;kMIAeRr~Y*LlcQC3R)KK`YE`G)q?D=8>ih*BBx*BMPC{5A&kNGa zNN2`RU^$=IV%Au0`)i%#)weOx0l95Rdt-Xg>^KiLGC(k!E%v~D#|K7mA~5qN=(=ea zu%sU|mVR2}fowP&lu`)>!ZNL`s`HJ5jD5;=AWLkXP>&(JU0ye~WBAl2{qsYJZD6ua z5pj$QD#&ky^0+K^u+#e=IbjdOFWpJ|Y-W-|G~IsYj-N|@DldSx2VI0liA z|0%$H1P99%M9o30W9X}-XBPvMXUbG>8)q$~8WxtgA#qoY^YnR2r2GY6??#WA+f%TC zCK@0qU@7nu{8zk>5A`@GmiE)+R5*G(zHU32CKmP1{%BELkNE1h@3G6%apdPSFef5T zLk71#^K4p$fyPsE;F$uAK}y>ZYh1Oz+_s7})AzojX4SIut^)gah^+5on;5^nqB%so z*X+Z>_8osTVtxM27*@iOtciUDdj4pjMP&JJi1sgx<6{4p>vvJ=js9~mEE=&Vp|}QW z6FZ6Ogl&bTR#Oz)8nfegY%9o`72;`g#p}Z=1hRMwZY=A~rqRpcr{N#)#J+wuEmq)z z4!9!u3!_Nq?@+wi08P?@)l&3RSx3e4I)+9Lz#e3!q)nozaCZUzP*QKD3K|<0^my2c zkwu*Ncb~H0A*Nf=D9EMF`;90o-@DH6Xfv}cTht24b)3d#V{X29K+w>zyx!LOJ&zV% z1ITGKaU-M+zjt_k82k9De@E-I_zl$h-ug1;pbosaKRiP3(Zz$Ed<2B>j~#T^-tN0Y z03Gb5hhZ@5akhr%ai5_QJ(v|V1Oazr;^7B7fpW3r{-?J&%R4rC`mQrdLAP^{qH!I{ z99>iEH)Llhud#Ke{k{YxeOXd|!53m%AXKw>p66h-dvD+_kY#L3PNW>~$8PsJ*d7eYx{T1JCXVHAC!>sVPRFo1 z41?%|mX(W0O-sdvOdhd>D1XrhR;aRzbC5?ZNa1VR29fRDvtye2bX-juICz*%^HcfW zT0kZysR}8&63y3_Am_xo>6TjupF`RFux)x7-tX(4JYaeHKiT#_0!Xpna59hx0j5#? zf8*o|+mP-Mpg!g&Y|-HU4wL`d>pw8LN=?dc9XLAns)q3X^T;#|2n=d42)K>YX*Je-RGn2KPl72@QXG((!BwfW z%q#AU=Uw%>*XCum8IB>I!t=d+ksw7wHPaAQ!$b%+c!FKfhr=LWy}>W##sR{SV|Mv3?lHT1l;48FE z92E=q1KxBv!>F(G?Q>km+C_P^g^Uo}Y|`bqtJt;R{DnW*mhaLulH|ATkXdEnrF{~C zLMKBi_fZL-@evu>$}2n!`8Oi)k2ZpPb`+Bu!`&c1_=4aZrs*b#EAV5}O$Yn7SC!eA z3z&lgx*?}S5(hK^QB$I!*Y@67Mv8$@J0H;!ms-ByBpEr}2j;P=wN$G+euhL**xi1^ z4Wc$l8KuBJ3%YAR-Y?tEkF!YVA%;>?<%8rjNZL_MJdWz&KvW%`E%?dQ-A$rq{v17dG|-Gc~Jt{Q9oI^1y}V}|l`VXu1!x6y{o#NB#em(PW3<_7i1f}Xow z!l=D{QuKjeGXmjqU7%;2$}EY5@rT`(-WDFt8UuAm)TE+^q2l-j&2kL?zH4yFv z4Ov8|FAk3fy|t;{&%x4~fH9M|Y!^;mzzRfU(l6iAdZ{JG#6dY`db%jj_^W=}sGs z+TEKoX%H=+QhvDb z%xu1X!ld{Gy1>{l>dZ~4dJiMAU%(|P(p=Re| zi&e^c9vU*_juC7f)NIpTOj&x>A2933=>M|UNa62jcjVg^)gi<7`ZCX@QLX;|TVf9b z6|ubr(7n!p|2JU$mo57Tu$Hn~XF%$DR)f;B4m66#$IyOiYea&w$PHBtQpLV1rdpUaJ!pwBn+i;(1~Y8P!Cd>hKP|`b0f<#K2>BW+CaE_;C3~+s zo((KM?BO^i%R>lxzz?6VZ;U-_rKt@Dr@AmAm}FjDzW`WuF*$sQmw}U8mND=zKlC>= zH`trH@vezPk`)9ivphz@4VuQ}9}g;;^JTGED4_w8pNOMb%2sC51lhHuVDl^De#&}< zVzIZ+FJMQFx@xf+^)a3llC)|az=KDG2x7S$!bwYV{hBLCA)n9B={BWs#gy(z=A~uj z{E%E2+U&)=#)Wk}m z(zN-N;4Jh4OXfxoKTJkSJ}=^-27kzMyJfp0?mHflLToG@LZg*;PK`Lzr4oo;X zWowX2;BafTd!(G)F(_|!N52I(h{@fpv^fNTx8{USZQaWdWcW&q+q$pNYv?S=OS2+6 zP@ls_T#Lk*@9HDE-H`l(6$nDJ0ZLcYM=AKFXzG}0`Y^_eB7y?lEHwOo0m6T&=0AY2stgbaBXt7#=u1IGE7*&?6nzzz3|-e5+dvsl zGILARhByh8!k;sQ?>?c4;&IJQl;{&gwcpboLYQOvHENcw(ssNYna>gWfZX&#`N@L+ z{J7sINW$Ievd!3TfNc$%frIe*JGN%O=$UEkI>mye-6YnQuuQwI%XG#ObYcbmXrR#W zEEEO&+=Fr;qtK>8%lU?#!;$IwV}G^S<)X!{F*p92URO8v1ae`F6a#{_jrI-(~xt2vF* zWCrf5DueOpPxbQ)lsRGgJ%x4Fs1ZrQSigf(>L>@^rn6Ky`Ms7EH|K{DSu%7T1f zy`e46y9J1r+0H7H(Ae#hN<}KTqWIqJ0y?v?cNEGoUffKnwNE9&cb-`zj6bFD5+t0R z!2G-{t$5KZ>c-euv@_GAA5`S^iB)1f8a|Xh88#z_+WzcWM;rN?zBKFVps_^41A;!m z=h6(G$GOG`FLC6)zGsDbcDErNo5_Tp8XLzkf(VeJAH=n)BXnOwF-!~PqT4FY-C+SB zoJ3U?S(RusNYv9_SpDKE$R*4865wDx!;b(GjO*Jysqh7Z1bJ>*HU=W=R%msb(iTBp zjU3*=DU+4?xb9ns*_^AtL!c`rXmye7)~5_8KJ&nvl&BH{;WX1aQ2{Nh`cQmyDrJ!a zo(ez?m3APiKVBPpsmG;&TGcd}99YalK_I#Vv-oD5a_9Z}%)ay6paeeC1U}(#jNx65 zC7{?P*XhnM+^SbbcUh9VR4>NNl78PkGP@YP{`J5(M3x+D$-6J?ljy>iMx#(J5UzN1 z^Ua9KEV3KsP}lD#+i^un+_mH|ay^h|5_O{Na1c_Db1`K}#_CTWqFMyF`IjyEH-wA=M(jXC0(5dn zl=3UEHEkDpkYIG_GA)D6-)&=CkMq=#5?Pv8z86X=hHu!sd6|jxQfTNZhkhY5;-qrawkAPYE~tv4mc_=OLni>2f5P(r8z z-9m4&IJ&+wrLjEHXF=abbkSHX zNedJGSsxEWW0UIrl6~{}?yQAf>OM3Mfvp6_x(5^Yu43|X=L*}TKuH^6SXr6L#pCH# zC*8vl8yqmDOlZlL;WNla0C;LOSvIqqb&jtrn}j*dbb(Becrqfd&UKXP+)iJ2j~ z4WVs=`ix6EV-&A;9faB`sj859NL+URX%Z`K0by|jEL%5Vj!Hup$X}xzr^72_AL*)qBh5}Mo$o*J z0n!qvQxx_(UqnILFuxAQs_RAa&U9JJ_rJ>)W9LXq6vm8Tq^4Lv-_-n-?SWO=s=% z61E(*$N@^Qd40rK4|leBI=TN_@CVX?X%ckaqq~Qw(KSkwa2!tJoi*u-NZ;k?( zreWeb`Mq7kjUcr;)6=uXblPPAxJ-X8GbAsc<=NTvf|y$5(zTB#+sY@+rZPZp@h7Yh ziG5kiCRy83+QB7q-X|<*ZLc0vidv}euRrCHD;SuZHoAA=T5!H1ti6L*W*X zZM@0D)2AsF-w_XQY#>A)EMqhve^p4%Vx*2pURX~Uc%Rj1%{pYPi4YVjGH?*GUFi)e zjJVCTZ?ZwGaMtiu@Nv#nHpd6(VDK!l>SEuJMWn!A?n*7p7O)i@xu4rfQQ#ljVDgpW z7eo9iqsG#T4%OG;8$6Kt zyi1D3%wTd?%VSQqciY5V9$@Q}Y6M<1tSY2oPqNYKva6OZhuNK|J>LV6!qI|^lC4v0 z=UN2Iz|jcah4)_RsU~13#o1{=^pTscxYr6?3EW8Pn=wg`j{Wg@+kW7Q*t%ms>1trB zTO&KJtC+uFx45$R3OibZ*ro36ej@l)Q^FW>OuM3w%;E0!w@e{h0`-z6;9VXwFf)Y) z_&ecN3+?Z;i3zZ@+`!r7uaa^(*ggih!K-J~JTvx1XqZiB@~i%c?-b1SLhm!GfzGfjsV34z8Y1tJkl{x8)6r`vM}YQQVuV>8gA^<8`Nu(b z6$29;WX4Q$a3z#hYiKN-Ro9)!eEIi{2Q>5>3*#M z-!%)Eh4W`sEwD->p>_)$pu`&k%)i`tr4AdLMsG!_wrcLr6E2|>ToOo5{Ze%Y@)g(v zR8P@Wk$_917@uIvE7#S&m9RB4wQrdNWgq35R9QdLq(1U`-|KlCh4QYtn_jm9l>L_>~4{l&fem zZiJZkbC%aQz8q2B)h~*ep2Sc#WV-uujc~0{T~_mIJPnF{c*lg@&(;$PIs^w3Uq;MZ zcxo1+kW_rY$_78Z11wC{LA)LZe4XVd?{pl8PamO}Kp zbkBGGRBHDV7XegjAaoL8(~xplwkc*-chr{qtGTeNay!i#2(g)sfE8uYXSTQ~68l1_ zqNiZaFd=?MYmh8rH8bMZalYAHt76qjCMyM!uPzrK!*}KrB*TL8m7rZ zap^eQxZL&mggt84oz?5?@3dXIq1#zB<3XuNC8_kt@_kgn7ROSEG0C3vMaOuQ&k}z? z09V;J?ec3%qiYY4?_@M>p}Bz{t|UxhkJ$zgli9)kEQiTN$xqRVrd0T$GeKFjD>3{j zUO$ni=~{*V&DFXo$OQUx#;y=+1%yAVN_y`o@~_~^%BXMPwH7onS^O-lwJy)kp1d-g z?5nfMU%C49!3;j?GVy3(Ei?#KL|LIgnzyM_JzQ zbW z&d3UQXA+;m0N44fr|*Xp&uf3&G?#TEEyrupA?nAj-_ZO$#MdWh+;$b8D*?KBaQu3L(gX|nKK_$u(f&&aU-EHqC;`3B}Xs2yUFI}6Ff6#G{8pd;6O>2 z3NHY#wFXm2aFc)%@tikChZ!X zR^Fj4d;(Iky3JF7dpxnUiDJs)qHA@yv0U7U{WXrAIm`nj&6u%yRJ| zz4N^;i;D{4Hv@=l&Jx|nCf~lxj3hI?BD!nsr}YIAC&yEmzH_1>cG6Yk$5Ao7+#nEM zf>^s#CHQc9BfP@@)0l+|YVH54;P#KSK&jsi*gv$%+1;61LbhsJhK6FA7HG>x#-)ya zuU5InqAB$Tx<(tQ-#N!WwbL2+Qx8~Js0Y+cgFi~8qf~UP_Bc>|9%_(I0`bhXO&?4s zP>YRqn$MYn$dpVm(esvLb~!E%jfN11e)4j0Jt!5-9OuoA;~e&S zG`CKrnro3d?G(+Nn6rHSywW)2dEXDf>U?IieKNz$(0 z!}@WoDZg+__37Fxk=#5-JsD)g- z>UM%V<3!g_5jMT+^P4@MF5{f25C_zIo!Z%D+%UZ~Z3@%IRR({^#cjfAF0$!{Yl1r7 zN;h8n+|t({LKrj%si_j3?AD!y!vR;7rm$1JkvJ`Hg4Ua9LT#=6@%eK=w?H=-A zgXN8kd#Zcdh-fY#BZv7KPR1Jnxd}|gsERMk#_>2hqn1LXeYy~Dzzom|!7`j#&dZV6 zrdYn-X>bh?>DhpGJ7i8=fPcV?fpwPPkuez2XzFxP0>0B(DqD7VQta3Gu84~to_S_^ zZtTJ%q&|i%A~TJ>IMK46G!*60DZSEik-gP~U8eLkDOd1{Em>~H9uNG0`6HYv_ZQD4pZQIGjwr$&)*!Ii4zk09UyYG)a)pe>)ov!ce>fUSZz1M0z zh25c!CLg#lr{?{Fbeie;D2C|s77&CmYU*Pq$b%kg$7z4q8Z$oHMgIq42AWyN?;;t1 zAyrnmjSuqSD$+46JYYtcciI2%UvoXslfPa2+sXTU9-p*?ai@+;#)&7(ulKy z(ZOmnb}Yf^&gwOm;aOm{`Oa%hNvjtYy{N|5Se0O$40I9~?00TENy#~3geHx;-;l(k zZ7Sa|=RUUEmfoP=rWr~!t@&}L=<<-eP%tK9gyXRaS2ITwmvWfrH|qEQe7^<`57+lh z#jQX_Gk2Mz}g)van&k=xYSrzcPwyhfs!Ai1*hq28eBQlEU7I|w{u!q zO3lNBr0R8<<4h*>wk3BVsv6=#TH;?uR3Mkb$mGCm%Cl+Qq`E~hNypJ#@3n+=(_c~& z?Xyhw-@ayP8skPwo9Sve$(#wq_e*g2l*~Tf8+nL`}ifu7H>5Im46Ibnp6m;_8Pk=3+$ zBt-BYrkW|o$#NUup~1{{b6AB)unlyU6(_D1!L(|Vv66ORER@IKAqSzuOrI#wsVK*N z7JvT6I4I`^RUo7?aBaZ`6hq(RVt?$CuQ~dW#1iV{&NLokyu>?Ez!u|vd#std>{2j` z?`!)V0u9hQ6{$@4Dj!QAOD}l{aCB`G4&b-^_R0M*nBsSMXx0dO24jv1Mcv z7QsY;Ed)?UXe$sAkleFXCqBm!LVWZ>s=o!ZWYk+X34rvOksl$N_P|)8Z+{>0KN&Wl zEW!~R?-v^cL{(BJCwoY%a!h#c)BTw;j|p=B-y_~*q|x9%VkqX{gZh7}EdKLGZS1Ta z3@JugV11e40X>`D3-q%7JE{hFmb1kq5)i=ZoxjZ<@B{8DV}n{bhjv&QAPzTh%`8o@ z)DV|2;QL`|@M^U1SqQGMLK;}hvH_wDdzc~;p^7`nU19>CKL4}31lxD;DImUie-Y&W zjdj15qHkmBY^Cq;->vmmb@M-c=fG)Q6liZ)QW%18{cA~hQ%ptD|L90?LA z&I$8FG2X80j`uoHYOn5EV-m+ayTb}l?Y{JuR+sJptbS1p&@Lt zgv1h}jGm?6SO{8#>AhDwVXD*s4XWTPJ4mWRmonw|KE`a`fVc(=7Qz}SZ?ADNYrb?0 zf^C6eR$N@drEdZ9v6y1=7rW#re>SfyUJh8M7HCAwhr!=C*SJcC%h&!qCmh2V#&#Mi zL)6caOb45Dng&3%kmWpqfq2Fyu}1l$ys>fR1kdr`Da+tJ#s984vw*A8bvo)^P((*Q z=n-Mq!a9IX$8TVd z9@+0i)sUzh?e=b?oWPeSOb}XQMkM;>DAc?Bh%~S%LSq}o-T>ZeDmI#12&7IAmOIEv zTlZF7Sq@*m2Ax*AaJtw+YqtcIe5Q``CI|>LXtw=53EcDsQnQg&GHiluJ)Iw{^lU3l zuiQvNQwXdy=hj*aDnKg6O;NvQUBQ@Q*vAGk?3W9K~* zv6J;D95O~9k5uE?j3aTyt25=|gNu@vJwgY?y8q4Rv3 zt6={1AkIUZlmBgHR-I36`AEUoHJ+e+L&m|DGpP*q+r#4gpZhN_Sv8*}UcEOw(OwPX z&-AiSWT>$-*p86-0^`&%9&yK5&5nLHM~2~VDRq*6TvN0AgIrGImaNdXu{<4(g8F4L zkjyoZF5M;iFCArj_m2WVN*oB)lcsP=@+RVc_uJK=%EfkyW-=5|&1-sfii;{(J9b0jmqX z4j>oC?}-4&yY4Nlr%66EBtoReVWsrcW#w{t5~egG8! zFq8-O{}Vp@K4pv@9BdsN8NZ|1i!7;_{T7$urx)1zS!^&RcIK+b3N4rI@}YaBY3bK| z-Q)5!`p912WHsZ>WC~r0D)H&gRg*YTj#cmx(uxB(qd33|+fzbdqE&>)A34eVaq%AM z_V-w7g`;5sBqXHnbcEsY?vG&sAdnqTHf2n1Wq`xM`A#KmeW^3R8se`?a8>*hy<%Fm z%w$irLx%=2RQDhJ^D5P8Sy|bx+v!_dp{d*MqA6D0vtY5ViOgf5NY?S!_vjYQIu`gI z-I5$QtbIo>RKJd1uznrUya|d=D?Y|WXP0aU69~w)n*Q|3c8GC$=LH^SA3wW~@y*=a z_1wWv|KOc=5SR`)-2VK~{kZd*{-||mZv}T3Z%n2GYkxkt>Pmk|IY`<1Dn0-P^u~Kv zH{XZ5-)__{9vx)fcPA7T{YCKF`Q9O>xdjtopsu7^~$2{xVK)3lZ0 z^L?dlwB2cC;r;HPx?6rm#9M-0pVof3EcP@cHm$95n*WgLW_QN=64No*f6>_9+LU*o zxTXzuB|AvCiT}&2v6&nu^JKKk7$u_}=Yjj=zx**<6+hz6!Uw$ir**LTBp~C_*$Y<< z`mvUJR$SbY>|nH|6aPkvv2$D;jE`)43QRD~ zK&4&Z&&(7|vu1ob6FqW0Gz)X8uh1x@lzBQ1+FP@%d~dvqcNH3Jhx($#czhnlAO7b) zq3q#7V)J80+!d&K6F@Q#jkZN$hGPEmVe3iZ!oos&u$N=KALQT^KQkx3U7ST*8gAc0 z`@9&Z^7BewiC$TI9kCJ8)z4M5Fshp_{nnzfZ6oQ&*ZiLxoclCPY?HKK+nfPp>l*DV zm+@a{@hESuybe}Go>n^)t#c8D22XN{oCDZfh+&`5gv9ekZ+m`Aqckr5pBl&L!wM=qKBA_78`M^?ldz<>?sCv)zHIRVb?AVV} zE<($AaDR%{n5$|_Guq~r`?l8+R_6_3tU0gva!uIfZ-(@<86qv*2QsF+xe-yv68bQg z?!OsAy+Mz@g5h&W;4BQ>Ul9_ZDYhT?SU`|1rBm_ti76=GKg)aIo5z)>CKdL*AhnC!i<%!$Cl*bx2^&`1B^psR-n6L z!ME@@l?v>p*54fdh7hW;#v)`bji*9Z!T`y~vAfTHTLG`v`(4G>c=S^WP^w<~1>|lW zC!5$^J$^dJGFe|-!N1T8Vo7<$JSoZgxk&}n2phNyq(y*IcGyOFK;s`_zyiR6LGYr1 zb)mEsIA@DxV<~7T31GHFHz@&`0J=mG1B?JHzG^6;QFOwQHGPZ9MjfItOKp7dq7fim zmguH_{XUpBFnw&e?oey+HA}=x#T2G;VR5d`3RoEb=>4}jnQeQuq)BOSth{lv`8Jbv zNtmjfmwT|XmPgbl7>q0ljDwX_y9)w%Zg21u`;o>52ZQD;ZKlw3!DhZokFw;eGgEJK zUW7=;#03#ak`RkyQ`ayH5K$IzX5WPY#?gZP3AhKG@!_|Yf)Nq+E5n|LW;cWr4o zDcKWCr3708SdYz_sLi!Pc%08Xs1?|O6F?eS`A2%s=(C>I_)uC@#oeV%r~mpP1n&mc zK{J`Li=DWlGb>3*u|!Sy3m5@{ho^728kC(oXt8JzAidH-nPjaNdKFB>K|bvJQ!op5 z0Y~;%+yzGcfxMB7Fsc&{avhBjrg|t$TP2G&$Ark#(}*(GC)pSgTy*-pi6Bv~9L8%T zE1H}?e?qwS?QuPa9?VaK->c*_5tR&3Sxc$1c*glhNoSZYX{^&Nl-=#S=$CQ`~n-kf>)z??g_ckyiJqqKP>lEq-6jVh?( zxi$u`;I$;p0|!+3n3!Nac2~jtP48=9vTx!>AoP*P24MzUqEz~g^8B(C2shz;cx+m9 z9nrS)-U@3#W@6!(9Pra^?v1`9|@PqbV(9KWLhrx58{>3 z==IyX(K8J?YBq*0^E6*hACUbb9;jGETRPq?$sIgJcahfr|4$;6)36{Ik|Vzxc! znhtM&Hc2;JunVhYa?v~TA~hxeA|M6M?%blTF0j2oQ|9-tW}u9Ouhm5r^?KgLie;=R zxK*}?G)ffrD5Mrx)!{zwyiJhU9&RmBlwE}TZ}oK3=e$Jz;Ac zZNW`s-U0Ox&1&#|z_Xp#zm}Ln_2jnI&SiF2`#KL|C4zeoQmDPM2aZtqUi|@tohq=6 z1T(zkRJ*xLWZ-Q68ivaWg+QN?A&NXcT5l{qRHBS;E8BZM2Z^HG5fcD$g2CbpmfzV!@Xc=0M>#W#F~tC2x(5$L68S` zjb|D_lht0XyR$Z^z4Ib+QdA&5R&)Y4(q-N`fY*D4D-?w0=d+56*s?mR8wW;m0yjQU zv|&(jzrC=kK*B5+PLd^LSKo*V)|Th+bRg2u{23dgp6=`54b&k-I$9~B64S2-`38iD zvqikfjm9WoZ&QJy&p(VRye*TkrAR_za94`DAh zlo_Psln9>!Sj}SW7NO|8$r!xf%nhCKA~kz!Z`55_zJ89DtC{hZ=*hKsFlXi3Xxne9 zPUu%TRlC;CTcVcB<@({p`89k9?NFhmAHPr6W0uMPeCXlERCm0Si2Osnn~^!tc!XgG zq5~Nvc^;{92H>_>n=3LQ=S?7ra{%{%SFci|d>7z^&y=+nJFFf=>FH&`-+J%})sG5^5f#0^Nz#`oZBs@vIK!Nu}?I?gjlz8 zpfGIsy7shZc6=KJ5!8x(z)|!R8CRy5_#H0ynPV}*)>P>)1ucV3nac%sem~ zf@*H)uHoI1V@yRKId)9rS~X69#bp`IwhV>498~HFuwY)6skZ&l+XmJ`Tu+UvVN|#d zaZft62-qim{ALi$>vCcyq3 z8tObGA&J&oh6QU|b2s`rlqE(~dS9pe4b5G!5k%_w85mO+lbEtJ$Nv51aGu}8J0=or zdpf+2%i%!5ODwBW9IafH9l}^gp%kW+Oey^v#w7!3yI!DugU7>Jf%F*GxUfXi&Od3GNegk z5doMh38Wp_cJ`C|(vTHEseBFi5?$Nz4yeScu0K(@ru}s^m7=ktDy7!AK{f>8+M23awu#-; zBkpXAb6VpJp8!Xwm{u8%1N5mVuc^-F#H6miv8YDM->u7=K?eCi3Krxmbr}GAIu)lT)9cIz2tsxzaQt>8xjWzjt% zymfV#b9)e6KI~3=OtOux{%V>Dqf4Jy;Cv_OLV!EG3$d6uwv_Ia-gehSsiQ~=GsbR3 z5sJnu*MjnStc+hw9`0 zK`6zDm}e z`BN90grEhJ5$c|OW(h~4JH_0^n2T+d3aH~j#sJ3jW2x`;gTcGFwKyKHmz%lgT)ZCP z-$#4xnM5G@C$crJ@PPY)Njc)+500oS3RcOX+Gk*Ffa0(>xlH52fo$F1$(XI=b;!2Q8`6`}bJ3{xe`m1JDa zB$*kye{Tx1OXZm8@srC02tlhB1LIvl=0fM>d1t~2>#Y-zWpK4msWp;8mjIT$l~Eq* zY8Covwu&6R+&$zy~q0DEysTH0fU{EQP;J`baszuIE_eESx{6x~y)upCwMG-ZJb&!yvP?U^9n4qSyC% zT{OLtjd}`yH%q}KohNM&wzRv8^~*{!#PH z%FgS2?=)I|<60e*j5(s{0|q!~p|=Gmp5_U7`fK;6niu{m9+D)SRTJg-8@10(r)}DG zNp>*OV6ob&@|i7)>-w)HM^@9aIqI)&LdIW90sTU6<|uKNPka<&NJ>Huv|;Omr^G&w zTYNoUJzn{J93?DtJQ~Kh+?6C~a8W7${ODaxb{LQiuuF*Vo?v=NNFb5s+0GvBhx+Xr zvq!^`A&-uCS+(z%(`-#3`=9*i7>HYoW` zu=*a{cdNIp_TOX?({og&J=2iV8%UH*d!`Bh_abJUX85k_{c@3W0Bym&niLQq3t*=# z3x=et9J6WhKnOj#J{B#j4QkeV&H9RYibL(B4ZJ3dzn+FWA&&*W6|nXCC2XXt)3R7H*DFP6=55E^9MDcFpf|6o}&bin`M-VokvH?cdeeUod@4;SU&p1 zj!4&<)ZOZQ2x*>=Wni&GhTjTBB1ZLz1D&vYj!%GZki^>%wU(-7JH_Y*r8elNHkeb{ z`X%54gajXA)=ORbWM<&~Z3t=BK)hhz=nPL&|Nlg6p6$pX+s7ga(Oq*50ve9C)WSoI zUPN-4R5%}&MFp(E@!9s60tG_PI5#&5dg6PtWRN_CSy|qKQ#A)Or0<2zKYKzkA%bxb zkbV@Bn-sr-l7phAi5({;>enoLf@K5me3+tBLZBtn!Sn`k4>-0|ixDAHT1)BHfvnXT!BESri3ah(K7_ z!^t+rJ4f6L*c9WCmHXTL%g7gQU!B+v*AMDE*@}XK^cyJ1hkWA%?ahuPLSiq&?xSY9 z`9#d{&0RPfLlGjM?f{`Fdfdf_de1nWl$*(84aUGSErJwbd^)rXp0hj7hB3aufmF>c zXgC4>pd`}(6qfD}#x4+3Q6F_q-MPE$(>h#Arh2v+d^{|ITPHeIMu+7-2QF2ISA>L= zYL30<@v2cbmvdC9Cy==i$P*GCY(^Y^1X)3xGN!0&d`jCnHDEIt^I(+}G3N7=$O~ur zlz<&1=vh-`1X3N}lCmQ-Wh!;->CSIwjVz@dba0&@cMK$>8#fGl_}7B#+p1n(4fs$O zGZ8~Pa8|^T9c28V<+LOj*p%_|lf%7TlG)z^#>P%r6ePxq`c7#!J@Oj}*ivw=L2-W> zn<|1Jl6l3mD1ls{AA3qt@KA32T)c3pLBZ$nfBSWC|8h&F`c6sq|4}ki(WBBkH#ay> zG>@I&%N2sbFoSKHYejbvsN#zfvZDwq?PuSZ-w6}g#t>+3o1pQR?eB9Sqt$M--1Gbj zKfpW|&<5kvuxyT4vU$M29&}3(wutVh(+{0^{{{s)L_iX!!nd=HnV1$xBl5Fn4sB2&4!mqsjddnv7}M`Sm+;R5 zb9kR4{SV+8Y!V6hGdI_JB;CF#1_58gNPb`)AQcx<(T&pZKcIl7R5A(?L_-e!8##2? z3s5JY;?-+Uv&H>{zrdKNl_?gQ?*2q0rTt7LQnLL@^}D?{eMUs*cb zVEsoF4s68&Tl0gfsn0qdx^~jE4>B0Q1;7W{$YtHZ|6X=~^UG4t5GJVa67x)-d_z4j zmmv@V0Yn-Ks#xEdnW6HnBFQGb&HZgj2-n8Zg1L+tP#*k?J<^wUi4t5**E7j2p9IVO z3S+$yG7%FH1=esnds=l!x{s#9plRudjKc^iSg9W>O1iYV6pDcVA4C8GdHs{iO*zJ# zsZA=^%wn;LlX!;~Lp83{r5eXfdI>0N+`EHpr{or4OsUh5Gf3uA+f-oLQn7lxg$GR1 zs3YJL-Gpm4Tuy-g!8^an7TfKV(F)R4_M<6#yXz##KbF=bDL!p;sw}g0#D@(#5$J5qSKh%U_iKMfQ(E)_PwZC}}2_w0-k`EzSq~XPrfr_ZTWR zmF#&#otKR0XbfQ_W(SuE_5(ogIk|mxfjfMl{Bh$*{q^N0kLa6<`@4#I$G227S0c4; zH2UqR+*MVK!@5{4SmYsb)L|ry46gCdtold@?wDeY8I7qu_mGpDO@y-Tea&@^YBTS- zgPY)>1|nxLo^52!8^#By!N#~(^_Sog#P@6Imrl%m-{=EoYeNMy9$erONI5B325ueQ zc^dZjvqqd*c_2rv)pC)JcXi{A+4w_4d!QQMKs>6+ITOI^P>8?0Bx-779@Cc@m$Xqj z&)?iKoP1J3u*$6r)REWD|1i^(`bscCQ!ie6V{5&0J1J2$CGi+Ot?Kv?%u57G0_y2kR?PR+y~kL z&`J2ga^2QfZj2cQtjyAFM+NCy`-Qz-#%{PK&N~P&f`X|l!;ba7x&XW5qphJWa!F({ z22E`Qt2h5S;liKlC#V$^8poz>t@!#(iNK#Ul3&$r7P9Ht~6;X&V!oMlIOvKj!WUi@=(DrCw zZX9@uFAx`pTo)jz9_8Fyu{18oz%R``V&lhI%QUWY+&C~mQvjqU56ayIw(uHAlU;5T zbJ^CYa3#6Z14*0Avr~YYgl`I$8w@OqznX-g8n`~rt|&4c3^5v1-LGmUNTcsLvs9|s zziNy?H3@(QgCL#~6z+f|=oG4ba)-f6KhF1jVxT_~n8wVANmqtKmgg=u=+L?%=7DEhz6gCDl;Qs*9BG*7&m%~@@mO^)|q$Rdu75s5Q4SI3j zyrcR?KmG{Ik0D+mU+#7Sq?P=O>H-H;X}KX5#KWz}^cRk!br`8M-Iq%vHHkMskT2PO(3b`q7qX^`FM$l%=MJ%yZGCL(9aLGr!!;3X z($=UtAdnJkpvQtnNr8OkE5tbOVv+&};JW4Op#gn-41KF^#;Z22MVn5YA2799*O)0j z(j9gt9xzLzhkH=XXL&`H1#}LL1Fv?jen0oBBY4!60j~o zsC+Gj_R2^A{HzZQ!=_cIqC0_*?oCk*o&`m}<$Z|-YZ#yrdvRudjyxBPUYn4}aD$<$ ztV$lihrVeVP5}-S`ke*zO|4rR+rV!Z%+;s@3;`aXqaObD4XS|Y9-T+Z+mSuDwFuHG zkk~;$|B{}Y3PJD=9soMq`Uh(eG4?a05UMgbEJ!F|;HNK_US?6yJ2vpP74@x&PyYlP`LV$)m)eTI66 z8xq*3WUL4w(P?^^62VpG4ZZT$CJ|}M{Rt@zN_p0M5Gy~LRskziZ|+1QeG=BN8Dp6v z3|B>tve=@B4T<{Vr3!rCIOQq{#S0F)5kqgcSRQ!##296m0k%PUG%VR*{nf5WmME1} za7TYsL{58;TWHQ1kDGZ4Fz4<14%v>+aak}nzs-$v{uJC$ZKhBC&6uSv(DSM3nZUh= zo*{$2LD7pxellqCq|FdZ{UvEAnLMI zFd>NCL#S5`c^*Y;IQ<_I7d#v2E~wpK3MZcZ9}+hhe+S{365F;U>82?>x=O=HTSy(? zg%H5RtH>U~E>Y%IhPnj*2b?-N&>*e@REzpGy2wSpg#k>kMjtxb5c&R_!{x$?g&IMN z?D%0n@>bIa{8!QHtV(Xw!14Ia;zD!4_x?ZxceN(ffCa?{iOlciDuWj@XnMs850@HO zW10lz*?=B4=%5A+2IpbxTyjS=h3b)mB7=nbe;WrPikx*Re`^~-U9-b?nS*;3V)#Uf z?P#6o_Ao&oC!l*5kV=I-AHRiK8cc$tZr|4=W~JvXBY=+h+n20Io*BQQT7o8hPKFJ`QtAMbOL|b>NI85N zo{iGJ1XceUz6C9s2a7w0^J}_31&=(_jBgjb-d)ohhKf|P@xrn%?6<*$;bIN`dDKAd zN2GCu0H`WsVkGkQ?NeM_YiE`c>gWYe}FK_s{Ev1ACWbRD1La7HA9Jw5Tzt$@pym=&t z7i6WeS_zab0yQgv|v_bNMjP zxaT%l*i@NZNOT&^VY zrbe*nqi`-UjbMUc)?$m_X%V8<@&6{SjVz7(G!?&z>q^TtV+vB_)61k!!mSr|{PM)*RvB zWm>Y1B1juaDu!96#Qy1B)&lgMga{(|{Jp_1t&qV2Rpz`kdpTyts95^p3wQ{anx9H) zFlhj7Jew)^>0tLpIy~YE8KXf{QS%C93x-xLP^$maAIkZ%-4MPPU(=7+oClv6_00H}%6h3Z0_}e7 zNwKFb5tTBC$yvpP&Pr|)C{JCkqawgw~KnkUT)(3vYEiol}tf zX~bYsf2v6%&>4;`YJ=(Shk%}-Sr}s|5&~3sdSHiY>$NR`1EtO~0%ehmzSx412?3j0OGR^hKFw`#7rY-8wrmF6bfBH zD8X%`bONbM>g7=4Di}{@F{U;Ll9f?{%_HCjR+?$*9wu~|YUbAnnNG+%4XKB(+kbzI z5{NWHsUz+Xh!p7%g!KE!kKhoVAonaHd9q#a$0;ddyr0QgzOx)5VS9`Ai z=YNM!Sd58vK%6doqs=jqs~tjBKkr==_8uYy{$8XN74omtJ53Ti>n)q~Bd*-9{d+

#CX-P#ATd&!Qd1YUT!xQQGyf6koAoc!_oEU|Z2qSDwg61Y(V`_GQK;dAR@nu+5cC@{<{*;Qs2Q^ zpYdDG%jCP{*js5`_FMhNds=lu)1^p8YppybSMqP{F~ToVqSrqp*OUnZm4@wI^uz<* z%irQo4Mn^ZgwvTnQhrc^cPa4wDJ?QnKVoZ?hBd<(#=sWYk+#ZB!i6kDNxkkCz8{75 z+tM(m{f@*z@@HhB7mSCku9heg4syN3#I$aqyij(=w-9;9Ay>J8J(?7a?AM4IRHv!^ z29fH0-jFn%bGh1qWIgG)?_$=vzp|_Iix!=F@X9!g{c~Q}*}>3KtBHYfqDNedq5Necjzi$-&0E{DLyx~=j!(>Mw+~DS^dc6n z@S>dCvO`3=OUx|4I)=nyWWs0)3zxEnV{o5}T@JFMm?xMo(M6WiA4QM`^Ut|prCq~( z>aNJz9l9a+h6|KLw&1_qm!PGH4y>4Xxh^GHA_55r%8`2WB;4tD(H?2`;65Ei0k8^L zbJoDrJij00xn*VtJKi-eEO&TvAgTqe{>csjs*WN^#}JU{k$vfnm56vVg*Dfjz2jKZ zNX4lk=9KK><=%Cew$^hqsBTP1>FPZ(DNvQlBv`!T`XDfd9KODc+ zV6AQGxOeTVPTekzmxdldv|y6mUqT^fy6e;t4_`F_*$C)V)UkI@HSe-LIH$a?W46IA z?Ybiz-8Ik|#Q&+P;gUz9zstFd;<{#arPRC5S*&nXagUVEq zh)E!aFehqxLH#9Qtu1G`&`)i0Hm zMOzx{$LG`6^F{MEfo>D~ygZf#c3Rid+t>TwPol-E?Bnvolx;&G=d<^dF!5MZX@&gD z{ug6+N_U43tpJ@xu1EOk=s%PJn}eME5~^ry|9sOn11jus z5aO=0%dJPU(%ekVXBMqeAMk>gytmzq6f`AaMJIlLIDD2W8?^)>fhc~TUdQM3_!Mx~ z!o_khk2!Ub>-V9`-+s zcg#=ut#@!87s=A}{PNz*)nlY%C@)sRR~>7*pT!EKR!`E;P#&)?76++^jz-(_(w&%r z7Wf%&W$(X|e)uu=K+5K4Y|+rG^ex5c6R@vMqw*2a;7BF{Khn!nYrDI#U;PwHjDnU47~42g$^b{Uy(1GsR0cIaDw?RkRl2*l2t2BkHca_wc}+qnyxp;L_}C~2Ul2nHF&6y-60g&!Z~^|P#3nPl zF1ACLgKvWSqObKs3dJiq8N0Q( zq-*R10-KvH;F-4dlXwc}sZZIq#S_Vg9Q*(&l8b;B*r9WwELw1svTkr3-cHi7Kyno# zIf?mNrQtX*Kxf{4wEN1vh?M)Of-tUZ0~Mg zrmoBcp}T+v^*S!Ebk`qXlvIcGC1~UKmZ!tm7Nmi;&&5{cKog4C_IOUQb?ls~Tokcg z7Uh@#)O*^0T(o&6SlfIA;%ncxAJLtDIe1ghG%`NR%_8{EG%ImK_A5XJ^xWU`L_yQB zVQ9-J4}&-_R&;7>l`X%fUE_lc=TTQ&^~8-!*5b$Xi+F#OY28TDW_L=m7oCcIJs`bX zcMWsWx~ne=|JVxCHVVa#TsVzwjXo*U0ra~Ab+45j+flqwD=d)>*uiCcNqEo(b0~bG4VL$fBpWtiQ)ViXmP-!sa!^AF<_7U zAy*?(XOj#|{Iv^Fh4DeXy32~tv$lS;W+VE7ayBT6d$pXQ$;PkEH}HpJqQz_ND01*y zs7|-=vy;PH9Y2idaAvveMf@RfuIl;X>pf?i_6;idDd+2qXoM$@p|-UhygD~`6JrrJ z>S}k?@mdSMldu0>Wvju5X`BtgEvpWa7qZzQ;iT1!2Y>8!ZS<@*fmXO{(K^&=n`O;8 zZ+=tHEK91nCG_D?1=e~I)5e9T?q{wxkOtlt^v6cN93=J!^0*d>`6x$qjwQv|?^4#i zD*Gw89 zWh_H?(;MY+YKJWK7)Vk9n5S3s7}CA))<>)(;}CNZP4jvYlPnT-#-Ks2U5(6fAe$E| zxT&Fwzm403HoV^7f;@FYVh_3@&J%f|=16$Jhtn*Qfn2Jg5>Q5i_aY2{bvoREUuxmD zS)7Eh&8QvqQKw#oN#VTJ`Sd~{WYI-<>>-0O!HMpdRch(x2sf*EWUNQd7Hvx zCcCnXn_Co(s?g2e)>)7pktPM2OnR{R&+?=I7;X|3Gxqh~m7ZoNSAG}Hn{o{&u7SN% zLl8ttGPSS`k<~O@kh3enwKoJ^p#~wMm(Y*)TM|-1ez;Vwb?`Q*HVO0sH)cl`+Zk95 zdLpmT!BG1uD>)sh2aekbJnGe>ZsYXq=Ow1(P8z7N5kO~a&AL1WP^oFOXs#7*E*5u{ zlYg3xGku#!!))~`4qq~ONepWdUr=bxJ9&5207Atk3a_TmjWa;@Wri)Rkl}ht(6oHR z{W=(eorcX^UlTlRp{=ukp{&Wm-}0es3@ov9!GYakSjfK~dMBL{Pmp%n(mjlglD;h3 z*>`B`VkXks?Yu9uwv7$7Ir@P>EU6abiO#=AoQx~o+U1hU*VkJSRqI~Wg!h+Ss(R$| z3R%k;iBpswxSrgAL>-Zn69P~^)jaq`7)hYyJ+DBO5WqkQE*=SZ>D59Bi(l^qtRH)! zE1^ue>WuK&>UgBcM+w(yh)p|P4n=&70#nMI6(e5E@5Hf5(@fju}_#F;JjGP(d%ZXK=J5_Yrs|t z0&9>7C9{4d<{auJVgg@$aErmOsGl%pNa=phlZ;BS+5p0qNhos|cHx*f)8OFJ*6x$A zJxSZu>tt|;ueEE=rBNZu$bFI>V^Chvo(b#Wj>5YH3n>?9Fbcf+^v<0xNqR=+qp>gV zahSZ9gh+3JQhWE>BhuJ^qn^mH*N%wKjqm4Mo%R=D0@-!-E3(*u4!Ul4i!~f8$11}L zeV77U*)C2VC`QxvGFDxnTk>jj%O!eHP|xO_V&`+j0;M%3ML*of#=qUCm%RwS{57(b z_U_ZljK2bWk_NonVTt#+(LJogh)l=HID;XdKO=*^7Lx+=L<8J9F~7#$ZMSPebU+;! z{|Iz2dIj%b#Zt%7@~`T@4cH7^a&UebAiC9`$Q^|)N$jdr*vQjz{&mHn$;GzLm^4ZE zU55jTC>c|)!2iNYxdnx|0EaGsI4!g`gQu-4)Y&}PKNtTJxg zz~;gGKvCkBP47K=qQbG(3*^_mUI=LX6aEHNr>(&)w_^Np-)YfHpJg!US`FwfmXvhi zseh0P>YBdR&5&;Oy;33gqKI(J7Dkq#V&^-|?Q|u4%g)(W2QN7Z{(2Q}-Fbh^p_mpx zO>7l8e#`&yWZ%30oSaNmW*O1e79atpb{Qv{( z2ELvunm@Ph_9dhO)ObEV?*V%!^Ph+dUqu&z-Cz$;{Lgm~r&qD{%PlKR(V$y6{%@ka zjSdu%gXe$5993}bcQxkMjmc>%=I1A7HZ7ABN@dS%vT@AX(8+(2ja{rvdn&#u+GoWS zut!(?u6}(wc=g=F<}^U-<_AT2jZOf9L$zh9pK|7dEoXh1wk~}Ik4G8X8nt3MMMdV6 z{+YdkU^|z=U{AYETh{<>j=|5xo%fE~vG?Ty;o=*9NH9!#iOW#W^aS4SzG)?xaaJ`w> zjvg-iXjP=C*sz@Q!sh2~IO2=R+X-FgGW|jNQ$i{bAfgOVjzxpFOH)*^NhQcXU^ZPX ziY~x@Ho`FhnT9t+tK$>+eM^BCW%gvkpJJ6vcPkM4mxL}q+s=LY>K2Ae#IETkq`XIo zijq{<+~syyQ#tm1OL4?A~RUj8+RZ_GR!P-|@Cq+2OE1!4dy~-@yELq!1?veyv29$j`Ic(#F zxwVV^c$njHugI<17QHk~_3rJ|ba;t7d4+Py9_8xkOt&7fA%b!SAx9X@RdGk_?hcIF ze#1GmV1zu4ShO=SN&Y8$B9*3WcQeSEyCqy$?65NCpWV)Sl;Ey9ql~j>TQz0#&Q@b^ zdf2Ai=3wIW0+U2%&n5wUe}^pLTCvT431bzrD!?c)w#Sw{F|!FyA1PE^U4wTZrCulm zlT^eX^>*WhTLoFF--tuQwOj@XNtTk)V4d)YXR-T7d&F){cS#LvScH^xlFj@ig>F!i zVw31XBBxpP6HRuU^e*D@bU8Q)Rc0`%J z^$jA~)MDCEj%($kM?kS6spgiu6zXlWH9vJil);R;FximpGa&hCu|W{AOM0G;hi}V? z=`}~O8=`H5br=yQdh(!D_d(nJ?U8JqKi@-9HvZ-9Kh3tR@XE8os$WxL{(ZID{!4Y(-|7 z&NCMZkTN2Ewp_^6eBc(S|Gxl0K)=82MKl3%9zgrBHO3s7CE#^?%YPae61666R`JG$ z(c>|K@6T5SW;J2(VtV=`^6n@6Eb1Pk?~S;}zy5mKW*<;C`vP4(WH^fWf)KofX!)~{ zo7oMGG{>Pe>E_d6sBn7ew`^^EHLrK#Qc&@M)F%5qc3Zm1k==1AZk!eNQ0Q_SYNiB3 zAz;T3iS4)0HM>C7?0=Wi)G@sz+bs;bmsD$mp?%xR+XM@V71&kBT5bz!jT$1G$#ccC zr!m}!n|1*ZY~0|IvC9;rBB-x3WrUEUK$bl`h!GOxmwa=+5&;PsWw2e>@$qEz5OH_2 zeim&npzw{z7Y6?n)OM4|`DSuEb(d1pscy}En)x=+?-LGJgvJk8>OE)bqZtEiLYkVFZ$ts< zCR|N=S;uT)f~&O6dLUZ(b_yX(1{;=w42J@XsN$GtMeWGYAqt?9Vre2vm>dmSP8X81 z!JkEapP})b;Ag|Xoa&(&$=f?r_yO)=nzNJUh%f;c9&1V%x!aU0{4Ah)o!Di%z4j)- z#^aW`UVqNw)sQARtk-DTi!DQE1Cj@t3pwtk3#2Py3tnUhp0*34WjXqvgyu&`Jg4{3 z&@ZQTRHvZqLrU+U9;P$I=Q-QUTVoWN4S}(=QMapppZ6FVC1?P}mbn;BfE-)%JdwMLsmVtZGJmuFEOdTF;5n6F2Yxw?vpZz@D{gd_ z=njj7jYW^f_^3}BBrmPd#Q-IsY20mU*dmBhg9wns7B`Jk?{>R_Lq%i?)FBR z)bBz~HzNdU(^?lHj*@>CV=TKY!`YuiezSYbu&)qz$cUTSkn$q|G($vJli^~^vJsHP zOMiZBI)&1vpm~t{D@Yx0rp3}?T^RL8?KU6`w<>CbV|vq6rgm3oiLDOCr9jru{w%_q zT`|G3UvGe=rdLN^Y{6(N-z4zY>}+Cy&+YZB)AgxMgXuwmjmYf<#~K4^h>v1znZ*Vn zlxEmE!icE}pcyVB3K!G-R-DY$%rby})|g5*-=F|)wH^gqKmxbkyd8IPZMoJ5xS2QI7>Sh( znsl(G(ecxv>ltKw+#LrR@9=9|>4PBJZ@Wf1HP!J$a{LWMe#PH8jb8_TIf=7-pJi&<7|IP%Xz;OvSFXTL&%_r(oW??UygO5-j&zw#e%L*^cftCX+fHFsopnu1Zv*FOlkhDJ)5)Nw#F-t8)P;l0?)c3bJu~{e$ znBL&S{Gqa!?)j_Q?L~^kbi;`=3;LJ zN!@;8%^Ndiw4eNN48mxhLwpo^+SdYwoN*+9-D30-2 z)leOMt?g>#Zgj?c)s$Z)>RE01@6xWx#}N%GfZbN{as=Fexgf1qH`!GUW<)QKjbml- ze!op^rzel5_`Z7Yyb<+1I^*+vc!^`Cb*bJPv>Gv2L@VBbOcxmv`hR?a@sduu?J?dZ zH~yR>2y>OKR%maVpfd|Yq3OD{oskQ?<*smZHu&1hu zqrR>hR~n2Qo{3)~*?*mqQn6hrA-g6yyWJ=^Y(wN(>TdVYlV48e^ab???Yo8C#e2M^ zi$UTp9cg6P4ZWBXf-Pghil3u+2Qrz2j5_fq5chcq!DJ6xD+R9WEzg}v+8+{I)j(@oK4wEM^wfrC@=heZl z+v@(nE_3U*DVQNgtpG(PnPIkSN~GeM1f>o8eV)b^`P$DdZq!WYP9m;o$VUjd$v~2^ zvI9K@N&{jMOmooTCKzElteI_WXxnmQ_O77KokV`YaY^ErkF}kmxUodR&l1Gfho1`L z3%RZ@U2hbv)_<pFUyxoyS&=!wp~EFmYuM@ymAMySQWK4hk5ZuIgbS z4Vq(^>wlsv6_jXtg^2h#XiiB{Lbh6hbWJ`a>6siNUULEZ?MXfN)Q%W7aH6nD&T#tq zQbLB^xm4#aa>tFGaUX7FxUe<+gTnZauQ)5UzWajgPVmd6p`W?#q{#3S7rtgkKX=U` z>vr!&?@MX!z4Ejih=;fY*ISh5xO4OAw77w zoTuZg*)9~kuWjZjKa1f~d#yD6x3_wYEz} zkXNy}AO18t-`bSk)ivppy^#LxkE!88BYy&?vX}7U$ed7};cRHaGqbzi#9kL0&jUNr ztU+i;e->QbDv42J( zYu}aePBSFBOSi`;`h=HgxEsjvI_-f5D+fZr*W(d}kGe>Vbtau4(Y!WBVO`yKCQUmQ zCA-u1C#_MJoHiD%cWUSyN&lUi86p+MZ#4d?#y=@i|Nffm64U&0ZFX`3>H|j4(E%;U z+4&bAYO%}h0Z%}#^#J_0H>IW^#D7?JG0~7UXC$1F)#Zj{4g18(kvY5p=1sQY!=stj zY@+g7bBpQB=i3u*rV494#{#d3pcm^D!TppeUDgp8Mr6hUJ9xd6}eM#7h?L^6_`f}G+1%# zHUThno$vCvmCDeTa?(kE6o15i6mEz8$*SwDHlfj5wpW_1CH3}rx`BsooguBEr_3;7 zm8fl_35-Pvq!Zp;(%>ij$D_li%Q?NY#e6sK)3%uZ&)0p&Q$oFXjcQoipDlh}e-ihe z!29|HPjS?@{_iiXP9KO8sg^b_*rcKM_yy@Anyp8`h=i!n!nk?TOn+uRswtX`%knHJ zR;@nX6Nv^B%nNU+>6nfo)CNpL+wXE+y_4~jAS0miNRaK`IquMUdFbhQK0g=zGltdS z7Ox#CFPty`{z&y#XyWdkXP1ad!tzX-w5R?o2|6os=?0s%2jt{}CI-ra?Ka{VNCso3 zv62V2HxH&uW!PA=9e+n(h_+jr9A_cv9?Ln@NneP+0(_$m#VJxZy@NG z78uF_MOF*i)TqfWT1!>}LasrrRTBzCHGsF91wH~vV!?Yw!+$F_>WK9g$#gsOQ`0e! zB2^b$muYkmY!RS6JDm*icJ@9p@Ymq)SB`tf=r03L=lF-6%`0kk-(&bDYhM+zOV^&+ zK2$mPlg93*?j77+?A(`qv4!4UbL%9yT{p{J`F<&{?n{Dn*X8tK7EjluLqmZq*Jj~` zgS(WrqCpbJCx0|wAT4ewB-*yu4hFp6nkG7!tO19zCZl!ga5g$sM_~s5weiYdW>}9w z*6nn@MK%w#Q9ln5RQ~Ud(SQ5T-*#BKd9K6>r(C-BSt4=6b-h(RcVM1JSpUkgIM27x zkipm)wY}zcSdbfdCZf@jr5cJ72d(9Z5%gqK^ahNJ6@Ppy?NLxuU-o=w=ByGo4mQ$| z3<)w*Fe0vFeOqdGhMhVe%^uqN^b8B_7t!MhmfX#?`9lTvwlM3j?)&2mUHW1%VD3b7`h zjv7ikrGKN(=!F|*LAe;d#?p3jUK5?b5P#H9PG31%Jjovzae#lPN`8@X_-EkH?=&Jm z&8WZ5%Cm{}1{tslYFRoyZ9pRgg5BUKL$#Wo-30t)m&?4N_X{0@O=sOOKdYq6rwRTI^#{d4hme%4?R%t z<$p<+OOaWg@nUZ^po0)-<&rDJyc-e543L+e0QUvJU;`!QDMxX1k{2>y5jg=z3FC(X zYDb7Dsj&Z0J52979ge5}1VY53e@umU74ZA<5|fIWUD>p7jc$8i?d64!8Tx@8jF(7R zG*qD|QXQ?&!8N&#hmx~}G?_^@1AZ9C8-Jmt+T5@|oExj4&QC_18n(A#V(@yZC`|#j z_$bnbkz9~lQ64dUZi}tZhb-?GR8~cZqW@Egn0-=~PNeS=b=1R-^vXXzRab|@4yw^z zb@3Ia{f~F&guA9^zShxD=jV7FZ8)nkyeDI^`?7BmxxEYaG!?8zM+tER{*tFplYdIH z8^=5w?|R@jtV`6y?1R;fa^7FbtL2h6eocPEq~6`UsF(K*^%Hfn_X|WKa z9$rxCwgt>XNrD@Ui)hn_y3KsGb^3c$S1mYBfB9vke^&SVXOW#>trn_CnsTQL*27L3 zNJAlN;S)4<@&U}vXWB$Dx(r+z3A#u?G|wxcwQz}EVe0g33UU%ME))XBFn?jyE3|-} zwmQNu>6m|x<`bf4zk=jisH#$={OM>uXOJvec|fC&8(CLmOX{-xbg6bgZxX8o!)-VPVG|cU0zT6sZ&2GseZc7i*{D7ga68#9#d#WG( z@bk(3U1uUZi~RL^7{oanihmnXogvHy59Bp7+vuJkfJ7m8d!*J|=?p`x3dRPASu$Oi z8^9X1`SlcQ*ZE>Swflk*Y3^EHZ;Ua!RfEB==)!#!-Fqgubauk8V}_@8(0>*yAZ-HL zjF^dG5S;)WbY)gzhuaRbfdG2EP-vYidMFV%*<_gOeRV-={T0h4Gk>YshuVBc#JC(c z`tDHlS+0R}6`e5+`d2i1y@>2HHaIkR{iQ7M#0LA%;sGa)b8y;PZfrqMKwMC9Od+%xBElP+7^J*3ToQvO8H`(I)1)T9ngO!+Dj@r{3gD@You9SQGdF&|tgtatkcyePT2YQGa5RGf1HVgGnR_0<4hY zq|0w*o8LB@>+PCp|B`0VmoR-!{9hyZ-_-Z{Y0{r90b$AkZOh=JVM;aeG`6W=Ht*Jh zDDR}1AxdNpD!PqCC_R}E^S%qMGF@&C7TX4HqRAGBU>%yR*fhX_O})?J#;@oydl}n% z9(dOu`WJD*Q-6B~KZ^s{%$z!n_W?2vi+Lf7sFlUruk{ z?B81A#gl!f40>j8QSxa$S_ZqE1dzU@s&KJO6ZI3_1x14m6_?sQkaf>X9n z6!><~q7s91Txes{QdebpE>UC`0vHf&hO89^W>0H0t=MFLRTTX)whMzQZ!fhdY8FXQh{x z7yDipznXPketF-5{bwu1ql*+(O7;zFtAp%MvVVHJuGzZd@6&Mg^K%>gvE+qGGhu_-7x&|z)~@{5KhV;u zK!1PxPprc3<9WrC~JuXPG6h`0=+|RVYL$?@k*4x6dS(j)9NUKj~0$|3;w?4=5veFrA6^?v;9RrHYT1- zhcP8v^G-%faM2y>b6-L;DhY|KiMzeofb6F2qC4ugsaWvLL~`7L=%LF4x=3a15*zfk#~nORS_><%J)Zq5CH-n~op-&mL=Uv0S}wXLG5dE_qHi#}7UPGn zpR5-ZLw#4Y_epE!62ZNY=RCqFVq$;~min?=5ae3ulc@#hN`Ii(kl9DM>A ztJ75EBrMIZ`xH+(uejTJ6HYJ@JAEZy`a3gE_x zy`C!++FEunqK(GBpRkD|H8YtvaF*fQlbIvTdjqeqr=y(PPRF9i4aeGW{YY8w8}1Ir z((J#~>NhLp>rX0_S5&P;W`E5Qi@X+u{uj^MxD)rS2%3wfjiI80h4Y#)9Xw z-&L%h{#4ubjJnb36?zUG{8;t2U&B6pp$*+VPSpO{S+(}vM?aL zEzm}|O?fnHH9CC@o=rER7+Scij6g%@3vJqy1G_hvcy)I`Ct62U>5MMaZWLlGXoT8$ zDLaoSzWtk5RNH;1!FcIaTvvDd;ipk+?;0!jsk~PLmxfOD8qaCX|FbAw^n0nhp0be< zHI-o0$`{e5>+1Ow%75o%m)2LjXDhTnFr#d%)$2BqPw+|NGp;=ECadX^i(GPIkfgUZ zWp+qY0u=Hp{V>H3n13Vu{(G(34t0zE@RQa%)%7j2R=%E^eIT-zkU8Qkp^%s32@RBOP zy#?=f+xR2MJeo$eAMov4jl8M!$R{bi%O1rq&TM?_AAH6s;Pau!sm3_Qkxs|pQfuw> zT-CP&mcgh{E)dEBIxr}fnOYT!FGeF?V#iEt1Z`$92s8$0BMdq!-B8_U z1(+`2|7~CBiGM%vq;tSN;0H6RDZXrfaL~{()Dus+xnR5eykinOfQdAis1x>Z+!Xna z45faHjD|U>tFfKV(|FRbwh@LmGqkA>S}HSBH(Eiav|w@)IhX|D(&+WDfFP!lD>S!T zK3P4oqP`-xWQ5nxr45AK=wY`TeGd7x#BowniH%+J#(!Gi?;}0mZ;}?d>ta9xM9qj> z=B?5^6;*3dZ*{gtlaU=pF$+N)@JWv*>+H~y2#-s;o}x5+-7RF;03#5M5$!}vl|gw` z$J+~hHG9M)y@G^2IWCRV>Njx!f8wxTc7BJ-F3%iKy0|)Nt87k_@X%ip2{}+)Lt7Q0S$eQS8d#j+74U30Tt>50AiRF& zw|`uDI!^=N)dU6GP8`+xgD7PDz38J0;}b9o$w?WRhTf-q2*Uf=`B3~Q=-ytYI?Ue1?Ut6S&QE)C0m8^X*U>`tvY zh`5ieX4g}g*V%%45?gpDzS%o7a=+ub3V$SfyPgC8aXeI$9Y0at0QTQM8EXEgSCx>I zq)Wxft%ZpfAC6|M_L-klq8xWgxU^gTpH<`Wz%IqETvj#Hc{w8ZJiYk>wpWv{=TShl zQ2v3QY^1*sct1pgkATF?^rOS0h|@PNuXXlkv!Lg1aS{yv{oPr`X9t__&PsyM0DqF* zS$w;uCMQF0L6m--0qF41C_e?{;?q&D4`njo#Lf<1t6v=Nz%~6FRieFRieW z1LK3i^X?3fTU`q*HHvGBscCyXM4dOsf8Tij?8PpvQU}#~1a-SPLM1ol3}mAUeVRMu z?b;cf%%YU)iiyj2iH+BJo;LU|C4b>SxU=N`YwQ2BE(*80G8~$i@9f>*UlNVy5Pk{a z_FS-ZXpP}(a4+z=!O3v8LJ9=e#^@@+0W%0_;0R{GeA=l*+YAl^iUAgDAp3 z&d;1I;a*#bmc#WQThwnQu{38a~ugLf0r`{)mUqUy>mdsgX2R>aZUxP%|*i)*49?x+-hl7ZWgvZSx z0aN~1iZ)=~H2`A~(o46Kv;}?>Zoqz8xA~$+vT7d8so4x_4E+H|fL+SQ$0WS6mguIp zjICv=f|NSU;r1he*?)?X75kL`YL$6)op*Ml&)}U@TTa(~fvf=_0gHA~v@0&4VH z8Y(h9neS`|W|s{+$+AA`k`JlGP^|bl6l>Vc#`M_3ed*7RC_1k>x`VUd*3ZgwJLA!) zbn8T?^fwPJ=IfWw9{sjBw*y3+sujJ!!ZxOBbhtJql3t>%1$tDE8r)(;Ph-J$n*iGG zA)?21#r9~$wSOS9U}sW7d0IZBa%>PT+<<74{yOBX?IUUAQ_NQ99-aK~lPZ}`$?@5+ zcj~o}bsKO1ZrTJBpM$n5wM`9S07?^av|${rpy(0r*U3SY54pyY>&QJ8WV&-L1d`hSy38_vA$JA~x47~=Q$CtzI9 z>+OZ+BbEFVt$U|XrA$6!XMr_K07vzo{`{PVz8Kil+|zG ze6DVXK)gyOo6yrq3zRcvzRZ_=y5;7fX|4m9d+zAT6?%*ie zFn?)T8x0^~)9~yttq)c)z|t!OYtB)3)%RDQ=-B>?dFHI&`jhwr6nE)^^0Ehx`MlVD z`)fz|-nEv@J5+(^Y-8wgLwf~dL$_~%iv>i9u%Hjn_RR7f!Hu^QENpd1R8X6C+A>+h zvXBKa^b!`CQu-Vpc^l41NIck@WX69&dw;wl@911*_)_8TCrN!ojFQlAU-sSgukQ^# ze*A4}%ZmLZGG8qJUoU0ZDa}Pcp?Gd$hXZvXz=AWGxXU@75@3em<899AYdd8u)mal! z60H^(;Es{igz1g=7_>W~JOM1K9*_%Vt>fy&wy4)>v(um0K}z9!4sgFpTh~1l5q}(x zwb~VUTs;))DDZs?{N+sS(f}?U;KRYx=o8M$s%TqnG*Rp)vWsILv&;Kb`>wacE|ac* zHiv$8mY5jIh8e5!PHUEmfrQ|*RQmNZ%|C!VJ*Mmeym?3-gF#rXZTs`xu=AYxKb;Em z$N{D%Dq;|q3$=Nac-|G8oJ7k0d4FVsAKvQ9J6tLRcvlBtdgk5{fKLN*A0FbqfA@V5 z@hsO`Wq$)vwnbg7iQ$f*yY%|I6mek6cC}~Q)YNE4CF(p)T;9fiJ+|*GL4V-p{j^GD zPhQRzzdwF&7v`(^QaQD)DD4sRF5dT=j4c;F;u*o3Ki^sNL9jwi)1h%2h<}(CZn7g* z@M1*jQG>v=0b}5J3fpK}OCM>wZGHx!%?8T|ld(ZIhLY!|jcnQlKtJLGxf4qEv}w)w zuN)LtX@|qkwmm=JRyg;hy>_>}Y{#BT{T(ZqXl?iW4xe>^DFFBic%vB{J&_$x=fWP0Khs4&h&^GBQT;V(@w6ubT=)s& zOg4PmRK%D319p!I@ID27beHeV9baPbN~)?b>(3LbhtGWda54T+ntxnTH|5$s47L}H zddbF@i;T`ns7^)g-%yqy)uzLyDr^Rx-ei_*st>U7cp^=~dcEk{#Znne+-3v!EDBt! zTY9+0J1E<-djT}xv;<=k12p0$eS}&wpxht23=Kq&WCE`9%Y_43zlCG>>iiDY-1QYx zR5hrnn?!Vee@R2WGk=(B`}eLY%&J$sl4=aqoqbo+O0(~XOLrOc6TP2=X3ctQfpZ@} z-fVTT&kLSDFD~+5B!6xs%nSxb02&T9fDVv^v+4ER!brxW*=+9gJXl@%f??3pCSA z)W>v#lS~ri*EwIb1eh+@vVNOS-KH0hLx!in9yj_q;ej2ocBV!3;fXsM^Qjr&?yh0lBpDN$5_Ar z0iBo6pA_<+r#kMejz@lqb$*}dbG;e0$fck&0*f)%7yH_J(@@ER!FmayJDS&b*Gvx* zSy;h0(=1zItCdBYiz(2-x~aU%hw=a<^-p){+Kz+%2?(#9UxMXg1A3gfxf;^vGx1adiHkP}tif+Hs zn}0Wb9p>ne`CCxOzR%T{dhH!}0pyCddFL=6cY;2PYL|qFa_>diRlC}xQYD=}rgGS@ zSF&F_02bc`7rv}&{_7w3Brz-&%3KBUxSje(&LuC^q&Ao2kw zS;A=31)H!y`{bz4&|c!MMW;Ci+(?qm`hV2abJXe2v?(8GqY*;%MqN>8KNJ&q6XlnH z)t)_WI2H(YR|WHZ3goO+-6cl+Y_%Y>4N#2gg$tz99>7gHGGQhGMnc^dqaIAnJGnhC zkZkFW)D14#$e6FMR$FZV%y8I+GCN+TF#z{CL8}ke(cF#dB0G4*TYEp79gfd`e1B3C zvq$j#Dt6cHvp31CY^?padz-pkWPO%cY+KDmZvt$PtkGUZcE;hYK^xw%oj8Qt<(y9dc|(hI-s|TT-Upv2d~z~;-RG48G%dQdi`A6ljN3KCB7==FMnjX)ui=6 zsC|pp>dP}|bswF;XjE4wNM};4EM{g55+9nuL_O6IX5y1E+9$WY9CtcY;PU|?F>?qU z_u8a3T_N>psu^BxYa6eLWNbF^7Adw$x_$8x$=^+U*R`bl|D*!=Pgui$hUiIREY|~P zjTb8y^?GSvUs2p5kY-7iv42}v7f8eJFWga&_Pr!ew|*8Fyb;(5+U}Op(Gej8oiw^m zIMf=nu_(PK2^&*^tcTU0K2!!hD%`4u}KHjn@~I9*ZhifSby6>SDp2mOB#H= zj&;30BOndL?61esW0iL=)miVI=R3lzuOO9=RMDU&g?<`cbH~3gD!Ze9$yK}m&togM z^!!3oytA#>L&bjGj8B$AbA$@)T2BI|jhZa1gaK{VXN~q2!I}yrFGaQv_d||Ju`Oo3w^LY2dME@e%v5&Opy+9taDZ#D)5O1*!s5@suv)`>xf^mdxw*Vb- zz&fY8@RA;Y*chcmv#o?NNz3a9+HRZp2qKU;7z&!@CJk%${(lIiPm9D|On$!_eTRxq zWGOCIo9{rrFIRESPbJfy|MY##TT^mRbWQoM!^E^XoFhXls>Lg8?V<~4qE_&dxF*D*He*-nfPpL}MU#bl=&o0Fgrqa9(Nx`W*V**6!135Q)#glcV0WCY zxDX@`;1g*f;7YJ1iB5zh*a)G_acA++fidT#**UB)U#dV7emg#o-;U3}+};huxOPYy ze_{XdCx1}v_b`4s94*=U5J}seq1PF}^Yv=eOEh!Nl#725gv;)T5#WNtLW27mH*iu5O^NWI)ICma(3UX(v`RQpIs321Q9AO(j$w z>h*9rMSANbn*;aUr5*mxe||at`FC>j+moAL$z0yjsy`_!iR{Yeey%y`o59LdyUn_Z zVt)cmqi~GI^d|7y>!scRohIHk3>*k2Ta(3B%U~Xl+~F9|x>iALC+3)qp#W#&3EqlZ zP@wSjZLm?eFO+y(%}PE)lO2oz(ewPcYJed|wII3M^tr+(KbKw&v87IYPuLm?lL7;O zw6JM2qv=7>MXbfNpDul-F;#>_pd=JSRDUF#CVgr)6^uF)_}aQ2ED_P3xaoR0F(*-P zWNujc3+v4ll$O-HGV)|vUN!{pC+Deuu_G#ex?%X5FS(B*UhyLj?srsehCY~C$XVXd z36q*E%Ent~IQSRYQJ45^xUdOLTDcC?L$Y2SRXPPTBj&9&(nY~=F`tARY5|zCB!7vD zB`vZ{?WnD2+}k2txK@`w4d82Yo-c)E%*pNk5+d5P`~|&mo!oD%c46@A^$Z_A{_n;7 zI~Vg8#D{97Lu5ycqMBBIu6pd(0w>W=(0g}C=)w|kNsP~&N7a?ee`*$#507>{;MYng zM1IQ%esqgUX}D%o&O~&aqdVPoaet_mK4fW6M9HyJil3PuCznLF8SK5?(o0pn`1s}1 zu(V4~3V<&?``jlzE7LR)sj@w&&mT5}4(N&PGS{X_OviMHE?($qKbz~4*Z zw=RXdUa7%PTnqOPEN#{oU4Mt)*uV8F)&gxpIuJQ0@GVLs5y20(CLxfzzgqdFsjA7n^{O>Z+QZKz^S*yAxJ>kS zi@{~E?^zA5ywcyx!GEXa;2tLUdp-D`_26*mM&LWSW|mGx5c*qFxqrJ=@4aa?2(JM^V)rNf5GX=Y9|?|(~fbzB1XoK62dQ?DfRc4)H`{){Jb`Gs}8M0@7%v}i7hP0 z(pWg`daKoV>A5oRJ9@WJpvN6vYtV21d8Os?L(1VAgS;t;YY;W)2Y#c)&TjN> zK2^P~8Ty{9zMTvHR|>qgd~-~q0NV8w$djeUWJJ;H_IQDc*xDS{sh%*(*MsgFEPu`I zo}dg;C|%ZVMUD&#WIUk3u6je;ww*NwtMZg_`*8CMMFH2b`hP~(3xiJ%zkV1J`2>C6 z?GL^fj`+3fe=2Fpno6;)4K*P@?``OTB;yFMRbwm&EenDzYd}VfSGOpmKAO!O&1owu zVVi8&rPcFBFe3Y{_Qq_&eYR;rJlJN^uXDj|GQ2UwZMY}f28UV;(+&LalTrD<1TVxM zPor!Ux|-7;;eSh!+to5T1E8~QGy{fPD_zkd&{PwHwuZGF6Bn2$rEpD69S!CF6;Z?XxH zC3pj45V>f3vs}d+J(_6wPN(B?^oOWfJV4sZ`Jvd`SCnk;~gKoxL#ay0e)AESQQ6WX2WA;HeboVReuSqS8L_ZuI2mdII$C6ZAgYbk;Nqv zLHJ}fFBang3+0lFF0rf$`@yo)8I0Ca%39d4X#gazGEq{#@-q`RO#FO>G)ff3Y{P^D@{P->-{&}18SLO82H57hn^VhdBlQh81{z4H} zuz%qD0y={n z5aW$6?ty-??ur7(+&!msc58|%J7OrtU-DOeeo)lUu>}#d0Di04ZF*Y2 z7FyI#FN9LhZ8q1ot^lNR6%KwYla}g3>C}@W#}Y1OO)NC=LbhTjv}t% zfu8Gn^9hpsz_q)9iSQqJ+mTqllu3#d=(I8W;mvGEwp z$uX%roGPflT02=&p~sEM^6Sq0C7irba;J}wbsKk{Tkocq(CSa#lZe7)BR|I?W-?Rim9{M?#xb2qJ1UC9??C3jmJbk(0p zESYoqBFr7um`*xDqyU4E>GhKiq^Lxhb5&mVd|8|DU}#ZE}=Z(gpuY+k6~Nme>W?=FYU(B#@90Ahxk- zGd2NY6+85u|2|UgnWa?jRrz)wv-6&Uj0h0^JmKLH;qGx9;V6X_>lFhZqLF3_9UJ9i zWY`X_<89*Iu$$2C%nkAp>kaN?PLD#|fDp4*goO^b8!~nggxH6Bdw+Jo*6wVz`82i< zX6KiMC;V<;{N`Kp5T$(bt+{S?JLY~Y$^7>~`{v9Yq2=X4Hi5^GI80i|6J%h6?r?Yn zNDFQfB3%+19+}X?PEzenrL1QYr37STKJaR(2s4g|ye(d=cC}gH^kOGh6QlnGwD(rc zgB|lFz}_t9+O@w6yML=)Sa`dvyY;?3{{7^yQ&*mQ`E5<&{rZRSdm!D*@2c z?)`xE!Ckpmc=~4<2pzU3qXcO*wvOY=kZ8l8<&n&JF~SozGz5X@ydM} z;W@L3lxEhN`tqP}yD^mY2HeY1kYMw*UQ_kYV&F$X{f(9%ebD!CwZV_`{=&1|)4*pX zUUeFQkHvTS;D4^1zzWl+8700!iGEpV*6l0rcht{*$Y1IuSf}|*Q{nYCjMaUds*|kD zIk*c)WsJuwywG)6c|`{9oRfu$HNy8)7=N z?IAE^s@Hdmh>HxL$A^ZH7Na7|2s%>q0ml233tU2{>=l5QurO;5OGq$g>UQQzo8@xM zA4tyQ1OBf^4}J{lvlM+x!!KrNUSxSzS|=amusLlUp8nt!Re#+*?ETHD?SE#yNM6g) zj;CGM5`QL%6=9CZVKWY?4sGuw7Ni$Y&{nBT4i%ri}gG# zAPt!cfz12TVu;2b9dr6Ru%YE#%a%FKP84R|$0|Ckf12Z#KZoOF>+5`)I>}bA-D-Qv=8M@3wn4OU+v;w5PO3Ry2CXnc}vCUsifTvRb4S)Qn z#n+?8C8cpJeJ|{{{s70vE2W&9 z3DaYn*^t(#96(!2Yih4=ZOHRR6vZIJCN#^c+Y+RZZPrAH4O><=fW;p8Y3QF7{%<5W zTkAWxaLclzzBvt3?+s@AQ7e`kN`F((d4R(}ut1rOHPMIqz(t!aQ7Pj#IN0?m?XBwY}q1U5bmb6?SsU$qUtfkjel8ivr?US zO%|YQC@` z%e#1M$w8d6beL{Xp=xGZqNAtlVKGN0BlbA|@OsF$SS}L^0)>LbdYOZBX`+@xW;W|m z9U~)2DudP@as77DYZe&F~i+O&SG4z zQ)9fCjIAlu<%2c#qfH7=`a#+q*NBEFs!&m@0_3eReMkAk?}Z8?5Rb-RiD1N4HP9fWW{Gi{=Wdq^*%)kp-} z?JQJV%|!VmqouY(NAN6R@_5y&0#rh@k8?|Qli-CWAb3{Hdeq1{l#KB$*iU*l{%N+u z?~?zG1HV`K4lVHDCSZS6PH<(1&(A^mTQtB_kp@HujC1AC0@fn)Bt;0xQKSXaOo0$_ zI*Lk7CJVNTQJIs}+>F~5yjqEyy%$U&W=w#r3QPN?mZ^##YmThKKW%t8Fa6)CaJJg_ z(&20Ump;RUMYbTAVh##qOo^QesMA%XYtYzjktVihbiw)qZYO`+lAIY4D>=5*3IXPD zd~Lg85p_~q&)kZNfM(o^%EI3GCzQZsaL2X1Cs zL}lvsr}5`4o)>?wR{fqGuqw;1Vz2RzQ=irf|G`8Gy3wf4mkqiF3*Xqa9@vqaZ6GCE z1Y9vqzHN#e-$#W^xy?*Y1$2CHC86qL9usHinvF+ zY))x@#H4?6r*37m+eHR;CNXT{RxYF0WByUXrw=D;K zVJeAH$WA66Tu)3HnP$_PZ>)SE*aQGTv8EvvUgm$;T4oT|*hJpQk-gTaQZE`Q^TSNi z`c0fkRNrVza2@r#6%!LpG5(3He~D^m`TtVougX*L!W%!E-tBxQ&p*%pKTBbGV;s5a z@VjI)LZ)RfSuvv`x0kG)9H!7r$>d^`!NMV?Vxj#xXa;SYyh zLb-o)6fUvxK1FvwW)bWXM_%Uc^W<;eLfRWoz|uKRjoh!d0RDVtE+?oJ*%$=}fEbN; zXe{h;Q#O2x>G^DBCE470ZJpo9TI|iD^2uClVkW3TsE@E1UsGhE!AnDCp*#cexHH zu|oJU%V@9Rc8*NvYkpP!t_!dG!f3!h&!c}D+|TmB#A}*^#Qk{A?=fS8>6@iVt@i6U zFZLaY%Ibg>2S~x>b}Ef1x6l>0o z48(Y7Dk|lKr0Ha|6%Ha;m!5VA9BHa&fW~F&9>P|WNMuP&p1@1L=_3nSD5k-6m2c?b z3Y{yxw6>=cXa$8e2H~OdDF%Yjmo0z5T*CZi3O{eTbzO z<{RfuU;R+#D?iR`>-M!E_zV2wiIEZ-pNgEGlpZt5%*OO*QTL7-VsJ_5&x|w`6>=OMHK~GWN=U zWupEso{9Q*;O{c;e0lWgOUmXYKd)*?mb| z&BRBmg7am`2sk3=_)3Z~WHHaGy4vz(fG~rH#*-}On^7XkJ~9>(+XR2A2K2mxWV=l^ z9#uud)8n;+(uoE+KTvT!#``Sm9+iI|>FPWVofB^&um1<{&o0d_;4!x$?ePxo@UGRk zx(}!G9>vl^n!%!HC^;ELLu5$O2CMjGjo~rFqAGU~wq`h0Cl)?Z>WR?C0&bnTTe5s+N%KBlymApEe4dOO;=bO!eH&EM5ZF@IL{qkwDY?;G7nz2AjA?gSc29rI{?MH7+ZWpZenL<@7Dq<6 zZ$LzKP@&eyoG-I2<-!4tsRZqlSqibLu|)Q~Z>Q>p*eSk|WT_JjCcjo!{=oQlmm}+T zy2H8w6UHE-|G@d+6Ed77$JfNI$6dPEJREn_J$m3rpQpY;c6?P@FCeuf8Y>$K6S$R z@^7O|99BmI@A>J!|0KE)tH}t8*&)<$jmXIuQU+@kRPZSG1x^wt$Pf-5B^OmHFI0CM zZtUyIOst!}0}6URXja#nkK57twGaXlIZ()xb~+VFQsoukTK+4s@p`!j9)1?sdM z(n`}|7G)?38n~zKuyAb z_Mw^E?x@jc*n&XtpGciIrRaED{coe#?6M*gw#mdIa%V(mfP^ zJIjt!yW=Eg{~^WoBGs_7C&K+GstLY?qA_wH7OuOcIfWS*)<{vaoYA@;CT8R=SzIMtMhFOsCR!a zD*LkXk}oFR*Sh?@aD42KUcw0avYdomJRc9sx#b1QsB6PMsl;_&hAqcQF*RMcBncpu zg(}r3UUAiwc4NJT_fyRzFqMb)ad2eiNL+Cnak1JpKXFU@IgKuH`mWBmE7JPE-*9cq zgrKhNtmB45S)G4#s)c_71+xQ*X>)g> z%wqU3=BiDQFXdFz*7<(RFUvKxRC|y0Jte9lQl`SXXvB5dR|M>bHb+oNy=Z@wEmLX9 z3{?Y@9-+#3@mZDktJ@B zVmTAD>qRr2&UW}*sko{ZdvauY>%++{qfsiKFyr-fUyAMmw)zdTKg{>raliD`A7~Xm zV0D&0_od%QmTymW`WIp@sfB-rtM#bfnyI&;#7a+Oa!X+=YPxlM3&H}8V&bV5pouES zvQ#D^C2qI$!iG(t98w<)!YOY71U`=fpU4oIfzizm91q`=B=t?zZ)O^oGQymSKg6%^ zNtXH-bdnP1MZtqJ(u51FBrxNE`^=`np%NdX^OREO6~oKUc259RFyDXJtv6y&VHt#w zZLj7e!`DO6#FZ`=^@!Ydb2`iX$xqZtFK1c3Q0dz=)Ys%%{r;Wv-+uVRv7&#Evpd<+ ztsTPw=`OW2fQQ)D||2%y&qQaww{gRK0ZW^;Z7u>DAyZYF*;OYAsDnhpgB}5 zh3(T0-X=MpEJ))Ax^#bBN(+0b)_1e^I#BT|Ro;t4eK|Dp&oXYYAJ2l3)I!o~X;TYO zooah%i{O3N9hOiBL?DogmF!9uu!g#Hu3Iq;l%ousViuqwu+Ia)bd|yQzKb@6=S1tF zL4V-d$76smaP7MM`&jm!Nr?Xfc@DxDOR!*L4zy#kaFz~SV0wS_oL(ca9j#XJS_eWr zg<}aDmUa!yR*;JycEXrrB;GWvI+)Jo<-CW96$?<6+c=^7bIv&C@a#i#xVBrMd4(Lg1(s5R}`<96axg!Tfqb4ls?ZACt7xr3%XddM;tC#Ai;FGif zexN>k!0H@l9!kHDEVrqdkFN9iF`(Qm&WB^R^qkgjRv~2AWYsX8M&x-UXu6;a1{4DRD zR{3`FeNFE7k7Z!_uR&g>*};(`90q*~cRPbmOhWX*b+d4*LrDv=M$&`17MIODR4N!_ zCg^@iZ!&+h1^r6e66gkDP?4^xLfJtm+v4uH)|Nk*yqA*BzoqtHp!C0zsr+|KrgG1L zf4*-e*Kk`;MHhuURz<7X$;X*vJ(3tKj*EF=a0>(Mchu0Y$FZQ#H=}$~&w?yXW@=bA zSS6FP>T82QW`W`~5k)i`XT$zLQ~o!|&a0JYmA-$CL8txBPh>KFLzvy8H$1%ex-Rew zM1MhlIz53tZYXQNTft;#0iNv*vM)?^8;3T7@4A!~CAq>euz}D&|?-@4`5~IsvjT zp<7*s`~rcuwt?^T-|>I1w|T0gPwlX$%}ReSX>MIFPd=&OAMPKmnFbdphAgg?s342wkF(Je9scrQ+GllmC zw)=rogIijj?HD&j-16c-$FXy$o$Ivc>V4ao_N8ah{*(7?vQ?CFB%s(^X^Z9uo>lGjnk{oRE^@Wa zpgB02Z@E&Xn;ccGdN!j)&}5o5zDm)q7nl-ut;pK1yEutf+9X=}a)SLp1N?twUx#n@ zZz!wKiTaQGJ^u`4r5(tv?0afkI|eNh+i|y&HdZp}R9r*N-3Vi{i2})zx@F@)3WXKx zvIC0Oz|k=QeS|GDfLEtgR@ zC5i=~ud-QbFVm^Nrd&pjCvejm=}5)w5!%k}f|;ym3zZh<+@`Tvc{qPCSkWFVs)ZzG zG`M^bFC%A%8|x|X1E(@i;5y5k#}&S9h3M}5ORsdS{GV)EmncIQ3}$P@WLb4$!k6)DccWox>oiOheX@%{wxO9cEgy!-y6#s3N9!%5??hG(9-npYi0>|C)7 zR`4zzwyR~Kx$__zhmPEaepK=^XIRMLV2(^oCaF#-ClJgQs4B0rLg5w@Ya~GxCpYb9 z$Kr3J-nrAoFV%ngG7_Hlf~wPL&hO&dDcHxVu3W3K-n2P$Avox3&&z#CW^v<3d%4l= zX8fR$&=`em-)LMdUJ@{t+!j~#qNoBuD5?XcJ8aw`?55Cn=npec2*vL z$weQ&6JNojZoiJO39{HdLU~FR`#{fM(97?5{gm(5nw~k-^7b%05PoChXD1R~H%HE< zDt_Afx4i#1zxnmozLcZ;t?h5q(w`3SD(;RFejU$ejjO5%?PDMLNaG`2@3aR9yaA+V ziB#YEov(jnNqqC`F9&kJ_1ByW*M0L(I2)5TgiJ?Ev9;NS3o7f%uY*hAhr zv^=zZLDjym`59&Vt_QsTwP(+}d(RV|pJDc{woiXgvU*q3=jRc8*`lK9}UZ zPkCvSd+RfHcjphhtI@Mm^6zPTW~6#+(??r3?`rn?AotcsT}=7% zr1_)ddmm|Dzx#K;lDq0%k9wSA=v@t8HB;XHr1PfATid^At-SLAcOm}oYI@1odH+M+ zGmC$^l%L|Q4Sx%^`$+2-ZLPOI==ZpU_q2cR@%WyW&wP&G)A$8dYY5SXD|I!0)mK1K4{HZSR3;cy$rP*=FI|~2Q z-)~-0{vqFC%f1%VDM>n;=k-*3>xODbVfbqD2*1=^UxK@ za$^?ej$!u_myJo2QV0MO1$S8YdL=Iv|1fil+^H3s(t8&KrwLkbw#$m<9BP*;4!-*V zyQ}wWRHvoe`V)zOt2HPhQhrIq`m z;dIzQveB{0a?7wqv_LDn=V4$!t7ov;XgL{@#FFa4=;0Ks42A1540#D>WZ zhPsKOc@!g5k7&hIbq-|YH!6Q*2#~UcyBS7v?Rv6?J;$cSod)*;Z11Nt=iqGS;fSKD zy&%CZ$IQFZ#q7@z?v@oN!rfkeHQ~-?arjt}_J=ge<0|_ibbYV&;_F{L-S*$HCfP9+`cf zT6X}vJPo*q=rjwsx%mo|-iFY)6kvA^vFkzKFE#;RXTS!W!0irNtTVhqIJ}wnSj-Z6 zw#_H@LIK9BIfL&+9fE(wb{C|5v^%5})JfesRa;cAXPotsu7CaEM_8`^y*kYoUPc6d z(2*Ab=|AZ0e6uxh^|WJu>X4Q{+wJBETH-r(Y3%KQonw&8@vv4UJSNFvHHK+v4VaDs z$=lEfH~j#UOVuW(G?Ol40!$`|wGm}_It-q*H|NIMiZu_mKaqdRT}Wsf%DVHbJBH5wbms7hx`cMmuxPiPXOR`VPwd^7^>zz0&zUCx&2TAhG#LJzZzwG?_h6qg2&e`u&UCx>grbaQ!} zuA&zvIoG9^#Ox)hm4|op?ziZs+ZL@=hF4W^TKO(-00n=Zn!QyShxYBt#9ODQ=b%6m z45Dj_0VZ(^fMv$FVo1&?Af2`?uo+3l1W?UUiV!``twFf&!!)op%3GyYW&^Pm(UrCt zWVc#r1MjW*3QDZeht$U-SzXh-@Qx%$E*uDUUHS&;ziSoXtK^!KqUP3^uv|!MQU}mh zUM-xJJsN){C{v6U8)6fYBTW@XxdtsdBFN-IRni(J-H1aya%pCZddy>qHYTwUvw0Hg zEB!+X&2;hP$__y6xF>=5wGB;xF(Ql|YmX3C-x`+g7h z95$luC>-!^$mcwxPGfdF6~JmPF3TM%D_KGmZMA2RU@>JOfZgf(N?{L-q4)es3&2@u zdhUM{w%_glKL3EMeER7}0sa2rZ^rStl#eLhUmwQ;=rpdrJQ$pc-vnan7FpJ0IVFJ*eF)kam9-P61|M4!trMG*Xe%_3Cb0!FU+ndK zj8-^rg5bv9?6xvCb|h*sYmJ~k3KYP-pJ_la6Xpx5zoiBUk>OG>zt)G=JzbV zij##pL!uQ|_nM%NA;`46f*c(zIYb4w&?^@^PJN@MgbvgSp#UNY>pZIHuxbK)FR{D0 zow4M`=}c+=^ceBK1ZMXb$}jDiyTbGA@<)!HDDOyJC(&DespI^y)3pvs^>BaAtWwG+ zCBj}|d}$!-B-m{$ZtlUR1|(KHos1-U>Iln1;7n@=z^ix@>I6OONjyM4WDMO3>GNJRIlJb^|lat{uXE$G!;okN687U8D%1^tkzoAt(+jySml#!w6^#5md` zP}V|8e((y3>}WnsqAg8X8)bj&?pC^{k4=;Mkgol5K)WY!erXSl!?f;x+C;?`ZFD*} zE8APm_S8Z(iS6t#YcrhL)`+t_U^qjdQxsmr-Bgs4ZErLXjj=$z|MM-)(?&5#kM3|2iVW-hL z$-=IU90aWFN0Y(>486BT3()ZrI7VHoX~G=|u51xE{@NDF(lO*V(wa7sBp8rF^wfpXZ%bL*wO_LU|J7J(8!JuLd&CvebX2 zU*6UEgK*aK9Y`j^3_mc<;1g;Xan7vu&3?phexiq%uZxz#5$g%C^xsn}!W{FEBXY zHZqm2W@M@;!D7ac1h_VXq2Q4N$92(ICX=3~|Gss5E6IO*NN?}I8q^XdfbJrsH#Pns zphI1vWj7p1o`yZWYn%NF>^G6*<S2Ta#q_ig7FKB3m>NTKLDus_n;Lg$xj!1+grLB{F5@d4%apyQhcH-Zku5^!j z<(DoVq+ay{7lO1bgam!i_riARI9w~FODF8jIg(PGkZYT12_VFR@q}jWtfsWuWx$r? zHsVfBIufR|h3BTn0kqUiciAkRA@_i4`5_zVMm~R^hw(wqM;sq+z7EJN_tGDT2oWf&qMi0(nl;G-F+RHWu0c_PlFO0`TK;;cRfJPx2TfmRa6J~ zsBVAZBrO`h9J#W%(KqV`_98ng+%_E)m9ddS$~7Cu&<~hDM7@$`CgGa15smm(vG`D@ zycW;rfxMUUDKhfz`s+Y!!xV;o;*y>T_-nKOF4 z7&Yq#ujj!^UP$YHOJHE+R}>9;1j-%6g}i@R%qJD8lyNG<)fE4bv2!h=&ja~L(kGv; zhr6!^^T#de$(qixAhm=fbtPD8IU-3aZmXRsH?&_Z$K%Df#6BTDk zKARXUyC@PLmiy^mE|_=+C?O9v_|EwIP~I--J=}aXkUvbdRvUK=dON-zL<%b?wq<{s zW#xq9ZpAu^8KsF{5;$wnyV-HxFA`MDssx_)i(=pxswf05o$iNa;BI?CN5pb7756Ro zp;7WnVeB6D$1hT?M-#ZZ&T~|!M-h&1|8N%r{szz1+ZIh&hNtk5`z&AZgD>r4v@Qbt zA0YG#f_~k^XoJWvaB_2f1Rgu@X}^D~?-J`gdR$$^kL7x@C{9UFJ*NsA@7M8TZ$I;G zTv{k4FC>|r*af@v;3XXZA_cK}?#KpTjfLetj)F0Pnf59{fCqj6uhHWio)l$LG+(O+Tl;_JmT$+$KS>YqG|Kl$bcT0IT6BXFaU{Z%5YQ(P!0<1S zcv>9#>0Oov^cVE3>?T?3?kLpYRk#@YKi;tDv@@KJW>TZYo^!r_8goJVn-d zd1iZsH$)GfbC=n)hj;4IZl;pXQDa6@$7#uSEp1D;LC&8P4S1Jc^FgP5`T5NEMU&fz`{*63#I?D04*>={6WS|DyA5&{q&>{8KN;?X8ivqCQWk-Xo7 zWrX`&A7IWlb7sjLSpwTSnDYH7;00-lB>wR~_9Ke?R_=eN3r0 zj{@^pHN9;XbP7EGo2qE#jXVY^fN`j$`Lv^P6dHfv>1YLz6)LU9;ZR)nXlF7z2ObNJW&NNO$mYX=Yt++v!}fE*RtA{-oM=m zNZGFJNA0Tzw-SXmiOh2UdUEuwlQz0@UEVK3#MZK5i&OTLKQ3>|{wd zi5QW6$lFEiOsUx&)|_(PfX)HqjA%Pz{4pO6dOK2RsF7A5N*VDoe%EOW9&S&$4&e)? zz)kVTsPZq%{O4HaZ$;@}R>$A7I-X*5^XJ>6G@d5zeRb32IJW(X<%o2!BI}Jdh~0lR zo{Fe=gBmuHI;^6=g(&+bxz4F*zgqx3lBuf7w_HuDq$JRKA@;hoByc=Lt(-^*JM9@} zuk8|4i^!zf?UuU_CBnEU<`bSz|8kub<5JfH-ZPXw9|<4#u@}a#Cry4Iz|IMqfCs}B z7Z#WoP0+f-(OwZ1Q%8nfy=pGLMv^`Q!3x83Llaym#4+(8!X?K zdhNGloo`9sdcMw+g~S&#w>?S8_-sA?Q6~1uFZ2^ho>^c#FPvT8<67Otn|^<)@d{IT z>A3C|fsv)_nSrc(Eji8yJf(>OA`xV&%UW}}UqwM1j-fC=P=2Mi?K+xN8jxsu^qwTo zP3?_uPIo-zwc9y2&`y4JC-WQr)jj^|J1np3T$lg;-Opbq+#C}3$}1m=|LwP};rpWh z@3)t|mG!Sv@h0i?i^W&S#g%^r1YrUiocvE zAx99>9u#>n2c1Fb%Ta$87e09~BZPqt6ksTWH_wYs-Xz&P%v6`3EI*{2pUe4fk)}wl zPTmujIz9Ae1mB$A^Tp1Ly@KFxyXpUalV~@otLu$-&q{7b>F0;n-qgEaorO-!M94Z+&HM*|3vJ1 zdibdP=@($ny=Q)ea$_NttR~txX%Ea6v6q09&R1qajo00<>Ii3Tbn`>1>#!hVMQt?Z z)|_fYnD{GV6pU+98D}Cs3ve9gp|vU7d)d>R59M2ZLf^ZLtHiHME>_p2Z|)M~7s_rq z{f6#xo-%(BExmu?rEUZ*B-qeU4u^OV*n! z-2kK1Zo_UvQdsKoY#z*pzMRB|up)fT`W#v(>D_JdTq}i(v--CwFwYI&J~8>s2=u7V z{b@e;FTZgy`lI*~i}^a|M=rNSWit-(n8!NTGM1t^ka&Mb5;K#le3%f(-X4N(3Sv!{ zltZ#%3#ql&Qv$@bDO5By-UT${k8^CPFYM0^Q#ax`d4yeG95KE$NI`ENq^=i|-9L-* zkI=Y>lrOi=dPd1J&_iiu%@j!kk>ci1R0>uPhZJnMv1QCcad8;2ei_fqq;gZ;4r*?~ z#mZz&_H=*7Fb6jcwy7#XLGVdaHw()MAKcoSd*Va z@LI}8{I37~vVM_UWqCTi_Rng+7cAyK2r!ye2QK$Z(csG95cn0-%4JS&7zFUDtd?+< z>xO@vj~XHbTy@UneGrC?9nFccI}3%S+gpi(B8KG7_~kCf6*gVYKWDz&L2+87y}kUh zAucbnB!BZ(#JM$_)9?g8JF51~ME%wo8a!X`kwF7ps&+RZU#@nicP`g@#PG`%3#*O8 z^7RLnUVL{*^^HjWAZ=%?6Z%xgD+_84TMU0S2waV@fxqOnlx+lk$T@_futtw^>$qMQS7uSiENFLQERwcrXO;h`wfg!(9zA+mkmMp{pnxJ!XKoIkR*!Aa@g>OK;KKuA|@nuWG|Db;r5@Fz|WsUUmM8&5^4O~uF(%K{%1)RfK=2)mJ zCmhJ`&1g7GvT>9NrL^0~gH*BEh=r#aBJV&7u=C}@+vf{%JlbQz=TLdPLUJPe%R#}{ zr56z?Ez2p#P8`@_sa={PJLcrr-q$n+#!qk zOj_0g4$8T=jq<4Z2!s!xaDD8=-OVY4?t%)(Ufl@33}RXJzny3QcTs=5`9<%A(Oyjm z+-G+iebj_4EKT}irb&miSV5T-G0}Fvj}d;AA`Cw3I(uKuVAd`ejx*pG9|uLdZ8?uJ zn-JO5Yehr^rcmn-#XdfK!yS${cURcOoeb>Nos1WO^pb4CphX8lI~}4379KijS)x*=Q(Uv z>t?L>ACcUn&p0haKHOdJ0lloph=+jO_e$>B*!Y%&8dh0ZWoV@j<0QX1l*=WdAr-azmqgk-V2hWjNVr95!r)1B%&<%r=%^C4=D3 z)>YQ+q>=0mASh(^&Y4unNR75m6cM%tZN1%2nW^hwAHq`WmOi<@wN-}hh>Z3hpCj6%H$b}~#wav8l!d#s!{7?rQ!KXSa0i~4@9vHC2 zpcEKaJfF_1y@s1}XX9k3Z{X;D3MV139HsJyV0wQn%zK2&E9jI%=tA_yb@4@zUUC9| z3tW#HmtJ)rsd`##dX{|XN!6?_oa3+0uTJ49&TWGuTraw(zu<9>e1(2}N8oXoOl@)& z*(1;1^xeWA8gyPmX(Fw=j9%;W00+FLb2ELlE}Choc9S7Y1_xYcXh2BuPDL6~w-_iC z(0qT*+Rd7xV(nvWIE&hiclq7j)k#M9h3*UAb*fjHar?8b>LUD{c6^7hZHbe8irb6O zu|L_GENTK1qR2)SC6no*nkHg?IofjFc2Tt|KgC-OR0Ge4P_sJlRv1J&KA-tM-GiC@n z@Bl><0A3vD@ly^|-A3eecQyf3-huL|(88-J#xIs=5U@2^@;V@->G-p}z8Ayw8v5<^ z6?k##4!$;Z|4j?s#xK89*?mp8%v+S#*pJ^I1uBuTP2p`%UJ1fo;UiUZG zO6cXZQ`e>M0_^dy@!ka%zkyB3;^Zy7+ly0wRTd#B9cJ2Qi5+UT0}w+HwedjXy09#_ zFq8xoW$)}YwlLY{zA zQ1P9#e>x1@`DDI#lz-c(efAXcRfJhg+uO6sYPF;vi$j~v==Gk8mYPgM?V;JvW>bGX zZK;{o%Z9!)vaSLEj*k>}gfDG$Lbq!ho?1XgP}AJYL*ujfJ(1~YZ|U*<)lGj_zsP^+ z;x^MM0n$F}ujg^LReDbo2!CKY`SuKpp9)gH*?0_{;EkS`m_ALl4b2Htc*H8<+OYPS zap>h}J>q4n0{Tjy$+hGjh+1bR83Fl2y=gZBh;$Yu#pH5!fEzRk=^2GIk*cI9(!C=< zwkqS$zP6Y@o+9>ls7@&*t}lPCGfBK`9)Pc!2ag_oof0dzoan6`_#A_7@rgRFC&=2hdk-1^!Ith8Z|8wrw~}98_@S#s_B2 zb|&Boic!o5A{2Ije@MYx+?%XI`2s2-5_;r01W-sew2O zYdg5GgQhyeBGyKl8KBE|J)hUT)!+re&UP_VjoBSsqpJ!#+9Q9F2`8YABsl%^SsNtw zj#8|SBj)L!arki)PR)NtqMVyEXuM1OBwP)~f;ebWfCpo0UbHljWMBZ&!xHI-`AXht zM!laYMybsTj|Kq1cl^YhED)o_l#Woks$97Ahh~N6Kk4M3{QtA}rp?JK%evrSIk7)p z+DkJM5vMy^paBFzLJ|V;VMS=3=b0OQetR?6em!6tV|%^p?!AA$*pf;E_2f`lSy`D6 zcH2>6-X8klUkSC|!0h-*_&%ucPPg>zm=@(Bl;uk0e<{Wb`L0m%>t~n=84W92sc?ED z2UWGy8!~B-nvsweX+#kb6fK_i;&Qs5dt?AF(=j!T4H@CttZ4*dzTJwXoJ<3nEsA+z z%@uwwCadd3<5GVh@SzL;h5Xll|JQ}x=~wf>yCiyy2z}LZVh!m1q$|+7Zo4S94B+_Z z&eDt4PrkL@%SGGURoeY>O(D}B)$@w=w(uZPfx4W5aoZyvCDlq$5~?`|#|xiV>1?^$ zy8AZT(5@Jh1WAffx^{b^gp)aDMjV5svzw~$oICuwBUgVD?ILu?GG{NZ-5ujQq4&Hx z$L3Fw>AMPmT(qHu3iL=XfOiY+Fi+al1Xk=TY;X1xay3(UZ^F~!GD@MU*6c7-Nh@3} z{dU{Y;UpMqO{2D5^xC-F8DlPoYiN4QCH zzF0)+0%#AU`Bh8m%cO}*_c)_2?(FX9Dy)E)7yc@XZnc|Mk?IuXhSu4v zo)O!ow4jFY&EXi%cCpPQjLC0&ap7#X9Zymz2QPyOoeT?M%RR$gIudqgwISZmCfZIodwJSE$nnNp_sonal3a( z+x-=Pg#CFd(s9AM2P_sycJ$WK{Aiv^Hr|s9eHl+v&7P%P&Ea!yp!I9mXf&b`8yI)+ z_1t5nOdHxYqvf>fbrWXilI!Ja%p=L~up&PJ<|V2=w>1A6qW-8h*1FYVpm*T*SPOpV z&;3}2w=3Nlz%9k9I%FrR#O~>4u}~R1*RimFgN=<*nex^xyI?iO6DiMUEHg6~mCXR< z)K;t2P4nx&jq*)fS zT@Kb7hS2~REHrb__DUuh?P+;02$)Wd`^KA*rO=NQBin)N0wy_Q zJ;T=uj)dk~k>R)_`Kd70b*QX>fb8RCI;on&3rC|c+$#{XtGBA@I$?PW6I-u<r{eq6D8dVboo^|oZ5THhyZ zm8Q{wcWmQz(81@{jQ)g(V@*?k=+t%ilzj<0$7I8=a#$XEd5_iMt_QHNKZZ|>`98V5 z*NfzSO$T~XJLSWz4Rvbo0&9Th7UDO1W684eep+e3hVPX5dbpz?cQ)&FaGemU4cThv zUT|WlNy-I0v%=!$dmyF7I`I|x1Clz)__o>>01%b8Cf ztn?EQ@XNdw2ZeT*>4hFe_OB3@(I>YL9Qf$Uog7ULqvIUuV{A&VVt6^96iC0N+Q7sc zrKA;4qLMI!sre{_>U<%84X9~-nA0o;B?ad!KGmjc+|IK3EnFnC>cc+mv8Unr-bkRhSB4X2M7} zbYmRrY`dMM*9;Z_kg85l~s64mQa-Jlhl;I%JN4z&vZH-odTBL z-u!TCeV0yIe9**yfbQDKm6>@?x85BC>`oNGUmBRlZro)>+Vyu2Z~fQ(`2XUr^5SjJ zV)de;zB6^xyB}7;SDO)2GkpCT;+GZvmC%pVb}ICi?NmZxF2${t$s9yT=8I1~+*Q~IlnSHWTdZ#CFrHh>Xwp1q~C zoh$Tzbp)WU+R!RG-h|3F(Ab#D`F2#>u4Qn`+q@BvEOp$l=vSwFiEoe9r`GFO^tskO z(5?GO<9-z5cxCCDMK%U7bUXlH4)gLA?#H{SW33TLh}>1);XB_{qc9$Ydv^r11Up~Q zCgX4sP~p^srfOU0$0Rk}PbRG*44HiiE>rq{7I05}J_ey4|9_wC|1+F@ayWa1*@Gr{ z#Oqbt@(nOY#g8Bv_LInInBs5Lz=@4u}1s6DQ~KO zXK}*u=)XArzeC@xjPOKHzf0g_LCB+v!8+}aIM{r5W_rLNd7fkV6_MY4>_l|`K-cb* zL_ZFQMx9vE8H(2xT8=j)X%Xv@zqcPheg1V1xBpEHDU(>&?84q<@o+RQdZ`Z=^<3)b z%ueIVojg%cUDYtFMANw6w*Ht)=Tb7&4^ zcsM*QL%mnG={n(lpyhEl%;(Hr_r5?8_EG}fC^a6;*%wL3ytJ$i40N=F4#sGD*;u~c zid7tJQM<8|JHxJyI|}TgNQ?3fp?@XtmR{-iUiDAe$}j4m{#GA?gYUJ;FkI<%wVgIg zV?l3R3aHr{8g!Fn~I`xb%f*cieN;+vCjx+gZod)pi*Jhdcg=KR3EK z>7n;UdKh2sOwW%VhL{)rt>Ew@%golp$L42D{?2oC)CI{906~$knqvcZstRZN&1n8)0)8}KzUQ$g(GJi z=U<0^XRG&}T*#;9YZ&!^#iaiV#rZdyA$OjmiQDur^XLzTsRmMm2A+p)lTeON0t8Qyi2DnBB3!3YVmbgRi+?o64E36~=)&Xsm3_&ME`GO`fA zGI2VCamtf*JUDN9eSgpEH4xXy-D4QUCoJ3& zT`qNRrjdZ{5jw3(!iHtm)K}Ive(}JOyB<%znhNyw+y5C4e_%X(3*UbR!+)2-uxKOT z0AhIkqG9*z{$u>uwF|qKFr{YA#GCUrniG?1RvJYeF92PCa~%SkVojtBZ3YRF$z&r) z!Em9p>9m>?t5MiPgPJGc`HTl$eQqJ}Ql;8KJ48!!dQ%$xr#Kv-Js;eid-q=O9P+_j z>-g46Pv4c4bNjz;awd0Ae#esm>AT`|-7&6q`jC>ZRk^BrG;q9hn9DM*5Po1Jox2b4eVxLJpLn6%P>OwpoYeh2`qTLp? z#0%7arXEJF!jHVE>4S34uf_vqV zziY?RcM+2+l3^gQFoKg5P!5HSSb;;M{bIUi};zm%xsLS)G)KVl|qI359>(L|ho zXAkl!h?^{!1)ATnvuNEd5SKLx8mLXBpCo%MlNd&1R?(Qpcfe>H8`Fjr)3pyQv;_Md zIZ@t(`%Kyk4c|`U1J@j0{x=&;fvp9ukj2>LY~SVb?F8V=5Ew<6F>vMsX2OGs#PC~J z+!o$W^wMsV)=Zdc5zIh>Qw+$Jg9defhEk!bjIm-w)SW!}8xZ&m_6y>kFa88!PT>fv{M>g8@g$!_E;K7ElZsVU<NkdtvWC3n}@C=nYNz~)%LTWe6?^eOx1u^ZuSxC>B0q%vrY|Ouz z0-honyi>iY3T;*!Wi}~RYbA?>a5=NU9x-8Li;%Fh-7=K1k;f&tb~kv8VGEBp$x#Mw zcX&%pU2o7cmFm_Qrfe5MeUm}_#RLwQ(LPi5<<;+{ZFdc2|7v;dn0BszDzmNZ2A0!> zS>fl?23uxv%*+-_v02dJnw3*Ot|gs}p&G)pO$Vs4zFrO1o0>~vH*dy3nW8d3m7Qgn zBYtp`ynZ8Hy9-q9zKy0?=msTl^57kPdCz^9pI+d)P~GDaod%E^1{MXb#tSjE*n(Tz zxn4{Om7FyzftneuH`-=@A}05K>EK$ra!YP5E}1mX7ptfqs4JjS<vmE93BjjI3g;MJ^pE2z)BSqW8{C?R@uyw6U0YxnWW)@2MY5OIbi7*a)_MVdauwRCsW{pslw@sM z23;=AZHaVaW#Nw3R4TL^2!(nm5C<}m=-$k*I)j=#dEp_S*=NPKh+n5Ty7v51tvEQO7~s9U46Ze;BnRd za?2h12m1cnBl^MOyANmve&A#JQurdh09O<=cO#Iq|K7~W)m`Trd0POi2~m%p26e^CNd zw$%hv&_iu%IY|)A+dNZPH^KCwx!&RfNr$KQG5}a>+X%4A3Bq>bYg!~RYjjc@3_rwa z&xrR6Atont+G#R`gTkAVczxBq{Wg5RDE4gK;%5l0psU}K z-2N@-UXiN%enLHX$$wh33cP218J#YH&^H=d-p7S72E=a8d!iEU2aL%!>3TNTQl)1t ztV*XDO1MzH>!B3tR!wiTZI)*?2!UK-vMgL;N%(tz)*Idd@E66NUH&Pe{b;^mmFo79 z1E!0;iljTC0A-#7Bbl<0Zh}tYWWy1v9ua<-jRJ{{Fe(Rt#e$MWrqD|n&*XR*V;yPE z^%g0oTW=hs(oN&n4-u@pM6Mr@u%B8qx)_%UM25*N838jz{XDDa!L(buEU|npt$UG%D zOw>k^&F$sI3@-}8S>_xcvD7@$_Zzs{qO&zK5^)Jr_?1XU*_41+xV@ZFt7H|Cy}wi) zNpwWG;W4sl{zfEx^#5OO_({Tj$F%8|;hnMmw}sv1Z`-He)TOL@>bjLp=GR_*$K z^;ll$ZQyR_a3#@0Cn;BSNTX)CT4|eT9F@$zlJ=8i+-y|G)NKIDyWdgt<$VM%lkVj0 z{}I|nX_~ltVR~1H-s#>ikg#P(HoUE7o;4V(YIlwC)X2w$B-2#`Bmy51p&sy-Mpvwm zQlsr?&IviR<}hn(?(K9q4dB9|=mo}qT2;u)>Ou!d;qM}RFMxbW#}~~%L(D8~?IJUW zXk>p#%2-L%pz@3Si2)!T{XJqIqQtn5~3ICAmQj+1t-x3CuXP9eCzg*)XWp zsb;8oI6Mszz}qb6zol5y2Oz#A;*+MICE$m{;#VnH%wsti(%Y4kcydU?@LP2W)^JikzoTpl@aR+ zt}fC^I-d|6GEZRUHh1(75UsncF}t7CmfspsbTNoz*94irj^$;C?HRYNR-_8CI;-y$ zSlTqXP5Gdw4#;Xk)JdLyQ*x;@f!H~vFwDhjE=ewo$$m*rHVKE|nx7I?rT<;Ttb0R4 zc0Z&Ye<)z+65ZC-Znc_WAz%;|ZXy1N+{GkN4OVCsBAze-D_-E`9A+b46P(Vl%Z^23 zPPVBGq^BhUE=NY`wCkos_6*tp(q6D{tM&QCAd&YG{6)D}fj~chLOWkS{P~b{$GdJ{ zA&)^MY-X^7mHjNAZYR5isA^dx64}c1M%=XD&UXmO^7F_gtVv(M;(Qvk(!$?h$U-TW zz|utMd@9Vlb+=Dt(VAPk+XxN8cezE+_+{lz-7(|fvUvJ;}>x-A~KW4`; zpKSU>{y@HGAN}90J`9i8ma5HtlHHKxyu`QEBHKg4eBGmevZC_J03B*5+FRNl9C|Y% z1}x5ziQQCbwqk^HURcZunWA~y8f<)PSFz%(<8Rta{+bDnY3uLF_T_>s7vBE-hRdvU z1rzGQkq3^sH-^tCa%i}7ju{HisZWuYX|*r&*pnMF-%KAf%Au!pS5I0WiY?)P5HCw3 z)P-r^#rkP~uQT`u{>O!o#sBj;H=2*j#>hADidhhqUF?^&Ov&+loDu-AT1o<%ud!I= zAct|#Zp`V)cAokqUz3#A4S=*&XIYhK3O1VUh+s`7`amu|v60&J($Kf`^G@?)fGYg@ zcfWiCZ=cdrUVh83tFQI_W7Gfl>KoU}*98WD2qyf0%cbj?`u$c#|DjODy6c;F2XM}j zqX+pw4&3~VA=BcNLliqe0=+G9|Arz3+96p?Y%S*!cQW7XMzeubEd@ga5M*VJd z_pIE1ui(&QWun8cwX-`rul49&DZD8AaLVp^{^VU%=*M6BP^|F<0So(-{^Fbp^cS<9 zYBM|=1?s}TyU(G4p7`||w!9cPeXPEAe9t-m=ksrTm>|^7Qo}CnefTSG?0r^az^avq zHMCm~ac)LecDObd;WkVKEav&xWhtVZ1KpZ`*>ePl1>JNNjbLvUGi07``JUGTSrq!U zNVZeW8{@ExmCE*((CPz)k9NjktUiz6?M0&Jpk>hqnnp2Yh(wo2bEtK;Bx&IfXuIBTKCAM>d8d>ud zFCn`XXd>9G6R|F7SA@A-q!V`{nNtUULPL5mSD?FL9!PdRxI3oBd9QL5 z@_sJX$M?RrtN$vHx&1h|)LEa$Fe9xdb)*dwW3}fqj>}}9BevTO?^VKfWY1lFyUm=X zz+gzUU(vy=w<5VpEWl=}dIDJ+RMjkMgrw4KGo7bkJlbIIX7+nw3H4tq zvUB#SXXE=T$+=)GT%p{a8NlIx!LG0m)5<$f&kn!t{L)v`(-+A*;mgIPUY3pejan2_ z_n0NBb)C#+%>--421tr+isy3@Wk)@a5}~-^lz~9gQi+1Gg|X>cf_mF(GdtYu&={P0 zEsDm&{dyo6rLY|M)Yr7=ksFQ{>*3$GY0#>6KFeU&u*KoE4~8WCD{KFM@1Z1$HdWc! zinz{lFCHx?g|Q|kn}v<2W(ZFONL(&jT$<^DF~&QuN-$|G#*n&{Cw!Z;+SH@UF~<@v z#eg{)xxH0#%}x96o}zwCqIUr4NU67{9H-W+=y-U@cdo1;{smp}vI_j)+7KfAd54bx z21ls5!8($I4=U8k3llVdqynrwODsCFI!sNa7&y!zSRC?)0#r0nBAbcHEf(Y5G(kLq zM@(oeuSsLn*TmPx=rfrPv_Bu5%C5d`y+R)qyeR#|jC8*Mj?*n8?Vi&A@{Y*>MKYOh zxe}dA#S|DJ#M&9uB)yoq>NsBQT_A@v76PrHcMj%$Qmlz3vEAo?6AwtkomKAh@k-e_ zqoHr{4vj@gtK6v6p8xWrIGm4;n^Ez1>f4>0KS0Gg{*J<{OCYuj*FH{qZ9V?#X6+00 zes8UT@$@%d$IXi><$13oqqHztFJ`paVBv|bA?_xjD-H`C(iTGyrEO-o9ue6hS|R>s z#`4xio(!5zERHvSTZ^IP~HsQB#sq~6{u>V$k?ho)v|SAY2F zso;B>9IMXSv41S(^8qO@Cd(X8I;L$5)&-<;Hy>5O)*?!DZMG7~Z)|1-ZRz=JrK%0d za{X*+75SVu6{%mOb3!U@hz<4&b!4`|MupO7D{bhldK>qDzum8s1^?pODUtNsqy4#f z%zH4lejMqE4J2-NXL@>3UFkWnkUPC7^#0KS^~|O_v>HWSdiSj_?8rl_nI4$+@!6NH z_CxLGSH|L@@x0XW=Cht}7sp86{>?j%3{Yfgq*rf$q|}qp?(YXH2!8HfJGPtp{pk2= zz2w+xpYi~Iq{*B8Kk!}l@OAiAYvsrG^M1a5LsjTW=|B6(b0@N6nfF8Ui$t?eojzBO znBsCgn_4v>A*M~?LDE9?WULAS8Y0SY);ZQ@*p*c2Mr^q2(+yA4Ia)1Jx-NnRJ>SPv zB{d@x!9sN@Y4C?eboK^}h09Ki+Cu;m7x+ahxVs+*a5h z-gKnc$G<8&Fy8UbgM_5*{p!J|9=NmmM2~&*tp^{L5B?k;(-PnN@XS3=$Q{R=+T}I4%(#z zU=TwP6hR(JZIr-)WN$-xpxB#jN3Rg^zFUa6jeyTR(+`At*A(rRO~=u~+TVz*x31UL z$|X5*E(n~%CbNFt%Yq4xw&A>Vt+fguG`w$r+MQIC0HBhJt!AMPPW?lXyup z-24`=&gs`4&ClciyAx}@W3UY0_uL$&bU_K2dXgsoVeq(65%Di8LZVhGu`jKZ$eF1< z0FbaHAWL5K3{GMAki^UhGx5M!{t})M;j&G|7gW?gyh>z;jN~J%}?3D%i|_ zbEdL3HyJyZFgaQ|&n}#*-MnWheh~C?{>b6fj;>t=euGy3dZlVF)#Ce&)7lZt;DF5Z zwUeOK_Hc1DSUK}#2_hVuSNt)PSg}w?Y4S?PQ+v(j_2HN5 z*?d{Sl*TbP`HEh^aI7`*aD12{kKRqEHTpnS4ioJXFjA}k720r>z?_~l(f1q4L#!!< zi;Y1@(Vo#bDQDM)UVnj$?@>6p7fwfaN5?brwx8|Xd^Nm>Df!XG+20)u*gHp$BI97M zehPED%MM*C5A*7xIeL(eu@83-=Jd~~nq)XB;7*1-v?l9Fl&q*7a?!AaQkIw(svn6< zxL?l{x*W_=g5L8ShissKt|#W0y{VZd$PfufNnysyaR#rQF-J^qRreoZ`Gj(pnh(kd zdoOtD*m?~yi>uA2Z=wM)VX3AV2hiS4;hY|%GLi|CUR&l9vFQ1Y1{Z8a>WtWd4CjNp(P85C zwoQ3zy_O6I+|ulS+CZNd^t(umr=+@72>3nEMDk*sFB#Z1ryBuep+*F>Rqr%Kw*w7k zP~-7_ZS5eSo1-LJ7WE)0^xYow5ucugPz2TeVH!iJ${1Z41|5>xm+?PC^@|Fp)5Exd z|M9j=oLaA;#XV^M`$mp!VPclb>j{_n76mA5^*8HeBigW(S7^X)V)ZCTSnl|5UkJ>*ZzP_%EO1hMogshq$J?PaJ{g@Oy0Xyy3{mN z*|_vB%+R)PUW&%@lrl_NVND5k1HOH+Ec?Y(|s@N`Lcw}pUz zfcO*gzG}NWI=zQ)lKa$pHL-#JOh#CuiyhptIHgx=?oD=kfi)n_$0FX?$~!Z*7L+}r z_i|#1Dm=|L+GZ~qx?MzY0^@#B#3Pgzv#iK5Hbo$NGh?)6+g3QNbVx$or=4(9*#345|L2W%HGHWyL z;WYL~c{R!Wnac)sD}dHYR^SNx!rbr|5Dze)PL56}{J5Fm9WT_e{bQs9y^Q_jqj>5| zPzhNEEt-a|cUDL^vfP;79FBLbrxxW>0XdhWN0dM0gG8TYF}>vpWib)OLammH<`Dyb zj2A792u21u9dvd_({^(Sp`E5_qsr3qgp3S;$HC(ZQBN8IPGt0?< z>~|BYE5>jZ?ii8X=c~x@HCljc?TooVjq)l2S6Q`Vk4Xb4N)=xDJRirKR3{79}F^1^GL6 ztF&))#;v72EBC*BWM|C=6)l?sQfQUjX`u#4)Dh z@b3m($K>8>uHK3+v+7_N>|t1#FO^&$_x700ryFgLn$ZZYGPAQ8 zs~M+A8?eKzIIef#caP<=kyA{3s(2s)YVpm77{0KUU!twg0HwOkWW zx8>%5rXJ>73uQM+XYD$rQskToSQ$!v9w1_z)ODsHgG+Ojjd3RXbi?`|2uz z)5(fi0*m=%XEibpW{i{@a{L7jdfJ`i2FE9-r%>_t97^yt4yA_;HlGD*JCh}s;$X$Z za#WG>#^tJgDTR!-=BNJgg~yDHSVG52JxH5FCqM z2O1aPobiy02xoQGKS}A5<5VCVe zj$4IJ2X{w*?c4Ex0;kq%&^b8)u2=B)cHh7Lgf6%gzOMZ{nC6EHC-jDG;wA=@7gU8evRRB_CWXtl)FzM|I2Os1$^^G#_j-r6Lvhg#N5N!xaum3&Jm#} zqigv^Z>G*9w z(7E|zY}-OV)+;Ohe|>%+jH+4I4MHAw0e&IvoX}X{bUC2|XFoAzbCHEEg4aZ{_ z$oV)_xYmN$85vOWf@qbDA5QlPrU_^!8zX2;w>9(yo$=V0WBA;|@g4GRs44PcsOkCD ze>MAmDW1v}c`pOUx%opp&BD*ByZdGT`{m{=s$Kuu z{qOhB9E0@h4lybWPSRhKd#*R3hFDvuTgrXY<4IZ@5d&8 z3X{rmHumE&O+}T=jil9dG9elh5GkYc(U7pzc1<>DIwGtP19@ma9&jwvWhlakxI+vW zk&uSZlZAhqkw*&=Me!|qUA5LSY z(g)&TDx^-a_{^@ye*+QyB?I>x`Ohc6UpPM_m#<8S`wPFs1bsO)eybUAy-o0cp1$}G z72x5?103DfQ`Jl-yUt?9JTuU9pqUcLWJ?ND-<}L$)-TbRAp&lzU^eOW6fy@( zva7_p?ev*26B@7Gt5gMqpBR>}5aQd#>p`-9OsGD;59n{j^Ix@~Uk;W3p)!Q-1`A>a z3%R#biJXKGm`B^?0s;GM$7;fVN*z1A+fh?sj;*(#xXhIrGKYJ8Br8}k8dQ4IOnqe& zkCXX6n{WUdsZ8~WB*^~;1L5P^&Hbv)56J3EVf%u~`Bf4650#T3@T?gPFd_qw@f}p} z&7qH^o4LW7Q-i=)ElzHNVGz^#QgA57;5JoI0b8e?WX&?n*iB?|dg@|-jUo+Hl^C=m z&WUbcdU%^VPWg?Woc{zn{@nBj{><#>Wqy@UO@6*D*C6=OhVH)`D|b^LgLG>=O-I5A zK|qdRSK6vA=HuL$i5$DzsVikVje8gby(V1jm<=~$Dv|~8nWk*keVq&`de~yKgpzDq zR*U|9-_NShO+Eb2>^So{4q>E*N2{LCPK z1KxO{ZNF?oe5|Wqn-JHlY&S{`mq@vl<> zgnx#KwiS%5rR%t+hEmw!)|;at-85X7=td@pshn&!px-mY-hxt#bWBz`k3g@10>U#rRL; z>Ov9!hGH?KXDexc>X}(I98>~9^U@#;!cZO5?s~ZuVw;pnsEGEVMbP4`@bxC!q&~fe zrV^koJVTvXUY@8!8s~N~2%2N6gTHCOc}l2b!r3RMe;u1XRlip-q*3UmeP?&sah?7? z=ixVH0zWX-slE+_9{CG>+_(Q7$z1(X*Uc-_xJpxBH%D=Qqf0H=A*3d`)C8Jzq=-)y zb6FJh#3l(%DQ?1$tU-lubvm$l7K}$V_Pv^QTZO1_}Vjw=Cm7lIFCj z;r{q;^zKHP(N!B|zS|Q0KhZ_t?{a@Yl~9k;l(w!SL6{8mb=)QNsFQ(y;=5r$;@eD_ ztHWe6wvcIm28a^N##Wm+p$aP=-)oU2Lamg=fNzaPWv1L7EcC7^`XerZZ?`ai#L@fx zHs*bPxVMbV7ghcKS%`fqG`a{iz*rWOr1$t~RT8EH<*VTenlWuFHROtwnlTe@q7(t5 zDLfLkBqCPjz=Xi0le*csppul&_nvFDs*O1?H5B%L|CUzfw~OpwvQGLkZ_O!M1%fZ! z_+nyq!05{-m zFEzuorfO~tRXe|cSQ+-gfx4CSif`o^1gssi7i|n7sc=1*)!*w>{Jyo=Z?>ebo9li1 z8qlqO{+s_y248t2d?*R=4$V(UgNYa*MI!c|6%^tSC(oo95MUhwT4fs2X+1Zm zR0!R*R_t5Ypswkq7ffL~XLJTk{6SClIk4FC`i!%@CAZzp@OrUtXAwD2@pO~+$#7B^ z-!wsbUnPCfvsNdro&kp+QJYm=*w&pHrO$J!U4m;9r%NK&XJm?R6m4UWyP73LjrJ0M z%nA3nwJUOEyu+=k0)S`%vu#{LGc#FX{WK3KXG<^@7NFXm;$g%g@cr1K?9okdUH<*Y zY2=Gbr>r*b)e`!kv|r;_c2ENMo45?U^v%1nd{OS>(;622pM2$;J zPjE)qZg0}@WK?KEVrp)aqkF`vb*`9yxWSSbcVt4TePZc}V{$uC7;bm_>3+BZJ;jeZ z#U-?n3w**!bp+sEQQTV`rzykv2)iui*HZBfaF1chFD`wxQg~}0UUite7*T)tpk7{^ z<^1_`o*iX&ahKGwVV~0|K*!1t&ppEzA2}68G5X3j@0sBK!E5yHC9X*7F-^IDD<_6b zQ_29sn5>Epa*EXJYbQdv$l&=MDne~$3l8NmLuM6rI}S1cdmt{_Vz`BtUON;Pv5nPb zJ>F>JroL(2?iI2J>K*;`*6(BNgZZbr#?BfB_H{0&cob(>h9~9ZX$gszW{3%<=4fw9HA z_slmi=gY1g6O_KV^h0PGwd35xszE=++sd5!^RlEWMwG?_e`R$M<&1_gNF$@(07IuJAFX;J0BfmNUN>lcpa^Y51ZS8Dh3 zn)^NVakUbEDifC;>Z;m*1P(^W$t8#W0e?ZiGc=)3Vi{Wv?UiP#h&w|t0p)OF)J7|E zfO7{+OUbxA!R(ks*Ra>LT--3y1e^+#JU}#ixbagej9hD2njCUa`U(AgbiXIXFT&qn z_#yFmIeLCpijbADnD--u0cmo!U3g&X(yBBpgxVlSWV#z{B+XlY8cbi0aWFtE$!ez~ zcaU$r1u1}`ud$JfC>^B^W@xce3P4+K!|y}$S@(Wn`t0&gB3nMz2mZWZolU6+k^WGG z9u?u$PrPM0z5U?n3D9;JpkJthX0C~BI3cN!a0g5QPtZlKWOlmm^zkwQm_dPsN;Qr{lg*CDb2qr!Q z`uB`N3(JPmWjeF=>fQ>MG`ZP9)(%UHnc+ADsEO9JgTc6%w$nmgE|p+e5H!;5M`J59 z*<4x~B&`peu$UMFM`Dx+7?!JV0Q`){0q^s{cf(y*4spvUvs`W1V6Kp|26ZYHPIR%0^-;UHt)!3GQ+jRPGICfkf7C-*rg)!%Yo73#N&Yy06W~+X9e|47?M+Plk zYWN#efVAY+v(*&f2%iL?K!H{^M)B`gd4u6ChK9#qB=1ctWk&og(LiccsaWt6kKncsocsxmJ z3+VeTNCX?E#rQJSBM!<|C~Yy38;n$IJ7;Tp$(H_P7TE3#+Ths;D%O!X1{Pzim$>;- zF~4ijdUWYNIr{;odujxHC)a)B^db~xc}ou|8`=sI0fUnfD9uthb(fDf9%C^j(YfacM_C$3VVG}_@UQ}Bn#ccyUft>qg#BSY$mr&zZFbDh*TVZY>Z?d z1gNvYx(&b_#?@i6qAQ4A_IQl$Cg$FU27pnkYmv-j*2(utDM(9Zzvct8Qf3*UkE(id z6F4tBbHeC+_|;|JhgYAq>Xl0cL3!8f@hQUOPvlhw=BkYRbEGVM+AVaAvio@XAHuF( z|JQv|De+9MtG%TIMNT+LEw+k(8}(`%@GxvFlR3O|gCZk?)l`-vB-&RN?2PQDte_2$ zClidIq&=?StHfGUbxca=m*{p6?ql+>@|>yst}y>V-_Gm)1^jNIM>KJGS=;5Rfc8aU z5p?5OzVqiYbZC2c^S~BfbBrf`we;M<+zh;X&LfUT-a_oTq~9% zAw&RqTO>8=Z%4fl4qz;Q$+xH<%W#s_jiOO;3asc|nzy@9(`QtO1Zp!D6k$fYw5KnG zJ{ik5g)!a3bgE}~Iyy+wyXFP*p?UExC7(&er!9|>DY{{^h*YFPX8Q$5f!d;B$r0&= zsAx87yEc}fC9?&ji{P@TP?AoRwNbN8UegmWCWk1SSCF&!i%2SeG2hsB#4V`(>cuhM z>+$3y1#gFBLLY@>?!(md^yD|O`}NVg7sG+}jT<4V}*x|<7Yub2vk+4Zd5R z8vLL_IeK!LyRb|z-EVotN&zuweV($V$ zaAoGom6|RYbl3O+d20;?iPEmF`M|BY49%)m~;0WbOSS+i7j*_(65W<=!TJj63YYc|NbvZ7k6gC5b7r?Y1~%L`pQ zHl2@7`KX^a6g*x1nosZ5`a5!eBQ5oD`u5m$k&PF#Lg@Lx!KB@tU&JZ~IjEX*BXOeE zc1&*;<8T=O0p=X7B!e%ih8FdqRN;&q4U)75M7#(UeQG-He^Nm8;WPT?ZRlMu%h!|s zN#^b-8~pBCpczU2F6jT;0`PLjN&TNsek0r$)CO<66Sv4M?w0_Zt{FaZY})Ddnqs9w z^|-NDT5&MAjj=LH1zk^vsO#y|2IkbJ5wUf&aav!{dON1@G#jgxnYv9SMTmwIaLGMo ze0C!}3G?x9f7epyp0PrHWvrZ^;zenamwl})b5F0zFY9k_u74oBX@fmhQHaHj1z-o8 z%iLfvJeZNImeJu9>VXRqS_cIDJi?p}N6!QJ-G=coF`f4iQ2w}_i(gQ7Q`yEZtXUB6`1 zCJd*9gF?wJz~oRZQ*g%BErl7DU&q9rkN6Z#7rBRTS!=O}7i%0VsKRn|wVZfy#Ecl# zd5XBNMQ`W+F{h(X*6u$d`jH5)WIi7EyZ%F;M{7=3?-ADYtD@`|NtGK`q^12Z(acBV zu;+WPe`;vqkDYQ(4Wq8MX8ztqmY^T0{38DvJgAV=dXDLXlwPazB{xm$nt`(g3hFHz z$&iyHyYhvRw_B0~qXcIu0TqQ2Z64P9lrVt);AjG-+Xi1Wjc{Y&EUThNZA(L7;%m>4Wp~xm=r}%y< zXRk`=7f8C&u z+Ur76bUu;Z# zb`Salq_@sO3}n|*uUZ?Xv_v>2({OgvCWx6BGt$A7DKeTHN>t(SL=VPCcOOXEb~aSV z&6wvaW3_NHD=1KVvW6H6ZIk*>%+~v(_!_7!`*L>Lf77`ahz7sp zd0@45lA8}Q{c~0EtN?6j8JNyT)Vj^FJyp2@#3&I&4;zV71~YvMCkMr{qI{C2dXe!y zw`sPE*2Bg$nESiQ3Y#`lWbfshl}y$F>H)i6kpDretv3N6Z|R>u;Lr;tFD!aFJ^Cs4 zv^e&a7Vnx28%daa+$jCif93JPpb>!A01ZQn2%-pW=au0En|lB}#Ydy%ob(`ie?S6` z8d8*A50E`D5BI#e5win9)5-xryge{YLrG@Y9f_zDaSmb0{wb^GZQvb6-_H8zUAqST z%C702WEFmG?h4Sf0h~D2{ejN7vk~e;!2caCp_ns9DY{ z%UQ7<5BzW?MH0~PVbyS(-fpmD>-of?Z2H$F=(kh6(Yal#|xt`r+xIUi;sTg;&+d7%P4$97rh@Ye7XNMWcd2QpK<&DdC%=% zr=aasi>v?lE@vQWf5B00j`S9^7S;0_(N%hpqTFaE&UYoyPB^gGr67Rf174Z0*tVC= zF@K4SH!98uJ7}#^9)h-;x?EY>7~s-jRUMw5f$!<}!l3t`o_38s50{^=e#NI#z~q`e zKV$7@)pV(bD|tPh!82|kuhoRy)WsGp5-oO}1fR|2h(uXve>OB&vOq+zPEAibw9?=J z>~>Qj6qG$eWTL1KQga##YnFei%K2@OFEl)dh_JgWIQG>F-aV)&+wf6cc2c27&c(-< zl&^;Ojkwf5U3VF-76#V?_`fgO>_2Y&np>%O;PblA=K5jWq?8mA1% z`C4jr$YfVDf5-@*myNFOmUJA;9yAM@-h@e9a}kbG7>4T&HJiBBZn~7&r;PBoF}(2h ze0I`b_rh5BVNJBS{R6bAxHAbu?_l*)t#oe(p;GTi`B!l9tBA@sxBMQQjJzOOqje4r z(Xz+%#9`n|2*1ttiIKvpCLcr$TN^_>a+ZRL@VdeFehpMmYsPL} zm4U}C)%_}mq~|nS`vMmAa(HsKKesB7FRhBp6W@J$P491*Zse^Uzh_Ma|M=H%LoW_G z4e$`(rfa0MxYgW`Sa?H^M~7-lvg(0mHZ_|KVU4tl$-dU)l5;6#%32!}N}we}g7g~i z?Mzlxf907j7uBqu`T;#uGHJQqJQFf`rDvDc_Hx{Z|3dDl(9_j>oV$^(cGFYs4V28% z_0b_!2cpzdG+cxSS}{o+OA-#oxTGd?GZi?lr5p&fK}?fJo8eK4v#|{<)EZ)aYS+tP zKxc;z!tAS@N1j?qZseB1cUKYkt5qcTwBt%?f936Cs>XbC{@}NSohO_>9b6o3nFU|A z70CsbMA>u(PVBX`<9l*d3;y75op0lyG8BZl9!XWESG=O}rZeynsYihq;xD6T4L!sB3XOh9=bkn43&GsXAV)7p6 z>DhDXj8_9WQRwRYln#71+5QXR>z;MABi~~i{EYd>joT&>SKD<<3wTkkEu5V?v5B&Y zRZ|n^7I{xi3A z2eLcY-|ILK9^o1*z zXf*Vle3>gkcY5UNv=4Rae*zkOzFeQL-_s|Jmpr;%aFn~G?&p^2>xOU6+ZcUer0 zadr?9Z?+b|ajq>#vOF+0&O*f|104xuMX#xFBJK4D-%KphHQfsT zC{xV#VzX~(hHLeYe{uRC7-P)fwFKPqBt#)!N^87EB4u}x-jy$6poLz%fb zQ`7(gF9mZYVk>AjNe9ld%0Vum+fhMKbrIwcIOWxmdi2v=kFK^VmWC_a40L|f1s69>!q)b} zE>NCZegQuwF|SmJQmuT`io{tdx44an{BA@L z{~{uIkLbuBP2xxA<~v8Fdr?;@ch&%xKt4i#!`{&mqQ^_H2L$dGEBHv6ZT^?R@o;JuIT%D)Me{7Xb_AYBWj)yl2v^$i&I@fnM+IR??6(*>+9*tUzst@4k0vSd z#Uw@gf2XqCdqr>1{i?UyPw%$(Vk`f>;OB>A=CPfdTjD@C43bKkxyW_j^=EBk=}`kH z`DklYvcCgW-CS4xQrH2u!44A3ssnfp5DR*59Y}O-RXmc&eze$uv=T`?KRA`|-a{q??`C9lX~{VHb!GFzagZm21GbeXMe%qH;;4sn>o~-F@2TthugiVs ze*nlIzQZ{Fwkqu4ukXI*{MhrVSEXIC(VxaSC_dG;As3S+DsOa=quZX2^?2UdOXxl? z7;>-gbKP4`(}J*hW)=7Wlef6fY}pbq3;`D`E3r7Bw5)@KMb*~mmE_r6N=7j{W zRQ1en)*b5)Y+>JYD0>^q6QmiwUIa}qsLc7kS%tVegw^?+9>aN$sJ&)mPquXhf503s zsa{ksTdj&_zOc8H{R-v+6ELW0-CGU?dkV1p<~gnQqRcuS)~W8#CG2$d9x-03fS#7G z|MM|{+P5=UY@=xtX}L%`jgW(pOS^?QSdMGAjqR#Jdjhwj6x=x2*}$zLdPwAay7n1! zuw8r1VvR4dg9iH&rXRF_q4$M-E93sKc!E%e|}!UF8_{w zY`5;ku63m0ThZjZs6=jQrlyBhR~KSw(Z%(76wm2qE~NqzhA?oBUa2uVn$O#G znU&gNEQ_4tF1+zUjrNh(uthwi$D+?ss%Me+hd}g;Q#`_X0h2{4)8jbSO@})Os;&e=QZF3|oIKWJ?6lxoJx{4a`pNGuyol%{ za41(&_jDYECfG1Hf2Z0sUg*@K2eC=H-DkT(M=T@6Y7ZaU$A8n++L$MGiW~bPbwFP| z#L|jq{0LwP%5+Ax-0yNBv2qt4eePItz|ON9Tg z8+#g$2I99Bwf5vdX#=wdQwQH^8_Od_#U@MFj7e&;nj{V^pz8^JNHW_Gvm8g2iO;IL zFth;$-}8`@e`KL0OSqnolc-r{PkD9kNp?~N?>_lvvqwEi!Uw9nZ|1S?M42LlWKhh< zL$bH_W#?ed$|O*zpoI?lp%f5Bn$h}>00G2UQS)dSiKaxfu+y7@7Obdk-9oL5;$*q) zP4X&Z>!+-N%NS0?IUk-PEBDa>?^5NrUc--A2Oncpe=lh<4=p@uOilJn(h_Zkt) z=k&8n78>+E8Li8xyW>dzQ`6Dj@$vPm|B{rn=U)9UOG)Dy51O}-BU=j(+DBl9CXGBI zb(E1%bUZ?bdL+YJ(_PF&HZ#eT*?T*2%mL#flIld6!aZ!3gCwRBv6j?A&2!8cdTxfl zf6?hQ&#wfxbJXr^l$+z zqn#{4H3U0o~ponZG(F0e|l8he|IY{@8e}$9@1qcK4FQnJ#|S-wXAEJN4w=7 z4{)Y-jGo@~h6TQ__$kMQSdcWpP~&yiQxDlvta;QBVYp?(aSDrcX^hw2N^( zwVx--X&7&mIi*^5^XG04viniqM_q8-Ve%HvobJwjie%wz)e_f#e z(kOB1*8*lWf`bpo(xey6qg7nai@mv%A(b4CwlZGEQ`j{*FxWH#4p^HV+_b$>CYQ3Z z-$tc95}4Vfsq!S}YKFI&1NI!VUzu`Z_UqYq!;?SdssENjI`2J z)J1AT3@|(=jU4WYK1tD-S(3H9e?W#yDzQ|IoF`mMugL{@7zcAzF8!74;r2nqM_@WM zjjjGv+3#u89UZ&Br}ELyrwj7MbTPD3j_6}kiR4CGuO{fjswh-s!Pvyz0kZpm3++ziqz7A*URunDSeBb3_k8> zeV8mC3zCk{)|@WYuiM(_mR$3tSnBuUB&~G&g;C|F%>I*c`>31vf9yKv6`E!|xWW;` zJt>XA0B`(+Fi{FvG~1cJrfnTJdYjaQfoKS~5))cAG1W1bRXv&+c)BeTbsCT!T3CA? zXGU>5a0zy+3kXm=1%2E}#!)=GKj%u9JIK4I%YQ1H*ZT2~!;f?7urt)0E>Ukie zc(ADhV^jO7qTrOhe+`-8vSl_HLx&SM!U`(ld*VbS3T&X-g;Xe93c@}$&AaS9X}l_N z`I&kAI*1N|^X2$VuRFUmr^|Qg6^4Dit9R(^xVC>zWPC7gc3eT<_6GlcjsagUX;pmg zl-)i5)xG->6xClhK`?IdPfj6C52v8FX9D@)8`{)t*eaf5e}K9ViqRA62c*dF7jFd*`Qiy7Dn* zel7`T)%H#eI#$^~QaktJ!ZWV8|4(D>vkEcvT`kui6O~?D3VH}6l{lKoqp=XdA|*QeU0OR7+WJdOUh4ULGd3mvz;*6p z3yrj5Z{$6mvRFFOX^N!}tBR=-6&%r9l0U2)-yZY^fxTm&*(|)K(@7TJPW!L>6$Esz z$H3c7fA@s51LS&4!M}$c>zuC-cJn1xSezjnU+v!;TK%iY&5f@-1vGa0|4$*t5`{+bmwv}rDbvp>Q1fXmdMm-S+8w$tCMMX-1Nc6xlfy*;V zwAy+=r%MorJT!VH{(c@q|NQq9t+@w__(iPYf4Gd9)-xa((TemKBX>b0W5$vcB^2yN z!)e^4VJJvF5CF6A;AcBl#^sg_&u$Mp^l}ydA!Ca?cjbI) zx~5tcMqU`^?-q*(ozKk*LhQ&-NokBmbcaM00^iY5^_7R8xR zX@fb#BmkqN;ErnO95EIdwo8Xuw4jy_e`9L!``VcoKIS0Is^mWPTO`I z%H(#m*@eVvyclvbtcWLK+*pNMZ=6MGEeATvwr1E%H~OSyA;#F#iu08Db{h1Ve~8C} zZ{~rW_WZ35+P=#x*`Md{H^_N&&fRS5Gxw=fn;oiJ6IVX%t;kSj%<&we7POZIv^x`~ z&2*v6s(3+_nB9_%vmOBcTr}JbiPrX{$tf_DmO~<;SEQfEK%GBF&yN&1(e~qq-*qhB z^^f=tfgd9;YI^j5(^hPcmncDTe;GF)i!ALZyC$*rf`P5VK-7h7E*G0M4llL`bqC18 z%0IMYYBYkjRpB&aLsnL-I&@QzUewE{HrTHQ-qH2#tPkI9)eV2qs@n*?zES1HqeZC4 zpIdhI$FJY%lKB6(*oD(*9~H5hZ;OL3M9c~;cm4ngnzl9P0FNCOicDKLe*jV&fTEx@ zv#{N8QiB@2vlq(^5za86)iJJE=hhTokEjt;vdgFJ`B#>lIQ?>RLIS>rHTZ>wIzFgK zy*U0YOA2Ec438Vj+`FDY$a&wJ}#xFH7Fa3&058(Ttx>@c}pl2o?hHMF@(d6sb0gdpB05o`sCmkscp3PGo~Pq)<{=!P zQ+W?gsgF|z{aCNmf8CM3O*;Ier&Z_L)#aa?aSt+bC|mQmtqKC8Q2>}P2hOsD0Hau` zZ32+8-qY+E6&$Fv992S*m;-K<1mk&S?otm(jtd&%#v8s@NIh(^kp#2H~=IXCU5mby$*Yg{({gLS?OvH|uE3J_Rs*HH;&zuFg&r z`&{J7ul2`Aig`ufuEOv zM#!7qVXIBoe-%fOh&1zcB9^OhY??GT-h*z?v}J* za?jfxfM-PEnhzaB)YWO1yL`8m5PW~f`*)^auL@5E(!Vn8eh;<(&gP$4h<+;a{z{TR zjlOR;3Yz@;YA_Gl(OBgba-dnYU(y%wTn~m8lN|W11 z^NqN~e^q%qMyYk#mcYm(7zlr6iF+NvQG8yH`@~&^L42t&lK7jQN*_*{oG!T?yc85X zL^KZY@rKTpD|ZWYh}R4-2nz%?oM1ilSSvB4)fz-g&gK`xV$DN~Y)7w`0x|X|a57P+ za;0(;9Od=roT}rXPqxDG@3%um-7~r}%g3hLe^Ga)q2|T?Gal$^viycX5OhfkiTyg0 z-Ua86)I7o2zOe4o)WDyKO~n>B95mIz1?m7aCtPi{l)8tt#dtT_1V}m)tR5iE50kkK z5w-=2;{m?Rlc3yE>xhsAXfccrD^1sCdXi-g(+ca}Q|Ur)lzwI1sk#-tYwMw3*?J#3 ze-S;BS91EwOJe3fx)h)r`5j+S{}W6776&AWr5A-ycoop-Si*0OV#LX5I-19Y4`8sk zSxLN(9u`;1t~Mdwuexaf@p9(*LU2c5_ajze{lS9HCqGKa0XPmu^SCURc8{?wo#F6Hz_u> zLCcoOicPi&mZ77DFt-EIS&mhQDwa>V*#|O2WW0We=EfE zO|02NwV_B`vB%&6J%OJJF1`%ng)-goNkZWJyC3k~#VU#P-f?e%xg z7$ZLiqw&xj9xdmnAlcQ_;%veg>O)~VQe9qVvK|~zR6YzNX}CA`BNiJDA#TU3r97f! z#x=vk;2>`6#wA0>jQ5csT0jpof6Rn6pIZCg)9}Qfcb}Zr!_T|uovz;JQKZGc0aHID zzT;(|tPIZp$9nTgyVj z_7cBmwLmy!V|P~*SObWH@tmT&4B||l^KrKYa<8Bm{t7>&dr}ggcEtNskNW?^#;^GC&u zAaf0EVNM{>VEnV&^*WGYHU;Nx%O4ub)bD ze$eiuMo3O9FjHw}q|mgJl_Z3sw47@q1y2HMq^)u^AcpHI@)xC)e~y{eE)Btj4ym+= zPc4QXnkfY1v{MWUp~+<5*|yS?8uOKaXU@Eyo^77bmF4;JBg}aiE8`B5);m6T`5)j1 zAgRxxv|`IiO78*^{I`Gn2Go8OMc3+p>0dCaKKdk@ZS~&}*E;aMcTwuScCHxt$4}W-`n&P>eFUJqP8da8@y+fP2t!IzU$_vm|gtY7)BaeR*F zJmiynrig7X)R<3AX`<%(Xgioa=CY5Kz zflw~&v5lx@N-DHkzyr)%Lg{m6{D~)LHg`uyWAt8rF7j1=?&T@2*JUTQeJ%C+wibr^ zxAVjv_9NE>(!;~$D6+lzBH!w2mgB(=@}Sz2GBr-&f7lg@$-J->RXjS3S_RI=tZh%p zVCkW(KijoYx^X0PpZS#I?Vuwmn5WRL?$P3k;I}S+U*(**`tODA#}~RAg#Gs-cOuLQ zq}KF`G%39DVQc-SS^O{cqDFgbx0+Yu8bpmIq&!7Gc=%yO&4AFgTusqer4v?D7X{b> z#$-ahe~MfU;dgULqZEoXMiZAi2)p%uX6=I-ld_;!J{P_JorY&M@$u8+I*i?Ic!7Np zK79u${5>p?N4H2;er6lP5$mPAsnUCN%PbYzj*Qukk4PvqM_@`N>1yBRqwz*JBzysE zD9Xgf#JmTVtDIbB-Wn`Nn#tfJ9-WudT6|I|f4>g?in=Fw;S;vS-LT%>s;o>W=k#d1_x-7Y~>y8d0Pb@8Ha^##j1f~qn$b2 z%?{$Km*YLp@G4eF%rc@&ujd}k!T@}dP9H{drq+iKk2D+H^~wfcsEs7>{$)bg1|Fuy ze|%*V7#Lz;HC}~bi3j{-wngJ5WX&hbqkpfm!*++6iWBph9%s=iAF|jGofBa$O!+0; zmWtr5L^uKWI9mhH1qQoep9tC=oNA&z4_}?Geo4fB;W@EZR{1l#B1?)ngc@g%p0Ob( z#9r5?>a^~1(UfgUR&-`VjfRT$yneDJSYI z=LIBru6W_YSl(##`q62ce%=J?boDN^(xa^vx25|&KjQv|bnU)2`06?}{#>qYWEELu zSr1mLLp}_ScdKI0&jSM;ZjpH^hyq!b007imWRy-0$zdgqMA){gow328IPp1kf5T5& zL=k!f#n3G_+PLO!%WWCcS*^i#`kVw`QSrp2tMijB@i~X!M>uq8AbIKg^cbH`aL%ug zNyw?V>&o-{P}BE&qG)%6et~WIEx+gYdc7NoT)+1R{g|t2<=*)zzP8t{5Y+HDWQT7W zJ`j2C#`&0tep#IUKxz6*v(JY_e^{Q^=FsBW$)uMnd&>g!RZMMLk=)xG(~wt{iLw(N z_l+It#WPE(j42B;aicDWez?RAVQE#>K1s&Q7MV@japUeo@ef+uU3Dh?JNB*S%{S!q zWhT7;v=71eQ9aD>+2lK&7cS<+Sf>wsPSRg~IWip{(Ea(!2Qf37$`1A8I& zX4P+%vZqiBeEISRw3x^<^5hMd3H|G)m;XfOaf~WJJWTR@QU7VXU_!P zUU_&FqSxbor^W6eG$UW^f2TewIKKmXy)pH%CGu7AqCXZ=dvqU6oH+85wo-4ptrrL- z#p9-jZQ@l0bHlB9(8miDvt~84&ym9r+3#jnYnf0%58PNcL}LKRxzGxjv{>fB_<(Bi zV3$0zDqfN81>5R;*2nGw0bpMN0er>AZ}aOh_}woPQO`YH9%7o>f7BC_72VC!QBvGv zW<5j)a}u{v6Q@-CAg?iuvxjJF(rl&9R0A6sTT85ZaXhw58C|h{1wc?V zLAQEhDoTzNh79ki91d4{k%nRkD*>N^`=uUC1~u;$zzUmf05e5znH z&kC#FUgT77`_TP5Ea=NZtgnRhRZza;p+5-Qrc)f|>pU<*e^$seEYJxki;x>FB+%s7 zphooMEgnPdK`O@>I+}^3iPu2f%tk6a*9mi2;Rda!3yl(3+}LKLwGdVGGgLkgx+_6B z9h?%#KVO82FWMx0N#pAY_Z{Si|I#V4@rlCj^-3%qGB`raWjkn6P8MuMvpfguF}yN! zjcl@Q@JLx6e=ygauExwpSb9^VhIZS)EeJ|G@XR=sHogbVru1(8ObFqXnn(V<9-nmi z=ko;rGQ9l-3tyDtci4Bkli0tGgF6aOBxD7oY+Gfu;86Su1YBxQCX{Lf2R(=+a2>3Z zV%ZZ6RBFb_G?1d2bGXXZtT=2bhlPa3NlhcS39Q9ceT$gtV?V2YWlS57kVS=Tl~zNZU0PNo%7GWBAE1@-_KS?(y{p zNB0K3e-G1#KEU2C-g52@_@{Bt-Ez41AjU|i=$Og0*sqKo%=t*oided1WO6W6^uWmX zW)l?CB(K*Dv(qAV5SpVP9cVQiMnh)i1ftGTD5K1$gVA%fxmV+Oq0;5_3>|kLGwwcx z9z=FMm)rEUq6@X2<^EQJ+TVXJUE(oe<|mv!f173%fk@*VIJ#`PIH-n}*UZP&US9fD z2>9k`F9ZBYo^X(>Y)mM2h=a&Ay=~6glXx~JGBn1=+;U(rWwQqs_;YD7`Gq;%b}4yh zujq93E>nuAYolp<4~C$vcz=pJzF7Mok_Wx01M%zI$AP?rCO<@4j#IAHqVS^5KG^N2 ze~g(Ey!FMdzZpM#W=Z?G^~~(sUKd}@9}^m{+|MwVl-LzTN8)N^plf^54q?e=Igfy- z24{hVx8F3`U|v|fE-I^#*o!Van^JfB*U0TOOA0SE)RS`RiGKaMyYod}Tcyc~d?N zF#UpVc|%T(qUaaaYrlfu{7*B9`mYi8{57ii@t&*Xu@gkgyKtpjM&Y^1<~e3be!L+~ ziqg11#gkq0Tja*oeq@=R*3T*{zv=_r`NQz-SWB8)9Z1QEMPl87*SKE#E3tpke;4BY za<$@DlpMMEt4w~CuJvnPHRZ3;(xU(Nt9FxLwHJoJbn9279p}UG-*J+iZcFo|)9b&S zr2E7^E@E14{`G5O|7w%!|1f`rUSM^P3@j`CRTbS$FYo+sztU58v|mN-m4^HBV(ny= zgl+%jRmb0Sd~WbdD}UXX!lo8`e^!_$7N8#=R(1@(r&DgtX0gqz8XS!Uu->lac_x7~ zoGL956H*~rufg2qCC^eVk1u+QNLn@CRA{v--VR}YgxgERJkYMn)2TiZ*=d7_OK1-~ zwN76s)6I{|;VCN;zL$>CUA{X#lDNM=%B=Y?&QM#W=d~Zd6=}UiL1o*ne<^1hYTAtU zP-ldeJfOF|pzV1i%nmrr3RaXDVBok+Wu_gLC?^b8X5lxhu_DqX+d7dG!3SZsVF0Q) z7)2!ap4vIQ9_@*YSLgk%OW(ah4(xu&t!shRFH)_3aL4fE9|l%?Su;Nt?7Nn(#ZLcH z-*j7_)UJ2gy?}4Wy}v}`e|(IA)dpUQ$^wp+L(#&Tkdm6@>8>|0!@~&U92b*0Z8#9U z)tGIWl{cNX4Oq1pv{cPj(MyV(R~o8L4{P4g5b?mTP2{N_l2=4Nv+w2X6ejt6+n4xi z+xPe!(=tLW*G@@J$5p@lvPSNUMlR^NcOw4M%H=Uqy9Ai8g&wk)e~&6{$gE}-S&sn2 zRH~jS?joH?rzN?A3(MTz(M%!+=KrXAaEUg#5dAqCi39{@E7(f28Bm$1jD6p@n_DJrcv#s_@0#|Ip*;B0JUO2A zoI%7lBWS+>cD`)QeRlIyW2ZM}UPR=4^4&1*8&kit5%q)d^~YJmt=73CO^F;eiygnl z$9|OB5)X+4?hc3@DcCYJ21m(3>LnvM7~>n;BBv~Vf2i|7Z#?JVEjTZxr#**W_|$+s znk1$4l<9pT;)OBYVIRNC7W`|rScP%Tl;2vwz24GYdilZ+EME`qDFQ1Um>ND0K~OxG zjd7x5!n`cvaxOp`Jzq0kIs{fhy&JhLljkf-$YKLPM}rU6b>A_N-GP>Q1zKToiLv@9 zp$@?2f5~}q9Qm2_$Aj+{{Z~bqL=OR+--MB%d~76UAZPZZf*xP=IZnm z_x`-v@Tzd{CWTC-ujRS+V17kMq&&W~sDys?e{YrY{}3K%X)dX#r6AHnr$n9R={6(j z&}eooBGU-86s%#}(xX-?iQ$GbC3d;P!t{_>STtg{yR~BVl8oer`(QBjQ%`T%4LW^> z_AeCbYNIZP{m!2KJd|;|dY|TcUVYpM{j|grITf|MWdC(ke`nx#*K_S$1bXAziDhXL zf1ZOTH@|*fdR~6~hl@vVA%3k?`rD15+z;`an#H~nZnlA1Y$oGSEGmVAdPrpk+tCCi zXTIm!u}hA2ixIq(2T5;n7|(`9D1rDiOkwfIL}ND^#(>G)YJAkgoX2ZQqLMG5*zoa^1;g(&r~~OSbeXz*n7GEX~N}) zb-P#tGLhG7I~3ZP0kJE_8v5W|syLpfjC~axN#qu8jyt%4ndHGm4cJ-wTUg(Lf2L}U zXNmzWMi7B6;BvF)UHeJ(@-oab?Ox7KLif31N4{3<7q##+I_86t_-CnsRMFSdnaQp; z6d5k(Q_B#VM724*=|v*EUM6HNN4wrKYYe@m5ZAWSeB?}40Kh459CEa)Sd)oR%kU`c zIV>dw?kaeeFGu=acyc`Yz7nv`f6mxXi*hXiDvvOTYRo3PI@R~fdWqS5kK6`k0eLAw z!K=ms+a@+t+03BXpa_gHp@)WL2BO;A8BQ~%qL~Vfa%rqnOnvacXAI}d5YHs*j=nF# zc2`bP{u79{ZPyc=Vyi8nq$SBKGiS#YlQoHJW#k1_B1W{GL{SOOww$+=e;580z3~65Z}x-)eeu8JI1^J-*GJRGjBQp2>DT{=VvTJuBC8!)^Zwv>U0wz|M_I z$57oGnw7)(3~hQrvvS$YkEH>!EQ!T_xe~di_2x7c?uDFljW9qA2HRMamJRGCvZb^~ z$4WiOSz%Z`NxRo^p2_!mfBMb-^}iWTk15gD?to_v`p;65h`ueLTx1ZWN=Pl6+WZ2U zDhtZfotoT5aBnu)4|$fG!jjY5dn<`ZFd^Dw(eC)G=e?1%)hJO>F?mX@R6Kwd%7K8N zRFYR@I`L%Gx1&K`3+G|f9$!^n*x!xD0*Hk zA(&QTQ(B*`SK#b0f{fVLX-;vo!*Vs5!_(9vQ_0dN?C42jvm4@>a;I&!Z_oTfi_cqi zkBRJy_jhsUJ}mmKmcGOfZ^poH#@Lo_F_a-C(hrur4_%~Gh#ebc=%$viXo!?m89hndGcnHOJsY27RViGb-ZnN@P&LoGuRJR5#r=%*sThk=5I3%N}80FK<8P$vhWR0C-LTDGZ z0BtZ9@HQnVM69kXiP2`Fy1-MY;J;7mj=`V9sX%X=mw!l>m-+eFRDY?+eJ~e-T?c#K z&laOKwQI%Df8Aovfo^DP0#a5AqO*D;rNs`2W+N+{dTTr8G2BnEsldoA(Y6aXn8`Xm zD@Q#UGh87cs%()xX;+>GdYQ)EVZW=9B<@i3c=gXIdOa)eidx7Fv8`bUtvMQ&TCpjc zc!vnmkgK$WCC7Y`rdf|QTz&?x(U=FAh)oyVoFj#ue}!>@P^PlClEqzZ&%yGr^~DZq z_8HQ={^G6*w^)UP9m#w$}~k@zspK=~{jaQFTkrjoV7^jFaA}m|KtO zu3x>W3x@qUn);&_#CWHmIO;$$M`#YRC0y0FNhw)Z)M%0gp-5#%c?eNaQ#5@wnr;V+ zIlw~Oe;{<#ncEYWh%F3S8IHw(PUN04+u_0fslHP$+`3GT?(hU;f3KhN{ffK~&i+r^ zUUsyS6n!p4MWkeSGu@C3G*|;p4d`{w*>knYC-WX>@0|FMt+;gHixDrl#cJ)V;xyig z=59M!!eooKmd+nCh7X(YS<1bP^FqCs)9?HGf6r;=zhBvR{{*3xwb`($#}Oik8&Aq$ zY8W8aENSW;J4-qI;D*RZr;;hLi6AGj`37f0Xrf~W7@4nGcQl}iaY(d1^Dwh8x`rt# zA3xQ9?v_SJ?k3eBUnJGM>4!hnS^rc|ZIki-(blqjT^3g8#L=UT!}w{i&F9K`JB@XE zf1Hp6S`mW4Pn2RZlhmG=s(`Z!NFnpn6+{6B&TV0nrU+s;2g6_qKPl`Tea-|wY07Uq z8t?V(V`P8P;h*ds-2^IBr!LO&1vgyzNxI2)LwD2`OCwR`J%F_{alYFJONW(S$8$$D@wk`Y`&>8uAlyI-{R+e zi+l8aQh7YMyKYUE7GOzeKriczXd90>pa!0?str%JW5Z9BAts8;7F}h7^&S}j1C+=@ z#+bvaS>UY>OXaYSi)y%Q>@(u&Y zv6f&PE*7BXL!0%JtNCe+Z(@~yKl1l(-}$NN>xVt^FZQ=Cn==nND4#eV|ANRbmGXPr zFJuWX9T0Ew8H%7ZsI=xRC#F_Ce;gv49)Juafh#n1C62*ER33G)dVD=5V6$NaCVtdR zkAsPpn;oPr_QM(a2RYXtHv&Jgp8h~^UfFz+p4ap5iclASzGa>KaR$|+%O^4zipyCs z*!jE*b~W|#AkJ50Z6IEvB7j8!2S?}UNg9vfn$D(d(zJD5vmv$G?xjLCe`B$s0C8z8 zlhF>zNB&0h-KPwdx1o1LeLFjMIQu+t<$U=bQU4Si`GmOg=hNX~SSnmu?SM%`Z-Es) zq-M#E(^%9f(?JvUc37|l(!*AxIca3I8#nQrFpqjD-r^)j;cgDDjG8Tqq?vmRyPht6 zd$9wg?cMC>-?Ng7Dm|srfB%-`ak~2B$;I!1n$f?|J4WZ}>Ro4%c=n@zSSi$hRE zD+dLH+yG%fp1u z;~K7;nCh)1!9hw=1N5itm-FvEjsA4hMf2}s{N8Vob}47*U!V*glWe`}5`Uv#+xGol zfBE65eeYZoh5b~su>V>F6J-k^tF^#_4Z<(BrX)4&bh=k1Zi6n`MY1lMa;FhwSWn7Y zT1uR2!s|gJ7KXM0wkw;gXmvvQ)>Iq~H>?W~qmm)jr_|hgGM|LvJ>gF<*>{quzwj7t zvGIRd;28=}fwTf}yB!)7w11Kgz&KO3i;b6txm_9x?Cj`4Mhs$SPV|BrwayyOzaiq3iR*4xrbgG#d@G?NN!&d}yuaLd(ddFU+wagg*>_B+$+Lb)sETs}F01bx1NihqH$ae3OKW$v%N zX`zRqU;lNJ${JPLENKC$(ZW3(-r*^c=5n4?_xL+#M^(>(bKrTq#2 z=3mFAc~7v^ZIb0kDmpRQ*sZjX*g4^FS}@!CZo#?Ac!k&s<;~DZK$W1!)DXJ#JT`)L z76A=RRj~{ll@x{0JZ2Ao;rhwB^^T}#cD?)PyJ5tSO-jGPG=K1~hkc)2D>KC09zfb` z2ujTi<;OYC4b_lf%>rVmh&7F5qt$9WV+fgX)45YtlL@R*fkSA-Qbr-aS;#wxNYRZs zjs-42gy$^8>+zrHd;P&_QrrXL|BAZlH;sVD8@)P2%;5{%Y#A?^CJsf7(|nxLVT#sKn!W59nxUP%KLS)WT~Q<e&!pp)6~)5rt`^jj>gq0)I(7kNHHu?%+hhd#zu;T0D=B zPud@l!7G2|_+$Te*>@*F5Imy*bnQBCBJJIxHe5`{)0L)9CYl7*s5h18$sBZJVM~lt z1Bgim&18C7lgn(RsQ({(Z{DN2lI@9pm96^op*$Y&DD~}k!QcsO12$m0)%E0g!1I7H zE!Ag#W`D>`$RtTNxvT14sim_|dJG(Et%z8$hTn2=s~&jCnB5Jt#xYjb%-K?K)SqOb zM(z4yo6YB*@m#Xbi9SbiACF$3yKY73JGY`!9kMc`z_flw^9Kige>vLr7?LWfi3F`D z0@ILglLPXa*G#TkhBSP-ga+MOHnL5B3=h~jCVyh0*+)u-phH|i2eX#Q)e$wRYmT*{ z;R38y&x!3@$B)E(J39*S*J=g)ty*zx=}eg4O~_L~k*nufqHO8WKoM(JM2mFR$U}zc z7j#)mJwa7j66lMNLz@ZGShKLIwxKW`&(=J+V3wLUALAC|$^A;)nK{J>_;Ygh){S@a z9DmM^4wZW|eJ%+oI1nTFVF5%taNU=(n=QNjfK&ct z*d6LqK;2gE3~zFZPC8?pJnj}9ht6yQm4C&kKLd#r4iOg|?~*pk7bQ9*D?YR`vVt}R z3+Ib~peVc4+{Ph!PoCBVJzO_AQcF>g?cELPvf{R(Ck648ZgWMLBdIShyujpN*uAyo zc>6Bli{Ge@aE*WcMWO6JGb^q)#X2LI1u{|H38U?$X{^-DDu9-2+HJ}{jwhsLp?@e) znb@e#TN=x(IXW-{cHFfv$~1C|rG{L*TbK>DWdIn*4G{z zwN)ENvq{1+gjz2cV!e%rGh$Z~GaPDr@?;`oy1Gus-n4dRyK0m!#{rA4H?}id7ju*! zW@F4qSGlXZ{Bpht)#j<*N83Zkqkrwo!DoCAYZJOJXG!^Tz~OKnj2rpS$O$aJ@|@=I z2MDqkt830W`xpLD6@>>ioe1$PIjw!m<0gx3+mhr$TuI(e4FYT2gF`(PV?&H}j8_B3 z2!|3saLNrN4I{o5&<$+$oZ4pW>28#(^^BTuA-2h$(#qe6c;Ly~$qRY_JAb=lHpA|V zV`^1JkUSU);gs(nAnu9Y*3lCo!wyitdC=SSw?z3?o%;Bdff2Pra?&rgwZrU#a z86UKZT_YQuDyd(2Azp95AAeDjoL%LdVcI^GX4!j2mEi2yI>4CD}|cCWD;88dYg~kgSdO zv6xL%NiLsSJ1rph?-?Y0~zSH$B4L|nt|2-Sz{^P-Pf`;1+iZ;kAAX%Z~`6QobXpe2KFUJzQya%e z#@^Fj-&yg$-dJ(vJ%4@`juSsFnE#lm^b&pN-R1nh@A65Sj+J4EHK0@)-mtONWM+$? zl?X0PrkRM5R@%e$!gOa$VP~_II9!GxTxJ0_ghafd{OLTHB?u+6z@Fc#4T^a&``kQx zNy!tRE-t(r$IBf)^}plO`{4S?fc*oy$E`et-yEd}PG9utcYg<`%kfU}^T z^n0{Fkf+9zoIiHp#P{RjF(=?&5;uH5ktOXEo$WVM@IT*i6{l7AC6RIdp5J8Xzm9Mq2EtbRURwg^Zcqbhc90M8ngN5`QrAv{SN}A>y%X!(=5xh}W^d z-M~OJW8GvJC^#{APK7#r{*gV0gBR#vY`B9$e7yZFdtRJfc0vvQ!(6(VwUuTohRRkl zw4AUwx|*;|J*RUSV(QWf!97B^^Ucg|!XSb@k=ypBVwn#kGnMukPwSJFLZZxuYS+9u zlx=!UFMrn@{M1(ZvhVLCJ)OTC>{R!R*5G$KNGG7(oS7AAl|HzgFrw(i47%SovMg#} zd?(6IQ1mCq`xU(70Y$bb(zn|mradps?DL&JR3PCOcniz*b`cB_NV>IWzOd8=6s9X* zACtkHjTwx{2P7XT!q82n_uTPykqM9?0xKzwBb z?4V_v=7!z!NQ#qkGlpzFu9)&Eo_MXDCd+o!KGN;31B;UR2 z-hcN2I}VWfMj04M6WG;vC8loCh9d|?)7KWM1{%AVBS7Cmi!gM(P3TYCH5}Cp=R3@J zVTyB1((qPkO(T)Bk*XU+i?5b5Qj15HX7rpkM=S#mruq6A$+_-HN1^KrFW_B=7Wj=r z%W+=ZpeUa~LtG0@Oium9GVQIG#zO3E7k~9w$r*y=JU#PQvbeEn&oQx52F8d~j5ram z5P}*wjm)YRMiG(Yj)=*`EzqroL1r45#W2ho1aaH;L(-wVE&xL$FwUIgSkvd{CcJ%dF8R~dVjn# zK!N~-YJx7uZOSt=i{aL2tZsC98%V)qLFtGZbgSZy>+@sE)5?B2+4Tm&em;Cku)~9H z*Zu#PdI;UmJ%sKG@gW1@P!OS;OfA1iDkNm#2<0|BWxm?aX@xlaU@*X*d(>yL{ey|6jks3Zlw14+*9BB1^ zc2J-01;s<)H&OA&w#t`tUAvzIT0DxryJ-c87`s8zsJfY?B;764l_bs>7$h>*7HfrF`gEa1R=V##qWOh=OGW-RIk_+rV# zHc%)dw^!5^P#VD~n2(*6B6@?EAKL?I_Y@}HTh9;ld_6vDfcJ**eSn5zJJ%b~yX7O& zZf6k%r`)x_A8L<07Djt>^eaQ7P8&$+qZ+W*g-wu>VIY7yx(iu0=6?}24s|JqBEVMn z{iuM;MMB^S-@@Rc&T`p@XgKb(>`cxG+2>|+zTo>;i^+$4nWyO8(dL!h-QiKQgzh3f zb+;d&_lNw}OS(S5hthC6E!V7F_hb+1#QETKfPejuU;T%DEPqfhKwN5rlTGOh_Fl-~ zf(5aBUGQThNTYn}Q-4dgCj~X%X`WKY(SU%`WRhyOg*?R%}hd9a}#s;yjF z6V6Xu*m}T^wphcmob2ST{Kvr>aaE7=Pyqu&PAOQQthiR1Zh~XG{{a8Ouf1!p-}H}D zmC8%I?l}wcZ-4(lx?j6Cjl6dMqE5{}(2sQ}n^VJGSMfg+ya0iZ!}ICXh6`m)9JNdW zE-4$AS&3v+vX6kPE8u;RgZREM0Q3+IteJL?}~ikEJ=+)U{baSC`B4H z-2iL!86&_Ot&Sr2N;j+eMTwx{xG)hO< z%*qLIOeH>HBZCr}dp@{F8LuBM7F-^Lb74@n{pok@ttx1oaEpqf_EN{U-%ww z9`X%to|E`Sw;0+l=ih#e+g@Qz=oZoHE0bIyK<$1c9Wz<-J&bdwKkCmo5CkWFBToN&<&D>jUx zj5pA3NloA}q`}&Alj1dnkE8ec(xFBWaW}^6aQiM7^ExOVb$lQ27*3-4%01}MELy~0 z560ZJp_t5yK7;~vC?k8WT<$cl)|Rsw>_FlKvGFmkOVgm$D9faWfg-frs0TOO7;dm$ zrhfyeEevpLu?hWhW=2&rl#1zd%sCP6$duFJF$&@9YL3UdcUkdZ4CKi{lh(g*d7?qr*q+;a0?ZPC|JXw&l>NLJW8mWqXK60?5{6cMyjnt_-TR6;1sSJ1AyzDiFfaE;OcuJKH_Wn15pj zPTV;jb}Jfs7XbtP#xnI@G5(*QmDcm2XfvJox}a!b>Ore<#)}Lt>ipWOEqFBA0C}0q z%3?uDYA_9*Ib=gXZ5K<%&xPI68$``S)IrOI>)c3CW?IWn`JdmqeWc|1_=jPTT{}UM zCh_;Rw*E^4CG3_}I@<`IXYN5^)_=|dZe2i=3^ANFAC7cP7PiZUAH<=pd*z}YWf6@6 zCFxrp-KgXzBCtj@Q@~>bs zjux3X8SWH-TEsIcYB_nHiD5679c^p1F;FWYzk^LdiYC)aZXrFM)~lU7o_|c*v;ai3 z^|u+I2Mm;s`=hZY$n;b4d+x)5Fz3S`W>$91Bv(d@&rZPe6My@C7hqNtXY0hxY=v8- zws|Za7N|UjR+S_tVvG%VSat^^+h4`^7Gk zlhtSx(rJOCa=3Ez^=u9NQGZM8fgC6LC+X3~O58QE68DLp7B3(DQD0iL%M$`)P@X_@ z!S{*j39}q-)2tZh4lR!>SRa;D5v0W=N%}gRg+13MH!+9J({+OYz-ZbFtntvbz$KpZ zR8Z!HQcjexL0pk;;cQJxxm=A1>op?`Yy5nkYcEZ`K? z&l86qROOt0vT!|jsUwBROyNH4Ej<;)=3?7Adk~q}AzjeDsj^5VH7+ENo{T(XNZDn$ zCdP?^kxp)FOuWeWCYY@>v7n4~|0$w=BEf;YpDz9|Cg)Q7{+G~KcTpCe>?(K{DqEA^ z&R~5#9dp!ffQ3DO5Pu7ME^4zD*;zvm+D=x8kwA&Rn zXw8kWXd?kwXh~FEx*Kfj3v8yU?r^FqIm=VBhNyK*AZ z<&}<9_+3)r-;pXc!$b11QKj*N@y)mPivQA$9?f_x>=XNm7{p{0hPZ7c8+vAO6gMHM z3^n84q0*kLIWs>q7_{e<#Z(#vK9?3rZ{}b$!|&I=I!~+F7Vn3O5!vdK?71Z0i9MHB zei~PFsOwz4RDWrCNE7p_N9*x@7h&18+0nIYvJ9;ABk*6?GNf16<{C{C;zf+oZ5M2d2e^xQyo;k*v zy`KI7w%jrybnNGnvNEDbwu&ID9+_EVuP@Dn72>OH(`4yrSNG?(f$pfuJm_sHMb8PD zVd&aV_x@bB2RtM;8b$p0lMlDL`}RrOD4HO7sL}JI-?!-&j$@XOed+77 z-?thCNxgf3PH)dW*kWMx;fEfK(lqfNY%~tC5<{Hmp@$!llhcz$P+HpupDp_&xO?cy z?`fYn)niQRRMzCNC(AlX%Lkt8M6M4%#Lm-`4}Y-p^x*YRf0}_Fo}f={zPuj2oEp3r z1^xDW_a@_)#wf3XK>p1$@~s(n3eZtEOBLz--*e1*fmU z-19YL>#abr?`&;t)Z}x39E-x71Bci`NNsYbAT(Q5DJDH-kGi1Q1uG7xe}EsB5gFgw zq<{X56?VN0;XK+I^GZTIY?h-IOfrhKW=5?|1l5$-D^ZYnV`|^$opKNa(j={Hf?r~3 zHSEs@l*eGZ4ZmzxzL^SguhSQS@Hxff)axTPPlrD&gL7ymb6)%^{w~z=CQf1?*O`t; zPR%q0Mnnwfv@x(LIbGmWd&x*Lr318_D1SSva5m;PFFbp>i+D-Mg3y@gt8l;<>w+l( zjBg1^sN0ZyF2eu48)w>_&yM@_d&nc-82L^uom9G$$Lk8%@>`ekztb?SRGB66eRZ(k z^_iR~*NZ)Gtc-9`g7s=QfVIKKHlZ+vwasjgFqq>l=|MZ_C+M~sbGqZsHt5)oag7m*>75B|MT?LOUIwM zPCBBWn|XOhH$gsvXncO>)Zy&>ICkw}JwJYKxOl~`fv)2qUY|N;n-G7Y_=i^fIyREV zPY?F)L)2X22Htx$n!leT9=?9#Y=3zO;zIz+IW759df$H@SP%s!;Ff#WZl^8_rxVt@NDajUQdUpP$$UWl*KX%f@NT^M8*bA3gM0 zr-mQzksrmwDf8MX+P}Se=Rf?yN0gn5o32q-FGn!^S8UBw5xJ|Mc7u-TPA7Nmi)C28 zbLPbdzPpF?&-z3N^dljTP*<5zoKa8A9wu3ipzyYt&4DRQ>S`IA3HTtuneb?2{+9-i|yV_eZQ4w4{# zEm-sSQ@!$w+lkQ)IwLyFKL@Vdrs{bsU7f1p{QXda_Nw29?qn~A0Dqs`5>Yw3>ZN)M z&H5t9w3}E9$1rk zu?o)CIj-vGMd+brynoHorXB6Fm8@kOwIJ8CEj%0OR3!!(By7=%vIb?UF4QeR22y4f zEIBjs^~-rx z>SUs-f@|yj7mN3eSMH`--c()Xb%YM-`FcDHpb$~QmB>|lZGVN^x~lh@8OC9{UksU! zd~;u4ERAs#_+zqOEH;+8NJJCImcnF2_lw?IiA9TMXZqnk{-=hZ%cLUb&U^$9fKYA!0<#M%Va>kR(;WloO+ znE0m-;7??DNPqO=rRCyt23Eoi&Vne)Z@`EdhNd@4++v%DzUq?|yqgL#73wRiAr~gJ z!pfDXN?_&pL(h@$eH5){ins`(KEEarCAk|@{6T#^Wk_5~AaPrdxqI{0DEy!Q@#`qe zzy8N56#h(m@Z%bJ{mvKZOy5UFf9%O)fzgljDpH(QFk z07q#;-~q;VCe>;xo0^B{Tp!^v3Z(GToFbZ(!Oo6poCMd)dYDi4OJB9YT$uMUxE(9~ zeXpK16Mx48Knhl==WJ>3Jbagv<7u}J-*cW5-#X84?MEK7aQv6lmzvD#O;smogP`2a zqVZTQ)2g>fTk zwU(C{MdMATw@s3{wjF~p8pj(8QYO7*H^{OLH5IriXy}M-Mk`888QJ&a-ex~W^kusl zrhkI>RK!=u)lP!C|Gyj!BVR8Gr`x|NGv*=f<^(BKK3qn%+ryudP2TsOpH#VSdF{4) zFXsx=7jW+15k8>m89SoAf>m)A-i|o{-DP_a-^=U}1eL7zazECpRV+vt3K20?FN9Rm z=*S)qBhZ5gymr&QZLr>YVrjIALHToApMMXe>6q~0$|>&So4}9nH$%XGg@yaS-;{4l z6%0vuqzL{<=&58pL>RR<(hI$!mi%lAG?As`ySCV&+*SfPt>UJ3Rm_=r)*}*B2E9gJ z#ga>k^CA=^&zziOfU@l@~0~OoLOB7|@A=28^xK^jCh0>PhNOdns7`KuUWgON31QiuB z;b)5AnQpvAo*BLugzC6*ads?C_cc?n?|?PFV2bf5a_AA4)_qN(>VE7QO!R+mnMhwk z=SwRjqNp$#kPO!TYzYrx5aFR+GJkWX#AY6^(n2oVo*afyxgk8R^)YLiT1?C$Q*c@P z1HSF~ys#J`vDf{hpV5%dbU*XAJ9_B^6~2q>bGrYV{QW|=`9JrFv@=bLMQuu3q6#s! z#=c+sILi|RtJdp&UUGd6+)Fw=arH54W$S=l0{m{j!C-P&l!gh!Lcq?efPZX+S&u-_ z#mwoio|&I+eSAl;TbEysy4oENJ@SBY@0xU%ChA92i%|AtP>XDBuuqfC?*s#~^0mAOZh9VC(B1Cf= zu0MTdvUww2$D+66m(%=xFR10a<>c(~dnC)+N{iEwApZK`Iy>I#c7Ij7vy#_;+yQw* zFaPhS4W>n{8}uej3niob@Ma`)D1f7@pkW-n>kPxRu$W4+*PoMS(nLu-iV=32Y$9+m z?kBw@9H|Sij~g1)+o6OG>}k7s&S-Ps=TQ$l9K4WsORbRKlv+77(#-{z;&H=2as=%7 z+go(Q@$}#nxe|b_5q|}?QC&RudgY5qh~EvYdNk{YscE{wmziz!JS6MQ*=@207pjcnYL$E zbw|fIl&^V(e4F3%>YsmT)AnIk_WQn$6Oz|sM7InwYe+YZX@6@n+tO3jA5NAN64bX$ zU`tbA6JvS{bvq+OXj}A^9NJ8Ql%l-^ z7BzKTsA8qfEEs5tzH76}g!p(7IK2MkGScA|9YR zxm*HMl^o&pT8>wwJkWO7b8`1ut>Jw1)BNjC$x%;a9z7TUY;aSQ-r=#)tc;FmMg$L& z@fZojZGQ^oV`VojW*m@$=oH^$UW&U^1M%2+9-9+{j(K<>;~Q^91Cz17uHB()Q;}fJ zo+9Eo*XWR4bjY8+XK?%m)8|kK%!z}la*F;pbi73p!mHV#PBoK+_BER=;I^z6>v5qp zek#H$p^m4Es8}>v>^d48hV@cJDq}8`%jsmfN`IjVGTQ1<56nEbP1};)ZR73usnFlc zjvQ!nd462R@4;7r_u;Fy=dxx$LSrHR+C3N;FmHOWyP2DV>3)u{KtTh!+)h+ca+GS; z?Rz4T+~b3R(NLH2MuU48kRS>_cb65MX)5pCyyK`{Mr|7~{l${$Ko=Ti1 zdJRi`yt3ZEU=x3_cRw-scQJy9TF!JmwSNZsVmO#_V@~A5{xl`omI$0W0l3RNDPn z_1MWkT|K=i;0Ves9|XGGkiWXqT}@W z=*a7@%{AzECbiGJz8>1YyQDVB2-e5Fs3*@TpiX6Qt2$$H3iZIIHiUczFnFq45WJlhkbh_P!w3(Wx$iaEz+|48{x7=GQS0LDi0c0Ja@zlH zIo)+)bim>2nllY+le+FRn#X*nDJylksaxFTxHPEdn_0u8$&jG?dPq29iI8)T)VF}S z;qV1N*r$!-AYMU0tg!*?rIHO`9bdU~FAc(QCPqvBsqMh$?i{C)Pgj3xyMNtf-4{LR zQTm8&e3gT7%=~z9BcoG}F7OC0o-k9py?`Qy4kpX1#mP!LmL%1S9dvU-8B}k!P4q*a z#`(F|NR1^6M!Wm$m(PV?65QhGS0{d|2S#a7k_t-tMx_kKIkLRv)i%If2Gtls6r=U7w8RNC?t0Gafh7y z^HZNbEp{qDwO{%ELFGu3gE|qEzH_F_hbWvQ!(YjA`1Jnu-9zE_^dj%&h=70f3t5rw z+84vNi#O8Zm-qAImtmjsjS3^_I(LD64SmGEK4MM2qu_tS#xBpbiGRUQDI{H!+k+-& zR434j?wdd4Oq5P_tgv+u($qZ*{D(%m*Xz=GW6gEG!@=|)yb`_Z-PLG2?Q-8zHa=y0 zY>iU}&zWC;V4?r@m1W>=5Y@H0{Z25|ExY`AZhA-+fC)DD1bq+(K1A&+GHLO=z?==7 z+cu5RR%8nM#-|qJA%7;(F54^AK~jrUlR`j4Z;Q(G%K6AdHnORbwnIj=S*XdAGxy=E zPqX#msM{yP_onF6?eFL4YjxmlhWnQoDX>w(CD{&_d3Z4C_b6pK26O@3u9c;k>XhT{ zd#pVr}5+T ze#a~JO_snzYd0?pSlZ7%Fg)uP32ewBKSI%Iz5=3jll&tVnmB1@(?&HFD$AETN7Jrkl|MJ2+ zb1)&)3UcTu-tt3SAFp;b+2;zO4TO@Rz^D*-VZC2?J8aG5b=zrg`pWD> zTc7dN#Eoa7sK&cQ+AM~}b6Cx{3h(@^w)agEe^3=35aF*iou4W+KT{0uu8nn)1sAH{ zpF#|KP^_?_NLB@ln^`~KCdH!Nm&1L}>qq)d5p~5}Nq_Ko1yM4%=B$xvG}_A6wu~DI zV}P8`Kp)Jsh#CzkCDaTu(f7hAYj@A=r7pSMN!I1*5%un#1-JkE6XQonw}+-TCCe~y zhH*=Nq|{OlpgLjM-5j-0vK_2zSB+Uf21PHgAp6PFh?jA%AjB$ z*MC#3)$^$!0XZW!5ZK6Daz3JF?p}3Ux{wS?meYlskD-YRm8g+m7?1Kgg~6iJFuiz! zkfB6XbLT0y&o$A{yu7$_G`ifc8G3&bxo-bDaxTsu<8oTQbkEwsJDT$!+2?_OJzT&h zTu^+JhLc3^cf0dSkwTlx(?vZSPlj7jwSO2b-%Hqjt*gYuos6@D(a_NKJbvCEhm*P{ zMu^ET3b5^28c)ZX$;B%348uA)9D?QI?Byi1cQyp?GJTUZpHr6Z2H(2>lTWU{qb&$Nzx=@x z76(?5z9&&3f8E4I{k}xbqZ31_!bLrx=Zy*R)xVXcX#ia7 zgCZh;l}Vv~YI}tJG`TK5{FO)-myT-hz0ynI-IU2=b9E7Xqxim?TL0JE2)#lR_%e6( zv%&S22lnt82RVLw?E=r>=I?xI-hIfx`}yb2NB{fo>@uj_6hfrHlz+EqGsuwDS}bX8 z3=J%QzEHNLHDuOf1g*{2gZYo@_1XH8Usn7RXejDrDbIBC1 zTzi#=L&9`}yPWftQs7{*4hX*%W>vED z@J(TaGky>zV+IHUNq@$J-kz$~jJ_ZlP#jK7b>d?x8Iwz{)Koykw%cblmi$b){E*^G z-p4j4@9rC2qjg>y$1GE8<2;zL`1bgVU34N@yz0N`pD8JK2+n$Ny`=Ug6*ZAMsDs6P z5G`9bpLyF^Dozw@qs$n$(N~CW5B!xigCn1uEO)#In<%px$eqgzFGsMPuPdUSZr@{5;MK)PL&?`c)L$Uu9mM?xfbSDk|Nfo9 zE3IMIb9ynEs+z=j;{+S%MoofY-}7Xt4RUn}(j>3Dj;i^h(c?FW+A9%s0Pj6COiBW0 z2DH&fhQk=%sDBNjW};=# zVgclyL36DE=ghtt6=F>3wKgeCTV4tb#z8v8%omQkS*-k8+HlM&aq!{1kB;PS@yWM* zPCpl@{5V?aQOfV4%iaSqz5%oN2x3QMR2Q&}{Uz^T3@6t@_e6KajT34r2t~L z9+YlPVL!F$;qbWuaZS79$hf|AOz!@Ay~4gRa~_(Dg7U$I>Kc9#y^V44XdoD5jcd0r z_eRB(ntxC6v4U;(Jui-U+ssKGZ&gYTCxE0=vq3K(i_p5I5q;o-n#LOlQ2H!i7;pvH zKtaeWgb#YV-cuFjUP*8!?&0t#^7qQUgWoJJ=NA8TVlezL#h(a{y}41GG%Elyi;ThI z;gV)66Bn0pH5gJRyw>$Nh8r=E2B<$UiIq*s$bYbJSHim8TInjVtY)wVC~3S1)oBIp zXV6|EM^$VSY~d&kN6+ZvAF7g z87HwZSX*lpuXii4nCm&MhMbbE`z5!W$1KD2bJ-;0rQwr;Cs2s!tbI|6#84e;d7ljz zgY6VEisd%Y8HO*19<~Sf<@y=F=Y1#fy??+Y=$qKltCs(72+bc3q4}D+|JM(Hk2=?L z$|KuMr+c9|!Q@=jQ2SR+6u~C9H_Twrx_}CJGhb!4t~Y3_kzS3=qGxWzB@c?Ej|v=M z$F@h6{Rs0@wy>641S%I3r|E}WQ)Ea!AF08FB$rk5j5X_maK}7`x3gne`FpJT#(!yY zfO0=6%_nE&BOdHDj?+^X)B&l~4R&w)k?Zwg^=--;wLh5NUQ#!4C zf*3t0Th3N9oi{Qwz$G*{O@3x;4IoPMP8Bp)Q+uRnLVtiP$UQL11_DTMBZFqj?aT`p zoq*TlW;t4i&uJi+tUIk97Z-k5P=Dhc#7 zhXVaV$(BQ#fxJ57B0`wa)Ib=3bG3=1aO!WV<)oWG?J?r5H!Zo(YKF2@Rx0-hPc}0~ zT<4fH$zpE5@B2^fZoB>*sCTfZpm!sH&~GAuu1qoynsCnL5B`3z)Lkj;8>ii#M+bVX zOBX?081CCF`@2YS&s%*c&3_t;Mvtznl-8?n&Pv3hcL3ecYmfs5;>E90=Nuwxjx*dGCUO3iI6W(v)dOYkE z?WVF|8`y`qp5OJ1E^5um5MOq&;5&uIG+uzInze@T0m^zRAF{9rN3;MGv zMNk@s^`J80acVfpE4p5fy*<6)2jrTXJ2F4jN1?|rC4mz5yMJm+=RhByq@e%}X{L7b zl4`*M$>n*hO-o%9oOY4#xwRzJ8Q6a=66*uQjzV#L={U)JZPV(16RzGh7r)p>^&k?2 zab5r8L!qLM;GKHXO?($P4F?0#RsP-0V%j$VA>uqP?WRXn}ZA3QjyavHx^@+(Fz+jr&G7N{i+Y>ik{6_#exIGwTHdy4kQ=u(Co) z1KGj+5sVVmxHjo@F*Olg9AM&3Sg~RQdy>_#DcQ7!8Gr4Cy);1guyOU6;VFv1Atf8oZM+P~St##IH$;FNWDKpXupYtFddVQp0_xI(P`+i6E(fcz=k$)zKDgssYs8{5&PGA3!d;YvnmLv3< z2Xcml#kEQV=nm3|ybb%?VDGDPACh+AWK{R3lUWZ3@}cK)la&z#IyY|8RmxA6Y~-&i zt=LqX-3n4mwU^DJre)`jy`m*KgY9fXAiYxj6)ei`oN#&mFdi+M#!_6>d zbbpbySS2!uJOyZJLk&c1u@3`dqsuV5m}+!k301}Crfn0$b&(8uF0)7RKtyLIh-KR$ zvyD)14h|8*=A$xFx2f_}?Ts@H4h1;QheuyHbT3r>9hLJpDgG|GU)y6Yr27EkdOrQl zTDBLgJ*iOmwF>o$Is2ut?omE&1QwT6m4C;zLL{Y4ACT1|sgXb>TWQmS(Ii)qAeP}I ztG9|aic?@d*3-PvH=4nXHvMq59ZHJCjlHpNOrb*7Xhd2wZ?m07tA5qz_ET^KYSS$inOWOavouA}mYD)FW}sM9$bVp* zC`+?uws}3Kz<7WmOkP3mJ}9C^i3X;+dn(}Yy%&cZ!nczjmezL5L)o>mGQLOHyBV2> zNrNVpITxqfP~f&CQuj=>uzg1#hx-NAXOy;E%h23MhCvV3yk<&S9N^AqeKSR8abj z3mn34-c0i!(zOrawPm+tT@`iuBYW$=Zf&n`DuIYi(ykt?C(Q!<}GkOQjhl#oe z$zqAp&)I|DX?kMS>)A2J|9_rE^t;LFufj2+vnT7WRalb1jD@2KW`Mq)3~ZxDxoXeO zI4oO6TMiF}RkB;o7gd9#4GlLNB9dgote6p{%n-&aJXkQ9WG#^@F@Y+iQ(uJ9bF_H- z<|8fM&VD=%{Tv4M@5BLn=`3}}?A5c-eG&EJ8zpy`M59PPqFz0+B7dN}bmXc%cZ$2l zKuHWQUe7X%8AGZ5b~^+$w(#iQ5Yl43k04MbglK~GHy{kbs+jW4 zKJs06e#M9nrr7uE<&|CgZ?;i1@3rOr_wO_E4S)6Z@aW#Zhn4(|fxk|UyV#dbPyEgf z^Q_r33Vow6AM~NBtbe#;@mwW$3>tIIZsXUnlI{S3TZoqAjf&m66e>6y6mWmdW|&Yc zA`z#DGM!Ob(MwknNaF|+EWHH*C%oV?u>4fu)g@sL^u9d*Vd$0TBnKPj&$j&7vAD-W zMD6<`E*j-V+X|!UTtiVWS@*?xFLuiDz{!+6@_MVW8RGkWK7SU5E79LujHWp`odQav z?J;K_&{hVqR!QknFJU~H*gvB$o;!Z1#B@43O+YWEQyF*Z`To#dvL2j%(IGVMtj#=4 zUhJ>`=P(Tsr$7JmKYn#n(}Tr=MlC(-4+WsFvC3ScmQ$bsC7oS>Q8sayVwYAHyS8$1 zY$s8b(~Y`v{eSUrP9hGM_K|@*&r+~pHUVO_T;Nt3i23kS*$N-XaU}ZHm17Xs*EtIC zH#rK2)|^A)oKXfw^3nu$^+PKb^l#1R*=CNw%_lo+uZ#e&geiNxW-SwR9NxhcI^3B( z9^H8=?#tFLFPORw<1J!ABV^FgBAU2ux&u9)Z#b6j)_etN_pMS+lO}cU@8)>IPEVvby^>#8Jjj_mW{IQHr z#cEiCSX!;Ql&CudZ9K37@!xCCEXX+^h~r!&PpW;kw)%>b$4`}fzK{dM{=DRcRW5> zPwyK~zhT;&_xC5u(Z4mCuqv^2(1XK%I1lpx2OD${iNuIbHgZGtggoAj_x5-&9m2V) zYk#Z03YZY3xZZ5a@S@-h);oLVE(uzK;<7I;U4x}&+OsS=k@L)>)5#BW=pM&mx!!T# zC?XvsZWMTa^>!DzzUw<3X-|m?*UtURyu449&C|qsHGO|dYdK8l<%?l<^s6i9{vgf@ z=MejPq%zZW`q!CA<*%#=(&AT@{@RkijDN!Um88F-H1V9`S6OFSS{z>bvrgMT9HWxZ z3vfTpmas+{i(Qja{C0{hw-gKWbW5+eZKF&}&V`{t%|=4G89=_T*qh!o()db>w2Itf z_O{%Q;32cfhtpm#pODX1czrG3dnlLu^?vwx_ZzYNQQ_p@Pi%WM7GxlCo;;_VEPvp& zX4W4p5@y!`RY0o0LsyAX!|YJA_a-w#P-og4>WA%z{jENATE~V;iu4oHk}MLC3!xRz z#cW<|m_1_1r2dp$`|Fy?KP#JXZ0QnB{C6$?(ME+>UTs>z%8CX+wWV&sB|a*$LBI}S z2w3&TP{A&Xm6&ce+aYG=!d?Ma?g|44Oc#F*vw}7dqDh>g;K^t!seLYZnnG_qc_q@@ zg&!8sJxcr4mCtFfkL1c4k?XTc(Bgk?l6`BdLg8eMo0E|7=a!{;I*6XZG-jJ6H9-i zO$^GJ;2As42hw!B_;BTD%Yp8Kmvpy(iyi;Bn(@D_n(@sV>;ELg%e6Gjfbp1F23*O@ z8Qxf1pv45K7p=%;H*nVZuBP3XaE8r(Eqbjxv@vGi2o?HDZ*Htu zIt=!!XN%Z>C~yuPSqo{ERm$$Q5Ahwbqp+vshT?0?)7UE=cH z$a)C8BrPs0abD_hm#}#s5UR__DE+rz{O^`b6#ATMNc$ElO@eDv%P8u#oIpw#C&N`q z3|il6iGC69M4rgudOe{AVV_EPtvAUSS*2=cFtfk_L^rGLYEsRhij@0LssDdRK{(5n z*&apf8_BvaSSKsX&w}0lO*RjGIa&A^uvyaSAW#OCqRn)^?>A&4?smN)H2%lJ3t7bT@1i`X+4j zIym5xE7BJ}t(%zECXWJPOk01VNLiy5nv#x^45vK6mizSzoZ)@b>d`Ke$Uwn|JFl07?F!zE$In&xemG%|*baGO z)GVnTL~i{UuM1zPmt}zfTsy-eWVILPqiwHf*|}t{`--AuWIM!&b*6a>2R2QEsdb=q zaD>ZgXO#%aRDX^GCpsOee>(gjs=!-|*Ac3h6#hT|1d1vglPG_go%Y84UPiTpsMfdD zc!XwXL*-N3*;B1Q)Ay5^wcFBJG_H`Kx5gz&q$ZxD(@MYBqN1+5k|YB|+cJ_E60~R3 z!1taWB2nK?j<~P)(IbX`&T&p`Ez+YPS(b7&GOYpcpKj~jTdEP>TzRqM;p z_k;q%H@Q;BLWF+}+--l%$&s_fbA7o_)e)@>SY<>ZqLea#zFYRk+enx_vuM0_@hpmm z!yhKcUptKcTk1NHH4^QHhj}CQW3yj+h!jr@Vc66ZLX=t(pzN}NN86GjrXizDC#2tV zOi0BRZ7s15;w#{CO06*>NMk=-rjunJJun`g&+^wwLw-hpi_4uJ6-5k~`EQ)U4bc zVuN`x^t{C+%O*52MPXo-+pAJS@KN-PJ>UaF&dk2Ja`X$`4->g(8+dclKH_xv!khe0 z9;uZm9)TA@GWs*4iH2yMz769c=max?AwIXf&L+-EMM3MI0QXYS^&gWu`t!PzLMv3|-?aL|dZv z)O2(v?LpShhesv%9)kaURLi#(udaS3?nB^Lh4XSK@cLf<^^?E)o!7_8d{RrUX~odXEuYZamo&m6n)=E8q| z`=sY{C5a3iEH{WMbrYh~$N?A;<18$D<5jW_Cb*c6xG}Q?S$~Z8^j0HA5GQDO#||`F z6tv2CI5dUO2}8__SzN~mx=(& zZPZA^8I#|+<(L4H?PRkYFCnH+SWtf%h}e9F8RSeMg_s=@Op=wB91Fahb}E|06%|q2 zKr^e-uq_b=>phkB_1=v`-q-ut4;{c?5bdL&K9Xp9L;9be_!2<&lkF(LXx21W?lvgW z-Z~8fdcWp|xK*Gk2Md_-T_)70|Ds2+^7$Wd=mX54O&DY$uyeC2N0`84&Lm z?o;qS|37c5*O6+Fe!FV+HvyYeBT#L)) zsy==1Ge>H5k-$GqMnq;t#`23xQk?4cy}ogFWnTAWUL${<#7C6upuVK%-%-mXIs9f2 zKl@5vG3}Gzd5=Tz{)ZmlIsJdq`yY6`x()^u1^%EE(xEJB%eKU99Q9>9*4M>g8nq~8 zji(mqsgxVQ#uuvne5t#YX;U-}|Snu66e63J;2CDUlvO^{p=O0VRp&-d!ffSL1jl(Yg84Bg~&A)v(aiKL z1bIE||MPLG{vWSD;x~U+fa4E-cXqOd6YBgVvX=8`gZOSUt&oKQe1@E%(a_*J>X zzm&T=e>|U5Zeku-Zr_t4efSUpnedWUX*d`nQ3tSLJs3~M!?k}gt;{gSL7QF3Ea>)` z6t%v(T@TU;*Sqn4%juR?ttE({r#6SiNNrRgV-&{vzE$k~cuoX+fBONxy1Bt$Zf=Jz z5BsG1SS#qeB%3F>$nI92mHRcT?PleHY?xWCrWA4E`L6=JfO{B0Xnf+_J2> zx%0GMetO0M--hH?ZheaLKCTd%_w-?x$Cqmb#Oo|@j?0}FoR`Ym&m_Nw`^?l`$qwCy z+?#Z0xXVKH;~ry2MH>X`Y^J9zJq0&F9+S@6xB9S!;Cz3&QY>Phxl^H^^bj zgq{sn>arG%yNnmFSoS0g=hjc1X|MWyqmKW(T=p-O%RbTgyfNGd1S0iS(-G@jEf7bu zhoB-%^JZm?fF%>-US0ck-=yIjb7^7A3Xsjgu9yT=Z_g$gl?^cwqhe2OZGzy1jbf6>p0CyO{E2>Jv2^6oB3*}l3hd*pce18>vk9o zNmBL%&|A~7yr4jg)Yx*=bZVZ*h9nEbJT*ySi7bEaUTsc%c;@l3aXfh9*H51K4Kv}? zmFeFcdw+WM!7?8)im-QKhfk$X{_|+b{lMvR$GUYZV95&bqj6vu6W!QB8bT)h;q+&F zc00!swOr|efcBziLK2s6wO}^%8Odq`M;^3pp@>sE7)m?Ro|u-aE~tGjij&zUxbF~j zN!ou)#+^y{Ll41AcS+zJ&M%!=-UtZx&l9-j<@7$8sj=gFaD|nx9=-p^_@%n=K1Jj5 zVIG@7dQ8iEK3IKKe)$*57oOl&kXLXkqkFti5IWWXk25*Y)|dNfHYhE7w{;-_3e^d< z*xT>~*)L-Rt7v^W-Z^Wd?l4A?dBZ@Z315Fz!I%%{MxR?%HH`+lY8VOYyS37{GH@ws zFZWLnEZ0D}zgA7EUsx^PG+JM%vG`&+U;vafo#RD6Gq z8GwC(8L({*r@4nSSo04x$RlJucI!MLVTReD#c&0N+ig_&kwxp!lx%g*o9H-FrIGBL zT|L4SY=+3|RfLk!LK}?wvAZb+M9zh=W5k)wW^tH~s@YJNBjzq5zb3$uz^~gstkAtZ ztfi%M{9o|H2_gRKrywS8a{ukJLL>LoO7dk7nrO%H*7RxgE6_=+9EP;Jkk~W zjB_>&W^vx!6iZ7O1uQ47W!c~DmL)@{*1$kJAcgNzL0^sjNZ41+ABIQ=c9ymeaZuli za%mo2AuryC!(wn`9r(xp{>QI`3VkOyv4wbrrn$a0gEo2Jj93PJ?wcQ;(<9YjZ-Qd zc&#S!@$psWRMo?iS@eDsD&TP=2A}X=ZcU`mn5`F6nm+fI3sF*`phY%>raCJa-Z4vS zAI>X!uawAGPW(a=R#QiX4u5|D$^)uuhkNtIY%&JI#;c8ZAgT6&VZ{Y~-xdeB96+8` zwfzVLhkxwkx|u;wggi0msc~}YT`L`PeN75n*ToR{3*P+I-lW4s%&K2@CUsr+=WB=3 zcYouE1=+;)aoE>>=G^Vs0CCO=|E^KovHM~>Y1aS+qw#uUYXz0_1Ezl#ma^t>!U7#m zJS42Zeb%T|!D1X*F}tAVr~$?sJV@})TAWXvhS<;Hib*p~P7Cv{CEzRzNAWseoz~@R zr$+GVq47B}?AEyCKW?1V2-Da5Ay=A0Smd4OKyc)OxU|xQa1+j0P>@oj#zY!+RZ3lW zI5{>k3(-}D7|LylCY^sME0_?Wty2kUhHTxz3cpLMK9BH7u+zn1E?%p%fP7JB;jxp0 zdKlfdkzFDr5Wi#i_#K={t!da%N2TF5?PC@glj{}aj2kXrEhg2F#ltvHy0tNaymhwj zhxmrahpWL>*w^?R*3)uM&3vb~3STn)lo@&iX8X`xLj6M27e0R-TPJ*s&oyQA3r+bz zc;7uJhWiTve~0vZ@!ARO`Q^@sc+bD6TcE4SIG#wGoyt({z&jRN&(^DB2cIg>Q?F;eIbgDtQv?0DZ9UAUYu^U!8?qJWHzC}Kxt&PaI)Ea#Fy zH>|zS6wDao{(RfxioQ0M%X-kSqmGqYuZUN52kby6MeKVL^SRgEAvJy z3v-dpn)H9dp_Y?LB4}yaV-+&3IDbl6W-;nOp_jIkBHFH0UjgSTC{6EL@n42|qTzGv z;phvxc9a4Byn2#3&XE(k(wp4MU&^*}CX-DAk3q5Lcx+w{!96?ZkT9{D2$*PON_R_I zZpnR>le37Tt)+>xI~9oitznz&j7pb!V1<))Tws3~u?B`cvb!sY?P8oq3chatFz@OW z6x0pN&*uQn`y*(S zotb}!5!>z`r+Qd_9354M=hYupfA}|%+yBv;@nJXmquU1d_V{HV)imGd@@7A-B3{^TKz=}{ zWS|Jdo>elAafN)%?@ZV++zlreZgV&ffDYuhA+9GEH+~?Y2K8Zx6AHlvGN*&J`M7$he zCE=$~$w>7uO7y$na}U%wkv2F59(|q&a(a4|s z^3np@D6(-EM&R=xPb7Q1K8fQsX!7ew`$7I-I!8_G##p4VvR>tVzFN#Ls}7x#V16fM z^Hw(Pr?tFivv9Lsb(_^xE=qGiuXqPmQmFLaKe3SQq;ujPfLM?O6_9*C<)RpP6oYJTkJhjr&p7~3}j;rUQU zf*FK$RlHFEV!J7_QccTruSB`1^#d*lCUmeW7L7$4xzS8@&O>=PulxnL(Rq5Oz`LzR zl}pXDhMlM5o_^mh^fd4jH6MSk4^n^4*#Lg!Y^WYi`9HyzCE-4lwEYNG@OqT(v&mXu z2aV-|=-#DPi?z*j(rhye@GV&~aNtn$q=G4ykZT?*$QZD)01+p9CJ(F5LS|BO(3*uj zG>yBE-d_#lNSv3}smkFs?7J^)e$_D}*DU*ow-q?9;H#bgfxD~S6TpAkOOoC1#PQsS z9VF08!>!PK(mQ>_22SN-Vl~~>7KiDYtJ2xtUk-h&bVfThNQ_0?ZkF_r2L=DI66ie! zBAn;Ob62xzfxwcaUb6QY6)#A5=FY1dKXgI{$0|pq`*NngEvfzzJ^MEn=f5qVUiC6p z7^5CH!@HHi&}!($smXtGDAuBb1y-zQbbHW~S(6}Qcvn$nqZp#5$fHIYFH+bU?-M+Z z49Vo@Wf_F?0^5Pmos53PiZi2M-8eEDy$%e5el-`J%#JR@soz^DU%mAYI}SdnLib%= z^RSMNo^(z4RKW33*F-;_*Z(?ej~j=!Pfubxt%lfm&14mc@I!wvr36^Q7+H!~dpObO zJyIqNt+yMqXUiZ09AYSQ29nNTk1#5&C8IGK&GKnVl$>%`!0I#Q&#XUR|1kC@IMuv* z3*P*TMrFm~t??pSpd&~N;E>+5FmYJ(Wj` z+EcQ;;OfnC5*v*V@05BTF7jcuho&u=-FG7Xn6_v3ee{3ir^cq|xWZc!{9jL;$5P^d zsdr}Q#){cbrx3dFX>6l!;{eNb2;MHNMU0DKi||?!%_i`cbS6-}#aCWLw1!#Dg+a2` z#m$z9{EfHU&Xu(6mL+`XPq|YSe3?b3DFk0V7s6ki3#*4?X@>(7?JTX$1HHc7fIfKt z%2srRg)e_Q*=L}l_fv7CQ30gQA?XlAR;fFt_0;8k!4fUYt} zU}7qCwP{&!*5pnIR)U0#qQQFRkwbh&qjxHWug3mF-sc-Xr)+E{mScWF)DO?D7$q?5 zqNEoE5-=n|78we!Bh(-&f8gc?ko27GOiebmgq0!g+I>aWj#0>JACuU8=S8hwmZ1cJ zE7X7J>>$VpmAi8vIS=AYo%8h%Ymx*<%#Qk?x2rT>n05YRNzHE>f)4}xm3z&_5KD0Z_UAUV1GS!>EWtV+q)17F9&+2;Hw)ytT_87 zL*++d-GAq@;dhltgLpImAyxuNv}Ix-)3!rh+$Uy7mVB2Xbc=QZzn3C&ZBi3X=J)fo zt;R4oS;m&O(hu|6U-4WUjCo3rJhgwjQ_XlL=p(b9nm<1sKX>KYH&>8(e)|d5;jcUw zY!NB<*O4C7HiVO;V3^&?5cJuU+03+*EEK zKmT9L#3_VE#ZN^<#1g7Xmh4Wq=la%ViUl#54Z&5C`fbRsgRP6Yo)$=j4Px+s*$rXO z2#4he8qi8hI;%B5($IPgmA<?myWwKxvtYQ>O(?L|# zQnqx~V z^(S|-=NVt^`TB=R^M`(#G&mV?Udp8X58f7|bjN}PtWr>eSM`$Z$J&e%ogI_3$Yc{N zZ71f;vAiBziZ|=cfw;}`MOM${vZ~2#(G(55$s699*h5@F6MyYXjC6nJ$?zRv&wP7# z`xNPREuH^Mt-9Nq7u(~9xiyQOpGO2nK7#8gf*%zXc zn+#$FRbqaG@yOiF>&l+QiplxY!_kA;djkhU7;TsB?^GC$-+Ly;@#j(-+O=*|YmyW0l>Y-y9+_Olr$SWMRKc%g{0YYM zyLJ$5?r%ALA)xeJ-e=gpOKg9%NLE^}^G4Wmu7E>*n2);UWR-uYX(^x>?#Y(TlNp+V z)Qo0E)jnf~!@l+hD?T=HVtg0G+~W{V6gii8`l)f~>}LLHarT4e^GQJ2y;AFkbyfyZ zV5Y;{gf^I#g^N_%mQ-!VN;UZN%yvtI#SIX}M2FL&pDwMZ|gJ7Jy^ zamr8G=`!^Rnb|HW!7|3SmZvCDB?@a7kO!f=(APA5wGH8-m|LrPTwqES;^e^NWPolc zn&rTluX@?#?xV!vYfmIN{QV*L(W@ibU*Y4_wzv`q`_q*W;lJ zx~iQ)UutKynOaT!5C_&_zdHO&)Bo34*hSpQD`rrRbZv+WizV-+Nm*0+w&BBFS%BRj z%=hFRQ8$15DgfuMrl-1?=*t~*`0kmwNbEs3AG0klZ)nj>qii&1?kX+xAqh?t{^0(@ zvE1FYa);nm=#`5;4_d;3uF1`S82_|P%Z-QjQ*}M;PY}8+z4!RW8OQ0fcb>i>Jna7V z)=kXQi=$JN<*WBTlZ+Bw%RU-nX88 z$lAAp^z!7CaQL|>p8GDF_AQi-8MzNmaAxg0M6So<#W6@J&YswR zD{g=DFR`DcJQ_&qYRZcAoKkUjxWNp4pC`Rl?uG+LS$XraHSl7Ym1F4$Gpl(u^;_j@z`;|nYk@D^C< zX0dqr*~g^={T(>RTbZ6h6;5}N!*DZ-g-CyiCylm>{56~PI6hwPTi1_*IzmT_-O}me zdM>WaDMqP*uw1vDk(KdC#sDy&Lt8(nGyG5}b?wJYf5nWqoPYJ`tmR!-^6p^&8_dVo z4En)V{v9Pr4Sh3j5Vyx8_j54EuZ*YZO7bDB=B*fVdw<9SIQ8#~` z72zJ(SOpj(x>*=$sac&a>O#*z42<0cy8b-)Bl6>OY&?ird=TnW7qe}S`fSYGF9VFoS`oQ{YReqo^iu{BJh2}Na-vysN zzHmZj_lwj%gh$4?noO+)N*Pxg}A>jLp<` zorf3xyu(AVv(z^0Ctd0GGH4%Kv8YCxP8h$OuCXcD(%cZ)<@*t~SEhgDXtc^3zt|>5 zttEnpA^@IH%~HrjgNYrQj7L6zV~v;X9dqjIylpTR3KVQ`4=>(O>%f*bcg~efz9?;S zZM%KXq1W@^7T&yL=sA-R`}ySg-);mzz;YDYE`xE{0gDOUb%2fq5H=utt`v03>!ZAdbRQlr6hGT=#dObg z;t({6B4}s&zQwm{Yc6R{FpL>#R_M6(*5ei9Xw8046?bXyug8CJT3ue6rwZiP%(2%K z14Fl}4`*l3yG>p%_lHX?-9sl2)e$e?u-H5%!x0B{^O?JoikE2&K$|jMgy{8ux<=8 znQTS04>Y(>8svX!o7xTL0vSC~J#MWvt75)X!@H#7iFZelc)ET#6YFy$^($I_E)lO- z^s~(UA5(i@*6unjjGZTMu^3^d{T#x>IYc-CKE=yv=GyZqG+XSZ5ZQ?(SuI@Ca?BiY zM;r*n6L>o1a2?SYa#OG{Z5GIUVc$*dC%QaQ{HgtL7UO?wR7bC?e8=970j9UsB74NR zep&5bD`RfyzpLH&l~L>yp4^pjeL`utjlWpiY;iW)E;=mJk&#edW zXfD1IT5^ASd>e1y7(m`~;kK;Sr^4x?kk^8(CU9I|nHarZ75}6kJ`ATpp+KeiJMj4MDpB(=2v9 zRQKRrRXHw(akS7}+<3sRrt9Hfr0a!2eSwMc{(OITe&KY!J6_+K-TDzthe5(G4lAFf zFG?-U7Ss@!B13{@`axZUEIFl2rCJOmaG6R)Sk03bRVNS~4)_pHN!?tIyFS7q9@(h& zK%~t&T`ljzG&~OEII|9aj?r}2Y$NB#*C==lj*Q&&p7JJ3XQq0wO+&W2=L4aT{fcTv zC?kJ0gz9iJab&rMC2gk*I6}?bpVfh9XH>l+EPE&B23hA5FroxhvGzVpnN6h+CH@{E zdmhA*Do@ux%pN@S)0;_rUAms%ISbgi`CTJI>7VQ<&-=@bQ@qP}n~GRuyNx-cl^M%z zR{exFjXWMPyfPjzBw@~i-c9(e&_z4hKb(I~;1$5ne3hN%t3sMAmUfaD^Tsci;VdR= zy}c_f>~Yv<+8?h@h&8+v07z{Z#;u=io9MN*DiJoU0hiswYee|%>6EUpxhv|PU zVdYl#7ZP}W<5B*OjbGTrZp7ggjRsrO`!J$ph(e?alDSPKdyBltX8mR}CWy`_}#ij?#Jr->G zumauRfkoy8qr-k|Ng*|DQg!Be$=n(FyQ<>+XpxLdp$#fHSeRQ>?0i6=W}$z+rR~W) zsdzMCqgesf)6SYitJrq2(XdX|3)sG!{-4q5i6fsqKk?|AW#)QZz^U(ZV8=sUn#X^6 zkA-F-;6sT>p>??4a+?&0=i7)e$J9zxm-6~RI1MJp!(}SA_^$Pfy2!_Cf)n9nDesgW zu#7XfF7|F=jVduN8&m8dr@4RY^zgxWo=EuS_7CG7&t29}Ma2&qdf@dB-9Zu0n)>1R z^w9ol4ImvW>jyDbk0FuOQ6}t}sUAtlydnD#SVa&t5Bp+d?)tqG5yRn33BU&OBtTXY z2jCZ7fodXeu<^LTF)FCEphWKq55H+N&xHKqy&q=N9(zH)*tgDa{3Cx3tSgR}?`~Wl zL`r`MVwi}!i@E*Om;$gpcQh&M{Q)aZq0(3n27JCR`*_wVI)T^FA{wapT8wc<=S9D( z_l||fYt}>eMmJIS?o`mdomutw3On-16y$_Vb`6gYyl(h7^kF|d$AW!#0hp@(6h-*n z4NH}g+9@Mhg=mb*iV1&mh|G}Af>XbB0)WmbLs#Vqg=hlyE= zk|1_!n6t&rOhgP-gH9Q2%l zXFzreCVd39Y_pSq{fj`pntSbOlr5*S}+9R_} z%&Uv=(;KJ5!@2Q|bL&4(2Apbm{&W5!wp2Q0ed@!{Tgq#9zM=XU-X-{WLkTKSAK~sSiJ*q@sVqmwhKe@-!mkl8$d9 z3f=}BL{3mXMfM#_E4+GEgqih(%K9b8}t&TuF^`AlZI8^NI z;}0SZUp;@Xlcxu7ezP$z3ick~!2X0TKD>VpSUJJ#{Z3)(Tf-~nWv%Wkr>y~B%rnT} zu>);9i)Uk6*jjolz(&c)ei0Km(=ph9$9Vvp8zQ_-Cyp8|LU&&qrMRYr!e5!8qWekB z4ewDGPt*Od+MNDA9Hr^=OzzXuuU47|ds}9S`w)MdO?pt%Z#2T6(9d6q&%L|r_HgEl z?E+|PD`TbcVlZJTPN5iarw=w-$`#v*Y*Ru}Q279MlG#Yk3q&4=ku@Hz!8suWs7OZB z094A-N)jwr`DlNS_VOVup6UGN{>dqSHBIsB75?3CUA*!{-_JLJXAT@Ufpg;wu7eHPVK;zo#Jh=7t2M7~MRS&H0RSAX zHpz6iQ@v(ZNNYt*uzJ#))kswC0v9?_;J~lb-{07QKgqJWCt4r85=wfJT=I|qMBajI zUynQe?8VD~^AF4Oz6+B*_lbP91O39Pd-Q*U-)bxBLu_lgMH9&qScr-Rj@V|Qa6LU7 zO8!irb`;3<&YVWq%eqQUFK!1zQDGNVB^~xaLyfnZX8JurtPU%OT0`wvWwCo~m6t;C zvNAj+9D!Hi5x_6t5#IFv@0hp$#xZZ#c=gMPW5Ao})Q=L!0Qgefy^=Tve!5`XoH&1m z{`p~G!(SHfp6`n~hk$h64%dvhU$afSr7XPIhRI6HkT%pJfLPNowTu;gKjJfY23Z}k zuEtm@=7d3da4`)#au-eZye-hf8A#k$is9RNW60GN0r|m-@b4JQ|4uQM*Esb&b3Rw( zUMAO3lRLM567E1QF|D0Y+Eg4A;EsQ!^(xva=wwC%#WJ<9$!@QfnQvhLrDuv4*yPTr zf-&n1_;{2g#ZbsgmgvXjGU`itw-V8HDBK0_aURMsbmVmLfL>L~(3_O9$H33q)CGhJJCWnRwxOEdwI^39-S|HIh!$rS5eMN5E;Y<9Uh}FF{v8f2PY##shWR{;>nX> z-k3pDTTPIm4A=!!wNX+f4O~thnTPeJxjf!nFJt^{Df-V(;M>2Y{5;b0S@(IO@6SpZ zU-os%#5yabcbUT#5}z&>fw><~*8-WazK^ne4ZvKn#C?A*k#&jH*t!ZxoLHK<3_PU2l2v_R%XDKb{AEjcu2t zeaFO$r$5AbIQRT2r|+r6@=eqB!+-Z=H{pX1HV|-@F z`$rGQ@`u;_rSR9>`1g_Gzx|Qol0{ExjJHgfbBV_j4F9u*`%-~FypcZ~KlbB==UA8V z-6`8x%;`P6#D}7@&g9BNfrhlL1ErR zB|!;|y{#|A@yPAQ(}P?KyWC*f&PV&O2No=}tKF>FqcE&d*>EZOX$cHjwjKmF6wFq8 z8K)a)Abg(85xsVN=JUrffUfMg!i!NMRgTy350= z>!Y-WuH`$i8^2ji<7e2pAMoMd$H)J6$H#x;Lj1+J5Fb(ba@)LNCR{H0PZ>iOHnq=I zxR2mj-fzmk;?#eH(ANhSS|!3Gnp%^U575J zO_-58PwA?3)Z30*B{rN7WDu~Cz0_=gJRU5(o#yPM+L^Y=LK9_ltr#pkR_neVG66OL z(7T*}Pa`-w0H3aZSY_b@*?EdUxFt>a8H5CT8$xk}nTbv3$<+P^-szZ6dBjXVoWY-? zO~AK)xnqA(@}N9Hez|s?l}~YQZ~FTBT+jn}`fO*o_=(e4Ux$f+qZiD)e+{I?1W4Mp zp0J}i;?={D)uVaN0)u4a$~)0B$#%7%=cTEKAlNCID(Jh7+8X}Mu=y<-vMd+PnC%qi zHj=zu!Ye+v@r1l**FVwXAX;ad0O-|x^Yi071nYmc5YS(uZ4Uq`OUElt8&E4~(-JkN z@$o>0wktVX5hft--TihOqstrw{MnGxRywwUdV<#1+eRfRP}!#5V6r~iBwafncZ!7H zP1~2K(bvs~lgJUDTcJ*muTk3V+Z!##7s(_4_@r9IM}*9F+BBLjhxE(?sl__gcgwks zCO&^Q>nTdL+0mq?*<=pdwVDCaVilx-qz3Vz91h?T5pvC^hF%IpVYS1OP-#NG;PSfy zgARi|+K>)^4!b6D<*W<8U_X9LwjgyLvI{(fU(_=lTLGGQK-7slA&}Kz2jW9_JNF$T zp%*4JuiD9~-X~DoleCB}rMzy(lR*u`y0(8d)o?{F?J{RvCZ=$mDjRhPBk1-nf}MwX zq}loE1oZg1vVne4Pv#uR^WZu`eQPRn9@yDS80LFxTD5iK_@QXB`Z4?FPy7e=vr z%UL!oY<5lT;KLBQ%$6(RRT^~H-mpD zWZJScVw77{C9}<_y~pHmL8$}%FWSdKpx15B!1awgepA{0ox|<;!gKE3x$yx>Ki%20 zyg%p{eZ4&^T2Hy3uh@2u0zQ7!!>bSfPvGr)brlCsWh+1Qs()GG@W!+WV%bj#uXU4g zx%V6imjWm#OY9(cpcuiWSl5#^xs89ji9hIgxmt^Bf2uppct!(s#bC49Zqyx4ej-Xz zXif3Fk*@ji_d?j+vP`poJOv_LsyrV=w_ipoB*y@o>M`{9zeaDvl`r5=!MFPNbAT=I zHuN@iT_=9g!>(Xw{@3w%dA}d#C}c-DKm2((!lGTvv4y@A?uR%#sEW9!~(dLpD1_im=W_93cy*-9nrgf(C30nAZH; z{doT@tK-K$2jh(X+#M4gfj@tD7lGa9wdNe=e$I+~cS3v`_7!*D+fNbP+PwNXFHc@g zzDEgoe)Ru-_z}1L)1OHCqkKLI{JHgTtSo*_Z3e$kn@@A-(v0~s?Qhy^AJc)}#X`P{ zZbCku=D(A@Hdl!Cz_D?DzFSik?xX=mcyo?Z$B5Q%vOZJ;lxXn4tsdE5qJ zI#@)Bkt9oBLh;1LMj?MlTkeWkR%O+hvg`jQFAiil8JUw~J`sA={fmB+2X_V~-9}lP z+^4Bb@ZqdbQH(^ur9hzwC72}-qN9<{q*i2`L8lkct{);>lbOZU)=%>=YXxmKn0g*e zI5!DpHlcQvEavUdt;zN zUb=B}P`=}r6sSwR`-oR&Z{72z{9mJ&_=#^UG=Gc^hp`(uHn3J;Glgkn&eGU+m_lxA zE5k~TXKO0waS1|#_{ zz?~xvPy2G4J{*6CeT^H}D{aMr?WTuQ&ZA=SkjIT5+{mA&e{&X%m)wK#k{w?^U$#s#x!}vsVQ9i?y^5nRgV74)IQ5-!XkibU-65tVm4Apg(ib+iC*XdS-5_nuwX|83ZJ{-W>l&styv?lYde9<)IREwR32^<|Nk3ou(obfku149^xROylfs zXN5HjF;+=)oB_r7a9eh2$GOy|4PdzuR}w7HrFIw0(W@~$G4R}aK(D6zpkJi>e8j=G zOX+uG3|T!Q+b~czJdV-x(hwC|Q4Lq)Y$AVY=W}nEuFY5w6~r-0e^U?{B5IMf8Ee7_ zrL>(_7u@OAqExr$kYe1Tc?5#cUEE^dwTcnRzI zwMd*_n>jB7`tHOD3e?p!4uw-$UM-CR^K|NvdMwv?2v0Ut|WQ(AQ0sy%|}IxZKJ!e)?SA27~}ICrEzWH!|qu4ei-6|zCA9! z3!}ikh~)ac>S{kPcmBf=9~N&)6H*4y24n|G!$(holtp2V@PTEU#G zOONL3hvS zKwGoo(yBJ&Sg{XNqrPRgnxz@t#4EpbGYW;<*q9MjO#vQAPjm13X!bAM}mtGA;u7#P{Go=g0x~;CZm-KjV)f<#YvtSz-m$1Gi{i$vvr0S zlvy+E%9~gQs-r>9ipJWzM@^`V#TqFlIKF>Dge7ZkC~faGg}YItst?G0HOS5FL=G)Z--9x@mp1#+Vohv% zm{~({!lTBN`vGxJ1bCtCsrf@x$2dD0nGPG(tCgMCws)X;w(CqqF*d8%_KzT`=e z8?W|t#!C=ir`mM4O2NgHR3@5wm+9z*^e3J^w@x8&*H)r06sji_y#MDK8Co!!&@O_- zuDxnvbHX)?mDEGa9`@(#4BY@C)Y3py`s>Z2tmMI*S+P@Xr-k-fssL=hwKf{-I^$W3 zuQx#5h6vLt_sM_u@pDfUI9@zFBKKVbz&|1TeMNvj9oNd5)V>GLnOOn-WIr+J%+R-~ z!lpSrg!vHg=9_53^u^M`yhV*N&fzq_i&cGG=P*cbQQaDqEY}Vbg7h&33cdU9l6&%lbrfGq zZ9X33O}~HjYPZ32u9bKBY=bF=;pA%2ZgRFqE2bhFerXJ`QfAEqZ(R~M`N1aY3$)1v zMw<_bv5vN)vG{LxqNZ;v4!I3*Rh+j%AVq!-T*y7 z!oONDj-J1_7L2bf9RCW(%-<jyaZyb7R zC@Y5FcpNFoCQ*xH+8yg#XE08=)Sb^jutU61H0rFWbG<r6!RbZyC92ozLtk0rmLJbi`q}5>- zzU~l)kOq_CidYXe@L*OphPE*CRVW!7L*1t`VtBfL%S2*`j?lGX=y>1wE04mZaI`gw zgK(7n_%8qE_c1+?tE)D0=c;|7#r=ls>l&WHCl;8$(aZHR{BN%>N}#0vW*F-3nt;f{ z<;E75^ec-Fw!z-+79o=@Rn8c$fKHbyuS!6z1597y`*_{TD-{dI3hPX_hXZ4}QkmL)OtU^U)v9h3DYOVA!F<^(i<;T2ZU5etVa1;K_SR3vFQH(aTcal6n{ zb-Lk3!I<9(WKL~3D%gN`nxziBI-3d(7YEgpxT=~GU#X_C+qxGOb-&Jy_%M&|YHDd6 zIBpzx^_8U3r&mt1^|74%9t-Q6pu%x0*=j;*1c$FZjf$& z5zvf<_9fxqJ6%2i z2d;c>O+G!nfoJDp-LCU+Zsn!x1-siO@r!-=i1l`XLjOu4o&ra1B6tHMy1m=k1H~D~ zTex?bVZ+B1ph&7j^&|hFX(|hTojC}92U|(t?W2JV$I6`EEodzTh?+HLL2hke4Gt|P z#fTxA+~tqIN}ktT_~`i$t5;tC?Hi0RznDq3;@EVRJK2DS207J_<7U&=^aRO~jwII zY2kW0&G^~0LJ46gFen_;_S7-B%q|^WzMs|~nlOG=!sHv&ep*}Z&^X`KeLLGhA4{U1 z#U_6?a$E#KZO!UM?*zZlOW&h^^_@Lw)p>ZY`gq6v-%Ry{;-xkhl(?e2urMTR-fJxY z2m~eRFo%J-gRUrE7>l#UrCpL3;@QAeDk0rTv&ldn@WhCy99r=)YEW}gHbt`(xJPKt zs`115?+Nbs^Hh`be(@`U@(FWqpPx4?Z>_b=N)lmIS#M(#KvjG>EPRuH;d&D+*nKxC z532g6G5FGQ6jfkR4%kqvymY;2scjoE;#e(pF;a$*iQ;8I(as9y9%bO8As<8LKe&C` ziIA&15%N2H_@SZTub(If79@&dk;P;s;W@aO^C3J^Lz2?qL5o|eLg%Ul^WoChBO$Me2L>#*v>%=f~LAb<#{Y`9cMEq)v`Co)2zWjXg6S%dl{K{I0!T0fb07vkXOsB zv0x?T7>~2d92?z5?Tf)4DSmO|hh+?2M?c>n^t~A$(-EGxkK00jr9Y%^z8Ej%F~oK4 zAY6?{O|5F0Rc;5fxGVvt9k~d~vimV3t{0dS4VgADCptc#COiU8VtWef5}2YshqnvB zbSskaGvY4C^AiP*O7YY6soux6gq`bJS#p?h_Txf!8)5$$mVx8Dog2rGZ~{j(fulM4 zRhsDir6B@t*{wK#ZzS^?q>fXAhb0wn0@{X^>6Br+ zK2UeIW!D3$!brT}3dljnQ&gnWE@9^%L9`O7HMGWM{vc-0vai7U2#na@DIT5!NyNCI{BK3Hp1 z7&qLypX0f0Hjz=ddv3px?>S(+6XB=X@%r8aa9xc8zS5(TV~q2G4u>VRI?L)$BKkip zhx!qfhupfhIyyq8E2*cuo?oL|HZs;bfk>?og~>q>YqOLZE>UunPZ1W@AZwIs)*PFV zAzVj)TXLM|maN7e%Ao6*m`@wv9)0P}_)j#xxN|z%T{CW7cQYlf6POQ2i|$Y12@kXG zHrhJHpm1ZEFr#U1i}BD`wdHE3abh%{0o@Xk$E^h~D=k#;Ov{*X!mex-Bf6Q9s7aKn z3^MJ6x#(MnI4~i8=jmoH-L-qajQvR0m)3EA@4iCQ(HAKwiJN#gI$r*H#4!%D{e4;zP)+h;Q~4Ca^q0Z`{(wL3>8kVKtRh@-9ek)m zoP0(fR?hxN(qK0zy$x1xt(Zv)ChWU-l{C7hw6;AR&#V+E@j{;Ola&YngxAetB)-ZhJl5^<8T!{2k(!qr$~>qW^P8QP>=iFq7R0-V};hZQ*%bn7ubj`Qq2>Cb?&OF zz%R*Uzcv*apE#>EJRBtz3YH*0*wSc!a^KW`L!uM$-`qP{W3E}nuER&4`#X!|7aaJA zR;JYs3(;;QFM`yiW!2DK+U=`xJd^3%{NZu@)YS!!(N`YFIz_F+1NR_vs!Marnx>~0K+crER7drwglza2=Rg!H4) zW+saFP3+?cpQr|&>i%7;Dt8^CEph8@=KNgAxp5;7?07tN7sVn$@jX+gn~^&J=3uXK zPP<=ZX2YwnPL3h1oeo=x30ww$E4*dgsT*}L850s=No!6aMoqlS)Qp;fP^E$QN#JSR zCo-O{PD;%+XX^C=O@|%gpwoY|Ue*h$-R0RKu(Ox)iQr3$4+Za6T`wx4|0^brt<+bWm_EP90p$XOh%3RYR5PuHrsVrsdUiEr$|22(oT`E4L>AgE$c=&5?4+qT1E$iD@HB+J5 zovs#PLW3H`GW*8B>GhtPXdPXUpox1G*LJ2-Pq$-zM!T8=WPm)uN0f>VHV(aBD|1V! znt3-1BZ}0ErL5lN8h<8#-hork*AL*;OkePeOy3`4{(gVi`b3BP(8s1ok>JuR2q;A% zFw2TuaMKMxXZ@D9hx0Pbym3m#VqJTP87|u*SdMYIa#PFBGP~wc2VGUDS!~wic(Ar2 zzTO6RRR(+w`3qi!`~|;&{5^GXiEZ*%uKbmx!i|PGM%>o=Y}a*vyXj;kHNjR}1T?nl z$CivJ`ffszM4}q4=$Dx+c7`mAyfnip&Y}kZz0zVa;{lZdQx)=!_Ff7;oFFM4uMR6e zaus#_^!O`E9uFBep1@uxf6@03PeeC!Vly!YmXsj-)ruJyIaQ8TBIQS_&ryc78p-tv zSZs8rg{#3}wTNtgUo%%DA05C;u;E}Kl*4^rO-W$CYxFrX+U`wv9o$za@riCv%s4ep z0UgliF3a=d>--4p2QU4ZXx~Upv9QDl$!OUfZ*YCB_fFgp(^By$h1pNTl(AKO=>PnmD+y#aBB+Hrs%LQ5Uuq*{HLqxuQ-TsU2J23bD5tcM za`SFyT^6~288Hck4>T+xOAl88P*eIbCf7o|ND0JrW z+pb$yp0}L3*y)$Ww+b%rv=JvV-l~|~sp?u7sbRjgn}dHRtpD?pC;uEhM)bU`<@(HZ zA}3IE9zfAeC)cLpSe^uqdyG8M8RQ#%Y41?SsY~shJFR_VwMXoO`htDzd*AN8-8%|5@_eu7 ze&1%H7J@}HXu}$Ou^Ktsc_~v>7mX`5p7lTy>_?K6*oGkRwWa%EI&TerudW_!JdbwdUoYYxHeQB*E$0S5^s_G8Lh4ckV*2`pD6BtPZiL;=z9G0{O=c5e>jz}tJ3x}FfmDe}Y zp~RuSJ*eY3JTAJH=uhXN307{`FzJHk=APGod8ypV{m#zR!vHJlivX*`ILC;Pe}q9b zTzN)jJ_l#GY3K>L?gSE|m7XIQv6{}in2MMtmgG!RNgZ;sWo98$F+DS}Xkp{5*Q<6( z0?LdbR>6FVHk-WHP3PakHUqJcfJN}6h^W}1X zxChp?EN_*z)tSyU4(;?vcjk%q%SJhXZdUs}CR!<;1Sv-_VLNB-{wj=Y2% zhM18fnM#Z;F`DzD*`>Q>zrAMVd`+l-16NFn8tg)}Q=o|3RXaN-GA0Z+`~*2?P+lVGbm?Fke`I0#TCiM2NW6xsd$AJxwtL5HtPd{4 z{WIHl|6ltz+5;bYIuPgn4){TT{C-#iERjygjOjv&xf#R+#Ik5(gI=ycT(iZ0yQZci zIOIX|sWxj*8x#+HB%>EIdAw>*dqYXIql}?u1(tS3#k|<<3M#&{zS|<_LC3?LZfc)< zL>`(R-dPLZrJw_TFX`tP9)UcRQ^*r?>MC~TyVUc1^Oj%o*a*>B(X@1=t{0fl#O-Kd zOe)zn+U1z+jj#bd1rtUQyW66F-O(oAScO23De_Lcbaj_yw?myW=vkC^DiYx4d4Cah z!}*Vv_5pNPh}mEGVTAV0=ue2fXJS)7v5mfv8Tlk1u6@b3YlR&EMUTg~az6++l3YcI zx9Mi4n%TuxBTyCZ4W>f9=`*gZir5?*!MtZ9%rx-4!ARrRj->WIY;6sHQLW?tmXO?6 zsl*@Fu*6?P&^yS#yDvg2oA+(KS!1IPM4}6kRj_F)D-#;2Rtq}p&xc+a!NJ7ZfM{EZ zVJ8|xc4Lj_XsItmXH^Y0OPtL&rX9{ifYVs3AYM_{PE$DaKV#`RC<1_i^+uw(~i} z&bsVBJI@)K!FUXJr+#M`(l8T&n56-fGUr%{Ot)Kk;p162#1{2`8q(ILZD9Jip>mbL z44gnXmeL21o|_r2Vg3j&sW>Ryu+pO)3!LD0YW%w2Gu z_QS7nnE&w)rs!w4S0-KO6S6aNS2)DX7d;j!5h&?x-4Nx8zzdXeGzZq3)FxKkq!$sk zSj#IlXtO=KD}Y{qEox-y@dBiIT2D`il^+sg`&It4hvAeo}P*2qOSX?op7mfI^6!R2m2} zFs!7?cqx*9{qn2#ptwBWw}gJ=6+Yg61e5GG)bDqaMDKyrDG?Iz>}9e_nl+B(Kr>Ax zbTRDM1s0M$Z|GU*Y#_?iV2D|U*zauA2rWFHa5$q ztLz-Omz_fQeiY=Me??0;7j8>)D(XBUzy>JIx^(D&u-iXi9)_@gq4s?I%+H0# zTVK1kw|>9%-Tv#I>s$AX+eom3(9Q1U0e<=oKbXXamUusK)ZPAA>*LQM4~-_sS4NXEEPQKU)!UBq zhs*f($p_)}pH}jW^TUdC&Ly938c7yj83*SEl0T_<0z2DyB#g){+~LFp1{E$OmNJun zrG05VYENeU2<*zHIkh^I{&c*Iy5MwWO<1liIn&OzZ%wd;43@z>0kodH>X=;BFVmNH zoR>h`WAF03MeG3ZxjXN0_aW+vCo}y11Q)rn$%V1k*Z@vK%fX`I!F+5%@xtihStl6D zZqw5&&ZKraR?gb8ilsNfl-Isxg;{>2@cPt{x3$*`|8u1R51H9kP zepqNRHsZv$AFhzMM-DRWc=m_&8yP77-aerRKK?=eAdUy<2~!vGzV~D9{^#4E@5BG? zLBjLjo{AYW{J+1w@xM<;|LY;0NTiawBQ;jjwmASsLv2p3mAM^KDo|C@ypN852eqs* z4JFW7g3N~tB86pVHsPoZfS^Pg8-e6(Mv>q1q)3N*CPx$aD|+@3J^SXFhl6AH!7thm zK8l%%jiStqeMzk-HL@>zkjZn2Asru?6;hY-h#3y8T^CViiP_623hz#t1E}J(MRhBeY|cQ z#||IoYrQ>kTsOzFZ-^D&O5)8qg}TcxkSb@$pnV$WIMIpb&vI-b^t#Z zUH|rf|Lqe$*)P`lL)~sbP*v^??d>pyvmS)6=b6BweiE)ludDU|*)RJ`6#yn32%u0Y zWJI!U6f(gtFsxJurISE^MctE%4cqi_hM3#K>KF0#4$Xb{kGGT7fyN(LR`7>P-&IMX zKP9IxY9>>J@#mx7+DoueCK&xmV+=QNu+&^AQfJGKBx}R6+rtKayJ7vY;extQj&V5N zq!x-+A~t8t>4Io@!QZKA&ZZ(?Vs~#-KP-at$pN6B2Rau%>k?6qlqyEUCr}^Px zrbg*dlPka$Q~4#!)7vBtkU1S6Qb!;8M;`Z~jF)xm+99jTL#;1N=U2AcmiZZX=)vvADi5qpOWG% zA-M@4V%=d^k%b8VMhecq8S%0VjSSq$&ghh4anPc#s@z;Wl{ue0b88RYi?w%i=?h!Q zdzjcG`p)In@pC>G`Q#Vz!|}e+O#GqC`a|Q*WoPy~4$NzR1JB?8uT!nGf1Euht-(Po z(1Pw_ld{8GE(0hjoYI3e)D&BC1?-rRw^{aiF4zCGg6Mr-V)H;x=;K@Ji+f;igOn!cqLpoGR=L;Go`O{8I{j~Q8DU^gjhx)T@Z zPG$r4U@8-iTZl{4hMt1%vrxO5;U08X7h1@};^xQ~#mx_cxyenh;ui!k{*5N^_J}QH zXSm_0+0fEvV0&(o-BB{57e$H>2gA@Z=>oOwb;PfKRRMR$I=2WUT?(YobvA_*@P(ly z3m4o-lB8^wnZ2lrSGMTyvp>W2aMEf&R7TrhDWfM2@p!B8{?Ku=aXSB|A z??vRVn3YrSHpouAQ!T=lX;?wxx+ku7Q^#@{Cgy2>zn|UJAj^Hao?C`3H-BSy_?3vb zQ>OEO@BiMu!#dNWz3oHuq?^){w@rF4YqlcSwJ!_fc;iFIrpohA)o6Yxl;QPal%o`h zxn5`L^|rfRwnnJ9;Eq(z`5(niMXSG=TWB0E$2UW^?P{yDLKLr+rsn>_rQ=HErrFxBKg#T;1tfmMPphg&iico^4&r+Km}x-d7Ms%mBN_@ z3#p5I5-tf(MY)v|3{=$FsUIFTC1atK^l3Ym@;q3ib>ys-0i|w(PKc|^Nf-|-(%X`K zb}hfO^ZEjCAL;8OY!A+q2lXJ}uNAs~J&U@!dJ<2c)7n{n+531TCy&5AVMX7==}z|V zFq4$A-Lky1Zjamwqt$MndFmL!+wBiEo!<+g?W0c6+uIk(i8n8wVAwj8ICZlmYT56x zO1C0&U*I;k$}BtksoL!)N$}4|fG0W|a>79YUau0uMrm+VYoe)kT@$x}S!%<7VAO$| z2@^UMA()uQF8nS-`KBuqRT_{!nmd~T&Tw%g z2O?{6?oMD81k+`i(vh1E&EVUAZnv7sIJeCqDPdpHBo1>v^a0-eZ6OaECm>%mPPh-% zv;Ovle0fvz_Db~Ol`{5yK$ORh^kG_-~_A%fX0mhQkCY}2qi?+C@F)-8RQ zi2$kw*;EmFy-}m9XryV=4YD?5hah?cD>I^^Bq^Zj={H3~oOb0mbn{4j? z{xGQXfThUTan#spUHdJcl#XMG?$l;BGLSXfcQY2}Wr65u{nB5?u}~5I7}iFng-D^% z?rkT+YO&R2-w#}lj0lGwy1Pk;1u{fuOHp=;^^%E}{LAX|`&7=t@cqi6AZPn=Mb7q@ z(l2vD$H>2IZ+|zbua@V3Lf#n^TY60}8YBSBbX#sQazHvnE|~VR3e4er?2xL~!JQz= z$0)XjhF~EuIdABZ*#$x(#t3yr_Cl=a)$(h>gI5zd1L}N!EDAt9t_nas1XKDr((Am_ zJnpR1?`n63X=Vq2|62L)--&G~j3JE9eBY0z;36(+bJPgqZI=gsQj{IY>5@UBnycmr z8S$%`nz`&`EIS&?GQE`@r$te?y|Oo6a5WH4M-Vv%BGCX}LvLCfu$mT!*pbhfKk=mj zD%;m0+B#^nXF8bS%VwE>XeNlUk<3z7h$ zBfRjI3P%h#a4gEkiWXMbh7oj@i-}i6!hJj)XyD$B7XHwzi+^d>eIK;&zF>sE=!V4| zJ;%fxnHOC@@6A(6fqAtd?RAjyKH8ZnTZA{(K8ris(u8(@Z?MX?Sg4iS!Mk{WV2%R~ z9>pdYG3Z=NR^3TuRpM)w$x8s-fVnsp#`s(&BVWqo^I(|~|J3yR9r&7d5`c@%px;Mg zH4VaehL2#cE{0OpH4}M7TTQ1N$8*+`aUA2SOpzwF3ef8`Z#FV6E_rQ~rxFmBSXV46 zbTyg`q@Tfm_W*$_03WWjpoi&L&==`gdxm1}wprn`dAa)+J}#xBVEeU1-QSTy0KG9h zhXt#uEwI(3CucD_?@3#^=8IVh7GXnX){Crt*h6~)0U40YT^J;gw$6m1%mB4W1_mz^nJ($O?WAS+$= z<3@li%^@ICo9ZvA4Icn|!rS%57XGlY4E{x9nMX_MFAh$%hjrHlwmN`pow2?c)KOD* z2VtLo8S&+YWH}0jYBT7f!Fa|@i*@4RnZU__FdZl~4o)f`S~|r%D#`}!7i9|3L(5=Z zT{?GRbVA78d%v)(&Z8OGlRXlj((!KGM?cj7W8+x1{j=hwf1di+=Ow65SCPjERntc1 z78wBOh&87pFBRfp;Z86_H|1s(Y$DbY6*TUDRw*8BhVzwzuG|aUov7sEN~`^aBmYZJ{%O3ANwJ?=Zk9VLbz0T2=l0$O_)E(AZ>;p()3OoQ zS3p4=p~*O+E2assp4Bw@U{3C?PFWg(6-dXX+f8aX&H`?RoSO{V?uzZi-Ogs#^gEz` zumbrGXGc(#FSq7Pn$87WFVJ>K*CijKjC=-Tz7@10BlcbAS+w1iu1D-$dyAjYh;AOg z<}N!-ez2N<;01r-;#Tdb?Lr|OFHEGWnRKvn6R%p@oT|()NS#$%wx2SKS#C1J#jMYA zWZM9JNcGCG>1CtgFtYtM0%$`{2n7)vqt^OGKXOpFNIX2RHh8e5ho59SD;E*h@w?A6=v-`>+oZFBD;2fXXwv1 zaSzrvsTl!v@KXPlkMwgQ((8+@Hu1R8Bk@H8fy0nxe8=wa8HFD9006_WP#6}N6=J{vB~*X+3G8SGJZK0YLWzCWtj`-MBN zer%er-wgBhZP-TLvcjZ%Ph2~lf75#ZZ*f+IU|}3K3p-|aqw@b-V`Om}EWt3b0@n}E zlyc+ee|zj8Sq|MJk9M_IX8nO)#lQXAHb^Ybh{09fYK$m68i=6lAUUJ|n{&t@FaeVr~eH48A=7I7U$owI&+fE86J~%o4qmAFn z%;U{tjeh(140J20oOoUO*-@MN5BN|7=i;vGFU!n1f$b(N(G87r;_Z!;bx#c)&cg9# zzDJe+?bWN$iq&38vHeqoBTOB-2wrIkZ^-!o=Dh}CwT_s+`^EczdW3n)*oTHBVHq9s zj{gI`S2_AE`h&W?`_UD8_6Xi+2^RwBr~_P0Bsr@v^z zXyziC6}?rUE;BKIZf3Y!tjp8tA`Y1#xA!iP3%8#(&b<<^U(Kyel}wlZ6iWg-W6?Y0S%iIlVghM8Q)E4Q6Jy0g4nOTD_Ag|yL8#-i?YmV!Rr z-VGUikp21U%d z51)9retOvYnL&?b6;5A}Z>%RCu0KBdf&9Km=*cUqwPzqFF@9Qr?LWvHv9){dBAsXV zQ{)rBHFU0guAlwhRmS#B;Og70|NY6=Jooa4PkmOCsrM>O-8w?|QN|GRIK->I4nw*U zz)z^3yU#6u_ExEPp?wM={PXq%eQ?{Ot9;;6+?r1oLv%i$(A~tO07WVTeQHxEGv_dZ zMT;aUCuL}MYfJU;$v|c0ai~puLc9$|!$#SRB*Rcd$rWER_rJq#hnx44L%7%@=j9hW zv>OcBj=Ptqp|NdRyUla@_a9jKpD$OKL)2RG$fiPn0#8}_!pc|svB58>I$Z0FMhLU4 z;cL5|5z@#h*JC#g<#deWjlk(;fviLW+sak2sM<@4?Zf53LtkpGd^`Icq;D_%uyWTe zyqzdn`TK3RQ-1;KyCr~?mA19!l1xKRMfcJzG^|sN+f+-uSv75mN9w{!)>|A!XWayx zjFxeK)&_aL0DKGag`FfM*%F!B!#1iIRXkH8p2c4=C|pcq2eXT_gH`l%aFO2xm!G%n z-A3bze}3E*T|V`T?PCAZ4u%}y!<|9SWj!4C4VAN?Dq+Z6Y{;ck1l6=EIwq)e~?J0jsCu7W^=73H~Z> z=_8umG7JxD^lp!^Q}okh2b4dB8Eu;=!hjOUySXxNm>pv>oYf3rxQ*8xSmD6-x0)fS zWsfG2iMTR}SSk6~w!1xk19<^3(1^~E4)eJ)Kvidnvon10Qm5ZffN%u+y*odRWBG`G zgzBKYJ>xyPTyeh@=Jzzp+`*R43@GCTRqa%KXDJ0ey__bx6$8^^(Mz4~G75&maH!So ze9UM@S7}y4N9h`H+-oy@MC3@^PC9YoC9`SAkO;AQMMfV%cEr%`;IJwl$%@Bqg>MmZ zv#SkJ7fMRw!CBE>Zn>~^Mqh�)c~&xsn`RDbunwClozVg*eMKwU? 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
QXQ7M>1m1bHgN)Y{{q`6=orhOH-hj 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

e_P2Ov?c@y~Z8}Mb^L5eydcp(RA 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 delta 22728 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 None: - """Init the project so it has a manifest + the template .gitignore.""" - monkeypatch.chdir(project_dir) - with patch.object(template_cli, 'ensure_cache', return_value=template_cli._cache_dir('v2')): - template_cli.update('v2', 'basic', 'playbook', force=True) - - def test_collect_keys_includes_gitignore(self, template_cli): - """_collect_template_file_keys for playbook/basic now contains '.gitignore'.""" - cache_dir = template_cli._cache_dir('v2') - keys = template_cli._collect_template_file_keys(cache_dir, 'basic', 'playbook') - - assert '.gitignore' in keys - - def test_locally_modified_gitignore_force_overwritten_default( - self, template_cli, project_dir, monkeypatch - ): - """A locally-edited .gitignore is restored to template content with no prompt.""" - self._init(template_cli, project_dir, monkeypatch) - - # sanity: the initialized .gitignore is the 1508-byte template content - project_gitignore = project_dir / '.gitignore' - assert _sha256_file(project_gitignore) == GITIGNORE_CONTENT_SHA - - # user edits .gitignore locally - project_gitignore.write_text('# user gitignore edits\n') - assert _sha256_file(project_gitignore) != GITIGNORE_CONTENT_SHA - - with ( - patch.object(template_cli, 'ensure_cache', return_value=template_cli._cache_dir('v2')), - patch('tcex_cli.render.render.Render.prompt.ask') as mock_prompt, - ): - template_cli.update('v2', 'basic', 'playbook') - - # template-owned .gitignore overwritten back to the 1508-byte template content - assert _sha256_file(project_gitignore) == GITIGNORE_CONTENT_SHA - # and the user was never prompted for it - prompted_files = [call.args[0] for call in mock_prompt.call_args_list] - assert not any('gitignore' in f for f in prompted_files) - - def test_locally_modified_gitignore_force_overwritten_no_prompt( - self, template_cli, project_dir, monkeypatch - ): - """Under --no-prompt, a locally-edited .gitignore is force-overwritten, not preserved.""" - self._init(template_cli, project_dir, monkeypatch) - - project_gitignore = project_dir / '.gitignore' - project_gitignore.write_text('# user gitignore edits\n') - - # simulate the template moving on so the file is a clear update candidate - manifest = json.loads((project_dir / 'manifest.json').read_text()) - manifest['.gitignore']['last_commit'] = 'old_commit_before_update' - (project_dir / 'manifest.json').write_text(json.dumps(manifest, indent=2)) - - with ( - patch.object(template_cli, 'ensure_cache', return_value=template_cli._cache_dir('v2')), - patch('tcex_cli.render.render.Render.prompt.ask') as mock_prompt, - ): - template_cli.update('v2', 'basic', 'playbook', no_prompt=True) - - # template-owned .gitignore overwritten (not preserved as a user file) - assert _sha256_file(project_gitignore) == GITIGNORE_CONTENT_SHA - # zero prompts during the entire run - mock_prompt.assert_not_called() diff --git a/tests/template/test_managed.py b/tests/template/test_managed.py new file mode 100644 index 0000000..6ae9a66 --- /dev/null +++ b/tests/template/test_managed.py @@ -0,0 +1,417 @@ +"""Synthetic-manifest unit tests for the ``managed`` flag (``tcex update --managed``). + +These tests are entirely hand-crafted: they build tiny manifests / cache trees on +``tmp_path`` and never depend on the committed ``tcex-app-templates-v2.zip`` fixture +(which is regenerated by the templates repo and swapped in later). They cover: + +* ``Planner.build(managed_only=True)`` routing — managed-and-changed → ``auto_update``, + managed-and-identical → ``skip``, non-managed → ``skip``, all removed keys → ``skip`` + (with empty ``prompt_user`` and empty ``template_removed`` so ``--managed`` can never + prompt or delete). +* ``managed_only`` being independent of ``force``. +* ``_build_merged_template`` propagating ``'managed'`` through both the normal and the + ``.gitignore`` special-case branches (including the ``_app_common`` ``.gitignore`` → + ``managed: true`` canary). +* ``apply()`` on a ``managed_only`` plan performing copies without raising and deleting + nothing. +""" + +# standard library +import hashlib +import json +from pathlib import Path + +# third-party +import pytest + +# first-party +from tcex_cli.cli.template.planner import Hasher, ManifestStore, Planner, SafeFileOps +from tcex_cli.cli.template.template_cli import TemplateCli + +# ================================================================== +# Helpers +# ================================================================== + + +def _sha256_bytes(data: bytes) -> str: + """Return the SHA-256 hex digest of bytes.""" + return hashlib.sha256(data).hexdigest() + + +def _write_manifest(path: Path, meta: dict) -> None: + """Write a manifest.json to path.""" + path.write_text(json.dumps(meta, indent=2)) + + +def _make_planner() -> Planner: + """Return a Planner wired with the real (stateless) collaborators.""" + return Planner(ManifestStore(), Hasher(), SafeFileOps()) + + +def _entry(content: bytes, *, last_commit: str, managed: bool, template_path: str) -> dict: + """Build a single manifest FileMeta entry for ``content``.""" + return { + 'last_commit': last_commit, + 'sha256': _sha256_bytes(content), + 'template_path': template_path, + 'managed': managed, + } + + +# ================================================================== +# Planner.build(managed_only=True) — routing +# ================================================================== +class TestManagedOnlyRouting: + """``managed_only`` routes keys to skip/auto_update; never prompts or deletes.""" + + @staticmethod + def _build_scenario(tmp_path: Path) -> tuple[Path, Path]: + """Create a template + project covering every managed_only routing case. + + Template keys: + * ``managed_changed.py`` — managed, local content differs → auto_update. + * ``managed_identical.py`` — managed, local content identical → skip. + * ``unmanaged.py`` — not managed (modified locally) → skip (out of scope). + Removed-from-template key (present locally, absent in template): + * ``dropped.py`` — managed in the local manifest, modified on disk → skip + (``--managed`` never deletes). + """ + template_dir = tmp_path / 'template' + project_dir = tmp_path / 'project' + template_dir.mkdir() + project_dir.mkdir() + + managed_changed = b'# template managed (new)\n' + managed_identical = b'# template managed (stable)\n' + unmanaged = b'# template unmanaged (new)\n' + + (template_dir / 'managed_changed.py').write_bytes(managed_changed) + (template_dir / 'managed_identical.py').write_bytes(managed_identical) + (template_dir / 'unmanaged.py').write_bytes(unmanaged) + + template_meta = { + 'managed_changed.py': _entry( + managed_changed, + last_commit='c-new', + managed=True, + template_path='managed_changed.py', + ), + 'managed_identical.py': _entry( + managed_identical, + last_commit='c-new', + managed=True, + template_path='managed_identical.py', + ), + 'unmanaged.py': _entry( + unmanaged, last_commit='c-new', managed=False, template_path='unmanaged.py' + ), + } + _write_manifest(template_dir / 'manifest.json', template_meta) + + # local manifest: tracks all three template keys at an older commit plus a + # key the template no longer ships (dropped.py). + dropped = b'# old dropped file\n' + local_meta = { + 'managed_changed.py': _entry( + b'# old managed content\n', + last_commit='c-old', + managed=True, + template_path='managed_changed.py', + ), + 'managed_identical.py': _entry( + managed_identical, + last_commit='c-old', + managed=True, + template_path='managed_identical.py', + ), + 'unmanaged.py': _entry( + b'# old unmanaged content\n', + last_commit='c-old', + managed=False, + template_path='unmanaged.py', + ), + 'dropped.py': _entry( + dropped, last_commit='c-old', managed=True, template_path='dropped.py' + ), + } + _write_manifest(project_dir / 'manifest.json', local_meta) + + # on-disk local files + (project_dir / 'managed_changed.py').write_bytes(b'# old managed content\n') + (project_dir / 'managed_identical.py').write_bytes(managed_identical) + (project_dir / 'unmanaged.py').write_bytes(b'# user edits to unmanaged\n') + (project_dir / 'dropped.py').write_bytes(b'# user edits to dropped\n') + + return template_dir, project_dir + + def test_managed_changed_goes_to_auto_update(self, tmp_path): + """A managed file whose local hash differs from the template → auto_update.""" + template_dir, project_dir = self._build_scenario(tmp_path) + plan = _make_planner().build(template_dir, project_dir, managed_only=True) + + assert ('managed_changed.py', 'managed_changed.py') in plan.auto_update + assert ('managed_changed.py', 'managed_changed.py') not in plan.skip + + def test_managed_identical_skips(self, tmp_path): + """A managed file already matching the template hash → skip (no rewrite).""" + template_dir, project_dir = self._build_scenario(tmp_path) + plan = _make_planner().build(template_dir, project_dir, managed_only=True) + + assert ('managed_identical.py', 'managed_identical.py') in plan.skip + assert ('managed_identical.py', 'managed_identical.py') not in plan.auto_update + + def test_unmanaged_skips(self, tmp_path): + """A non-managed file (even if locally modified) → skip (out of scope).""" + template_dir, project_dir = self._build_scenario(tmp_path) + plan = _make_planner().build(template_dir, project_dir, managed_only=True) + + assert ('unmanaged.py', 'unmanaged.py') in plan.skip + assert ('unmanaged.py', 'unmanaged.py') not in plan.auto_update + + def test_removed_key_skips(self, tmp_path): + """A key removed from the template → skip (never appended to template_removed).""" + template_dir, project_dir = self._build_scenario(tmp_path) + plan = _make_planner().build(template_dir, project_dir, managed_only=True) + + assert ('dropped.py', 'dropped.py') in plan.skip + assert ('dropped.py', 'dropped.py') not in plan.auto_update + + def test_never_prompts_and_never_removes(self, tmp_path): + """``--managed`` can never prompt or delete: both lists are empty.""" + template_dir, project_dir = self._build_scenario(tmp_path) + plan = _make_planner().build(template_dir, project_dir, managed_only=True) + + assert plan.prompt_user == [], 'managed_only must never populate prompt_user' + assert plan.template_removed == [], 'managed_only must never populate template_removed' + + +# ================================================================== +# Planner.build — managed_only is independent of force +# ================================================================== +class TestManagedOnlyIndependentOfForce: + """``managed_only`` routing is identical with ``force=True`` and ``force=False``.""" + + @staticmethod + def _build(tmp_path: Path) -> tuple[Path, Path]: + """Template + project: one managed-changed file and one unmanaged file.""" + template_dir = tmp_path / 'template' + project_dir = tmp_path / 'project' + template_dir.mkdir() + project_dir.mkdir() + + managed_new = b'# managed new\n' + unmanaged_new = b'# unmanaged new\n' + (template_dir / 'managed.py').write_bytes(managed_new) + (template_dir / 'unmanaged.py').write_bytes(unmanaged_new) + + template_meta = { + 'managed.py': _entry( + managed_new, last_commit='c-new', managed=True, template_path='managed.py' + ), + 'unmanaged.py': _entry( + unmanaged_new, last_commit='c-new', managed=False, template_path='unmanaged.py' + ), + } + _write_manifest(template_dir / 'manifest.json', template_meta) + + local_meta = { + 'managed.py': _entry( + b'# managed old\n', last_commit='c-old', managed=True, template_path='managed.py' + ), + 'unmanaged.py': _entry( + b'# unmanaged old\n', + last_commit='c-old', + managed=False, + template_path='unmanaged.py', + ), + } + _write_manifest(project_dir / 'manifest.json', local_meta) + + (project_dir / 'managed.py').write_bytes(b'# managed old\n') + (project_dir / 'unmanaged.py').write_bytes(b'# unmanaged old\n') + + return template_dir, project_dir + + @pytest.mark.parametrize( + argnames='force', + argvalues=[ + pytest.param(False, id='force-false'), + pytest.param(True, id='force-true'), + ], + ) + def test_managed_routing_unaffected_by_force(self, tmp_path, force): + """The managed_only plan is the same regardless of the ``force`` flag.""" + template_dir, project_dir = self._build(tmp_path) + plan = _make_planner().build(template_dir, project_dir, force=force, managed_only=True) + + # managed file routed to auto_update; unmanaged skipped; force had no effect + assert ('managed.py', 'managed.py') in plan.auto_update + assert ('unmanaged.py', 'unmanaged.py') in plan.skip + assert ('unmanaged.py', 'unmanaged.py') not in plan.auto_update + assert plan.prompt_user == [] + assert plan.template_removed == [] + + +# ================================================================== +# Planner.apply — managed_only plan copies, raises nothing, deletes nothing +# ================================================================== +class TestManagedOnlyApply: + """Applying a managed_only plan performs copies and removes no files.""" + + def test_apply_copies_without_raising_and_deletes_nothing(self, tmp_path): + """auto_update copies the managed file; a sibling on disk is left untouched.""" + template_dir = tmp_path / 'template' + project_dir = tmp_path / 'project' + template_dir.mkdir() + project_dir.mkdir() + + managed_new = b'# managed new\n' + (template_dir / 'managed.py').write_bytes(managed_new) + _write_manifest( + template_dir / 'manifest.json', + { + 'managed.py': _entry( + managed_new, last_commit='c-new', managed=True, template_path='managed.py' + ) + }, + ) + + _write_manifest( + project_dir / 'manifest.json', + { + 'managed.py': _entry( + b'# managed old\n', + last_commit='c-old', + managed=True, + template_path='managed.py', + ) + }, + ) + (project_dir / 'managed.py').write_bytes(b'# managed old\n') + # an untracked local file that must survive the apply() + (project_dir / 'keep_me.py').write_bytes(b'# untouched local file\n') + + planner = _make_planner() + plan = planner.build(template_dir, project_dir, managed_only=True) + + # apply returns None and must not raise (no removals to crash on) + result = planner.apply(plan, template_root=template_dir, project_root=project_dir) + assert result is None + + # managed file copied from template; sibling untouched; nothing deleted + assert (project_dir / 'managed.py').read_bytes() == managed_new + assert (project_dir / 'keep_me.py').read_bytes() == b'# untouched local file\n' + + +# ================================================================== +# _build_merged_template — propagates 'managed' (normal + .gitignore branch) +# ================================================================== +class TestBuildMergedTemplateManaged: + """``_build_merged_template`` copies ``managed`` into both manifest branches.""" + + @staticmethod + def _make_cli(tmp_path: Path) -> TemplateCli: + """Return a TemplateCli with cli_out_path pinned under tmp_path.""" + cli = TemplateCli(proxy_host=None, proxy_port=None, proxy_user=None, proxy_pass=None) + cli.__dict__['cli_out_path'] = tmp_path / '.tcex' + return cli + + @staticmethod + def _build_cache(cache_dir: Path) -> None: + """Hand-build a minimal cache: ``_app_common`` parent + ``playbook/basic`` leaf. + + ``_app_common`` ships: + * ``pyproject.toml`` (managed boilerplate, normal branch) + * ``app.py`` (unmanaged app logic, normal branch) + * ``gitignore`` (managed boilerplate, renamed → ``.gitignore`` special case) + ``playbook/basic`` ships: + * ``playbook_app.py`` (managed scaffold, normal branch) + """ + common = cache_dir / '_app_common' + leaf = cache_dir / 'playbook' / 'basic' + common.mkdir(parents=True) + leaf.mkdir(parents=True) + + # --- _app_common files + manifest --- + pyproject = b'[project]\nname = "x"\n' + app_py = b'# app logic\n' + gitignore = b'*.pyc\n__pycache__/\n' + (common / 'pyproject.toml').write_bytes(pyproject) + (common / 'app.py').write_bytes(app_py) + (common / 'gitignore').write_bytes(gitignore) + (common / 'template.yaml').write_text( + 'contributor: tc\ndescription: app common\nsummary: app common\nversion: 1.0.0\n' + 'template_files:\n - pyproject.toml\n - gitignore\n' + ) + common_manifest = { + 'pyproject.toml': _entry( + pyproject, last_commit='c1', managed=True, template_path='pyproject.toml' + ), + 'app.py': _entry(app_py, last_commit='c1', managed=False, template_path='app.py'), + # the renamed gitignore is recorded under the dotted key with managed=True + '.gitignore': _entry( + gitignore, last_commit='c1', managed=True, template_path='.gitignore' + ), + } + _write_manifest(common / 'manifest.json', common_manifest) + + # --- playbook/basic files + manifest --- + playbook_app = b'# playbook scaffold\n' + (leaf / 'playbook_app.py').write_bytes(playbook_app) + (leaf / 'template.yaml').write_text( + 'contributor: tc\ndescription: basic\nsummary: basic\nversion: 1.0.0\n' + 'template_parents:\n - _app_common\n' + 'template_files:\n - playbook_app.py\n' + ) + leaf_manifest = { + 'playbook_app.py': _entry( + playbook_app, last_commit='c1', managed=True, template_path='playbook_app.py' + ), + } + _write_manifest(leaf / 'manifest.json', leaf_manifest) + + @staticmethod + def _cleanup(merged_dir: Path) -> None: + """Remove a caller-owned merged temp dir.""" + for p in sorted(merged_dir.rglob('*'), reverse=True): + p.unlink() if p.is_file() else p.rmdir() + merged_dir.rmdir() + + def _merged_manifest(self, tmp_path: Path) -> dict: + """Build the merged template from the synthetic cache and return its manifest.""" + cli = self._make_cli(tmp_path) + cache_dir = tmp_path / 'cache' + cache_dir.mkdir() + self._build_cache(cache_dir) + + merged_dir = cli._build_merged_template(cache_dir, 'basic', 'playbook') + try: + return json.loads((merged_dir / 'manifest.json').read_text()) + finally: + self._cleanup(merged_dir) + + def test_normal_branch_propagates_managed_true(self, tmp_path): + """A managed parent file keeps ``managed: true`` in the merged manifest.""" + manifest = self._merged_manifest(tmp_path) + assert manifest['pyproject.toml']['managed'] is True + + def test_normal_branch_propagates_managed_false(self, tmp_path): + """An unmanaged parent file keeps ``managed: false`` in the merged manifest.""" + manifest = self._merged_manifest(tmp_path) + assert manifest['app.py']['managed'] is False + + def test_leaf_branch_propagates_managed_true(self, tmp_path): + """A managed leaf (child) file keeps ``managed: true`` in the merged manifest.""" + manifest = self._merged_manifest(tmp_path) + assert manifest['playbook_app.py']['managed'] is True + + def test_gitignore_special_case_propagates_managed_true(self, tmp_path): + """The renamed ``_app_common`` ``.gitignore`` resolves to ``managed: true``. + + Finding-#2/#3 regression canary: the special-case branch must read + ``managed`` from the same parent entry it reuses for ``last_commit``. + """ + manifest = self._merged_manifest(tmp_path) + assert manifest['.gitignore']['managed'] is True + # template_path is the dotted project-relative name + assert manifest['.gitignore']['template_path'] == '.gitignore' diff --git a/tests/template/test_merged_template.py b/tests/template/test_merged_template.py index b544bac..55899f5 100644 --- a/tests/template/test_merged_template.py +++ b/tests/template/test_merged_template.py @@ -166,7 +166,6 @@ def test_inherits_app_common(self, merged_organization: Path): class TestBuildMergedThreeLevelChain: - def test_egress_has_files_from_all_levels(self, merged_egress: Path): assert (merged_egress / 'requirements.txt').is_file() assert (merged_egress / 'pyproject.toml').is_file() diff --git a/tests/template/test_template_abc.py b/tests/template/test_template_abc.py index 72be41b..df69c53 100644 --- a/tests/template/test_template_abc.py +++ b/tests/template/test_template_abc.py @@ -21,9 +21,7 @@ def cli(tmp_path): """Return a bare TemplateCli instance with tmp_path as cli_out_path.""" tcex_dir = tmp_path / '.tcex' tcex_dir.mkdir(parents=True, exist_ok=True) - inst = TemplateCli( - proxy_host=None, proxy_port=None, proxy_user=None, proxy_pass=None - ) + inst = TemplateCli(proxy_host=None, proxy_port=None, proxy_user=None, proxy_pass=None) inst.__dict__['cli_out_path'] = tcex_dir return inst @@ -40,7 +38,6 @@ def create_cache_dir(cli_instance: TemplateCli, branch: str = 'v2') -> Path: # Cache directory naming # ------------------------------------------------------------------ class TestCacheDir: - def test_path_format(self, cli): result = cli._cache_dir('v2') assert result == cli.cli_out_path / 'templates' / 'templates-v2' @@ -118,7 +115,6 @@ def test_returns_cache_dir_path(self, cli): # Clear cache # ------------------------------------------------------------------ class TestClearCache: - def test_removes_directory(self, cli): cache_dir = CacheHelper.create_cache_dir(cli) (cache_dir / 'some_file.txt').write_text('data') diff --git a/tests/template/test_template_commands.py b/tests/template/test_template_commands.py index 72119bf..42b1f0c 100644 --- a/tests/template/test_template_commands.py +++ b/tests/template/test_template_commands.py @@ -229,53 +229,6 @@ def test_modified_non_template_file_preserved_when_same_version( assert (project_dir / 'app.py').read_text() == '# user modification' - def test_modified_template_owned_file_reconciled_when_same_version( - self, template_cli, project_dir, monkeypatch - ): - """A modified template-owned file is reconciled even at the same last_commit. - - playbook_app.py is template-owned (declared in playbook/basic template_files). - Even when the local manifest's last_commit still matches the template, a local - modification is overwritten back to the template content — the last_commit match - does NOT preempt the template-owned reconciliation. - """ - ProjectHelper.init_project(template_cli, project_dir, monkeypatch, 'basic', 'playbook') - - cache_dir = template_cli._cache_dir('v2') - template_content = (cache_dir / 'playbook' / 'basic' / 'playbook_app.py').read_text() - - (project_dir / 'playbook_app.py').write_text('# user modification') - - ProjectHelper.run_update(template_cli) - - assert (project_dir / 'playbook_app.py').read_text() == template_content - - def test_template_owned_file_force_overwritten_when_template_changes( - self, template_cli, project_dir, monkeypatch - ): - """A template-owned file (playbook_app.py) is overwritten without prompting. - - playbook_app.py is declared in playbook/basic template_files, so even when - the user modified it and the template changed, it is force-overwritten with - no prompt (the new template_files ownership behavior). - """ - ProjectHelper.init_project(template_cli, project_dir, monkeypatch, 'basic', 'playbook') - - cache_dir = template_cli._cache_dir('v2') - template_content = (cache_dir / 'playbook' / 'basic' / 'playbook_app.py').read_text() - - (project_dir / 'playbook_app.py').write_text('# user modification') - ProjectHelper.simulate_template_change(project_dir, 'playbook_app.py') - - with patch.object(template_cli, 'ensure_cache', return_value=template_cli._cache_dir('v2')): - with patch('tcex_cli.render.render.Render.prompt.ask') as mock_prompt: - template_cli.update('v2', 'basic', 'playbook') - - # template-owned file is overwritten from the template — no prompt issued - assert (project_dir / 'playbook_app.py').read_text() == template_content - prompted_files = [call.args[0] for call in mock_prompt.call_args_list] - assert not any('playbook_app.py' in f for f in prompted_files) - def test_unmodified_file_auto_updates_when_template_changes( self, template_cli, project_dir, monkeypatch ): @@ -353,26 +306,6 @@ def test_deleted_file_restored_on_force(self, template_cli, project_dir, monkeyp assert (project_dir / 'playbook_app.py').is_file() - def test_deleted_template_owned_file_restored_without_force( - self, template_cli, project_dir, monkeypatch - ): - """A deleted template-owned file is restored on update without --force. - - playbook_app.py is template-owned, so even at the same last_commit a missing - file is routed to auto_update and restored from the template content. - """ - ProjectHelper.init_project(template_cli, project_dir, monkeypatch, 'basic', 'playbook') - - cache_dir = template_cli._cache_dir('v2') - template_content = (cache_dir / 'playbook' / 'basic' / 'playbook_app.py').read_text() - - (project_dir / 'playbook_app.py').unlink() - - ProjectHelper.run_update(template_cli) - - assert (project_dir / 'playbook_app.py').is_file() - assert (project_dir / 'playbook_app.py').read_text() == template_content - def test_deleted_non_template_file_skipped_without_force( self, template_cli, project_dir, monkeypatch ): @@ -488,23 +421,6 @@ def test_in_sync_files_skipped(self, template_cli, project_dir, monkeypatch): assert (project_dir / 'app.py').read_text() == before - def test_modified_template_file_auto_updates(self, template_cli, project_dir, monkeypatch): - """A template_file that the user modified should be auto-updated (no prompt).""" - self._simulate_legacy_project(template_cli, project_dir, monkeypatch) - - original = (project_dir / 'playbook_app.py').read_text() - (project_dir / 'playbook_app.py').write_text('# user modification') - - with patch.object(template_cli, 'ensure_cache', return_value=template_cli._cache_dir('v2')): - with patch('tcex_cli.render.render.Render.prompt.ask') as mock_prompt: - template_cli.update('v2', 'basic', 'playbook') - - # playbook_app.py is template-owned → force-overwritten back to the template - # copy with no prompt (the new template_files ownership behavior) - assert (project_dir / 'playbook_app.py').read_text() == original - prompted_files = [call.args[0] for call in mock_prompt.call_args_list] - assert not any('playbook_app.py' in f for f in prompted_files) - def test_modified_non_template_file_prompts(self, template_cli, project_dir, monkeypatch): """A non-template_file that the user modified should prompt (and be preserved on 'N').""" self._simulate_legacy_project(template_cli, project_dir, monkeypatch) diff --git a/tests/template/test_template_files_force.py b/tests/template/test_template_files_force.py deleted file mode 100644 index 3fd77bb..0000000 --- a/tests/template/test_template_files_force.py +++ /dev/null @@ -1,533 +0,0 @@ -"""Tests for the template_files force-overwrite + --no-prompt update feature. - -Covers: -* TemplateCli._collect_template_file_keys — union of template_files across the - resolved parent chain (real fixture data). -* Planner.build(force_keys=...) — would-be prompt_user entries whose key is in - force_keys are routed to auto_update; others keep prompt-on-modify behavior. -* Planner.apply(no_prompt=True) — prompt_user files are left untouched on disk - and returned in the preserved list (prompt_fn is never called); force=True - still overwrites and returns an empty preserved list. -* End-to-end TemplateCli.update(..., no_prompt=True) over the fixture cache. -* Regression: default update still prompts for a modified non-template file. -""" - -# standard library -import hashlib -import json -from pathlib import Path -from unittest.mock import patch - -# first-party -from tcex_cli.cli.template.planner import Hasher, ManifestStore, Plan, Planner, SafeFileOps - -# ================================================================== -# Helpers -# ================================================================== - - -def _sha256_bytes(data: bytes) -> str: - """Return the SHA-256 hex digest of bytes.""" - return hashlib.sha256(data).hexdigest() - - -def _write_manifest(path: Path, meta: dict) -> None: - """Write a manifest.json to path.""" - path.write_text(json.dumps(meta, indent=2)) - - -def _make_planner() -> Planner: - """Return a Planner wired with the real (stateless) collaborators.""" - return Planner(ManifestStore(), Hasher(), SafeFileOps()) - - -class RecordingPrompt: - """A prompt_fn stand-in that records calls and raises if invoked. - - Used to prove no_prompt never prompts: any call fails the test loudly. - """ - - def __init__(self) -> None: - """Initialize the call recorder.""" - self.calls: list[str] = [] - - def __call__(self, msg: str) -> str: - """Record the prompt and fail — should never be reached under no_prompt.""" - self.calls.append(msg) - ex_msg = f'prompt_fn was called unexpectedly: {msg!r}' - raise AssertionError(ex_msg) - - -# ================================================================== -# _collect_template_file_keys -# ================================================================== -class TestCollectTemplateFileKeys: - """Union of template_files across the resolved parent chain.""" - - def test_playbook_basic_union(self, template_cli): - """playbook/basic + parent _app_common template_files union (the seven files). - - ``_app_common`` lists ``gitignore`` in its ``template_files`` (renamed to the - on-disk ``.gitignore`` key), so the union is the leaf-owned files plus the four - parent-owned files including ``.gitignore``. - """ - cache_dir = template_cli._cache_dir('v2') - keys = template_cli._collect_template_file_keys(cache_dir, 'basic', 'playbook') - - assert keys == { - 'playbook_app.py', - 'run.py', - 'run_local.py', - '.coveragerc', - '.pre-commit-config.yaml', - 'pyproject.toml', - '.gitignore', - } - - def test_includes_parent_keys(self, template_cli): - """The parent (_app_common) keys are present in the union, not just the leaf's.""" - cache_dir = template_cli._cache_dir('v2') - keys = template_cli._collect_template_file_keys(cache_dir, 'basic', 'playbook') - - # leaf-owned - assert 'playbook_app.py' in keys - # parent-owned - assert 'pyproject.toml' in keys - # parent-owned, renamed gitignore -> .gitignore (now template-owned) - assert '.gitignore' in keys - - -# ================================================================== -# Planner.build(force_keys=...) -# ================================================================== -class TestPlannerBuildForceKeys: - """force_keys routes would-be prompt_user entries to auto_update.""" - - @staticmethod - def _setup_diverged(tmp_path: Path) -> tuple[Path, Path]: - """Create a template dir + project dir where a tracked file diverged. - - The template file content, the local manifest's recorded sha256, and the - on-disk local content are all distinct, so the planner's hash comparison - falls through to the "user modified" (prompt_user) branch. - """ - template_dir = tmp_path / 'template' - project_dir = tmp_path / 'project' - template_dir.mkdir() - project_dir.mkdir() - - # template ships new content for owned.py - new_content = b'# template version\n' - (template_dir / 'owned.py').write_bytes(new_content) - template_meta = { - 'owned.py': { - 'last_commit': 'new_commit', - 'sha256': _sha256_bytes(new_content), - 'template_path': 'owned.py', - } - } - _write_manifest(template_dir / 'manifest.json', template_meta) - - # local project last synced an older version (recorded sha differs) - old_content = b'# original template version\n' - local_meta = { - 'owned.py': { - 'last_commit': 'old_commit', - 'sha256': _sha256_bytes(old_content), - 'template_path': 'owned.py', - } - } - _write_manifest(project_dir / 'manifest.json', local_meta) - - # but the user edited it to something else entirely → diverged - (project_dir / 'owned.py').write_bytes(b'# user edits\n') - - return template_dir, project_dir - - def test_diverged_file_in_force_keys_goes_to_auto_update(self, tmp_path): - """A modified, diverged file routes to auto_update when its key is forced.""" - template_dir, project_dir = self._setup_diverged(tmp_path) - planner = _make_planner() - - plan = planner.build(template_dir, project_dir, force_keys={'owned.py'}) - - assert ('owned.py', 'owned.py') in plan.auto_update - assert ('owned.py', 'owned.py') not in plan.prompt_user - - def test_diverged_file_not_in_force_keys_stays_prompt_user(self, tmp_path): - """The same diverged file stays in prompt_user when not forced.""" - template_dir, project_dir = self._setup_diverged(tmp_path) - planner = _make_planner() - - plan = planner.build(template_dir, project_dir, force_keys=set()) - - assert ('owned.py', 'owned.py') in plan.prompt_user - assert ('owned.py', 'owned.py') not in plan.auto_update - - def test_new_file_on_disk_in_force_keys_goes_to_auto_update(self, tmp_path): - """New template file already on disk: forced → auto_update, not prompt_user.""" - template_dir = tmp_path / 'template' - project_dir = tmp_path / 'project' - template_dir.mkdir() - project_dir.mkdir() - - content = b'# new template file\n' - (template_dir / 'fresh.py').write_bytes(content) - _write_manifest( - template_dir / 'manifest.json', - { - 'fresh.py': { - 'last_commit': 'c1', - 'sha256': _sha256_bytes(content), - 'template_path': 'fresh.py', - } - }, - ) - # local manifest has no entry for fresh.py (new), but file exists on disk - _write_manifest(project_dir / 'manifest.json', {}) - (project_dir / 'fresh.py').write_bytes(b'# pre-existing local file\n') - - planner = _make_planner() - - forced = planner.build(template_dir, project_dir, force_keys={'fresh.py'}) - assert ('fresh.py', 'fresh.py') in forced.auto_update - assert ('fresh.py', 'fresh.py') not in forced.prompt_user - - unforced = planner.build(template_dir, project_dir, force_keys=set()) - assert ('fresh.py', 'fresh.py') in unforced.prompt_user - assert ('fresh.py', 'fresh.py') not in unforced.auto_update - - -# ================================================================== -# Planner.build — template-owned reconciliation ignores last_commit match -# ================================================================== -class TestPlannerBuildForceKeysSameCommit: - """Regression: a force_keys file is reconciled even when last_commit matches. - - The closed gap: a locally-modified template-owned file whose local manifest - last_commit STILL MATCHES the template was previously short-circuited to skip - (never restored). The force_keys reconciliation must run BEFORE the - last_commit short-circuit. - """ - - @staticmethod - def _setup_same_commit(tmp_path: Path, *, local_content: bytes) -> tuple[Path, Path, bytes]: - """Template + project where last_commit matches but on-disk content may differ. - - The local manifest's ``last_commit`` is identical to the template's, so the - ``last_commit ==`` short-circuit would skip the file if it ran first. ``sha256`` - is also recorded as the template hash (a clean prior sync). The on-disk content - is driven by ``local_content`` so the caller can exercise both the diverged and - identical cases. - """ - template_dir = tmp_path / 'template' - project_dir = tmp_path / 'project' - template_dir.mkdir() - project_dir.mkdir() - - template_bytes = b'# template version\n' - (template_dir / 'owned.py').write_bytes(template_bytes) - - shared_commit = 'same_commit' - meta = { - 'owned.py': { - 'last_commit': shared_commit, - 'sha256': _sha256_bytes(template_bytes), - 'template_path': 'owned.py', - } - } - _write_manifest(template_dir / 'manifest.json', meta) - # local manifest records the SAME last_commit (clean prior sync) - _write_manifest(project_dir / 'manifest.json', meta) - - # on-disk content per the caller's scenario - (project_dir / 'owned.py').write_bytes(local_content) - - return template_dir, project_dir, template_bytes - - def test_modified_force_key_same_commit_goes_to_auto_update(self, tmp_path): - """Modified template-owned file at matching last_commit → auto_update (restored).""" - template_dir, project_dir, _template_bytes = self._setup_same_commit( - tmp_path, local_content=b'# user edits\n' - ) - planner = _make_planner() - - plan = planner.build(template_dir, project_dir, force_keys={'owned.py'}) - - assert ('owned.py', 'owned.py') in plan.auto_update - assert ('owned.py', 'owned.py') not in plan.skip - assert ('owned.py', 'owned.py') not in plan.prompt_user - - def test_deleted_force_key_same_commit_goes_to_auto_update(self, tmp_path): - """Missing template-owned file at matching last_commit → auto_update (restored).""" - template_dir, project_dir, _template_bytes = self._setup_same_commit( - tmp_path, local_content=b'placeholder\n' - ) - (project_dir / 'owned.py').unlink() - planner = _make_planner() - - plan = planner.build(template_dir, project_dir, force_keys={'owned.py'}) - - assert ('owned.py', 'owned.py') in plan.auto_update - assert ('owned.py', 'owned.py') not in plan.skip - - def test_identical_force_key_same_commit_skips(self, tmp_path): - """Template-owned file already matching the template hash → skip (no rewrite).""" - template_dir, project_dir, template_bytes = self._setup_same_commit( - tmp_path, local_content=b'# template version\n' - ) - # sanity: on-disk content matches the template byte-for-byte - assert (project_dir / 'owned.py').read_bytes() == template_bytes - planner = _make_planner() - - plan = planner.build(template_dir, project_dir, force_keys={'owned.py'}) - - assert ('owned.py', 'owned.py') in plan.skip - assert ('owned.py', 'owned.py') not in plan.auto_update - - def test_modified_non_force_key_same_commit_skips(self, tmp_path): - """The same modified file, NOT force-owned, still skips at matching last_commit.""" - template_dir, project_dir, _template_bytes = self._setup_same_commit( - tmp_path, local_content=b'# user edits\n' - ) - planner = _make_planner() - - plan = planner.build(template_dir, project_dir, force_keys=set()) - - # non-template file at matching last_commit short-circuits to skip - assert ('owned.py', 'owned.py') in plan.skip - assert ('owned.py', 'owned.py') not in plan.auto_update - - -# ================================================================== -# End-to-end — modified template-owned file restored at matching last_commit -# ================================================================== -class TestUpdateRestoresTemplateOwnedSameCommit: - """End-to-end: a modified template-owned file is restored without a manifest bump.""" - - @staticmethod - def _init(template_cli, project_dir, monkeypatch) -> None: - monkeypatch.chdir(project_dir) - with patch.object(template_cli, 'ensure_cache', return_value=template_cli._cache_dir('v2')): - template_cli.update('v2', 'basic', 'playbook', force=True) - - def test_modified_run_py_restored_with_unchanged_manifest( - self, template_cli, project_dir, monkeypatch - ): - """run.py (template-owned) is overwritten back to the template with no prompt. - - The local manifest is left untouched (last_commit still matches the template), - proving the last_commit match does not preempt template-owned reconciliation. - """ - self._init(template_cli, project_dir, monkeypatch) - - cache_dir = template_cli._cache_dir('v2') - template_run = (cache_dir / 'playbook' / 'basic' / 'run.py').read_text() - - # modify a template-owned file WITHOUT touching the manifest - (project_dir / 'run.py').write_text('# user run.py edits') - - with ( - patch.object(template_cli, 'ensure_cache', return_value=template_cli._cache_dir('v2')), - patch('tcex_cli.render.render.Render.prompt.ask') as mock_prompt, - ): - template_cli.update('v2', 'basic', 'playbook') - - assert (project_dir / 'run.py').read_text() == template_run - prompted_files = [call.args[0] for call in mock_prompt.call_args_list] - assert not any('run.py' in f for f in prompted_files) - - def test_unmodified_run_py_skipped_with_unchanged_manifest( - self, template_cli, project_dir, monkeypatch - ): - """An untouched template-owned file already matching the template is skipped.""" - self._init(template_cli, project_dir, monkeypatch) - - cache_dir = template_cli._cache_dir('v2') - template_run = (cache_dir / 'playbook' / 'basic' / 'run.py').read_text() - - with ( - patch.object(template_cli, 'ensure_cache', return_value=template_cli._cache_dir('v2')), - patch('tcex_cli.render.render.Render.prompt.ask') as mock_prompt, - ): - template_cli.update('v2', 'basic', 'playbook') - - # unchanged on disk (already matched → skipped, no needless rewrite) - assert (project_dir / 'run.py').read_text() == template_run - mock_prompt.assert_not_called() - - -# ================================================================== -# Planner.apply(no_prompt=...) -# ================================================================== -class TestPlannerApplyNoPrompt: - """no_prompt preserves prompt_user files; force still overwrites.""" - - @staticmethod - def _setup_apply(tmp_path: Path) -> tuple[Path, Path, bytes]: - """Create a template + project where prompt.py is a modified prompt_user file.""" - template_dir = tmp_path / 'template' - project_dir = tmp_path / 'project' - template_dir.mkdir() - project_dir.mkdir() - - template_bytes = b'# template content\n' - (template_dir / 'prompt.py').write_bytes(template_bytes) - - local_bytes = b'# user content\n' - (project_dir / 'prompt.py').write_bytes(local_bytes) - - return template_dir, project_dir, template_bytes - - def test_no_prompt_preserves_and_records_without_prompting(self, tmp_path): - """no_prompt: file untouched on disk, returned preserved, prompt_fn never called.""" - template_dir, project_dir, _template_bytes = self._setup_apply(tmp_path) - local_bytes = (project_dir / 'prompt.py').read_bytes() - - plan = Plan(prompt_user=[('prompt.py', 'prompt.py')]) - planner = _make_planner() - recorder = RecordingPrompt() - - preserved = planner.apply( - plan, - template_root=template_dir, - project_root=project_dir, - no_prompt=True, - prompt_fn=recorder, - ) - - # file left byte-for-byte unchanged - assert (project_dir / 'prompt.py').read_bytes() == local_bytes - # returned in the preserved list - assert ('prompt.py', 'prompt.py') in preserved - # prompt_fn never invoked - assert recorder.calls == [] - - def test_force_overwrites_and_returns_empty_preserved(self, tmp_path): - """force=True: prompt_user file is overwritten, preserved list is empty.""" - template_dir, project_dir, template_bytes = self._setup_apply(tmp_path) - - plan = Plan(prompt_user=[('prompt.py', 'prompt.py')]) - planner = _make_planner() - recorder = RecordingPrompt() - - preserved = planner.apply( - plan, - template_root=template_dir, - project_root=project_dir, - force=True, - prompt_fn=recorder, - ) - - # overwritten from template - assert (project_dir / 'prompt.py').read_bytes() == template_bytes - # no preserved entries on the force path - assert preserved == [] - # prompt_fn never invoked under force - assert recorder.calls == [] - - -# ================================================================== -# End-to-end update --no-prompt over the fixture cache -# ================================================================== -class TestUpdateNoPromptEndToEnd: - """Drive TemplateCli.update(no_prompt=True) over the real fixture cache.""" - - @staticmethod - def _init(template_cli, project_dir, monkeypatch) -> None: - monkeypatch.chdir(project_dir) - with patch.object(template_cli, 'ensure_cache', return_value=template_cli._cache_dir('v2')): - template_cli.update('v2', 'basic', 'playbook', force=True) - - @staticmethod - def _simulate_change(project_dir: Path, filename: str) -> None: - manifest = json.loads((project_dir / 'manifest.json').read_text()) - manifest[filename]['last_commit'] = 'old_commit_before_update' - (project_dir / 'manifest.json').write_text(json.dumps(manifest, indent=2)) - - def test_template_owned_file_overwritten_and_non_template_preserved( - self, template_cli, project_dir, monkeypatch - ): - """no_prompt: run.py (template-owned) overwritten; app.py (non-template) preserved. - - The whole run completes with zero prompts. - """ - self._init(template_cli, project_dir, monkeypatch) - - cache_dir = template_cli._cache_dir('v2') - template_run = (cache_dir / 'playbook' / 'basic' / 'run.py').read_text() - - # user modifies both a template-owned file and a non-template file - (project_dir / 'run.py').write_text('# user run.py edits') - (project_dir / 'app.py').write_text('# user app.py edits') - - # simulate the template moving on for both - self._simulate_change(project_dir, 'run.py') - self._simulate_change(project_dir, 'app.py') - - with ( - patch.object(template_cli, 'ensure_cache', return_value=template_cli._cache_dir('v2')), - patch('tcex_cli.render.render.Render.prompt.ask') as mock_prompt, - ): - template_cli.update('v2', 'basic', 'playbook', no_prompt=True) - - # template-owned run.py overwritten back to the template content - assert (project_dir / 'run.py').read_text() == template_run - # non-template app.py preserved byte-for-byte - assert (project_dir / 'app.py').read_text() == '# user app.py edits' - # zero prompts during the entire run - mock_prompt.assert_not_called() - - def test_no_prompt_renders_preserved_report(self, template_cli, project_dir, monkeypatch): - """no_prompt with a preserved file renders the 'Preserved - Review Manually' report.""" - self._init(template_cli, project_dir, monkeypatch) - - (project_dir / 'app.py').write_text('# user app.py edits') - self._simulate_change(project_dir, 'app.py') - - with ( - patch.object(template_cli, 'ensure_cache', return_value=template_cli._cache_dir('v2')), - patch('tcex_cli.render.render.Render.table.key_value') as mock_table, - ): - template_cli.update('v2', 'basic', 'playbook', no_prompt=True) - - titles = [call.args[0] for call in mock_table.call_args_list] - assert 'Preserved - Review Manually' in titles - - -# ================================================================== -# Regression — default update still prompts for non-template files -# ================================================================== -class TestDefaultUpdateStillPrompts: - """Without --no-prompt, a modified non-template file still prompts y/N.""" - - @staticmethod - def _init(template_cli, project_dir, monkeypatch) -> None: - monkeypatch.chdir(project_dir) - with patch.object(template_cli, 'ensure_cache', return_value=template_cli._cache_dir('v2')): - template_cli.update('v2', 'basic', 'playbook', force=True) - - @staticmethod - def _simulate_change(project_dir: Path, filename: str) -> None: - manifest = json.loads((project_dir / 'manifest.json').read_text()) - manifest[filename]['last_commit'] = 'old_commit_before_update' - (project_dir / 'manifest.json').write_text(json.dumps(manifest, indent=2)) - - def test_modified_non_template_file_prompts(self, template_cli, project_dir, monkeypatch): - """Default update prompts for app.py (non-template) and preserves it on 'N'.""" - self._init(template_cli, project_dir, monkeypatch) - - (project_dir / 'app.py').write_text('# user app.py edits') - self._simulate_change(project_dir, 'app.py') - - with ( - patch.object(template_cli, 'ensure_cache', return_value=template_cli._cache_dir('v2')), - patch('tcex_cli.render.render.Render.prompt.ask', return_value='N') as mock_prompt, - ): - template_cli.update('v2', 'basic', 'playbook') - - # app.py was prompted for (it is not template-owned) - prompted_files = [call.args[0] for call in mock_prompt.call_args_list] - assert any('app.py' in f for f in prompted_files) - # answered 'N' → preserved - assert (project_dir / 'app.py').read_text() == '# user app.py edits'