TechMicroservices¶
TechMicroservices are the ‘atoms’ of the CoWorks microservices framework. They represent the building blocks for other more complex ‘compound’ microservices.
The microservice class can be defined as a class inheriting from TechMicroService
:
class SimpleMicroService(TechMicroService):
pass
Routing¶
Routes are defined by decorated functions in the microservice class.
The decorator is:
@entry
The function names must follow the syntax below:
<request_method>_<associated_route>
The request method is then defined for the associated route.
Composed routes are defined with the _
separator.
For example, get_content
would define the request method GET
for route /content
.
Please see more examples in below section:
Examples¶
The following function defines the GET method for the root path of the microservice:
@entry
def get(self):
return "root"
The following function defines the GET method for the route /service/test
:
@entry
def get_service_test(self):
return "service test"
The following function defines the PUT method for the root path:
@entry
def put(self):
return "put"
Entrypoint¶
URI Parameters¶
You can define entrypoint function arguments as part of the URI:
@entry
def get_content(self, index):
return f"get content {index}"
This defines a route which first part is the fixed string content
and the second part is the index
value.
We denote the route like this:
/content/{index}
You can have several positional parameters (ordered from the URL path):
@entry
def get_content(self, bucket, key1, key2, key3):
return f"get content of {bucket} with key {key1/key2/key3}"
which defines the route /content/{bucket}/{key1}/{key2}/{key3}
.
Note: In this case, the keys parameters must not have the /
character.
You can also construct more complex routes from different parameters:
@entry
def get_content(self):
return "get content"
@entry
def get_content_(self, value):
return f"get content with {value}"
@entry
def get_content__(self, value, other):
return f"get content with {value} and {other}"
This defines the respective following routes:
/content
/content/{value}
/content/{value}/{other}
This is useful for offering a CRUD microservice:
@entry
def get(self):
return "the list of instances of a model"
@entry
def get_(self, id):
return f"the instance with id {id}"
@entry
def post(self, data):
return f"creates a new instance with {data}"
@entry
def put(self, id, data):
return f"modifies an instance identified by {id} with {data}"
Query or body parameters¶
You can define default parameters to your entry point function. In that case the value of those default parameters are defined by query parameters or JSON content.
@entry
def get_content(self, id=None, name=""):
return f"the instance with id {id} and/or name {name}"
Where the id
parameter can be defined by the query parameter:
/content?id=32&name=test
Or in python code using the requests
module:
requests.get("/content", params={"id": 32, "name": "test"})
or by a JSON structure (for POST/PUT method):
request.post("/content", json={"id": 32, "name": "test"})
Beware: With API gateway you can only use query parameters for a GET
method. Attempting
to send data in the request body for a GET
request will raise an error in execution.
You can also use the **
notation to get any values:
@entry
def get_content(self, **kwargs):
return f"here are all the parameters: {kwargs}"
For more information on how to use keyword arguments in Python, see this useful article
Note: The current implementation doesn’t take into account the typing of the entry point function parameters
(forcasted in a future release).
So all query parameters are from type string
. If you want to pass typed or structured values, use the JSON mode.
Typed parameters¶
You can specify the type of your URI parameters or data query in order to get more control on your parameters. You can use basic types, List, or Union.
@entry
# id of type int
def get(self, id:int):
return f"the type of id is int: {type(id)}"
@entry
# flag of type bool with default value None
def get_(self, flag:bool = None):
"""Take care True for 'true', '1' or 'yes' values, not bool(str)."""
return f"the type of flag is bool : {type(flag)}"
@entry
# ids of type list(int) with default value None
def get_(self, ids:List[int] = None):
"""Avoid to deal with one id or more ids, always a list."""
return f"the type of ids is list of int : {type(ids)}"
Entrypoints¶
The entries define routes with the following format :
For an app entry : {function_name}
For an blueprint entry : {blueprint_name}.{function_name}
For example
url_for('get') # app root entry
url_for('manager.get_dashboard') # get_dashborad entry for the manager blueprint
Binary type¶
the current AWS ApiGateway integration with Lambda doesn’t allow to defined the content type in response header so the caller must know in advance the returned content type ot the entry.
from coworks import TechMicroService
from coworks import entry
class SimpleExampleMicroservice(TechMicroService):
@entry(binary_headers={'Content-Type': 'application/octet'})
def get_content_type(self):
return b"test"
@entry(no_auth=True, binary_headers={
'Content-Type': 'application/zip','Content-Disposition': 'attachment; filename=plugins.zip'
})
def get_zip(self):
return send_from_directory('assets', "plugins.zip", conditional=False)
Response¶
Flask
automatically converts return values from a class microservice into a response
object for you.
If the return value is a
string
orbytes
, it’s converted into a response object with the string or bytes list as response body, a 200 OK status code and aapplication/json
mimetype.If the return value is a
dict
or alist
, it’s converted to a JSON structure, a 200 OK status code and aapplication/json
mimetype.If a
tuple
is returned the items in the tuple can provide extra information. Such tuples have to be in the form (response, status), or (response, status, headers). The status value will override the status code and headers can be a list or dictionary of additional header values.
Nevertheless we strongly recommend to use only JSON structure (str
or dict
) and use werkzeug HttpException
for returning status code. This allows you to easily call your entry from another entry.
Blueprints¶
Blueprints¶
CoWorks blueprints are used to add to your application more routes deriving from logical components. Blueprints allow you to complete your microservices with transversal functionalities.
Blueprints are a part of Flask. To learn more about how Blueprints are implemented and used in Flask, check out Flask Blueprints.
Blueprint Registration¶
Blueprints are defined similarly to microservice classes. However, they will instead
inherit from the CoWorks implementation of the Blueprint
object.
Methods within the class should still be decorated with @entry
.
from coworks import Blueprint
class Admin(Blueprint):
@entry
def get_event(self):
return request.aws_event
This blueprint defines a new route context
. To add this route to your microservice, just register the
microservice, you’ll need to register the blueprint:
app = SimpleExampleMicroservice()
app.register_blueprint(Admin(), url_prefix="/admin")
The url_prefix
parameter adds the prefix admin
to the route event
.
Now the SimpleExampleMicroservice
has a new route /admin/event
.
Predefined Blueprints and Extensions¶
Some blueprints and extensions are defined to help understanding, manipulating and debugging the frameworks.
Admin¶
The admin blueprint adds routes for documentation and description of the microservice.
Profiler¶
The profiler extension adds a middleware to profile the execution of each request. This can help identify bottlenecks in your code that may be slowing down your application.