Automatic REST generation¶
Common REST scenarios include CRUD interaction with a database object (a view, a table or a predefined query); these often involve a RequestRecord with request validation rules, a DTO Record representing the database object, a Service that implements domain logic, and obviously a RestView to implement the different endpoints.
Pokie's rest.Auto helpers leverage the existing Code Generation mechanisms to generate in runtime the required classes for a barebones REST implementation. These classes can be incrementally overridden later, making them ideal in PoC and simple use cases. They can also be used to perform rapid prototyping - implementations can be done incrementally, starting with automatic helpers and gradually implementing the specific business logic.
Note: The rest.Auto database functionality is only available for PostgreSQL databases. Some data types may not work well, and complex schemas may not be fully supported.
Note: Code generated classes always use id as the name representing the primary key field on the database - e.g. a table with a field id_foo will be referenced on the DTO Record and the RequestRecord as id, and not id_foo
Automatic REST from DTO¶
This mechanism enables the automatic creation of a RestView object based on a DTO Record representing a database object. It will automatically generate a Request object if none is specified, based on the existing database table referenced by the DTO Record. The generated View class can also extend either a custom base class or a set of mixins.
Method signature¶
Auto.rest(app: object, slug: str, dto_record: object, request_class: RequestRecord = None, service: str = None, id_type: str = None, search_fields: list = None, allow_methods: list = None, base_cls: tuple = None, mixins: tuple = None, **kwargs)
Parameter | Type | Description |
---|---|---|
app | Flask | Flask object |
slug | str | A endpoint url slug |
dto_record | RickDB DTO Record | A DTO Record to use |
request_class | Rick RequestRecord | An optional RequestRecord to use for input values |
service | str | An optional service to use instead of the automatically generated one |
id_type | str | The type of the record id for the urls, supported by Flask; defaults to "int" |
search_fields | list | Optional list of fields to perform text search; if omitted, all varchar/text fields are used |
allow_methods | list | If specified, will only allow the specified http methods |
base_cls | class | Optional base class to use instead of pokie.rest.RestView |
mixins | tuple | Optional tuple with additional mixins |
Usage example¶
A complete minimal application to expose a database table called customers as a REST endpoint:
from rick_db import fieldmapper
from rick.resource.config import EnvironmentConfig
from pokie.config.template import BaseConfigTemplate, PgConfigTemplate
from pokie.core import FlaskApplication
from pokie.core.factories.pgsql import PgSqlFactory
from pokie.rest.auto import Auto
@fieldmapper(tablename="customers", pk="customer_id")
class CustomerRecord:
id = "customer_id" # this field is actually a varchar
company_name = "company_name"
contact_name = "contact_name"
contact_title = "contact_title"
address = "address"
city = "city"
region = "region"
postal_code = "postal_code"
country = "country"
phone = "phone"
fax = "fax"
# config parameters, injectable from ENV vars
class Config(EnvironmentConfig, BaseConfigTemplate, PgConfigTemplate):
pass
# Our custom route initialization to skip usage of modules
# because Auto.rest() will require database access to the table customers, we need to postpone route registration
# to the actual web initialization routines, as it is done with modules
def router(p: FlaskApplication):
# Auto.rest() will generate all required classes and register the following routes:
# /customer OPTIONS,HEAD,GET
# /customer/<string:id_record> OPTIONS,HEAD,GET
# /customer POST,OPTIONS
# /customer/<string:id_record> OPTIONS,PUT,PATCH
# /customer/<string:id_record> OPTIONS,DELETE
Auto.rest(p.app, # our Flask app
"customer", # the base slug
CustomerRecord, # the DTO to use
search_fields=[CustomerRecord.company_name, CustomerRecord.contact_name], # fields to allow text search
id_type="string" # type of id_record to use
)
def build_pokie():
# load configuration from ENV
cfg = Config().build()
# modules to load & initialize
modules = []
# factories to run
factories = [PgSqlFactory, ]
# build app
pokie_app = FlaskApplication(cfg)
flask_app = pokie_app.build(modules, factories)
# register our route initializer to be used only on web context
pokie_app.register_pre_http_hook(router)
return pokie_app, flask_app
main, app = build_pokie()
# =============================================================================
if __name__ == '__main__':
main.cli()
While it is possible to build single-file applications with Pokie, the most common scenario is to build modular applications. When using modules, Auto.rest is commonly used in the build() section of your module.py:
from pokie.core import BaseModule
from pokie.rest.auto import Auto
from pokie_test.dto.records import CustomerRecord
class Module(BaseModule):
# internal module name
name = "my_module"
(...)
def build(self, parent=None):
app = parent.app
# Auto.rest() will generate all required classes and register the following routes:
# /customer OPTIONS,HEAD,GET
# /customer/<string:id_record> OPTIONS,HEAD,GET
# /customer POST,OPTIONS
# /customer/<string:id_record> OPTIONS,PUT,PATCH
# /customer/<string:id_record> OPTIONS,DELETE
Auto.rest(app, # our Flask app
"customer", # the base slug
CustomerRecord, # the DTO to use
# fields to allow text search
search_fields=[CustomerRecord.company_name, CustomerRecord.contact_name],
id_type="string" # type of id_record to use
)
(...)
Automatic REST from Database Table¶
It is possible to just skip the usage of a DTO Record and have the framework build one instead in runtime, from an existing database table; Auto.view() will generate a RestView class for the specified table, that can be registered using the AutoRouter or traditional Flask route registration mechanisms.
Method signature¶
Auto.view(app: object, table_name: str, schema: str = None, search_fields: List = None, camel_case: bool = False, allow_methods: list = None, base_cls: tuple = None, mixins: tuple = None, **kwargs) -> PokieView:
Parameter | Type | Description |
---|---|---|
app | Flask | Flask object |
table_name | str | Database table name to use |
schema | str | Optional database schema |
search_fields | list | Optional list of fields to perform text search; if omitted, all varchar/text fields are used |
camel_case | bool | If true, dict keys are camelCased |
allow_methods | list | If specified, will only allow the specified http methods |
base_cls | class | Optional base class to use instead of pokie.rest.RestView |
mixins | tuple | Optional tuple with additional mixins |
Usage example¶
A complete minimal application to expose a database table called customers as a REST endpoint:
from rick.resource.config import EnvironmentConfig
from pokie.config.template import BaseConfigTemplate, PgConfigTemplate
from pokie.core import FlaskApplication
from pokie.core.factories.pgsql import PgSqlFactory
from pokie.http import AutoRouter
from pokie.rest.auto import Auto
# config parameters, injectable from ENV vars
class Config(EnvironmentConfig, BaseConfigTemplate, PgConfigTemplate):
pass
# Our custom route initialization to skip usage of modules
# because Auto.rest() will require database access to the table customers, we need to postpone route registration
# to the actual web initialization routines, as it is done with modules
def router(p: FlaskApplication):
# Auto.view() will generate a view for the customers table
view = Auto.view(app, "customers", search_fields=["company_name", "contact_name"])
# and AutoRouter.resouce() registers the following endpoints:
# /customer HEAD,GET,OPTIONS
# /customer/<string:id_record> HEAD,GET,OPTIONS
# /customer OPTIONS,POST
# /customer/<string:id_record> PATCH,PUT,OPTIONS
# /customer/<string:id_record> DELETE,OPTIONS
AutoRouter.resource(p.app, "customer", view, id_type="string")
def build_pokie():
# load configuration from ENV
cfg = Config().build()
# modules to load & initialize
modules = []
# factories to run
factories = [PgSqlFactory, ]
# build app
pokie_app = FlaskApplication(cfg)
flask_app = pokie_app.build(modules, factories)
pokie_app.register_pre_http_hook(router)
return pokie_app, flask_app
main, app = build_pokie()
# =============================================================================
if __name__ == '__main__':
main.cli()
While it is possible to build single-file applications with Pokie, the most common scenario is to build modular applications. As it happens with Auto.rest(), Auto.view is commonly used in the build() section of your module.py:
from pokie.core import BaseModule
from pokie.rest import Auto
from pokie.http import AutoRouter
class Module(BaseModule):
# internal module name
name = "my_module"
(...)
def build(self, parent=None):
app = parent.app
# Auto.view() will generate a view for the customers table
view = Auto.view(app, "customers", search_fields=["company_name", "contact_name"])
# and AutoRouter.resouce() registers the following endpoints:
# /customer HEAD,GET,OPTIONS
# /customer/<string:id_record> HEAD,GET,OPTIONS
# /customer OPTIONS,POST
# /customer/<string:id_record> PATCH,PUT,OPTIONS
# /customer/<string:id_record> DELETE,OPTIONS
AutoRouter.resource(p.app, "customer", view, id_type="string")
(...)