From 17c5e606c527a1e951ad88df4f1ee497adfae892 Mon Sep 17 00:00:00 2001
From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com>
Date: Mon, 19 Feb 2024 10:54:12 +0100
Subject: [PATCH 01/61] feat(frontend): add messages page
---
.../frontend/templates/messages.html | 120 ++++++++++++++++++
1 file changed, 120 insertions(+)
create mode 100644 src/zulip_write_only_proxy/frontend/templates/messages.html
diff --git a/src/zulip_write_only_proxy/frontend/templates/messages.html b/src/zulip_write_only_proxy/frontend/templates/messages.html
new file mode 100644
index 00000000..5481d358
--- /dev/null
+++ b/src/zulip_write_only_proxy/frontend/templates/messages.html
@@ -0,0 +1,120 @@
+{% extends 'base.html' %}
+{% block content %}
+
Key:
diff --git a/src/zulip_write_only_proxy/repositories.py b/src/zulip_write_only_proxy/repositories.py
index 9a79c4e4..3dff7f6d 100644
--- a/src/zulip_write_only_proxy/repositories.py
+++ b/src/zulip_write_only_proxy/repositories.py
@@ -1,55 +1,62 @@
-import threading
+import asyncio
+from dataclasses import dataclass, field
+from pathlib import Path
+from typing import Generic, TypeVar
import orjson
-import zulip
-from pydantic import BaseModel, DirectoryPath, FilePath, SecretStr, validate_call
+import pydantic
+from anyio import Path as APath
+from pydantic import BaseModel
-from . import models
+T = TypeVar("T", bound=BaseModel)
-file_lock = threading.Lock()
+@dataclass
+class BaseRepository(Generic[T]):
+ file: Path
+ index: str
+ model: T
-class ZuliprcRepository(BaseModel):
- directory: DirectoryPath
+ lock: asyncio.Lock = field(default_factory=asyncio.Lock, init=False, repr=False)
+ data: dict[str, T] = field(default_factory=dict, init=False, repr=False)
- def get(self, key: str) -> zulip.Client:
- return zulip.Client(config_file=str(self.directory / f"{key}.zuliprc"))
+ @staticmethod
+ def _serialize_pydantic(obj):
+ if type(obj) is pydantic.AnyUrl:
+ return str(obj)
+ if type(obj) is pydantic.SecretStr:
+ return obj.get_secret_value()
+ raise TypeError
- @validate_call
- def put(self, name: str, email: str, key: str, site: str) -> zulip.Client:
- (self.directory / f"{name}.zuliprc").write_text(
- f"""[api]
-email={email}
-key={key}
-site={site}
-"""
- )
- return zulip.Client(config_file=str(self.directory / f"{name}.zuliprc"))
+ async def load(self):
+ if not await APath(self.file).exists():
+ return
- def list(self):
- return [p.stem for p in self.directory.iterdir() if p.suffix == ".zuliprc"]
+ self.data = orjson.loads(await APath(self.file).read_bytes())
+ async def write(self):
+ async with self.lock:
+ await APath(self.file).write_bytes(
+ orjson.dumps(
+ self.data,
+ option=orjson.OPT_INDENT_2,
+ default=self._serialize_pydantic,
+ )
+ )
-class ClientRepository(BaseModel):
- """A basic file/JSON-based repository for storing client entries."""
+ async def get(self, key: str) -> T:
+ return self.model.model_validate(self.data.get(key))
- path: FilePath
+ async def insert(self, item: T):
+ _item = item.model_dump()
+ key = _item[self.index]
- def get(self, key: str) -> models.ScopedClient:
- data = orjson.loads(self.path.read_bytes())
- client_data = data[key]
+ if type(key) is pydantic.SecretStr:
+ key = key.get_secret_value()
- return models.ScopedClient(key=SecretStr(key), **client_data)
+ self.data[key] = _item
- def put(self, client: models.ScopedClient) -> None:
- with file_lock:
- data: dict[str, dict] = orjson.loads(self.path.read_bytes())
- data[client.key.get_secret_value()] = client.model_dump(exclude={"key"})
- self.path.write_bytes(orjson.dumps(data, option=orjson.OPT_INDENT_2))
+ await self.write()
- def list(self) -> list[models.ScopedClientWithKey]:
- data = orjson.loads(self.path.read_bytes())
-
- return [
- models.ScopedClientWithKey(key=key, **value) for key, value in data.items()
- ]
+ async def list(self) -> list[T]:
+ return [self.model.model_validate(item) for item in self.data.values()]
diff --git a/src/zulip_write_only_proxy/routers/api.py b/src/zulip_write_only_proxy/routers/api.py
index c3dd06d4..91221a0c 100644
--- a/src/zulip_write_only_proxy/routers/api.py
+++ b/src/zulip_write_only_proxy/routers/api.py
@@ -15,14 +15,14 @@
api_key_header = APIKeyHeader(name="X-API-key", auto_error=False)
-def get_client(
+async def get_client(
key: Annotated[str, fastapi.Security(api_key_header)]
) -> models.ScopedClient:
if key is None:
raise fastapi.HTTPException(status_code=403, detail="Not authenticated")
try:
- return services.get_client(key)
+ return await services.get_client(key)
except KeyError as e:
raise fastapi.HTTPException(
status_code=401, detail="Unauthorised", headers={"HX-Location": "/"}
@@ -66,7 +66,7 @@ def update_message(
content: Annotated[str | None, fastapi.Body(media_type="text/plain")] = None,
topic: Annotated[str | None, fastapi.Query()] = None,
):
- if not (content or topic):
+ if not (content or topic): # sourcery skip
raise fastapi.HTTPException(
status_code=400,
detail=(
diff --git a/src/zulip_write_only_proxy/routers/frontend.py b/src/zulip_write_only_proxy/routers/frontend.py
index b7eb1093..827c0956 100644
--- a/src/zulip_write_only_proxy/routers/frontend.py
+++ b/src/zulip_write_only_proxy/routers/frontend.py
@@ -52,6 +52,8 @@ async def check_auth(request: Request):
detail=f"Forbidden - `{user.get('preferred_username')}` not allowed access",
)
+ logger.debug("Authenticated", user=user)
+
async def auth_redirect(request: Request, exc: AuthException):
logger.info("Redirecting to login", status_code=exc.status_code, detail=exc.detail)
@@ -73,13 +75,13 @@ async def auth_redirect(request: Request, exc: AuthException):
@router.get("/")
-def root(request: Request):
- return client_list(request)
+async def root(request: Request):
+ return await client_list(request)
@router.get("/client/list")
-def client_list(request: Request):
- clients = services.list_clients()
+async def client_list(request: Request):
+ clients = await services.list_clients()
clients.reverse()
return TEMPLATES.TemplateResponse(
"list.html",
@@ -93,7 +95,7 @@ def client_list(request: Request):
@router.get("/client/create")
-def client_create(request: Request):
+async def client_create(request: Request):
schema = models.ScopedClientCreate.model_json_schema()
optional = schema["properties"]
required = {field: optional.pop(field) for field in schema["required"]}
@@ -113,13 +115,13 @@ async def client_create_post(request: Request):
)
dump = client.model_dump()
dump["key"] = client.key.get_secret_value()
- bot = services.get_bot(client.bot_name)
+ bot = await services.get_bot(client.bot_name)
return TEMPLATES.TemplateResponse(
"fragments/create-success.html",
{
"request": request,
"client": models.ScopedClientWithKey(**dump),
- "bot_url": bot.base_url,
+ "bot_site": bot.site,
},
)
except Exception as e:
diff --git a/src/zulip_write_only_proxy/services.py b/src/zulip_write_only_proxy/services.py
index 4d233100..307a5378 100644
--- a/src/zulip_write_only_proxy/services.py
+++ b/src/zulip_write_only_proxy/services.py
@@ -1,27 +1,41 @@
+import asyncio
from typing import TYPE_CHECKING, Annotated
import fastapi
+import zulip
from . import logger, models, mymdc, repositories
if TYPE_CHECKING:
from .settings import Settings
-CLIENT_REPO: repositories.ClientRepository = None # type: ignore[assignment]
-ZULIPRC_REPO: repositories.ZuliprcRepository = None # type: ignore[assignment]
+CLIENT_REPO: repositories.BaseRepository = None # type: ignore[assignment]
+ZULIPRC_REPO: repositories.BaseRepository = None # type: ignore[assignment]
def configure(settings: "Settings", _: fastapi.FastAPI):
"""Set up the repositories for the services. This should be called before
any of the other functions in this module."""
global CLIENT_REPO, ZULIPRC_REPO
+
+ ZULIPRC_REPO = repositories.BaseRepository(
+ file=settings.config_dir / "zuliprc.json",
+ index="name",
+ model=models.BotConfig,
+ )
+
+ CLIENT_REPO = repositories.BaseRepository(
+ file=settings.config_dir / "clients.json",
+ index="key",
+ model=models.ScopedClient,
+ )
+
logger.info(
- "Setting up repositories",
- client_repo=settings.clients.path,
- zuliprc_repo=settings.zuliprcs.directory,
+ "Setting up repositories", client_repo=CLIENT_REPO, zuliprc_repo=ZULIPRC_REPO
)
- CLIENT_REPO = repositories.ClientRepository(path=settings.clients.path)
- ZULIPRC_REPO = repositories.ZuliprcRepository(directory=settings.zuliprcs.directory)
+
+ asyncio.create_task(CLIENT_REPO.load()) # noqa: RUF006
+ asyncio.create_task(ZULIPRC_REPO.load()) # noqa: RUF006
async def create_client(
@@ -36,56 +50,77 @@ async def create_client(
)
logger.debug("Stream name from MyMdC", stream=new_client.stream)
- if new_client.bot_name is None:
- new_client.bot_name = str(new_client.proposal_no)
- logger.debug("Bot name from proposal number", bot_name=new_client.bot_name)
+ name = new_client.bot_name or new_client.proposal_no
+ key, email, site = (new_client.bot_key, new_client.bot_email, new_client.bot_site)
- bot_name = new_client.bot_name
- key, email, site = new_client.bot_key, new_client.bot_email, new_client.bot_site
-
- if bot_name not in ZULIPRC_REPO.list():
+ if name not in await ZULIPRC_REPO.list():
logger.debug("Bot zuliprc not present")
if not key or not email:
- key, email = await mymdc.CLIENT.get_zulip_bot_credentials(
+ _id, key, email = await mymdc.CLIENT.get_zulip_bot_credentials(
new_client.proposal_no
)
- logger.debug("Bot credentials from MyMdC", bot_email=email, bot_key=key)
+ logger.debug(
+ "Bot credentials from MyMdC",
+ bot_name=name,
+ bot_email=email,
+ bot_key=key,
+ bot_id=_id,
+ )
if not key or not email:
raise fastapi.HTTPException(
status_code=422,
detail=(
- f"bot '{bot_name}' does not exist, and a bot could not "
+ f"bot '{name}' does not exist, and a bot could not "
f"be found for proposal '{new_client.proposal_no}' via MyMdC. To "
"add a client with a new bot provide both bot_email bot_key."
),
)
- ZULIPRC_REPO.put(bot_name, email, key, site)
+ bot = models.BotConfig(
+ name=str(name),
+ id=_id,
+ api_key=key, # type: ignore[arg-type]
+ email=email,
+ site=site, # type: ignore[arg-type]
+ )
- _ = new_client.model_dump()
- _["created_by"] = created_by
- client = models.ScopedClient.model_validate(_)
+ await ZULIPRC_REPO.insert(bot)
+ else:
+ bot = await ZULIPRC_REPO.get(str(name))
+
+ client = models.ScopedClient.model_validate(
+ {
+ **new_client.model_dump(),
+ "created_by": created_by,
+ "bot_id": bot.id,
+ "bot_name": bot.name,
+ }
+ )
- CLIENT_REPO.put(client)
+ await CLIENT_REPO.insert(client)
logger.info("Created client", client=client)
return client
-def get_client(key: str) -> models.ScopedClient:
- client = CLIENT_REPO.get(key)
+async def get_client(key: str) -> models.ScopedClient:
+ client = await CLIENT_REPO.get(key)
+ bot_config = await ZULIPRC_REPO.get(client.bot_name)
- if isinstance(client, models.ScopedClient):
- client._client = ZULIPRC_REPO.get(client.bot_name)
+ client._client = zulip.Client(
+ email=bot_config.email,
+ api_key=bot_config.api_key.get_secret_value(),
+ site=str(bot_config.site),
+ )
return client
-def get_bot(bot_name: str):
- return ZULIPRC_REPO.get(bot_name)
+async def get_bot(bot_name: str) -> models.BotConfig:
+ return await ZULIPRC_REPO.get(bot_name)
-def list_clients() -> list[models.ScopedClientWithKey]:
- return CLIENT_REPO.list()
+async def list_clients() -> list[models.ScopedClientWithKey]:
+ return await CLIENT_REPO.list()
diff --git a/src/zulip_write_only_proxy/settings.py b/src/zulip_write_only_proxy/settings.py
index 7f4be726..bf0198f1 100644
--- a/src/zulip_write_only_proxy/settings.py
+++ b/src/zulip_write_only_proxy/settings.py
@@ -4,8 +4,6 @@
from pydantic import AnyUrl, DirectoryPath, HttpUrl, SecretStr
from pydantic_settings import BaseSettings, SettingsConfigDict
-from .repositories import ClientRepository, ZuliprcRepository
-
class Auth(BaseSettings):
client_id: str
@@ -27,7 +25,9 @@ class MyMdCCredentials(BaseSettings):
token_url: HttpUrl
_access_token: str = ""
- _expires_at: dt.datetime = dt.datetime.fromisocalendar(1970, 1, 1)
+ _expires_at: dt.datetime = dt.datetime.fromisocalendar(1970, 1, 1).astimezone(
+ dt.timezone.utc
+ )
class Settings(BaseSettings):
@@ -40,8 +40,6 @@ class Settings(BaseSettings):
auth: Auth
mymdc: MyMdCCredentials
- clients: ClientRepository
- zuliprcs: ZuliprcRepository
model_config = SettingsConfigDict(
env_prefix="ZWOP_", env_file=[".env"], env_nested_delimiter="__"
From 10406bae6d95e5a9b2a990e670d108218bd397fb Mon Sep 17 00:00:00 2001
From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com>
Date: Tue, 20 Feb 2024 09:17:12 +0100
Subject: [PATCH 04/61] feat(mymdc): return bot id with credentials
---
src/zulip_write_only_proxy/mymdc.py | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/src/zulip_write_only_proxy/mymdc.py b/src/zulip_write_only_proxy/mymdc.py
index c9af65ae..2a5e86f0 100644
--- a/src/zulip_write_only_proxy/mymdc.py
+++ b/src/zulip_write_only_proxy/mymdc.py
@@ -138,12 +138,10 @@ async def get_zulip_stream_name(self, proposal_no: int) -> str:
return res
- async def get_zulip_bot_credentials(
- self, proposal_no: int
- ) -> tuple[str | None, str | None]:
+ async def get_zulip_bot_credentials(self, proposal_no: int) -> tuple[int, str, str]:
res = (await self.get(f"/api/proposals/{proposal_no}/logbook_bot")).json()
if res is None:
raise NoStreamForProposalError(proposal_no)
- return res.get("bot_key", None), res.get("bot_email", None)
+ return res.get("bot_id"), res.get("bot_key"), res.get("bot_email")
From e4d1c97f194baa001d255c63be92aa79ea609978 Mon Sep 17 00:00:00 2001
From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com>
Date: Tue, 20 Feb 2024 09:20:53 +0100
Subject: [PATCH 05/61] feat(frontend): set htmx/css path in Jinja env
---
src/zulip_write_only_proxy/frontend/templates/base.html | 4 ++--
src/zulip_write_only_proxy/routers/frontend.py | 7 +++++++
2 files changed, 9 insertions(+), 2 deletions(-)
diff --git a/src/zulip_write_only_proxy/frontend/templates/base.html b/src/zulip_write_only_proxy/frontend/templates/base.html
index 21ebe86d..83ea17d3 100644
--- a/src/zulip_write_only_proxy/frontend/templates/base.html
+++ b/src/zulip_write_only_proxy/frontend/templates/base.html
@@ -5,11 +5,11 @@
-
+
diff --git a/src/zulip_write_only_proxy/routers/frontend.py b/src/zulip_write_only_proxy/routers/frontend.py
index 827c0956..01c9b5aa 100644
--- a/src/zulip_write_only_proxy/routers/frontend.py
+++ b/src/zulip_write_only_proxy/routers/frontend.py
@@ -29,6 +29,13 @@ def configure(_: "Settings", app: fastapi.FastAPI):
)
TEMPLATES = Jinja2Templates(directory=templates_dir)
+ TEMPLATES.env.globals["static_main_css"] = app.url_path_for(
+ "static", path="main.css"
+ )
+ TEMPLATES.env.globals["static_htmx"] = app.url_path_for(
+ "static", path="htmx.min.js"
+ )
+
class AuthException(exceptions.ZwopException):
pass
From f4702e5a4eb93518df3f6cd7d264570855fddb9f Mon Sep 17 00:00:00 2001
From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com>
Date: Tue, 20 Feb 2024 09:22:07 +0100
Subject: [PATCH 06/61] feat(frontend): add client/messages route
---
.../routers/frontend.py | 26 +++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/src/zulip_write_only_proxy/routers/frontend.py b/src/zulip_write_only_proxy/routers/frontend.py
index 01c9b5aa..40fde2bb 100644
--- a/src/zulip_write_only_proxy/routers/frontend.py
+++ b/src/zulip_write_only_proxy/routers/frontend.py
@@ -136,3 +136,29 @@ async def client_create_post(request: Request):
"fragments/alert-error.html",
{"request": request, "message": e.__repr__()},
)
+
+
+@router.get("/client/messages")
+async def client_messages(request: Request):
+ client_key = request.headers.get("X-API-Key")
+ if not client_key:
+ raise exceptions.ZwopException(
+ status_code=400,
+ detail="Bad Request - missing X-API-Key header",
+ )
+ client = await services.get_client(client_key)
+ _messages = client.get_messages()
+ logger.debug("Messages", messages=_messages)
+ messages = [
+ models.Message(topic=m["subject"], id=m["id"], content=m["content"])
+ for m in _messages["messages"]
+ ]
+ return TEMPLATES.TemplateResponse(
+ "messages.html",
+ {"request": request, "messages": messages, "client": client},
+ headers={
+ "HX-Retarget": "#content",
+ "HX-Reselect": "#content",
+ "HX-Swap": "outerHTML",
+ },
+ )
From 757ea5d326b8cc91b4682db8f5fc4286495f8091 Mon Sep 17 00:00:00 2001
From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com>
Date: Tue, 20 Feb 2024 09:27:44 +0100
Subject: [PATCH 07/61] feat(frontend): make navbar sticky
---
.../frontend/templates/fragments/navbar.html | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/zulip_write_only_proxy/frontend/templates/fragments/navbar.html b/src/zulip_write_only_proxy/frontend/templates/fragments/navbar.html
index 4f1fe97a..d6bcbdc7 100644
--- a/src/zulip_write_only_proxy/frontend/templates/fragments/navbar.html
+++ b/src/zulip_write_only_proxy/frontend/templates/fragments/navbar.html
@@ -1,4 +1,8 @@
-
+
Date: Tue, 20 Feb 2024 09:28:43 +0100
Subject: [PATCH 08/61] feat(frontend): add button to go to messages
---
.../frontend/templates/list.html | 46 ++++++++++++++++---
1 file changed, 40 insertions(+), 6 deletions(-)
diff --git a/src/zulip_write_only_proxy/frontend/templates/list.html b/src/zulip_write_only_proxy/frontend/templates/list.html
index 3f2aff6f..ed1feba1 100644
--- a/src/zulip_write_only_proxy/frontend/templates/list.html
+++ b/src/zulip_write_only_proxy/frontend/templates/list.html
@@ -6,22 +6,51 @@
Proposal |
Stream |
Name |
-
Key |
+
|
{% for client in clients %}
-
+
{{ client.proposal_no }} |
{{ client.stream }} |
{{ client.bot_name }} |
+
|
@@ -31,7 +60,7 @@
{% endblock %}
From 74b68a545b0a70f969e2fefb3b2059eaf2cac048 Mon Sep 17 00:00:00 2001
From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com>
Date: Tue, 20 Feb 2024 09:29:13 +0100
Subject: [PATCH 09/61] fix(build): use raw url for htmx add
---
Dockerfile | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Dockerfile b/Dockerfile
index e6ca572f..47c8775e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -16,8 +16,8 @@ COPY ./src/zulip_write_only_proxy/frontend/templates/ \
RUN pnpm build
-ADD --link https://unpkg.com/browse/htmx.org@1.9.10/dist/htmx.js \
- https://unpkg.com/browse/htmx.org@1.9.10/dist/htmx.min.js \
+ADD --link https://unpkg.com/htmx.org@1.9.10/dist/htmx.js \
+ https://unpkg.com/htmx.org@1.9.10/dist/htmx.min.js \
./src/zulip_write_only_proxy/frontend/static/
FROM python:3.11-alpine
From f75136be195e0009242ebcec6bcdc0c8204a7d02 Mon Sep 17 00:00:00 2001
From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com>
Date: Tue, 20 Feb 2024 09:29:51 +0100
Subject: [PATCH 10/61] build: add prettier/plugins to dev deps
---
package.json | 5 ++++
pnpm-lock.yaml | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 82 insertions(+)
diff --git a/package.json b/package.json
index 9ce45320..0945d519 100644
--- a/package.json
+++ b/package.json
@@ -7,5 +7,10 @@
"htmx": "cd src/zulip_write_only_proxy/frontend/static/; wget -N https://unpkg.com/browse/htmx.org@1.9.10/dist/htmx.js; wget -N https://unpkg.com/browse/htmx.org@1.9.10/dist/htmx.min.js",
"build": "tailwindcss -o src/zulip_write_only_proxy/frontend/static/main.css",
"watch": "tailwindcss -o src/zulip_write_only_proxy/frontend/static/main.css --watch"
+ },
+ "devDependencies": {
+ "prettier": "^3.2.5",
+ "prettier-plugin-jinja-template": "^1.3.2",
+ "prettier-plugin-tailwindcss": "^0.5.11"
}
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b34b9d04..9026d951 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -12,6 +12,17 @@ dependencies:
specifier: ^3.4.1
version: 3.4.1
+devDependencies:
+ prettier:
+ specifier: ^3.2.5
+ version: 3.2.5
+ prettier-plugin-jinja-template:
+ specifier: ^1.3.2
+ version: 1.3.2(prettier@3.2.5)
+ prettier-plugin-tailwindcss:
+ specifier: ^0.5.11
+ version: 0.5.11(prettier@3.2.5)
+
packages:
/@alloc/quick-lru@5.2.0:
@@ -557,6 +568,72 @@ packages:
source-map-js: 1.0.2
dev: false
+ /prettier-plugin-jinja-template@1.3.2(prettier@3.2.5):
+ resolution: {integrity: sha512-ROglSKajIA4TRxM5othKfhc+dZrWYaVoJW/0EHlN0WwCXS1FmF/2mtfDkJW4wSTBqu7re1svsYMAu+oMGf6T9Q==}
+ peerDependencies:
+ prettier: ^3.0.0
+ dependencies:
+ prettier: 3.2.5
+ dev: true
+
+ /prettier-plugin-tailwindcss@0.5.11(prettier@3.2.5):
+ resolution: {integrity: sha512-AvI/DNyMctyyxGOjyePgi/gqj5hJYClZ1avtQvLlqMT3uDZkRbi4HhGUpok3DRzv9z7Lti85Kdj3s3/1CeNI0w==}
+ engines: {node: '>=14.21.3'}
+ peerDependencies:
+ '@ianvs/prettier-plugin-sort-imports': '*'
+ '@prettier/plugin-pug': '*'
+ '@shopify/prettier-plugin-liquid': '*'
+ '@trivago/prettier-plugin-sort-imports': '*'
+ prettier: ^3.0
+ prettier-plugin-astro: '*'
+ prettier-plugin-css-order: '*'
+ prettier-plugin-import-sort: '*'
+ prettier-plugin-jsdoc: '*'
+ prettier-plugin-marko: '*'
+ prettier-plugin-organize-attributes: '*'
+ prettier-plugin-organize-imports: '*'
+ prettier-plugin-style-order: '*'
+ prettier-plugin-svelte: '*'
+ prettier-plugin-twig-melody: '*'
+ peerDependenciesMeta:
+ '@ianvs/prettier-plugin-sort-imports':
+ optional: true
+ '@prettier/plugin-pug':
+ optional: true
+ '@shopify/prettier-plugin-liquid':
+ optional: true
+ '@trivago/prettier-plugin-sort-imports':
+ optional: true
+ prettier-plugin-astro:
+ optional: true
+ prettier-plugin-css-order:
+ optional: true
+ prettier-plugin-import-sort:
+ optional: true
+ prettier-plugin-jsdoc:
+ optional: true
+ prettier-plugin-marko:
+ optional: true
+ prettier-plugin-organize-attributes:
+ optional: true
+ prettier-plugin-organize-imports:
+ optional: true
+ prettier-plugin-style-order:
+ optional: true
+ prettier-plugin-svelte:
+ optional: true
+ prettier-plugin-twig-melody:
+ optional: true
+ dependencies:
+ prettier: 3.2.5
+ dev: true
+
+ /prettier@3.2.5:
+ resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==}
+ engines: {node: '>=14'}
+ hasBin: true
+ dev: true
+
/queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
dev: false
From bae3c28b8c2b232e0bc096ba744c7ac435b8a0d3 Mon Sep 17 00:00:00 2001
From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com>
Date: Tue, 20 Feb 2024 09:30:48 +0100
Subject: [PATCH 11/61] build: remove typer, add mail dependency, add vcr
---
poetry.lock | 153 +++++++++++++++++++++++++++++++++++++++++++++++++
pyproject.toml | 5 +-
2 files changed, 156 insertions(+), 2 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index 27756005..db364a2a 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -516,6 +516,41 @@ files = [
{file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"},
]
+[[package]]
+name = "dnspython"
+version = "2.6.0"
+description = "DNS toolkit"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "dnspython-2.6.0-py3-none-any.whl", hash = "sha256:44c40af3bffed66e3307cea9ab667fd583e138ecc0777b18f262a9dae034e5fa"},
+ {file = "dnspython-2.6.0.tar.gz", hash = "sha256:233f871ff384d84c33b2eaf4358ffe7f8927eae3b257ad8467f9bdba7e7ac6bc"},
+]
+
+[package.extras]
+dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "sphinx (>=7.2.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"]
+dnssec = ["cryptography (>=41)"]
+doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"]
+doq = ["aioquic (>=0.9.25)"]
+idna = ["idna (>=3.6)"]
+trio = ["trio (>=0.23)"]
+wmi = ["wmi (>=1.5.1)"]
+
+[[package]]
+name = "email-validator"
+version = "2.1.0.post1"
+description = "A robust email address syntax and deliverability validation library."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "email_validator-2.1.0.post1-py3-none-any.whl", hash = "sha256:c973053efbeddfef924dc0bd93f6e77a1ea7ee0fce935aea7103c7a3d6d2d637"},
+ {file = "email_validator-2.1.0.post1.tar.gz", hash = "sha256:a4b0bd1cf55f073b924258d19321b1f3aa74b4b5a71a42c305575dba920e1a44"},
+]
+
+[package.dependencies]
+dnspython = ">=2.0.0"
+idna = ">=2.0.0"
+
[[package]]
name = "fastapi"
version = "0.104.1"
@@ -764,6 +799,105 @@ files = [
{file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
]
+[[package]]
+name = "multidict"
+version = "6.0.5"
+description = "multidict implementation"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9"},
+ {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604"},
+ {file = "multidict-6.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:411bf8515f3be9813d06004cac41ccf7d1cd46dfe233705933dd163b60e37600"},
+ {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d147090048129ce3c453f0292e7697d333db95e52616b3793922945804a433c"},
+ {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:215ed703caf15f578dca76ee6f6b21b7603791ae090fbf1ef9d865571039ade5"},
+ {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c6390cf87ff6234643428991b7359b5f59cc15155695deb4eda5c777d2b880f"},
+ {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fd81c4ebdb4f214161be351eb5bcf385426bf023041da2fd9e60681f3cebae"},
+ {file = "multidict-6.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cc2ad10255f903656017363cd59436f2111443a76f996584d1077e43ee51182"},
+ {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6939c95381e003f54cd4c5516740faba40cf5ad3eeff460c3ad1d3e0ea2549bf"},
+ {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:220dd781e3f7af2c2c1053da9fa96d9cf3072ca58f057f4c5adaaa1cab8fc442"},
+ {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:766c8f7511df26d9f11cd3a8be623e59cca73d44643abab3f8c8c07620524e4a"},
+ {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:fe5d7785250541f7f5019ab9cba2c71169dc7d74d0f45253f8313f436458a4ef"},
+ {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1c1496e73051918fcd4f58ff2e0f2f3066d1c76a0c6aeffd9b45d53243702cc"},
+ {file = "multidict-6.0.5-cp310-cp310-win32.whl", hash = "sha256:7afcdd1fc07befad18ec4523a782cde4e93e0a2bf71239894b8d61ee578c1319"},
+ {file = "multidict-6.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:99f60d34c048c5c2fabc766108c103612344c46e35d4ed9ae0673d33c8fb26e8"},
+ {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba"},
+ {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e"},
+ {file = "multidict-6.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd"},
+ {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3"},
+ {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf"},
+ {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29"},
+ {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed"},
+ {file = "multidict-6.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733"},
+ {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f"},
+ {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4"},
+ {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1"},
+ {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc"},
+ {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e"},
+ {file = "multidict-6.0.5-cp311-cp311-win32.whl", hash = "sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c"},
+ {file = "multidict-6.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea"},
+ {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e"},
+ {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b"},
+ {file = "multidict-6.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5"},
+ {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450"},
+ {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496"},
+ {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a"},
+ {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226"},
+ {file = "multidict-6.0.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271"},
+ {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb"},
+ {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef"},
+ {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24"},
+ {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6"},
+ {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda"},
+ {file = "multidict-6.0.5-cp312-cp312-win32.whl", hash = "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5"},
+ {file = "multidict-6.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556"},
+ {file = "multidict-6.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:19fe01cea168585ba0f678cad6f58133db2aa14eccaf22f88e4a6dccadfad8b3"},
+ {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bf7a982604375a8d49b6cc1b781c1747f243d91b81035a9b43a2126c04766f5"},
+ {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:107c0cdefe028703fb5dafe640a409cb146d44a6ae201e55b35a4af8e95457dd"},
+ {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:403c0911cd5d5791605808b942c88a8155c2592e05332d2bf78f18697a5fa15e"},
+ {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aeaf541ddbad8311a87dd695ed9642401131ea39ad7bc8cf3ef3967fd093b626"},
+ {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4972624066095e52b569e02b5ca97dbd7a7ddd4294bf4e7247d52635630dd83"},
+ {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d946b0a9eb8aaa590df1fe082cee553ceab173e6cb5b03239716338629c50c7a"},
+ {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b55358304d7a73d7bdf5de62494aaf70bd33015831ffd98bc498b433dfe5b10c"},
+ {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a3145cb08d8625b2d3fee1b2d596a8766352979c9bffe5d7833e0503d0f0b5e5"},
+ {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d65f25da8e248202bd47445cec78e0025c0fe7582b23ec69c3b27a640dd7a8e3"},
+ {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c9bf56195c6bbd293340ea82eafd0071cb3d450c703d2c93afb89f93b8386ccc"},
+ {file = "multidict-6.0.5-cp37-cp37m-win32.whl", hash = "sha256:69db76c09796b313331bb7048229e3bee7928eb62bab5e071e9f7fcc4879caee"},
+ {file = "multidict-6.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:fce28b3c8a81b6b36dfac9feb1de115bab619b3c13905b419ec71d03a3fc1423"},
+ {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76f067f5121dcecf0d63a67f29080b26c43c71a98b10c701b0677e4a065fbd54"},
+ {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b82cc8ace10ab5bd93235dfaab2021c70637005e1ac787031f4d1da63d493c1d"},
+ {file = "multidict-6.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cb241881eefd96b46f89b1a056187ea8e9ba14ab88ba632e68d7a2ecb7aadf7"},
+ {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e94e6912639a02ce173341ff62cc1201232ab86b8a8fcc05572741a5dc7d93"},
+ {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09a892e4a9fb47331da06948690ae38eaa2426de97b4ccbfafbdcbe5c8f37ff8"},
+ {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55205d03e8a598cfc688c71ca8ea5f66447164efff8869517f175ea632c7cb7b"},
+ {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37b15024f864916b4951adb95d3a80c9431299080341ab9544ed148091b53f50"},
+ {file = "multidict-6.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2a1dee728b52b33eebff5072817176c172050d44d67befd681609b4746e1c2e"},
+ {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:edd08e6f2f1a390bf137080507e44ccc086353c8e98c657e666c017718561b89"},
+ {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:60d698e8179a42ec85172d12f50b1668254628425a6bd611aba022257cac1386"},
+ {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3d25f19500588cbc47dc19081d78131c32637c25804df8414463ec908631e453"},
+ {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4cc0ef8b962ac7a5e62b9e826bd0cd5040e7d401bc45a6835910ed699037a461"},
+ {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eca2e9d0cc5a889850e9bbd68e98314ada174ff6ccd1129500103df7a94a7a44"},
+ {file = "multidict-6.0.5-cp38-cp38-win32.whl", hash = "sha256:4a6a4f196f08c58c59e0b8ef8ec441d12aee4125a7d4f4fef000ccb22f8d7241"},
+ {file = "multidict-6.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:0275e35209c27a3f7951e1ce7aaf93ce0d163b28948444bec61dd7badc6d3f8c"},
+ {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e7be68734bd8c9a513f2b0cfd508802d6609da068f40dc57d4e3494cefc92929"},
+ {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d9ea7a7e779d7a3561aade7d596649fbecfa5c08a7674b11b423783217933f9"},
+ {file = "multidict-6.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ea1456df2a27c73ce51120fa2f519f1bea2f4a03a917f4a43c8707cf4cbbae1a"},
+ {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf590b134eb70629e350691ecca88eac3e3b8b3c86992042fb82e3cb1830d5e1"},
+ {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c0631926c4f58e9a5ccce555ad7747d9a9f8b10619621f22f9635f069f6233e"},
+ {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dce1c6912ab9ff5f179eaf6efe7365c1f425ed690b03341911bf4939ef2f3046"},
+ {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0868d64af83169e4d4152ec612637a543f7a336e4a307b119e98042e852ad9c"},
+ {file = "multidict-6.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:141b43360bfd3bdd75f15ed811850763555a251e38b2405967f8e25fb43f7d40"},
+ {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7df704ca8cf4a073334e0427ae2345323613e4df18cc224f647f251e5e75a527"},
+ {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6214c5a5571802c33f80e6c84713b2c79e024995b9c5897f794b43e714daeec9"},
+ {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd6c8fca38178e12c00418de737aef1261576bd1b6e8c6134d3e729a4e858b38"},
+ {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e02021f87a5b6932fa6ce916ca004c4d441509d33bbdbeca70d05dff5e9d2479"},
+ {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ebd8d160f91a764652d3e51ce0d2956b38efe37c9231cd82cfc0bed2e40b581c"},
+ {file = "multidict-6.0.5-cp39-cp39-win32.whl", hash = "sha256:04da1bb8c8dbadf2a18a452639771951c662c5ad03aefe4884775454be322c9b"},
+ {file = "multidict-6.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:d6f6d4f185481c9669b9447bf9d9cf3b95a0e9df9d169bbc17e363b7d5487755"},
+ {file = "multidict-6.0.5-py3-none-any.whl", hash = "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7"},
+ {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"},
+]
+
[[package]]
name = "mypy"
version = "1.11.1"
@@ -1547,6 +1681,25 @@ h11 = ">=0.8"
[package.extras]
standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"]
+[[package]]
+name = "vcrpy"
+version = "6.0.1"
+description = "Automatically mock your HTTP interactions to simplify and speed up testing"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "vcrpy-6.0.1.tar.gz", hash = "sha256:9e023fee7f892baa0bbda2f7da7c8ac51165c1c6e38ff8688683a12a4bde9278"},
+]
+
+[package.dependencies]
+PyYAML = "*"
+urllib3 = {version = "<2", markers = "platform_python_implementation == \"PyPy\""}
+wrapt = "*"
+yarl = "*"
+
+[package.extras]
+tests = ["Werkzeug (==2.0.3)", "aiohttp", "boto3", "httplib2", "httpx", "pytest", "pytest-aiohttp", "pytest-asyncio", "pytest-cov", "pytest-httpbin", "requests (>=2.22.0)", "tornado", "urllib3"]
+
[[package]]
name = "wcwidth"
version = "0.2.13"
diff --git a/pyproject.toml b/pyproject.toml
index 4996fe28..f80345ca 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -16,8 +16,7 @@ uvicorn = "^0.23.1"
zulip = "^0.8.2"
python-multipart = "^0.0.6"
orjson = "^3.9.2"
-pydantic = "^2.4.2"
-typer = "^0.9.0"
+pydantic = {extras = ["email"], version = "^2.6.1"}
pydantic-settings = "^2.4.0"
authlib = "^1.3.0"
itsdangerous = "^2.1.2"
@@ -29,6 +28,7 @@ pytest = "^7.4.0"
httpx = "^0.24.1"
pytest-cov = "^4.1.0"
pytest-asyncio = "^0.21.1"
+vcrpy = "^6.0.1"
[tool.poetry.group.dev.dependencies]
poethepoet = "^0.21.1"
@@ -36,6 +36,7 @@ commitizen = "^3.6.0"
types-colorama = "^0.4.15.20240205"
rich = "^13.7.0"
debugpy = "^1.8.1"
+vcrpy = "^6.0.1"
[tool.poetry.group.lint.dependencies]
ruff = "^0.1.0"
From 8bd1e94546fd8e8fdb9f7407fe761d99d22ecf3d Mon Sep 17 00:00:00 2001
From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com>
Date: Tue, 20 Feb 2024 12:05:42 +0100
Subject: [PATCH 12/61] fix(frontend): use function names in `url_for` calls
---
.../frontend/templates/create.html | 2 +-
.../frontend/templates/fragments/navbar.html | 12 ++++--------
2 files changed, 5 insertions(+), 9 deletions(-)
diff --git a/src/zulip_write_only_proxy/frontend/templates/create.html b/src/zulip_write_only_proxy/frontend/templates/create.html
index cbfae552..1561c812 100644
--- a/src/zulip_write_only_proxy/frontend/templates/create.html
+++ b/src/zulip_write_only_proxy/frontend/templates/create.html
@@ -3,7 +3,7 @@