├── .gitignore ├── LICENSE ├── README.md ├── lib └── __init__.py ├── setup.cfg └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | MANIFEST 3 | test.py 4 | dist/ 5 | __pycache__/ 6 | *.pyc 7 | *.egg-info/ 8 | build/ 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 - 2021 Polybit Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Autocode standard library Python bindings 2 | 3 | Basic Python bindings for Autocode standard library service accession. 4 | Python 2.x and 3.x supported. 5 | 6 | Used to interface with services built using [Autocode](https://autocode.com) and 7 | the [Autocode CLI](https://github.com/acode/cli). 8 | 9 | The `lib` package is available on [PyPI: lib](https://pypi.python.org/pypi/lib) 10 | and operates as zero-dependency interface to run StdLib functions. 11 | This means that you can utilize any service on StdLib without installing any 12 | additional dependencies, and when you've deployed services to StdLib, you have 13 | a pre-built Python SDK. For example. 14 | 15 | ``` 16 | from lib import lib 17 | 18 | try: 19 | result = lib.yourUsername.hostStatus(name='Dolores Abernathy') 20 | except RuntimeError as err: 21 | # handle error 22 | ``` 23 | 24 | To discover StdLib services, visit https://stdlib.com/search. To build a 25 | service, get started with [the StdLib CLI tools](https://github.com/stdlib/lib). 26 | 27 | ## Installation 28 | 29 | To install in an existing Python project; 30 | 31 | ```shell 32 | $ pip install lib 33 | ``` 34 | 35 | ## Usage 36 | 37 | ```python 38 | from lib import lib 39 | 40 | # [1]: Call "utils.reflect" function, the latest version, from StdLib 41 | result = lib.utils.reflect(key='value') 42 | result = lib.utils.reflect({'key': 'value'}) # also works 43 | result = lib.utils.reflect('value') # also works, if first parameter is "key" 44 | 45 | # [2]: Call "utils.reflect" function from StdLib, with "dev" environment 46 | result = lib.utils.reflect['@dev'](key='value') 47 | 48 | # [3]: Call "utils.reflect" function from StdLib, with "release" environment 49 | # This is equivalent to (1) 50 | result = lib.utils.reflect['@release'](key='value') 51 | 52 | # [4]: Call "utils.reflect" function from StdLib, with specific version 53 | # This is equivalent to (1) 54 | result = lib.utils.reflect['@0.0.1'](key='value') 55 | 56 | # [5]: Call functions within the service (not just the defaultFunction) 57 | # This is equivalent to (1) when "main" is the default function 58 | result = lib.utils.reflect.main(key='value') 59 | 60 | # Valid string composition from first object property only: 61 | result = lib['utils.reflect'](key='value') 62 | result = lib['utils.reflect[@dev]'](key='value') 63 | result = lib['utils.reflect[@release]'](key='value') 64 | result = lib['utils.reflect[@0.0.1]'](key='value') 65 | result = lib['utils.reflect.main'](key='value') 66 | result = lib['utils.reflect[@dev].main'](key='value') 67 | result = lib['utils.reflect[@release].main'](key='value') 68 | result = lib['utils.reflect[@0.0.1].main'](key='value') 69 | ``` 70 | 71 | ## Sending File Data 72 | 73 | In order to send file parameters, in Python 2.7 or 3.6, simply use; 74 | 75 | ```python 76 | lib.username.service(parameter=open('/path/to/file.jpg')) 77 | ``` 78 | 79 | Where `parameter` is the parameter name expecting a file type (type "buffer" 80 | as listed on StdLib). 81 | 82 | ## Additional Information 83 | 84 | To learn more about Autocode and the standard library, visit 85 | [autocode.com](https://autocode.com) or read the 86 | [Autocode CLI documentation on GitHub](https://github.com/acode/cli). 87 | 88 | You can follow the development team on Twitter, 89 | [@AutocodeHQ](https://twitter.com/AutocodeHQ). 90 | 91 | Autocode is © 2016 - 2021 Polybit Inc. 92 | -------------------------------------------------------------------------------- /lib/__init__.py: -------------------------------------------------------------------------------- 1 | class LibGen(): 2 | 3 | import os 4 | 5 | HOST = 'functions.api.stdlib.com' 6 | PORT = 443 7 | PATH = '/' 8 | 9 | LOCALENV = 'local' 10 | LOCALPORT = os.getenv('STDLIB_LOCAL_PORT', 8170) 11 | 12 | def __init__(self, cfg={}, names=[]): 13 | cfg['host'] = cfg['host'] if 'host' in cfg else self.HOST 14 | cfg['port'] = cfg['port'] if 'port' in cfg else self.PORT 15 | cfg['path'] = cfg['path'] if 'path' in cfg else self.PATH 16 | self.__cfg__ = cfg 17 | self.__names__ = names 18 | 19 | def __repr__(self): 20 | return '' 21 | 22 | def __str__(self): 23 | return '.'.join(self.__names__) 24 | 25 | def __append_version__(self, names, value): 26 | 27 | import re 28 | 29 | if not re.compile('^@[A-Z0-9\-\.]+$', re.I).match(value): 30 | raise NameError('.'.join(names) + ' invalid version: ' + value) 31 | 32 | return names + [value] 33 | 34 | def __append_path__(self, names, value): 35 | 36 | import re 37 | 38 | if not re.compile('^[A-Z0-9\-]+$', re.I).match(value): 39 | 40 | if '@' in value: 41 | raise NameError('.'.join(names) + ' invalid name: ' + value + ', please specify versions and environments with [@version]') 42 | 43 | raise NameError('.'.join(names) + ' invalid name: ' + value) 44 | 45 | return names + [value] 46 | 47 | def __append_lib_path__(self, names, value): 48 | 49 | import re 50 | 51 | names = names + [] if len(names) else [] 52 | default_version = '@release' 53 | 54 | if len(names) == 0 and value == '': 55 | return names + [value] 56 | 57 | elif len(names) == 0 and ('.' in value): 58 | 59 | versionMatch = re.compile('^[^\.]+?\.[^\.]*?(\[@[^\[\]]*?\])(\.|$)', re.I).match(value) 60 | arr = [] 61 | 62 | if versionMatch: 63 | version = versionMatch[1] 64 | version = re.sub('^\[?(.*?)\]?$', r'\1', version) 65 | value = value.replace(versionMatch[1], '') 66 | arr = value.split('.') 67 | arr = arr[0:2] + [version] + arr[2:] 68 | else: 69 | arr = [''] if value == '.' else value.split('.') 70 | 71 | while len(arr) > 0: 72 | names = self.__append_lib_path__(names, arr.pop(0)) 73 | 74 | return names 75 | 76 | elif len(names) == 2 and names[0] != '': 77 | if value[0] == '@': 78 | return self.__append_version__(names, value) 79 | else: 80 | return self.__append_path__(self.__append_version__(names, default_version), value) 81 | 82 | else: 83 | return self.__append_path__(names, value) 84 | 85 | def __getitem__(self, name): 86 | return self.__getattr__(name) 87 | 88 | def __getattr__(self, name): 89 | return LibGen(self.__cfg__, self.__append_lib_path__(self.__names__, name)) 90 | 91 | def __call__(self, *args, **kwargs): 92 | 93 | import sys 94 | import urllib 95 | 96 | cfg = self.__cfg__ 97 | 98 | if len(self.__names__) == 0: 99 | cfg = args[0] if len(args) > 0 and type(args[0]) is dict else {} 100 | return LibGen(cfg) 101 | elif len(self.__names__) >= 3 and self.__names__[2] == '@' + self.LOCALENV: 102 | cfg['host'] = 'localhost'; 103 | cfg['port'] = self.LOCALPORT; 104 | 105 | pathname = '/'.join(self.__names__[0:2]) + '/'.join(self.__names__[2:]) 106 | pathname += '/' 107 | args = list(args) 108 | 109 | if len(args) > 0: 110 | if len(args) == 1 and type(args[0]) is dict: 111 | kw = args[0].copy() 112 | kw.update(kwargs) 113 | kwargs = kw 114 | elif bool(kwargs): 115 | raise ValueError('.'.join(self.__names__) + ': Can not pass arguments and kwargs') 116 | 117 | headers = {} 118 | headers['Content-Type'] = 'application/json' 119 | headers['X-Faaslang'] = 'true' 120 | if 'token' in cfg: 121 | headers['Authorization'] = 'Bearer {0}'.format(cfg['token']) 122 | if 'keys' in cfg: 123 | headers['X-Authorization-Keys'] = json.dumps(cfg['keys']) 124 | if 'convert' in cfg: 125 | headers['X-Convert-Strings'] = 'true' 126 | if 'bg' in cfg: 127 | pathname += ':bg' 128 | if type(cfg['bg']) is str: 129 | bg = urllib.quote(cfg['bg'].encode('utf-8')) 130 | pathname += '={0}'.format(bg) 131 | 132 | if (sys.version_info > (3, 0)): 133 | body = self.__py3body__(args, kwargs) 134 | return self.__py3request__(cfg, pathname, body, headers) 135 | else: 136 | body = self.__py2body__(args, kwargs) 137 | return self.__py2request__(cfg, pathname, body, headers) 138 | 139 | def __py3body__(self, args, kwargs): 140 | 141 | import base64 142 | import json 143 | import io 144 | 145 | if bool(kwargs): 146 | kw = {} 147 | for key in kwargs: 148 | value = kwargs[key] 149 | if isinstance(value, io.IOBase): 150 | if value.closed: 151 | raise ValueError('.'.join(self.__names__) + ': Can not read from closed file') 152 | if value.encoding == 'UTF-8': 153 | value = value.buffer 154 | base64data = base64.b64encode(value.read()).decode('UTF-8') 155 | value.close() 156 | kw[key] = {'_base64': base64data} 157 | else: 158 | kw[key] = value 159 | body = json.dumps(kw) 160 | else: 161 | a = [] 162 | for value in args: 163 | if isinstance(value, io.IOBase): 164 | if value.closed: 165 | raise ValueError('.'.join(self.__names__) + ': Can not read from closed file') 166 | if value.encoding == 'UTF-8': 167 | value = value.buffer 168 | base64data = base64.b64encode(value.read()).decode('UTF-8') 169 | value.close() 170 | a.push({'_base64': base64data}) 171 | else: 172 | a.push(value) 173 | body = json.dumps(args) 174 | 175 | return body 176 | 177 | def __py2body__(self, args, kwargs): 178 | 179 | import json 180 | 181 | if bool(kwargs): 182 | kw = {} 183 | for key in kwargs: 184 | value = kwargs[key] 185 | if isinstance(value, file): 186 | if value.closed: 187 | raise ValueError('.'.join(self.__names__) + ': Can not read from closed file') 188 | base64data = value.read().encode('base64').strip() 189 | value.close() 190 | kw[key] = {'_base64': base64data} 191 | else: 192 | kw[key] = value 193 | body = json.dumps(kw) 194 | else: 195 | a = [] 196 | for value in args: 197 | if isinstance(value, file): 198 | if value.closed: 199 | raise ValueError('.'.join(self.__names__) + ': Can not read from closed file') 200 | base64data = value.read().encode('base64').strip() 201 | value.close() 202 | a.push({'_base64': base64data}) 203 | else: 204 | a.push(value) 205 | body = json.dumps(args) 206 | 207 | return body 208 | 209 | def __py3request__(self, cfg, pathname, body, headers): 210 | 211 | import http.client 212 | 213 | conn = http.client.HTTPSConnection(cfg['host'], cfg['port']) 214 | conn.request('POST', self.__cfg__['path'] + pathname, body, headers) 215 | r = conn.getresponse() 216 | contentType = r.getheader('Content-Type') 217 | response = r.read() 218 | conn.close() 219 | 220 | return self.__complete__(r.status, contentType, response) 221 | 222 | def __py2request__(self, cfg, pathname, body, headers): 223 | 224 | import httplib 225 | 226 | conn = httplib.HTTPSConnection(cfg['host'], cfg['port']) 227 | conn.request('POST', cfg['path'] + pathname, body, headers) 228 | r = conn.getresponse() 229 | contentType = r.getheader('Content-Type') 230 | response = r.read() 231 | conn.close() 232 | 233 | return self.__complete__(r.status, contentType, response) 234 | 235 | def __complete__(self, status, contentType, response): 236 | 237 | import json 238 | import re 239 | 240 | if contentType == 'application/json': 241 | response = response.decode('utf-8') 242 | try: 243 | response = json.loads(response); 244 | except: 245 | response = None 246 | elif contentType is None or re.compile(r"^text\/.*$", re.I).match(str(contentType)): 247 | response = response.decode('utf-8') 248 | 249 | if status / 100 != 2: 250 | raise RuntimeError(response) 251 | 252 | return response 253 | 254 | lib = LibGen() 255 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file=README.md 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | 3 | setup( 4 | name = 'lib', 5 | packages = ['lib'], 6 | version = '4.0.0', 7 | description = 'Autocode standard library Python bindings', 8 | author = 'Keith Horwood', 9 | author_email = 'keithwhor@gmail.com', 10 | url = 'https://github.com/acode/lib-python', 11 | keywords = ['autocode', 'stdlib', 'microservice', 'serverless', 'faas', 'lib'], 12 | license='MIT', 13 | classifiers = [ 14 | 'Programming Language :: Python :: 2', 15 | 'Programming Language :: Python :: 3' 16 | ], 17 | ) 18 | --------------------------------------------------------------------------------