ESC

Type to search...

Annotations

Annotations attach metadata to handlers. The runtime reads it. No magic, no metaclasses.

Available Annotations

AnnotationPurpose
@intentSet pipeline, timeout, priority
@requiresDeclare required dependencies
@validatesInput validation schema
@rate_limitRate limiting config
@bodyRequest body declaration
@paramsPath/query parameter declaration
@headersHeader requirements

@intent

Declare pipeline, timeout, and priority on a handler.

from evoid.core.annotations import intent

@intent(pipeline=("validate", "authorize", "GET:/pay"), timeout=30)
@get("/pay")
async def process_payment(ctx):
    return {"paid": True}

Critical rule: the last element in pipeline must be the intent name (e.g. "GET:/pay"), not the Python function name. When you use @get, @post, etc., the handler registers under the intent name, not fn.__name__.

# CORRECT: intent name in pipeline
@intent(pipeline=("validate", "GET:/users"))
@get("/users")
async def list_users(ctx): ...

# WRONG: function name in pipeline (handler won't run)
@intent(pipeline=("validate", "list_users"))
@get("/users")
async def list_users(ctx): ...

Without Route Decorators

When using native IOP (no @get/@post), the pipeline uses the Intent name directly:

from evoid import Intent, Level, add_intent

@intent(pipeline=("validate", "pay"))
async def pay(ctx): ...

PAY = Intent(name="pay", level=Level.CRITICAL)
add_intent(PAY, pay)

@requires

Declare required dependencies. Checked at registration time.

from evoid.core.annotations import requires

@requires("auth_engine", "db_connection")
async def get_user(ctx):
    auth = ctx.deps["auth_engine"]
    db = ctx.deps["db_connection"]
    return await db.read("user:1")

@validates

Declare input validation schema.

from evoid.core.annotations import validates

@validates({"amount": {"type": "number", "required": True}})
async def process_payment(ctx):
    amount = ctx.metadata["body"]["amount"]
    return {"paid": amount}

@rate_limit

Declare rate limiting.

from evoid.core.annotations import rate_limit

@rate_limit(max_calls=100, period=60)
async def api_call(ctx):
    return {"ok": True}

@body and @params

Declare input expectations for route handlers.

from evoid.core.annotations import body, params

@body(fields={"name": {"type": "string", "required": True}})
@post("/users")
async def create_user(ctx): ...

@params(fields=["id"])
@get("/users/{id}")
async def get_user(ctx): ...

How Annotations Flow

  1. Decorator attaches metadata to the function (fn._evoid_intent, etc.)
  2. Route decorator (@get, @post) calls apply_annotations() to read metadata
  3. validate_annotations() checks for errors (e.g. intent name missing from pipeline)
  4. Metadata is used to configure the Intent and pipeline
  • Intent: the data that annotations configure
  • Pipeline: how annotations affect execution
  • IOP Levels: level determines default pipeline