diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml
index 8252235..3015ce3 100644
--- a/.github/actions/setup/action.yml
+++ b/.github/actions/setup/action.yml
@@ -12,8 +12,5 @@ runs:
run: curl -LsSf https://astral.sh/uv/install.sh | sh
shell: bash
- name: Install dependencies
- run: |
- uv venv
- uv pip sync requirements.txt
- uv add ruff
+ run: uv sync --extra dev
shell: bash
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index f29d976..bc44e96 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -59,6 +59,11 @@ uv run ruff check .
uv run mypy .
```
+## Running docs suite
+```sh
+flask --app docs/app --debug run --host 0.0.0.0
+```
+
## Submitting Pull Requests
1. Commit your changes with a clear and descriptive commit message. Please try to follow the [Conventional Commits](https://www.conventionalcommits.org/) specification if possible.
diff --git a/cspell.json b/cspell.json
index a2e1fa1..9f037ab 100644
--- a/cspell.json
+++ b/cspell.json
@@ -1,106 +1,109 @@
{
- "version": "0.2",
- "language": "en,sv",
- "allowCompoundWords": true,
- "ignorePaths": [
- "requirements.txt",
- "ba.code-workspace",
- "**/*.nix",
- "pyproject.toml",
- ],
- "words": [
- "adformat",
- "adid",
- "adout",
- "adsize",
- "adtitle",
- "adtype",
- "blocket",
- "companyad",
- "direnv",
- "httpx",
- "incarousel",
- "lagenheter",
- "monokai",
- "mypy",
- "notinapp",
- "opblock",
- "ppid",
- "pydantic",
- "pymdownx",
- "pyproject",
- "pytest",
- "qasa",
- "recommerce",
- "respx",
- "schenvhash",
- "stjacka",
- "venv",
- "ekey",
- "pmce",
- "oweb",
- "uptodate",
- "noti",
- "dedup",
- "uptodate",
- "noti",
- "MESG",
- "AIWAYS",
- "SIDDELEY",
- "HEALEY",
- "AUTOBIANCHI",
- "CUPRA",
- "TOMASO",
- "DESOTO",
- "DFSK",
- "FISKER",
- "HEINKEL",
- "HONGQI",
- "INFINITI",
- "IVECO",
- "LEVC",
- "LYNK",
- "MAXUS",
- "SIMCA",
- "XPENG",
- "ZEEKR",
- "ABARTH",
- "Tamagotchi",
- "AGIRRA",
- "AIXAM",
- "BAJAJ",
- "BAOTIAN",
- "BENDA",
- "BENGHE",
- "BIMOTA",
- "HOSS",
- "BUELL",
- "CAGIVA",
- "CFMOTO",
- "ENERGICA",
- "HYOSUNG",
- "JAWA",
- "KYMCO",
- "LAMBRETTA",
- "LIFAN",
- "LIGIER",
- "GUZZI",
- "AGUSTA",
- "PIAGGIO",
- "QINGQI",
- "QUADRO",
- "RIEJU",
- "ROMET",
- "SHERCO",
- "SOCO",
- "TOMOS",
- "VIARELLI",
- "VOGE",
- "YADEA",
- "ADLY",
- "elts",
- "Avant",
- "TFSI",
- "quattro",
- ],
+ "version": "0.2",
+ "language": "en,sv",
+ "allowCompoundWords": true,
+ "ignorePaths": [
+ "requirements.txt",
+ "ba.code-workspace",
+ "**/*.nix",
+ "pyproject.toml"
+ ],
+ "words": [
+ "adformat",
+ "adid",
+ "adout",
+ "adsize",
+ "adtitle",
+ "adtype",
+ "blocket",
+ "companyad",
+ "direnv",
+ "httpx",
+ "incarousel",
+ "lagenheter",
+ "monokai",
+ "mypy",
+ "notinapp",
+ "opblock",
+ "ppid",
+ "pydantic",
+ "pymdownx",
+ "pyproject",
+ "pytest",
+ "qasa",
+ "recommerce",
+ "respx",
+ "schenvhash",
+ "stjacka",
+ "venv",
+ "ekey",
+ "pmce",
+ "oweb",
+ "uptodate",
+ "noti",
+ "dedup",
+ "uptodate",
+ "noti",
+ "MESG",
+ "AIWAYS",
+ "SIDDELEY",
+ "HEALEY",
+ "AUTOBIANCHI",
+ "CUPRA",
+ "TOMASO",
+ "DESOTO",
+ "DFSK",
+ "FISKER",
+ "HEINKEL",
+ "HONGQI",
+ "INFINITI",
+ "IVECO",
+ "LEVC",
+ "LYNK",
+ "MAXUS",
+ "SIMCA",
+ "XPENG",
+ "ZEEKR",
+ "ABARTH",
+ "Tamagotchi",
+ "AGIRRA",
+ "AIXAM",
+ "BAJAJ",
+ "BAOTIAN",
+ "BENDA",
+ "BENGHE",
+ "BIMOTA",
+ "HOSS",
+ "BUELL",
+ "CAGIVA",
+ "CFMOTO",
+ "ENERGICA",
+ "HYOSUNG",
+ "JAWA",
+ "KYMCO",
+ "LAMBRETTA",
+ "LIFAN",
+ "LIGIER",
+ "GUZZI",
+ "AGUSTA",
+ "PIAGGIO",
+ "QINGQI",
+ "QUADRO",
+ "RIEJU",
+ "ROMET",
+ "SHERCO",
+ "SOCO",
+ "TOMOS",
+ "VIARELLI",
+ "VOGE",
+ "YADEA",
+ "ADLY",
+ "elts",
+ "Avant",
+ "TFSI",
+ "quattro",
+ "htmx",
+ "htpy",
+ "orgid"
+ ]
}
diff --git a/docs/__init__.py b/docs/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/docs/api-reference.md b/docs/api-reference.md
deleted file mode 100644
index b1b9569..0000000
--- a/docs/api-reference.md
+++ /dev/null
@@ -1,9 +0,0 @@
----
-hide:
- - toc
- - navigation
----
-
-
from the code_block passed in
+ panels = [
+ h.div(
+ ".ct-panel.active" if i == 0 else ".ct-panel",
+ id=f"{prefix}-{i}",
+ )[block]
+ for i, (_, block) in enumerate(variants)
+ ]
+
+ return h.div(".code-block.code-tabs-wrap")[
+ h.div(".code-header")[
+ h.button(".cb-copy", onclick="copyBlock(this)")["Copy"],
+ h.div(".ct-group")[btns],
+ ],
+ panels,
+ ]
+
+
+def tip_box(icon: str, content: h.Node, kind: str = "") -> h.Node:
+ return h.div(f".tip.{kind}" if kind else ".tip")[
+ h.div(".tip-icon")[icon],
+ h.p[content],
+ ]
+
+
+def section_header(label: str, title: str, description: str = "") -> h.Node:
+ return h.div(".section-header")[
+ h.span(".section-label")[label],
+ h.h2[title],
+ h.p[description] if description else "",
+ ]
+
+
+def page_hero(title: str, description: str | h.Node, crumb: str) -> h.Node:
+ return h.div(".page-hero", style="padding-left:1.5rem;padding-right:1.5rem;")[
+ h.div(".breadcrumb")[
+ h.a(href=url_for("index"))["Home"],
+ h.span["/"],
+ crumb,
+ ],
+ h.h1[title],
+ h.p[description] if isinstance(description, str) else description,
+ ]
+
+
+def ic(text: str) -> h.Node:
+ """Inline code span."""
+ return h.span(".inline-code")[text]
+
+
+def doc_layout(sidebar_content: h.Node, main_content: h.Node) -> h.Node:
+ return h.div(style="max-width:1200px;margin:0 auto;")[
+ h.div(".doc-layout")[
+ h.aside(".sidebar")[sidebar_content],
+ h.main[main_content],
+ ]
+ ]
+
+
+def sidebar_section(title: str, links: list[h.Node]) -> h.Node:
+ return h.div(".sidebar-section")[
+ h.h4[title],
+ links,
+ ]
+
+
+def sidebar_link(label: str, href: str, active: bool = False) -> h.Node:
+ ep = next(
+ (
+ ep
+ for ep, _ in _NAV_PAGES
+ if url_for(ep) == href or url_for(ep) == href.split("#")[0]
+ ),
+ None,
+ )
+ is_external = href.startswith("http")
+ if ep:
+ extra = _htmx(ep)
+ elif is_external:
+ extra = {"target": "_blank"}
+ else:
+ extra = {}
+ return h.a(
+ ".sidebar-link.active" if active else ".sidebar-link", href=href, **extra
+ )[label]
diff --git a/docs/custom.css b/docs/custom.css
deleted file mode 100644
index ec2e2d6..0000000
--- a/docs/custom.css
+++ /dev/null
@@ -1,7 +0,0 @@
-nav.md-tabs {
- background-color: hsla(265.2, 89.3%, 78%, 0.1)
-}
-header.md-header {
- background-color: #1E2029;
- border-bottom: .05rem solid hsl(265.2, 82%, 76.1%);
-}
\ No newline at end of file
diff --git a/docs/examples.md b/docs/examples.md
deleted file mode 100644
index ca8ed4a..0000000
--- a/docs/examples.md
+++ /dev/null
@@ -1,111 +0,0 @@
-# Examples
-
-This is some very basic examples of how to use the API. For more details, see the [API reference](https://blocket-api.se/api-reference).
-
-## Searching
-
-### Search everything
-
-Search for everything using keyword.
-
-```python
-import httpx
-
-items = httpx.get(
- "https://blocket-api.se/v1/search",
- params={"query": "flipper zero", "sort_order": "PUBLISHED_DESC"},
-).json()["docs"]
-
-for item in items:
- data = httpx.get(
- "https://blocket-api.se/v1/ad/recommerce",
- params={"id": item["id"]},
- ).json()["loaderData"]["item-recommerce"]["itemData"]
-
- print(
- f"{data['title']}\n{data['description']}\n{data['price']}\n"
- f"{data['location']['postalName']}\n{item['canonical_url']}"
- )
- print("=" * 50)
-```
-
-Will return:
-```
-Flipper zero
-Fin flipper zero, knappt använd! Kan skickas.
-2500
-Stockholm
-https://www.blocket.se/recommerce/forsale/item/19075245
-==================================================
-Flipper Zero oanvänd
-Säljer Flipper Zero som bara legat och samlat damm efter att jag införskaffade den på en konferens i Sverige.
-Förpackningen är endast öppnad för att fota innehållet.
-2500
-Linköping
-https://www.blocket.se/recommerce/forsale/item/19053126
-==================================================
-Flipper zero
-Flipper Zero säljes tillsammans med silikonskydd och extra tillbehör. Kort och flera taggar av olika slag följer med. Alla tillbehör är nya och oanvända. Flipper zero i nyskick. Se bilder för detaljer.
-2450
-Helsingborg
-https://www.blocket.se/recommerce/forsale/item/18847358
-==================================================
-```
-
-### Search for cars
-
-```python
-import httpx
-
-response = httpx.get(
- "https://blocket-api.se/v1/search/car",
- params={
- "page": 1,
- "locations": "STOCKHOLM",
- "models": "AUDI",
- "price_from": 10000,
- "price_to": 50000,
- "year_from": 2010,
- "year_to": 2025,
- },
-)
-
-for item in response.json()["docs"]:
- print(f"({item["id"]})", item["price"]["amount"], "SEK -", item["heading"])
-```
-
-Will return:
-
-```
-(18460933) 45000 SEK - Audi A4 Avant 2.0 TDI DPF Euro 5
-(18566460) 45000 SEK - Audi A4 Avant 2.0 TFSI E85 Euro 5
-(13436700) 10819 SEK - Audi A6 Avant e-tron Proline - Operationell leasing
-(18712562) 50000 SEK - Audi A4 2.0 TDI Avant quattro (170hk)
-```
-
-### Get specific store ads
-
-The id of the store can be found in the URL of the store page. For example, `https://www.blocket.se/mobility/search/car?orgId=XXXXXXXX` or `https://www.blocket.se/mobility/dealer/XXXXXX/store-name`.
-
-```python
-import httpx
-
-response = httpx.get(
- "https://blocket-api.se/v1/search/car",
- params={
- "org_id": 1337,
- # see all available params in the swagger documentation
- },
-)
-```
-
-## Get full ad details
-
-Use the `/v1/ad/` endpoint to get full ad details.
-
-```python
-httpx.get(
- "https://blocket-api.se/v1/ad/car",
- params={"id": 1234},
-)
-```
\ No newline at end of file
diff --git a/docs/index.md b/docs/index.md
deleted file mode 100644
index 27c7a88..0000000
--- a/docs/index.md
+++ /dev/null
@@ -1,16 +0,0 @@
----
-hide:
- - toc
- - navigation
----
-
-
-
-
-
-# Welcome to BlocketAPI
-
-BlocketAPI provides a series of api endpoints for interacting with the blocket.se. It uses the [blocket_api](https://github.com/dunderrrrrr/blocket_api) library as a backend to provide a simple and easy to use api. The actual library documentation can be found on GitHub. This page aims to provide a simple overview of the rest api and it's functions.
-
-If you are working with Python, please see our package [blocket_api](https://github.com/dunderrrrrr/blocket_api).
-
diff --git a/docs/static/blocket-api.png b/docs/static/blocket-api.png
new file mode 100644
index 0000000..432cb2f
Binary files /dev/null and b/docs/static/blocket-api.png differ
diff --git a/docs/static/main.js b/docs/static/main.js
new file mode 100644
index 0000000..4370e98
--- /dev/null
+++ b/docs/static/main.js
@@ -0,0 +1,132 @@
+const _bar = () => document.getElementById("page-loading-bar");
+document.addEventListener("htmx:beforeRequest", () => {
+ const b = _bar(); if (b) { b.style.width = "30%"; b.style.opacity = "1"; }
+});
+document.addEventListener("htmx:afterRequest", () => {
+ const b = _bar(); if (b) { b.style.width = "100%"; setTimeout(() => { b.style.opacity = "0"; b.style.width = "0"; }, 300); }
+});
+
+function toggleMenu() {
+ document.getElementById("mobileMenu").classList.toggle("open");
+}
+
+function initSidebarObserver() {
+ const anchors = document.querySelectorAll(".sidebar-link[href^='#']");
+ if (!anchors.length) return;
+
+ const targets = [...anchors].map(a => document.querySelector(a.getAttribute("href"))).filter(Boolean);
+
+ const observer = new IntersectionObserver((entries) => {
+ entries.forEach(entry => {
+ if (entry.isIntersecting) {
+ anchors.forEach(a => a.classList.remove("active"));
+ const link = document.querySelector(`.sidebar-link[href="#${entry.target.id}"]`);
+ if (link) link.classList.add("active");
+ }
+ });
+ }, { rootMargin: "-20% 0px -70% 0px" });
+
+ targets.forEach(t => observer.observe(t));
+}
+
+initSidebarObserver();
+
+function updateActiveNav() {
+ const path = window.location.pathname;
+ document.querySelectorAll(".nav-links a, .mobile-menu a").forEach((a) => {
+ const href = new URL(a.href, location.origin).pathname;
+ a.classList.toggle("active", href === path);
+ });
+}
+
+document.addEventListener("htmx:afterSettle", () => {
+ updateActiveNav();
+ window.scrollTo({ top: 0, behavior: "instant" });
+ initSidebarObserver();
+ if (document.getElementById("ep-root")) loadHtmxTarget("ep-root", "/_/endpoints-body");
+ if (document.getElementById("cl-root")) loadHtmxTarget("cl-root", "/_/changelog-body");
+ document.querySelectorAll("pre code:not(.hljs)").forEach((el) => hljs.highlightElement(el));
+});
+
+function loadHtmxTarget(id, url) {
+ const el = document.getElementById(id);
+ if (!el || !el.querySelector(".ep-spinner")) return;
+ htmx.ajax("GET", url, { target: "#" + id, swap: "innerHTML" });
+}
+
+function switchInstall(btn, name) {
+ document
+ .querySelectorAll(".install-tab")
+ .forEach((b) => b.classList.remove("active"));
+ document
+ .querySelectorAll(".install-body")
+ .forEach((p) => p.classList.remove("active"));
+ btn.classList.add("active");
+ document.getElementById("inst-" + name).classList.add("active");
+}
+
+function switchExample(btn, name) {
+ document
+ .querySelectorAll(".example-btn")
+ .forEach((b) => b.classList.remove("active"));
+ document
+ .querySelectorAll(".example-panel")
+ .forEach((p) => p.classList.remove("active"));
+ btn.classList.add("active");
+ document.getElementById(name).classList.add("active");
+}
+
+function switchExTab(btn, name) {
+ const bar = btn.closest(".examples-tab-bar");
+ bar.querySelectorAll(".ex-tab").forEach((b) => b.classList.remove("active"));
+ bar.closest(".examples-tabs").querySelectorAll(".ex-tab-panel").forEach((p) => p.classList.remove("active"));
+ btn.classList.add("active");
+ document.getElementById(name).classList.add("active");
+}
+
+function switchTab(btn, name) {
+ // ct-group: inline header tabs (.ct-btn / .ct-panel)
+ const ctGroup = btn.closest(".ct-group");
+ if (ctGroup) {
+ const wrap = ctGroup.closest(".code-tabs-wrap");
+ wrap.querySelectorAll(".ct-btn").forEach((b) => b.classList.remove("active"));
+ wrap.querySelectorAll(".ct-panel").forEach((p) => p.classList.remove("active"));
+ btn.classList.add("active");
+ document.getElementById(name).classList.add("active");
+ return;
+ }
+ // tab-group: pill tabs (.tab-btn / .tab-panel)
+ const group = btn.closest(".tab-group");
+ const card = group ? group.parentElement : document;
+ card.querySelectorAll(".tab-btn").forEach((b) => b.classList.remove("active"));
+ card.querySelectorAll(".tab-panel").forEach((p) => p.classList.remove("active"));
+ btn.classList.add("active");
+ document.getElementById(name).classList.add("active");
+}
+
+function copyBlock(btn) {
+ const block = btn.closest(".code-block");
+ const active = block.querySelector(".ct-panel.active code") || block.querySelector("code");
+ navigator.clipboard.writeText(active.textContent).then(() => {
+ btn.textContent = "Copied!";
+ btn.classList.add("copied");
+ setTimeout(() => {
+ btn.textContent = "Copy";
+ btn.classList.remove("copied");
+ }, 2000);
+ });
+}
+
+function copyCmd(id, btn) {
+ navigator.clipboard
+ .writeText(document.getElementById(id).textContent)
+ .then(() => {
+ btn.textContent = "Copied!";
+ btn.classList.add("copied");
+ setTimeout(() => {
+ btn.textContent = "Copy";
+ btn.classList.remove("copied");
+ }, 2000);
+ });
+}
+
diff --git a/docs/static/style.css b/docs/static/style.css
new file mode 100644
index 0000000..3dc5c57
--- /dev/null
+++ b/docs/static/style.css
@@ -0,0 +1,726 @@
+:root {
+ --bg: #ffffff;
+ --bg2: #f9f9f9;
+ --bg3: #f2f2f2;
+ --border: #e8e8e8;
+ --accent: #e02b20;
+ --accent2: #c0201a;
+ --text: #111111;
+ --text-muted: #777777;
+ --text-dim: #bbbbbb;
+ --green: #16a34a;
+ --blue: #2563eb;
+ --orange: #d97706;
+ --red: #e02b20;
+ --radius: 8px;
+ --radius-sm: 5px;
+ --shadow: 0 1px 4px rgba(0,0,0,0.07), 0 4px 16px rgba(0,0,0,0.05);
+ --font-mono: "JetBrains Mono", "Fira Code", monospace;
+ --px: 2rem;
+}
+
+* { box-sizing: border-box; margin: 0; padding: 0; }
+html { scroll-behavior: smooth; }
+
+body {
+ background: var(--bg);
+ color: var(--text);
+ font-family: "Space Mono", var(--font-mono);
+ font-size: 15px;
+ line-height: 1.7;
+ -webkit-font-smoothing: antialiased;
+}
+
+a { color: var(--accent); text-decoration: none; }
+a:hover { text-decoration: underline; }
+
+/* ── NAV ── */
+nav {
+ position: sticky;
+ top: 0;
+ z-index: 100;
+ background: rgba(255,255,255,0.92);
+ backdrop-filter: blur(12px);
+ border-bottom: 1px solid var(--border);
+}
+
+.nav-inner {
+ max-width: 1100px;
+ margin: 0 auto;
+ display: flex;
+ align-items: center;
+ height: 56px;
+ gap: 1.5rem;
+ padding: 0 var(--px);
+}
+
+.nav-logo {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ font-weight: 700;
+ font-size: 1rem;
+ color: var(--text);
+ text-decoration: none;
+ white-space: nowrap;
+ flex-shrink: 0;
+}
+.nav-logo span { color: var(--text); }
+
+.nav-links { display: flex; gap: 0.1rem; flex: 1; }
+.nav-links-right { flex: 0; margin-left: auto; }
+
+.nav-links a {
+ color: var(--text-muted);
+ font-size: 0.875rem;
+ padding: 0.35rem 0.65rem;
+ border-radius: var(--radius-sm);
+ transition: color 0.1s;
+ text-decoration: none;
+ white-space: nowrap;
+}
+.nav-links a:hover { color: var(--text); text-decoration: none; }
+.nav-links a.active { color: var(--text); font-weight: 600; }
+.nav-external { opacity: 0.6; }
+.nav-external:hover { opacity: 1; }
+
+.gh-source {
+ display: inline-flex; align-items: center; gap: 0;
+ background: var(--bg);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-sm);
+ text-decoration: none;
+ overflow: hidden;
+ transition: border-color 0.1s;
+ flex-shrink: 0;
+}
+.gh-source:hover { border-color: #bbb; text-decoration: none; }
+.gh-source-icon {
+ display: flex; align-items: center; justify-content: center;
+ padding: 0 0.55rem;
+ border-right: 1px solid var(--border);
+ height: 100%;
+ background: var(--bg2);
+}
+.gh-badge-svg { width: 14px; height: 14px; fill: var(--text-muted); }
+.gh-source:hover .gh-badge-svg { fill: var(--text); }
+.gh-source-info {
+ display: flex; flex-direction: column;
+ padding: 0.25rem 0.65rem;
+ font-size: 0.72rem;
+ font-weight: 600;
+ color: var(--text);
+ line-height: 1.3;
+}
+.gh-facts {
+ display: flex; gap: 0.6rem;
+ list-style: none; margin: 0; padding: 0;
+}
+.gh-fact { display: flex; align-items: center; gap: 0.2rem; font-size: 0.65rem; font-weight: 400; color: var(--text-muted); }
+.gh-fact-icon { width: 10px; height: 10px; fill: var(--text-muted); flex-shrink: 0; }
+
+.hamburger {
+ display: none;
+ flex-direction: column;
+ gap: 4px;
+ cursor: pointer;
+ padding: 4px;
+ margin-left: auto;
+}
+.hamburger span {
+ display: block; width: 20px; height: 1.5px;
+ background: var(--text-muted); border-radius: 2px;
+}
+
+/* ── LAYOUT ── */
+.container { max-width: 1100px; margin: 0 auto; padding: 0 var(--px); }
+.section { padding: 5rem var(--px); }
+.section-alt { background: var(--bg2); border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); }
+.section-cta { text-align: center; }
+.container-narrow { max-width: 780px; }
+
+/* ── HERO ── */
+.hero-wrapper {
+ border-bottom: 1px solid var(--border);
+ overflow: hidden;
+}
+
+.hero-center {
+ max-width: 640px;
+ margin: 0 auto;
+ padding: 6rem var(--px) 5rem;
+ text-align: center;
+}
+.hero-center .install-block { text-align: left; margin-top: 2.5rem; }
+.hero-center .hero-links { justify-content: center; }
+
+.hero-split {
+ max-width: 1100px;
+ margin: 0 auto;
+ padding: 5rem var(--px) 4rem;
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 4rem;
+ align-items: center;
+}
+
+.hero-title {
+ font-family: "Space Mono", var(--font-mono);
+ font-size: clamp(2.4rem, 6vw, 4.2rem);
+ font-weight: 700;
+ line-height: 1.0;
+ letter-spacing: -0.03em;
+ margin-bottom: 1.25rem;
+ color: var(--text);
+}
+.hero-title .hero-comment {
+ font-size: 0.45em;
+ vertical-align: middle;
+ margin-right: 0.2em;
+ color: var(--accent);
+ opacity: 1;
+ letter-spacing: 0;
+}
+.hero-title-accent { color: var(--accent); }
+
+.hero-wordmark {
+ color: var(--text);
+}
+
+.hero-comment { color: var(--accent); }
+.hero-accent { color: var(--accent); }
+
+.hero-subtitle {
+ font-size: 0.85rem;
+ color: var(--text-dim);
+ margin-bottom: 0.75rem;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.75rem;
+}
+.hero-subtitle::before,
+.hero-subtitle::after {
+ content: "";
+ height: 1px;
+ flex: 1;
+ max-width: 3rem;
+ background: var(--border);
+}
+
+.hero-desc {
+ color: var(--text-muted);
+ font-size: 0.9rem;
+ line-height: 1.8;
+ margin-bottom: 0;
+ margin-top: 1rem;
+}
+.hero-desc-sub {
+ color: var(--text-dim);
+ font-size: 0.8rem;
+}
+
+.hero-tag {
+ font-size: 0.72rem; font-weight: 600;
+ color: var(--accent);
+ background: rgba(224,43,32,0.07);
+ border: 1px solid rgba(224,43,32,0.2);
+ border-radius: 99px;
+ padding: 0.2rem 0.6rem;
+ letter-spacing: 0.03em;
+}
+
+.hero-links {
+ display: flex; gap: 1.5rem; flex-wrap: wrap;
+ margin-top: 1rem; font-size: 0.875rem;
+}
+.hero-links a { color: var(--text-muted); }
+.hero-links a:hover { color: var(--text); text-decoration: none; }
+
+.hero-right { display: flex; flex-direction: column; gap: 0.75rem; }
+
+/* ── INSTALL BLOCK ── */
+.install-block {
+ background: #111;
+ border-radius: var(--radius);
+ overflow: hidden;
+ margin-top: 1.5rem;
+ border: 1px solid #2a2a2a;
+}
+.install-tabs {
+ display: flex;
+ border-bottom: 1px solid #2a2a2a;
+ padding: 0 0.25rem;
+}
+.install-tab {
+ background: none; border: none;
+ color: #666;
+ font-size: 0.78rem; font-weight: 600;
+ font-family: inherit;
+ padding: 0.55rem 0.75rem;
+ cursor: pointer;
+ border-bottom: 2px solid transparent;
+ transition: color 0.1s;
+ letter-spacing: 0.03em;
+}
+.install-tab.active { color: #fff; border-bottom-color: var(--accent); }
+.install-tab:hover:not(.active) { color: #ccc; }
+
+.install-body { display: none; padding: 1rem 1.25rem; }
+.install-body.active { display: block; }
+.install-cmd { display: flex; align-items: center; gap: 0.75rem; }
+.install-prompt {
+ color: var(--accent);
+ font-family: var(--font-mono);
+ font-size: 0.875rem;
+ user-select: none; flex-shrink: 0;
+}
+.install-cmd code {
+ font-family: var(--font-mono);
+ font-size: 0.875rem;
+ color: #f0f0f0;
+ flex: 1;
+}
+.install-note {
+ font-size: 0.75rem; color: #555;
+ margin-top: 0.5rem;
+ font-family: var(--font-mono);
+}
+.copy-btn {
+ background: #222; border: 1px solid #333;
+ border-radius: 4px; color: #888;
+ font-size: 0.72rem; font-weight: 600;
+ padding: 0.2rem 0.55rem;
+ cursor: pointer; transition: all 0.1s;
+ white-space: nowrap; font-family: inherit; flex-shrink: 0;
+}
+.copy-btn:hover { color: #fff; border-color: #555; }
+.copy-btn.copied { color: var(--green); border-color: var(--green); }
+
+/* ── CODE BLOCKS ── */
+.code-block {
+ background: #f6f8fa;
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ overflow: hidden;
+ margin: 1rem 0;
+ min-width: 0;
+}
+.code-block pre {
+ overflow-x: auto;
+ white-space: pre;
+}
+.code-block pre code.hljs {
+ background: transparent;
+ padding: 0;
+}
+.code-header {
+ display: flex;
+ align-items: center;
+ padding: 0.45rem 1rem;
+ background: #f0f2f4;
+ border-bottom: 1px solid var(--border);
+ font-size: 0.75rem;
+ color: var(--text-muted);
+ font-family: var(--font-mono);
+ gap: 0.5rem;
+}
+.code-dots { display: flex; gap: 5px; }
+.code-dots span { width: 9px; height: 9px; border-radius: 50%; display: block; }
+.dot-red { background: #ff5f57; }
+.dot-yellow { background: #ffbd2e; }
+.dot-green { background: #28c940; }
+.code-lang {
+ margin-left: auto;
+ background: var(--bg3);
+ color: var(--text-dim);
+ font-size: 0.68rem; font-weight: 700;
+ padding: 0.1rem 0.4rem;
+ border-radius: 3px;
+ letter-spacing: 0.06em;
+}
+.cb-copy {
+ background: none;
+ border: 1px solid var(--border);
+ border-radius: 4px;
+ color: var(--text-dim);
+ font-size: 0.7rem;
+ font-family: inherit;
+ padding: 0.15rem 0.5rem;
+ cursor: pointer;
+ transition: all 0.1s;
+}
+.cb-copy:hover { color: var(--text-muted); border-color: #bbb; }
+.cb-copy.copied { color: var(--green); border-color: var(--green); }
+
+pre {
+ padding: 1.1rem 1.25rem;
+ font-family: var(--font-mono);
+ font-size: 0.82rem;
+ line-height: 1.65;
+ color: var(--text);
+}
+code { font-family: var(--font-mono); }
+
+.inline-code {
+ background: var(--bg3);
+ border: 1px solid var(--border);
+ border-radius: 4px;
+ padding: 0.1rem 0.35rem;
+ font-family: var(--font-mono);
+ font-size: 0.83em;
+ color: var(--accent);
+}
+
+/* ── CODE TABS ── */
+.code-tabs-wrap {
+ background: #f6f8fa;
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ overflow: hidden;
+ margin: 1rem 0;
+ min-width: 0;
+}
+.code-tabs-wrap .code-block {
+ border: none; border-radius: 0; margin: 0;
+}
+.ct-group { display: flex; gap: 0; margin-left: auto; }
+.ct-btn {
+ background: none; border: none;
+ color: var(--text-dim);
+ font-size: 0.72rem; font-weight: 700;
+ padding: 0.45rem 0.7rem;
+ cursor: pointer; transition: color 0.1s;
+ font-family: inherit;
+ letter-spacing: 0.04em;
+}
+.ct-btn.active { color: var(--text); }
+.ct-btn:hover:not(.active) { color: var(--text-muted); }
+.ct-panel { display: none; }
+.ct-panel.active { display: block; }
+.ct-panel .code-block { border: none; border-radius: 0; margin: 0; }
+.ct-panel .code-header { display: none; }
+
+/* ── PILL TABS (quickstart install) ── */
+.tabs { display: flex; gap: 0.25rem; background: var(--bg3); padding: 0.25rem; border-radius: var(--radius-sm); width: fit-content; margin-bottom: 1.5rem; }
+.tab-btn { background: none; border: none; color: var(--text-muted); font-size: 0.85rem; font-weight: 500; font-family: inherit; padding: 0.35rem 0.8rem; border-radius: 4px; cursor: pointer; transition: all 0.1s; }
+.tab-btn.active { background: #fff; color: var(--text); box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
+.tab-panel { display: none; }
+.tab-panel.active { display: block; }
+.tab-note { margin: .75rem 0; color: var(--text-muted); font-size: .9rem; }
+
+/* Tab group (quickstart) */
+.tab-group { display: flex; gap: .25rem; background: var(--bg3); padding: .25rem; border-radius: var(--radius-sm); width: fit-content; margin-bottom: .75rem; }
+
+/* ── BUTTONS ── */
+.btn {
+ display: inline-flex; align-items: center; gap: 0.4rem;
+ padding: 0.6rem 1.25rem;
+ border-radius: var(--radius-sm);
+ font-weight: 600; font-size: 0.875rem;
+ text-decoration: none; transition: all 0.15s;
+ cursor: pointer; border: none;
+}
+.btn-primary { background: var(--accent); color: #fff; }
+.btn-primary:hover { background: var(--accent2); opacity: 1; transform: none; text-decoration: none; }
+.btn-secondary { background: var(--bg3); color: var(--text); border: 1px solid var(--border); }
+.btn-secondary:hover { border-color: #bbb; text-decoration: none; }
+.btn-sm { font-size: 0.8rem; padding: 0.45rem 0.9rem; }
+.btn-row { display: flex; gap: .75rem; flex-wrap: wrap; margin-top: 1.25rem; }
+.hero-actions { display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap; }
+
+/* ── EXAMPLES ── */
+.example-split {
+ display: grid;
+ grid-template-columns: 220px 1fr;
+ gap: 2rem;
+ align-items: start;
+ max-width: 1100px;
+ margin: 0 auto;
+ padding: 0 var(--px);
+}
+.example-nav { display: flex; flex-direction: column; gap: 0.25rem; }
+.example-btn {
+ display: flex; align-items: center; gap: 0.65rem;
+ background: none; border: none;
+ border-radius: var(--radius-sm);
+ padding: 0.65rem 0.75rem;
+ cursor: pointer; transition: background 0.1s;
+ text-align: left; width: 100%;
+}
+.example-btn:hover { background: var(--bg3); }
+.example-btn.active { background: var(--bg3); }
+.example-btn-icon { font-size: 1.1rem; flex-shrink: 0; }
+.example-btn-label { font-size: 0.875rem; font-weight: 600; color: var(--text); }
+.example-btn-sub { font-size: 0.75rem; color: var(--text-muted); margin-top: 0.1rem; }
+.example-btn.active .example-btn-label { color: var(--accent); }
+.example-panel { display: none; }
+.example-panel.active { display: block; }
+.example-panel-note { margin-top: 0.5rem; font-size: 0.82rem; color: var(--text-muted); }
+
+/* ── FEATURES ── */
+.feature-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 0;
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ overflow: hidden;
+ margin-top: 2.5rem;
+}
+.feature-cell {
+ padding: 1.5rem;
+ display: flex; gap: 0.875rem; align-items: flex-start;
+ border-right: 1px solid var(--border);
+ border-bottom: 1px solid var(--border);
+ background: var(--bg);
+}
+.feature-cell:nth-child(3n) { border-right: none; }
+.feature-cell:nth-last-child(-n+3) { border-bottom: none; }
+.feature-icon { font-size: 1.1rem; flex-shrink: 0; margin-top: 0.15rem; }
+.feature-title { font-size: 0.875rem; font-weight: 700; margin-bottom: 0.2rem; color: var(--text); }
+.feature-desc { font-size: 0.78rem; color: var(--text-muted); line-height: 1.55; }
+
+/* ── SECTION HEADER ── */
+.section-header { text-align: center; margin-bottom: 3rem; }
+.section-label { display: inline-block; font-size: 0.72rem; font-weight: 700; color: var(--accent); text-transform: uppercase; letter-spacing: 0.12em; margin-bottom: 0.6rem; }
+.section-header h2 { font-size: clamp(1.5rem, 3.5vw, 2.2rem); font-weight: 800; letter-spacing: -0.02em; margin-bottom: 0.5rem; }
+.section-header p { color: var(--text-muted); max-width: 500px; margin: 0 auto; }
+.section-h2 { font-size: 1.4rem; font-weight: 700; letter-spacing: -0.01em; margin-top: 2.5rem; margin-bottom: 1rem; }
+
+/* ── UV SECTION ── */
+.uv-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 4rem; align-items: center; max-width: 1000px; margin: 0 auto; }
+.uv-heading { font-size: clamp(1.4rem, 3vw, 2rem); font-weight: 800; letter-spacing: -0.02em; margin: 0.5rem 0 1rem; }
+.uv-desc { color: var(--text-muted); line-height: 1.7; margin-bottom: 1.25rem; }
+.uv-code-stack { display: flex; flex-direction: column; gap: 0.5rem; }
+.check-list { display: flex; flex-direction: column; gap: 0.5rem; margin-bottom: 1.5rem; }
+.check-item { display: flex; align-items: flex-start; gap: 0.5rem; font-size: 0.875rem; color: var(--text-muted); }
+.check-item .check { color: var(--accent); flex-shrink: 0; font-weight: 700; }
+.gradient-text { color: var(--accent); }
+
+/* ── CTA ── */
+.cta-terminal {
+ font-family: var(--font-mono);
+ font-size: 0.875rem;
+ background: var(--bg2);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ padding: 0.875rem 1.25rem;
+ display: inline-flex; flex-wrap: wrap; gap: 0.35rem;
+ margin-bottom: 1.25rem;
+ max-width: 100%;
+ overflow-x: auto;
+ color: var(--text);
+}
+.cta-terminal .cmt { color: var(--text-dim); }
+.cta-terminal .install-prompt { color: var(--accent); }
+.cta-terminal .str { color: var(--green); }
+.cta-desc { color: var(--text-muted); max-width: 420px; margin: 0 auto 1.5rem; }
+
+/* ── TABLE ── */
+.table-wrap { overflow-x: auto; }
+table { width: 100%; border-collapse: collapse; font-size: 0.875rem; }
+th { background: var(--bg3); color: var(--text-muted); font-weight: 600; text-align: left; padding: 0.6rem 1rem; border-bottom: 1px solid var(--border); font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.05em; }
+td { padding: 0.65rem 1rem; border-bottom: 1px solid var(--border); vertical-align: top; }
+tr:last-child td { border-bottom: none; }
+.endpoint-table { overflow-x: auto; margin: 1.25rem 0; border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
+.type-code { font-size: 0.78rem; color: var(--blue); font-family: var(--font-mono); }
+
+/* ── SIDEBAR LAYOUT ── */
+.doc-layout {
+ display: grid;
+ grid-template-columns: 210px 1fr;
+ gap: 3rem;
+ max-width: 1100px;
+ margin: 0 auto;
+ padding: 0 var(--px);
+ align-items: start;
+}
+.doc-layout > main { min-width: 0; }
+.sidebar { position: sticky; top: 72px; }
+.sidebar-section { margin-bottom: 1.75rem; }
+.sidebar-section h4 { font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.1em; color: var(--text-dim); margin-bottom: 0.4rem; padding-left: 0.5rem; }
+.sidebar-link { display: block; padding: 0.3rem 0.5rem; font-size: 0.85rem; color: var(--text-muted); border-radius: var(--radius-sm); text-decoration: none; transition: color 0.1s; }
+.sidebar-link:hover { color: var(--text); text-decoration: none; }
+.sidebar-link.active { color: var(--accent); font-weight: 600; }
+
+/* ── PAGE HERO ── */
+.page-hero {
+ padding: 3.5rem var(--px) 2.5rem;
+ border-bottom: 1px solid var(--border);
+ margin-bottom: 2.5rem;
+ max-width: 1100px;
+ margin-left: auto; margin-right: auto;
+}
+.page-hero h1 { font-size: clamp(1.6rem, 3.5vw, 2.4rem); font-weight: 800; letter-spacing: -0.02em; margin-bottom: 0.4rem; }
+.page-hero p { color: var(--text-muted); font-size: 1rem; max-width: 560px; }
+.breadcrumb { font-size: 0.78rem; color: var(--text-dim); margin-bottom: 1.25rem; }
+.breadcrumb a { color: var(--text-dim); }
+.breadcrumb a:hover { color: var(--text-muted); }
+.breadcrumb span { margin: 0 0.35rem; }
+
+/* ── FOOTER ── */
+footer {
+ background: var(--bg2);
+ border-top: 1px solid var(--border);
+ padding: 3rem var(--px) 2rem;
+ margin-top: 4rem;
+}
+.footer-inner { max-width: 1100px; margin: 0 auto; display: grid; grid-template-columns: 2fr 1fr 1fr 1fr; gap: 2rem; }
+.footer-brand p { font-size: 0.85rem; color: var(--text-muted); margin-top: 0.75rem; max-width: 260px; line-height: 1.6; }
+.footer-col h4 { font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-dim); margin-bottom: 0.75rem; }
+.footer-col a { display: block; font-size: 0.875rem; color: var(--text-muted); text-decoration: none; margin-bottom: 0.35rem; transition: color 0.1s; }
+.footer-col a:hover { color: var(--text); }
+.footer-bottom { max-width: 1100px; margin: 1.5rem auto 0; padding-top: 1.5rem; border-top: 1px solid var(--border); display: flex; justify-content: space-between; align-items: center; font-size: 0.78rem; color: var(--text-dim); flex-wrap: wrap; gap: 0.5rem; }
+
+/* ── MOBILE MENU ── */
+.mobile-menu { display: none; background: var(--bg); border-bottom: 1px solid var(--border); padding: 0.75rem 1.5rem; flex-direction: column; gap: 0.15rem; }
+.mobile-menu a { display: block; padding: 0.45rem 0.5rem; color: var(--text-muted); border-radius: var(--radius-sm); font-size: 0.9rem; text-decoration: none; }
+.mobile-menu a:hover { color: var(--text); }
+.mobile-menu.open { display: flex; }
+
+/* ── TIP ── */
+.tip { display: flex; gap: 0.75rem; background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 0.875rem 1.1rem; margin: 1.25rem 0; }
+.tip-icon { font-size: 1rem; flex-shrink: 0; }
+.tip p { font-size: 0.875rem; color: var(--text-muted); margin: 0; }
+.tip p strong { color: var(--text); }
+
+/* ── BADGES ── */
+.badge { display: inline-block; padding: .15rem .45rem; border-radius: 4px; font-size: .7rem; font-weight: 600; text-transform: uppercase; letter-spacing: .04em; }
+.badge-feat { background: #dcfce7; color: #16a34a; }
+.badge-fix { background: #fee2e2; color: #dc2626; }
+.badge-refactor { background: #dbeafe; color: #2563eb; }
+.badge-docs { background: #fef9c3; color: #a16207; }
+.badge-perf { background: #dbeafe; color: #2563eb; }
+.badge-latest { background: var(--accent); color: #fff; }
+
+/* ── EXAMPLES TABS (home page) ── */
+.examples-tabs { margin-top: 1.5rem; }
+.examples-tab-bar {
+ display: flex;
+ gap: 0.1rem;
+ border-bottom: 1px solid var(--border);
+ margin-bottom: 1.25rem;
+ flex-wrap: wrap;
+}
+.ex-tab {
+ background: none; border: none;
+ color: var(--text-muted);
+ font-size: 0.8rem; font-weight: 400;
+ font-family: inherit;
+ padding: 0.5rem 0.9rem;
+ cursor: pointer;
+ border-bottom: 2px solid transparent;
+ margin-bottom: -1px;
+ white-space: nowrap;
+ transition: color 0.1s;
+}
+.ex-tab:hover { color: var(--text); }
+.ex-tab.active { color: var(--text); font-weight: 600; border-bottom-color: var(--accent); }
+.ex-tab-panel { display: none; }
+.ex-tab-panel.active { display: block; }
+
+/* ── EXAMPLE CARDS (python page) ── */
+.example-full-card { background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; margin-bottom: 2rem; }
+.example-card-header { padding: 1.1rem 1.25rem; border-bottom: 1px solid var(--border); }
+.example-card-icon { font-size: 1rem; }
+.example-card-body { padding: 1.25rem; }
+.ep-summary { color: var(--text-muted); font-size: 0.875rem; margin-bottom: 1rem; }
+
+/* ── ENDPOINTS ── */
+.ep-spinner { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1rem; padding: 8rem 0; color: var(--text-muted); font-size: .9rem; }
+.ep-spinner-ring { width: 32px; height: 32px; border: 2px solid var(--border); border-top-color: var(--accent); border-radius: 50%; animation: ep-spin .7s linear infinite; flex-shrink: 0; }
+@keyframes ep-spin { to { transform: rotate(360deg); } }
+.ep-error { color: var(--red); padding: 2rem 0; font-family: var(--font-mono); font-size: .85rem; }
+.ep-swagger-tip { font-size: .82rem; color: var(--text-muted); background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: .6rem 1rem; margin-bottom: 2rem; }
+.ep-swagger-tip a { color: var(--accent); }
+.ep-block { margin-bottom: 3rem; scroll-margin-top: 80px; }
+.ep-block-header { display: flex; align-items: center; gap: .75rem; margin-bottom: .5rem; }
+.ep-path { font-family: var(--font-mono); font-size: .95rem; color: var(--text); font-weight: 600; }
+.ep-summary { color: var(--text-muted); font-size: .875rem; margin-bottom: 1rem; }
+.ep-curl { display: flex; align-items: center; gap: .6rem; font-family: var(--font-mono); font-size: .78rem; background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: .6rem 1rem; margin-top: .75rem; overflow-x: auto; color: var(--text-muted); }
+.ep-curl code { color: var(--text); }
+.ep-no-params { color: var(--text-dim); font-size: .85rem; padding: .75rem; }
+.ep-enum-cell { font-size: .75rem; line-height: 1.8; max-width: 300px; }
+.ep-enum-cell code { background: var(--bg3); border: 1px solid var(--border); border-radius: 3px; padding: .1rem .35rem; font-family: var(--font-mono); font-size: .72rem; color: var(--text-muted); }
+.method-badge { display: inline-block; font-family: var(--font-mono); font-size: .7rem; font-weight: 700; padding: .15rem .5rem; border-radius: 4px; background: rgba(22,163,74,.12); color: var(--green); letter-spacing: .05em; }
+.param-required { display: inline-block; font-size: .68rem; font-weight: 700; padding: .1rem .35rem; border-radius: 4px; background: rgba(224,43,32,.08); color: var(--red); text-transform: uppercase; letter-spacing: .04em; vertical-align: middle; margin-left: .25rem; }
+.param-optional { display: inline-block; font-size: .68rem; font-weight: 700; padding: .1rem .35rem; border-radius: 4px; background: var(--bg3); color: var(--text-dim); text-transform: uppercase; letter-spacing: .04em; vertical-align: middle; margin-left: .25rem; }
+.default-code { font-size: .78rem; color: var(--text-dim); font-family: var(--font-mono); }
+
+/* ── CHANGELOG ── */
+.changelog-timeline { margin-top: 2rem; }
+.changelog-entry { display: flex; gap: 1.5rem; margin-bottom: 2rem; }
+.changelog-line { flex-shrink: 0; width: 1px; background: var(--border); margin-top: .4rem; position: relative; }
+.changelog-dot { position: absolute; top: .1rem; left: 50%; transform: translateX(-50%); width: .55rem; height: .55rem; background: var(--accent); border-radius: 50%; border: 2px solid var(--bg); }
+.changelog-body { flex: 1; padding-bottom: 1.5rem; border-bottom: 1px solid var(--border); }
+.changelog-meta { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; margin-bottom: .6rem; }
+.changelog-date { color: var(--text-dim); font-size: .8rem; }
+.changelog-ref { color: var(--text-dim); font-size: .8rem; font-family: var(--font-mono); }
+.changelog-title { font-size: .9rem; font-weight: 600; color: var(--text); margin-bottom: .2rem; }
+.changelog-desc { font-size: .8rem; color: var(--text-muted); }
+.changelog-author { display: inline-flex; align-items: center; gap: .3rem; color: var(--text-dim); font-size: .8rem; text-decoration: none; margin-left: auto; }
+.changelog-author:hover { color: var(--text-muted); text-decoration: none; }
+.changelog-avatar { width: 16px; height: 16px; border-radius: 50%; border: 1px solid var(--border); vertical-align: middle; }
+.changelog-year-group { margin-bottom: 2.5rem; }
+.changelog-year { font-size: 0.75rem; font-weight: 700; color: var(--text-dim); text-transform: uppercase; letter-spacing: .1em; margin-bottom: 1rem; padding-bottom: .5rem; border-bottom: 1px solid var(--border); }
+
+/* ── MISC ── */
+.link { color: var(--accent); }
+.link:hover { text-decoration: underline; }
+.ref-link { margin-top: .75rem; font-size: .875rem; color: var(--text-muted); }
+.inline-run-block { background: var(--bg3); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: .65rem 1rem; display: flex; align-items: center; gap: .75rem; }
+.pill { display: inline-flex; align-items: center; gap: .35rem; background: var(--bg3); border: 1px solid var(--border); border-radius: 99px; font-size: 0.8rem; color: var(--text-muted); padding: .25rem .7rem; }
+.pill .dot { width: 5px; height: 5px; border-radius: 50%; background: var(--green); flex-shrink: 0; }
+.page-loading-bar { height: 2px; background: var(--accent); width: 0; position: fixed; top: 0; left: 0; z-index: 9999; transition: width .3s; }
+
+/* Tags */
+.tag { display: inline-block; font-size: .7rem; font-weight: 600; padding: .12rem .4rem; border-radius: 4px; font-family: var(--font-mono); }
+.tag-optional { background: var(--bg3); color: var(--text-dim); }
+.tag-required { background: rgba(224,43,32,.08); color: var(--red); }
+.tag-string { background: rgba(22,163,74,.1); color: var(--green); }
+.tag-int { background: rgba(217,119,6,.1); color: var(--orange); }
+.tag-list { background: rgba(37,99,235,.1); color: var(--blue); }
+.tag-enum { background: rgba(224,43,32,.08); color: var(--accent); }
+
+/* ── RESPONSIVE ── */
+@media (max-width: 860px) {
+ .hero-split { grid-template-columns: 1fr; gap: 2rem; padding: 3rem 1.25rem 2rem; }
+ .example-split { grid-template-columns: 1fr; }
+ .uv-grid { grid-template-columns: 1fr; gap: 2rem; }
+ .feature-grid { grid-template-columns: 1fr 1fr; }
+ .feature-cell:nth-child(3n) { border-right: 1px solid var(--border); }
+ .feature-cell:nth-child(2n) { border-right: none; }
+ .footer-inner { grid-template-columns: 1fr 1fr; }
+ .doc-layout { grid-template-columns: 1fr; }
+ .sidebar { display: none; }
+}
+@media (max-width: 768px) {
+ :root { --px: 1rem; }
+ .nav-links { display: none; }
+ .gh-badges { display: none; }
+ .hamburger { display: flex; }
+ .install-block { font-size: 0.82rem; }
+ .install-cmd code { font-size: 0.8rem; }
+ .examples-tab-bar { gap: 0; }
+ .ex-tab { font-size: 0.75rem; padding: 0.45rem 0.6rem; }
+ th, td { padding: 0.5rem 0.75rem; }
+ .ep-path { font-size: 0.85rem; }
+ .cta-terminal { font-size: 0.78rem; }
+}
+@media (max-width: 560px) {
+ :root { --px: 0.875rem; }
+ .feature-grid { grid-template-columns: 1fr; }
+ .feature-cell { border-right: none !important; }
+ .footer-inner { grid-template-columns: 1fr; gap: 1.5rem; }
+ pre { font-size: 0.76rem; padding: 0.875rem 1rem; }
+ .hero-title { font-size: 1.9rem; }
+ .hero-subtitle { font-size: 0.8rem; }
+ .section-header h2 { font-size: 1.4rem; }
+ .page-hero h1 { font-size: 1.5rem; }
+ .btn { font-size: 0.8rem; padding: 0.5rem 1rem; }
+ .btn-row { flex-direction: column; }
+ .install-tab { font-size: 0.72rem; padding: 0.45rem 0.6rem; }
+ table { font-size: 0.75rem; }
+ th, td { padding: 0.4rem 0.6rem; }
+}
diff --git a/docs/views/__init__.py b/docs/views/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/docs/views/changelog.py b/docs/views/changelog.py
new file mode 100644
index 0000000..3a19e15
--- /dev/null
+++ b/docs/views/changelog.py
@@ -0,0 +1,43 @@
+import htpy as h
+from flask import url_for
+
+from markupsafe import Markup
+from docs.components_base import fragment, page, page_hero
+
+
+def content() -> list[h.Node]:
+ return [
+ page_hero(
+ "Changelog",
+ "Merged pull requests from GitHub — features, fixes, and improvements.",
+ "Changelog",
+ ),
+ h.section(".section")[
+ h.div(".container.container-narrow")[
+ h.div(
+ "#cl-root",
+ hx_get=url_for("changelog_body"),
+ hx_trigger="load",
+ hx_swap="innerHTML",
+ )[
+ h.div(".ep-spinner")[
+ h.div(".ep-spinner-ring"),
+ h.span["Loading changelog…"],
+ ],
+ ],
+ ],
+ ],
+ ]
+
+
+def view() -> Markup:
+ return page(
+ active="changelog",
+ title="Changelog — BlocketAPI",
+ description="Full release history for BlocketAPI — Python library and REST API. Features, fixes and new endpoints.",
+ content_fn=content,
+ )
+
+
+def fragment_view() -> str:
+ return fragment(content)
diff --git a/docs/views/endpoints.py b/docs/views/endpoints.py
new file mode 100644
index 0000000..bc27e37
--- /dev/null
+++ b/docs/views/endpoints.py
@@ -0,0 +1,43 @@
+import htpy as h
+from flask import url_for
+
+from markupsafe import Markup
+from docs.components_base import fragment, page, page_hero
+
+
+def content() -> list[h.Node]:
+ return [
+ page_hero(
+ "Endpoint Reference",
+ "Full parameter documentation for every BlocketAPI endpoint. All endpoints return JSON.",
+ "Endpoints",
+ ),
+ h.section(".section")[
+ h.div(".container")[
+ h.div(
+ "#ep-root",
+ hx_get=url_for("endpoints_body"),
+ hx_trigger="load",
+ hx_swap="innerHTML",
+ )[
+ h.div(".ep-spinner")[
+ h.div(".ep-spinner-ring"),
+ h.span["Loading API spec…"],
+ ],
+ ],
+ ],
+ ],
+ ]
+
+
+def view() -> Markup:
+ return page(
+ active="endpoints",
+ title="Endpoints — BlocketAPI",
+ description="Full parameter reference for all BlocketAPI REST endpoints: search, car, motorcycle, boat, and ad detail.",
+ content_fn=content,
+ )
+
+
+def fragment_view() -> str:
+ return fragment(content)
diff --git a/docs/views/examples.py b/docs/views/examples.py
new file mode 100644
index 0000000..ea74aa3
--- /dev/null
+++ b/docs/views/examples.py
@@ -0,0 +1,56 @@
+import htpy as h
+
+from docs.components import examples as c
+from markupsafe import Markup
+from docs.components_base import (
+ GITHUB,
+ fragment,
+ page,
+ page_hero,
+ section_header,
+ tip_box,
+)
+
+
+def content() -> list[h.Node]:
+ return [
+ page_hero(
+ "Examples",
+ "Ready-to-run code snippets covering the most common use cases. Copy, paste, run.",
+ "Examples",
+ ),
+ h.section(".section")[
+ h.div(".container")[
+ section_header(
+ "Code Examples",
+ "From simple searches to production scripts",
+ "Every example runs standalone with uv — no environment setup required.",
+ ),
+ h.div(".example-cards")[c.cards()],
+ tip_box(
+ "🤝",
+ [
+ "Have an example you'd like to share? ",
+ h.a(".link", href=GITHUB + "/issues", target="_blank")[
+ "Open an issue on GitHub ↗"
+ ],
+ " or submit a PR.",
+ ],
+ kind="tip",
+ ),
+ ],
+ ],
+ ]
+
+
+def view() -> Markup:
+ return page(
+ active="examples",
+ title="Examples — BlocketAPI",
+ description="Ready-to-run BlocketAPI code examples: general search, car filters, price alerts, CSV export, pagination, and more.",
+ content_fn=content,
+ )
+
+
+def fragment_view() -> str:
+ return fragment(content)
diff --git a/docs/views/index.py b/docs/views/index.py
new file mode 100644
index 0000000..bd93fd4
--- /dev/null
+++ b/docs/views/index.py
@@ -0,0 +1,27 @@
+import htpy as h
+from docs.components import index as c
+from markupsafe import Markup
+from docs.components_base import fragment, page
+
+
+def content() -> list[h.Node]:
+ return [
+ c.hero(),
+ c.examples_section(),
+ c.features(),
+ c.uv_section(),
+ c.cta(),
+ ]
+
+
+def view() -> Markup:
+ return page(
+ active="index",
+ title="BlocketAPI — The unofficial API for blocket.se",
+ description="BlocketAPI is the leading developer API for blocket.se. Search ads, cars, boats, motorcycles and more. Free to use, no API key required.",
+ content_fn=content,
+ )
+
+
+def fragment_view() -> str:
+ return fragment(content)
diff --git a/docs/views/python.py b/docs/views/python.py
new file mode 100644
index 0000000..dd0cd6f
--- /dev/null
+++ b/docs/views/python.py
@@ -0,0 +1,39 @@
+import htpy as h
+
+from docs.components import python_lib as c
+from markupsafe import Markup
+from docs.components_base import doc_layout, fragment, page, page_hero
+
+
+def content() -> list[h.Node]:
+ return [
+ page_hero(
+ "Python Library",
+ "Use BlocketAPI as a Python package — typed enums, clean return values, no boilerplate.",
+ "Python",
+ ),
+ h.section(".section")[
+ h.div(".container")[
+ doc_layout(
+ c.sidebar(),
+ [
+ *c.install_section(),
+ *c.methods_section(),
+ ],
+ ),
+ ],
+ ],
+ ]
+
+
+def view() -> Markup:
+ return page(
+ active="python",
+ title="Python Library — BlocketAPI",
+ description="BlocketAPI Python package documentation — search, car, boat, motorcycle and ad detail methods.",
+ content_fn=content,
+ )
+
+
+def fragment_view() -> str:
+ return fragment(content)
diff --git a/docs/views/quickstart.py b/docs/views/quickstart.py
new file mode 100644
index 0000000..5691f41
--- /dev/null
+++ b/docs/views/quickstart.py
@@ -0,0 +1,40 @@
+import htpy as h
+
+from docs.components import quickstart as c
+from markupsafe import Markup
+from docs.components_base import doc_layout, fragment, page, page_hero
+
+
+def content() -> list[h.Node]:
+ return [
+ page_hero(
+ "Quickstart",
+ "Get from zero to your first API response in under 5 minutes — no account, no API key.",
+ "Quickstart",
+ ),
+ h.section(".section")[
+ h.div(".container")[
+ doc_layout(
+ c.sidebar(),
+ [
+ *c.installation(),
+ *c.first_search(),
+ *c.uv_section(),
+ ],
+ ),
+ ],
+ ],
+ ]
+
+
+def view() -> Markup:
+ return page(
+ active="quickstart",
+ title="Quickstart — BlocketAPI",
+ description="Install BlocketAPI and make your first search in under 5 minutes. Supports Python, REST, curl and JavaScript.",
+ content_fn=content,
+ )
+
+
+def fragment_view() -> str:
+ return fragment(content)
diff --git a/docs/views/tips.py b/docs/views/tips.py
new file mode 100644
index 0000000..5514bbf
--- /dev/null
+++ b/docs/views/tips.py
@@ -0,0 +1,44 @@
+import htpy as h
+
+from docs.components import tips as c
+from markupsafe import Markup
+from docs.components_base import doc_layout, fragment, page, page_hero
+
+
+def content() -> list[h.Node]:
+ return [
+ page_hero(
+ "Tips & Tricks",
+ "Patterns and techniques to get the most out of BlocketAPI — from automation to market analysis.",
+ "Tips & Tricks",
+ ),
+ h.section(".section")[
+ h.div(".container")[
+ doc_layout(
+ c.sidebar(),
+ [
+ *c.uv_tip(),
+ *c.cron_tip(),
+ *c.pagination_tip(),
+ *c.sorting_tip(),
+ *c.location_tip(),
+ *c.orgid_tip(),
+ *c.analysis_tip(),
+ ],
+ ),
+ ],
+ ],
+ ]
+
+
+def view() -> Markup:
+ return page(
+ active="tips",
+ title="Tips & Tricks — BlocketAPI",
+ description="BlocketAPI tips: running scripts with uv, scheduling with cron, pagination, multi-location searches, price analysis and more.",
+ content_fn=content,
+ )
+
+
+def fragment_view() -> str:
+ return fragment(content)
diff --git a/flake.nix b/flake.nix
index 0689a9f..65bdbb2 100644
--- a/flake.nix
+++ b/flake.nix
@@ -23,16 +23,10 @@
pkgs.uv
pkgs.ruff
python
- (pkgs.writeShellScriptBin "build" ''
- git pull
- mkdocs build -d /srv/blocket-api/
- '')
-
];
shellHook = ''
- uv venv
+ uv sync --extra dev
source .venv/bin/activate
- uv pip sync requirements.txt
pre-commit install --overwrite
set -a
source .env 2> /dev/null
diff --git a/pyproject.toml b/pyproject.toml
index 1eac3db..1a0a9aa 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -7,14 +7,19 @@ readme = "README.md"
requires-python = ">=3.10"
dependencies = [
"httpx>=0.28.1",
+ "beautifulsoup4>=4.14.3",
+]
+
+[project.optional-dependencies]
+dev = [
"pre-commit>=4.5.1",
"pytest>=9.0.2",
"respx>=0.22.0",
"mypy>=1.19.1",
- "mkdocs>=1.6.1",
- "mkdocs-material>=9.7.1",
- "mkdocs-swagger-ui-tag>=0.7.2",
- "beautifulsoup4>=4.14.3",
+ "ruff>=0.9.0",
+ "flask>=3.1.3",
+ "htpy>=26.5.1",
+ "gunicorn>=26.0.0",
]
diff --git a/requirements.txt b/requirements.txt
index 07342e1..9215f6b 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,141 +2,25 @@
# uv pip compile pyproject.toml -o requirements.txt
anyio==4.9.0
# via httpx
-babel==2.17.0
- # via mkdocs-material
-backrefs==5.9
- # via mkdocs-material
beautifulsoup4==4.14.3
- # via
- # blocket-api (pyproject.toml)
- # mkdocs-swagger-ui-tag
+ # via blocket-api (pyproject.toml)
certifi==2025.6.15
# via
# httpcore
# httpx
- # requests
-cfgv==3.4.0
- # via pre-commit
-charset-normalizer==3.4.3
- # via requests
-click==8.3.0
- # via mkdocs
-colorama==0.4.6
- # via mkdocs-material
-distlib==0.3.9
- # via virtualenv
-filelock==3.18.0
- # via virtualenv
-ghp-import==2.1.0
- # via mkdocs
h11==0.16.0
# via httpcore
httpcore==1.0.9
# via httpx
httpx==0.28.1
- # via
- # blocket-api (pyproject.toml)
- # respx
-identify==2.6.12
- # via pre-commit
+ # via blocket-api (pyproject.toml)
idna==3.10
# via
# anyio
# httpx
- # requests
-iniconfig==2.1.0
- # via pytest
-jinja2==3.1.6
- # via
- # mkdocs
- # mkdocs-material
-librt==0.7.7
- # via mypy
-markdown==3.9
- # via
- # mkdocs
- # mkdocs-material
- # pymdown-extensions
-markupsafe==3.0.3
- # via
- # jinja2
- # mkdocs
-mergedeep==1.3.4
- # via
- # mkdocs
- # mkdocs-get-deps
-mkdocs==1.6.1
- # via
- # blocket-api (pyproject.toml)
- # mkdocs-material
-mkdocs-get-deps==0.2.0
- # via mkdocs
-mkdocs-material==9.7.1
- # via blocket-api (pyproject.toml)
-mkdocs-material-extensions==1.3.1
- # via mkdocs-material
-mkdocs-swagger-ui-tag==0.7.2
- # via blocket-api (pyproject.toml)
-mypy==1.19.1
- # via blocket-api (pyproject.toml)
-mypy-extensions==1.1.0
- # via mypy
-nodeenv==1.9.1
- # via pre-commit
-packaging==25.0
- # via
- # mkdocs
- # pytest
-paginate==0.5.7
- # via mkdocs-material
-pathspec==0.12.1
- # via
- # mkdocs
- # mypy
-platformdirs==4.3.8
- # via
- # mkdocs-get-deps
- # virtualenv
-pluggy==1.6.0
- # via pytest
-pre-commit==4.5.1
- # via blocket-api (pyproject.toml)
-pygments==2.19.2
- # via
- # mkdocs-material
- # pytest
-pymdown-extensions==10.16.1
- # via mkdocs-material
-pytest==9.0.2
- # via blocket-api (pyproject.toml)
-python-dateutil==2.9.0.post0
- # via ghp-import
-pyyaml==6.0.2
- # via
- # mkdocs
- # mkdocs-get-deps
- # pre-commit
- # pymdown-extensions
- # pyyaml-env-tag
-pyyaml-env-tag==1.1
- # via mkdocs
-requests==2.32.5
- # via mkdocs-material
-respx==0.22.0
- # via blocket-api (pyproject.toml)
-six==1.17.0
- # via python-dateutil
sniffio==1.3.1
# via anyio
soupsieve==2.8
# via beautifulsoup4
typing-extensions==4.14.1
- # via
- # beautifulsoup4
- # mypy
-urllib3==2.5.0
- # via requests
-virtualenv==20.31.2
- # via pre-commit
-watchdog==6.0.0
- # via mkdocs
+ # via beautifulsoup4
diff --git a/tests/docs.py b/tests/docs.py
deleted file mode 100644
index 59d794f..0000000
--- a/tests/docs.py
+++ /dev/null
@@ -1,22 +0,0 @@
-import os
-import shutil
-import tempfile
-
-from mkdocs.commands.build import build
-from mkdocs.config import load_config
-
-
-def test_mkdocs_valid_config() -> None:
- config = load_config("mkdocs.yml")
- assert config["site_name"] == "Blocket-API.se"
-
-
-def test_mkdocs_build() -> None:
- tmpdir = tempfile.mkdtemp()
- config = load_config("mkdocs.yml")
- config["site_dir"] = tmpdir
- build(config)
-
- index = os.path.join(tmpdir, "index.html")
- assert os.path.exists(index)
- shutil.rmtree(tmpdir)
diff --git a/uv.lock b/uv.lock
index 1727271..81b9392 100644
--- a/uv.lock
+++ b/uv.lock
@@ -17,29 +17,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" },
]
-[[package]]
-name = "babel"
-version = "2.17.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" },
-]
-
-[[package]]
-name = "backrefs"
-version = "5.9"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/eb/a7/312f673df6a79003279e1f55619abbe7daebbb87c17c976ddc0345c04c7b/backrefs-5.9.tar.gz", hash = "sha256:808548cb708d66b82ee231f962cb36faaf4f2baab032f2fbb783e9c2fdddaa59", size = 5765857, upload-time = "2025-06-22T19:34:13.97Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/19/4d/798dc1f30468134906575156c089c492cf79b5a5fd373f07fe26c4d046bf/backrefs-5.9-py310-none-any.whl", hash = "sha256:db8e8ba0e9de81fcd635f440deab5ae5f2591b54ac1ebe0550a2ca063488cd9f", size = 380267, upload-time = "2025-06-22T19:34:05.252Z" },
- { url = "https://files.pythonhosted.org/packages/55/07/f0b3375bf0d06014e9787797e6b7cc02b38ac9ff9726ccfe834d94e9991e/backrefs-5.9-py311-none-any.whl", hash = "sha256:6907635edebbe9b2dc3de3a2befff44d74f30a4562adbb8b36f21252ea19c5cf", size = 392072, upload-time = "2025-06-22T19:34:06.743Z" },
- { url = "https://files.pythonhosted.org/packages/9d/12/4f345407259dd60a0997107758ba3f221cf89a9b5a0f8ed5b961aef97253/backrefs-5.9-py312-none-any.whl", hash = "sha256:7fdf9771f63e6028d7fee7e0c497c81abda597ea45d6b8f89e8ad76994f5befa", size = 397947, upload-time = "2025-06-22T19:34:08.172Z" },
- { url = "https://files.pythonhosted.org/packages/10/bf/fa31834dc27a7f05e5290eae47c82690edc3a7b37d58f7fb35a1bdbf355b/backrefs-5.9-py313-none-any.whl", hash = "sha256:cc37b19fa219e93ff825ed1fed8879e47b4d89aa7a1884860e2db64ccd7c676b", size = 399843, upload-time = "2025-06-22T19:34:09.68Z" },
- { url = "https://files.pythonhosted.org/packages/fc/24/b29af34b2c9c41645a9f4ff117bae860291780d73880f449e0b5d948c070/backrefs-5.9-py314-none-any.whl", hash = "sha256:df5e169836cc8acb5e440ebae9aad4bf9d15e226d3bad049cf3f6a5c20cc8dc9", size = 411762, upload-time = "2025-06-22T19:34:11.037Z" },
- { url = "https://files.pythonhosted.org/packages/41/ff/392bff89415399a979be4a65357a41d92729ae8580a66073d8ec8d810f98/backrefs-5.9-py39-none-any.whl", hash = "sha256:f48ee18f6252b8f5777a22a00a09a85de0ca931658f1dd96d4406a34f3748c60", size = 380265, upload-time = "2025-06-22T19:34:12.405Z" },
-]
-
[[package]]
name = "beautifulsoup4"
version = "4.14.3"
@@ -53,6 +30,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" },
]
+[[package]]
+name = "blinker"
+version = "1.9.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" },
+]
+
[[package]]
name = "blocket-api"
version = "0.5.1"
@@ -60,27 +46,34 @@ source = { virtual = "." }
dependencies = [
{ name = "beautifulsoup4" },
{ name = "httpx" },
- { name = "mkdocs" },
- { name = "mkdocs-material" },
- { name = "mkdocs-swagger-ui-tag" },
+]
+
+[package.optional-dependencies]
+dev = [
+ { name = "flask" },
+ { name = "gunicorn" },
+ { name = "htpy" },
{ name = "mypy" },
{ name = "pre-commit" },
{ name = "pytest" },
{ name = "respx" },
+ { name = "ruff" },
]
[package.metadata]
requires-dist = [
{ name = "beautifulsoup4", specifier = ">=4.14.3" },
+ { name = "flask", marker = "extra == 'dev'", specifier = ">=3.1.3" },
+ { name = "gunicorn", marker = "extra == 'dev'", specifier = ">=26.0.0" },
+ { name = "htpy", marker = "extra == 'dev'", specifier = ">=26.5.1" },
{ name = "httpx", specifier = ">=0.28.1" },
- { name = "mkdocs", specifier = ">=1.6.1" },
- { name = "mkdocs-material", specifier = ">=9.7.1" },
- { name = "mkdocs-swagger-ui-tag", specifier = ">=0.7.2" },
- { name = "mypy", specifier = ">=1.19.1" },
- { name = "pre-commit", specifier = ">=4.5.1" },
- { name = "pytest", specifier = ">=9.0.2" },
- { name = "respx", specifier = ">=0.22.0" },
+ { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.19.1" },
+ { name = "pre-commit", marker = "extra == 'dev'", specifier = ">=4.5.1" },
+ { name = "pytest", marker = "extra == 'dev'", specifier = ">=9.0.2" },
+ { name = "respx", marker = "extra == 'dev'", specifier = ">=0.22.0" },
+ { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.9.0" },
]
+provides-extras = ["dev"]
[[package]]
name = "certifi"
@@ -100,70 +93,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" },
]
-[[package]]
-name = "charset-normalizer"
-version = "3.4.3"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/d6/98/f3b8013223728a99b908c9344da3aa04ee6e3fa235f19409033eda92fb78/charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72", size = 207695, upload-time = "2025-08-09T07:55:36.452Z" },
- { url = "https://files.pythonhosted.org/packages/21/40/5188be1e3118c82dcb7c2a5ba101b783822cfb413a0268ed3be0468532de/charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe", size = 147153, upload-time = "2025-08-09T07:55:38.467Z" },
- { url = "https://files.pythonhosted.org/packages/37/60/5d0d74bc1e1380f0b72c327948d9c2aca14b46a9efd87604e724260f384c/charset_normalizer-3.4.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601", size = 160428, upload-time = "2025-08-09T07:55:40.072Z" },
- { url = "https://files.pythonhosted.org/packages/85/9a/d891f63722d9158688de58d050c59dc3da560ea7f04f4c53e769de5140f5/charset_normalizer-3.4.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c", size = 157627, upload-time = "2025-08-09T07:55:41.706Z" },
- { url = "https://files.pythonhosted.org/packages/65/1a/7425c952944a6521a9cfa7e675343f83fd82085b8af2b1373a2409c683dc/charset_normalizer-3.4.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2", size = 152388, upload-time = "2025-08-09T07:55:43.262Z" },
- { url = "https://files.pythonhosted.org/packages/f0/c9/a2c9c2a355a8594ce2446085e2ec97fd44d323c684ff32042e2a6b718e1d/charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0", size = 150077, upload-time = "2025-08-09T07:55:44.903Z" },
- { url = "https://files.pythonhosted.org/packages/3b/38/20a1f44e4851aa1c9105d6e7110c9d020e093dfa5836d712a5f074a12bf7/charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0", size = 161631, upload-time = "2025-08-09T07:55:46.346Z" },
- { url = "https://files.pythonhosted.org/packages/a4/fa/384d2c0f57edad03d7bec3ebefb462090d8905b4ff5a2d2525f3bb711fac/charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0", size = 159210, upload-time = "2025-08-09T07:55:47.539Z" },
- { url = "https://files.pythonhosted.org/packages/33/9e/eca49d35867ca2db336b6ca27617deed4653b97ebf45dfc21311ce473c37/charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a", size = 153739, upload-time = "2025-08-09T07:55:48.744Z" },
- { url = "https://files.pythonhosted.org/packages/2a/91/26c3036e62dfe8de8061182d33be5025e2424002125c9500faff74a6735e/charset_normalizer-3.4.3-cp310-cp310-win32.whl", hash = "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f", size = 99825, upload-time = "2025-08-09T07:55:50.305Z" },
- { url = "https://files.pythonhosted.org/packages/e2/c6/f05db471f81af1fa01839d44ae2a8bfeec8d2a8b4590f16c4e7393afd323/charset_normalizer-3.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669", size = 107452, upload-time = "2025-08-09T07:55:51.461Z" },
- { url = "https://files.pythonhosted.org/packages/7f/b5/991245018615474a60965a7c9cd2b4efbaabd16d582a5547c47ee1c7730b/charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b", size = 204483, upload-time = "2025-08-09T07:55:53.12Z" },
- { url = "https://files.pythonhosted.org/packages/c7/2a/ae245c41c06299ec18262825c1569c5d3298fc920e4ddf56ab011b417efd/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64", size = 145520, upload-time = "2025-08-09T07:55:54.712Z" },
- { url = "https://files.pythonhosted.org/packages/3a/a4/b3b6c76e7a635748c4421d2b92c7b8f90a432f98bda5082049af37ffc8e3/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91", size = 158876, upload-time = "2025-08-09T07:55:56.024Z" },
- { url = "https://files.pythonhosted.org/packages/e2/e6/63bb0e10f90a8243c5def74b5b105b3bbbfb3e7bb753915fe333fb0c11ea/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f", size = 156083, upload-time = "2025-08-09T07:55:57.582Z" },
- { url = "https://files.pythonhosted.org/packages/87/df/b7737ff046c974b183ea9aa111b74185ac8c3a326c6262d413bd5a1b8c69/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07", size = 150295, upload-time = "2025-08-09T07:55:59.147Z" },
- { url = "https://files.pythonhosted.org/packages/61/f1/190d9977e0084d3f1dc169acd060d479bbbc71b90bf3e7bf7b9927dec3eb/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30", size = 148379, upload-time = "2025-08-09T07:56:00.364Z" },
- { url = "https://files.pythonhosted.org/packages/4c/92/27dbe365d34c68cfe0ca76f1edd70e8705d82b378cb54ebbaeabc2e3029d/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14", size = 160018, upload-time = "2025-08-09T07:56:01.678Z" },
- { url = "https://files.pythonhosted.org/packages/99/04/baae2a1ea1893a01635d475b9261c889a18fd48393634b6270827869fa34/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c", size = 157430, upload-time = "2025-08-09T07:56:02.87Z" },
- { url = "https://files.pythonhosted.org/packages/2f/36/77da9c6a328c54d17b960c89eccacfab8271fdaaa228305330915b88afa9/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae", size = 151600, upload-time = "2025-08-09T07:56:04.089Z" },
- { url = "https://files.pythonhosted.org/packages/64/d4/9eb4ff2c167edbbf08cdd28e19078bf195762e9bd63371689cab5ecd3d0d/charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849", size = 99616, upload-time = "2025-08-09T07:56:05.658Z" },
- { url = "https://files.pythonhosted.org/packages/f4/9c/996a4a028222e7761a96634d1820de8a744ff4327a00ada9c8942033089b/charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c", size = 107108, upload-time = "2025-08-09T07:56:07.176Z" },
- { url = "https://files.pythonhosted.org/packages/e9/5e/14c94999e418d9b87682734589404a25854d5f5d0408df68bc15b6ff54bb/charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1", size = 205655, upload-time = "2025-08-09T07:56:08.475Z" },
- { url = "https://files.pythonhosted.org/packages/7d/a8/c6ec5d389672521f644505a257f50544c074cf5fc292d5390331cd6fc9c3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884", size = 146223, upload-time = "2025-08-09T07:56:09.708Z" },
- { url = "https://files.pythonhosted.org/packages/fc/eb/a2ffb08547f4e1e5415fb69eb7db25932c52a52bed371429648db4d84fb1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018", size = 159366, upload-time = "2025-08-09T07:56:11.326Z" },
- { url = "https://files.pythonhosted.org/packages/82/10/0fd19f20c624b278dddaf83b8464dcddc2456cb4b02bb902a6da126b87a1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392", size = 157104, upload-time = "2025-08-09T07:56:13.014Z" },
- { url = "https://files.pythonhosted.org/packages/16/ab/0233c3231af734f5dfcf0844aa9582d5a1466c985bbed6cedab85af9bfe3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f", size = 151830, upload-time = "2025-08-09T07:56:14.428Z" },
- { url = "https://files.pythonhosted.org/packages/ae/02/e29e22b4e02839a0e4a06557b1999d0a47db3567e82989b5bb21f3fbbd9f/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154", size = 148854, upload-time = "2025-08-09T07:56:16.051Z" },
- { url = "https://files.pythonhosted.org/packages/05/6b/e2539a0a4be302b481e8cafb5af8792da8093b486885a1ae4d15d452bcec/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491", size = 160670, upload-time = "2025-08-09T07:56:17.314Z" },
- { url = "https://files.pythonhosted.org/packages/31/e7/883ee5676a2ef217a40ce0bffcc3d0dfbf9e64cbcfbdf822c52981c3304b/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93", size = 158501, upload-time = "2025-08-09T07:56:18.641Z" },
- { url = "https://files.pythonhosted.org/packages/c1/35/6525b21aa0db614cf8b5792d232021dca3df7f90a1944db934efa5d20bb1/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f", size = 153173, upload-time = "2025-08-09T07:56:20.289Z" },
- { url = "https://files.pythonhosted.org/packages/50/ee/f4704bad8201de513fdc8aac1cabc87e38c5818c93857140e06e772b5892/charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37", size = 99822, upload-time = "2025-08-09T07:56:21.551Z" },
- { url = "https://files.pythonhosted.org/packages/39/f5/3b3836ca6064d0992c58c7561c6b6eee1b3892e9665d650c803bd5614522/charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc", size = 107543, upload-time = "2025-08-09T07:56:23.115Z" },
- { url = "https://files.pythonhosted.org/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", size = 205326, upload-time = "2025-08-09T07:56:24.721Z" },
- { url = "https://files.pythonhosted.org/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", size = 146008, upload-time = "2025-08-09T07:56:26.004Z" },
- { url = "https://files.pythonhosted.org/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", size = 159196, upload-time = "2025-08-09T07:56:27.25Z" },
- { url = "https://files.pythonhosted.org/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", size = 156819, upload-time = "2025-08-09T07:56:28.515Z" },
- { url = "https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", size = 151350, upload-time = "2025-08-09T07:56:29.716Z" },
- { url = "https://files.pythonhosted.org/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", size = 148644, upload-time = "2025-08-09T07:56:30.984Z" },
- { url = "https://files.pythonhosted.org/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9", size = 160468, upload-time = "2025-08-09T07:56:32.252Z" },
- { url = "https://files.pythonhosted.org/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", size = 158187, upload-time = "2025-08-09T07:56:33.481Z" },
- { url = "https://files.pythonhosted.org/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", size = 152699, upload-time = "2025-08-09T07:56:34.739Z" },
- { url = "https://files.pythonhosted.org/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", size = 99580, upload-time = "2025-08-09T07:56:35.981Z" },
- { url = "https://files.pythonhosted.org/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", size = 107366, upload-time = "2025-08-09T07:56:37.339Z" },
- { url = "https://files.pythonhosted.org/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", size = 204342, upload-time = "2025-08-09T07:56:38.687Z" },
- { url = "https://files.pythonhosted.org/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", size = 145995, upload-time = "2025-08-09T07:56:40.048Z" },
- { url = "https://files.pythonhosted.org/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", size = 158640, upload-time = "2025-08-09T07:56:41.311Z" },
- { url = "https://files.pythonhosted.org/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", size = 156636, upload-time = "2025-08-09T07:56:43.195Z" },
- { url = "https://files.pythonhosted.org/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", size = 150939, upload-time = "2025-08-09T07:56:44.819Z" },
- { url = "https://files.pythonhosted.org/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", size = 148580, upload-time = "2025-08-09T07:56:46.684Z" },
- { url = "https://files.pythonhosted.org/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", size = 159870, upload-time = "2025-08-09T07:56:47.941Z" },
- { url = "https://files.pythonhosted.org/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", size = 157797, upload-time = "2025-08-09T07:56:49.756Z" },
- { url = "https://files.pythonhosted.org/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", size = 152224, upload-time = "2025-08-09T07:56:51.369Z" },
- { url = "https://files.pythonhosted.org/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", size = 100086, upload-time = "2025-08-09T07:56:52.722Z" },
- { url = "https://files.pythonhosted.org/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", size = 107400, upload-time = "2025-08-09T07:56:55.172Z" },
- { url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" },
-]
-
[[package]]
name = "click"
version = "8.3.0"
@@ -216,15 +145,32 @@ wheels = [
]
[[package]]
-name = "ghp-import"
-version = "2.1.0"
+name = "flask"
+version = "3.1.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "python-dateutil" },
+ { name = "blinker" },
+ { name = "click" },
+ { name = "itsdangerous" },
+ { name = "jinja2" },
+ { name = "markupsafe" },
+ { name = "werkzeug" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943, upload-time = "2022-05-02T15:47:16.11Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/26/00/35d85dcce6c57fdc871f3867d465d780f302a175ea360f62533f12b27e2b/flask-3.1.3.tar.gz", hash = "sha256:0ef0e52b8a9cd932855379197dd8f94047b359ca0a78695144304cb45f87c9eb", size = 759004, upload-time = "2026-02-19T05:00:57.678Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/9c/34f6962f9b9e9c71f6e5ed806e0d0ff03c9d1b0b2340088a0cf4bce09b18/flask-3.1.3-py3-none-any.whl", hash = "sha256:f4bcbefc124291925f1a26446da31a5178f9483862233b23c0c96a20701f670c", size = 103424, upload-time = "2026-02-19T05:00:56.027Z" },
+]
+
+[[package]]
+name = "gunicorn"
+version = "26.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "packaging" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/6d/b7/a4a3f632f823e432ce6bc65f62961b7980c898c77f075a2f7118cb3846fe/gunicorn-26.0.0.tar.gz", hash = "sha256:ca9346f85e3a4aeeb64d491045c16b9a35647abd37ea15efe53080eb8b090baf", size = 727286, upload-time = "2026-05-05T06:38:25.529Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e6/40/9c2384fc2be4ad25dd4a49decd5ad9ea5a3639814c11bd40ab77cb9f0a14/gunicorn-26.0.0-py3-none-any.whl", hash = "sha256:40233d26a5f0d1872916188c276e21641155111c2853f0c2cd55260aec0d24fc", size = 212009, upload-time = "2026-05-05T06:38:23.007Z" },
]
[[package]]
@@ -236,6 +182,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
]
+[[package]]
+name = "htpy"
+version = "26.5.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "markupsafe" },
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d3/18/48b0fe5f7b23c8bb0b45b09c306f511417fee74afc49a0765178d6dcbac2/htpy-26.5.1.tar.gz", hash = "sha256:43a365c1fc670094da781b923883019339cec031397d61d5fe5f8e2f2278b63e", size = 292049, upload-time = "2026-05-22T08:31:31.387Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e7/68/ad12d6519ccc48852ebb1effbe5a160ff21d7b48c484d0dae6fd173f6d22/htpy-26.5.1-py3-none-any.whl", hash = "sha256:148566db9d720c897baa5832e2a496954ce317ab06ef49a103b44f8a15322c3b", size = 21428, upload-time = "2026-05-22T08:31:30.153Z" },
+]
+
[[package]]
name = "httpcore"
version = "1.0.9"
@@ -291,6 +250,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" },
]
+[[package]]
+name = "itsdangerous"
+version = "2.2.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" },
+]
+
[[package]]
name = "jinja2"
version = "3.1.6"
@@ -376,15 +344,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/51/0e/b756c7708143a63fca65a51ca07990fa647db2cc8fcd65177b9e96680255/librt-0.7.7-cp314-cp314t-win_arm64.whl", hash = "sha256:142c2cd91794b79fd0ce113bd658993b7ede0fe93057668c2f98a45ca00b7e91", size = 39724, upload-time = "2026-01-01T23:52:09.745Z" },
]
-[[package]]
-name = "markdown"
-version = "3.9"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/8d/37/02347f6d6d8279247a5837082ebc26fc0d5aaeaf75aa013fcbb433c777ab/markdown-3.9.tar.gz", hash = "sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a", size = 364585, upload-time = "2025-09-04T20:25:22.885Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl", hash = "sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280", size = 107441, upload-time = "2025-09-04T20:25:21.784Z" },
-]
-
[[package]]
name = "markupsafe"
version = "3.0.3"
@@ -470,96 +429,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" },
]
-[[package]]
-name = "mergedeep"
-version = "1.3.4"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661, upload-time = "2021-02-05T18:55:30.623Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354, upload-time = "2021-02-05T18:55:29.583Z" },
-]
-
-[[package]]
-name = "mkdocs"
-version = "1.6.1"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "click" },
- { name = "colorama", marker = "sys_platform == 'win32'" },
- { name = "ghp-import" },
- { name = "jinja2" },
- { name = "markdown" },
- { name = "markupsafe" },
- { name = "mergedeep" },
- { name = "mkdocs-get-deps" },
- { name = "packaging" },
- { name = "pathspec" },
- { name = "pyyaml" },
- { name = "pyyaml-env-tag" },
- { name = "watchdog" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159, upload-time = "2024-08-30T12:24:06.899Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451, upload-time = "2024-08-30T12:24:05.054Z" },
-]
-
-[[package]]
-name = "mkdocs-get-deps"
-version = "0.2.0"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "mergedeep" },
- { name = "platformdirs" },
- { name = "pyyaml" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/98/f5/ed29cd50067784976f25ed0ed6fcd3c2ce9eb90650aa3b2796ddf7b6870b/mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c", size = 10239, upload-time = "2023-11-20T17:51:09.981Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521, upload-time = "2023-11-20T17:51:08.587Z" },
-]
-
-[[package]]
-name = "mkdocs-material"
-version = "9.7.1"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "babel" },
- { name = "backrefs" },
- { name = "colorama" },
- { name = "jinja2" },
- { name = "markdown" },
- { name = "mkdocs" },
- { name = "mkdocs-material-extensions" },
- { name = "paginate" },
- { name = "pygments" },
- { name = "pymdown-extensions" },
- { name = "requests" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/27/e2/2ffc356cd72f1473d07c7719d82a8f2cbd261666828614ecb95b12169f41/mkdocs_material-9.7.1.tar.gz", hash = "sha256:89601b8f2c3e6c6ee0a918cc3566cb201d40bf37c3cd3c2067e26fadb8cce2b8", size = 4094392, upload-time = "2025-12-18T09:49:00.308Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/3e/32/ed071cb721aca8c227718cffcf7bd539620e9799bbf2619e90c757bfd030/mkdocs_material-9.7.1-py3-none-any.whl", hash = "sha256:3f6100937d7d731f87f1e3e3b021c97f7239666b9ba1151ab476cabb96c60d5c", size = 9297166, upload-time = "2025-12-18T09:48:56.664Z" },
-]
-
-[[package]]
-name = "mkdocs-material-extensions"
-version = "1.3.1"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847, upload-time = "2023-11-22T19:09:45.208Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728, upload-time = "2023-11-22T19:09:43.465Z" },
-]
-
-[[package]]
-name = "mkdocs-swagger-ui-tag"
-version = "0.7.2"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "beautifulsoup4" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/ac/98/37df3ff6895cc25ad5e95922e4c862248533b0af630c1c3fd31ce8ece6b2/mkdocs_swagger_ui_tag-0.7.2.tar.gz", hash = "sha256:d3030e70a3937b1b2c2a399cf5a77c54a13c2eb6dd88e7c45864a175bf3a283e", size = 1277948, upload-time = "2025-08-23T16:24:47.495Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/cc/9f/8b9664d53021a6fe4272f7ec4c3d73dcef44546ba7f2959398a15396b7e4/mkdocs_swagger_ui_tag-0.7.2-py3-none-any.whl", hash = "sha256:6e96e983f6990c5b7e96604c28a33f35984c862df18879ef31d96f3df2dbe5c7", size = 1292596, upload-time = "2025-08-23T16:24:45.515Z" },
-]
-
[[package]]
name = "mypy"
version = "1.19.1"
@@ -633,15 +502,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
]
-[[package]]
-name = "paginate"
-version = "0.5.7"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252, upload-time = "2024-08-25T14:17:24.139Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746, upload-time = "2024-08-25T14:17:22.55Z" },
-]
-
[[package]]
name = "pathspec"
version = "0.12.1"
@@ -694,19 +554,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
]
-[[package]]
-name = "pymdown-extensions"
-version = "10.16.1"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "markdown" },
- { name = "pyyaml" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/55/b3/6d2b3f149bc5413b0a29761c2c5832d8ce904a1d7f621e86616d96f505cc/pymdown_extensions-10.16.1.tar.gz", hash = "sha256:aace82bcccba3efc03e25d584e6a22d27a8e17caa3f4dd9f207e49b787aa9a91", size = 853277, upload-time = "2025-07-28T16:19:34.167Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/e4/06/43084e6cbd4b3bc0e80f6be743b2e79fbc6eed8de9ad8c629939fa55d972/pymdown_extensions-10.16.1-py3-none-any.whl", hash = "sha256:d6ba157a6c03146a7fb122b2b9a121300056384eafeec9c9f9e584adfdb2a32d", size = 266178, upload-time = "2025-07-28T16:19:31.401Z" },
-]
-
[[package]]
name = "pytest"
version = "9.0.2"
@@ -725,18 +572,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" },
]
-[[package]]
-name = "python-dateutil"
-version = "2.9.0.post0"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "six" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
-]
-
[[package]]
name = "pyyaml"
version = "6.0.2"
@@ -781,33 +616,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" },
]
-[[package]]
-name = "pyyaml-env-tag"
-version = "1.1"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "pyyaml" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/79c822141bfd05a853236b504869ebc6b70159afc570e1d5a20641782eaa/pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff", size = 5737, upload-time = "2025-05-13T15:24:01.64Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" },
-]
-
-[[package]]
-name = "requests"
-version = "2.32.5"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "certifi" },
- { name = "charset-normalizer" },
- { 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" }
-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" },
-]
-
[[package]]
name = "respx"
version = "0.22.0"
@@ -821,12 +629,28 @@ wheels = [
]
[[package]]
-name = "six"
-version = "1.17.0"
+name = "ruff"
+version = "0.15.20"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/43/dc/35b341fc554ba02f217fc10da57d1a75168cfbcf75b0ef2202176d4c4f2d/ruff-0.15.20.tar.gz", hash = "sha256:1416eb04349192646b54de98f146c4f59afe37d0decfc02c3cbbf396f3a28566", size = 4755489, upload-time = "2026-06-25T17:20:37.578Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
+ { url = "https://files.pythonhosted.org/packages/94/d9/2d5014f0253ba541d2061d9fa7193f48e941c8b21bb88a7ff9bbe0bd0596/ruff-0.15.20-py3-none-linux_armv6l.whl", hash = "sha256:00e188c53e499c3c1637f73c91dcf2fb56d576cab76ce1be50a27c4e80e37078", size = 10839665, upload-time = "2026-06-25T17:19:44.702Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/d3/ac1798ba64f670698867fcfc591d50e7e421bef137db564858f619a30fcf/ruff-0.15.20-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9ebd1fd9b9c95fc0bd7b2761aebec1f030013d2e193a2901b224af68fe47251b", size = 11208649, upload-time = "2026-06-25T17:19:48.787Z" },
+ { url = "https://files.pythonhosted.org/packages/47/47/d3ac899991202095dfcf3d5176be4272642be3cf981a2f1a30f72a2afb95/ruff-0.15.20-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c5b16cdd67ca108185cd36dce98c576350c03b1660a751de725fb049193a0632", size = 10622638, upload-time = "2026-06-25T17:19:51.354Z" },
+ { url = "https://files.pythonhosted.org/packages/33/13/4e043fe30aa94d4ff5213a9881fc296d12960f5971b234a5263fdc225312/ruff-0.15.20-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3413bb3c3d2ca6a8208f1f4809cd2dca3c6de6d0b491c0e70847672bde6e6efd", size = 10984227, upload-time = "2026-06-25T17:19:54.044Z" },
+ { url = "https://files.pythonhosted.org/packages/76/e6/92e7bf40388bc5800073b96564f56264f7e48bfd1a498f5ced6ae6d5a769/ruff-0.15.20-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd7ec42b3bb3da066488db093308a69c4ac5ee6d2af333a86ba6e2eb2e7dd44b", size = 10622882, upload-time = "2026-06-25T17:19:57.037Z" },
+ { url = "https://files.pythonhosted.org/packages/13/7a/43460be3f24495a3aa46d4b16873e2c4941b3b5f0b00cf88c03b7b94b339/ruff-0.15.20-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1a36ad0eb77fba9aabfb69ede54de6f376d04ac18ebea022847046d340a8267", size = 11474808, upload-time = "2026-06-25T17:20:00.357Z" },
+ { url = "https://files.pythonhosted.org/packages/27/a0/f37077884873221c6b33b4ab49eb18f9f88e54a16a25a5bca59bef46dd66/ruff-0.15.20-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b6df3b1e4610432f0386dba04d853b5f08cbbc903410c6fcc02f620f05aff53c", size = 12293094, upload-time = "2026-06-25T17:20:03.446Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/74/165545b60256a9704c21ac0ec4a0d07933b320812f9584836c9f4aca4292/ruff-0.15.20-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e89f198a1ea6ef0d727c1cf16088bc91a6cb0ab947dedc966715691647186eae", size = 11526176, upload-time = "2026-06-25T17:20:06.301Z" },
+ { url = "https://files.pythonhosted.org/packages/86/b1/a976a136d40ade83ce743578399865f57001003a409acadc0ecbb3051082/ruff-0.15.20-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309809086c2acb67624950a3c8133e80f32d0d3e27106c0cd60ff26657c9f24b", size = 11520767, upload-time = "2026-06-25T17:20:09.191Z" },
+ { url = "https://files.pythonhosted.org/packages/19/0f/f032696cb01c9b54c0263fa393474d7758f1cdc021a01b04e3cbc2500999/ruff-0.15.20-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2d2374caa2f2c2f9e2b7da0a50802cfb8b79f55a9b5e49379f564544fbf56487", size = 11500132, upload-time = "2026-06-25T17:20:13.602Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/f4/51b1a14bc69e8c224b15dab9cce8e99b425e0455d462caa2b3c9be2b6a8e/ruff-0.15.20-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a1ed17b65293e0c2f22fc387bc13198a5de94bf4429589b0ff6946b0feaf21a3", size = 10943828, upload-time = "2026-06-25T17:20:16.635Z" },
+ { url = "https://files.pythonhosted.org/packages/71/4b/fe267640783cd02bf6c5cc290b1df1051be2ec294c678b5c15fe19e52343/ruff-0.15.20-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f701305e66b38ea6c91882490eb73459796808e4c6362a1b765255e0cdcd4053", size = 10645418, upload-time = "2026-06-25T17:20:19.4Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/c0/a65aa4ec2f5e87a1df32dc3ec1fede434fe3dfd5cbcf3b503cafc676ab54/ruff-0.15.20-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b9c0c367ad8e5d0d5b5b8537864c469a0a0e55417aadfbeca41fa61333be9f4", size = 11211770, upload-time = "2026-06-25T17:20:22.033Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/a4/0caa331d954ae2723d729d351c989cb4ca8b6077d5c6c2cb6de75e98c041/ruff-0.15.20-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:01cc00dd58f0df339d0e902219dd53990ea99996a0344e5d9cc8d45d5307e460", size = 11618698, upload-time = "2026-06-25T17:20:25.259Z" },
+ { url = "https://files.pythonhosted.org/packages/10/9b/5f14927848d2fd4aa891fd88d883788c5a7baba561c7874732364045708c/ruff-0.15.20-py3-none-win32.whl", hash = "sha256:ed65ef510e43a137207e0f01cfcf998aeddb1aeeda5c9d35023e910284d7cf21", size = 10857322, upload-time = "2026-06-25T17:20:28.612Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/f0/fe47c501f9dea92a26d788ff98bb5d92ed4cb4c88792c5c88af6b697dc8e/ruff-0.15.20-py3-none-win_amd64.whl", hash = "sha256:a525c81c70fb0380344dd1d8745d8cc1c890b7fc94a58d5a07bd8eb9557b8415", size = 11993274, upload-time = "2026-06-25T17:20:31.871Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/2b/9555445e1201d92b3195f45cdb153a0b68f24e0a4273f6e3d5ab46e212bb/ruff-0.15.20-py3-none-win_arm64.whl", hash = "sha256:2f5b2a6d614e8700388806a14996c40fab2c47b819ef57d790a34878858ed9ca", size = 11343498, upload-time = "2026-06-25T17:20:35.03Z" },
]
[[package]]
@@ -895,15 +719,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" },
]
-[[package]]
-name = "urllib3"
-version = "2.5.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" },
-]
-
[[package]]
name = "virtualenv"
version = "20.31.2"
@@ -919,33 +734,13 @@ wheels = [
]
[[package]]
-name = "watchdog"
-version = "6.0.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/0c/56/90994d789c61df619bfc5ce2ecdabd5eeff564e1eb47512bd01b5e019569/watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26", size = 96390, upload-time = "2024-11-01T14:06:24.793Z" },
- { url = "https://files.pythonhosted.org/packages/55/46/9a67ee697342ddf3c6daa97e3a587a56d6c4052f881ed926a849fcf7371c/watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112", size = 88389, upload-time = "2024-11-01T14:06:27.112Z" },
- { url = "https://files.pythonhosted.org/packages/44/65/91b0985747c52064d8701e1075eb96f8c40a79df889e59a399453adfb882/watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3", size = 89020, upload-time = "2024-11-01T14:06:29.876Z" },
- { url = "https://files.pythonhosted.org/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c", size = 96393, upload-time = "2024-11-01T14:06:31.756Z" },
- { url = "https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2", size = 88392, upload-time = "2024-11-01T14:06:32.99Z" },
- { url = "https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c", size = 89019, upload-time = "2024-11-01T14:06:34.963Z" },
- { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471, upload-time = "2024-11-01T14:06:37.745Z" },
- { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449, upload-time = "2024-11-01T14:06:39.748Z" },
- { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054, upload-time = "2024-11-01T14:06:41.009Z" },
- { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" },
- { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" },
- { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" },
- { url = "https://files.pythonhosted.org/packages/30/ad/d17b5d42e28a8b91f8ed01cb949da092827afb9995d4559fd448d0472763/watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881", size = 87902, upload-time = "2024-11-01T14:06:53.119Z" },
- { url = "https://files.pythonhosted.org/packages/5c/ca/c3649991d140ff6ab67bfc85ab42b165ead119c9e12211e08089d763ece5/watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11", size = 88380, upload-time = "2024-11-01T14:06:55.19Z" },
- { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" },
- { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" },
- { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" },
- { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" },
- { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" },
- { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" },
- { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" },
- { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" },
- { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" },
- { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" },
+name = "werkzeug"
+version = "3.1.8"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "markupsafe" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/dd/b2/381be8cfdee792dd117872481b6e378f85c957dd7c5bca38897b08f765fd/werkzeug-3.1.8.tar.gz", hash = "sha256:9bad61a4268dac112f1c5cd4630a56ede601b6ed420300677a869083d70a4c44", size = 875852, upload-time = "2026-04-02T18:49:14.268Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/93/8c/2e650f2afeb7ee576912636c23ddb621c91ac6a98e66dc8d29c3c69446e1/werkzeug-3.1.8-py3-none-any.whl", hash = "sha256:63a77fb8892bf28ebc3178683445222aa500e48ebad5ec77b0ad80f8726b1f50", size = 226459, upload-time = "2026-04-02T18:49:12.72Z" },
]