Compare commits

105 Commits

Author SHA1 Message Date
afaab8ed77 Merge pull request #79 from nikdoof/renovate/pydantic-2.x-lockfile
Update dependency pydantic to v2.11.1
2025-03-31 09:41:31 +01:00
renovate[bot]
64d7917190 Update dependency pydantic to v2.11.1 2025-03-28 22:29:52 +00:00
29e7a4bc2e Merge pull request #78 from nikdoof/renovate/ruff-0.x-lockfile
Update dependency ruff to v0.11.2
2025-03-25 10:07:30 +00:00
renovate[bot]
20337bc649 Update dependency ruff to v0.11.2 2025-03-21 14:57:56 +00:00
1b28247424 Merge pull request #77 from nikdoof/renovate/ruff-0.x
Update dependency ruff to ^0.11.0
2025-03-17 13:24:00 +00:00
renovate[bot]
81f2638b52 Update dependency ruff to ^0.11.0 2025-03-14 16:10:15 +00:00
f0a956e015 Merge pull request #75 from nikdoof/renovate/prometheus-flask-exporter-0.x-lockfile
Update dependency prometheus-flask-exporter to v0.23.2
2025-03-14 08:51:21 +00:00
04dc3ff0e3 Merge pull request #76 from nikdoof/renovate/ruff-0.x
Update dependency ruff to ^0.10.0
2025-03-14 08:49:52 +00:00
renovate[bot]
bbfed13bfc Update dependency ruff to ^0.10.0 2025-03-13 19:54:33 +00:00
renovate[bot]
34b8c907ae Update dependency prometheus-flask-exporter to v0.23.2 2025-03-12 01:39:58 +00:00
501805b5fd Merge pull request #74 from nikdoof/renovate/ruff-0.x-lockfile
Update dependency ruff to v0.9.10
2025-03-08 14:19:38 +00:00
renovate[bot]
a284dd5eed Update dependency ruff to v0.9.10 2025-03-07 19:23:48 +00:00
16c1bc291f Merge pull request #73 from nikdoof/dependabot/pip/jinja2-3.1.6
Bump jinja2 from 3.1.5 to 3.1.6
2025-03-07 11:26:02 +00:00
dependabot[bot]
2023870950 Bump jinja2 from 3.1.5 to 3.1.6
Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.5 to 3.1.6.
- [Release notes](https://github.com/pallets/jinja/releases)
- [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst)
- [Commits](https://github.com/pallets/jinja/compare/3.1.5...3.1.6)

---
updated-dependencies:
- dependency-name: jinja2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-06 04:51:33 +00:00
11d79eb192 Merge pull request #71 from nikdoof/renovate/ruff-0.x-lockfile
Update dependency ruff to v0.9.9
2025-03-03 09:45:17 +00:00
66514b2d78 Merge pull request #72 from nikdoof/renovate/pytest-8.x-lockfile
Update dependency pytest to v8.3.5
2025-03-03 09:44:01 +00:00
renovate[bot]
aac1ff9890 Update dependency pytest to v8.3.5 2025-03-02 13:33:47 +00:00
renovate[bot]
130370106b Update dependency ruff to v0.9.9 2025-02-28 10:42:40 +00:00
2b3269bda4 Merge pull request #70 from nikdoof/renovate/ruff-0.x-lockfile
Update dependency ruff to v0.9.8
2025-02-28 10:40:49 +00:00
renovate[bot]
30d6959e00 Update dependency ruff to v0.9.8 2025-02-27 16:25:39 +00:00
73a90dcb14 Version 0.1.1 2025-02-23 22:43:40 +00:00
7c130eb484 Build arm64 container images 2025-02-23 22:41:23 +00:00
2ffebbea61 Update Poetry to 2.1.1 2025-02-23 22:41:13 +00:00
9d3686716b Merge pull request #69 from nikdoof/renovate/ruff-0.x-lockfile
Update dependency ruff to v0.9.7
2025-02-21 11:32:47 +00:00
renovate[bot]
3fec3a05f9 Update dependency ruff to v0.9.7 2025-02-20 15:27:02 +00:00
86632293ed Merge pull request #68 from nikdoof/renovate/ruff-0.x-lockfile
Update dependency ruff to v0.9.6
2025-02-18 14:02:42 +00:00
renovate[bot]
a25a468021 Update dependency ruff to v0.9.6 2025-02-10 17:13:29 +00:00
c45dc60cce Merge pull request #66 from nikdoof/renovate/ruff-0.x-lockfile
Update dependency ruff to v0.9.3
2025-01-24 08:47:10 +00:00
6fad382f69 Merge pull request #67 from nikdoof/renovate/pydantic-2.x-lockfile
Update dependency pydantic to v2.10.6
2025-01-24 08:45:18 +00:00
renovate[bot]
926446d01d Update dependency pydantic to v2.10.6 2025-01-24 05:41:55 +00:00
renovate[bot]
d09109736f Update dependency ruff to v0.9.3 2025-01-23 20:59:23 +00:00
c9dd7fe2cb Merge pull request #65 from nikdoof/renovate/pytest-github-actions-annotate-failures-0.x
Update dependency pytest-github-actions-annotate-failures to ^0.3.0
2025-01-18 12:32:33 +00:00
renovate[bot]
1b9b5f5a5f Update dependency pytest-github-actions-annotate-failures to ^0.3.0 2025-01-18 00:40:58 +00:00
cbc118877f Merge pull request #64 from nikdoof/renovate/ruff-0.x-lockfile
Update dependency ruff to v0.9.2
2025-01-17 08:50:37 +00:00
renovate[bot]
4e8f8d0069 Update dependency ruff to v0.9.2 2025-01-16 17:29:45 +00:00
1259702113 Merge pull request #63 from nikdoof/renovate/ruff-0.x-lockfile
Update dependency ruff to v0.9.1
2025-01-12 01:07:29 +00:00
renovate[bot]
c5c6e000af Update dependency ruff to v0.9.1 2025-01-10 22:06:25 +00:00
7a2681d624 Merge pull request #62 from nikdoof/renovate/ruff-0.x
Update dependency ruff to ^0.9.0
2025-01-09 16:01:12 +00:00
5910b567a6 Merge pull request #61 from nikdoof/renovate/pydantic-2.x-lockfile
Update dependency pydantic to v2.10.5
2025-01-09 16:00:57 +00:00
renovate[bot]
0b4b95af3a Update dependency ruff to ^0.9.0 2025-01-09 14:33:07 +00:00
renovate[bot]
2775f57f53 Update dependency pydantic to v2.10.5 2025-01-09 14:33:01 +00:00
fcce70132f Merge pull request #60 from nikdoof/renovate/ruff-0.x-lockfile
Update dependency ruff to v0.8.6
2025-01-05 16:50:04 +00:00
renovate[bot]
a3197f5796 Update dependency ruff to v0.8.6 2025-01-04 16:50:44 +00:00
7e7ca4c6a4 Merge pull request #59 from nikdoof/dependabot/pip/jinja2-3.1.5
Bump jinja2 from 3.1.4 to 3.1.5
2025-01-04 10:07:48 +00:00
9d3bf714dd Merge pull request #58 from nikdoof/renovate/ruff-0.x-lockfile
Update dependency ruff to v0.8.4
2024-12-30 12:09:10 +00:00
dependabot[bot]
b4d351b9c7 Bump jinja2 from 3.1.4 to 3.1.5
Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.4 to 3.1.5.
- [Release notes](https://github.com/pallets/jinja/releases)
- [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst)
- [Commits](https://github.com/pallets/jinja/compare/3.1.4...3.1.5)

---
updated-dependencies:
- dependency-name: jinja2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-24 00:41:30 +00:00
renovate[bot]
8d83ef2d22 Update dependency ruff to v0.8.4 2024-12-19 15:15:39 +00:00
3d5ba55887 Merge pull request #57 from nikdoof/renovate/pydantic-2.x-lockfile
Update dependency pydantic to v2.10.4
2024-12-18 22:41:27 +00:00
renovate[bot]
0c73f9cae1 Update dependency pydantic to v2.10.4 2024-12-18 20:38:24 +00:00
f509a4ca54 Merge pull request #56 from nikdoof/renovate/ruff-0.x-lockfile
Update dependency ruff to v0.8.3
2024-12-16 13:08:14 +00:00
renovate[bot]
c51fe84922 Update dependency ruff to v0.8.3 2024-12-12 15:34:06 +00:00
035df5140d Merge pull request #55 from nikdoof/renovate/ruff-0.x-lockfile
Update dependency ruff to v0.8.2
2024-12-09 09:30:36 +00:00
renovate[bot]
f362ec7aec Update dependency ruff to v0.8.2 2024-12-05 14:41:07 +00:00
03c95c9384 Merge pull request #54 from nikdoof/renovate/pydantic-2.x-lockfile
Update dependency pydantic to v2.10.3
2024-12-03 20:08:15 +00:00
renovate[bot]
4899f0bbe7 Update dependency pydantic to v2.10.3 2024-12-03 18:52:06 +00:00
11defa20e8 Merge pull request #53 from nikdoof/renovate/pytest-8.x-lockfile
Update dependency pytest to v8.3.4
2024-12-02 19:07:18 +00:00
renovate[bot]
741eaab4c3 Update dependency pytest to v8.3.4 2024-12-01 13:42:12 +00:00
ef3cac0471 Merge pull request #49 from nikdoof/renovate/ruff-0.x-lockfile
Update dependency ruff to v0.8.1
2024-11-29 10:28:17 +00:00
renovate[bot]
5e80d712aa Update dependency ruff to v0.8.1 2024-11-29 06:34:19 +00:00
946747138e Merge pull request #52 from nikdoof/renovate/pydantic-2.x-lockfile
Update dependency pydantic to v2.10.2
2024-11-26 17:02:54 +00:00
renovate[bot]
467563a930 Update dependency pydantic to v2.10.2 2024-11-26 15:36:53 +00:00
7d51606aa1 Merge pull request #50 from nikdoof/renovate/pydantic-2.x-lockfile
Update dependency pydantic to v2.10.1
2024-11-25 13:42:10 +00:00
94d661e1f0 Merge pull request #48 from nikdoof/renovate/flask-3.x-lockfile
Update dependency Flask to v3.1.0
2024-11-25 13:41:50 +00:00
d5bf462c47 Merge pull request #51 from nikdoof/renovate/ruff-0.x
Update dependency ruff to ^0.8.0
2024-11-25 13:41:13 +00:00
renovate[bot]
040c099c96 Update dependency ruff to ^0.8.0 2024-11-22 12:05:53 +00:00
renovate[bot]
ef86f0ee1e Update dependency pydantic to v2.10.1 2024-11-22 01:19:29 +00:00
renovate[bot]
e1eaae9e4a Update dependency Flask to v3.1.0 2024-11-13 19:04:38 +00:00
0905ccbb27 Merge pull request #47 from nikdoof/dependabot/pip/werkzeug-3.0.6
Bump werkzeug from 3.0.3 to 3.0.6
2024-11-12 15:54:39 +00:00
26947a8b67 Merge pull request #45 from nikdoof/renovate/ruff-0.x-lockfile
Update dependency ruff to v0.7.3
2024-11-12 15:51:19 +00:00
renovate[bot]
ecd914dc19 Update dependency ruff to v0.7.3 2024-11-08 12:52:47 +00:00
dependabot[bot]
cdbd904a87 Bump werkzeug from 3.0.3 to 3.0.6
Bumps [werkzeug](https://github.com/pallets/werkzeug) from 3.0.3 to 3.0.6.
- [Release notes](https://github.com/pallets/werkzeug/releases)
- [Changelog](https://github.com/pallets/werkzeug/blob/main/CHANGES.rst)
- [Commits](https://github.com/pallets/werkzeug/compare/3.0.3...3.0.6)

---
updated-dependencies:
- dependency-name: werkzeug
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-26 00:19:29 +00:00
c82681125d Merge pull request #46 from nikdoof/renovate/ruff-0.x
Update dependency ruff to ^0.7.0
2024-10-18 19:51:29 +01:00
6b1d92d45f Merge pull request #43 from nikdoof/renovate/pydantic-2.x-lockfile
Update dependency pydantic to v2.9.2
2024-10-18 19:51:18 +01:00
491f351c06 Merge pull request #44 from nikdoof/renovate/pytest-8.x-lockfile
Update dependency pytest to v8.3.3
2024-10-18 19:51:06 +01:00
renovate[bot]
a81b609c48 Update dependency ruff to ^0.7.0 2024-10-17 21:03:39 +00:00
renovate[bot]
65a5729f6b Update dependency pydantic to v2.9.2 2024-09-17 17:08:54 +00:00
renovate[bot]
09c0724d2c Update dependency pytest to v8.3.3 2024-09-10 12:25:50 +00:00
bacf1c0101 Merge pull request #41 from nikdoof/renovate/ruff-0.x-lockfile
Update dependency ruff to v0.6.4
2024-09-06 15:09:04 +01:00
9da246a16e Merge pull request #42 from nikdoof/renovate/pydantic-2.x-lockfile
Update dependency pydantic to v2.9.0
2024-09-06 15:08:45 +01:00
renovate[bot]
2b889abe8f Update dependency pydantic to v2.9.0 2024-09-05 16:50:16 +00:00
renovate[bot]
655d98cc96 Update dependency ruff to v0.6.4 2024-09-05 16:50:03 +00:00
4b7d27b63a Merge pull request #40 from nikdoof/renovate/ruff-0.x-lockfile
Update dependency ruff to v0.6.3
2024-08-30 13:59:15 +01:00
renovate[bot]
a7c22414c2 Update dependency ruff to v0.6.3 2024-08-29 15:57:35 +00:00
6bff18e3c0 Merge pull request #31 from nikdoof/renovate/ruff-0.x-lockfile
Update dependency ruff to v0.6.2
2024-08-28 12:30:08 +01:00
renovate[bot]
30f4c53d1b Update dependency ruff to v0.6.2 2024-08-22 15:02:08 +00:00
e9426644a2 Fix Gunicorn config in Docker 2024-08-18 21:57:12 +01:00
30d5e40520 Update README 2024-08-18 21:49:58 +01:00
017e593163 Add some sample custom links 2024-08-18 21:46:19 +01:00
22adcbbb34 Add CORS support 2024-08-18 21:34:57 +01:00
d2bbd4b135 Update to Poetry 1.8.3 2024-08-18 21:20:22 +01:00
c8b2030f60 Switch to Gunicorn 2024-08-18 21:18:14 +01:00
df4b5e056f Update default port for the container to match previous config 2024-08-17 10:29:53 +01:00
9c42d3d8c3 Remove ARM platforms for now 2024-08-17 10:22:51 +01:00
ad8344aae3 Merge pull request #30 from nikdoof/renovate/python-3.x
Update python Docker tag to v3.12.3
2024-08-16 16:33:51 +01:00
3f6e94a80a Add Prometheus metrics 2024-08-16 16:23:24 +01:00
549cf1b7b2 Validate responses against the models 2024-08-16 16:18:19 +01:00
18ab52e123 Add Pydantic support for JRD (resolves #1) 2024-08-16 16:12:47 +01:00
82ba2af669 Fix rel filtering (resolves #4) 2024-08-16 12:33:57 +01:00
aa17b60400 Rework into a testable, modern app 2024-08-16 12:12:27 +01:00
5862b259d1 Return Error 400 on invalid requests 2024-08-16 10:03:34 +01:00
479dcd8a52 Add openssl-dev 2024-08-16 10:00:59 +01:00
13f3408ddc Add cargo to docker deps 2024-08-16 09:51:10 +01:00
bc73f3da76 Add rust for armv7 container builds 2024-08-16 09:46:02 +01:00
renovate[bot]
9e1fe5bfdb Update python Docker tag to v3.12.3 2024-08-16 08:34:25 +00:00
029c88c44f Update to Poetry (resolves #27 and #29) 2024-08-16 09:33:47 +01:00
19 changed files with 1048 additions and 89 deletions

View File

@@ -27,7 +27,7 @@ jobs:
uses: docker/build-push-action@v6
with:
push: true
platforms: linux/amd64,linux/arm64,linux/arm/v7
platforms: linux/amd64,linux/arm64
tags: |
ghcr.io/${{ github.repository_owner }}/simple-webfinger:${{ github.ref_name }}
ghcr.io/${{ github.repository_owner }}/simple-webfinger:latest

37
.github/workflows/tests.yaml vendored Normal file
View File

@@ -0,0 +1,37 @@
name: Run Tests
on:
push:
branches:
- "main"
pull_request:
jobs:
pytest:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.12"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Test with pytest
run: |
make tests
lint:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.12"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Lint with ruff
run: |
make lint

4
.gitignore vendored
View File

@@ -128,4 +128,6 @@ dmypy.json
# Pyre type checker
.pyre/
config.yaml
config.yaml
.ruff_cache

View File

@@ -1,13 +1,34 @@
# syntax=docker/dockerfile:1.9
FROM --platform=$BUILDPLATFORM python:3.12-alpine
FROM python:3.12.3-alpine3.18 AS base
FROM base AS builder
ENV PYTHONFAULTHANDLER=1 \
PYTHONUNBUFFERED=1 \
PYTHONHASHSEED=random \
PIP_NO_CACHE_DIR=off \
PIP_DISABLE_PIP_VERSION_CHECK=on \
PIP_DEFAULT_TIMEOUT=100 \
POETRY_NO_INTERACTION=1 \
POETRY_VIRTUALENVS_CREATE=false \
PATH="$PATH:/runtime/bin" \
PYTHONPATH="$PYTHONPATH:/runtime/lib/python3.12/site-packages" \
# Versions:
POETRY_VERSION=2.1.1
# System deps:
RUN apk add build-base unzip wget python3-dev libffi-dev rust cargo openssl-dev
RUN pip install "poetry==$POETRY_VERSION" "poetry-plugin-export"
WORKDIR /src
# Generate requirements and install *all* dependencies.
COPY pyproject.toml poetry.lock /src/
RUN poetry export --without-hashes --no-interaction --no-ansi -f requirements.txt -o requirements.txt
RUN pip install --prefix=/runtime --force-reinstall -r requirements.txt
FROM base AS runtime
COPY --from=builder /runtime /usr/local
COPY . /app
WORKDIR /app
COPY requirements.txt /app
RUN --mount=type=cache,target=/root/.cache/pip \
pip3 install -r requirements.txt
COPY app.py /app
ENTRYPOINT ["python3"]
CMD ["app.py"]
EXPOSE 8000/tcp
CMD ["/usr/local/bin/gunicorn", "simple_webfinger.app:create_app()", "-b", "0.0.0.0:8000", "--access-logfile", "-"]

17
Makefile Normal file
View File

@@ -0,0 +1,17 @@
.venv:
python3 -m pip install poetry
python3 -m poetry install --with github
.PHONY: tests
tests: .venv
python3 -m poetry run pytest
lint: .venv
python3 -m poetry run ruff check --output-format=github --select=E9,F63,F7,F82 --target-version=py37 .
python3 -m poetry run ruff check --output-format=github --target-version=py37 .
serve-uwsgi:
SIMPLE_WEBFINGER_CONFIG_FILE="examples/example-config.yaml" python3 -m poetry run gunicorn "simple_webfinger.app:create_app()"
serve:
DEBUG_METRICS=1 SIMPLE_WEBFINGER_CONFIG_FILE="examples/example-config.yaml" FLASK_DEBUG=1 FLASK_APP="simple_webfinger.app:create_app()" python3 -m poetry run flask run

View File

@@ -4,9 +4,19 @@ A simple, Flask-based webfinger handler.
Simple Webfinger was created to provide an OIDC href from a basic YAML configuration file. Ideally for use with Tailscale and Authentik.
## Configuration
## Running the application.
The `example-config.yaml` has the basic layout of the YAML file, which has the following fields. This should be provided as `config.yaml` in the working directory you're running the process.
`simple_webfinger` is a basic Flask application and can be configured via environment variables.
| Environment Variable | Description |
| ------------------------------ | ----------------------------------- |
| `SIMPLE_WEBFINGER_CONFIG_FILE` | Path to the YAML configuration file |
## Configuration File
The `examples/example-config.yaml` file has the basic layout of the YAML configuratio, The path to the file should be provided in the environment variable `SIMPLE_WEBFINGER_CONFIG_FILE`.
### Global Values
| Key | Value Example | Description |
| ----------- | --------------------------- | ------------------------------------------------------------- |

67
app.py
View File

@@ -1,67 +0,0 @@
from urllib.parse import urlparse
from flask import Flask, request, abort
import yaml
app = Flask(__name__)
with open('config.yaml', 'rb') as fobj:
data = yaml.load(fobj, yaml.SafeLoader)
def get_account_links(user):
links = []
account_data = data['accounts'][user]
# Append custom links
if 'links' in account_data:
links.extend(account_data['links'])
if 'mastodon' in account_data:
account, domain = account_data['mastodon'].split('@')
links.extend([
{'rel': 'http://webfinger.net/rel/profile-page', 'type': 'text/html', 'href': 'https://{0}/@{1}'.format(domain, account)},
{'rel': 'self', 'type': 'application/activity+json', 'href': 'https://{0}/users/{1}'.format(domain, account)},
{'rel': 'http://ostatus.org/schema/1.0/subscribe', 'template': "https://{0}/authorize_interaction?uri={{uri}}".format(domain)}
])
# Append the OIDC link
if 'oidc_href' in data:
links.append({
'rel': 'http://openid.net/specs/connect/1.0/issuer',
'href': data['oidc_href'],
})
return links
def filter_links(links, rel):
new_links = []
for link in links:
if link['rel'] == rel:
new_links.append(link)
return new_links
@app.route("/.well-known/webfinger")
def webfinger():
resource = request.args.get('resource')
account, domain = urlparse(resource).path.split('@')
if domain != data['domain'] or account not in data['accounts']:
abort(404)
links = get_account_links(account)
rel = request.args.get('rel')
if rel:
links = filter_links(links, rel)
return {
'subject': resource,
'links': links
}
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000)

View File

@@ -8,9 +8,13 @@ oidc_href: https://id.doofnet.uk/application/o/tailscale/
# Accounts to return valid responses for.
accounts:
testaccount: # Extra values do not need to be specified
testaccount: # Extra values do not need to be specified
nikdoof:
mastodon: nikdoof@mastodon.incognitus.net # A valid mastodon account will inject the required links for Mastodon to work correctly
mastodon: nikdoof@mastodon.incognitus.net # A valid mastodon account will inject the required links for Mastodon to work correctly
aliases: [] # Aliases for this WebFinger account: https://datatracker.ietf.org/doc/html/rfc7033#section-4.4.2
properties: [] # https://datatracker.ietf.org/doc/html/rfc7033#section-4.4.3
links: [] # https://datatracker.ietf.org/doc/html/rfc7033#section-4.4.4
links: # https://datatracker.ietf.org/doc/html/rfc7033#section-4.4.4
- rel: "http://webfinger.net/rel/avatar"
href: "https://nikdoof.com/img/avatar-dithered.png"
- rel: "http://webfinger.net/rel/profile-page"
href: "https://nikdoof.com/"

619
poetry.lock generated Normal file
View File

@@ -0,0 +1,619 @@
# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand.
[[package]]
name = "annotated-types"
version = "0.7.0"
description = "Reusable constraint types to use with typing.Annotated"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
{file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
]
[[package]]
name = "blinker"
version = "1.9.0"
description = "Fast, simple object-to-object and broadcast signaling"
optional = false
python-versions = ">=3.9"
groups = ["main", "dev"]
files = [
{file = "blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc"},
{file = "blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf"},
]
[[package]]
name = "click"
version = "8.1.8"
description = "Composable command line interface toolkit"
optional = false
python-versions = ">=3.7"
groups = ["main", "dev"]
files = [
{file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"},
{file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"},
]
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
[[package]]
name = "colorama"
version = "0.4.6"
description = "Cross-platform colored terminal text."
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
groups = ["main", "dev", "github"]
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
markers = {main = "platform_system == \"Windows\"", dev = "platform_system == \"Windows\" or sys_platform == \"win32\"", github = "sys_platform == \"win32\""}
[[package]]
name = "flask"
version = "3.1.0"
description = "A simple framework for building complex web applications."
optional = false
python-versions = ">=3.9"
groups = ["main", "dev"]
files = [
{file = "flask-3.1.0-py3-none-any.whl", hash = "sha256:d667207822eb83f1c4b50949b1623c8fc8d51f2341d65f72e1a1815397551136"},
{file = "flask-3.1.0.tar.gz", hash = "sha256:5f873c5184c897c8d9d1b05df1e3d01b14910ce69607a117bd3277098a5836ac"},
]
[package.dependencies]
blinker = ">=1.9"
click = ">=8.1.3"
itsdangerous = ">=2.2"
Jinja2 = ">=3.1.2"
Werkzeug = ">=3.1"
[package.extras]
async = ["asgiref (>=3.2)"]
dotenv = ["python-dotenv"]
[[package]]
name = "gunicorn"
version = "23.0.0"
description = "WSGI HTTP Server for UNIX"
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d"},
{file = "gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec"},
]
[package.dependencies]
packaging = "*"
[package.extras]
eventlet = ["eventlet (>=0.24.1,!=0.36.0)"]
gevent = ["gevent (>=1.4.0)"]
setproctitle = ["setproctitle"]
testing = ["coverage", "eventlet", "gevent", "pytest", "pytest-cov"]
tornado = ["tornado (>=0.2)"]
[[package]]
name = "iniconfig"
version = "2.0.0"
description = "brain-dead simple config-ini parsing"
optional = false
python-versions = ">=3.7"
groups = ["dev", "github"]
files = [
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
]
[[package]]
name = "itsdangerous"
version = "2.2.0"
description = "Safely pass data to untrusted environments and back."
optional = false
python-versions = ">=3.8"
groups = ["main", "dev"]
files = [
{file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"},
{file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"},
]
[[package]]
name = "jinja2"
version = "3.1.6"
description = "A very fast and expressive template engine."
optional = false
python-versions = ">=3.7"
groups = ["main", "dev"]
files = [
{file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"},
{file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"},
]
[package.dependencies]
MarkupSafe = ">=2.0"
[package.extras]
i18n = ["Babel (>=2.7)"]
[[package]]
name = "markupsafe"
version = "3.0.2"
description = "Safely add untrusted strings to HTML/XML markup."
optional = false
python-versions = ">=3.9"
groups = ["main", "dev"]
files = [
{file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"},
{file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"},
{file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"},
{file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"},
{file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"},
{file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"},
{file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"},
{file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"},
{file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"},
{file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"},
{file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"},
{file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"},
{file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"},
{file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"},
{file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"},
{file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"},
{file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"},
{file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"},
{file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"},
{file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"},
{file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"},
{file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"},
{file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"},
{file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"},
{file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"},
{file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"},
{file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"},
{file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"},
{file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"},
{file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"},
{file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"},
{file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"},
{file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"},
{file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"},
{file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"},
{file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"},
{file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"},
{file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"},
{file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"},
{file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"},
{file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"},
{file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"},
{file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"},
{file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"},
{file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"},
{file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"},
{file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"},
{file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"},
{file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"},
{file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"},
{file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"},
{file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"},
{file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"},
{file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"},
{file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"},
{file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"},
{file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"},
{file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"},
{file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"},
{file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"},
{file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"},
]
[[package]]
name = "packaging"
version = "24.2"
description = "Core utilities for Python packages"
optional = false
python-versions = ">=3.8"
groups = ["main", "dev", "github"]
files = [
{file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"},
{file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
]
[[package]]
name = "pluggy"
version = "1.5.0"
description = "plugin and hook calling mechanisms for python"
optional = false
python-versions = ">=3.8"
groups = ["dev", "github"]
files = [
{file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
{file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
]
[package.extras]
dev = ["pre-commit", "tox"]
testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "prometheus-client"
version = "0.21.1"
description = "Python client for the Prometheus monitoring system."
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "prometheus_client-0.21.1-py3-none-any.whl", hash = "sha256:594b45c410d6f4f8888940fe80b5cc2521b305a1fafe1c58609ef715a001f301"},
{file = "prometheus_client-0.21.1.tar.gz", hash = "sha256:252505a722ac04b0456be05c05f75f45d760c2911ffc45f2a06bcaed9f3ae3fb"},
]
[package.extras]
twisted = ["twisted"]
[[package]]
name = "prometheus-flask-exporter"
version = "0.23.2"
description = "Prometheus metrics exporter for Flask"
optional = false
python-versions = "*"
groups = ["main"]
files = [
{file = "prometheus_flask_exporter-0.23.2-py3-none-any.whl", hash = "sha256:94922a636d4c1d8b68e1ee605c30a23e9bbb0b21756df8222aa919634871784c"},
{file = "prometheus_flask_exporter-0.23.2.tar.gz", hash = "sha256:41fc9bbd7d48cc958ed8384aacf60c3621d9e903768be61c4e7f0c63872eaf1a"},
]
[package.dependencies]
flask = "*"
prometheus_client = "*"
[[package]]
name = "pydantic"
version = "2.11.1"
description = "Data validation using Python type hints"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "pydantic-2.11.1-py3-none-any.whl", hash = "sha256:5b6c415eee9f8123a14d859be0c84363fec6b1feb6b688d6435801230b56e0b8"},
{file = "pydantic-2.11.1.tar.gz", hash = "sha256:442557d2910e75c991c39f4b4ab18963d57b9b55122c8b2a9cd176d8c29ce968"},
]
[package.dependencies]
annotated-types = ">=0.6.0"
pydantic-core = "2.33.0"
typing-extensions = ">=4.12.2"
typing-inspection = ">=0.4.0"
[package.extras]
email = ["email-validator (>=2.0.0)"]
timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""]
[[package]]
name = "pydantic-core"
version = "2.33.0"
description = "Core functionality for Pydantic validation and serialization"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "pydantic_core-2.33.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:71dffba8fe9ddff628c68f3abd845e91b028361d43c5f8e7b3f8b91d7d85413e"},
{file = "pydantic_core-2.33.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:abaeec1be6ed535a5d7ffc2e6c390083c425832b20efd621562fbb5bff6dc518"},
{file = "pydantic_core-2.33.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:759871f00e26ad3709efc773ac37b4d571de065f9dfb1778012908bcc36b3a73"},
{file = "pydantic_core-2.33.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dcfebee69cd5e1c0b76a17e17e347c84b00acebb8dd8edb22d4a03e88e82a207"},
{file = "pydantic_core-2.33.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b1262b912435a501fa04cd213720609e2cefa723a07c92017d18693e69bf00b"},
{file = "pydantic_core-2.33.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4726f1f3f42d6a25678c67da3f0b10f148f5655813c5aca54b0d1742ba821b8f"},
{file = "pydantic_core-2.33.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e790954b5093dff1e3a9a2523fddc4e79722d6f07993b4cd5547825c3cbf97b5"},
{file = "pydantic_core-2.33.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:34e7fb3abe375b5c4e64fab75733d605dda0f59827752debc99c17cb2d5f3276"},
{file = "pydantic_core-2.33.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ecb158fb9b9091b515213bed3061eb7deb1d3b4e02327c27a0ea714ff46b0760"},
{file = "pydantic_core-2.33.0-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:4d9149e7528af8bbd76cc055967e6e04617dcb2a2afdaa3dea899406c5521faa"},
{file = "pydantic_core-2.33.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e81a295adccf73477220e15ff79235ca9dcbcee4be459eb9d4ce9a2763b8386c"},
{file = "pydantic_core-2.33.0-cp310-cp310-win32.whl", hash = "sha256:f22dab23cdbce2005f26a8f0c71698457861f97fc6318c75814a50c75e87d025"},
{file = "pydantic_core-2.33.0-cp310-cp310-win_amd64.whl", hash = "sha256:9cb2390355ba084c1ad49485d18449b4242da344dea3e0fe10babd1f0db7dcfc"},
{file = "pydantic_core-2.33.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a608a75846804271cf9c83e40bbb4dab2ac614d33c6fd5b0c6187f53f5c593ef"},
{file = "pydantic_core-2.33.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e1c69aa459f5609dec2fa0652d495353accf3eda5bdb18782bc5a2ae45c9273a"},
{file = "pydantic_core-2.33.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9ec80eb5a5f45a2211793f1c4aeddff0c3761d1c70d684965c1807e923a588b"},
{file = "pydantic_core-2.33.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e925819a98318d17251776bd3d6aa9f3ff77b965762155bdad15d1a9265c4cfd"},
{file = "pydantic_core-2.33.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5bf68bb859799e9cec3d9dd8323c40c00a254aabb56fe08f907e437005932f2b"},
{file = "pydantic_core-2.33.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1b2ea72dea0825949a045fa4071f6d5b3d7620d2a208335207793cf29c5a182d"},
{file = "pydantic_core-2.33.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1583539533160186ac546b49f5cde9ffc928062c96920f58bd95de32ffd7bffd"},
{file = "pydantic_core-2.33.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:23c3e77bf8a7317612e5c26a3b084c7edeb9552d645742a54a5867635b4f2453"},
{file = "pydantic_core-2.33.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a7a7f2a3f628d2f7ef11cb6188bcf0b9e1558151d511b974dfea10a49afe192b"},
{file = "pydantic_core-2.33.0-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:f1fb026c575e16f673c61c7b86144517705865173f3d0907040ac30c4f9f5915"},
{file = "pydantic_core-2.33.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:635702b2fed997e0ac256b2cfbdb4dd0bf7c56b5d8fba8ef03489c03b3eb40e2"},
{file = "pydantic_core-2.33.0-cp311-cp311-win32.whl", hash = "sha256:07b4ced28fccae3f00626eaa0c4001aa9ec140a29501770a88dbbb0966019a86"},
{file = "pydantic_core-2.33.0-cp311-cp311-win_amd64.whl", hash = "sha256:4927564be53239a87770a5f86bdc272b8d1fbb87ab7783ad70255b4ab01aa25b"},
{file = "pydantic_core-2.33.0-cp311-cp311-win_arm64.whl", hash = "sha256:69297418ad644d521ea3e1aa2e14a2a422726167e9ad22b89e8f1130d68e1e9a"},
{file = "pydantic_core-2.33.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:6c32a40712e3662bebe524abe8abb757f2fa2000028d64cc5a1006016c06af43"},
{file = "pydantic_core-2.33.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8ec86b5baa36f0a0bfb37db86c7d52652f8e8aa076ab745ef7725784183c3fdd"},
{file = "pydantic_core-2.33.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4deac83a8cc1d09e40683be0bc6d1fa4cde8df0a9bf0cda5693f9b0569ac01b6"},
{file = "pydantic_core-2.33.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:175ab598fb457a9aee63206a1993874badf3ed9a456e0654273e56f00747bbd6"},
{file = "pydantic_core-2.33.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f36afd0d56a6c42cf4e8465b6441cf546ed69d3a4ec92724cc9c8c61bd6ecf4"},
{file = "pydantic_core-2.33.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a98257451164666afafc7cbf5fb00d613e33f7e7ebb322fbcd99345695a9a61"},
{file = "pydantic_core-2.33.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecc6d02d69b54a2eb83ebcc6f29df04957f734bcf309d346b4f83354d8376862"},
{file = "pydantic_core-2.33.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a69b7596c6603afd049ce7f3835bcf57dd3892fc7279f0ddf987bebed8caa5a"},
{file = "pydantic_core-2.33.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ea30239c148b6ef41364c6f51d103c2988965b643d62e10b233b5efdca8c0099"},
{file = "pydantic_core-2.33.0-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:abfa44cf2f7f7d7a199be6c6ec141c9024063205545aa09304349781b9a125e6"},
{file = "pydantic_core-2.33.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20d4275f3c4659d92048c70797e5fdc396c6e4446caf517ba5cad2db60cd39d3"},
{file = "pydantic_core-2.33.0-cp312-cp312-win32.whl", hash = "sha256:918f2013d7eadea1d88d1a35fd4a1e16aaf90343eb446f91cb091ce7f9b431a2"},
{file = "pydantic_core-2.33.0-cp312-cp312-win_amd64.whl", hash = "sha256:aec79acc183865bad120b0190afac467c20b15289050648b876b07777e67ea48"},
{file = "pydantic_core-2.33.0-cp312-cp312-win_arm64.whl", hash = "sha256:5461934e895968655225dfa8b3be79e7e927e95d4bd6c2d40edd2fa7052e71b6"},
{file = "pydantic_core-2.33.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f00e8b59e1fc8f09d05594aa7d2b726f1b277ca6155fc84c0396db1b373c4555"},
{file = "pydantic_core-2.33.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a73be93ecef45786d7d95b0c5e9b294faf35629d03d5b145b09b81258c7cd6d"},
{file = "pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff48a55be9da6930254565ff5238d71d5e9cd8c5487a191cb85df3bdb8c77365"},
{file = "pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:26a4ea04195638dcd8c53dadb545d70badba51735b1594810e9768c2c0b4a5da"},
{file = "pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41d698dcbe12b60661f0632b543dbb119e6ba088103b364ff65e951610cb7ce0"},
{file = "pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ae62032ef513fe6281ef0009e30838a01057b832dc265da32c10469622613885"},
{file = "pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f225f3a3995dbbc26affc191d0443c6c4aa71b83358fd4c2b7d63e2f6f0336f9"},
{file = "pydantic_core-2.33.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5bdd36b362f419c78d09630cbaebc64913f66f62bda6d42d5fbb08da8cc4f181"},
{file = "pydantic_core-2.33.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:2a0147c0bef783fd9abc9f016d66edb6cac466dc54a17ec5f5ada08ff65caf5d"},
{file = "pydantic_core-2.33.0-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:c860773a0f205926172c6644c394e02c25421dc9a456deff16f64c0e299487d3"},
{file = "pydantic_core-2.33.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:138d31e3f90087f42aa6286fb640f3c7a8eb7bdae829418265e7e7474bd2574b"},
{file = "pydantic_core-2.33.0-cp313-cp313-win32.whl", hash = "sha256:d20cbb9d3e95114325780f3cfe990f3ecae24de7a2d75f978783878cce2ad585"},
{file = "pydantic_core-2.33.0-cp313-cp313-win_amd64.whl", hash = "sha256:ca1103d70306489e3d006b0f79db8ca5dd3c977f6f13b2c59ff745249431a606"},
{file = "pydantic_core-2.33.0-cp313-cp313-win_arm64.whl", hash = "sha256:6291797cad239285275558e0a27872da735b05c75d5237bbade8736f80e4c225"},
{file = "pydantic_core-2.33.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7b79af799630af263eca9ec87db519426d8c9b3be35016eddad1832bac812d87"},
{file = "pydantic_core-2.33.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eabf946a4739b5237f4f56d77fa6668263bc466d06a8036c055587c130a46f7b"},
{file = "pydantic_core-2.33.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8a1d581e8cdbb857b0e0e81df98603376c1a5c34dc5e54039dcc00f043df81e7"},
{file = "pydantic_core-2.33.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:7c9c84749f5787781c1c45bb99f433402e484e515b40675a5d121ea14711cf61"},
{file = "pydantic_core-2.33.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:64672fa888595a959cfeff957a654e947e65bbe1d7d82f550417cbd6898a1d6b"},
{file = "pydantic_core-2.33.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26bc7367c0961dec292244ef2549afa396e72e28cc24706210bd44d947582c59"},
{file = "pydantic_core-2.33.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ce72d46eb201ca43994303025bd54d8a35a3fc2a3495fac653d6eb7205ce04f4"},
{file = "pydantic_core-2.33.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:14229c1504287533dbf6b1fc56f752ce2b4e9694022ae7509631ce346158de11"},
{file = "pydantic_core-2.33.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:085d8985b1c1e48ef271e98a658f562f29d89bda98bf120502283efbc87313eb"},
{file = "pydantic_core-2.33.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31860fbda80d8f6828e84b4a4d129fd9c4535996b8249cfb8c720dc2a1a00bb8"},
{file = "pydantic_core-2.33.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f200b2f20856b5a6c3a35f0d4e344019f805e363416e609e9b47c552d35fd5ea"},
{file = "pydantic_core-2.33.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f72914cfd1d0176e58ddc05c7a47674ef4222c8253bf70322923e73e14a4ac3"},
{file = "pydantic_core-2.33.0-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:91301a0980a1d4530d4ba7e6a739ca1a6b31341252cb709948e0aca0860ce0ae"},
{file = "pydantic_core-2.33.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7419241e17c7fbe5074ba79143d5523270e04f86f1b3a0dff8df490f84c8273a"},
{file = "pydantic_core-2.33.0-cp39-cp39-win32.whl", hash = "sha256:7a25493320203005d2a4dac76d1b7d953cb49bce6d459d9ae38e30dd9f29bc9c"},
{file = "pydantic_core-2.33.0-cp39-cp39-win_amd64.whl", hash = "sha256:82a4eba92b7ca8af1b7d5ef5f3d9647eee94d1f74d21ca7c21e3a2b92e008358"},
{file = "pydantic_core-2.33.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e2762c568596332fdab56b07060c8ab8362c56cf2a339ee54e491cd503612c50"},
{file = "pydantic_core-2.33.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5bf637300ff35d4f59c006fff201c510b2b5e745b07125458a5389af3c0dff8c"},
{file = "pydantic_core-2.33.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62c151ce3d59ed56ebd7ce9ce5986a409a85db697d25fc232f8e81f195aa39a1"},
{file = "pydantic_core-2.33.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ee65f0cc652261744fd07f2c6e6901c914aa6c5ff4dcfaf1136bc394d0dd26b"},
{file = "pydantic_core-2.33.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:024d136ae44d233e6322027bbf356712b3940bee816e6c948ce4b90f18471b3d"},
{file = "pydantic_core-2.33.0-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e37f10f6d4bc67c58fbd727108ae1d8b92b397355e68519f1e4a7babb1473442"},
{file = "pydantic_core-2.33.0-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:502ed542e0d958bd12e7c3e9a015bce57deaf50eaa8c2e1c439b512cb9db1e3a"},
{file = "pydantic_core-2.33.0-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:715c62af74c236bf386825c0fdfa08d092ab0f191eb5b4580d11c3189af9d330"},
{file = "pydantic_core-2.33.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:bccc06fa0372151f37f6b69834181aa9eb57cf8665ed36405fb45fbf6cac3bae"},
{file = "pydantic_core-2.33.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5d8dc9f63a26f7259b57f46a7aab5af86b2ad6fbe48487500bb1f4b27e051e4c"},
{file = "pydantic_core-2.33.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:30369e54d6d0113d2aa5aee7a90d17f225c13d87902ace8fcd7bbf99b19124db"},
{file = "pydantic_core-2.33.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3eb479354c62067afa62f53bb387827bee2f75c9c79ef25eef6ab84d4b1ae3b"},
{file = "pydantic_core-2.33.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0310524c833d91403c960b8a3cf9f46c282eadd6afd276c8c5edc617bd705dc9"},
{file = "pydantic_core-2.33.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eddb18a00bbb855325db27b4c2a89a4ba491cd6a0bd6d852b225172a1f54b36c"},
{file = "pydantic_core-2.33.0-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:ade5dbcf8d9ef8f4b28e682d0b29f3008df9842bb5ac48ac2c17bc55771cc976"},
{file = "pydantic_core-2.33.0-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:2c0afd34f928383e3fd25740f2050dbac9d077e7ba5adbaa2227f4d4f3c8da5c"},
{file = "pydantic_core-2.33.0-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:7da333f21cd9df51d5731513a6d39319892947604924ddf2e24a4612975fb936"},
{file = "pydantic_core-2.33.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:4b6d77c75a57f041c5ee915ff0b0bb58eabb78728b69ed967bc5b780e8f701b8"},
{file = "pydantic_core-2.33.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ba95691cf25f63df53c1d342413b41bd7762d9acb425df8858d7efa616c0870e"},
{file = "pydantic_core-2.33.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:4f1ab031feb8676f6bd7c85abec86e2935850bf19b84432c64e3e239bffeb1ec"},
{file = "pydantic_core-2.33.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58c1151827eef98b83d49b6ca6065575876a02d2211f259fb1a6b7757bd24dd8"},
{file = "pydantic_core-2.33.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a66d931ea2c1464b738ace44b7334ab32a2fd50be023d863935eb00f42be1778"},
{file = "pydantic_core-2.33.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0bcf0bab28995d483f6c8d7db25e0d05c3efa5cebfd7f56474359e7137f39856"},
{file = "pydantic_core-2.33.0-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:89670d7a0045acb52be0566df5bc8b114ac967c662c06cf5e0c606e4aadc964b"},
{file = "pydantic_core-2.33.0-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:b716294e721d8060908dbebe32639b01bfe61b15f9f57bcc18ca9a0e00d9520b"},
{file = "pydantic_core-2.33.0-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fc53e05c16697ff0c1c7c2b98e45e131d4bfb78068fffff92a82d169cbb4c7b7"},
{file = "pydantic_core-2.33.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:68504959253303d3ae9406b634997a2123a0b0c1da86459abbd0ffc921695eac"},
{file = "pydantic_core-2.33.0.tar.gz", hash = "sha256:40eb8af662ba409c3cbf4a8150ad32ae73514cd7cb1f1a2113af39763dd616b3"},
]
[package.dependencies]
typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
[[package]]
name = "pytest"
version = "8.3.5"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.8"
groups = ["dev", "github"]
files = [
{file = "pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820"},
{file = "pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845"},
]
[package.dependencies]
colorama = {version = "*", markers = "sys_platform == \"win32\""}
iniconfig = "*"
packaging = "*"
pluggy = ">=1.5,<2"
[package.extras]
dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
[[package]]
name = "pytest-flask"
version = "1.3.0"
description = "A set of py.test fixtures to test Flask applications."
optional = false
python-versions = ">=3.7"
groups = ["dev"]
files = [
{file = "pytest-flask-1.3.0.tar.gz", hash = "sha256:58be1c97b21ba3c4d47e0a7691eb41007748506c36bf51004f78df10691fa95e"},
{file = "pytest_flask-1.3.0-py3-none-any.whl", hash = "sha256:c0e36e6b0fddc3b91c4362661db83fa694d1feb91fa505475be6732b5bc8c253"},
]
[package.dependencies]
Flask = "*"
pytest = ">=5.2"
Werkzeug = "*"
[package.extras]
docs = ["Sphinx", "sphinx-rtd-theme"]
[[package]]
name = "pytest-github-actions-annotate-failures"
version = "0.3.0"
description = "pytest plugin to annotate failed tests with a workflow command for GitHub Actions"
optional = false
python-versions = ">=3.8"
groups = ["github"]
files = [
{file = "pytest_github_actions_annotate_failures-0.3.0-py3-none-any.whl", hash = "sha256:41ea558ba10c332c0bfc053daeee0c85187507b2034e990f21e4f7e5fef044cf"},
{file = "pytest_github_actions_annotate_failures-0.3.0.tar.gz", hash = "sha256:d4c3177c98046c3900a7f8ddebb22ea54b9f6822201b5d3ab8fcdea51e010db7"},
]
[package.dependencies]
pytest = ">=6.0.0"
[[package]]
name = "pytest-mock"
version = "3.14.0"
description = "Thin-wrapper around the mock package for easier use with pytest"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"},
{file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"},
]
[package.dependencies]
pytest = ">=6.2.5"
[package.extras]
dev = ["pre-commit", "pytest-asyncio", "tox"]
[[package]]
name = "pyyaml"
version = "6.0.2"
description = "YAML parser and emitter for Python"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"},
{file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"},
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"},
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"},
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"},
{file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"},
{file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"},
{file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"},
{file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"},
{file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"},
{file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"},
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"},
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"},
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"},
{file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"},
{file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"},
{file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"},
{file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"},
{file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"},
{file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"},
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"},
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"},
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"},
{file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"},
{file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"},
{file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"},
{file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"},
{file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"},
{file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"},
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"},
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"},
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"},
{file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"},
{file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"},
{file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"},
{file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"},
{file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"},
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"},
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"},
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"},
{file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"},
{file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"},
{file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"},
{file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"},
{file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"},
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"},
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"},
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"},
{file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"},
{file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"},
{file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"},
{file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"},
{file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"},
]
[[package]]
name = "ruff"
version = "0.11.2"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
groups = ["dev"]
files = [
{file = "ruff-0.11.2-py3-none-linux_armv6l.whl", hash = "sha256:c69e20ea49e973f3afec2c06376eb56045709f0212615c1adb0eda35e8a4e477"},
{file = "ruff-0.11.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:2c5424cc1c4eb1d8ecabe6d4f1b70470b4f24a0c0171356290b1953ad8f0e272"},
{file = "ruff-0.11.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:ecf20854cc73f42171eedb66f006a43d0a21bfb98a2523a809931cda569552d9"},
{file = "ruff-0.11.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c543bf65d5d27240321604cee0633a70c6c25c9a2f2492efa9f6d4b8e4199bb"},
{file = "ruff-0.11.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20967168cc21195db5830b9224be0e964cc9c8ecf3b5a9e3ce19876e8d3a96e3"},
{file = "ruff-0.11.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:955a9ce63483999d9f0b8f0b4a3ad669e53484232853054cc8b9d51ab4c5de74"},
{file = "ruff-0.11.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:86b3a27c38b8fce73bcd262b0de32e9a6801b76d52cdb3ae4c914515f0cef608"},
{file = "ruff-0.11.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3b66a03b248c9fcd9d64d445bafdf1589326bee6fc5c8e92d7562e58883e30f"},
{file = "ruff-0.11.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0397c2672db015be5aa3d4dac54c69aa012429097ff219392c018e21f5085147"},
{file = "ruff-0.11.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:869bcf3f9abf6457fbe39b5a37333aa4eecc52a3b99c98827ccc371a8e5b6f1b"},
{file = "ruff-0.11.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2a2b50ca35457ba785cd8c93ebbe529467594087b527a08d487cf0ee7b3087e9"},
{file = "ruff-0.11.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7c69c74bf53ddcfbc22e6eb2f31211df7f65054bfc1f72288fc71e5f82db3eab"},
{file = "ruff-0.11.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6e8fb75e14560f7cf53b15bbc55baf5ecbe373dd5f3aab96ff7aa7777edd7630"},
{file = "ruff-0.11.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:842a472d7b4d6f5924e9297aa38149e5dcb1e628773b70e6387ae2c97a63c58f"},
{file = "ruff-0.11.2-py3-none-win32.whl", hash = "sha256:aca01ccd0eb5eb7156b324cfaa088586f06a86d9e5314b0eb330cb48415097cc"},
{file = "ruff-0.11.2-py3-none-win_amd64.whl", hash = "sha256:3170150172a8f994136c0c66f494edf199a0bbea7a409f649e4bc8f4d7084080"},
{file = "ruff-0.11.2-py3-none-win_arm64.whl", hash = "sha256:52933095158ff328f4c77af3d74f0379e34fd52f175144cefc1b192e7ccd32b4"},
{file = "ruff-0.11.2.tar.gz", hash = "sha256:ec47591497d5a1050175bdf4e1a4e6272cddff7da88a2ad595e1e326041d8d94"},
]
[[package]]
name = "typing-extensions"
version = "4.12.2"
description = "Backported and Experimental Type Hints for Python 3.8+"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
]
[[package]]
name = "typing-inspection"
version = "0.4.0"
description = "Runtime typing introspection tools"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f"},
{file = "typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122"},
]
[package.dependencies]
typing-extensions = ">=4.12.0"
[[package]]
name = "werkzeug"
version = "3.1.3"
description = "The comprehensive WSGI web application library."
optional = false
python-versions = ">=3.9"
groups = ["main", "dev"]
files = [
{file = "werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e"},
{file = "werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746"},
]
[package.dependencies]
MarkupSafe = ">=2.1.1"
[package.extras]
watchdog = ["watchdog (>=2.3)"]
[metadata]
lock-version = "2.1"
python-versions = "^3.11"
content-hash = "81ba232ec5aa11e05dd5c860bb4ba57722c831785414b8f545604a628f8a8853"

31
pyproject.toml Normal file
View File

@@ -0,0 +1,31 @@
[tool.poetry]
name = "simple-webfinger"
version = "0.1.1"
description = "A simple, Flask-based webfinger handler."
authors = ["Andrew Williams <andy@tensixtyone.com>"]
license = "MIT"
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.11"
Flask = "^3.0.3"
PyYAML = "^6.0.2"
pydantic = "^2.8.2"
prometheus-flask-exporter = "^0.23.1"
gunicorn = "^23.0.0"
[tool.poetry.group.dev.dependencies]
pytest = "^8.0.0"
ruff = "^0.11.0"
pytest-mock = "^3.12.0"
pytest-flask = "^1.3.0"
[tool.poetry.group.github]
optional = true
[tool.poetry.group.github.dependencies]
pytest-github-actions-annotate-failures = "^0.3.0"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

View File

@@ -1,2 +0,0 @@
flake8
autopep8

View File

@@ -1,2 +0,0 @@
flask==3.0.3
pyyaml==6.0.2

View File

143
simple_webfinger/app.py Normal file
View File

@@ -0,0 +1,143 @@
from urllib.parse import urlparse
import yaml
from flask import Flask, abort, request, Response
from prometheus_flask_exporter import PrometheusMetrics
from simple_webfinger.models.webfinger import JSONResourceDefinition
def get_account_links(user: str, data: dict) -> list:
links = []
account_data = data["accounts"][user]
# Append the OIDC link
if "oidc_href" in data:
links.append(
{
"rel": "http://openid.net/specs/connect/1.0/issuer",
"href": data["oidc_href"],
}
)
# Append custom links
if account_data:
if "links" in account_data:
links.extend(account_data["links"])
if "mastodon" in account_data:
account, domain = account_data["mastodon"].split("@")
links.extend(
[
{
"rel": "http://webfinger.net/rel/profile-page",
"type": "text/html",
"href": "https://{0}/@{1}".format(domain, account),
},
{
"rel": "self",
"type": "application/activity+json",
"href": "https://{0}/users/{1}".format(domain, account),
},
{
"rel": "http://ostatus.org/schema/1.0/subscribe",
"template": "https://{0}/authorize_interaction?uri={{uri}}".format(
domain
),
},
]
)
return links
def filter_links(links: dict[str, str], rel: list[str]) -> list:
"""
Filter links by rel provided.
"""
new_links = []
for link in links:
if link["rel"] in rel:
new_links.append(link)
return new_links
def create_app(config={}):
app = Flask("simple_webfinger")
metrics = PrometheusMetrics(app)
metrics.info('app_info', 'Application info', version='0.1.0')
app.webfinger_config = {
"domain": None,
"accounts": {},
}
app.config.from_prefixed_env("SIMPLE_WEBFINGER")
app.config.from_object(config)
if "CONFIG_FILE" in app.config:
with open(app.config["CONFIG_FILE"], "rb") as fobj:
app.webfinger_config = yaml.load(fobj, yaml.SafeLoader)
if not app.webfinger_config["domain"]:
app.logger.warning(
"No domain is configured for webfinger, this instance will not operate correctly."
)
@app.after_request
def inject_cors(response: Response) -> Response:
response.headers["Access-Control-Allow-Origin"] = "*"
return response
@app.route("/.well-known/webfinger")
def webfinger():
resource = request.args.get("resource")
# No resource requested, so return a HTTP 400
if not resource:
abort(400)
parsed_resource = urlparse(resource)
scheme = parsed_resource.scheme
account, domain = parsed_resource.path.split("@")
# If the request is not for the correct domain, return 404
if domain != app.webfinger_config["domain"]:
abort(404)
# Handle acct resource requests
if scheme == "acct":
if account not in app.webfinger_config["accounts"]:
abort(404)
account_data = app.webfinger_config["accounts"][account]
links = get_account_links(account, app.webfinger_config)
# If we have a 'rel' value on the request, filter down to the requested rel
# https://datatracker.ietf.org/doc/html/rfc7033#section-4.3
rel = request.args.getlist("rel")
if rel:
links = filter_links(links, rel)
response = {"subject": resource, "links": links}
# Add properties if defined in the config
if account_data and "properties" in account_data and len(account_data["properties"]):
response.update({"properties": account_data["properties"]})
return app.response_class(
response=JSONResourceDefinition(**response).model_dump_json(
exclude_none=True
),
status=200,
mimetype="application/jrd+json",
)
# Anything else, 404 for now
abort(404)
return app
if __name__ == "__main__":
create_app().run(host="0.0.0.0", port=8000)

View File

View File

@@ -0,0 +1,32 @@
from pydantic import BaseModel, AnyUrl, FutureDatetime
from typing import Optional, List, Mapping
class JSONResourceDefinitionLink(BaseModel):
"""
Link element of a JSON Resource Definition (JRD)
https://www.rfc-editor.org/rfc/rfc7033#section-4.4.4
"""
rel: str
type: Optional[str] = None
href: Optional[AnyUrl] = None
titles: Optional[Mapping[str, str]] = None
properties: Optional[Mapping[str, str]] = None
template: Optional[str] = None
class JSONResourceDefinition(BaseModel):
"""
JSON Resource Definition (JRD)
https://www.rfc-editor.org/rfc/rfc6415#appendix-A
https://www.rfc-editor.org/rfc/rfc7033#section-4.4
"""
subject: str
expires: Optional[FutureDatetime] = None
aliases: Optional[List[str]] = None
properties: Optional[Mapping[str, str]] = None
links: Optional[List[JSONResourceDefinitionLink]] = None

0
tests/__init__.py Normal file
View File

13
tests/conftest.py Normal file
View File

@@ -0,0 +1,13 @@
import pytest
import yaml
from simple_webfinger.app import create_app
@pytest.fixture(scope="module")
def app():
app = create_app()
app.webfinger_config = yaml.load(
open("examples/example-config.yaml", "r"), yaml.SafeLoader
)
yield app

101
tests/test_basic.py Normal file
View File

@@ -0,0 +1,101 @@
from simple_webfinger.models.webfinger import JSONResourceDefinition
def test_index_route(client):
"""
Check that the index route is 404
"""
response = client.get("/")
assert response.status_code == 404
def test_webfinger_route(client):
"""
Check a basic GET to the webfinger route returns a 400
"""
response = client.get("/.well-known/webfinger")
# We don't provide any arguments, so this should be a 400
assert response.status_code == 400
def test_domain(app, client):
"""
Check a correct call to the webfinger endpoint returns a valid response
"""
response = client.get("/.well-known/webfinger?resource=acct:nikdoof@doofnet.uk")
assert response.status_code == 200
assert JSONResourceDefinition.model_validate_json(response.text)
def test_invalid_domain(app, client):
"""
Check a invalid domain name results in a 404
"""
response = client.get("/.well-known/webfinger?resource=acct:nikdoof@xxxx.uk")
assert response.status_code == 404
def test_empty_config_user(app, client):
"""
Check a basic user (no extra config in the config file) results in a 200
"""
response = client.get("/.well-known/webfinger?resource=acct:testaccount@doofnet.uk")
assert response.status_code == 200
assert JSONResourceDefinition.model_validate_json(response.text)
def test_invalid_user(app, client):
"""
Check a invalid user results in a 404
"""
response = client.get("/.well-known/webfinger?resource=acct:nikxxxdoof@doofnet.uk")
assert response.status_code == 404
def test_invalid_user_request(app, client):
"""
Check a invalid user request (without acct) results in a 404
"""
response = client.get("/.well-known/webfinger?resource=nikdoof@doofnet.uk")
assert response.status_code == 404
def test_cors_headers(client):
# https://datatracker.ietf.org/doc/html/rfc7033#section-5
# Access-Control-Allow-Origin: *
response = client.get("/.well-known/webfinger?resource=acct:testaccount@doofnet.uk")
assert response.status_code == 200
assert 'Access-Control-Allow-Origin' in response.headers
assert response.headers['Access-Control-Allow-Origin'] == '*'
def test_content_type_response(client):
# https://datatracker.ietf.org/doc/html/rfc7033#section-10.2
response = client.get("/.well-known/webfinger?resource=acct:testaccount@doofnet.uk")
assert response.status_code == 200
assert response.headers['Content-Type'] == 'application/jrd+json'
def test_rel_filtering(client):
"""
Check that filtering links by rel work correctly
"""
response = client.get("/.well-known/webfinger?resource=acct:nikdoof@doofnet.uk&rel=self")
assert response.status_code == 200
assert JSONResourceDefinition.model_validate_json(response.text)
assert len(response.json['links'])
for link in response.json['links']:
assert link['rel'] == 'self'
def test_multiple_rel_filtering(client):
"""
Check that filtering links by mulitple rel work correctly
"""
rels = ['self', 'http://webfinger.net/rel/profile-page']
params = {
'resource': 'acct:nikdoof@doofnet.uk',
'rel': rels,
}
response = client.get("/.well-known/webfinger", query_string=params)
assert response.status_code == 200
assert JSONResourceDefinition.model_validate_json(response.text)
assert len(response.json['links']) > 1
for link in response.json['links']:
assert link['rel'] in rels