├── README.md
└── fashion
└── __init__.py
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | # Fashion [](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 |
--------------------------------------------------------------------------------