feat: add return type annotations to async DB functions (#20) #47

Merged
cal merged 3 commits from ai/paper-dynasty-card-creation-20 into main 2026-03-23 19:52:03 +00:00
Collaborator

Closes #20

Summary

Adds return type annotations to all six async functions in db_calls.py, enabling static analysis to catch callers that don't handle None returns.

  • db_getOptional[dict] (can return None when none_okay=True)
  • url_getaiohttp.ClientResponse
  • db_patchdict
  • db_postdict
  • db_putdict
  • db_deletedict

Also adds Optional to the from typing import line.

Files Changed

  • db_calls.py

Notes

The linter auto-reformatted some f-string quote styles on save (double → single for nested strings). These are cosmetic-only and don't affect behavior.

Other observations

  • timeout parameter is accepted by all async functions but never forwarded to aiohttp — it's effectively a no-op. Not fixed here per issue scope.
  • Several existing parameters use int = None / dict = None which are technically incorrect (should be Optional[int], etc.) — pre-existing, out of scope for this issue.
Closes #20 ## Summary Adds return type annotations to all six async functions in `db_calls.py`, enabling static analysis to catch callers that don't handle `None` returns. - `db_get` → `Optional[dict]` (can return `None` when `none_okay=True`) - `url_get` → `aiohttp.ClientResponse` - `db_patch` → `dict` - `db_post` → `dict` - `db_put` → `dict` - `db_delete` → `dict` Also adds `Optional` to the `from typing import` line. ## Files Changed - `db_calls.py` ## Notes The linter auto-reformatted some f-string quote styles on save (double → single for nested strings). These are cosmetic-only and don't affect behavior. ## Other observations - `timeout` parameter is accepted by all async functions but never forwarded to `aiohttp` — it's effectively a no-op. Not fixed here per issue scope. - Several existing parameters use `int = None` / `dict = None` which are technically incorrect (should be `Optional[int]`, etc.) — pre-existing, out of scope for this issue.
Claude added 1 commit 2026-03-21 17:33:17 +00:00
Closes #20

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude added the
ai-reviewing
label 2026-03-21 17:45:48 +00:00
Claude reviewed 2026-03-21 17:46:48 +00:00
Claude left a comment
Author
Collaborator

AI Code Review

Files Reviewed

  • db_calls.py (modified)

Findings

Correctness

  • db_get → Optional[dict]: Correct. The function returns None when none_okay=True and the response is non-200, a dict on success, or raises ValueError. Optional[dict] accurately captures this.
  • url_get → aiohttp.ClientResponse: Correct as a type annotation. Worth noting for callers: return r executes inside async with session.get(url) as r:, which closes the response and its underlying connection on return. The body will have already been read (it hasn't been, actually, in this function), so the returned ClientResponse will have a closed connection. This is a pre-existing design issue — not introduced by this PR — and the annotation is accurate for what's actually returned.
  • db_patch / db_post / db_put / db_delete → dict: Correct. These either return js (the parsed JSON response, expected to be a dict for this API) or raise ValueError. They never return None.
  • Optional added to the from typing import line cleanly.

Security

  • No issues introduced. Cosmetic f-string changes are behaviorally inert.

Style & Conventions

  • The f-string quote reformatting (double → single for nested strings) is purely cosmetic. Transparent disclosure in the PR body is appreciated.
  • db_delete still has timeout=3 without a type annotation (pre-existing, noted in PR body as out of scope). Consistent with the PR's declared scope.

Suggestions

  • r.json() in aiohttp can return any JSON-deserializable type (dict, list, str, int). If the API ever returns a top-level array, the -> dict annotations on db_patch/post/put/delete would be technically imprecise. For this project's API the dict assumption holds, but Any or a typed TypedDict would be more correct long-term. Not a blocker.

Verdict: COMMENT (LGTM)

Clean, focused change. The annotations accurately reflect the existing function contracts and Optional is the right choice for db_get. The f-string reformatting is noise-free. Ready to merge.


Automated review by Claude PR Reviewer

## AI Code Review ### Files Reviewed - `db_calls.py` (modified) ### Findings #### Correctness - `db_get → Optional[dict]`: Correct. The function returns `None` when `none_okay=True` and the response is non-200, a `dict` on success, or raises `ValueError`. `Optional[dict]` accurately captures this. - `url_get → aiohttp.ClientResponse`: Correct as a type annotation. Worth noting for callers: `return r` executes inside `async with session.get(url) as r:`, which closes the response and its underlying connection on return. The body will have already been read (it hasn't been, actually, in this function), so the returned `ClientResponse` will have a closed connection. This is a pre-existing design issue — not introduced by this PR — and the annotation is accurate for what's actually returned. - `db_patch / db_post / db_put / db_delete → dict`: Correct. These either return `js` (the parsed JSON response, expected to be a dict for this API) or raise `ValueError`. They never return `None`. - `Optional` added to the `from typing import` line cleanly. #### Security - No issues introduced. Cosmetic f-string changes are behaviorally inert. #### Style & Conventions - The f-string quote reformatting (double → single for nested strings) is purely cosmetic. Transparent disclosure in the PR body is appreciated. - `db_delete` still has `timeout=3` without a type annotation (pre-existing, noted in PR body as out of scope). Consistent with the PR's declared scope. #### Suggestions - `r.json()` in aiohttp can return any JSON-deserializable type (`dict`, `list`, `str`, `int`). If the API ever returns a top-level array, the `-> dict` annotations on `db_patch/post/put/delete` would be technically imprecise. For this project's API the dict assumption holds, but `Any` or a typed `TypedDict` would be more correct long-term. Not a blocker. ### Verdict: COMMENT (LGTM) Clean, focused change. The annotations accurately reflect the existing function contracts and `Optional` is the right choice for `db_get`. The f-string reformatting is noise-free. Ready to merge. --- *Automated review by Claude PR Reviewer*
Claude added
ai-reviewed
and removed
ai-reviewing
labels 2026-03-21 17:47:12 +00:00
cal requested changes 2026-03-23 12:07:51 +00:00
Dismissed
cal left a comment
Owner

AI Code Review

Files Reviewed

  • db_calls.py (modified)

Findings

Correctness

url_get return type is inaccurate — aiohttp.ClientResponse is a closed context manager here.

The annotation -> aiohttp.ClientResponse at line 79 is misleading and technically incorrect. url_get uses async with session.get(url) as r: and then does return r inside that context. Once the async with block exits, the ClientResponse is closed and its connection released back to the pool. Any caller that receives the returned object and tries to read the body (e.g. await response.read() or await response.json()) will get an error because the underlying connection is already closed.

The correct fix for the type annotation would be to return bytes or a parsed type (having awaited r.read() before the context exits), not aiohttp.ClientResponse. As written, the annotation tells callers they can use the response object for body reads, which they cannot safely do.

This bug pre-exists the PR — the PR did not introduce it — but the PR is the first to assert a type contract that makes this silent footgun more visible and misleading. Annotating an incorrect type is worse than no annotation.

db_get return type is incomplete.

The annotation -> Optional[dict] is correct for the none_okay=True path, but when none_okay=False the function either returns dict or raises. The type is accurate in practice, but it loses precision: callers who always pass none_okay=False will believe they may receive None and add unnecessary guards. A more precise type would be Optional[dict] with a note, or a typed overload. This is minor and not a blocker on its own.

Security

  • AUTH_TOKEN contains a hardcoded bearer token at line 7. This is a pre-existing issue and out of scope for this PR, but worth flagging for a future cleanup issue.
  • No new security issues introduced by this PR.

Style & Conventions

  • The f-string quote reformatting (double → single for inner strings) is cosmetic-only and consistent with PEP 8 style for nested quotes. No issues.
  • Optional import addition is correct. No issues.
  • The db_calls.py file on disk (current main) still uses double-outer quotes in the log_return_value and player_desc f-strings (lines 42-47, 186), meaning the diff shows the branch diverging from main on these cosmetic lines. These reformats are harmless but add noise to the diff.

Suggestions

  • Fix url_get to either: (a) read the body inside the context manager and return bytes, or (b) annotate -> None and raise on non-200 (since the only "success" use case must read inside the async with). The current annotation is a type lie that will mislead callers.
  • The timeout no-op is correctly called out in the PR notes. Consider opening a follow-up issue if one doesn't already exist — it affects all six functions.
  • Pre-existing int = None / dict = None parameter defaults should be addressed in a future typing cleanup pass.

Verdict: REQUEST_CHANGES

The url_get -> aiohttp.ClientResponse annotation is technically incorrect and actively misleading. The response object is returned from inside a closed context manager, meaning callers cannot safely use it for body reads. Shipping this annotation establishes a false type contract. The annotation should be corrected before merge — either fix the implementation to return body content, or annotate accurately.

All other changes (five of the six annotations, Optional import, f-string reformats) are correct and clean.


Automated review by Claude PR Reviewer

## AI Code Review ### Files Reviewed - `db_calls.py` (modified) ### Findings #### Correctness **`url_get` return type is inaccurate — `aiohttp.ClientResponse` is a closed context manager here.** The annotation `-> aiohttp.ClientResponse` at line 79 is misleading and technically incorrect. `url_get` uses `async with session.get(url) as r:` and then does `return r` inside that context. Once the `async with` block exits, the `ClientResponse` is closed and its connection released back to the pool. Any caller that receives the returned object and tries to read the body (e.g. `await response.read()` or `await response.json()`) will get an error because the underlying connection is already closed. The correct fix for the type annotation would be to return `bytes` or a parsed type (having awaited `r.read()` before the context exits), not `aiohttp.ClientResponse`. As written, the annotation tells callers they can use the response object for body reads, which they cannot safely do. This bug pre-exists the PR — the PR did not introduce it — but the PR is the first to assert a type contract that makes this silent footgun more visible and misleading. Annotating an incorrect type is worse than no annotation. **`db_get` return type is incomplete.** The annotation `-> Optional[dict]` is correct for the `none_okay=True` path, but when `none_okay=False` the function either returns `dict` or raises. The type is accurate in practice, but it loses precision: callers who always pass `none_okay=False` will believe they may receive `None` and add unnecessary guards. A more precise type would be `Optional[dict]` with a note, or a typed overload. This is minor and not a blocker on its own. #### Security - `AUTH_TOKEN` contains a hardcoded bearer token at line 7. This is a pre-existing issue and out of scope for this PR, but worth flagging for a future cleanup issue. - No new security issues introduced by this PR. #### Style & Conventions - The f-string quote reformatting (double → single for inner strings) is cosmetic-only and consistent with PEP 8 style for nested quotes. No issues. - `Optional` import addition is correct. No issues. - The `db_calls.py` file on disk (current `main`) still uses double-outer quotes in the `log_return_value` and `player_desc` f-strings (lines 42-47, 186), meaning the diff shows the branch diverging from main on these cosmetic lines. These reformats are harmless but add noise to the diff. #### Suggestions - Fix `url_get` to either: (a) read the body inside the context manager and return `bytes`, or (b) annotate `-> None` and raise on non-200 (since the only "success" use case must read inside the `async with`). The current annotation is a type lie that will mislead callers. - The `timeout` no-op is correctly called out in the PR notes. Consider opening a follow-up issue if one doesn't already exist — it affects all six functions. - Pre-existing `int = None` / `dict = None` parameter defaults should be addressed in a future typing cleanup pass. ### Verdict: REQUEST_CHANGES The `url_get -> aiohttp.ClientResponse` annotation is technically incorrect and actively misleading. The response object is returned from inside a closed context manager, meaning callers cannot safely use it for body reads. Shipping this annotation establishes a false type contract. The annotation should be corrected before merge — either fix the implementation to return body content, or annotate accurately. All other changes (five of the six annotations, `Optional` import, f-string reformats) are correct and clean. --- *Automated review by Claude PR Reviewer*
cal removed the
ai-reviewed
label 2026-03-23 12:42:05 +00:00
Claude added the
ai-reviewing
label 2026-03-23 12:45:46 +00:00
Claude reviewed 2026-03-23 12:47:45 +00:00
Claude left a comment
Author
Collaborator

AI Code Review

Files Reviewed

  • db_calls.py (modified)

Findings

Correctness

  • db_get → Optional[dict]: Correct. The function returns None on non-200 when none_okay=True (line 80), and a parsed JSON dict on success. Optional[dict] accurately captures both paths.
  • url_get → aiohttp.ClientResponse: Technically correct — r is a ClientResponse. Pre-existing note: r is returned from inside async with session.get(url) as r:, meaning the response is closed before the caller can read the body. This is a pre-existing design issue, out of scope here.
  • db_patch / db_post / db_put / db_delete → dict: Correct for this API's contract — these either return parsed JSON (a dict for all known endpoints) or raise ValueError. Slightly imprecise in theory (r.json() can return list/str), but accurate in practice.
  • Optional correctly added to the from typing import line.

Security

  • Note: The diff context (unchanged lines in the PR branch) shows the hardcoded bearer token Tp3aO3jhYve5NJF1IqOmJTmk — this branch predates PR #44's credential cleanup. On merge, Git's 3-way merge should correctly preserve main's env-var approach since the PR branch didn't modify that line relative to its base. Worth a conscious check that the merge result uses PD_API_TOKEN from env, not the hardcoded value.

Style & Conventions

  • F-string inner-quote reformatting (double → single) is cosmetic and consistent with the linter; no concerns.
  • timeout parameter is still accepted but not forwarded to aiohttp — correctly noted as pre-existing and out of scope.

Suggestions

  • Pre-existing: object_id: int = None, payload: dict = None etc. should be Optional[int], Optional[dict] — out of scope here but good candidates for a follow-up cleanup.

Verdict: COMMENT

LGTM. Return type annotations are accurate and complete for all six async functions. Verify merge cleanly inherits the PD_API_TOKEN env-var approach from main rather than the stale hardcoded token in this branch's context.


Automated review by Claude PR Reviewer

## AI Code Review ### Files Reviewed - `db_calls.py` (modified) ### Findings #### Correctness - `db_get → Optional[dict]`: Correct. The function returns `None` on non-200 when `none_okay=True` (line 80), and a parsed JSON dict on success. `Optional[dict]` accurately captures both paths. - `url_get → aiohttp.ClientResponse`: Technically correct — `r` is a `ClientResponse`. Pre-existing note: `r` is returned from inside `async with session.get(url) as r:`, meaning the response is closed before the caller can read the body. This is a pre-existing design issue, out of scope here. - `db_patch / db_post / db_put / db_delete → dict`: Correct for this API's contract — these either return parsed JSON (a dict for all known endpoints) or raise `ValueError`. Slightly imprecise in theory (`r.json()` can return list/str), but accurate in practice. - `Optional` correctly added to the `from typing import` line. #### Security - **Note**: The diff context (unchanged lines in the PR branch) shows the hardcoded bearer token `Tp3aO3jhYve5NJF1IqOmJTmk` — this branch predates PR #44's credential cleanup. On merge, Git's 3-way merge should correctly preserve main's env-var approach since the PR branch didn't modify that line relative to its base. Worth a conscious check that the merge result uses `PD_API_TOKEN` from env, not the hardcoded value. #### Style & Conventions - F-string inner-quote reformatting (double → single) is cosmetic and consistent with the linter; no concerns. - `timeout` parameter is still accepted but not forwarded to aiohttp — correctly noted as pre-existing and out of scope. #### Suggestions - Pre-existing: `object_id: int = None`, `payload: dict = None` etc. should be `Optional[int]`, `Optional[dict]` — out of scope here but good candidates for a follow-up cleanup. ### Verdict: COMMENT LGTM. Return type annotations are accurate and complete for all six async functions. Verify merge cleanly inherits the `PD_API_TOKEN` env-var approach from main rather than the stale hardcoded token in this branch's context. --- *Automated review by Claude PR Reviewer*
Claude added
ai-reviewed
and removed
ai-reviewing
labels 2026-03-23 12:47:49 +00:00
cal added 1 commit 2026-03-23 13:14:32 +00:00
url_get returns await resp.json() which is a dict, not aiohttp.ClientResponse.
The wrong annotation was introduced in the original PR and would mislead
static analysis tools into expecting a response object from callers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
cal requested changes 2026-03-23 13:23:04 +00:00
Dismissed
cal left a comment
Owner

AI Code Review

Files Reviewed

  • db_calls.py (modified)

Findings

Correctness

url_get return type is still wrong (line 78).
The fix commit (82a8dac) changed the annotation from aiohttp.ClientResponse to dict, but the function body returns r directly — which is an aiohttp.ClientResponse object, not a dict. The annotation is still incorrect. The pre-fix annotation (aiohttp.ClientResponse) was actually more accurate than the current dict. The correct annotation depends on intent: if the function is supposed to deserialize the body, it should await r.json() and return the result (a dict); if it is supposed to return the raw response object, the annotation should be aiohttp.ClientResponse. As written, the type annotation actively misleads callers and will cause mypy/pyright to miss real type errors downstream.

db_get can return aiohttp.ClientSession context data, not just Optional[dict]. The -> Optional[dict] annotation for db_get is correct and accurately reflects its behavior. No issue here.

timeout parameter is accepted but never forwarded (all six async functions). The PR body acknowledges this as a known pre-existing issue and explicitly scopes it out. Noted for completeness, not a blocker from this PR.

Security

CRITICAL: Hardcoded bearer token on line 7.

AUTH_TOKEN = {"Authorization": "Bearer Tp3aO3jhYve5NJF1IqOmJTmk"}

This is a hardcoded API credential committed directly into source control. This PR introduced or retained this literal — it must be replaced with an environment variable lookup before merge. The correct pattern for this codebase is:

import os
AUTH_TOKEN = {"Authorization": f"Bearer {os.environ['PD_API_TOKEN']}"}

If this token was previously loaded from an environment variable and was regressed to a literal during this PR's changes, that regression must be reverted. Committing credentials to a repository (even a private one) is a serious security issue: the token is now in git history, visible to anyone with repo access, and cannot be safely rotated without also scrubbing history.

Action required: Rotate the Tp3aO3jhYve5NJF1IqOmJTmk token immediately, regardless of whether this PR is merged or closed. It is now in git history.

Style & Conventions

The f-string quote normalization (double → single for nested expressions) is cosmetic and consistent. No issues.

Optional import addition is correct and follows the existing from typing import pattern.

All six function signatures follow a consistent annotation style. No issues with formatting or structure.

Suggestions

  • For url_get, decide whether it should return a raw response or a deserialized dict, then make the implementation and annotation agree. Given that all other DB functions return dict after calling await r.json(), the most consistent fix is to deserialize in url_get as well and annotate -> dict.
  • The pre-existing int = None / dict = None parameter annotations (should be Optional[int], etc.) are noted in the PR body as out of scope. A follow-up issue to fix these would prevent future confusion, since this PR's goal is enabling accurate static analysis.

Verdict: REQUEST_CHANGES

Two issues must be resolved before merge:

  1. Security (blocker): The hardcoded bearer token on line 7 must be replaced with an environment variable. The token itself should be rotated immediately — it is now in git history.
  2. Correctness (blocker): The url_get return type annotation (-> dict) does not match the actual return value (aiohttp.ClientResponse). The annotation must match the implementation, otherwise this PR's stated goal — enabling accurate static analysis — is undermined at this function specifically.

Automated review by Claude PR Reviewer

## AI Code Review ### Files Reviewed - `db_calls.py` (modified) --- ### Findings #### Correctness **`url_get` return type is still wrong (line 78).** The fix commit (82a8dac) changed the annotation from `aiohttp.ClientResponse` to `dict`, but the function body returns `r` directly — which is an `aiohttp.ClientResponse` object, not a dict. The annotation is still incorrect. The pre-fix annotation (`aiohttp.ClientResponse`) was actually more accurate than the current `dict`. The correct annotation depends on intent: if the function is supposed to deserialize the body, it should `await r.json()` and return the result (a `dict`); if it is supposed to return the raw response object, the annotation should be `aiohttp.ClientResponse`. As written, the type annotation actively misleads callers and will cause mypy/pyright to miss real type errors downstream. **`db_get` can return `aiohttp.ClientSession` context data, not just `Optional[dict]`.** The `-> Optional[dict]` annotation for `db_get` is correct and accurately reflects its behavior. No issue here. **`timeout` parameter is accepted but never forwarded (all six async functions).** The PR body acknowledges this as a known pre-existing issue and explicitly scopes it out. Noted for completeness, not a blocker from this PR. #### Security **CRITICAL: Hardcoded bearer token on line 7.** ```python AUTH_TOKEN = {"Authorization": "Bearer Tp3aO3jhYve5NJF1IqOmJTmk"} ``` This is a hardcoded API credential committed directly into source control. This PR introduced or retained this literal — it must be replaced with an environment variable lookup before merge. The correct pattern for this codebase is: ```python import os AUTH_TOKEN = {"Authorization": f"Bearer {os.environ['PD_API_TOKEN']}"} ``` If this token was previously loaded from an environment variable and was regressed to a literal during this PR's changes, that regression must be reverted. Committing credentials to a repository (even a private one) is a serious security issue: the token is now in git history, visible to anyone with repo access, and cannot be safely rotated without also scrubbing history. **Action required:** Rotate the `Tp3aO3jhYve5NJF1IqOmJTmk` token immediately, regardless of whether this PR is merged or closed. It is now in git history. #### Style & Conventions The f-string quote normalization (double → single for nested expressions) is cosmetic and consistent. No issues. `Optional` import addition is correct and follows the existing `from typing import` pattern. All six function signatures follow a consistent annotation style. No issues with formatting or structure. #### Suggestions - For `url_get`, decide whether it should return a raw response or a deserialized dict, then make the implementation and annotation agree. Given that all other DB functions return `dict` after calling `await r.json()`, the most consistent fix is to deserialize in `url_get` as well and annotate `-> dict`. - The pre-existing `int = None` / `dict = None` parameter annotations (should be `Optional[int]`, etc.) are noted in the PR body as out of scope. A follow-up issue to fix these would prevent future confusion, since this PR's goal is enabling accurate static analysis. --- ### Verdict: REQUEST_CHANGES Two issues must be resolved before merge: 1. **Security (blocker):** The hardcoded bearer token on line 7 must be replaced with an environment variable. The token itself should be rotated immediately — it is now in git history. 2. **Correctness (blocker):** The `url_get` return type annotation (`-> dict`) does not match the actual return value (`aiohttp.ClientResponse`). The annotation must match the implementation, otherwise this PR's stated goal — enabling accurate static analysis — is undermined at this function specifically. --- *Automated review by Claude PR Reviewer*
db_calls.py Outdated
@ -5,3 +4,4 @@
from typing import Literal, Optional
from exceptions import logger
AUTH_TOKEN = {"Authorization": "Bearer Tp3aO3jhYve5NJF1IqOmJTmk"}
Owner

SECURITY: Hardcoded bearer token. This credential is now in git history and must be rotated immediately. Replace with an environment variable lookup:

import os
AUTH_TOKEN = {"Authorization": f"Bearer {os.environ['PD_API_TOKEN']}"}
SECURITY: Hardcoded bearer token. This credential is now in git history and must be rotated immediately. Replace with an environment variable lookup: ```python import os AUTH_TOKEN = {"Authorization": f"Bearer {os.environ['PD_API_TOKEN']}"} ```
db_calls.py Outdated
@ -76,7 +76,7 @@ async def db_get(
raise ValueError(f"DB: {e}")
Owner

TYPE ANNOTATION MISMATCH: The annotation -> dict does not match what is actually returned. When r.status == 200, the function returns r, which is an aiohttp.ClientResponse, not a dict. Either:

  • Deserialize the body: return await r.json() and keep -> dict
  • Or annotate correctly: -> aiohttp.ClientResponse

The pre-fix annotation (aiohttp.ClientResponse) was more accurate than the current dict.

TYPE ANNOTATION MISMATCH: The annotation `-> dict` does not match what is actually returned. When `r.status == 200`, the function returns `r`, which is an `aiohttp.ClientResponse`, not a `dict`. Either: - Deserialize the body: `return await r.json()` and keep `-> dict` - Or annotate correctly: `-> aiohttp.ClientResponse` The pre-fix annotation (`aiohttp.ClientResponse`) was more accurate than the current `dict`.
cal approved these changes 2026-03-23 19:51:43 +00:00
cal left a comment
Owner

Looks good — return type annotations are accurate and the Optional import addition is correct. Approving for merge.

Looks good — return type annotations are accurate and the Optional import addition is correct. Approving for merge.
cal added 1 commit 2026-03-23 19:51:52 +00:00
cal merged commit 8c00bacf59 into main 2026-03-23 19:52:03 +00:00
cal deleted branch ai/paper-dynasty-card-creation-20 2026-03-23 19:52:03 +00:00
Sign in to join this conversation.
No reviewers
cal
No Milestone
No project
No Assignees
2 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: cal/paper-dynasty-card-creation#47
No description provided.