refactor: modernize typing and defaults across core API#1299
refactor: modernize typing and defaults across core API#1299Sachin-Bhat wants to merge 3 commits intosparckles:mainfrom
Conversation
Signed-off-by: Sachin Bhat <sachubhat17@gmail.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughModernizes type annotations to Python 3.10+ (PEP 604) across the codebase, replaces legacy typing generics with built-ins, applies lazy default initialization for mutable defaults, adds route deduplication, changes dependency merge behavior, and adjusts a few runtime checks (subprocess capture, async generator detection). Mostly signature and typing updates. Changes
Sequence Diagram(s)(omitted) Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@robyn/openapi.py`:
- Around line 108-116: The typed fields (schemas, responses, parameters,
examples, requestBodies, securitySchemes, links, callbacks, pathItems) are
annotated as dict[str, dict] | None but use default_factory=dict, creating empty
dicts rather than None; update the annotations to be non-optional (remove "|
None") so they read dict[str, dict] and keep default_factory=dict for each
field, or alternatively change the defaults to default=None if you really want
them nullable—adjust any code that expects None accordingly.
In `@robyn/templating.py`:
- Around line 11-12: The abstract `@abstractmethod` on __init__ makes all
subclasses abstract and uninstantiable; remove the `@abstractmethod` decorator
from __init__ in templating.py and provide a concrete, flexible initializer (def
__init__(self, *args, **kwargs): pass or call super().__init__(*args, **kwargs))
so subclasses can inherit a default constructor while still allowing overrides;
update the __init__ definition (symbol __init__) to be non-abstract and keep the
*args/**kwargs signature for compatibility.
🧹 Nitpick comments (3)
robyn/reloader.py (1)
119-119: Add type parameter tolistfor consistency.This uses bare
listwithout a type parameter, while the rest of the file useslist[str](e.g.,compile_rust_filesreturnslist[str],clean_rust_binariestakeslist[str]). For consistency and better type safety, this should belist[str].♻️ Proposed fix
- self.built_rust_binaries: list = [] # Keep track of the built rust binaries + self.built_rust_binaries: list[str] = [] # Keep track of the built rust binariesrobyn/logger.py (1)
68-76: Consider aligningdebugmethod signature with the other logging methods.The
debugmethod usescolor: Colors = Colors.BLUEwithout the| Noneoption, whileerror,warn, andinfoall useColors | None. If this is intentional (debug always requires a color), that's fine. Otherwise, consider updating for consistency.robyn/__init__.py (1)
197-207: Consider initializing_added_routesin__init__.The
hasattr/setattrpattern for_added_routesworks but is unconventional. Initializing in__init__would be cleaner and more explicit.♻️ Proposed refactor
In
BaseRobyn.__init__:self.authentication_handler: AuthenticationHandler | None = None self.included_routers: list[SubRouter] = [] self._mcp_app: MCPApp | None = None + self._added_routes: set[str] = set()Then in
add_route:- # Check if this exact route (method + normalized_endpoint) already exists - route_key = f"{route_type}:{normalized_endpoint}" - if not hasattr(self, "_added_routes"): - self._added_routes = set() - - if route_key in self._added_routes: + # Check if this exact route (method + normalized_endpoint) already exists + route_key = f"{route_type}:{normalized_endpoint}" + if route_key in self._added_routes:
Signed-off-by: Sachin Bhat <sachubhat17@gmail.com>
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
robyn/__init__.py (1)
33-67:⚠️ Potential issue | 🟡 MinorTreat whitespace-only endpoints as blank.
Right now" "normalizes to"/", which can accidentally register root middleware instead of global. Strip early and honortreat_empty_as_rootfor blank/whitespace values.Based on learnings: In robyn/__init__.py, for `before_request()` and `after_request()` middleware decorators: blank string `""` or no argument (None) registers global middleware; only explicit `"/"` registers middleware for the root endpoint. This is the intended behavior of the framework.🛠️ Suggested fix
- if endpoint is None or (not endpoint and not treat_empty_as_root): - return None - - # Remove trailing slashes - endpoint = endpoint.strip().rstrip("/") - - # Handle empty result - if not endpoint: - return "/" + if endpoint is None: + return None + + endpoint = endpoint.strip() + if not endpoint: + return "/" if treat_empty_as_root else None + + endpoint = endpoint.rstrip("/")
🤖 Fix all issues with AI agents
In `@robyn/openapi.py`:
- Around line 408-422: The anyOf generation for Union/Optional currently uses
get_openapi_type (which only handles primitives), causing complex members (e.g.,
list[str] or custom models) to be rendered as {"type":"string"}; update the
Union handling in the block that inspects origin in (typing.Union,
types.UnionType) so each arg is converted via self.get_schema_object (or calls
self.get_schema_object(f"{parameter}_union_member", arg) for non-None args)
instead of using get_openapi_type, while keeping {"type":"null"} for NoneType;
ensure the resulting list is assigned to properties["anyOf"] and then returned.
Signed-off-by: Sachin Bhat <sachubhat17@gmail.com>
integration_tests/base_routes.py
Outdated
| # ==== Exception Handling ==== | ||
|
|
||
|
|
||
| @app.exception | ||
| def handle_exception(error): | ||
| return Response(status_code=500, description=f"error msg: {error}", headers={}) | ||
|
|
||
|
|
|
|
||
| from integration_tests.subroutes import di_subrouter, static_router, sub_router | ||
| from robyn import Headers, Request, Response, Robyn, SSEMessage, SSEResponse, WebSocket, WebSocketConnector, jsonify, serve_file, serve_html | ||
| from robyn import ( |
| route_key = route_type.upper() | ||
| try: | ||
| route_type = getattr(HttpMethod, route_key) | ||
| except AttributeError as exc: | ||
| raise ValueError(f"Unsupported HTTP method: {route_type}") from exc | ||
| route_type = cast(HttpMethod, route_type) |
There was a problem hiding this comment.
I prefer the original way. Could you revert to that?
| return s.connect_ex(("localhost", port)) == 0 | ||
| except Exception: | ||
| raise Exception(f"Invalid port number: {port}") | ||
| raise Exception(f"Invalid port number: {port}") from None |
robyn/__init__.py
Outdated
| const: bool = False, | ||
| auth_required: bool = False, | ||
| openapi_name: str = "", | ||
| openapi_tags: List[str] = ["get"], |
robyn/__init__.py
Outdated
| endpoint: str, | ||
| auth_required: bool = False, | ||
| openapi_name: str = "", | ||
| openapi_tags: List[str] = ["post"], |
robyn/__init__.py
Outdated
| endpoint: str, | ||
| auth_required: bool = False, | ||
| openapi_name: str = "", | ||
| openapi_tags: List[str] = ["put"], |
robyn/__init__.py
Outdated
| endpoint: str, | ||
| auth_required: bool = False, | ||
| openapi_name: str = "", | ||
| openapi_tags: List[str] = ["delete"], |
robyn/__init__.py
Outdated
| endpoint: str, | ||
| auth_required: bool = False, | ||
| openapi_name: str = "", | ||
| openapi_tags: List[str] = ["patch"], |
robyn/__init__.py
Outdated
| endpoint: str, | ||
| auth_required: bool = False, | ||
| openapi_name: str = "", | ||
| openapi_tags: List[str] = ["head"], |
robyn/__init__.py
Outdated
| endpoint: str, | ||
| auth_required: bool = False, | ||
| openapi_name: str = "", | ||
| openapi_tags: List[str] = ["connect"], |
robyn/dependency_injection.py
Outdated
| This method iterates through the dependencies of this DependencyMap and applies them to the | ||
| target router's DependencyMap, overriding any existing keys. | ||
| """ | ||
| for dep_key in self.get_global_dependencies(): |
|
|
||
| if config_path.exists(): | ||
| with open(config_path, "r") as f: | ||
| with open(config_path) as f: |
| except TypeError as e: | ||
| # Handle parameter mismatch errors | ||
| raise MCPError(-32603, f"Handler parameter error: {str(e)}") | ||
| raise MCPError(-32603, f"Handler parameter error: {str(e)}") from e |
|
Hey @Sachin-Bhat 👋 Thank you for the PR, and apologies for the gazillion pings. I've left a few comments. |
5fb08e0 to
0472e42
Compare
Description
This PR fixes #1298
Summary
This PR does the following:
collections.abc.Callable,StrEnum, andTypeAliasusage).ConfigandDependencyMaplazily in constructors.allow_connection_pickling).PR Checklist
Please ensure that:
Pre-Commit Instructions:
Summary by CodeRabbit
Bug Fixes
Refactor
Tests
Style