# py2api
Expose python objects to external clients with minimal effort.
## For the eager
py2api allows you to role out webservices (with flask) with minimal boilerplate.
To get started quickly, have a look at the examples, and play around with edits of them.
The main boilerplate here is writing code for input and output conversion. To help with this,
we provide some tools. Have a look at the output_trans.py and py2rest/input_trans.py modules,
especially the doctest, which should give you an idea of how to work it.
A webservice module assembled with py2api looks something like this:
```python
from mystuff import SystemService
from py2api import OutputTrans
from py2api.py2rest import InputTransWithAttrInURL
attr_list = ['ping', 'status', 'stop', 'start', 'restart']
name = '/api/process/'
process = WebObjWrapper(
obj_constructor=SystemService,
obj_constructor_arg_names=['name'],
permissible_attr=attr_list,
input_trans=InputTransWithAttrInURL(trans_spec=None, attr_from_url=name + "(\w+)"),
output_trans=OutputTrans(trans_spec=None),
name=name,
debug=0
)
app = add_routes_to_app(app, routes={
process.__name__ + attr: process for attr in attr_list
}
)
```
Note the two trans_spec=None used when making the input_trans and output_trans arguments of
WebObjWrapper. There was no need to specify any special conversion there because the methods
we were wrapping here only used simple types (string, int, float, list).
But often, when faced with more complex types, specifying how to carry out conversion in each and every situation
constitutes a big part of the boilerplate.
We provide tools to do this through a mapping (dict) separate from the code.
This enables tools to be created to operate on this specification.
You can specify conversion verbosely, by specifying how every argument of every function should be converted.
If you're the type, you can also do so concisely,
by specifying contextual rules involving the object's names and types, etc. grouping conversion rules together.
Such a (more concise) conversion specification looks like this:
```python
from py2api.constants import _ATTR, _ARGNAME, _ELSE, _VALTYPE
def ensure_array(x):
if not isinstance(x, ndarray):
return array(x)
return x
trans_spec = {
_ATTR: {
'this_attr': list,
'other_attr': str,
'yet_another_attr': {
_VALTYPE: {
dict: lambda x: x
},
_ELSE: lambda x: {'result': x}
}
},
_ARGNAME: {
'sr': int,
'ratio': float,
'snips': list,
'wf': ensure_array
},
}
}
```
See that the conversion function could be a builtin function like list, str, float, or int,
or could be a custom function, declared on the fly with a lambda, or refering to a function
declared elsewhere.
## Motivation
Say you have some functions, classes and/or whole modules that contain some functionality
that you'd like to expose as a webservice.
So you grab some python libary to do that. If you make your choice right, you won't have
to deal with the nitty gritty details of receiving and sending web requests.
Say, you chose flask. Good choice. Known to have minimal boiler plate.
So now all you have to do make a module and do something like this...
```python
from blah import a_func_you_want_to_expose
@app.route("/a_func_you_want_to_expose/", methods=['GET'])
def _a_func_you_want_to_expose():
# A whole bunch of boiler plate to get your arguments out
# of the web request object, which could be in the url, the json, the data...
# oh, and then you have to convert these all to python objects,
# because they're all simple types like strings, numbers and lists,
# you'll probably want to do some validation, perhaps add a few
# things that weren't really a concern of the original function,
# like logging, caching, providing some documentation/help,
# and then, oh, okay, you can call that function of yours now:
results = a_func_you_want_to_expose(...the arguments...)
# But wait, it's not finished! You'll need to convert these results
# to something that a web request can handle. Say a string or a json...
```
That's all. Enjoyed it?
Now do that again, and again, and again, for every fooing object you want to expose.
Still enjoy it?
We didn't. So we made py2api.
With py2api, you write the same boilerplate, but you only write it once.
In py2api you specify all the concerns of the routes you want elsewhere, a bit like
what OpenAPI does. But further, you have tools to reduce this specification to a set
of conventions and rules that define them. For example, if you use a variable called
"num" a lot, and most of the time it has to be an int, except in a few cases, your
specification has to be just that. It looks like this:
This whole py2api thing wasn't only about minimizing the time spent on a boilerplate.
It's also because separation of concerns is good, and in the above example,
concerns aren't as separated as they could: You have conversion, validation, logging,
caching.
Also, because code reuse is good, and we don't mean copy/pasting: You probably
have many arguments and types that show up in different places, and will end up writing,
or copy pasting, code to handle these many times. What if you made a mistake? You'll have to
find all those copies and correct them all.
Minimizing what you have to write to get something done is good: There's less places to look
for bugs.
Consistency is good.
Et cetera.
Raw data
{
"_id": null,
"home_page": "https://github.com/thorwhalen/py2api",
"name": "py2api",
"maintainer": "",
"docs_url": null,
"requires_python": "",
"maintainer_email": "",
"keywords": "",
"author": "Otosense",
"author_email": "",
"download_url": "https://files.pythonhosted.org/packages/cd/66/cd6f2eedef09e030c259c4cf4d31d248df90d90d9886a0ad544ba48c72b1/py2api-0.0.3.tar.gz",
"platform": "any",
"description": "# py2api\nExpose python objects to external clients with minimal effort.\n\n## For the eager\n\npy2api allows you to role out webservices (with flask) with minimal boilerplate.\n\nTo get started quickly, have a look at the examples, and play around with edits of them.\n\nThe main boilerplate here is writing code for input and output conversion. To help with this,\nwe provide some tools. Have a look at the output_trans.py and py2rest/input_trans.py modules, \nespecially the doctest, which should give you an idea of how to work it.\n\nA webservice module assembled with py2api looks something like this:\n\n```python\nfrom mystuff import SystemService\nfrom py2api import OutputTrans\nfrom py2api.py2rest import InputTransWithAttrInURL\n\nattr_list = ['ping', 'status', 'stop', 'start', 'restart']\n\nname = '/api/process/'\nprocess = WebObjWrapper(\n obj_constructor=SystemService,\n obj_constructor_arg_names=['name'],\n permissible_attr=attr_list,\n input_trans=InputTransWithAttrInURL(trans_spec=None, attr_from_url=name + \"(\\w+)\"),\n output_trans=OutputTrans(trans_spec=None),\n name=name,\n debug=0\n)\n\napp = add_routes_to_app(app, routes={\n process.__name__ + attr: process for attr in attr_list\n }\n)\n```\n\nNote the two trans_spec=None used when making the input_trans and output_trans arguments of \nWebObjWrapper. There was no need to specify any special conversion there because the methods\nwe were wrapping here only used simple types (string, int, float, list). \n\nBut often, when faced with more complex types, specifying how to carry out conversion in each and every situation\nconstitutes a big part of the boilerplate. \nWe provide tools to do this through a mapping (dict) separate from the code.\nThis enables tools to be created to operate on this specification.\nYou can specify conversion verbosely, by specifying how every argument of every function should be converted. \nIf you're the type, you can also do so concisely, \nby specifying contextual rules involving the object's names and types, etc. grouping conversion rules together.\n\nSuch a (more concise) conversion specification looks like this:\n\n```python\nfrom py2api.constants import _ATTR, _ARGNAME, _ELSE, _VALTYPE\n\ndef ensure_array(x):\n if not isinstance(x, ndarray):\n return array(x)\n return x\n\ntrans_spec = {\n _ATTR: {\n 'this_attr': list,\n 'other_attr': str,\n 'yet_another_attr': {\n _VALTYPE: {\n dict: lambda x: x\n },\n _ELSE: lambda x: {'result': x}\n }\n },\n _ARGNAME: {\n 'sr': int,\n 'ratio': float,\n 'snips': list,\n 'wf': ensure_array\n },\n }\n}\n```\n\nSee that the conversion function could be a builtin function like list, str, float, or int,\nor could be a custom function, declared on the fly with a lambda, or refering to a function\ndeclared elsewhere. \n\n\n## Motivation\n\nSay you have some functions, classes and/or whole modules that contain some functionality \nthat you'd like to expose as a webservice. \nSo you grab some python libary to do that. If you make your choice right, you won't have \nto deal with the nitty gritty details of receiving and sending web requests. \nSay, you chose flask. Good choice. Known to have minimal boiler plate.\nSo now all you have to do make a module and do something like this...\n\n```python\nfrom blah import a_func_you_want_to_expose\n@app.route(\"/a_func_you_want_to_expose/\", methods=['GET'])\ndef _a_func_you_want_to_expose():\n # A whole bunch of boiler plate to get your arguments out\n # of the web request object, which could be in the url, the json, the data...\n # oh, and then you have to convert these all to python objects, \n # because they're all simple types like strings, numbers and lists, \n # you'll probably want to do some validation, perhaps add a few \n # things that weren't really a concern of the original function, \n # like logging, caching, providing some documentation/help, \n # and then, oh, okay, you can call that function of yours now:\n\n results = a_func_you_want_to_expose(...the arguments...)\n\n # But wait, it's not finished! You'll need to convert these results \n # to something that a web request can handle. Say a string or a json...\n```\n \nThat's all. Enjoyed it?\n\nNow do that again, and again, and again, for every fooing object you want to expose.\n\nStill enjoy it?\n\nWe didn't. So we made py2api.\n\nWith py2api, you write the same boilerplate, but you only write it once. \nIn py2api you specify all the concerns of the routes you want elsewhere, a bit like \nwhat OpenAPI does. But further, you have tools to reduce this specification to a set \nof conventions and rules that define them. For example, if you use a variable called \n\"num\" a lot, and most of the time it has to be an int, except in a few cases, your \nspecification has to be just that. It looks like this:\n\n\nThis whole py2api thing wasn't only about minimizing the time spent on a boilerplate. \n\nIt's also because separation of concerns is good, and in the above example, \nconcerns aren't as separated as they could: You have conversion, validation, logging, \ncaching. \n\nAlso, because code reuse is good, and we don't mean copy/pasting: You probably \nhave many arguments and types that show up in different places, and will end up writing, \nor copy pasting, code to handle these many times. What if you made a mistake? You'll have to \nfind all those copies and correct them all.\n\nMinimizing what you have to write to get something done is good: There's less places to look \nfor bugs.\n \nConsistency is good.\n\nEt cetera.\n \n",
"bugtrack_url": null,
"license": "mit",
"summary": "Declarative API entry point",
"version": "0.0.3",
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "4ee34f81f5965bbb61bc1a3317c77de26af150f3dc052a3b79ce4525f5b571fa",
"md5": "82c815cd34466a2b57c7434aea78b1c8",
"sha256": "6752d722a3828b656b2d64a1bc07a7a2318913ef91e7a18e547c60d7aca26d64"
},
"downloads": -1,
"filename": "py2api-0.0.3-py3-none-any.whl",
"has_sig": false,
"md5_digest": "82c815cd34466a2b57c7434aea78b1c8",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 36634,
"upload_time": "2023-01-16T15:38:21",
"upload_time_iso_8601": "2023-01-16T15:38:21.178344Z",
"url": "https://files.pythonhosted.org/packages/4e/e3/4f81f5965bbb61bc1a3317c77de26af150f3dc052a3b79ce4525f5b571fa/py2api-0.0.3-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "cd66cd6f2eedef09e030c259c4cf4d31d248df90d90d9886a0ad544ba48c72b1",
"md5": "2adca7005a6f4a96a0ad013a3592c76d",
"sha256": "461acdbc2b890948632b12ee002793cdbddd513e823c1e0ad14f7b136cf9bb67"
},
"downloads": -1,
"filename": "py2api-0.0.3.tar.gz",
"has_sig": false,
"md5_digest": "2adca7005a6f4a96a0ad013a3592c76d",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 32750,
"upload_time": "2023-01-16T15:38:23",
"upload_time_iso_8601": "2023-01-16T15:38:23.120632Z",
"url": "https://files.pythonhosted.org/packages/cd/66/cd6f2eedef09e030c259c4cf4d31d248df90d90d9886a0ad544ba48c72b1/py2api-0.0.3.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-01-16 15:38:23",
"github": true,
"gitlab": false,
"bitbucket": false,
"github_user": "thorwhalen",
"github_project": "py2api",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "py2api"
}