jec-api 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2 @@
1
+ # Auto detect text files and perform LF normalization
2
+ * text=auto
@@ -0,0 +1,29 @@
1
+ # Byte-compiled
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # Distribution
7
+ dist/
8
+ build/
9
+ *.egg-info/
10
+ *.egg
11
+
12
+ # Virtual environments
13
+ venv/
14
+ .venv/
15
+ env/
16
+
17
+ # IDE
18
+ .idea/
19
+ .vscode/
20
+ *.swp
21
+
22
+ # Testing
23
+ .pytest_cache/
24
+ .coverage
25
+ htmlcov/
26
+
27
+ # OS
28
+ .DS_Store
29
+ Thumbs.db
jec_api-0.0.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nik
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
jec_api-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,134 @@
1
+ Metadata-Version: 2.4
2
+ Name: jec-api
3
+ Version: 0.0.1
4
+ Summary: Beta version of JEC API
5
+ Project-URL: Homepage, https://github.com/alpheay/jec
6
+ Project-URL: Repository, https://github.com/alpheay/jec
7
+ Author: Nik
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: api,class-based,fastapi,routes,web
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Framework :: FastAPI
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
21
+ Requires-Python: >=3.9
22
+ Requires-Dist: fastapi>=0.100.0
23
+ Provides-Extra: dev
24
+ Requires-Dist: httpx>=0.24.0; extra == 'dev'
25
+ Requires-Dist: pytest>=7.0.0; extra == 'dev'
26
+ Requires-Dist: uvicorn>=0.20.0; extra == 'dev'
27
+ Description-Content-Type: text/markdown
28
+
29
+ # JEC-API
30
+
31
+ ## Features
32
+
33
+ - **Class-Based Routes**: Group related endpoints (e.g., CRUD operations) into a single class.
34
+ - **Auto-Discovery**: Automatically find and register route classes from your project packages.
35
+ - **Smart Method Naming**: API paths and HTTP methods are inferred from your method names (e.g., `get_by_id` becomes `GET /{id}`).
36
+ - **FastAPI Native**: Fully compatible with FastAPI dependencies, models, and OpenAPI generation.
37
+
38
+ ## Installation
39
+
40
+ ```bash
41
+ pip install jec-api
42
+ ```
43
+
44
+ ## Quick Start
45
+
46
+ 1. **Define a Route Class**
47
+
48
+ ```python
49
+ # routes.py
50
+ from jec_api import Route
51
+
52
+ class Users(Route):
53
+ # Optional: explicitly set path, otherwise defaults to /users
54
+ # path = "/my-users"
55
+
56
+ async def get(self):
57
+ """List all users"""
58
+ return [{"id": 1, "name": "Alice"}]
59
+
60
+ async def get_by_id(self, id: int):
61
+ """Get user by ID"""
62
+ return {"id": id, "name": "Alice"}
63
+
64
+ async def post(self, name: str):
65
+ """Create a user"""
66
+ return {"id": 2, "name": name}
67
+ ```
68
+
69
+ 2. **Create the App**
70
+
71
+ ```python
72
+ # main.py
73
+ from jec_api import Core
74
+
75
+ core = Core(title="My API")
76
+
77
+ # Auto-discover routes from a module/package
78
+ core.discover("routes")
79
+
80
+ # Or register manually
81
+ from routes import Users
82
+ core.register(Users)
83
+ ```
84
+
85
+ 3. **Run it**
86
+
87
+ ```bash
88
+ uvicorn main:core --reload
89
+ ```
90
+
91
+ ## Usage Guide
92
+
93
+ ### Defining Routes
94
+
95
+ Inherit from `jec_api.Route` to create a route group. The class name is automatically converted to kebab-case to form the base path (e.g., `UserProfiles` -> `/user-profiles`), unless you override it with the `path` attribute.
96
+
97
+ ### Method Naming Convention
98
+
99
+ JEC-API parses your method names to determine the HTTP verb and path parameters:
100
+
101
+ | Method Name | HTTP Verb | Generated Path |
102
+ |-------------|-----------|----------------|
103
+ | `get()` | GET | `/` |
104
+ | `post()` | POST | `/` |
105
+ | `get_by_id(id)` | GET | `/{id}` |
106
+ | `delete_by_id(id)` | DELETE | `/{id}` |
107
+ | `get_users()` | GET | `/users` |
108
+ | `post_batch_update()` | POST | `/batch-update` |
109
+
110
+ ### Path Parameters
111
+
112
+ To define path parameters, use the `_by_{param}` pattern in your method name.
113
+ For example, `get_by_user_id` will generate a path `/{user_id}`.
114
+
115
+ ### Manual Registration
116
+
117
+ You can register routes manually if you prefer not to use auto-discovery:
118
+
119
+ ```python
120
+ from my_routes import MyRoute
121
+ app.register(MyRoute, tags=["Custom Tag"])
122
+ ```
123
+
124
+ ### Auto-Discovery
125
+
126
+ The `discover()` method recursively searches the specified package for any classes inheriting from `Route` and registers them.
127
+
128
+ ```python
129
+ app.discover("src.routes")
130
+ ```
131
+
132
+ ## License
133
+
134
+ MIT License
@@ -0,0 +1,106 @@
1
+ # JEC-API
2
+
3
+ ## Features
4
+
5
+ - **Class-Based Routes**: Group related endpoints (e.g., CRUD operations) into a single class.
6
+ - **Auto-Discovery**: Automatically find and register route classes from your project packages.
7
+ - **Smart Method Naming**: API paths and HTTP methods are inferred from your method names (e.g., `get_by_id` becomes `GET /{id}`).
8
+ - **FastAPI Native**: Fully compatible with FastAPI dependencies, models, and OpenAPI generation.
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ pip install jec-api
14
+ ```
15
+
16
+ ## Quick Start
17
+
18
+ 1. **Define a Route Class**
19
+
20
+ ```python
21
+ # routes.py
22
+ from jec_api import Route
23
+
24
+ class Users(Route):
25
+ # Optional: explicitly set path, otherwise defaults to /users
26
+ # path = "/my-users"
27
+
28
+ async def get(self):
29
+ """List all users"""
30
+ return [{"id": 1, "name": "Alice"}]
31
+
32
+ async def get_by_id(self, id: int):
33
+ """Get user by ID"""
34
+ return {"id": id, "name": "Alice"}
35
+
36
+ async def post(self, name: str):
37
+ """Create a user"""
38
+ return {"id": 2, "name": name}
39
+ ```
40
+
41
+ 2. **Create the App**
42
+
43
+ ```python
44
+ # main.py
45
+ from jec_api import Core
46
+
47
+ core = Core(title="My API")
48
+
49
+ # Auto-discover routes from a module/package
50
+ core.discover("routes")
51
+
52
+ # Or register manually
53
+ from routes import Users
54
+ core.register(Users)
55
+ ```
56
+
57
+ 3. **Run it**
58
+
59
+ ```bash
60
+ uvicorn main:core --reload
61
+ ```
62
+
63
+ ## Usage Guide
64
+
65
+ ### Defining Routes
66
+
67
+ Inherit from `jec_api.Route` to create a route group. The class name is automatically converted to kebab-case to form the base path (e.g., `UserProfiles` -> `/user-profiles`), unless you override it with the `path` attribute.
68
+
69
+ ### Method Naming Convention
70
+
71
+ JEC-API parses your method names to determine the HTTP verb and path parameters:
72
+
73
+ | Method Name | HTTP Verb | Generated Path |
74
+ |-------------|-----------|----------------|
75
+ | `get()` | GET | `/` |
76
+ | `post()` | POST | `/` |
77
+ | `get_by_id(id)` | GET | `/{id}` |
78
+ | `delete_by_id(id)` | DELETE | `/{id}` |
79
+ | `get_users()` | GET | `/users` |
80
+ | `post_batch_update()` | POST | `/batch-update` |
81
+
82
+ ### Path Parameters
83
+
84
+ To define path parameters, use the `_by_{param}` pattern in your method name.
85
+ For example, `get_by_user_id` will generate a path `/{user_id}`.
86
+
87
+ ### Manual Registration
88
+
89
+ You can register routes manually if you prefer not to use auto-discovery:
90
+
91
+ ```python
92
+ from my_routes import MyRoute
93
+ app.register(MyRoute, tags=["Custom Tag"])
94
+ ```
95
+
96
+ ### Auto-Discovery
97
+
98
+ The `discover()` method recursively searches the specified package for any classes inheriting from `Route` and registers them.
99
+
100
+ ```python
101
+ app.discover("src.routes")
102
+ ```
103
+
104
+ ## License
105
+
106
+ MIT License
@@ -0,0 +1,44 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "jec-api"
7
+ version = "0.0.1"
8
+ description = "Beta version of JEC API"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.9"
12
+ authors = [
13
+ { name = "Nik" }
14
+ ]
15
+ keywords = ["fastapi", "api", "routes", "class-based", "web"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Framework :: FastAPI",
19
+ "Intended Audience :: Developers",
20
+ "License :: OSI Approved :: MIT License",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.9",
23
+ "Programming Language :: Python :: 3.10",
24
+ "Programming Language :: Python :: 3.11",
25
+ "Programming Language :: Python :: 3.12",
26
+ "Topic :: Internet :: WWW/HTTP :: HTTP Servers",
27
+ ]
28
+ dependencies = [
29
+ "fastapi>=0.100.0",
30
+ ]
31
+
32
+ [project.optional-dependencies]
33
+ dev = [
34
+ "pytest>=7.0.0",
35
+ "httpx>=0.24.0",
36
+ "uvicorn>=0.20.0",
37
+ ]
38
+
39
+ [project.urls]
40
+ Homepage = "https://github.com/alpheay/jec"
41
+ Repository = "https://github.com/alpheay/jec"
42
+
43
+ [tool.hatch.build.targets.wheel]
44
+ packages = ["src/jec_api"]
@@ -0,0 +1,7 @@
1
+ """JEC-API: Define FastAPI routes as classes."""
2
+
3
+ from .route import Route
4
+ from .router import Core
5
+
6
+ __all__ = ["Route", "Core"]
7
+ __version__ = "0.1.0"
@@ -0,0 +1,145 @@
1
+ """Auto-discovery of Route classes from packages and directories."""
2
+
3
+ import importlib
4
+ import importlib.util
5
+ import pkgutil
6
+ import sys
7
+ from pathlib import Path
8
+ from typing import List, Type
9
+
10
+ from .route import Route
11
+
12
+
13
+ def discover_routes(package: str, *, recursive: bool = True) -> List[Type[Route]]:
14
+ """
15
+ Discover all Route subclasses in a package or directory.
16
+
17
+ Args:
18
+ package: Package name (e.g., "routes") or path to directory
19
+ recursive: Whether to search subdirectories
20
+
21
+ Returns:
22
+ List of Route subclasses found
23
+ """
24
+ route_classes: List[Type[Route]] = []
25
+
26
+ # Check if it's a path or a package name
27
+ package_path = Path(package)
28
+
29
+ if package_path.is_dir():
30
+ # It's a directory path
31
+ route_classes.extend(_discover_from_directory(package_path, recursive))
32
+ else:
33
+ # Try as a package name
34
+ try:
35
+ route_classes.extend(_discover_from_package(package, recursive))
36
+ except ModuleNotFoundError:
37
+ # Maybe it's a relative path from cwd
38
+ cwd_path = Path.cwd() / package
39
+ if cwd_path.is_dir():
40
+ route_classes.extend(_discover_from_directory(cwd_path, recursive))
41
+ else:
42
+ raise ValueError(f"Could not find package or directory: {package}")
43
+
44
+ return route_classes
45
+
46
+
47
+ def _discover_from_package(package_name: str, recursive: bool) -> List[Type[Route]]:
48
+ """Discover routes from an installed package."""
49
+ route_classes: List[Type[Route]] = []
50
+
51
+ package = importlib.import_module(package_name)
52
+
53
+ if not hasattr(package, "__path__"):
54
+ # Single module, not a package
55
+ route_classes.extend(_extract_routes_from_module(package))
56
+ return route_classes
57
+
58
+ # Walk through the package
59
+ prefix = package_name + "."
60
+
61
+ for importer, modname, ispkg in pkgutil.walk_packages(
62
+ package.__path__,
63
+ prefix=prefix,
64
+ ):
65
+ if not recursive and ispkg:
66
+ continue
67
+
68
+ try:
69
+ module = importlib.import_module(modname)
70
+ route_classes.extend(_extract_routes_from_module(module))
71
+ except Exception:
72
+ # Skip modules that fail to import
73
+ continue
74
+
75
+ return route_classes
76
+
77
+
78
+ def _discover_from_directory(directory: Path, recursive: bool) -> List[Type[Route]]:
79
+ """Discover routes from a directory of Python files."""
80
+ route_classes: List[Type[Route]] = []
81
+
82
+ # Add directory to sys.path temporarily if needed
83
+ dir_str = str(directory.parent.resolve())
84
+ added_to_path = False
85
+
86
+ if dir_str not in sys.path:
87
+ sys.path.insert(0, dir_str)
88
+ added_to_path = True
89
+
90
+ try:
91
+ pattern = "**/*.py" if recursive else "*.py"
92
+
93
+ for py_file in directory.glob(pattern):
94
+ if py_file.name.startswith("_"):
95
+ continue
96
+
97
+ try:
98
+ module = _load_module_from_file(py_file)
99
+ if module:
100
+ route_classes.extend(_extract_routes_from_module(module))
101
+ except Exception:
102
+ # Skip files that fail to import
103
+ continue
104
+ finally:
105
+ if added_to_path:
106
+ sys.path.remove(dir_str)
107
+
108
+ return route_classes
109
+
110
+
111
+ def _load_module_from_file(file_path: Path):
112
+ """Load a Python module from a file path."""
113
+ module_name = file_path.stem
114
+
115
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
116
+ if spec is None or spec.loader is None:
117
+ return None
118
+
119
+ module = importlib.util.module_from_spec(spec)
120
+ sys.modules[module_name] = module
121
+ spec.loader.exec_module(module)
122
+
123
+ return module
124
+
125
+
126
+ def _extract_routes_from_module(module) -> List[Type[Route]]:
127
+ """Extract all Route subclasses from a module."""
128
+ route_classes: List[Type[Route]] = []
129
+
130
+ for name in dir(module):
131
+ if name.startswith("_"):
132
+ continue
133
+
134
+ obj = getattr(module, name)
135
+
136
+ # Check if it's a class that inherits from Route (but not Route itself)
137
+ if (
138
+ isinstance(obj, type)
139
+ and issubclass(obj, Route)
140
+ and obj is not Route
141
+ and obj.__module__ == module.__name__
142
+ ):
143
+ route_classes.append(obj)
144
+
145
+ return route_classes
@@ -0,0 +1,118 @@
1
+ """Route base class for defining API endpoints."""
2
+
3
+ import re
4
+ from typing import Any, Callable, Dict, List, Optional, Tuple, Type
5
+ import inspect
6
+
7
+
8
+ # HTTP methods that can be used as method prefixes
9
+ HTTP_METHODS = {"get", "post", "put", "delete", "patch", "options", "head"}
10
+
11
+
12
+ class RouteMeta(type):
13
+ """Metaclass that collects route information from class methods."""
14
+
15
+ def __new__(mcs, name: str, bases: Tuple[type, ...], namespace: Dict[str, Any]) -> type:
16
+ cls = super().__new__(mcs, name, bases, namespace)
17
+
18
+ # Skip processing for the base Route class itself
19
+ if name == "Route" and not bases:
20
+ return cls
21
+
22
+ # Collect endpoint methods
23
+ cls._endpoints: List[Tuple[str, str, Callable]] = []
24
+
25
+ for attr_name, attr_value in namespace.items():
26
+ if attr_name.startswith("_"):
27
+ continue
28
+ if not callable(attr_value):
29
+ continue
30
+
31
+ parsed = mcs._parse_method_name(attr_name)
32
+ if parsed:
33
+ http_method, sub_path = parsed
34
+ cls._endpoints.append((http_method, sub_path, attr_value))
35
+
36
+ return cls
37
+
38
+ @staticmethod
39
+ def _parse_method_name(name: str) -> Optional[Tuple[str, str]]:
40
+ """
41
+ Parse method name to extract HTTP method and sub-path.
42
+
43
+ Examples:
44
+ get -> (GET, /)
45
+ post -> (POST, /)
46
+ get_by_id -> (GET, /{id})
47
+ get_users -> (GET, /users)
48
+ post_batch -> (POST, /batch)
49
+ get_user_by_id -> (GET, /user/{id})
50
+ """
51
+ parts = name.lower().split("_")
52
+
53
+ if not parts or parts[0] not in HTTP_METHODS:
54
+ return None
55
+
56
+ http_method = parts[0].upper()
57
+
58
+ if len(parts) == 1:
59
+ # Just the HTTP method: get, post, etc.
60
+ return (http_method, "/")
61
+
62
+ # Check for "by_" pattern indicating path parameter
63
+ path_parts = []
64
+ i = 1
65
+ while i < len(parts):
66
+ if parts[i] == "by" and i + 1 < len(parts):
67
+ # Convert "by_id" to "{id}"
68
+ param_name = parts[i + 1]
69
+ path_parts.append(f"{{{param_name}}}")
70
+ i += 2
71
+ else:
72
+ # Convert to kebab-case path segment
73
+ path_parts.append(parts[i])
74
+ i += 1
75
+
76
+ if not path_parts:
77
+ return (http_method, "/")
78
+
79
+ sub_path = "/" + "/".join(path_parts)
80
+ return (http_method, sub_path)
81
+
82
+
83
+ class Route(metaclass=RouteMeta):
84
+ """
85
+ Base class for defining API route endpoints.
86
+
87
+ Inherit from this class and define methods with HTTP method prefixes:
88
+ - get(), post(), put(), delete(), patch(), options(), head()
89
+ - get_by_id(id: int) -> GET /{id}
90
+ - get_users() -> GET /users
91
+ - post_batch() -> POST /batch
92
+
93
+ Optionally set `path` class attribute to override the auto-generated path.
94
+ """
95
+
96
+ # Override this to set a custom path instead of deriving from class name
97
+ path: Optional[str] = None
98
+
99
+ # Set by metaclass
100
+ _endpoints: List[Tuple[str, str, Callable]] = []
101
+
102
+ @classmethod
103
+ def get_path(cls) -> str:
104
+ """Get the base path for this route class."""
105
+ if cls.path is not None:
106
+ return cls.path if cls.path.startswith("/") else f"/{cls.path}"
107
+
108
+ # Convert class name to kebab-case path
109
+ # UserProfiles -> user-profiles
110
+ name = cls.__name__
111
+ # Insert hyphens before uppercase letters and lowercase everything
112
+ kebab = re.sub(r"(?<!^)(?=[A-Z])", "-", name).lower()
113
+ return f"/{kebab}"
114
+
115
+ @classmethod
116
+ def get_endpoints(cls) -> List[Tuple[str, str, Callable]]:
117
+ """Get all endpoint definitions for this route class."""
118
+ return cls._endpoints
@@ -0,0 +1,105 @@
1
+ """Core - FastAPI wrapper with class-based route registration."""
2
+
3
+ from typing import Any, Callable, List, Type, Optional
4
+ from fastapi import FastAPI, APIRouter
5
+ from fastapi.routing import APIRoute
6
+
7
+ from .route import Route
8
+ from .discovery import discover_routes
9
+
10
+
11
+ class Core(FastAPI):
12
+ """
13
+ FastAPI application with class-based route registration.
14
+
15
+ Usage:
16
+ app = Core()
17
+ app.discover("routes") # Auto-discover from package
18
+ app.register(MyRoute) # Or register manually
19
+ """
20
+
21
+ def __init__(self, *args, **kwargs):
22
+ super().__init__(*args, **kwargs)
23
+ self._registered_routes: List[Type[Route]] = []
24
+
25
+ def register(self, route_class: Type[Route], **router_kwargs) -> "Core":
26
+ """
27
+ Register a Route subclass with the application.
28
+
29
+ Args:
30
+ route_class: A class that inherits from Route
31
+ **router_kwargs: Additional kwargs passed to APIRouter (tags, etc.)
32
+
33
+ Returns:
34
+ Self for method chaining
35
+ """
36
+ if not isinstance(route_class, type) or not issubclass(route_class, Route):
37
+ raise TypeError(f"{route_class} must be a subclass of Route")
38
+
39
+ if route_class is Route:
40
+ raise ValueError("Cannot register the base Route class directly")
41
+
42
+ base_path = route_class.get_path()
43
+ endpoints = route_class.get_endpoints()
44
+
45
+ if not endpoints:
46
+ return self # No endpoints to register
47
+
48
+ # Create an instance of the route class
49
+ instance = route_class()
50
+
51
+ # Determine tags from class name if not provided
52
+ if "tags" not in router_kwargs:
53
+ router_kwargs["tags"] = [route_class.__name__]
54
+
55
+ # Register each endpoint
56
+ for http_method, sub_path, method_func in endpoints:
57
+ # Build full path
58
+ if sub_path == "/":
59
+ full_path = base_path
60
+ else:
61
+ full_path = base_path.rstrip("/") + sub_path
62
+
63
+ # Bind the method to the instance
64
+ bound_method = getattr(instance, method_func.__name__)
65
+
66
+ # Get the appropriate router method (get, post, etc.)
67
+ router_method = getattr(self, http_method.lower())
68
+
69
+ # Register the route
70
+ router_method(
71
+ full_path,
72
+ **router_kwargs,
73
+ )(bound_method)
74
+
75
+ self._registered_routes.append(route_class)
76
+ return self
77
+
78
+ def discover(
79
+ self,
80
+ package: str,
81
+ *,
82
+ recursive: bool = True,
83
+ **router_kwargs
84
+ ) -> "Core":
85
+ """
86
+ Auto-discover and register Route subclasses from a package.
87
+
88
+ Args:
89
+ package: Package name or path to discover routes from
90
+ recursive: Whether to search subdirectories
91
+ **router_kwargs: Additional kwargs passed to each route's registration
92
+
93
+ Returns:
94
+ Self for method chaining
95
+ """
96
+ route_classes = discover_routes(package, recursive=recursive)
97
+
98
+ for route_class in route_classes:
99
+ self.register(route_class, **router_kwargs)
100
+
101
+ return self
102
+
103
+ def get_registered_routes(self) -> List[Type[Route]]:
104
+ """Get a list of all registered Route classes."""
105
+ return self._registered_routes.copy()