Skip to content

Commit b60fa53

Browse files
author
CI
committed
Initial migration + added examples
1 parent d960236 commit b60fa53

File tree

15 files changed

+211
-67
lines changed

15 files changed

+211
-67
lines changed

README.md

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
1-
# Algorithm Development Kit (ADK), Python edition
1+
# Algorithm Development Kit (ADK), Python edition
2+
23
```python
3-
import Algorithmia
4+
import from adk import ADK
5+
6+
47
# API calls will begin at the apply() method, with the request body passed as 'input'
58
# For more details, see algorithmia.com/developers/algorithm-development/languages
69

710
def apply(input):
811
return "hello {}".format(str(input))
9-
algo = Algorithmia.handler(apply)
12+
13+
14+
algo = ADK(apply)
1015
algo.serve()
1116
```
1217

@@ -31,12 +36,12 @@ This kit, when implemented by an algorithm developer - enables an easy way to ge
3136

3237
Algorithm development does change with this introduction:
3338
- Primary development file has been renamed to `src/Algorithm.py` to aide in understanding around what this file actually does / why it's important
34-
- An additional import (`from Algorithmia import Handler`)
39+
- An additional import (`from adk import ADK`)
3540
- An optional `load()` function that can be implemented
3641
- this enables a dedicated function for preparing your algorithm for runtime operations, such as model loading, configuration, etc
3742
- A call to the handler function with your `apply` and optional` load` functions as inputs
3843
- ```python
39-
algo = Algorithmia.handler(apply)
44+
algo = ADK(apply)
4045
algo.serve()
4146
```
4247
- converts the project into an executable, rather than a library
@@ -45,4 +50,7 @@ Algorithm development does change with this introduction:
4550
- this includes being able to step through your algorithm code in your IDE of choice! Just execute your `src/Algorithm.py` script
4651

4752
## Example workflows
48-
TODO
53+
Check out these examples to help you get started:
54+
- [hello world example](examples/hello_world)
55+
- [hello world example with loaded state](examples/loaded_state_hello_world)
56+
- [pytorch based image classification](examples/pytorch_image_classification)

src/ADK.py renamed to adk/ADK.py

Lines changed: 44 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
class ADK(object):
1212

