Package: eve-utils
eve-utilsTemplates and scripts to rapidly spin up a production-ready Eve-based API.Please note:  although I currently use these tools to create production-ready APIs, the tools themselves are still under development.  Use at your own risk.  This doc is under a heavy rewrite.  Information here is correct, but there are gaps and it's messy.IntroductionEveis amazing.  The full power of Flask/Python, optimized for an API over mongodb.  Nice.It does take a bit of work to go from the simple example in the docs...settings={'DOMAIN':{'people':{}}}app=Eve(settings=settings)app.run()...to a production-ready API, with robust exception handling, logging, control endpoints, configurability, (and so much more).eve-utilshelps make some of that work easier.Install eve-utils with pip.pip install eve-utilsGetting StartedAll utilities are accessed through theeve-utilscommand.  You can also useeufor short.Get helpTo see what you can do these tools:eve-utils --helporeu --helpAs the length of your commands grow, at each step you can always add--helpat the end to see your options.  It is always safe to tack on a--helpas it only shows the help text - the command itself is not executed.Quick startNOTE: this 1-2-3 quick start assumes you have a mongodb instance running at localhost.  If you have docker installed, you can do this quickly withdocker run --rm -d -p 27017:27017 --name my-mongo mongowhen you are done, clean up withdocker stop my-mongoGet started with three easy stepsCreate your API (I recommend creating avirtual environmentfirst).  In an empty folder namedmy-apieve-utils api create .Add a resource to the domaineve-utilsresourcecreatepeopleLaunch the APIeve-utilsrunNow, kick the tires of your freshly minted service.  Try it out with the following curl command (or use Postman if you prefer)curlhttp://localhost:2112
curlhttp://localhost:2112/_settings?pretty
curlhttp://localhost:2112/people(notice_itemsisanemptyarray)curl-XPOSThttp://localhost:2112/people-H"Content-type: application/json"-d"{\"name\":\"Michael\"}"curlhttp://localhost:2112/people(nowthereisonepersoninthe_itemsarray)Stop the service (Ctrl+C where the service is running)Command Cheat SheetUseeve-utilsto create and manage several objects that make up your API.  Below are some definitions and tips to help you develop your API service to its fullest.Note:  you can see these commands listed when you typeeve-utils --help.CommandsTermDefinitionapiThis, unsurprisingly, represents the API service itself.  When you runeve-utils api create...a ton of code is generated, comprising your service.  The code wraps start/stop (and other details) in a class calledEveServiceand your app is an instance of this class.EveServiceinherits fromFlaskso your application is a Flask application as much as it is an Eve application.resourceThese are the 'nouns' of your service.  The set of resources you add to your API comprise the servicedomain.  Use the HTTP verbs (GET, POST, DELETE, PATCH, etc.) to operate these resources, and add affordances to enchance state management beyond CRUD.linkThis creates a parent/child relationship between two resources and adds navigation affordances.affordanceIn a Hypermedia API, clients operate your service's state by way of hyperlinks.  When you want to offer your clients the opportunity to do so, you provide a link which when requested with an HTTP verb causes the state change.  See the Hypermedia section below for more details and examples.endpointIn the very unlikely event that you need to provide a capability that does not fit within the constraints of Hypermedia, you can define an arbitrary endpoint.  Use that cautiously, lest the ghost of Roy Fielding haunt you :-)integrationWhen your service needs to use other services (whether remote or installed locally) you may find it convenient to separate the integration logic into its own module.  That whatintegrationis for.  There are some built-in integrations (e.g. to AWS's S3), or you can start with a blank integration and roll your own.setting(coming soon)runThis command launches your service.  Call it anywhere in your service folder structure.Sub-commandsMost of the commands above require a sub-command.  For example,  to useeve-utils api...you need to say what you want to do with the api.To see what sub-commands and options are available:eve-utils command --helpe.g.,eve-utils api --helpSome commands have unique sub-commands, but many share the following:create- create the thing you're talking about, e.g.eve-utils api create...oreve-utils resource create...list- show the things you previously createdremove- removes the thing you previously created.Note - most commands have not yet implementedremoveCommand DetailsapiThere are two commands that run againstapicreateCreates the API service.  This command is best run in an empty folder.Service nameThe first choice you must make is the name of the service.  This is the only required parameter.e.g.,eve-utils create .(the service name will be the name of the folder you are in)eve-utils create whizbang(the service name will bewhizbang)Add-insAfter the name, you can select from several add-ins which enhance your service.  All are optional, and you can choose as many as you wish.  You can add them at create time, or any time later.To see the add-ins available. they are listed as Options when you type:eve utils api create --helpThe add-ins are:Add inDescription-g, --add_gitInitialize a local git repository-d, --add_dockerAdd Dockerfile and supporting files-a, --add_authadd authorization class and supporting files-v, --add_validationadd custom validation class that you can extend-w, --add_web_socketadd web socket and supporting files-s, --add_serverlessEXPERIMENTAL: add serverless framework and supporting filesNOTE: You will find more details on each add-in in the next section.You can mix and match these add-ins, e.g.,eve-utils api create foobar --add_docker --add_gitThis is the same aseve-utils api create foobar -dgIf you want all add-ins, the easiest way is:eve-utils api create foobar -davwsgNOTE: when you select --add_git, it will always be added last as it performs the initial commit for you.  This way all the add-ins that are installed first will be part of the commit.addinIf you didn't select an add-in when you created the API, you can always select it later with theaddincommand.In other words...eve-utils api create foobar --add_validation...is the same as...eve-utils api create foobar
...
...
eve-utils api addin --add_validation...no matter how much time passes between those two statements.All of the add-ins were introduced in the section above.  This section provides more details:--add_gitdetails--add_dockerAdds the following files:Dockerfile`` ``docker-compose.yml(note: by default this file does not use a volume for mongodb, so killing the container also kills your data).docker-ignore`` ``image-build`` ``image-build.bat...--add_authAdds a folder namedauthwith modules to add authorization to your API (docs to come)NOTE: the only supported IdP isAuth0at the moment, but it will be fairly easy to manually tweak to use any OAuth2 JWT issuer. (I have used a forkedGlewlwydwith very minimal changes)--add_validationadds a folder namedvalidationwith a module that adds custom validator toEveService.  Use this to extend custom validations.  It comes with two validations:unique_ignorecase- works exactly like the built-inuniquevalidator except case is ignoredunique_to_parent- set this to a string of a resource's parent (singular!).  Uniqueness will only be applied to sibling resources, i.e. the same name can be used if the resource has a different parent.e.g.eve-utilsresourcecreateregion
eve-utilsresourcecreatestore
eve-utilslinkcreateregionstoreNow indomain.store, change the name field definition from this:'name": {'type':'string',...'unique':True}to this:'name": {'type':'string',...'unique_to_parent':'region'}--add_web_socketDefine other events/listeners, emitters/senders inweb_socket/__init__.py- feel free to remove the default stuff you see thereThere is a test client at{{BASE_API_URL}}/_ws(which you can remove inweb_socket/__init__.pyby removing the/_ws/chatroute)This is useful to see how to configure the Javascript socket.io client to connect to the web socket now running in the APIIt is also useful to test messages - the chat app merely re-emits what it receives--add_serverlessAdds the following files:serverless.py- instantiates, but doesn't run, the Eve app object.  This object is made available to the serverless framework and is referenced in the.ymlfilesserverless-aws.ymlserverless-azure.ymlserverless-google.ymllogging_no-files.yml- copy this over the originallogging.ymlto eliminate logging to the file system (which is not available with serverless)Also installs serverless globally with npm, does an npm init in the root api folder, and locally installs some serverless plugins (node modules).resourceintrocreateaddsresource-nameto the domaindefault fields are name, descriptionadd fields by modifying domain/resource-name.py - as you would any Eve resourceNOTE: resources in Eve are collections, so eve-utils names resources as plural by convention,i.e. if you enter mkresourcedogit will create an endpoint named/dogseve-utils rely on theinflectlibrary for pluralization, which is very accurate but can make mistakeslistdetailsremovedetailslinkUselinkto manage parent/child relationships amongst resources.createFor example:eve-utilsresourcecreateperson
eve-utilsresourcecreatecars
eve-utilslinkcreatepersoncaryou could also have typedeve-utils link create people carsoreve-utils link create person cars- they all are equivalentIf you followed the example above, you have already POSTed a person named Michael:curl -X POST http://localhost:2112/people -H "Content-type: application/json" -d "{\"name\":\"Michael\"}"Normally GET a person by_id.eve-utilswires up the name field as anadditonal_lookup, so you can also GET by name.curl http://localhost:2112/people/Michael?pretty{"_id":"606f5453b43a8f480a1b8fc6","name":"Michael","_updated":"2021-04-08T19:06:59","_created":"2021-04-08T19:06:59","_etag":"6e91d500cbb0a2f6645d9b4dced422d429a69820","_links":{"self":{"href":"/people/606f5453b43a8f480a1b8fc6","title":"person"},"parent":{"title":"home","href":"/"},"collection":{"title":"people","href":"people"},"cars":{"href":"/people/606f5453b43a8f480a1b8fc6/cars","title":"cars"}}}Notice the_linksfield includes a rel namedcars.  You can POST a car to thathref(I'll demonstrate with Javascript):constaxios=require('axios')axios.defaults.baseURL='http://localhost:2112'axios.get('/people/Michael').then((response)=>{constperson=response.dataconstcar={name:'Mustang'}axios.post(person._links.cars.href,car)})-p--as_parent_ref:  field name defaults to_parent-resource_ref, e.g. if the parent name was dogs the field would be_dog_ref.  Using this parameter, the field name become literally_parent_ref.  Useful to implement generic parent traversals.listdetailsremovedetailsaffordanceintrocreatedetailslistdetailsremovedetailsendpointintrocreatedetailslistdetailsremovedetailsintegrationintrocreatedetailslistdetailsremovedetailssettingintrocreatedetailslistdetailsremovedetailsrundetailsFeaturesThis section is under heavy construction.  In no particular order...Settings/configuration systemTheEveServiceships with a sophisticated settings/configuration management system based on these principles:Easy to run: An API should run right out of the box without requiring configurationContainer compatible: All settings can be set with environment variables.Developer convenience:  Developers can easily modify settings without accidentally commiting code with experimental values.use_env.confto set these values - included when your API was createdThis file is in.gitignoreand.dockerignoreso it will never accidentally be shipped anywhere but your dev environmentVisible:  all setting values are logged at startup and can be viewed withGET /_settings- no more wondering what value was set when debugging a problem at 3:00 AM(TODO: cancellable, optional, prefix stuff?,utils.is_enabled()[0] == 'YyTtEe')Loggingout of the box, the python logging module is configured withTRACE leveluse as you wish (LOG.trace('very verbose message here'))Use@tracedecorator to automatically log a function's enter and exit (even if exception is thrown)Disable with ES_TRACE_LOGGING to avoid stepping into the detailed logger when debugging (disabled by default in your_env.conf)EnableES_LOG_TO_FOLDERand logs are created in/var/logs/service-name(which the devdocker-compose.ymlmaps to local volume so you can view the logs even if the container is stopped)You can be notified by email if the server sends a 5xx responseEnableES_SEND_ERROR_EMAILSthen configureES_SMTP_PORT,ES_SMTP_SERVER,ES_ERROR_EMAIL_RECIPIENTS, andES_ERROR_EMAIL_FROMView and change the logging verbosity at runtime with a GET and/or PUT to/_logging(provide details)Easily extend the logging capabilities using the standard PythonloggingmodulesPublish to Slack on 5xx, or whatever circumstance you wish (the 500th GET to a particular resource? - only limit is your imagination)See and modifyutils/log_setup.pyEchoSometimes you need to test a client's ability to respond to various message or error/status codes.  It may be difficult to purposefully generate a 500.  In these cases you can enableES_ADD_ECHO.  Then you can PUT to/_echoa JSON as follows:{"status_code":###,"message":{...}}This produce a response with status code as specified, with themessagevalue as the body.  It also goes through the service logging system, so you can test receiving emails on 5xx, etc. (or however you choose to extend the logging)Exception Handlingimproves on Eve's out-of-box behaviour by standardizing the error response - even in the case of 5xx's{_status: "ERR", _error: {code:422, message: ""}, _issues: [] }your code can callutils.make_error_response()to emit custom error messages that follow this standard.HAL media typeEve's out-of-box JSON structure is very close to HAL.  With eve-utils, you get even closerContent-type isapplication/hal+json_linksare tidied up, some superfluous metadata is removedhierarchical navigation follows IANA conventions(this is constantly improving - coming soon curies, _embedded, as well as other standard hypermedia types like Siren, Atom, Collection+JSON, etc.)API Gateway Registration(details)Miscellaneousenforced pluralizationcollections are pluralizeditems are singularizedCORS permissively set by defaultutils.get_db()to quickly access the mongodb collectionsutils.get_api()to make http requests in your code to the API itselfeve-utils errorlevels / exit codes:
1 - not run in API folder structure10x - git
101 - git already added20x - auth
201 - auth already added30x - validation
301 - validation already installed40x - docker
401 - docker already installed50x - web_socket
501 - web_socket already installed60x - serverless
601 - serverless already added
602 - node not installed
603 - serverless not installed
604 - node not initialized
605 - serverless plugin not installed70x - resource
701 - resource already exists80x - link
801 - link already exists90x - integration
901 - integration already exists
902 - name required when choosing empty integrationorganized folder structuredesigned with more than simple api in mind (e.g. scripts)src, doc, etcFAQ why my-api/src/my-api ?domain decompositionhooksintegrationsaffordances(addins: validation, authorization, etc...)common.py_x
name
description