schlerp / picoapi

An opinionated wrapper around FastAPI with custom microservice registration
BSD 2-Clause "Simplified" License
53 stars 5 forks source link

Error when service registration: TypeError: on_event() takes 2 positional arguments but 3 were given #2

Open BertKiv opened 2 years ago

BertKiv commented 2 years ago

I am using Docker and docker-compose and cannot register a slave service. What am I doing wrong? Api Gateway (main service) works like a charm but slave service registration fails.

I am not a FastAPI and Python master ;-)

Service main.py:

from picoapi import PicoAPI
from core.config import settings

app = PicoAPI(
    title       = settings.API_TITLE,
    description = settings.API_DESCRIPTION,
    version     = settings.API_VERSION,
    docs_url    = settings.API_DOCS_URL,
    responses   = {404: {"description": "Not found"}},
)

@app.get("/")
async def info():
    """
    Info about API documentation.
    """
    return { "message": "Welcome in %s microservice, version: %s. For more information go to /docs." % ( app.title, app.version ) }

.env file:

# API config
# ==========
API_TITLE="Example Service"
API_DESCRIPTION="Simple SaaS Boilerplate Example Service"
API_VERSION="0.0.1-alpha"
API_DOCS_URL="/docs"

API_BIND="0.0.0.0"
API_HOST="example-service"
API_PORT="8001"
API_TITLE="Example FastAPI Service"
API_TAGS="slave:microservice"

# microservice registration
# =========================
API_REGISTER_PATH="http://api-gateway:8000/register"
API_HEALTH_PATH="/health"
API_HEALTH_INTERVAL="300"

Dockerfiles:

FROM python:3.8.12

ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONBUFFERED 1
ENV PYTHONPATH=/app

RUN mkdir /app
WORKDIR /app/
ADD . /app

RUN pip install --no-cache-dir --upgrade pip
RUN pip install -r requirements.dev.txt --no-cache-dir

EXPOSE 8000

docker-compose.yml

version: '3.8'

services:
  api-gateway:
    image: api_gateway:dev
    build:
      context: ./app/api_gateway
      dockerfile: Dockerfile
    env_file:    
      - ./app/api_gateway/.env
    ports:    
      - 8000:8000
    volumes:    
      - ${PWD}/app/api_gateway/:/app
    command: sh -c "uvicorn main:app --reload --host 0.0.0.0"
    networks:
      - app

  example-service:
    image: example-service:dev
    build:
      context: ./app/custom_services/example_service
      dockerfile: Dockerfile
    env_file:    
      - ./app/custom_services/example_service/.env
    ports:    
      - 8001:8000
    volumes:    
      - ${PWD}/app/custom_services/example_service/:/app
    depends_on:
      - "api-gateway"
    command: sh -c "uvicorn main:app --reload --host 0.0.0.0"
    networks:
      - app

networks:
  app:
    external: true

Error:

example-service_1  | Traceback (most recent call last):
example-service_1  |   File "/usr/local/lib/python3.8/multiprocessing/process.py", line 315, in _bootstrap
example-service_1  |     self.run()
example-service_1  |   File "/usr/local/lib/python3.8/multiprocessing/process.py", line 108, in run
example-service_1  |     self._target(*self._args, **self._kwargs)
example-service_1  |   File "/usr/local/lib/python3.8/site-packages/uvicorn/subprocess.py", line 76, in subprocess_started
example-service_1  |     target(sockets=sockets)
example-service_1  |   File "/usr/local/lib/python3.8/site-packages/uvicorn/server.py", line 68, in run
example-service_1  |     return asyncio.run(self.serve(sockets=sockets))
example-service_1  |   File "/usr/local/lib/python3.8/asyncio/runners.py", line 44, in run
example-service_1  |     return loop.run_until_complete(main)
example-service_1  |   File "/usr/local/lib/python3.8/asyncio/base_events.py", line 616, in run_until_complete
example-service_1  |     return future.result()
example-service_1  |   File "/usr/local/lib/python3.8/site-packages/uvicorn/server.py", line 76, in serve
example-service_1  |     config.load()
example-service_1  |   File "/usr/local/lib/python3.8/site-packages/uvicorn/config.py", line 448, in load
example-service_1  |     self.loaded_app = import_from_string(self.app)
example-service_1  |   File "/usr/local/lib/python3.8/site-packages/uvicorn/importer.py", line 21, in import_from_string
example-service_1  |     module = importlib.import_module(module_str)
example-service_1  |   File "/usr/local/lib/python3.8/importlib/__init__.py", line 127, in import_module
example-service_1  |     return _bootstrap._gcd_import(name[level:], package, level)
example-service_1  |   File "<frozen importlib._bootstrap>", line 1014, in _gcd_import
example-service_1  |   File "<frozen importlib._bootstrap>", line 991, in _find_and_load
example-service_1  |   File "<frozen importlib._bootstrap>", line 975, in _find_and_load_unlocked
example-service_1  |   File "<frozen importlib._bootstrap>", line 671, in _load_unlocked
example-service_1  |   File "<frozen importlib._bootstrap_external>", line 843, in exec_module
example-service_1  |   File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
example-service_1  |   File "/app/./main.py", line 4, in <module>
example-service_1  |     app = PicoAPI(
example-service_1  |   File "/usr/local/lib/python3.8/site-packages/picoapi/api.py", line 85, in __init__
example-service_1  |     self.router.on_event("startup", register_uservice)
example-service_1  | TypeError: on_event() takes 2 positional arguments but 3 were given

What I was checking

When I change the slave service from PicoAPI to FastAPI and manually register with CUrl everything looks fine.

curl -X 'GET' \
  'http://api-gateway:8000/register' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "name": "Example Service",
  "tags": [
    "slave:service"
  ],
  "host": "example-service",
  "port": 8001,
  "metadata": {},
  "healthcheck": {
    "url": "/health",
    "interval": 300
  }
}'

result: null

Checking status from gateway:

curl -X 'GET' 'http://api-gateway:8000/services/status' -H 'accept: application/json'

Result:

[{"name":"Example Service","status":"unhealthy"}]

"status": "unhealthy" has no meaning as there is no /health endpoint.

BertKiv commented 2 years ago

OK, I solved it. When I'm finished I'll send you a PR.

BertKiv commented 2 years ago

PR https://github.com/schlerp/picoapi/pull/3

schlerp commented 2 years ago

Hey, sorry it took me so long to reply. just reviewing your PR now. So this looks like the FastAPI API has changed in a recent version. The general ideas of your PR seem to be on the right track. Unfortunately, this is actually a little more complicated. According to the HTTP spec, the health service registration request needs to be a PUT or POST as its sending a JSON body with information that has semantic meaning to the request (not allowed in a GET).

I have added comments (or am in the process of adding them) to you PR. Would you like to try to submit again making those changes? I'll leave this issue open for discourse until we merge your PR.

Thanks for taking an interest in this project and learning about its internals!

BertKiv commented 2 years ago

I need something like PicoAPI in my project so I can take care of it. I also want to extend PicoAPI with routing to slave microservices for a more complete solution and fits to my needs, but not only mine, I think :-)