Type Middleware
arc is based on a middleware design pattern. This also applies to arc's type system to give you further flexibility in defining your APIs.
What is a Type Middleware?¶
A Type Middleware is simply a callable object that recieves a value, and then returns a value of the same type. It can be used to modify that value, validate some property of that value, or simply analyze the value and return it.
Middlewares fall into a couple of rough categories, and so arc uses specific terms to refer to them.
- Validator: A middleware that checks the value against some condition. It raises an exception if the condition is not met, and otherwise it returns the original value
- Transformer: A middleware that modifies the value and returns it.
- Observer: A middleware that just analyzes the type, but won't every raise an error / manipulate it
These terms will be used throughout the rest of this page.
Creating a Type Middleware¶
Because middleware are just callables, they are extremely simple to define. For example, we could create a transform middleware that rounds floats to 2 digits.
Tip
arc already ships with a Round()
transformer, so you wouldn't actually need to implement this one yourself.
Using a Type Middleware¶
Type Middleware are attached to a type using an Annotated Type
from typing import Annotated
import arc
# Could use arc.types.middleware.Round() instead
# of implementing a custom middleware.
def round2(val: float):
return round(val, 2)
@arc.command
def command(val: Annotated[float, round2]):
arc.print(val)
command()
Tip
Middlewares are actually why most custom arc types require you to use arc.convert()
to
convert them properly. A good majority of them are actually just an underlying type + some middleware to provide a validation / conversion step. For example arc.types.PositiveInt
is actually just defined as
Builtin Middlewares¶
arc ships with a set of general-use builtin middlewares
Examples¶
Middleware that ensure that a passed in version is greater than the current version
import typing as t
import arc
from arc import types
def greater_than_previous(value: types.SemVer, ctx: arc.Context):
current_version: types.SemVer | None = ctx.state.get("curr_version")
if not current_version:
return value
if current_version >= value:
raise arc.ValidationError(
f"New version must be greater than current version ({current_version})"
)
return value
NewVersion = t.Annotated[types.SemVer, greater_than_previous]
@arc.command
def command(version: NewVersion):
arc.print(version)
command(state={"curr_version": types.SemVer.parse("1.0.0")})