13-
def __init__(self, apply_func, load_func):
13+
def __init__(self, apply_func, load_func=None):
1414
"""
1515
Creates the adk object
1616
:param apply_func: A required function that can have an arity of 1-2, depending on if loading occurs
@@ -20,20 +20,26 @@ def __init__(self, apply_func, load_func):
2020
apply_args, _, _, apply_defaults, _, _, _ = inspect.getfullargspec(apply_func)
2121
# apply_args, _, _, apply_defaults = inspect.getargspec(apply_func)
2222
# j = inspect.getfullargspec(apply_func)
23-
load_args, _, _, _, _, _, _ = inspect.getfullargspec(load_func)
24-
if len(load_args) > 0:
25-
raise Exception("load function must not have parameters")
23+
if load_func:
24+
load_args, _, _, _, _, _, _ = inspect.getfullargspec(load_func)
25+
if len(load_args) > 0:
26+
raise Exception("load function must not have parameters")
27+
self.load_func = load_func
28+
else:
29+
self.load_func = None
2630
if len(apply_args) > 2 or len(apply_args) == 0:
2731
raise Exception("apply function may have between 1 and 2 parameters, not {}".format(len(apply_args)))
2832
self.apply_func = apply_func
29-
self.load_func = load_func
3033
self.is_local = not os.path.exists(self.FIFO_PATH)
3134
self.load_result = None
3235

3336
def load(self):
3437
self.load_result = self.load_func()
35-
print('PIPE_INIT_COMPLETE')
36-
sys.stdout.flush()
38+
if self.is_local:
39+
print("loading complete")
40+
else:
41+
print('PIPE_INIT_COMPLETE')
42+
sys.stdout.flush()
3743

3844
def format_data(self, request):
3945
if request['content_type'] in ['text', 'json']:
@@ -74,36 +80,45 @@ def format_response(self, response):
7480
})
7581
return response_string
7682

77-
def write_to_pipe(self, data_string):
83+
def write_to_pipe(self, payload):
7884
if self.is_local:
79-
return data_string
85+
if isinstance(payload, dict):
86+
raise Exception(payload)
87+
else:
88+
print(payload)
8089
else:
8190
if os.name == "posix":
8291
with open(self.FIFO_PATH, 'w') as f:
83-
f.write(data_string)
92+
f.write(payload)
8493
f.write('\n')
8594
sys.stdout.flush()
8695
if os.name == "nt":
87-
sys.stdin = data_string
96+
sys.stdin = payload
97+
98+
def create_exception(self, exception):
99+
if hasattr(exception, 'error_type'):
100+
error_type = exception.error_type
101+
else:
102+
error_type = 'AlgorithmError'
103+
response = {
104+
'error': {
105+
'message': str(exception),
106+
'stacktrace': traceback.format_exc(),
107+
'error_type': error_type
108+
}
109+
}
110+
return response
88111

89112
def serve(self):
90113
try:
91-
self.load()
114+
if self.load_func:
115+
self.load()
92116
except Exception as e:
93-
if hasattr(e, 'error_type'):
94-
error_type = e.error_type
95-
else:
96-
error_type = 'AlgorithmError'
97-
load_error_string = json.dumps({
98-
'error': {
99-
'message': str(e),
100-
'stacktrace': traceback.format_exc(),
101-
'error_type': error_type
102-
}
103-
})
104-
l = self.write_to_pipe(load_error_string)
105-
return l
106-
response_string = ""
117+
load_error = self.create_exception(e)
118+
self.write_to_pipe(load_error)
119+
response_obj = ""
120+
if self.is_local:
121+
print("waiting for input...")
107122
for line in sys.stdin:
108123
try:
109124
request = json.loads(line)
@@ -112,20 +127,8 @@ def serve(self):
112127
apply_result = self.apply_func(formatted_input, self.load_result)
113128
else:
114129
apply_result = self.apply_func(formatted_input)
115-
response_string = self.format_response(apply_result)
130+
response_obj = self.format_response(apply_result)
116131
except Exception as e:
117-
if hasattr(e, 'error_type'):
118-
error_type = e.error_type
119-
else:
120-
error_type = 'AlgorithmError'
121-
response_string = json.dumps({
122-
'error': {
123-
'message': str(e),
124-
'stacktrace': traceback.format_exc(),
125-
'error_type': error_type
126-
}
127-
})
132+
response_obj = self.create_exception(e)
128133
finally:
129-
res = self.write_to_pipe(response_string)
130-
if res:
131-
return res
134+
self.write_to_pipe(response_obj)

adk/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .ADK import ADK

examples/algorithm_basic.py

Lines changed: 0 additions & 1 deletion
This file was deleted.

examples/hello_world/requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
algorithmia>=1.7,<2
2+
algorithmia-adk>=1.0,<2

examples/hello_world/src/Algorithm.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from adk import ADK
2+
3+
4+
def apply(input):
5+
return "hello {}".format(str(input))
6+
7+
8+
algorithm = ADK(apply)
9+
algorithm.serve()

examples/hello_world/src/__init__.py

Whitespace-only changes.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
algorithmia>=1.7,<2
2+
algorithmia-adk>=1.0,<2
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from adk import ADK
2+
3+
4+
# API calls will begin at the apply() method, with the request body passed as 'input'
5+
# For more details, see algorithmia.com/developers/algorithm-development/languages
6+
7+
def apply(input, state):
8+
# If your apply function uses state that's loaded into memory via load, you can pass that loaded state to your apply
9+
# function by defining an additional "state" parameter in your apply function.
10+
return "hello {} {}".format(str(input), str(state))
11+
12+
13+
def load():
14+
# Here you can optionally define a function that will be called when the algorithm is loaded.
15+
# The return object from this function can be passed directly as input to your apply function.
16+
# A great example would be any model files that need to be available to this algorithm
17+
# during runtime.
18+
# Any variables returned here, will be passed as the secondary argument to your 'algorithm' function
19+
20+
return "Loading has been completed."
21+
22+
23+
# This turns your library code into an algorithm that can run on the platform.
24+
# If you intend to use loading operations, remember to pass a `load` function as a second variable.
25+
algo = ADK(apply, load)
26+
# The 'serve()' function actually starts the algorithm, you can follow along in the source code
27+
# to see how everything works.
28+
algo.serve()

examples/loaded_state_hello_world/src/__init__.py

Whitespace-only changes.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
algorithmia>=1.7,<2
2+
algorithmia-adk
3+
six
4+
Pillow==8.1.0
5+
torch==1.6.0
6+
torchvision>=0.7
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import Algorithmia
2+
from adk import ADK
3+
import torch
4+
from PIL import Image
5+
import json
6+
from torchvision import models, transforms
7+
8+
CLIENT = Algorithmia.client()
9+
SMID_ALGO = "algo://util/SmartImageDownloader/0.2.x"
10+
LABEL_PATH = "data://AlgorithmiaSE/image_cassification_demo/imagenet_class_index.json"
11+
MODEL_PATHS = {
12+
"squeezenet": 'data://AlgorithmiaSE/image_cassification_demo/squeezenet1_1-f364aa15.pth',
13+
'alexnet': 'data://AlgorithmiaSE/image_cassification_demo/alexnet-owt-4df8aa71.pth',
14+
}
15+
16+
17+
def load_labels():
18+
local_path = CLIENT.file(LABEL_PATH).getFile().name
19+
with open(local_path) as f:
20+
labels = json.load(f)
21+
labels = [labels[str(k)][1] for k in range(len(labels))]
22+
return labels
23+
24+
25+
def load_model(name):
26+
if name == "squeezenet":
27+
model = models.squeezenet1_1()
28+
models.densenet121()
29+
weights = torch.load(CLIENT.file(MODEL_PATHS['squeezenet']).getFile().name)
30+
else:
31+
model = models.alexnet()
32+
weights = torch.load(CLIENT.file(MODEL_PATHS['alexnet']).getFile().name)
33+
model.load_state_dict(weights)
34+
return model.float().eval()
35+
36+
37+
def get_image(image_url):
38+
input = {"image": image_url, "resize": {'width': 224, 'height': 224}}
39+
result = CLIENT.algo(SMID_ALGO).pipe(input).result["savePath"][0]
40+
local_path = CLIENT.file(result).getFile().name
41+
img_data = Image.open(local_path)
42+
return img_data
43+
44+
45+
def infer_image(image_url, n, state):
46+
model = state['model']
47+
labels = state['labels']
48+
image_data = get_image(image_url)
49+
transformed = transforms.Compose([
50+
transforms.ToTensor(),
51+
transforms.Normalize(mean=[0.485, 0.456, 0.406],
52+
std=[0.229, 0.224, 0.225])])
53+
img_tensor = transformed(image_data).unsqueeze(dim=0)
54+
infered = model.forward(img_tensor)
55+
preds, indicies = torch.sort(torch.softmax(infered.squeeze(), dim=0), descending=True)
56+
predicted_values = preds.detach().numpy()
57+
indicies = indicies.detach().numpy()
58+
result = []
59+
for i in range(n):
60+
label = labels[indicies[i]].lower().replace("_", " ")
61+
confidence = float(predicted_values[i])
62+
result.append({"label": label, "confidence": confidence})
63+
return result
64+
65+
66+
def load():
67+
output = {'model': load_model("squeezenet"), 'labels': load_labels()}
68+
return output
69+
70+
71+
def apply(input, state):
72+
if isinstance(input, dict):
73+
if "n" in input:
74+
n = input['n']
75+
else:
76+
n = 3
77+
if "data" in input:
78+
if isinstance(input['data'], str):
79+
output = infer_image(input['data'], n, state)
80+
elif isinstance(input['data'], list):
81+
for row in input['data']:
82+
row['predictions'] = infer_image(row['image_url'], n, state)
83+
output = input['data']
84+
else:
85+
raise Exception("'data' must be a image url or a list of image urls (with labels)")
86+
return output
87+
else:
88+
raise Exception("'data' must be defined")
89+
else:
90+
raise Exception('input must be a json object')
91+
92+
93+
adk = ADK(apply_func=apply, load_func=load)
94+
adk.serve()

examples/pytorch_image_classification/src/__init__.py

Whitespace-only changes.

setup.py

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,16 @@
44

55
setup(
66
name='algorithmia-adk',
7-
version='0.1.2',
8-
description='Algorithmia python ADK client',
9-
long_description='Algorithmia Python Client is a client library for accessing Algorithmia from python code. This library also gets bundled with any Python algorithms in Algorithmia.',
10-
url='http://github.com/algorithmiaio/algorithmia-python',
7+
version='0.1.0',
8+
description='adk python ADK client',
9+
long_description='adk Development Kit code used for creating Python algorithms on adk.',
10+
url='http://github.com/algorithmiaio/algorithmia-adk-python',
1111
license='MIT',
12-
author='Algorithmia',
12+
author='adk',
1313
author_email='[email protected]',
14-
packages=['Algorithmia'],
15-
entry_points = {
16-
'console_scripts': ['algo = Algorithmia.__main__:main']
17-
},
14+
packages=['adk'],
1815
install_requires=[
19-
'requests',
2016
'six',
21-
'enum34',
22-
'toml',
23-
'argparse',
24-
'algorithmia-api-client>=1.3,<1.4'
2517
],
2618
include_package_data=True,
2719
classifiers=[
@@ -31,11 +23,12 @@
3123
'License :: OSI Approved :: MIT License',
3224
'Operating System :: OS Independent',
3325
'Programming Language :: Python',
34-
'Programming Language :: Python :: 2',
35-
'Programming Language :: Python :: 2.6',
36-
'Programming Language :: Python :: 2.7',
3726
'Programming Language :: Python :: 3',
38-
'Programming Language :: Python :: 3.3',
27+
'Programming Language :: Python :: 3.5',
28+
'Programming Language :: Python :: 3.6',
29+
'Programming Language :: Python :: 3.7',
30+
'Programming Language :: Python :: 3.8',
31+
'Programming Language :: Python :: 3.9',
3932
'Topic :: Software Development :: Libraries :: Python Modules',
4033
],
4134
)

src/__init__.py

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)