Package: nanopie
nanopieOverviewnanopie is a lightweight Python framework for writing microservices and API
backends. The framework provides pluggable solutions for a number of tasks,
such as input validation, serialization/deserialization, authentication,
logging, and tracing, commonly encountered in microservice and API backend
development.Note: nanopie in still in Development. At this moment, nanopie supports
only HTTP (RESTful) microservices/API backends.FeaturesBring your own transportPython has a large number of web framework + server solutions, suchflask+gunicorn,quart+uvicorn,
andTornado; many of these options are
mature, time-tested, and widely-adopted, and it is not nanopie's design
goal to create another system for processing requests. Instead, nanopie
leaves the request processing part up to a transport of your choice.
This allows developers to use nanopie with any web framework and
server they are most familiar with; it is also possible to
switch transports at will as you see fit without having to drastically
change the code.Data modeling and validationnanopie provides a data modeling mechansim, based on metaclasses
in Python (PEP 3115),
that helps developers model the inputs and outputs to the endpoints of
their microservices and API backends, making it easy to access data
idiomatically. nanopie models also features automatic data validation,
saving the trouble of writing additional checking logic in the code.fromnanopieimportModel,IntField,StringFieldclassUser(Model):name=StringField(max_length=20,min_length=1,pattern="[a-zA-Z]*")age=IntField(maximum=100,minimum=0)user=User(name="Albert Wesker",age=49)print(user.name)print(user.age)# This will raise an exceptionuser=User(name="Link in BotW",age=117)In addition, nanopie models work seamlessly with the built-in
serialization/deserialization solution; once configured, nanopie
can automatically parse incoming requests into data model objects,
and return data model objects as responses in interchangable formats.Pluggable authentication, serialization, logging, and tracing solutionsnanopie provides a number of solutions for common tasks in microservices and
API backend development, known as handlers. You can add handlers to the
endpoints of your microservices and API backends so that they can perform
the tasks when requests arrive at the endpoint:HTTPBasicAuthenticationHandler, for example, authenticates requests
based on theBasicHTTP Authentication Scheme
(RFC 7617),
andOpenTelemetryTracingHandler, for another example, traces the execution
of your endpoint and writes the trace toSTDOUT/STDERRevery time a
request is being processed.Handlers can be chained together to execute a sequence of actions.
A typically pattern is to chain an authentication handler,
a logging handler, a tracing handler, a serialization handler together
for an endpoint, so that when a request arrives at the endpoint,
it is authenticated, logged, and traced by respective handlers, with its
payload deserialized into an object of nanopie data model automatically.InstallationTo install nanopie, run the following command:pipinstallnanopieGetting startedThe following example showcases how to use nanopie to write a simple
microservice with RESTful HTTP API for user management with one
endpoint,get_user, which returns a user with a specific user ID.SetupInstallnanopieandflask, a popular Python web micro-framework in your
project. AFlaskapp will serve as the transport of yournanopiemicroservice in this example:pipinstallnanopieflaskDefining the data modelThe microservice you will build in this example, as introduced at the beginning
of this section, has only one endpoint, which returns the information of
a specific user to clients. To model the user information, create auser.pyscript in your project with the followingnanopiedata model:fromnanopieimport(Model,IntField,StringField)classUser(Model):name=StringField(max_length=20,min_length=1,pattern="[a-zA-Z]*")age=IntField(maximum=100,minimum=0)TheUsermodel includes two fields,name, andage, which takes aStringvalue and aIntegervalue respectively.Note that both fields have hints and constraints specified:Thenamefield must be an alphabetic string with a maximum of 20
characters and a minimum of 1 character.Theagefield must be an integer no smaller than1and no greater
than100.nanopiecan use these hints and constraints to validate data.Writing the microserviceCreate a Python script,main.py, as follows in your project. The script
first creates a Flask app, then creates aFlaskServiceobject that nanopie
provides for using a Flask app as the transport of your microservice. TheFlaskServiceobject includes a number of methods (decorators) for specifying
HTTP microservice or API backend endpoints; in the case of this project, you
will create aGETendpoint which returns aUserobject as defined by
theUsermodel detailed in the previous section.fromflaskimportFlaskfromnanopieimport(FlaskService)# Import the User model created in the last stepfromuserimportUser# Create a Flask appapp=Flask(__name__)# Create a nanopie microservice with the Flask app as transportsvc=FlaskService(app=app)# Create a HTTP RESTful API endpoint with the `GET` HTTP method@svc.get(name="get_user",rule="/users/<int:uid>")defget_user(uid):# Always return the same user regardless of the ID providedreturnUser(name="Albert Wesker",age=49)if__name__=="__main__":app.run(debug=True,port=8080)Run the script:pythonmain.pyOpen the browser of your system and visit127.0.0.1:8080/users/1. It should
return the following JSON string:{"name":"Albert Wesker","age":49}Adding authentication capabilitiesAt this moment your microservice accepts any request from the network. To
protect data from unauthorized sources, you should authenticate
requests in the microservice. nanopie provides a number of pluggable
authentication solutions, known as authentication handlers, that perform
the authentication of requests automatically; for this project, you will
enable HTTP OAuth2 Bearer Token with JWT (RFC 6750,RFC 7519) based authentication in the
service using the
authentication handlerHTTPOAuth2BearerJWTAuthenticationHandler.To enable authentication, editmain.pyand create
aHTTPOAuth2BearerJWTAuthenticationHandler:fromflaskimportFlaskfromnanopieimport(FlaskService,HTTPOAuth2BearerJWTModes,HTTPOAuth2BearerJWTAuthenticationHandler)# Import the User model created in the last stepfromuserimportUser# Create a Flask appapp=Flask(__name__)# Create an authentication handlerauthentication_handler=HTTPOAuth2BearerJWTAuthenticationHandler(# The secret used for generating JWT tokenskey_or_secret='my-secret',# The algorithm used for generating JWT tokensalgorithm='HS256',# Specify that the token comes in the URI query string of the HTTP requestmode=HTTPOAuth2BearerJWTModes.URI_QUERY)# Create a nanopie microservice with the authentication handler just createdsvc=FlaskService(app=app,authn_handler=authentication_handler)...Run the script again:pythonmain.pyOpen the browser of your system and visit127.0.0.1:8080/users/1. Without
a valid token, your service should return a401 Unauthorizederror.You can generate a valid token using the algorithm and secret specified in
the code snippet. There are many Python packages available for this purpose;
you can also usejwt.ioto get a token for testing
purposes interactively. Alternatively, use the token below:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.EpM5XBzTJZ4J8AfoJEcJrjth8pfH28LWdjLo90sYb9gAnd point your browser to127.0.0.1/users/1?access_token=YOUR-ACCESS-TOKEN(replaceYOUR-ACCESS-TOKENwith a token of your own). Your request is now
authenticated and you should see the user data returned to you.Adding logging and tracing capabilitiesIt is common for developers to use logging and tracing to observe their
microservices and API backends in action. Similar to authentication
handlers, nanopie provides a number of pluggable logging and tracing solutions
as well, integrating with thestandard Python logging moduleandOpenTelemetryrespectively.To enable logging and tracing, editmain.pyfor another time and create
aLoggingHandlerandOpenTelemetryTracingHandler:fromflaskimportFlaskfromnanopieimport(FlaskService,HTTPOAuth2BearerJWTModes,HTTPOAuth2BearerJWTAuthenticationHandler,LoggingHandler,OpenTelemetryTracingHandler)# Import the User model created in the last stepfromuserimportUser# Create a Flask appapp=Flask(__name__)# Create an authentication handler...# Create a logging handler and a tracing handler.logging_handler=LoggingHandler()tracing_handler=OpenTelemetryTracingHandler()# Create a nanopie microservice with an authentication handler,# a logging handler, and a tracing handler.svc=FlaskService(app=app,authn_handler=authentication_handler,logging_handler=logging_handler,tracing_handler=tracing_handler)Run the script for another time:pythonmain.pyOpen the browser of your system and visit127.0.0.1/users/1?access_token=YOUR-ACCESS-TOKEN. Check the output
in the terminal running the Python script, you should see outputs similar
to the snippets below:# The log entry nanopie writes when the execution of an endpoint begins.
{"host": "YOUR-HOST", "logger": "nanopie.logging.base", "level": "INFO", "module": "base", "func": "__call__", "message": "Entering span unspecified_span."}

# The trace of the execution of the endpoint.
{"name": "unspecified", "context": {"trace_id": "7732924096377129845968717310647764965", "span_id": "7510401874971537686", "trace_state": "{}", "is_remote": "False"}, "kind": "SpanKind.SERVER", "parent": null, "start_time": "2020-08-11T20:23:01.350515Z", "end_time": "2020-08-11T20:23:01.351445Z", "attributes": "{}"}

# The long entry nanopie writes when the execution of an endpoint completes.
{"host": "YOUR-HOST", "logger": "nanopie.logging.base", "level": "INFO", "module": "base", "func": "__call__", "message": "Exiting span unspecified_span."}What's nextLearn more about nanopie in itsdocumentation.