Package: hypermea
hypermeaThe fastest way to build production-ready Hypermedia APIs.Please note:  although we currently use this toolkit to create production-ready APIs, the toolkit is still under development.  Use at your own risk.  This doc is under a heavy rewrite.  Information here is mostly 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).hypermeahelps make some of that work easier.Install hypermea with pip.pip install hypermeaGetting StartedThis guide focusses on using the hypermea command line.  For details on methods you can use in your code, please seehypermea-coreAll utilities are accessed through thehypermeacommand.  You can also usehyfor short.Get helpTo see what you can do these ools:hypermea --helporhy --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-apihypermea api create .Add a resource to the domainhypermearesourcecreatepeopleLaunch the APIhypermearunNow, 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 SheetUsehypermeato 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 typehypermea --help.CommandsTermDefinitionapiThis, unsurprisingly, represents the API service itself.  When you runhypermea api create...a ton of code is generated, comprising your service.  The code wraps start/stop (and other details) in a class calledHypermeaServiceand your app is an instance of this class.HypermeaServiceinherits fromFlaskso your application is a Flask application as much as it is an hypermea/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 usehypermea api ...you need to say what you want to do with the api.To see what sub-commands and options are available:hypermea command --helpe.g.,hypermea api --helpSome commands have unique sub-commands, but many share the following:create- create the thing you're talking about, e.g.hypermea api create...orhypermea 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.,hypermea api create .(the service name will be the name of the folder you are in)hypermea api 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:hypermea 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-websocketadd 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.,hypermea api create foobar --add-docker --add-gitThis is the same ashypermea api create foobar -dgIf you want all add-ins, the easiest way is:hypermea 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...hypermea api create foobar --add-validation...is the same as...hypermea api create foobar
hypermea 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 toHypermeaService.  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.hypermearesourcecreateregion
hypermearesourcecreatestore
hypermealinkcreateregionstoreNow indomain.store, change the name field definition from this:'name": {'type':'string',...'unique':True}to this:'name": {'type':'string',...'unique_to_parent':'region'}--add-websocketDefine other events/listeners, emitters/senders inwebsocket/__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 inwebsocket/__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 HypermeaService 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 hypermea resourceNOTE: resources in Eve are collections, so hypermea names resources as plural by convention,i.e. if you enterhy resource create dogit will create an endpoint named/dogshypermea relies on theinflectlibrary for pluralization, which is very accurate but can make mistakesIf you want to specify the singular and plural names of a resource use "singular,plural" e.g.checkshows the singular and plural forms of the resource hypermea will inferlistdetailsremovedetailslinkUselinkto manage parent/child relationships amongst resources.createFor example:hypermearesourcecreateperson
hypermearesourcecreatecars
hypermealinkcreatepersoncaryou could also have typedhypermea link create people carsorhypermea 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.hypermeawires 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.listdetailsremovedetailsaffordanceintrocreatedetailsattachdetailslistdetailsremovedetailsendpointintrocreatedetailslistdetailsremovedetailsintegrationintrocreatedetailslistdetailsremovedetailssettingintrocreatedetailslistdetailsremovedetailsrundetailsFeaturesThis section is under heavy construction.  In no particular order...Settings/configuration systemTheHypermeaServiceships 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?,settings.has_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 HY_TRACE_LOGGING to avoid stepping into the detailed logger when debugging (disabled by default in your_env.conf)EnableHY_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 responseEnableHY_SEND_ERROR_EMAILSthen configureHY_SMTP_PORT,HY_SMTP_SERVER,HY_ERROR_EMAIL_RECIPIENTS, andHY_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 enableHY_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 hypermea, 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 defaultcreate_form / edit_form (RFC 6861)utils.get_db()to quickly access the mongodb collectionsutils.get_api()to make http requests in your code to the API itselfhypermea errorlevels / exit codes:
1 - not run in API folder structure
2 - an API already exists in this folder
3 - user cancelled when folder is not empty
11 - --set-version value not specified correctly10x - git
101 - git already added20x - auth
201 - auth already added30x - validation
301 - validation already installed40x - docker
401 - docker already installed50x - websocket
501 - websocket already installed60x - serverless
601 - serverless already added
602 - node not installed
603 - serverless not installed
604 - node not initialized
605 - serverless plugin not installed
666 - serverless installation canceled by user70x - resource
701 - resource name invalid
702 - resource already exists
703 - resource does not exist
704 - could not delete resource files80x - link
801 - link already exists
802 - local resource does not exist
803 - both parent and child of a link cannot be remote (at least one must be local)
804 - link does not exist90x - integration
901 - integration already exists
902 - name required when choosing empty integration100x - affordances
1001 - affordance already exists
1002 - affordance does not exist
1003 - cannot attach affordance to a resource that does not exist
1004 - affordance already attachedorganized 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
descriptionHALchemy