Skip to content

Commit d19b67f

Browse files
Sachin-BhatsansyroxSachin-Bhatclaudepre-commit-ci[bot]
authored
refactor: modernize typing and defaults across core API (#1299)
* refactor: modernize typing to PEP 604 style across core API - Replace Union[X, Y] with X | Y - Replace Optional[X] with X | None - Replace List[X], Dict[K, V], Tuple[X] with lowercase builtins - Import Callable from collections.abc instead of typing - Fix mutable default arguments (Config(), DependencyMap(), list defaults) - Improve OpenAPI union schema generation using get_origin/get_args - Make TemplateInterface.__init__ accept *args/**kwargs - Clean up unused typing imports Co-authored-by: Sachin-Bhat <sachin.bhat@users.noreply.github.com> Made-with: Cursor * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix: lint and Generator typing failures - robyn/responses.py: Generator[str | None | None] is malformed (typing.Generator needs 3 args yield/send/return); restore Generator[str, None, None] | AsyncGenerator[str, None]. - robyn/processpool.py: process_pool: List = [] → list = [] (List was unimported). - integration_tests/base_routes.py: TestTypedBody.items: List[str] → list[str]. - Drop unused typing imports flagged by ruff F401: Optional in robyn/ai.py, Dict/List in robyn/mcp.py, Protocol/TypeAlias in robyn/openapi.py, the TYPE_CHECKING-gated SubRouter import in robyn/router.py. - ruff-format pass: insert PEP 8 blank lines between top-level defs across ai/authentication/cli/logger/mcp/processpool/reloader/responses/router/ types/__init__.py. Fixes ubuntu/macos/windows tests, codspeed-benchmarks, lint, and pre-commit.ci. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: restore mutable defaults for Config/DependencyMap and handle PEP 604 unions in param coercion Two test regressions exposed by the typing modernization: 1. test_subrouter_global_dependency_injection expected app's later inject_global(GLOBAL_DEPENDENCY="GLOBAL DEPENDENCY") to override the subrouter's earlier inject_global(...="GLOBAL DEPENDENCY OVERRIDE"). That worked on main only because BaseRobyn(... config=Config(), dependencies=DependencyMap()) used mutable defaults — every Robyn / SubRouter constructed without explicit args shared the same instances, so app.inject_global mutated the same dict the subrouter held. The PR moved both to None + lazy-init in __init__, breaking the sharing. Restored the mutable defaults to match main's semantics. SubRouter signature also restored to match main (Config()/OpenAPI() defaults). 2. test_easy_access_optional_present[sync|async] returned 500 because robyn/_param_utils.py:unwrap_optional only recognized typing.Union via .__origin__. PEP 604 `int | None` is types.UnionType and has no __origin__ attribute, so unwrap_optional returned the raw union and coerce_value tried (int | None)("30"), raising. Replaced getattr(annotation, "__origin__", None) with typing.get_origin and accept both Union and types.UnionType. Same fix applied to is_list_type for symmetry. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Sanskar Jethi <sansyrox@gmail.com> Co-authored-by: Sachin-Bhat <sachin.bhat@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 2630311 commit d19b67f

16 files changed

Lines changed: 476 additions & 319 deletions

integration_tests/base_routes.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import pathlib
66
import time
77
from collections import defaultdict
8-
from typing import List, Optional, TypedDict
8+
from typing import TypedDict
99

1010
from integration_tests.subroutes import di_subrouter, static_router, sub_router
1111
from robyn import Headers, Request, Response, Robyn, SSEMessage, SSEResponse, WebSocketDisconnect, jsonify, serve_file, serve_html
@@ -1263,7 +1263,7 @@ def sample_openapi_endpoint():
12631263

12641264
class Initial(Body):
12651265
is_present: bool
1266-
letter: Optional[str]
1266+
letter: str | None
12671267

12681268

12691269
class FullName(Body):
@@ -1273,7 +1273,7 @@ class FullName(Body):
12731273

12741274

12751275
class TestTypedBody(Body):
1276-
items: List[str]
1276+
items: list[str]
12771277
numbers: list[int]
12781278

12791279

@@ -1501,22 +1501,22 @@ async def easy_access_async(id: int, q: str, page: int = 1):
15011501

15021502

15031503
@app.get("/easy/sync/optional")
1504-
def easy_access_optional_sync(name: str, age: Optional[int] = None):
1504+
def easy_access_optional_sync(name: str, age: int | None = None):
15051505
return {"name": name, "age": age}
15061506

15071507

15081508
@app.get("/easy/async/optional")
1509-
async def easy_access_optional_async(name: str, age: Optional[int] = None):
1509+
async def easy_access_optional_async(name: str, age: int | None = None):
15101510
return {"name": name, "age": age}
15111511

15121512

15131513
@app.get("/easy/sync/list")
1514-
def easy_access_list_sync(tag: List[str]):
1514+
def easy_access_list_sync(tag: list[str]):
15151515
return {"tags": tag}
15161516

15171517

15181518
@app.get("/easy/async/list")
1519-
async def easy_access_list_async(tag: List[str]):
1519+
async def easy_access_list_async(tag: list[str]):
15201520
return {"tags": tag}
15211521

15221522

@@ -1715,7 +1715,7 @@ def main():
17151715
app.include_router(static_router)
17161716

17171717
class BasicAuthHandler(AuthenticationHandler):
1718-
def authenticate(self, request: Request) -> Optional[Identity]:
1718+
def authenticate(self, request: Request) -> Identity | None:
17191719
token = self.token_getter.get_token(request)
17201720
if token is not None:
17211721
# Useless but we call the set_token method for testing purposes

0 commit comments

Comments
 (0)