'How to call another Sanic Application from One

I have two Python web frameworks (Sanic) deployed separately in a microservice architecture.

How can I call the endpoints of Framework A inside Framework B?

I am looking for something that removes the need to provide the full URL.

Something like

@app.route(/data_from_framework_a)
def foo(request):
    response = request.app.ctx.framework_a_client.get("/get_data")

Where framework_a_client is already authenticated to use service from Framework A and knows the host and port configured.

Note: both frameworks are isolated, hosted on different machines, and have authentication required to access them



Solution 1:[1]

This is a broad question with no single answer. The application instances are entire separate from one another. You could of course build a service, instantiate it, and provide all of the necessary details with how to interact with the other applications. But, this is entirely dependent upon what your application needs are.

With that said, here is a barebones service that you could use to follow a pattern that I have used in the past.

class ClientA:
    def __init__(self, base_url, authentication_details):
        self.base_url = base_url
        self._store_authentication_details(authentication_details)

    def _prepare_authentication_headers(self):
        return {
            "authorization": "..."
        }

    async def get(self, path: str) -> httpx.Response:
        async with httpx.AsyncClient() as client:
            return await client.get(
                "/".join([self.base_url, path]),
                headers=self._prepare_authentication_headers()
            )

Implement it as follows:

@app.before_server_start
async def start_client_a(app):
    app.ctx.framework_a_client = ClientA(...)

@app.route(/data_from_framework_a)
def foo(request):
    response = await request.app.ctx.framework_a_client.get("/get_data")

Alternatively using Sanic Extensions...

@app.before_server_start
async def start_client_a(app):
    app.ext.dependency(ClientA(...))

@app.route(/data_from_framework_a)
def foo(request, client_a: ClientA):  # Benefit of this is we now have a fully typed object
    response = await client_a.get("/get_data")

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Adam Hopkins