Quick Reference
Commands
Skeleton
Command Skeleton - apps/subapp/commands.py
from apiup import Command, Context, command, Jsonable, Permission
# - method options: GET, POST, PUT, PATCH, DELETE
# - endpoint: 1. starts with /, 2. slugify using -, 3. to lower case
# - permission options: ADMIN, STAFF, VERIFIED_USER, USER, PUBLIC, OWNER, ONLY_OWNER
# Defaults:
# - method: POST
# - endpoint: /subapp/name-of-the-class
# - permission: STAFF
@command(method="GET", endpoint="/my-command", permission=Permission.OWNER)
class MyCommand(Command):
async def command(self, ctx: Context) -> Jsonable:
return {} # dict, list, string, number, Document(s)
Parameters and Validation
Parameters and Validations - apps/subapp/commands.py
from apiup import Command, Context, command, Jsonable, F, Permission
@command()
class MyCommand(Command):
field1: bool = F(type='bool', required=True, default=False)
field2: int = F(type='int', choices=[1, 2, 3], min=0, max=10) # min/max value
field3: float = F(type='float', min=0, max=10) # min/max value
field4: str = F(type='string', format='email', choices=['a', 'b'], pattern='.*', min=0, max=10) # min/max length
field5: list[float] = F(type='list', subtype='float', min=0, max=10) # min/max list size
field6: dict[str, int] = F(type='dict', subtype='int', min=0, max=10) # min/max dict size
async def command(self, ctx: Context) -> Jsonable:
print(self.field1, self.field2, ...)
return self.get_data()
Context
Context - apps/subapp/commands.py
from apiup import command, Command, Context, Jsonable, Permission
@command()
class MyCommand(Command):
async def command(self, ctx: Context) -> Jsonable:
value: str = ctx.get_header('header-name')
current_user = ctx.user
current_user_id = ctx.user.get_id()
return None
Document Schemas
Minimum Schema
Minimum Schema - apps/subapp/schemas/AnyModel.yaml
name: Product
fields:
name:
type: string
price_cents:
type: int
Usage in Python
Usage - apps/subapp/any_module.py
from apiup import Doc
Product = Doc('Product')
p = Product(name='...', price_cents=100)
p.name = '...'
p.price_cents = 100
Common Schema
Common Schema - apps/subapp/schemas/AnyModel.yaml
name: Product
acl:
# choices: admin, staff, vuser, user, public, owner, only_owner
create: staff
read: user
update: staff
delete: admin
meta:
fieldsets: [product, price]
fields:
user_id:
type: string
format: id
collection: auth_user
index:
unique: false
name:
type: string
max: 50
index:
unique: true
meta:
fieldset: product
price_cents:
type: int
max: 200
meta:
fieldset: price
Fields
Fields - apps/subapp/schemas/AnyModel.yaml
name: Product
fields:
field_name:
# Type Constraints
type: # bool/int/float/string/list/dict
subtype: # bool/int/float/string/list/dict (for types list/dict)
# Field Constraints
required: true/false
default: type/value
read_only: true/false # Can be filled just once (in create or update) but can't update/change it after that
write_only: true/false # Can be writted but not read (like password fields)
# Value Constraints
min: # min value or min length
max: # max value or max length
choices: [] # For string or int
forbid: [] # For string or int
format: # For string: id, decimal, datetime, date, time, slug, email
pattern: # For string
schema: adict # For dict
# Meta information
collection: auth_user # for format=`id`
description: "..."
# May be used to define a custom UI component to edit/show the field.
# Any custom config may be added to the field.
meta:
# Examples:
component: ...
language: ...
extension: ...
test_default: ...
skip_auto_tests: true/false
comment: ...
alphabet: ...
# Modifiers
modifiers: # For String: upper, lower, capitalize, title, slugify, slugify_
modifiers: # For List: sort, remove_duplicates
encrypt: true/false # Type String
obfuscate: true/false # Type String
# DB/Optimizations
index:
unique: true/false
unique_with: [field1, field2]
load_on_search: true/false # The field won't be loaded when searching multiple documents
load_reference_data: true/false # for format=`id`
# Authorization
acl_write: admin/staff/vuser/user/public/owner/only_owner
acl_read: admin/staff/vuser/user/public/owner/only_owner
Multi Tenant Schemas
Multi Tenant Schemas - apps/subapp/schemas/AnyModel.yaml
name: Product
acl:
create: staff
read: user
update: staff
delete: admin
meta:
fieldsets: [product, price]
multi_tenant: true
fields:
# ...
Multi Tenant/User Schemas
Multi Tenant/User Schemas - apps/subapp/schemas/AnyModel.yaml
name: ProductFeedback
acl:
create: user
read: user
update: staff
delete: admin
fields:
# ...
Documents
Importing
Dynamic approach:
Import system - apps/subapp/any_module.py
# For usage (any module) -
from apiup import Doc
Product = Doc('Product')
Typed approach:
Import system - apps/subapp/any_module.py
# Basic data/model documents (any module)
from base_docs import ProductData, UserData
# For models with enhanced logic and services (app's products/docs.py)
from your_app_product.docs import Product
Common Operations
Common Operations - apps/subapp/any_module.py
from apiup import Doc
User = Doc('User')
Product = Doc('Product')
doc: Product = Product()
doc.get_data()
doc.assign(assign_from_user=ctx.user)
await doc.save(by=ctx.user)
await doc.delete(by=ctx.user)
await doc.refresh(by=ctx.user)
await doc.push(by=ctx.user, field='some field', value='some value')
await doc.increment(by=ctx.user, field='some field', value=10)
await doc.load_lazy_fields()
product_user: User = await doc.ref('user_id')
Custom Document Logic
Document Logic - apps/subapp/docs.py
from base_docs import ProductData
class Product(ProductData):
def is_active():
return self.status == 'active'
async def set_as_active():
self.status = 'active'
await self.save()
async def pre_create(self) -> None: pass
async def pos_create(self) -> None: pass
async def pre_update(self) -> None: pass
async def pos_update(self) -> None: pass
async def pre_delete(self) -> None: pass
async def pos_delete(self) -> None: pass
# Usage:
from your_app.docs import Product
p = Product()
print(p.is_active())
await p.set_as_active()
Document Manager
Document Manager - apps/subapp/any_module.py
from apiup import Q
# Searching a single Document
exists: bool = await Product.manager().exists(user=ctx.user, where=(Q.field1 == 'value1'))
doc: MyDoc | None = await MyDoc.manager().find_one(user=ctx.user, where=Q.field1 != 'value2')
doc: MyDoc | None = await MyDoc.manager().get_by_id('some id', user=ctx.user, cache_ttl=60)
doc: MyDoc | None = await MyDoc.manager().get_by(user=ctx.user, field1='value1', field2='value2', cache_ttl=60)
# It raises 404 Not Found error if no document was found
doc: MyDoc = await MyDoc.manager().get_by_id_or_raise('some id', user=ctx.user, cache_ttl=60)
doc: MyDoc = await MyDoc.manager().get_by_or_raise(user=ctx.user, field1='value1', field2='value2', cache_ttl=60)
doc: MyDoc = await MyDoc.manager().get_or_create(user=ctx.user, field1='value1', field2='value2', defaults=dict(field3='value3'))
doc: MyDoc = await MyDoc.manager().update_or_create(user=ctx.user, field1='value1', field2='value2', defaults=dict(field3='value3'))
# Searching multiple Documents - Cheat Sheet
docs: list[MyDoc] = await MyDoc.manager().all(user=ctx.user, limit=10, cursor_id=None)
docs: list[MyDoc] = await MyDoc.manager().find(user=ctx.user, where=Q.field1 >= 3, sort_by=['id'], limit=10, cursor_id=None)
docs: list[MyDoc] = await MyDoc.manager().text_search(
'some word',
user=ctx.user,
fields=['name', 'username'],
where=Q.active == True,
sort_by=['id'],
limit=10,
cursor_id=None,
)
total: int = await MyDoc.manager().count(user=ctx.user, where=Q())
Custom Document Manager
Custom Managers - apps/subapp/docs.py
from base_docs import ProductData
class Product(ProductData):
@classmethod
async def find_active(cls) -> list['Product']:
return await Product.manager().find_by(status='active')
# Usage:
print(await Product.find_active())
or
Custom Managers - apps/subapp/docs.py
from base_docs import ProductData
class ProductServices:
async def find_active(self) -> list['Product']:
return await Product.manager().find_by(status='active')
class Product(ProductData):
services: ProductServices = ProductServices()
# Usage:
print(await Product.services.find_active())
Tasks
Raw Tasks
Raw Tasks - apps/subapp/tasks.py
from apiup import Task
class ProductTask(Task):
task_params: dict[str, Field] = dict(field=Field(type='string'))
async def process(self, **kwargs):
# Update the progress manually
self.progress = 10 # 10%
# Logic added here...
return 'ok'
Iterative Tasks
Iterative Tasks - apps/subapp/tasks.py
from apiup import Task
# Task progress calculated automatically
class ProductTask(Task):
async def setup(self, **kwargs: typing.Any) -> None:
pass
async def get_items(self) -> list[typing.Any]:
# Return list of objects to be processed
return []
# self.task_id available
async def process_item(self, item) -> None:
# process each item returned by get_items individually.
pass
async def finalize(self, task_report: Task.JSONType = None) -> Task.JSONType:
# execute in the end of processing
return None
async def tear_down(self) -> None:
# the last/finally execution (execute it even in case of exceptions)
pass
Task Dispatch
Task Dispatch - apps/subapp/tasks.py
# Trigger/Enqueue the ProductTask with the correspondent parameters to be executed in a Worker machine
await ProductTask.dispatch(param1=None, param2=None)
# Execute the task immediately
await ProductTask().execute_task(param1=None, param2=None)
Queries
Warning
- Always use parenthesis in query expressions for clarity and correctness.
- Remember to create DB indexes to avoid full collection scan.
Tip
To create re-usable and easy to test queries, encapsulate them in methods of a Services class.
from apiup import Q
Q()
Q.field
Q('field')
# Query comparison operators
Q.field == value
Q.field != value
Q.field > value
Q.field >= value
Q.field < value
Q.field <= value
# Query logical operators
(Q.field > value1) | (Q('field2') < value2) # OR
(Q.field > value1) & (Q.field2 < value2) # AND
Q.field._nor(Q()) # NOR
Q()._not() # NOT
# Query search operators
Q.field._in(['value1', 'value2'])
Q.field.pattern('value1')
Q.field.ipattern('value1')
Q.field.iequal('value1')
Q.field.isearch('value1')
Q.list_field.contains('text')
Q.field.contains_text('text')
Q.field.contains_all([item1, item2])
Q.field.has_size(2)
Q.field.exists()