diff --git a/docs/en/tutorial/routers.md b/docs/en/tutorial/routers.md index e9c478c..67c16d8 100644 --- a/docs/en/tutorial/routers.md +++ b/docs/en/tutorial/routers.md @@ -182,7 +182,7 @@ from fasthttp import FastHTTP from src.clients.integrations import setup_integrations_router -app = FastHTTP() +app = FastHTTP(base_url="https://api.example.com") app.include_router(setup_integrations_router()) ``` diff --git a/docs/ru/tutorial/routers.md b/docs/ru/tutorial/routers.md index 53f3676..e5bde6e 100644 --- a/docs/ru/tutorial/routers.md +++ b/docs/ru/tutorial/routers.md @@ -186,7 +186,7 @@ from fasthttp import FastHTTP from src.clients.integrations import setup_integrations_router -app = FastHTTP() +app = FastHTTP(base_url="https://api.example.com") app.include_router(setup_integrations_router()) ``` diff --git a/fasthttp/app.py b/fasthttp/app.py index 7050798..9082e42 100644 --- a/fasthttp/app.py +++ b/fasthttp/app.py @@ -278,6 +278,23 @@ async def lifespan(app: FastHTTP): ), ] = "v4", base_url: Annotated[ + str | None, + Doc( + """ + Default base URL for included routers with relative paths. + + This value is used by `include_router()` when the router tree + contains relative URLs like `/users` and no explicit + `base_url` override is provided. + + Example: + ```python + app = FastHTTP(base_url="https://api.example.com") + ``` + """ + ), + ] = None, + docs_base_url: Annotated[ str, Doc( """ @@ -288,7 +305,7 @@ async def lifespan(app: FastHTTP): Example: ```python - app = FastHTTP(base_url="/api") + app = FastHTTP(docs_base_url="/api") ``` """ ), @@ -300,6 +317,7 @@ async def lifespan(app: FastHTTP): self.lifespan = lifespan self.proxy = proxy self.base_url = base_url + self.docs_base_url = docs_base_url self.generate_startup_uuid = generate_startup_uuid self.startup_uuid_version = startup_uuid_version @@ -550,9 +568,11 @@ def include_router( tags: Tags prepended before router tags. dependencies: Dependencies prepended before router dependencies. base_url: Base URL override for the included router tree. + If not provided, `FastHTTP.base_url` will be used. """ + resolved_base_url = base_url if base_url is not None else self.base_url routes = router.build_routes( - base_url=base_url, + base_url=resolved_base_url, prefix=prefix, tags=tags, dependencies=dependencies, @@ -1028,17 +1048,17 @@ async def index(resp): host: Host to bind to. Default is "127.0.0.1". port: Port to bind to. Default is 8000. base_url: Optional prefix for documentation endpoints. - If not provided, `FastHTTP.base_url` will be used. + If not provided, `FastHTTP.docs_base_url` will be used. """ self.logger.info("FastHTTP started") - base_url = ( - base_url if base_url is not None else self.base_url + docs_base_url = ( + base_url if base_url is not None else self.docs_base_url ) - app = ASGIApp(self, base_url=base_url) + app = ASGIApp(self, base_url=docs_base_url) server_base_url = f"http://{host}:{port}" - docs_urls = build_docs_urls(base_url) + docs_urls = build_docs_urls(docs_base_url) print(f"\n\033[92mfasthttp\033[0m running on \033[94m{server_base_url}\033[0m") print( diff --git a/tests/test_app.py b/tests/test_app.py index acdf55d..8583d3a 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -34,11 +34,17 @@ def test_app_creation_with_all_parameters(self) -> None: assert app.request_configs["GET"]["timeout"] == 60.0 assert app.request_configs["POST"]["timeout"] == 120.0 - def test_app_creation_with_docs_base_url(self) -> None: + def test_app_creation_with_base_url(self) -> None: """Test FastHTTP creation with base_url.""" - app = FastHTTP(base_url="/api") + app = FastHTTP(base_url="https://example.com") + + assert app.base_url == "https://example.com" + + def test_app_creation_with_docs_base_url(self) -> None: + """Test FastHTTP creation with docs_base_url.""" + app = FastHTTP(docs_base_url="/api") - assert app.base_url == "/api" + assert app.docs_base_url == "/api" def test_app_get_decorator(self) -> None: """Test GET decorator registers route.""" @@ -170,13 +176,26 @@ async def handler(resp: Response) -> dict: with pytest.raises(ValueError, match="Relative URL requires base_url"): app.include_router(router) + def test_app_include_router_uses_app_base_url_by_default(self) -> None: + app = FastHTTP(base_url="https://example.com") + router = Router(prefix="/v1") + + @router.get("/me") + async def handler(resp: Response) -> dict: + return resp.json() + + app.include_router(router) + + assert len(app.routes) == 1 + assert app.routes[0].url == "https://example.com/v1/me" + @pytest.mark.asyncio async def test_asgi_handle_request_docs_uses_app_docs_base_url(self) -> None: - """Test ASGI docs path uses base_url from FastHTTP.""" + """Test ASGI docs path uses docs_base_url from FastHTTP.""" from fasthttp.app import ASGIApp - app = FastHTTP(base_url="/api") - asgi_app = ASGIApp(app, base_url=app.base_url) + app = FastHTTP(docs_base_url="/api") + asgi_app = ASGIApp(app, base_url=app.docs_base_url) scope = {"type": "http", "path": "/api/docs", "method": "GET"} receive = AsyncMock()