py2http


Namepy2http JSON
Version 0.1.60 PyPI version JSON
download
home_pagehttps://github.com/i2mint/py2http
SummaryDeclarative HTTP service entry point.
upload_time2024-02-22 11:48:05
maintainer
docs_urlNone
authorOtosense
requires_python
licenseMIT
keywords http service web service
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            [Documentation here](https://i2mint.github.io/py2http/)


# py2http
Dispatching Python functions as http services.

You have some python objects and want to get an "equivalent" http service from them...


## Usage

Run a basic HTTP service from a list of functions.

```python
from py2http.service import run_app

# Define or import functions
def add(a, b):
    return a + b

def multiply(a, b):
    return a * b

class Divider:
    def __init__(self, dividend):
        self.dividend = dividend
    
    def divide(self, divisor):
        return self.dividend / divisor

divider_from_ten = Divider(10)

# Make a list of functions or instance methods
func_list = [add, multiply, divider_from_ten.divide]

# Create an HTTP server
run_app(func_list, publish_openapi=True, publish_swagger=True)
```

The HTTP server will listen on port 3030 by default.

Given we asked for those options, you can:
* access the OpenAPI specification of the http-service at http://localhost:3030/openapi
* access the swagger docs and fiddle of the http-service at http://localhost:3030/swagger
* actually use the http-service's routes, for example:

```python
# Test the server in a separate process
import requests

url = 'http://localhost:3030/add'
add_args = {'a': 20, 'b': 22}
requests.post(url, json=add_args).json()
# should return 42
```

## Configuration

`run_app` accept many configuration values to customize the handling of HTTP requests and responses. Configuration documentation is listed in the file `config.yaml`.

## Method transformation

Expose class methods by flattening the init -> method call process.

```python
from py2http import run_app
from py2http.decorators import mk_flat

class Adder:
    def __init__(self, a):
        self.a = a

    def add(self, b):
        return self.a + b

add = mk_flat(Adder, Adder.add, func_name='add')

func_list = [add]

run_app(func_list)
```

## Input mapping

By default, the server will only accept JSON requests and parse the values from the request body as keyword arguments. You can define custom input mappers to perform extra handling on the JSON body or directly on the HTTP library request object, such as default injection or type mapping.

```python
from numpy import array
import soundfile
from py2http.decorators import handle_json_req

from my_service.data import fetch_user_data

@handle_json_req  # extracts the JSON body and passes it to the input mapper as a dict
def array_input_mapper(input_kwargs):
    return {'arr': array(input_kwargs.get('arr', [])),
            'scalar': input_kwargs.get('scalar', 1)}

@handle_multipart_req  # extracts a multipart body and passes it as a dict
def sound_file_input_mapper(input_kwargs):
    file_input = input_kwargs['file_upload']
    filename = file_input.filename
    file_bytes = BytesIO(file_input.file)
    wf, sr = soundfile.read(file_bytes)
    return dict(input_kwargs, wf=wf, sr=sr, filename=filename)

def custom_header_input_mapper(req):  # takes a raw aiohttp request
    user = req.headers.get('User', '')
    return {'user': user}


def array_multiplier(arr, scalar):
    return arr * scalar

def save_audio(wf, sr, filename, storage_path='/audio'):
    target = os.path.join(storage_path, filename)
    soundfile.write(target, wf, sr)

def get_user_data(user):
    return fetch_user_data(user)

array_multiplier.input_mapper = array_input_mapper
save_audio.input_mapper = sound_file_input_mapper
get_user_data.input_mapper = custom_header_input_mapper
```

## Output mapping

By default, the server will send the return value of the handler function in an HTTP response in JSON format. You can define custom output mappers to perform extra handling or type conversion.

```python
from io import BytesIO
import soundfile

from py2http.decorators import send_json_resp, send_html_resp

from my_service.data import fetch_blog

@send_json_resp  # sends a JSON response
def array_output_mapper(output, input_kwargs):
    return {'array_value': output['array_value'].tolist()}

@send_html_resp  # sends an HTML response
def mk_html_list(output, input_kwargs):
    tag = input_kwargs.get('tag', 'p')
    item_list = output.split('\n')
    result = [f'<{tag}>{item}</{tag}>' for item in item_list]
    return ''.join(result)

def return_wav(output, input_kwargs):
    wf, sr = output
    wf_bytes = BytesIO()
    soundfile.write(wf_bytes, wf, sr)
    binary_result = wf_bytes.read()
    return web.Response(body=binary_result, content_type='application/octet-stream')


def array_multiplier(arr, scalar):
    return arr * scalar

def get_blog_posts(page):
    return fetch_blog(page)

def download_audio(filename):
    with open(filename) as fp:
        wf, sr = soundfile.read(fp)
    return wf, sr

array_multipler.output_mapper = array_output_mapper

get_blog_posts.output_mapper = mk_html_list

download_audio.output_mapper = return_wav


```

## Error handling

TODO

## Client generation

Py2http generates an OpenAPI specification for the server before running. You can use this document with any OpenAPI-compatible client tools. To extract the specification, you can generate a server application object before running it.

```python
import json
from py2http import mk_app, run_app

def add(a, b):
    return a + b

func_list = [add]

app = mk_app(func_list)
openapi_spec = app.openapi_spec

with open('~/openapi.json', 'w') as fp:
    json.dump(openapi_spec, fp)

run_app(app, port=3030)
```

## Configuration

Functions that generate an HTTP service, such as `run_app`, accept a number of keyword arguments for configuration. The available configuration arguments are listed below with their defaults.

* `framework=py2http.config.BOTTLE` The HTTP framework to use.
* `input_mapper=py2http.default_configs.default_input_mapper` A function to map an HTTP request to a dict of input arguments.
* `output_mapper=py2http.default_configs.default_output_mapper` A function to map the output of a function to an HTTP response.
* `error_handler=py2http.default_configs.default_error_handler` A function to map an exception to an HTTP response.
* `header_inputs={}` A dict of object properties in JSON schema format describing keys that should be excluded from request body definitions. These inputs are expected to be extracted from the request headers in the input mapper.
* `host='localhost'` The hostname the server can be reached at, to include in the OpenAPI specification.
* `port=3030` The TCP port to listen on.
* `http_method='post'` The default HTTP method for exposing functions, if a function does not specify its method.
* `openapi={}` A dict with several configuration options specific to the OpenAPI specification, described below.
* `logger=None` An object that implements a `log` method.
* `app_name='HTTP Service'` The name of the application (Flask only).
* `middleware=[]` A list of middlewares to run (aiohttp only).
* `plugins=[]` A list of plugins to install (bottle only).
* `enable_cors=False` Whether to enable CORS features.
* `cors_allowed_origins='*'` If CORS is enabled, the value to pass in the Access-Control-Allow-Origin header.
* `publish_openapi=False` Whether to add a GET /openapi route to the app that returns the OpenAPI specification.
* `openapi_insecure=False` Whether to skip middlewares and plugins for the /openapi route (disabling any authentication).
* `publish_swagger=False` Whether to add a GET route to the app that returns a Swagger HTML interface. 
* `swagger_url='/swagger'` The URL of the swagger route.
* `swagger_title='Swagger'` The title to display on the Swagger HTML page.
* `ssl_certfile=None` The path to a valid SSL certificate.
* `ssl_keyfile=None` The path to a valid SSL certificate's key.

OpenAPI configuration:

These values will be put in the OpenAPI specification document. All values are optional.

* `title` The title of the application.
* `version` The version number of the application.
* `auth`: A dict specifying the authentication details to include in the document, to make it easier for clients to automate authentication.
* `auth['auth_type']` Either 'jwt' or 'api_key'.
* `auth['login_details']` A dict describing how a client can log in (for jwt authentication only).
* `auth['login_details']['login_url']` The URL of the login route.
* `auth['login_details']['refresh_url']`: The URL of the refresh route.
* `auth['login_details']['login_inputs']`: A list of strings with the keys expected by the login endpoint.
* `auth['login_details']['refresh_inputs']`: A list of strings with the keys expected by the refresh endpoint.
* `auth['login_details']['outputs']`: A list of strings with the keys returned by the login/refresh endpoints.,

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/i2mint/py2http",
    "name": "py2http",
    "maintainer": "",
    "docs_url": null,
    "requires_python": "",
    "maintainer_email": "",
    "keywords": "http service,web service",
    "author": "Otosense",
    "author_email": "",
    "download_url": "https://files.pythonhosted.org/packages/b4/ba/391eae72f472b449ed343fc90a83776bc153640c13ad6613dd768166c0e4/py2http-0.1.60.tar.gz",
    "platform": "any",
    "description": "[Documentation here](https://i2mint.github.io/py2http/)\n\n\n# py2http\nDispatching Python functions as http services.\n\nYou have some python objects and want to get an \"equivalent\" http service from them...\n\n\n## Usage\n\nRun a basic HTTP service from a list of functions.\n\n```python\nfrom py2http.service import run_app\n\n# Define or import functions\ndef add(a, b):\n    return a + b\n\ndef multiply(a, b):\n    return a * b\n\nclass Divider:\n    def __init__(self, dividend):\n        self.dividend = dividend\n    \n    def divide(self, divisor):\n        return self.dividend / divisor\n\ndivider_from_ten = Divider(10)\n\n# Make a list of functions or instance methods\nfunc_list = [add, multiply, divider_from_ten.divide]\n\n# Create an HTTP server\nrun_app(func_list, publish_openapi=True, publish_swagger=True)\n```\n\nThe HTTP server will listen on port 3030 by default.\n\nGiven we asked for those options, you can:\n* access the OpenAPI specification of the http-service at http://localhost:3030/openapi\n* access the swagger docs and fiddle of the http-service at http://localhost:3030/swagger\n* actually use the http-service's routes, for example:\n\n```python\n# Test the server in a separate process\nimport requests\n\nurl = 'http://localhost:3030/add'\nadd_args = {'a': 20, 'b': 22}\nrequests.post(url, json=add_args).json()\n# should return 42\n```\n\n## Configuration\n\n`run_app` accept many configuration values to customize the handling of HTTP requests and responses. Configuration documentation is listed in the file `config.yaml`.\n\n## Method transformation\n\nExpose class methods by flattening the init -> method call process.\n\n```python\nfrom py2http import run_app\nfrom py2http.decorators import mk_flat\n\nclass Adder:\n    def __init__(self, a):\n        self.a = a\n\n    def add(self, b):\n        return self.a + b\n\nadd = mk_flat(Adder, Adder.add, func_name='add')\n\nfunc_list = [add]\n\nrun_app(func_list)\n```\n\n## Input mapping\n\nBy default, the server will only accept JSON requests and parse the values from the request body as keyword arguments. You can define custom input mappers to perform extra handling on the JSON body or directly on the HTTP library request object, such as default injection or type mapping.\n\n```python\nfrom numpy import array\nimport soundfile\nfrom py2http.decorators import handle_json_req\n\nfrom my_service.data import fetch_user_data\n\n@handle_json_req  # extracts the JSON body and passes it to the input mapper as a dict\ndef array_input_mapper(input_kwargs):\n    return {'arr': array(input_kwargs.get('arr', [])),\n            'scalar': input_kwargs.get('scalar', 1)}\n\n@handle_multipart_req  # extracts a multipart body and passes it as a dict\ndef sound_file_input_mapper(input_kwargs):\n    file_input = input_kwargs['file_upload']\n    filename = file_input.filename\n    file_bytes = BytesIO(file_input.file)\n    wf, sr = soundfile.read(file_bytes)\n    return dict(input_kwargs, wf=wf, sr=sr, filename=filename)\n\ndef custom_header_input_mapper(req):  # takes a raw aiohttp request\n    user = req.headers.get('User', '')\n    return {'user': user}\n\n\ndef array_multiplier(arr, scalar):\n    return arr * scalar\n\ndef save_audio(wf, sr, filename, storage_path='/audio'):\n    target = os.path.join(storage_path, filename)\n    soundfile.write(target, wf, sr)\n\ndef get_user_data(user):\n    return fetch_user_data(user)\n\narray_multiplier.input_mapper = array_input_mapper\nsave_audio.input_mapper = sound_file_input_mapper\nget_user_data.input_mapper = custom_header_input_mapper\n```\n\n## Output mapping\n\nBy default, the server will send the return value of the handler function in an HTTP response in JSON format. You can define custom output mappers to perform extra handling or type conversion.\n\n```python\nfrom io import BytesIO\nimport soundfile\n\nfrom py2http.decorators import send_json_resp, send_html_resp\n\nfrom my_service.data import fetch_blog\n\n@send_json_resp  # sends a JSON response\ndef array_output_mapper(output, input_kwargs):\n    return {'array_value': output['array_value'].tolist()}\n\n@send_html_resp  # sends an HTML response\ndef mk_html_list(output, input_kwargs):\n    tag = input_kwargs.get('tag', 'p')\n    item_list = output.split('\\n')\n    result = [f'<{tag}>{item}</{tag}>' for item in item_list]\n    return ''.join(result)\n\ndef return_wav(output, input_kwargs):\n    wf, sr = output\n    wf_bytes = BytesIO()\n    soundfile.write(wf_bytes, wf, sr)\n    binary_result = wf_bytes.read()\n    return web.Response(body=binary_result, content_type='application/octet-stream')\n\n\ndef array_multiplier(arr, scalar):\n    return arr * scalar\n\ndef get_blog_posts(page):\n    return fetch_blog(page)\n\ndef download_audio(filename):\n    with open(filename) as fp:\n        wf, sr = soundfile.read(fp)\n    return wf, sr\n\narray_multipler.output_mapper = array_output_mapper\n\nget_blog_posts.output_mapper = mk_html_list\n\ndownload_audio.output_mapper = return_wav\n\n\n```\n\n## Error handling\n\nTODO\n\n## Client generation\n\nPy2http generates an OpenAPI specification for the server before running. You can use this document with any OpenAPI-compatible client tools. To extract the specification, you can generate a server application object before running it.\n\n```python\nimport json\nfrom py2http import mk_app, run_app\n\ndef add(a, b):\n    return a + b\n\nfunc_list = [add]\n\napp = mk_app(func_list)\nopenapi_spec = app.openapi_spec\n\nwith open('~/openapi.json', 'w') as fp:\n    json.dump(openapi_spec, fp)\n\nrun_app(app, port=3030)\n```\n\n## Configuration\n\nFunctions that generate an HTTP service, such as `run_app`, accept a number of keyword arguments for configuration. The available configuration arguments are listed below with their defaults.\n\n* `framework=py2http.config.BOTTLE` The HTTP framework to use.\n* `input_mapper=py2http.default_configs.default_input_mapper` A function to map an HTTP request to a dict of input arguments.\n* `output_mapper=py2http.default_configs.default_output_mapper` A function to map the output of a function to an HTTP response.\n* `error_handler=py2http.default_configs.default_error_handler` A function to map an exception to an HTTP response.\n* `header_inputs={}` A dict of object properties in JSON schema format describing keys that should be excluded from request body definitions. These inputs are expected to be extracted from the request headers in the input mapper.\n* `host='localhost'` The hostname the server can be reached at, to include in the OpenAPI specification.\n* `port=3030` The TCP port to listen on.\n* `http_method='post'` The default HTTP method for exposing functions, if a function does not specify its method.\n* `openapi={}` A dict with several configuration options specific to the OpenAPI specification, described below.\n* `logger=None` An object that implements a `log` method.\n* `app_name='HTTP Service'` The name of the application (Flask only).\n* `middleware=[]` A list of middlewares to run (aiohttp only).\n* `plugins=[]` A list of plugins to install (bottle only).\n* `enable_cors=False` Whether to enable CORS features.\n* `cors_allowed_origins='*'` If CORS is enabled, the value to pass in the Access-Control-Allow-Origin header.\n* `publish_openapi=False` Whether to add a GET /openapi route to the app that returns the OpenAPI specification.\n* `openapi_insecure=False` Whether to skip middlewares and plugins for the /openapi route (disabling any authentication).\n* `publish_swagger=False` Whether to add a GET route to the app that returns a Swagger HTML interface. \n* `swagger_url='/swagger'` The URL of the swagger route.\n* `swagger_title='Swagger'` The title to display on the Swagger HTML page.\n* `ssl_certfile=None` The path to a valid SSL certificate.\n* `ssl_keyfile=None` The path to a valid SSL certificate's key.\n\nOpenAPI configuration:\n\nThese values will be put in the OpenAPI specification document. All values are optional.\n\n* `title` The title of the application.\n* `version` The version number of the application.\n* `auth`: A dict specifying the authentication details to include in the document, to make it easier for clients to automate authentication.\n* `auth['auth_type']` Either 'jwt' or 'api_key'.\n* `auth['login_details']` A dict describing how a client can log in (for jwt authentication only).\n* `auth['login_details']['login_url']` The URL of the login route.\n* `auth['login_details']['refresh_url']`: The URL of the refresh route.\n* `auth['login_details']['login_inputs']`: A list of strings with the keys expected by the login endpoint.\n* `auth['login_details']['refresh_inputs']`: A list of strings with the keys expected by the refresh endpoint.\n* `auth['login_details']['outputs']`: A list of strings with the keys returned by the login/refresh endpoints.,\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Declarative HTTP service entry point.",
    "version": "0.1.60",
    "project_urls": {
        "Homepage": "https://github.com/i2mint/py2http"
    },
    "split_keywords": [
        "http service",
        "web service"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "c465db3ec465478c3f4b1188ddc1b9a0866ea547c4d0354a04752efc54fde977",
                "md5": "c3cb66a28deab1372db8b61c49f920e5",
                "sha256": "55dc0856e2322c52cc51f744f40ca81c33404a227bf3e92b5cecee19370d433a"
            },
            "downloads": -1,
            "filename": "py2http-0.1.60-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "c3cb66a28deab1372db8b61c49f920e5",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": null,
            "size": 53430,
            "upload_time": "2024-02-22T11:48:02",
            "upload_time_iso_8601": "2024-02-22T11:48:02.600559Z",
            "url": "https://files.pythonhosted.org/packages/c4/65/db3ec465478c3f4b1188ddc1b9a0866ea547c4d0354a04752efc54fde977/py2http-0.1.60-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "b4ba391eae72f472b449ed343fc90a83776bc153640c13ad6613dd768166c0e4",
                "md5": "929c5f3e36a5692e1bc95830a1a3df23",
                "sha256": "fb51de9cb347ab58dfb7adc56d8664a849e7dace9dcdf9d817ef2c93cb18b51c"
            },
            "downloads": -1,
            "filename": "py2http-0.1.60.tar.gz",
            "has_sig": false,
            "md5_digest": "929c5f3e36a5692e1bc95830a1a3df23",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": null,
            "size": 47514,
            "upload_time": "2024-02-22T11:48:05",
            "upload_time_iso_8601": "2024-02-22T11:48:05.305229Z",
            "url": "https://files.pythonhosted.org/packages/b4/ba/391eae72f472b449ed343fc90a83776bc153640c13ad6613dd768166c0e4/py2http-0.1.60.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-02-22 11:48:05",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "i2mint",
    "github_project": "py2http",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "py2http"
}
        
Elapsed time: 0.30831s