Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(apigateway): add Router to allow large routing composition #645

Merged
merged 9 commits into from
Oct 7, 2021

Conversation

BVMiko
Copy link
Contributor

@BVMiko BVMiko commented Aug 23, 2021

Add Router class to ApiGatewayResolver

Issue #, if available: #644

Description of changes:

Add a Router class to be used as a proxy for ApiGatewayResolver. The class is able to at as a placeholder for the app instance and collect routes to later be applied; it includes all the functionality of current routes.

To access query strings or headers the original event and context are made available as router.current_event and router.lambda_context respectively. Also, to improve performance of a function which uses an endpoint with multiple methods, it offers a slight variation of the route() method's signature allowing a list/tuple.

Usage example:

app/main.py:

from aws_lambda_powertools import Logger, Tracer
from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver
from aws_lambda_powertools.logging import correlation_paths

from .routers import users, items

tracer = Tracer()
logger = Logger()
app = ApiGatewayResolver()
app.include_router(users.router)
app.include_router(items.router, prefix="/items")

@logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_REST)
@tracer.capture_lambda_handler
def lambda_handler(event, context):
    return app.resolve(event, context)

app/routers/items.py:

from aws_lambda_powertools import Logger, Tracer
from aws_lambda_powertools.event_handler.api_gateway import Router

tracer = Tracer()
logger = Logger(child=True)
router = Router()

@router.get('/hello')
@tracer.capture_method
def get_hello():
    return {
        "message": ['/hello', 'GET'],
        "query_string": router.current_event.query_string_parameters,
    }

@router.route('/world', ('GET','POST'))
@tracer.capture_method
def multi_world():
    return {
        "message": ['/world', 'GET/POST'],
        "query_string": router.current_event.query_string_parameters,
    }

Checklist

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@pull-request-size pull-request-size bot added the size/M Denotes a PR that changes 30-99 lines, ignoring generated files. label Aug 23, 2021
@boring-cyborg
Copy link

boring-cyborg bot commented Aug 23, 2021

Thanks a lot for your first contribution! Please check out our contributing guidelines and don't hesitate to ask whatever you need.

@boring-cyborg boring-cyborg bot added the tests label Aug 31, 2021
@BVMiko
Copy link
Contributor Author

BVMiko commented Aug 31, 2021

I modified the blueprint to not be a callable; but instead to use a .route() method to more closely match the original app style. I also added a test for this and fixed up the formatting to match the project's style.

@BVMiko
Copy link
Contributor Author

BVMiko commented Aug 31, 2021

I swapped the registration from being a function of the blueprint to a function of the app; specifically:

    blueprint.register_to_app(app)

to

    app.register_blueprint(blueprint)

@heitorlessa
Copy link
Contributor

Thanks a lot @BVMiko - I'll review when I get back from vacation (Sep 27th).

