├── README.md └── fashion └── __init__.py /README.md: -------------------------------------------------------------------------------- 1 |

2 | Fashion: python openfaas 3 |

4 | 5 | # Fashion [![Python 3](https://img.shields.io/badge/Python-3-brightgreen.svg)](https://github.com/Miserlou/Fashion) 6 | ### aka, python-openfaas 7 | 8 | _Work in progress!_ 9 | 10 | **Fashion** is a library for using [OpenFaaS](https://openfaas.com) functions in a Pythonic way. Fashion functions run inside their own Docker containers in your cluster, but act like local functions in your application's code! **Cool!** 11 | 12 | For instance, let's imagine that we've installed the `figlet` function from the OpenFaaS function store. To call it from our application, we can simply: 13 | 14 | ```python 15 | from fashion import figlet 16 | hi = figlet("Hello, world!") 17 | # _ _ _ _ _ _ _ 18 | # | | | | ___| | | ___ __ _____ _ __| | __| | | 19 | # | |_| |/ _ \ | |/ _ \ \ \ /\ / / _ \| '__| |/ _` | | 20 | # | _ | __/ | | (_) | \ V V / (_) | | | | (_| |_| 21 | # |_| |_|\___|_|_|\___( ) \_/\_/ \___/|_| |_|\__,_(_) 22 | # |/ 23 | ``` 24 | 25 | Fashion doesn't need any prior knowledge of the OpenFaas function, you can simply call it as if it were a local function! 26 | 27 | 28 | 29 | ## Table of Contents 30 | 31 | - [Installation](#installation) 32 | - [Usage](#usage) 33 | - [Ordinary Usage](#ordinary-usage) 34 | - [Async](#async) 35 | - [Callbacks](#callbacks) 36 | - [Instance Type Limiting / Automatic Cost Optimization](#instance-type-limiting--automatic-cost-optimization) 37 | - [Other OpenFaaS utilities](#other-openfaas-utilities) 38 | - [Settings](#settings) 39 | - [Related Projects](#related-projects) 40 | - [TODO](#todo) 41 | - [License](#license) 42 | 43 | 44 | 45 | ## Installation 46 | 47 | (Coming [soon](https://github.com/pypa/warehouse/issues/6725)!) 48 | ``` 49 | pip install fashion 50 | ``` 51 | 52 | ## Usage 53 | 54 | ### Ordinary Usage 55 | 56 | Fashion is very easy to use, just import your OpenFaaS function as if it were native! 57 | 58 | For instance: 59 | ```python 60 | from fashion import leftpad 61 | leftpad("Hello!") # " Hello!" 62 | ``` 63 | 64 | Alternately, you can use Fashion's `trigger` function directly, like so: 65 | 66 | ```python 67 | import fashion 68 | fashion.trigger("leftpad", "Hello!") #" Hello!" 69 | ``` 70 | Functions can be chained together without friction: 71 | 72 | ```python 73 | from fashion import leftpad, figlet 74 | figlet(leftpad("Hello!")) 75 | # _ _ _ _ _ _ _ 76 | # ( ) | | | | ___| | | ___ | ( ) 77 | # |/ | |_| |/ _ \ | |/ _ \| |/ 78 | # | _ | __/ | | (_) |_| 79 | # |_| |_|\___|_|_|\___/(_) 80 | ``` 81 | 82 | ### Async 83 | _(Still working on the interface for this, hang on a bit!)_ 84 | Similarly, you can use OpenFaaS functions asyncronously. To invoke an async function, import it with the prefix `async_` like so: 85 | 86 | ```python 87 | from fashion import async_leftpad 88 | async_leftpad("Hello!") # "6a3ae7fb-a8ee-4988-b7de-e3b81d1aed65" 89 | ``` 90 | 91 | or the old fashioned way: 92 | 93 | ```python 94 | import fashion 95 | fashion.async_trigger("leftpad", "Hello!") # "6a3ae7fb-a8ee-4988-b7de-e3b81d1aed65" 96 | ``` 97 | 98 | #### Callbacks 99 | 100 | Async functions can have their results sent to callback URLs and other OpenFaaS functions. For example, you can send to a pastebin like this: 101 | 102 | ```python 103 | from fashion import async_leftpad 104 | async_leftpad("Hello", callback_url="https://postb.in/b/1269724059086-0568930923473") 105 | ``` 106 | 107 | or to another OpenFaaS function like this: 108 | 109 | ```python 110 | from fashion import async_leftpad 111 | async_leftpad("Hello", callback_function="figlet") 112 | ``` 113 | 114 | #### Instance Type Limiting / Automatic Cost Optimization 115 | 116 | An interesting use of this is limiting execution of functions to **certain instance types**. For instance, if this is included in an `update_model` function definition: 117 | 118 | ```yaml 119 | #update_model.yml 120 | [..] 121 | constraints: 122 | - "node.labels.instance_type == gpu" 123 | ``` 124 | 125 | Then you can write code which is **automatically cost-optimized** when it executes: 126 | 127 | ```python 128 | # Runs on expensive GPU instance 129 | from fashion import update_model 130 | 131 | # Runs on cheap CPU instance 132 | from fashion import send_result_email 133 | 134 | result = update_model(input) 135 | send_email(result) 136 | # Email sent! 137 | ``` 138 | 139 | ### Other OpenFaaS utilities 140 | You can use Fashion to progromatically get information about all your OpenFaaS functions like so: 141 | 142 | ```python 143 | import fashion 144 | fashion.system_functions() 145 | ``` 146 | 147 | ### Settings 148 | You can configure your endpoints and other variables like so: 149 | 150 | ```python 151 | import fashion 152 | 153 | fashion.GATEWAY_URL = "http://internal.network.location" 154 | fashion.GATEWAY_PORT = 8081 155 | fashion.TIMEOUT_SECONDS = 30 156 | ``` 157 | 158 | ## Related Projects 159 | * [Zappa](https://github.com/Miserlou/Zappa) - Python's Serverless Framework for AWS Lambda 160 | 161 | ## TODO 162 | * Tests 163 | * Async 164 | * Auth/HMAC settings 165 | * Turn left__pad into left-pad invocation 166 | 167 | ## License 168 | 169 | (C) Rich Jones 2019, MIT License. -------------------------------------------------------------------------------- /fashion/__init__.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | import requests 3 | 4 | GATEWAY_URL = 'http://127.0.0.1' 5 | GATEWAY_PORT = 8080 6 | TIMEOUT_SECONDS = 900 7 | 8 | class Fashion: 9 | """ """ 10 | 11 | @staticmethod 12 | def trigger(name, body=None): 13 | """ """ 14 | 15 | fashion_endpoint = GATEWAY_URL + ":" + str(GATEWAY_PORT) + f"/function/{name}" 16 | response = requests.post(fashion_endpoint, data=body, timeout=TIMEOUT_SECONDS) 17 | response.raise_for_status() 18 | return response.text 19 | 20 | @staticmethod 21 | def async_trigger(name, body=None, callback_url=None, callback_function=None): 22 | """ """ 23 | 24 | headers = {} 25 | if callback_url: 26 | headers['X-Callback-Url'] = callback_url 27 | if callback_function: 28 | headers['X-Callback-Url'] = GATEWAY_URL + ":" + str(GATEWAY_PORT) + f"/function/{name}" 29 | 30 | fashion_endpoint = GATEWAY_URL + ":" + str(GATEWAY_PORT) + f"/async-function/{name}" 31 | response = requests.post(fashion_endpoint, data=body, timeout=TIMEOUT_SECONDS, headers=headers) 32 | response.raise_for_status() 33 | return response.headers['X-Call-Id'] 34 | 35 | @staticmethod 36 | def create_named_function(name, body=None): 37 | """ """ 38 | 39 | if "async_" in name: 40 | def async_named_trigger(body, callback_url=None, callback_function=None): 41 | return Fashion.async_trigger(name.replace('async_', ''), body, callback_url, callback_function) 42 | return async_named_trigger 43 | else: 44 | def named_trigger(body): 45 | return Fashion.trigger(name, body) 46 | return named_trigger 47 | 48 | # OpenFaaS utilities 49 | @staticmethod 50 | def system_functions(): 51 | """ """ 52 | fashion_endpoint = GATEWAY_URL + ":" + str(GATEWAY_PORT) + f"/system/functions" 53 | response = requests.get(fashion_endpoint, timeout=TIMEOUT_SECONDS) 54 | response.raise_for_status() 55 | return response.json() 56 | 57 | @staticmethod 58 | def system_alert(data): 59 | """ XXX UNTESTED XXX """ 60 | fashion_endpoint = GATEWAY_URL + ":" + str(GATEWAY_PORT) + f"/system/alert" 61 | response = requests.post(fashion_endpoint, data=data, timeout=TIMEOUT_SECONDS) 62 | response.raise_for_status() 63 | return response.json() 64 | 65 | ## 66 | # Convenience methods 67 | ## 68 | 69 | def trigger(name, body): 70 | return Fashion().trigger(name, body) 71 | 72 | def async_trigger(name, body): 73 | return Fashion().async_trigger(name, body) 74 | 75 | def system_functions(): 76 | return Fashion().system_functions() 77 | 78 | def system_alert(): 79 | return Fashion().system_alert() 80 | 81 | ## 82 | # We use PEP562 to create our named functions at import time. 83 | # We have to do this after the Fashion class is created, and we can't do an import, 84 | # which is why the Fashion class lives in this file. 85 | ## 86 | 87 | def __getattr__(name): 88 | """ 89 | We don't need to manually check for it's prexistence in dir(Fashion) because it will already raise an ImportError if it exist 90 | """ 91 | 92 | return Fashion.create_named_function(name) 93 | --------------------------------------------------------------------------------