Package: python-serverless-crud
python-serverless-crudThe ideaSimple and powerful tool for quick serverless data management via API.Key conceptsDon't Repeat Yourself - easy model definition with schema and cloud formation generation supportBest practices applied by default (created with AWS LambdaPower Tools)Flexibility - enable, extend and modify what is neededOne ring to rule them all - support for REST API, GraphQL (via API Gateway), AppSync GraphQL (direct resolvers)FeaturesFull CRUD support with validationNative support for DynamoDB (including CloudFormation creation via troposphere)GlobalSecondaryIndex supportLocalSecondaryIndex supportPrimary Key with and without sort keysSupport for Scan, Query operations on the tables and indexesVirtual List method on the table or indexIntegrated record owner feature with KeyCondition and FilterCondition support (auto-detect)DocumentationSample servicefromaws_lambda_powertoolsimportTracerfromaws_lambda_powertools.loggingimportcorrelation_pathsfromserverless_crudimportapifromserverless_crud.dynamodbimportannotationasdbfromserverless_crud.modelimportBaseModelfromserverless_crud.loggerimportloggertracer=Tracer()@db.Model(key=db.PrimaryKey(id=db.KeyFieldTypes.HASH),indexes=(db.GlobalSecondaryIndex("by_user",user=db.KeyFieldTypes.HASH,created=db.KeyFieldTypes.RANGE),),owner_field="user")classDevice(BaseModel):id:strcreated:intuser:str=Noneapi.rest.registry(Device,alias="device")@logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_REST)@tracer.capture_lambda_handlerdefrest_handler(event,context):returnapi.rest.handle(event,context)With just a few lines of the code we are able to createDevicemodel service which then can be extended.
In this example we:Defined ourDevicemodel with some extra metadata, used by our generators. That includes:Table key definitionGlobalSecondaryIndexDefinition of the field which will holdownerof the record (identity provided by cognito)Registered ourDevicemodel into rest API underdevicealiasCreated rest handler which then can be referred in ourserverless.ymlfileA few notes here:we need to definerest_handlerfunction if we would like to use it as a target for local execution with serverless freameworkLambda Power Tools are build around functions and they don't work properly with object methodsWe use one function per API type, and we relay on internal router provided by each API implementationServerless integrationIf you use (serverless-builder)[https://github.com/epsyhealth/serverless-builder] you can create yourserverless.ymlwith just a few lines of code (including DynamodbTables)fromserverlessimportService,Configurationfromserverless.aws.featuresimportXRayfromserverless.aws.functions.httpimportHTTPFunctionfromserverless.pluginsimportPythonRequirements,Prunefromserverless.providerimportAWSProviderfromtroposphereimportdynamodbfromtimemachine_api.handlersimportapiservice=Service("timemachine-api","Collect events in chronological order",AWSProvider(),config=Configuration(domain="epsy.app"))service.provider.timeout=5service.plugins.add(Prune())service.plugins.add(PythonRequirements(layer=False,useStaticCache=False,dockerSsh=True))service.enable(XRay())forname,table_specificationinapi.dynamodb_table_specifications().items():service.resources.add(dynamodb.Table(name,**table_specification))authorizer=dict(name="auth",arn="arn:aws:cognito-idp:us-east-1:772962929486:userpool/us-east-1_FCl7gKtHC")service.builder.function.http("rest","Time machine REST API","/rest/{proxy+}",HTTPFunction.ANY,handler="timemachine_api.handlers.rest_handler",authorizer=authorizer)service.render()InternalsAnnotationsserverless-crudproject provides one annotation which must be used for all managed models.fromserverless_crud.dynamodbimportannotationasdb@db.Model(key=db.PrimaryKey(name=db.KeyFieldTypes.HASH),indexes=(db.GlobalSecondaryIndex(...),db.LocalSecondaryIndex(...)),owner_field="field")Model annotation accepts:key- primary key definition, in form ofkwargswhere name of parameter would be a field name which should
be used  in key, and value should be a value ofKeyFieldTypesenumindexes- list of indexes GlobalSecondaryIndex|LocalSecondaryIndex. Indexes are defined in same way as primary keyowner_field- name of the field which should be used for data filtering (based on the cognito identity)Data ownerserverless-crudcan enforce some base data filtering on all kind of operations using Dynamodb conditional operations.
If you would like to use this feature you must setowner_fieldon each model you would like to use this feature.Library will use this field for:setting its value on model creation / update (it will overwrite any value provided by user)as an extraConditionExpressionduringGETandDELETEoperationsas a part of eitherFilterExpressionorKeyExpressionfor Scan, Query and List operationsModel registrationTo be able to manage given model, you must first register it with specific API.
This can be done with a single line of code:api.rest.registry(Device,alias="device")You need to provide only a model type toregistrymethod, all other parameters are optional.
If you like, you can omitaliasparameter, in that case framework will use model class name.Customizing endpoint behaviourFramework defines a set of classes located inserverless_crud.actions:CreateActionDeleteActionGetActionScanAction, ListAction, QueryActionUpdateActionall those classes are subclasses ofserverless_crud.actions.base.Actionclass and can be extended if needed.You may need to execute custom logic after object creation, that can be done with customCreateActionsubclassfromserverless_crud.actionsimportCreateActionclassCreateDeviceAction(CreateAction):defhandle(self,event:APIGatewayProxyEvent,context):super().handle(event,context)# custom logicapi.rest.registry(Device,create=CreateDeviceAction)You can set custom handlers for each supported operation:defregistry(self,model,alias=None,get=GetAction,create=CreateAction,update=UpdateAction,delete=DeleteAction,lookup_list=ListAction,lookup_scan=ScanAction,lookup_query=QueryAction):As you can see, all actions are defined by default. That also means that all actions are enabled by default, but
each action can be disabled.If you need to disable action you need to set action handler toNone, that will prevent framework from creating
route for given action, and it will disable whole logic behind it.RoutesREST API specific feature.Framework will create multiple routes for each register model, usingaliasas a URL namespace.
Generated routes:GET /rest/{alias}/{pk} - fetch object by PK (see notes about PK below)POST /rest/{alias} - create new recordPUT /rest/{alias}/{pk} - update record with given PKDELETE /rest/{alias}/{pk} - delete record with given PKGET /rest/lookup/{alias}/list - list all the records of given type using Query on the tableGET /rest/lookup/{alias}/list/{index_name} - list all the records of the given type using Query on specific indexPOST /rest/lookup/{alias}/query - perform a query on given tablePOST /rest/lookup/{alias}/query/{index_name} - perform a query on given indexPOST /rest/lookup/{alias}/scan - perform a scan on given tablePOST /rest/lookup/{alias}/scan/{index_name} - perform a scan on given indexPrimary KeysPlease remember that with DynamoDB key is a Partition Key with optional Sort Key.
In case you define Sort Key DynamoDB will require a value for it while getting / deleting key.
In that case framework will modify routes to include sort key as an extra path parameterEndpoints