cc @saragerion @pankajagrawal16 @t1agob for their thoughts on whether they know a more explicit name to avoid confusion when ported to other languages (Java, TS, C#)

@heitorlessa
Copy link
Contributor

hey @am29d - could you help review this one?

I like what the feature enables as some customers might have multiple routes and can still use multiple functions. However, Blueprint is Flask terminology specific and might not be 1/ easy to transfer to other upcoming runtimes, and 2/ not immediately clear what the purpose is for those not experienced with Flask.

@BVMiko I'm back from vacation now, so we'll be looking into this shortly. Thank you so much for the help so far!

@BVMiko
Copy link
Contributor Author

BVMiko commented Sep 29, 2021

I have no particular fondness towards the name Blueprint; it was only selected to mirror the first routing framework that I found via a Google search for a similar feature. I'll gladly update the PR if you have an alternate name in mind.

@heitorlessa
Copy link
Contributor

Discussed during our hackathon that @michaelbrewer will be looking at a more explicit name (we don't knot yet :/), and whether there are other ways we could achieve the same result too

@michaelbrewer
Copy link
Contributor

@heitorlessa @BVMiko - i have gone through the PR, other than the conflicts from the develop branch that need to be resolved, i did find a couple of isses:

  1. some test coverage was missing for using a list of methods.
  2. missing support for named parameters

Here is patch diff.

diff --git a/aws_lambda_powertools/event_handler/api_gateway.py b/aws_lambda_powertools/event_handler/api_gateway.py
index 7de2583..3e2545d 100644
--- a/aws_lambda_powertools/event_handler/api_gateway.py
+++ b/aws_lambda_powertools/event_handler/api_gateway.py
@@ -654,8 +654,8 @@ class Blueprint:
         def actual_decorator(func: Callable):
             @wraps(func)
             def wrapper(app: ApiGatewayResolver):
-                def inner_wrapper():
-                    return func(app)
+                def inner_wrapper(**kwargs):
+                    return func(app, **kwargs)
 
                 return inner_wrapper
 
diff --git a/tests/functional/event_handler/test_api_gateway.py b/tests/functional/event_handler/test_api_gateway.py
index 9920001..c15b669 100644
--- a/tests/functional/event_handler/test_api_gateway.py
+++ b/tests/functional/event_handler/test_api_gateway.py
@@ -879,3 +879,27 @@ def test_api_gateway_app_proxy():
     # THEN process event correctly
     assert result["statusCode"] == 200
     assert result["headers"]["Content-Type"] == content_types.APPLICATION_JSON
+
+
+def test_api_gateway_app_proxy_with_params():
+    # GIVEN a Blueprint with registered routes
+    app = ApiGatewayResolver()
+    blueprint = Blueprint()
+    req = "foo"
+    event = deepcopy(LOAD_GW_EVENT)
+    event["resource"] = "/accounts/{account_id}"
+    event["path"] = f"/accounts/{req}"
+
+    @blueprint.route(rule="/accounts/<account_id>", method=["GET", "POST"])
+    def foo(app: ApiGatewayResolver, account_id):
+        assert app.current_event.raw_event == event
+        assert account_id == f"{req}"
+        return {}
+
+    app.register_blueprint(blueprint)
+    # WHEN calling the event handler after applying routes from blueprint object
+    result = app(event, {})
+
+    # THEN process event correctly
+    assert result["statusCode"] == 200
+    assert result["headers"]["Content-Type"] == content_types.APPLICATION_JSON

@pull-request-size pull-request-size bot added size/L Denotes a PR that changes 100-499 lines, ignoring generated files. and removed size/M Denotes a PR that changes 30-99 lines, ignoring generated files. labels Oct 1, 2021
@BVMiko
Copy link
Contributor Author

BVMiko commented Oct 1, 2021

I've rebased on the latest develop branch and applied the fixes from @michaelbrewer. Let me know of any decisions for a better name and I'll gladly make those changes as well.

@codecov-commenter
Copy link

codecov-commenter commented Oct 1, 2021

Codecov Report

Merging #645 (836cd71) into develop (c21ba45) will decrease coverage by 0.08%.
The diff coverage is 88.23%.

Impacted file tree graph

@@             Coverage Diff             @@
##           develop     #645      +/-   ##
===========================================
- Coverage    99.93%   99.85%   -0.09%     
===========================================
  Files          116      116              
  Lines         4876     4942      +66     
  Branches       268      276       +8     
===========================================
+ Hits          4873     4935      +62     
- Misses           1        5       +4     
  Partials         2        2              
Impacted Files Coverage Δ
aws_lambda_powertools/event_handler/api_gateway.py 98.28% <88.23%> (-1.72%) ⬇️
aws_lambda_powertools/shared/constants.py 100.00% <0.00%> (ø)
aws_lambda_powertools/logging/formatter.py 100.00% <0.00%> (ø)
..._lambda_powertools/utilities/feature_flags/base.py 100.00% <0.00%> (ø)
...ambda_powertools/utilities/feature_flags/schema.py 100.00% <0.00%> (ø)
...da_powertools/utilities/feature_flags/appconfig.py 100.00% <0.00%> (ø)
...da_powertools/utilities/idempotency/idempotency.py 100.00% <0.00%> (ø)
...owertools/utilities/feature_flags/feature_flags.py 100.00% <0.00%> (ø)
...ools/utilities/idempotency/persistence/dynamodb.py 100.00% <0.00%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update c21ba45...836cd71. Read the comment docs.

@michaelbrewer
Copy link
Contributor

Naming wise @heitorlessa and @cakepietoast had the impression we don't want to confuse people that this would be a drop in replacement for flask.

So i will defer that part to @heitorlessa . After this is merged, we would need to work on the docs with some nice examples.

@BVMiko
Copy link
Contributor Author

BVMiko commented Oct 1, 2021

At the suggestion of a colleague, I added an additional prefix parameter when registering sub apps; tests are included.

@michaelbrewer
Copy link
Contributor

@BVMiko - a little more code coverage missing to test the different supported routes

I do see that if the app.register_blueprint(blueprint) happens before the route decorator, then non
of the entries are registered (but i think we can address that in the docs and some examples

diff --git a/tests/functional/event_handler/test_api_gateway.py b/tests/functional/event_handler/test_api_gateway.py
index 8d8b5a64..8a6978e2 100644
--- a/tests/functional/event_handler/test_api_gateway.py
+++ b/tests/functional/event_handler/test_api_gateway.py
@@ -942,3 +942,53 @@ def test_api_gateway_app_proxy_with_prefix_equals_path():
     # THEN process event correctly
     assert result["statusCode"] == 200
     assert result["headers"]["Content-Type"] == content_types.APPLICATION_JSON
+
+
+def test_api_gateway_app_proxy_with_different_methods():
+    # GIVEN a Blueprint with all the possible HTTP methods
+    app = ApiGatewayResolver()
+    blueprint = Blueprint()
+
+    @blueprint.get("/not_matching_get")
+    def get_func():
+        raise RuntimeError()
+
+    @blueprint.post("/no_matching_post")
+    def post_func():
+        raise RuntimeError()
+
+    @blueprint.put("/no_matching_put")
+    def put_func():
+        raise RuntimeError()
+
+    @blueprint.delete("/no_matching_delete")
+    def delete_func():
+        raise RuntimeError()
+
+    @blueprint.patch("/no_matching_patch")
+    def patch_func():
+        raise RuntimeError()
+
+    app.register_blueprint(blueprint)
+
+    # Also check check the route configurations
+    routes = app._routes
+    assert len(routes) == 5
+    for route in routes:
+        if route.func == get_func:
+            assert route.method == "GET"
+        elif route.func == post_func:
+            assert route.method == "POST"
+        elif route.func == put_func:
+            assert route.method == "PUT"
+        elif route.func == delete_func:
+            assert route.method == "DELETE"
+        elif route.func == patch_func:
+            assert route.method == "PATCH"
+
+    # WHEN calling the handler
+    # THEN return a 404
+    result = app(LOAD_GW_EVENT, None)
+    assert result["statusCode"] == 404
+    # AND cors headers are not returned
+    assert "Access-Control-Allow-Origin" not in result["headers"]

@michaelbrewer
Copy link
Contributor

Otherwise you might want to the registering of the routes outside of the handler ie:

from aws_lambda_powertools import Logger, Tracer
from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver
from aws_lambda_powertools.logging import correlation_paths

from . import endpoint1

tracer = Tracer()
logger = Logger()
app = ApiGatewayResolver()
app.register_blueprint(endpoint1.blueprint)

@logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_REST)
@tracer.capture_lambda_handler
def lambda_handler(event, context):
    return app.resolve(event, context)

@michaelbrewer
Copy link
Contributor

@heitorlessa @BVMiko - as a side note, i do like the developer experience of this library for Appsync resolvers (https://github.com/twkiiim/sleemo). Basically you just register a directory where you resolvers are.

@heitorlessa - i trust we can come up with a meaning for name for this, like ProxyResolver.

@michaelbrewer
Copy link
Contributor

@heitorlessa sent me another good implementation of this:

@michaelbrewer
Copy link
Contributor

Having looked a little at the FastAPI implementation i see some nice things about it:

  • APIRoute is a better name over all than BluePrint
  • Setting the prefix="/admin" in both the APIRoute and when you add a route app.include_router( makes sense
  • FastAPI documentation for this feature is nicely laid out, with some good folder layouts
  • Passing the context info is not done via the app handle by rather by starlette context. But maybe there can be another way to do this part

@heitorlessa
Copy link
Contributor

heitorlessa commented Oct 2, 2021 via email

@michaelbrewer
Copy link
Contributor

@BVMiko @heitorlessa - I have made the suggested changes in this patch

diff --git a/aws_lambda_powertools/event_handler/api_gateway.py b/aws_lambda_powertools/event_handler/api_gateway.py
index a296874f..d3a79761 100644
--- a/aws_lambda_powertools/event_handler/api_gateway.py
+++ b/aws_lambda_powertools/event_handler/api_gateway.py
@@ -631,22 +631,33 @@ class ApiGatewayResolver:
     def _json_dump(self, obj: Any) -> str:
         return self._serializer(obj)
 
-    def register_blueprint(self, blueprint: "Blueprint", prefix: Optional[str] = None) -> None:
-        """Adds all routes defined in a blueprint"""
-        for route, func in blueprint.api.items():
+    def include_router(self, router: "Router", prefix: Optional[str] = None) -> None:
+        """Adds all routes defined in a router"""
+        router._app = self
+        for route, func in router.api.items():
             if prefix and route[0] == "/":
                 route = (prefix, *route[1:])
             elif prefix:
                 route = (f"{prefix}{route[0]}", *route[1:])
-            self.route(*route)(func(app=self))
+            self.route(*route)(func())
 
 
-class Blueprint:
-    """Blueprint helper class to allow splitting ApiGatewayResolver into multiple files"""
+class Router:
+    """Router helper class to allow splitting ApiGatewayResolver into multiple files"""
+
+    _app: ApiGatewayResolver
 
     def __init__(self):
         self.api: Dict[tuple, Callable] = {}
 
+    @property
+    def current_event(self) -> BaseProxyEvent:
+        return self._app.current_event
+
+    @property
+    def lambda_context(self) -> LambdaContext:
+        return self._app.lambda_context
+
     def route(
         self,
         rule: str,
@@ -657,9 +668,9 @@ class Blueprint:
     ):
         def actual_decorator(func: Callable):
             @wraps(func)
-            def wrapper(app: ApiGatewayResolver):
+            def wrapper():
                 def inner_wrapper(**kwargs):
-                    return func(app, **kwargs)
+                    return func(**kwargs)
 
                 return inner_wrapper
 
diff --git a/tests/functional/event_handler/test_api_gateway.py b/tests/functional/event_handler/test_api_gateway.py
index 8d8b5a64..afc97906 100644
--- a/tests/functional/event_handler/test_api_gateway.py
+++ b/tests/functional/event_handler/test_api_gateway.py
@@ -13,11 +13,11 @@ import pytest
 from aws_lambda_powertools.event_handler import content_types
 from aws_lambda_powertools.event_handler.api_gateway import (
     ApiGatewayResolver,
-    Blueprint,
     CORSConfig,
     ProxyEventType,
     Response,
     ResponseBuilder,
+    Router,
 )
 from aws_lambda_powertools.event_handler.exceptions import (
     BadRequestError,
@@ -863,17 +863,17 @@ def test_api_gateway_request_path_equals_strip_prefix():
     assert result["headers"]["Content-Type"] == content_types.APPLICATION_JSON
 
 
-def test_api_gateway_app_proxy():
-    # GIVEN a Blueprint with registered routes
+def test_api_gateway_app_router():
+    # GIVEN a Router with registered routes
     app = ApiGatewayResolver()
-    blueprint = Blueprint()
+    router = Router()
 
-    @blueprint.get("/my/path")
-    def foo(app: ApiGatewayResolver):
+    @router.get("/my/path")
+    def foo():
         return {}
 
-    app.register_blueprint(blueprint)
-    # WHEN calling the event handler after applying routes from blueprint object
+    app.include_router(router)
+    # WHEN calling the event handler after applying routes from router object
     result = app(LOAD_GW_EVENT, {})
 
     # THEN process event correctly
@@ -881,42 +881,44 @@ def test_api_gateway_app_proxy():
     assert result["headers"]["Content-Type"] == content_types.APPLICATION_JSON
 
 
-def test_api_gateway_app_proxy_with_params():
-    # GIVEN a Blueprint with registered routes
+def test_api_gateway_app_router_with_params():
+    # GIVEN a Router with registered routes
     app = ApiGatewayResolver()
-    blueprint = Blueprint()
+    router = Router()
     req = "foo"
     event = deepcopy(LOAD_GW_EVENT)
     event["resource"] = "/accounts/{account_id}"
     event["path"] = f"/accounts/{req}"
+    lambda_context = {}
 
-    @blueprint.route(rule="/accounts/<account_id>", method=["GET", "POST"])
-    def foo(app: ApiGatewayResolver, account_id):
-        assert app.current_event.raw_event == event
+    @router.route(rule="/accounts/<account_id>", method=["GET", "POST"])
+    def foo(account_id):
+        assert router.current_event.raw_event == event
+        assert router.lambda_context == lambda_context
         assert account_id == f"{req}"
         return {}
 
-    app.register_blueprint(blueprint)
-    # WHEN calling the event handler after applying routes from blueprint object
-    result = app(event, {})
+    app.include_router(router)
+    # WHEN calling the event handler after applying routes from router object
+    result = app(event, lambda_context)
 
     # THEN process event correctly
     assert result["statusCode"] == 200
     assert result["headers"]["Content-Type"] == content_types.APPLICATION_JSON
 
 
-def test_api_gateway_app_proxy_with_prefix():
-    # GIVEN a Blueprint with registered routes
+def test_api_gateway_app_router_with_prefix():
+    # GIVEN a Router with registered routes
     # AND a prefix is defined during the registration
     app = ApiGatewayResolver()
-    blueprint = Blueprint()
+    router = Router()
 
-    @blueprint.get(rule="/path")
-    def foo(app: ApiGatewayResolver):
+    @router.get(rule="/path")
+    def foo():
         return {}
 
-    app.register_blueprint(blueprint, prefix="/my")
-    # WHEN calling the event handler after applying routes from blueprint object
+    app.include_router(router, prefix="/my")
+    # WHEN calling the event handler after applying routes from router object
     result = app(LOAD_GW_EVENT, {})
 
     # THEN process event correctly
@@ -924,21 +926,71 @@ def test_api_gateway_app_proxy_with_prefix():
     assert result["headers"]["Content-Type"] == content_types.APPLICATION_JSON
 
 
-def test_api_gateway_app_proxy_with_prefix_equals_path():
-    # GIVEN a Blueprint with registered routes
+def test_api_gateway_app_router_with_prefix_equals_path():
+    # GIVEN a Router with registered routes
     # AND a prefix is defined during the registration
     app = ApiGatewayResolver()
-    blueprint = Blueprint()
+    router = Router()
 
-    @blueprint.get(rule="/")
-    def foo(app: ApiGatewayResolver):
+    @router.get(rule="/")
+    def foo():
         return {}
 
-    app.register_blueprint(blueprint, prefix="/my/path")
-    # WHEN calling the event handler after applying routes from blueprint object
+    app.include_router(router, prefix="/my/path")
+    # WHEN calling the event handler after applying routes from router object
     # WITH the request path matching the registration prefix
     result = app(LOAD_GW_EVENT, {})
 
     # THEN process event correctly
     assert result["statusCode"] == 200
     assert result["headers"]["Content-Type"] == content_types.APPLICATION_JSON
+
+
+def test_api_gateway_app_router_with_different_methods():
+    # GIVEN a Router with all the possible HTTP methods
+    app = ApiGatewayResolver()
+    router = Router()
+
+    @router.get("/not_matching_get")
+    def get_func():
+        raise RuntimeError()
+
+    @router.post("/no_matching_post")
+    def post_func():
+        raise RuntimeError()
+
+    @router.put("/no_matching_put")
+    def put_func():
+        raise RuntimeError()
+
+    @router.delete("/no_matching_delete")
+    def delete_func():
+        raise RuntimeError()
+
+    @router.patch("/no_matching_patch")
+    def patch_func():
+        raise RuntimeError()
+
+    app.include_router(router)
+
+    # Also check check the route configurations
+    routes = app._routes
+    assert len(routes) == 5
+    for route in routes:
+        if route.func == get_func:
+            assert route.method == "GET"
+        elif route.func == post_func:
+            assert route.method == "POST"
+        elif route.func == put_func:
+            assert route.method == "PUT"
+        elif route.func == delete_func:
+            assert route.method == "DELETE"
+        elif route.func == patch_func:
+            assert route.method == "PATCH"
+
+    # WHEN calling the handler
+    # THEN return a 404
+    result = app(LOAD_GW_EVENT, None)
+    assert result["statusCode"] == 404
+    # AND cors headers are not returned
+    assert "Access-Control-Allow-Origin" not in result["headers"]

The updated user experience will be:

app/router/items.py

from aws_lambda_powertools import Logger, Tracer
from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver, Router

tracer = Tracer()
logger = Logger(child=True)
router = Router()

@router.get('/hello')
@tracer.capture_method
def get_hello():
    return {
        "message": ['/hello', 'GET'],
        "query_string": router.current_event.query_string_parameters
    }

@router.route('/world', ('GET','POST'))
@tracer.capture_method
def multi_world():
    return {
        "message": ['/world', 'GET/POST'],
        "query_string": router.current_event.query_string_parameters
    }

app/main.py

from aws_lambda_powertools import Logger, Tracer
from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver
from aws_lambda_powertools.logging import correlation_paths

from .routers import users, items

tracer = Tracer()
logger = Logger()
app = ApiGatewayResolver()
app.include_router(users.router)
app.include_router(items.router, prefix="/items")

@logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_REST)
@tracer.capture_lambda_handler
def lambda_handler(event, context):
    return app.resolve(event, context)

Recommended folder structure (based on FastApi)

.
├── app                  # "app" is a Python package
│   ├── __init__.py      # this file makes "app" a "Python package"
│   ├── main.py          # "main" module, e.g. import app.main
│   └── routers          # "routers" is a "Python subpackage"
│   │   ├── __init__.py  # makes "routers" a "Python subpackage"
│   │   ├── items.py     # "items" submodule, e.g. import app.routers.items
│   │   └── users.py     # "users" submodule, e.g. import app.routers.users
│   └── internal         # "internal" is a "Python subpackage"
│       ├── __init__.py  # makes "internal" a "Python subpackage"
│       └── admin.py     # "admin" submodule, e.g. import app.internal.admin

@DanyC97
Copy link
Contributor

DanyC97 commented Oct 4, 2021

@BVMiko 👋 just a minor note from someone new to this library who is trying to get a grasp on various PRs / discussions going on.

Imo would be nice to explain in the Pr/ commit description what this functionality will give to a new user; in other words why should they use it

Add a Blueprint class to be used as a proxy for ApiGatewayResolver. The class is able to collect routes and later apply them to the app instance; and includes all the functionality of current routes.

me sitting in my corner and thinking: what is the gap/ missing feature in the current release and what will this PR add / extend ... maybe an example of the logs output as a sample will help more?

@BVMiko
Copy link
Contributor Author

BVMiko commented Oct 4, 2021

@michaelbrewer: I've used FastAPI before and found it a very nice experience. Those changes look good to me, I'll update the PR in a few hours.

@DanyC97: I described the problem in more detail in the issue #644; essentially the problem is that using ApiGatewayResolver as it is today requires each of the different routes to be defined in the same file as the main lambda entrypoint, like this:

app = ApiGatewayResolver()

@app.get('/hello')
def get_hello():
    ...

@app.get('/world')
def get_world():
    ...

def lambda_handler(event, context):
    return app.resolve(event, context)

This is not ideal for larger projects with many different paths defined; as adding a new path requires modifying the main lambda entrypoint file. This PR is helpful for larger projects, as it introduces a way to separate distinct portions of the project into their own files/folders and it allows adding a new path without modifying the main Python entrypoint file each time.

@BVMiko BVMiko changed the title feat: Add Blueprint feat: Add Router Oct 4, 2021
Copy link
Contributor

@michaelbrewer michaelbrewer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BVMiko - LGTM :)

@heitorlessa
Copy link
Contributor

Just released 1.21 and I can now look at this properly starting this Friday. I'll likely create a separate PR with docs and get everyone's eyes to review to make sure our guidance is on point on how to use it.

Thank you so much everyone so far

@heitorlessa heitorlessa self-assigned this Oct 5, 2021
@heitorlessa heitorlessa added the feature New feature or functionality label Oct 5, 2021
@heitorlessa heitorlessa changed the title feat: Add Router feat(apigateway): add Router to allow large routing composition Oct 5, 2021
@heitorlessa heitorlessa merged commit 6fcedec into aws-powertools:develop Oct 7, 2021
@heitorlessa
Copy link
Contributor

Thank you so much everyone for this contribution and review!

Three things before it's released in the next version, which can be done in other PRs @michaelbrewer

  • def route might be reused to keep Router leaner
  • test to check what happens when you have the same route in App and Router
  • Docs

heitorlessa added a commit to heitorlessa/aws-lambda-powertools-python that referenced this pull request Oct 8, 2021
@BVMiko BVMiko deleted the blueprint branch October 10, 2021 23:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New feature or functionality size/L Denotes a PR that changes 100-499 lines, ignoring generated files. tests
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants