cruds 1.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
cruds-1.0.2/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 johnbrandborg
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, 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,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
cruds-1.0.2/PKG-INFO ADDED
@@ -0,0 +1,113 @@
1
+ Metadata-Version: 2.1
2
+ Name: cruds
3
+ Version: 1.0.2
4
+ Summary: CRUDs is a simple high level library for Humans, inspired by Python Requests
5
+ Home-page: https://github.com/johnbrandborg/cruds
6
+ Author: John Brandborg
7
+ Author-email: john.brandborg+pypi@pm.me
8
+ License: MIT
9
+ Project-URL: Source, https://github.com/johnbrandborg/cruds
10
+ Project-URL: Tracker, https://github.com/johnbrandborg/cruds/issues
11
+ Keywords: rest api crud http https
12
+ Platform: unix
13
+ Platform: linux
14
+ Platform: osx
15
+ Platform: cygwin
16
+ Platform: win32
17
+ Classifier: Development Status :: 5 - Production/Stable
18
+ Classifier: Intended Audience :: Developers
19
+ Classifier: License :: OSI Approved :: MIT License
20
+ Classifier: Operating System :: MacOS :: MacOS X
21
+ Classifier: Operating System :: Microsoft :: Windows
22
+ Classifier: Operating System :: POSIX
23
+ Classifier: Programming Language :: Python :: 3
24
+ Classifier: Programming Language :: Python :: 3 :: Only
25
+ Classifier: Programming Language :: Python :: 3.8
26
+ Classifier: Programming Language :: Python :: 3.9
27
+ Classifier: Programming Language :: Python :: 3.10
28
+ Classifier: Programming Language :: Python :: 3.11
29
+ Classifier: Topic :: Software Development :: Libraries
30
+ Classifier: Topic :: Software Development :: Testing
31
+ Classifier: Topic :: Utilities
32
+ Requires-Python: >=3.8
33
+ Description-Content-Type: text/markdown
34
+ License-File: LICENSE
35
+ Requires-Dist: certifi
36
+ Requires-Dist: urllib3
37
+ Provides-Extra: develop
38
+ Requires-Dist: flake8; extra == "develop"
39
+ Requires-Dist: pytest; extra == "develop"
40
+ Requires-Dist: pytest-cov; extra == "develop"
41
+
42
+ # CRUDs
43
+ [![Python appliction](https://github.com/johnbrandborg/cruds/workflows/Python%20application/badge.svg)](https://github.com/johnbrandborg/cruds/actions?query=workflow%3A%22Python+application%22)
44
+ [![PyPI version](https://badge.fury.io/py/cruds.svg)](https://pypi.org/project/cruds/)
45
+ [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=johnbrandborg_cruds&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=johnbrandborg_cruds)
46
+
47
+ CRUDs is a simple high level library for Humans, inspired by [Python Requests](https://requests.readthedocs.io/en/latest/)
48
+
49
+ ```python
50
+ >>> import cruds
51
+ >>> catfact_ninja = cruds.Client(host="https://catfact.ninja/")
52
+ >>> data = catfact_ninja.read("fact")
53
+ ```
54
+
55
+ Interact with RESTful API using Create, Read, Update and Delete requests.
56
+ Focus on using Python data types instead of worrying about serialization.
57
+ Authentication, timeouts, retries, and rate limit back-off are all built in
58
+ and can be adjusted.
59
+
60
+ ### Installation
61
+
62
+ You can install the client using PIP like so.
63
+
64
+ ```bash
65
+ pip install cruds
66
+ ```
67
+
68
+ ### Logging
69
+
70
+ If you want to see logging set the level using the standard logging interface.
71
+ DEBUG will show you URLLib3, but INFO will give you general information from
72
+ the cruds.
73
+
74
+ ``` python
75
+ >>> import logging
76
+ >>> import cruds
77
+ >>> logging.basicConfig(level=logging.INFO)
78
+ ```
79
+
80
+ ### Extensibility
81
+
82
+ The library has been created with extensibility in mind. You can Sub Class Client
83
+ for example and add the logic requirements needed to make the requests.
84
+
85
+ ```python
86
+ from cruds import Client
87
+
88
+ class CatFactNinja(Client):
89
+ """Cat Fact Ninja Interface"""
90
+
91
+ _fact_uri = "fact"
92
+
93
+ def __init__(self, **kwargs):
94
+ host = "http://catfact.ninja/"
95
+ super().__init__(host=host, **kwargs)
96
+
97
+ @property
98
+ def fact(self):
99
+ """ Get a Fact about Cats"""
100
+ return self.read(self._fact_uri)
101
+
102
+ cat = CatFactNinja()
103
+ print(cat.fact)
104
+ ```
105
+
106
+ ## Todo List
107
+ - [ ] OAuth Client for Authentication
108
+
109
+ ## License
110
+ cruds is released under the MIT License. See the bundled LICENSE file for details.
111
+
112
+ ## Credits
113
+ * [URLLib3 Team](https://github.com/urllib3)
cruds-1.0.2/README.md ADDED
@@ -0,0 +1,72 @@
1
+ # CRUDs
2
+ [![Python appliction](https://github.com/johnbrandborg/cruds/workflows/Python%20application/badge.svg)](https://github.com/johnbrandborg/cruds/actions?query=workflow%3A%22Python+application%22)
3
+ [![PyPI version](https://badge.fury.io/py/cruds.svg)](https://pypi.org/project/cruds/)
4
+ [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=johnbrandborg_cruds&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=johnbrandborg_cruds)
5
+
6
+ CRUDs is a simple high level library for Humans, inspired by [Python Requests](https://requests.readthedocs.io/en/latest/)
7
+
8
+ ```python
9
+ >>> import cruds
10
+ >>> catfact_ninja = cruds.Client(host="https://catfact.ninja/")
11
+ >>> data = catfact_ninja.read("fact")
12
+ ```
13
+
14
+ Interact with RESTful API using Create, Read, Update and Delete requests.
15
+ Focus on using Python data types instead of worrying about serialization.
16
+ Authentication, timeouts, retries, and rate limit back-off are all built in
17
+ and can be adjusted.
18
+
19
+ ### Installation
20
+
21
+ You can install the client using PIP like so.
22
+
23
+ ```bash
24
+ pip install cruds
25
+ ```
26
+
27
+ ### Logging
28
+
29
+ If you want to see logging set the level using the standard logging interface.
30
+ DEBUG will show you URLLib3, but INFO will give you general information from
31
+ the cruds.
32
+
33
+ ``` python
34
+ >>> import logging
35
+ >>> import cruds
36
+ >>> logging.basicConfig(level=logging.INFO)
37
+ ```
38
+
39
+ ### Extensibility
40
+
41
+ The library has been created with extensibility in mind. You can Sub Class Client
42
+ for example and add the logic requirements needed to make the requests.
43
+
44
+ ```python
45
+ from cruds import Client
46
+
47
+ class CatFactNinja(Client):
48
+ """Cat Fact Ninja Interface"""
49
+
50
+ _fact_uri = "fact"
51
+
52
+ def __init__(self, **kwargs):
53
+ host = "http://catfact.ninja/"
54
+ super().__init__(host=host, **kwargs)
55
+
56
+ @property
57
+ def fact(self):
58
+ """ Get a Fact about Cats"""
59
+ return self.read(self._fact_uri)
60
+
61
+ cat = CatFactNinja()
62
+ print(cat.fact)
63
+ ```
64
+
65
+ ## Todo List
66
+ - [ ] OAuth Client for Authentication
67
+
68
+ ## License
69
+ cruds is released under the MIT License. See the bundled LICENSE file for details.
70
+
71
+ ## Credits
72
+ * [URLLib3 Team](https://github.com/urllib3)
@@ -0,0 +1,26 @@
1
+ # MIT License
2
+ #
3
+ # Copyright (c) 2020 johnbrandborg
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all 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,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+ """
23
+ RESTful is a simple API library for Humans, inspired by Python Requests
24
+ """
25
+
26
+ from .core import Client
@@ -0,0 +1,316 @@
1
+ """
2
+ Clients that can be used for easily accessing RESTful APIs
3
+ """
4
+
5
+ import logging
6
+ import sys
7
+ from typing import Any, Dict, List, Union
8
+ from urllib.parse import urlencode
9
+
10
+ import certifi
11
+ import urllib3
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ DEFAULT_TIMEOUT = 20.0
16
+
17
+
18
+ class Client:
19
+ """
20
+ Represents an platform interface that supports CRUD operations as methods.
21
+
22
+ Data supplied as Dictionaries are automatically serialised and deserialized
23
+ as JSON. All parameters are key-word values, and positional arguments are
24
+ not accepted.
25
+
26
+ Attributes
27
+ ----------
28
+ status_whitelist : list
29
+ A list of status codes to ignore by instances with raise for status
30
+ enabled.
31
+
32
+ Methods
33
+ -------
34
+ create:
35
+ Makes a POST request to the API Server
36
+ read:
37
+ Makes a GET request to the API Server
38
+ update:
39
+ Makes a PATCH or PUT request to the API Server
40
+ delete:
41
+ Makes a DELETE request to the API Server
42
+ """
43
+
44
+ status_whitelist: List[int] = []
45
+
46
+ def __init__(self,
47
+ *,
48
+ host: str,
49
+ auth=None,
50
+ manager=None,
51
+ timeout=DEFAULT_TIMEOUT,
52
+ raise_status=True,
53
+ retries=4,
54
+ backoff_factor=0.9,
55
+ retry_status_codes=(504, 503, 502, 500, 429),
56
+ serialize=True,
57
+ verify_ssl=True,
58
+ ) -> None:
59
+ """
60
+ Constructs all the necessary attributes for the API object.
61
+
62
+ Parameters
63
+ ----------
64
+ host: str
65
+ The host name of the API server connections will be made too.
66
+ auth: str or tuple, optional
67
+ A bearer token can be supplied, or a tuple with the username
68
+ and password.
69
+ manager: URLLib3 PoolManager, optional
70
+ You can supply a PoolManager with custom configuration.
71
+ timeout : float, optional
72
+ How long to wait before the connection is considered to be
73
+ taking to long and cancelled.
74
+ raise_status : bool, optional
75
+ If a status code of 400-599 is returned in a response will an
76
+ exception is raised.
77
+ retries : int, optional
78
+ How many times to retry connecting to the host.
79
+ backoff_factor : float, optional
80
+ How much delay should be added with each retry.
81
+ retry_status_codes : typle[int], optional
82
+ Status codes that will trigger retries.
83
+ (default is (504, 503, 502, 429))
84
+ serialize : boolaen, optional
85
+ Serialize and Deserialize dictionaires for data sent and received.
86
+ (default is True)
87
+ verify_ssl : boolean, optional
88
+ Verify the SSL certificate with Certificate Authorities.
89
+ (default is True)
90
+ """
91
+ self.host: str = host if host.endswith("/") else host + "/"
92
+ self.serialize = serialize
93
+ logger.info("API Operation Timeout(sec): %s, Raises Exceptions on status: %s",
94
+ timeout,
95
+ raise_status)
96
+
97
+ self.raise_status: bool = raise_status
98
+ self.timeout: float = timeout
99
+
100
+ if isinstance(manager, urllib3.PoolManager):
101
+ logger.info("Using supplied HTTP Manager")
102
+ self._http = manager
103
+ else:
104
+ if retries:
105
+ logger.info("Retries: %s attempts (backoff factor %s) for status codes %s",
106
+ retries,
107
+ backoff_factor,
108
+ ', '.join([str(i) for i in retry_status_codes]))
109
+
110
+ retry = urllib3.Retry(connect=retries,
111
+ backoff_factor=backoff_factor,
112
+ status_forcelist=retry_status_codes)
113
+ else:
114
+ logger.info("Retries: Disabled")
115
+ retry = False
116
+
117
+ self._http = urllib3.PoolManager(
118
+ cert_reqs="CERT_REQUIRED" if verify_ssl else "CERT_NONE",
119
+ ca_certs=certifi.where(),
120
+ retries=retry)
121
+
122
+ if isinstance(auth, str):
123
+ self._http.headers["Authorization"] = f"Bearer {auth}"
124
+ logger.info("Token authentication setup")
125
+ elif isinstance(auth, (list, tuple)) and len(auth) == 2:
126
+ self._http.headers = urllib3.make_headers(basic_auth=":".join(auth))
127
+ logger.info("Basic authentication setup")
128
+ else:
129
+ logger.info("No authentication setup")
130
+
131
+ self._http.headers["Content-Type"] = "application/json; charset=utf-8"
132
+
133
+ def create(self,
134
+ uri: str,
135
+ data: dict,
136
+ params: Union[Dict[Any, Any], None] = None,
137
+ ) -> Union[Dict[Any, Any], bytes]:
138
+ """
139
+ Makes a basic Create request to the API, and returns the response.
140
+
141
+ The HTTP method used is POST, and the data can be either a dictionary
142
+ that is serialised to JSON or bytes and strings that will be sent
143
+ without serialisation.
144
+
145
+ For POST requests parameters are encoded into the URL.
146
+ https://urllib3.readthedocs.io/en/stable/user-guide.html#query-parameters
147
+
148
+ Parameters
149
+ ----------
150
+ uri : str
151
+ The URI to be used to with the connection to the API
152
+ data : dict or bytes or string
153
+ Payload to be sent to the API
154
+ params : dict, optional
155
+ Parameters to be added to the URI
156
+
157
+ Returns
158
+ -------
159
+ dict if the response is JSON, otherwise bytes
160
+ """
161
+ url = self.host + uri
162
+ safe_params = f"?{urlencode(params)}" if params else ""
163
+ method = "POST"
164
+ logger.info(f"API Create Operation to {url}")
165
+
166
+ if self.serialize and isinstance(data, dict):
167
+ response = self._http.request(method,
168
+ url + safe_params,
169
+ json=data,
170
+ timeout=self.timeout)
171
+ else:
172
+ response = self._http.request(method,
173
+ url + safe_params,
174
+ body=data,
175
+ timeout=self.timeout)
176
+
177
+ return self._process_resp(method, response)
178
+
179
+ def read(self,
180
+ uri: str,
181
+ params: Union[Dict[Any, Any], None] = None,
182
+ ) -> Union[Dict[Any, Any], bytes]:
183
+ """
184
+ Makes a basic Retrieve request to the API, and returns the response
185
+
186
+ The HTTP method used is GET.
187
+
188
+ Parameters
189
+ ----------
190
+ uri : str
191
+ The URI to be used to with the connection to the API
192
+ params : dict, optional
193
+ Parameters to be added to the URI
194
+
195
+ Returns
196
+ -------
197
+ dict if the response is JSON, otherwise bytes
198
+ """
199
+ url = self.host + uri
200
+ method = "GET"
201
+ logger.info(f"API Retrieve Operation to {url}")
202
+
203
+ response = self._http.request(method,
204
+ url,
205
+ fields=params,
206
+ timeout=self.timeout)
207
+ return self._process_resp(method, response)
208
+
209
+ def update(self,
210
+ uri: str,
211
+ data: Union[Dict[Any, Any], str],
212
+ params: Union[Dict[Any, Any], None] = None,
213
+ replace: bool = False,
214
+ ) -> Union[Dict[Any, Any], bytes]:
215
+ """
216
+ Makes a basic Update request to the API, and returns the response.
217
+
218
+ The HTTP method used is PATCH (or PUT with replace enabled), and the data
219
+ can be either a dictionary that is serialised to JSON or bytes and
220
+ strings that will be sent without serialisation.
221
+
222
+ For PUT requests parameters are encoded into the URL.
223
+ https://urllib3.readthedocs.io/en/stable/user-guide.html#query-parameters
224
+
225
+ Parameters
226
+ ----------
227
+ uri : str
228
+ The URI to be used to with the connection to the API
229
+ data : dict or bytes or string
230
+ Payload to be sent to the API
231
+ params : dict, optional
232
+ Parameters to be added to the URI
233
+ replace : bool, optional
234
+ Requests a full replacement of the entire entity. Uses PUT Method.
235
+
236
+ Returns
237
+ -------
238
+ dict if the response is JSON, otherwise bytes
239
+ """
240
+ url = self.host + uri
241
+ safe_params = f"?{urlencode(params)}" if params else ""
242
+ method = "PUT" if replace else "PATCH"
243
+ logger.info(f"API Update Operation to {url}")
244
+
245
+ if self.serialize and isinstance(data, dict):
246
+ response = self._http.request(method,
247
+ url + safe_params,
248
+ json=data,
249
+ timeout=self.timeout)
250
+ else:
251
+ response = self._http.request(method,
252
+ url + safe_params,
253
+ body=data,
254
+ timeout=self.timeout)
255
+
256
+ return self._process_resp(method, response)
257
+
258
+ def delete(self,
259
+ uri: str,
260
+ params: Union[Dict[Any, Any], None] = None
261
+ ) -> Union[Dict[Any, Any], bytes]:
262
+ """
263
+ Makes a basic Delete request to the API, and returns the response
264
+
265
+ Parameters
266
+ ----------
267
+ uri : str
268
+ The URI to be used to with the connection to the API
269
+ params : dict, optional
270
+ Parameters to be added to the URI
271
+
272
+ Returns
273
+ -------
274
+ dict if the response is JSON, otherwise bytes
275
+ """
276
+ url = self.host + uri
277
+ method = "DELETE"
278
+ logger.info(f"API Delete Operation to {url}")
279
+
280
+ response = self._http.request(method,
281
+ self.host + uri,
282
+ fields=params,
283
+ timeout=self.timeout)
284
+ return self._process_resp(method, response)
285
+
286
+ def _process_resp(
287
+ self,
288
+ method: str,
289
+ response: urllib3.response.BaseHTTPResponse,
290
+ ) -> Union[Dict[Any, Any], bytes]:
291
+ """
292
+ Processes the Responce from HTTP Requests in a standardize manner, and
293
+ displays information.
294
+ """
295
+ logger.info(
296
+ f"Method: {method}, Status Code: {response.status}, "
297
+ f"Data: {sys.getsizeof(response.data)} Bytes"
298
+ )
299
+
300
+ if self.raise_status and response.status not in self.status_whitelist:
301
+ if 400 <= response.status < 500:
302
+ error_type = "Client"
303
+ elif 500 <= response.status < 600:
304
+ error_type = "Server"
305
+ else:
306
+ error_type = None
307
+
308
+ if error_type:
309
+ msg = f"{error_type} Error with status code {response.status}" \
310
+ f" Message: {response.data.decode('utf-8')}"
311
+ raise urllib3.exceptions.HTTPError(msg)
312
+
313
+ if self.serialize and 'application/json' in response.headers.get('Content-Type', ''):
314
+ return response.json()
315
+
316
+ return response.data
@@ -0,0 +1,113 @@
1
+ Metadata-Version: 2.1
2
+ Name: cruds
3
+ Version: 1.0.2
4
+ Summary: CRUDs is a simple high level library for Humans, inspired by Python Requests
5
+ Home-page: https://github.com/johnbrandborg/cruds
6
+ Author: John Brandborg
7
+ Author-email: john.brandborg+pypi@pm.me
8
+ License: MIT
9
+ Project-URL: Source, https://github.com/johnbrandborg/cruds
10
+ Project-URL: Tracker, https://github.com/johnbrandborg/cruds/issues
11
+ Keywords: rest api crud http https
12
+ Platform: unix
13
+ Platform: linux
14
+ Platform: osx
15
+ Platform: cygwin
16
+ Platform: win32
17
+ Classifier: Development Status :: 5 - Production/Stable
18
+ Classifier: Intended Audience :: Developers
19
+ Classifier: License :: OSI Approved :: MIT License
20
+ Classifier: Operating System :: MacOS :: MacOS X
21
+ Classifier: Operating System :: Microsoft :: Windows
22
+ Classifier: Operating System :: POSIX
23
+ Classifier: Programming Language :: Python :: 3
24
+ Classifier: Programming Language :: Python :: 3 :: Only
25
+ Classifier: Programming Language :: Python :: 3.8
26
+ Classifier: Programming Language :: Python :: 3.9
27
+ Classifier: Programming Language :: Python :: 3.10
28
+ Classifier: Programming Language :: Python :: 3.11
29
+ Classifier: Topic :: Software Development :: Libraries
30
+ Classifier: Topic :: Software Development :: Testing
31
+ Classifier: Topic :: Utilities
32
+ Requires-Python: >=3.8
33
+ Description-Content-Type: text/markdown
34
+ License-File: LICENSE
35
+ Requires-Dist: certifi
36
+ Requires-Dist: urllib3
37
+ Provides-Extra: develop
38
+ Requires-Dist: flake8; extra == "develop"
39
+ Requires-Dist: pytest; extra == "develop"
40
+ Requires-Dist: pytest-cov; extra == "develop"
41
+
42
+ # CRUDs
43
+ [![Python appliction](https://github.com/johnbrandborg/cruds/workflows/Python%20application/badge.svg)](https://github.com/johnbrandborg/cruds/actions?query=workflow%3A%22Python+application%22)
44
+ [![PyPI version](https://badge.fury.io/py/cruds.svg)](https://pypi.org/project/cruds/)
45
+ [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=johnbrandborg_cruds&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=johnbrandborg_cruds)
46
+
47
+ CRUDs is a simple high level library for Humans, inspired by [Python Requests](https://requests.readthedocs.io/en/latest/)
48
+
49
+ ```python
50
+ >>> import cruds
51
+ >>> catfact_ninja = cruds.Client(host="https://catfact.ninja/")
52
+ >>> data = catfact_ninja.read("fact")
53
+ ```
54
+
55
+ Interact with RESTful API using Create, Read, Update and Delete requests.
56
+ Focus on using Python data types instead of worrying about serialization.
57
+ Authentication, timeouts, retries, and rate limit back-off are all built in
58
+ and can be adjusted.
59
+
60
+ ### Installation
61
+
62
+ You can install the client using PIP like so.
63
+
64
+ ```bash
65
+ pip install cruds
66
+ ```
67
+
68
+ ### Logging
69
+
70
+ If you want to see logging set the level using the standard logging interface.
71
+ DEBUG will show you URLLib3, but INFO will give you general information from
72
+ the cruds.
73
+
74
+ ``` python
75
+ >>> import logging
76
+ >>> import cruds
77
+ >>> logging.basicConfig(level=logging.INFO)
78
+ ```
79
+
80
+ ### Extensibility
81
+
82
+ The library has been created with extensibility in mind. You can Sub Class Client
83
+ for example and add the logic requirements needed to make the requests.
84
+
85
+ ```python
86
+ from cruds import Client
87
+
88
+ class CatFactNinja(Client):
89
+ """Cat Fact Ninja Interface"""
90
+
91
+ _fact_uri = "fact"
92
+
93
+ def __init__(self, **kwargs):
94
+ host = "http://catfact.ninja/"
95
+ super().__init__(host=host, **kwargs)
96
+
97
+ @property
98
+ def fact(self):
99
+ """ Get a Fact about Cats"""
100
+ return self.read(self._fact_uri)
101
+
102
+ cat = CatFactNinja()
103
+ print(cat.fact)
104
+ ```
105
+
106
+ ## Todo List
107
+ - [ ] OAuth Client for Authentication
108
+
109
+ ## License
110
+ cruds is released under the MIT License. See the bundled LICENSE file for details.
111
+
112
+ ## Credits
113
+ * [URLLib3 Team](https://github.com/urllib3)
@@ -0,0 +1,13 @@
1
+ LICENSE
2
+ README.md
3
+ setup.cfg
4
+ setup.py
5
+ cruds/__init__.py
6
+ cruds/core.py
7
+ cruds.egg-info/PKG-INFO
8
+ cruds.egg-info/SOURCES.txt
9
+ cruds.egg-info/dependency_links.txt
10
+ cruds.egg-info/requires.txt
11
+ cruds.egg-info/top_level.txt
12
+ tests/__init__.py
13
+ tests/test_core.py
@@ -0,0 +1,7 @@
1
+ certifi
2
+ urllib3
3
+
4
+ [develop]
5
+ flake8
6
+ pytest
7
+ pytest-cov
@@ -0,0 +1,2 @@
1
+ cruds
2
+ tests
cruds-1.0.2/setup.cfg ADDED
@@ -0,0 +1,74 @@
1
+ [metadata]
2
+ name = cruds
3
+ version = 1.0.2
4
+ description = CRUDs is a simple high level library for Humans, inspired by Python Requests
5
+ long_description = file: README.md
6
+ long_description_content_type = text/markdown
7
+ url = https://github.com/johnbrandborg/cruds
8
+ author = John Brandborg
9
+ author_email = john.brandborg+pypi@pm.me
10
+ license = MIT
11
+ license_file = LICENSE
12
+ platforms = unix, linux, osx, cygwin, win32
13
+ classifiers =
14
+ Development Status :: 5 - Production/Stable
15
+ Intended Audience :: Developers
16
+ License :: OSI Approved :: MIT License
17
+ Operating System :: MacOS :: MacOS X
18
+ Operating System :: Microsoft :: Windows
19
+ Operating System :: POSIX
20
+ Programming Language :: Python :: 3
21
+ Programming Language :: Python :: 3 :: Only
22
+ Programming Language :: Python :: 3.8
23
+ Programming Language :: Python :: 3.9
24
+ Programming Language :: Python :: 3.10
25
+ Programming Language :: Python :: 3.11
26
+ Topic :: Software Development :: Libraries
27
+ Topic :: Software Development :: Testing
28
+ Topic :: Utilities
29
+ keywords = rest api crud http https
30
+ project_urls =
31
+ Source=https://github.com/johnbrandborg/cruds
32
+ Tracker=https://github.com/johnbrandborg/cruds/issues
33
+
34
+ [options]
35
+ packages = find:
36
+ install_requires =
37
+ certifi
38
+ urllib3
39
+ python_requires = >=3.8
40
+
41
+ [options.extras_require]
42
+ develop =
43
+ flake8
44
+ pytest
45
+ pytest-cov
46
+
47
+ [tool:pytest]
48
+ addopts = --cov=. tests/
49
+
50
+ [coverage:run]
51
+ relative_files = True
52
+ omit =
53
+ env/*
54
+ .env/*
55
+ venv/*
56
+ tests/*
57
+ setup.py
58
+
59
+ [coverage:xml]
60
+ output = coverage.xml
61
+
62
+ [coverage:report]
63
+ show_missing = true
64
+ exclude_lines =
65
+ ^if __name__ ==
66
+ ^\s*except KeyboardInterrupt
67
+
68
+ [flake8]
69
+ per-file-ignores = __init__.py:F401
70
+
71
+ [egg_info]
72
+ tag_build =
73
+ tag_date = 0
74
+
cruds-1.0.2/setup.py ADDED
@@ -0,0 +1,4 @@
1
+ from setuptools import setup
2
+
3
+ if __name__ == "__main__":
4
+ setup()
File without changes
@@ -0,0 +1,233 @@
1
+ """
2
+ Pytests for Platforms in RESTful Platform2
3
+ """
4
+
5
+ from unittest import mock
6
+ import pytest
7
+ import urllib3
8
+
9
+ import cruds
10
+ from cruds.core import DEFAULT_TIMEOUT
11
+
12
+
13
+ def test_Platform_token_authentication():
14
+ """
15
+ Supplying an 'auth' string will placed into the header for bearer token
16
+ authenticationi.
17
+ """
18
+ api = cruds.Client(host="https://localhost", auth="api_token")
19
+ assert api._http.headers.get("Authorization") == "Bearer api_token"
20
+
21
+
22
+ def test_Platform_basic_authentication():
23
+ """
24
+ Supplying an 'auth' tuple or list will placed into the header for basic
25
+ authentication.
26
+ """
27
+ api = cruds.Client(host="https://localhost", auth=("username", "password"))
28
+ assert api._http.headers.get("authorization") == "Basic dXNlcm5hbWU6cGFzc3dvcmQ="
29
+
30
+
31
+ def test_Platform_creates_urllib3_manager():
32
+ """
33
+ When no URLLib3 manager is suppied, a manager is automatically created.
34
+ """
35
+ api = cruds.Client(host="https://localhost")
36
+ assert isinstance(api._http, urllib3.PoolManager)
37
+
38
+
39
+ def test_Platform_use_supplied_urllib3_manager():
40
+ """
41
+ When a URLLib3 manager is suppied use it, instead of creating one.
42
+ """
43
+ api = cruds.Client(host="https://localhost", manager=urllib3.PoolManager())
44
+ assert isinstance(api._http, urllib3.PoolManager)
45
+
46
+
47
+ def test_Platform_disable_retries():
48
+ """ Setting the retries to 0 or None will disable retries being used """
49
+ api = cruds.Client(host="https://localhost", retries=0)
50
+ assert api._http.connection_pool_kw.get("retries") == 0
51
+
52
+ cruds.Client(host="https://localhost", retries=None)
53
+ assert api._http.connection_pool_kw.get("retries") == 0
54
+
55
+
56
+ @pytest.fixture
57
+ def crud_api():
58
+ """
59
+ Creates a Platform Platform without response processing.
60
+ """
61
+ api = cruds.Client(host="https://localhost", retries=0)
62
+ mock_resp = urllib3.HTTPResponse(body=b'{"name": "test"}')
63
+ api._http.request = mock.Mock(return_value=mock_resp)
64
+ api._process_resp = lambda method, resp: resp
65
+ return api
66
+
67
+
68
+ def test_Platform_create_operation(crud_api):
69
+ """ Check the Create Operation formats the request properly """
70
+ sample = {"test_name": "test_Platform_create_operation"}
71
+ resp = crud_api.create("user/1", data=sample)
72
+
73
+ crud_api._http.request.assert_called_with("POST",
74
+ "https://localhost/user/1",
75
+ json=sample,
76
+ timeout=DEFAULT_TIMEOUT)
77
+ assert resp.data == b'{"name": "test"}'
78
+
79
+
80
+ def test_Platform_create_operation_with_bytes(crud_api):
81
+ """
82
+ Check the Create Operation formats the request properly.
83
+ """
84
+ sample = b'{"test_name": "test_Platform_create_operation"}'
85
+ resp = crud_api.create("user/2", data=sample)
86
+
87
+ crud_api._http.request.assert_called_with("POST",
88
+ "https://localhost/user/2",
89
+ body=sample,
90
+ timeout=DEFAULT_TIMEOUT)
91
+ assert resp.data == b'{"name": "test"}'
92
+
93
+
94
+ def test_Platform_read_operation(crud_api):
95
+ """
96
+ Check the Read Operation formats the request properly.
97
+ """
98
+ resp = crud_api.read("test")
99
+
100
+ crud_api._http.request.assert_called_with("GET",
101
+ "https://localhost/test",
102
+ fields=None,
103
+ timeout=DEFAULT_TIMEOUT)
104
+ assert resp.data == b'{"name": "test"}'
105
+
106
+
107
+ def test_Platform_update_operation(crud_api):
108
+ """
109
+ Check the Update Operation formats the request properly.
110
+ """
111
+ sample = {"test_name": "test_Platform_update_operation"}
112
+ resp = crud_api.update("test", data=sample)
113
+
114
+ crud_api._http.request.assert_called_with("PATCH",
115
+ "https://localhost/test",
116
+ json=sample,
117
+ timeout=DEFAULT_TIMEOUT)
118
+ assert resp.data == b'{"name": "test"}'
119
+
120
+
121
+ def test_Platform_update_operation_with_bytes(crud_api):
122
+ """
123
+ Check the Update Operation formats the request properly.
124
+ """
125
+ sample = b'{"test_name": "test_Platform_update_operation"}'
126
+ resp = crud_api.update("test", data=sample)
127
+
128
+ crud_api._http.request.assert_called_with("PATCH",
129
+ "https://localhost/test",
130
+ body=sample,
131
+ timeout=DEFAULT_TIMEOUT)
132
+ assert resp.data == b'{"name": "test"}'
133
+
134
+
135
+ def test_Platform_update_operation_with_replace(crud_api):
136
+ """
137
+ Check the Update Operation formats the request properly.
138
+ """
139
+ sample = {"test_name": "test_Platform_update_operation"}
140
+ resp = crud_api.update("test", data=sample, replace=True)
141
+
142
+ crud_api._http.request.assert_called_with("PUT",
143
+ "https://localhost/test",
144
+ json=sample,
145
+ timeout=DEFAULT_TIMEOUT)
146
+ assert resp.data == b'{"name": "test"}'
147
+
148
+
149
+ def test_Platform_delete_operation(crud_api):
150
+ """
151
+ Check the Delete Operation formats the request properly.
152
+ """
153
+ resp = crud_api.delete("test")
154
+
155
+ crud_api._http.request.assert_called_with("DELETE",
156
+ "https://localhost/test",
157
+ fields=None,
158
+ timeout=DEFAULT_TIMEOUT)
159
+ assert resp.data == b'{"name": "test"}'
160
+
161
+
162
+ def test_Platform_process_resp_return_bytes():
163
+ """
164
+ Check the response processing returns bytes for non-JSON content.
165
+ """
166
+ api = cruds.Client(host="https://localhost")
167
+ mock_resp = urllib3.HTTPResponse(body=b"name=test_Platform_process_resp_return_bytes", status=399)
168
+ assert api._process_resp("", mock_resp) == b"name=test_Platform_process_resp_return_bytes"
169
+
170
+
171
+ def test_Platform_process_resp_return_bytes_with_serialize_false():
172
+ """
173
+ Check the response processing returns bytes for JSON content when serialize
174
+ is disabled.
175
+ """
176
+ api = cruds.Client(host="https://localhost", serialize=False)
177
+ mock_resp = urllib3.HTTPResponse(
178
+ body=b'{"name": "test_Platform_process_resp_return_bytes_with_serialize_false"}',
179
+ headers={"Content-Type": "application/json; charset=utf-8"})
180
+ assert api._process_resp("", mock_resp) == b'{"name": "test_Platform_process_resp_return_bytes_with_serialize_false"}'
181
+
182
+
183
+ def test_Platform_process_resp_return_dictionary():
184
+ """
185
+ Check the response processing returns a dictionary for JSON content.
186
+ """
187
+ api = cruds.Client(host="https://localhost")
188
+ mock_resp = urllib3.HTTPResponse(
189
+ body=b'{"name": "test_Platform_process_resp_return_dictionary"}',
190
+ headers={"Content-Type": "application/json; charset=utf-8"},
191
+ status=399)
192
+ assert api._process_resp("", mock_resp) == {"name": "test_Platform_process_resp_return_dictionary"}
193
+
194
+
195
+ def test_Platform_raise_status_399():
196
+ """
197
+ Check the response return code of 399 doesn't raise an exceptions.
198
+ """
199
+ api = cruds.Client(host="https://localhost")
200
+ mock_resp = urllib3.HTTPResponse(body=b'{"name": "test"}', status=399)
201
+ api._process_resp("", mock_resp)
202
+
203
+
204
+ def test_Platform_raise_status_400():
205
+ """
206
+ Check the response status code 400 raises an exception.
207
+ """
208
+ api = cruds.Client(host="https://localhost")
209
+ mock_resp = urllib3.HTTPResponse(body=b'{"name": "test"}', status=400)
210
+ with pytest.raises(urllib3.exceptions.HTTPError):
211
+ api._process_resp("", mock_resp)
212
+
213
+
214
+ def test_Platform_raise_status_500():
215
+ """
216
+ Check the response status code 500 raises an exception.
217
+ """
218
+ api = cruds.Client(host="https://localhost")
219
+ api = cruds.Client(host="https://localhost")
220
+ mock_resp = urllib3.HTTPResponse(body=b'{"name": "test"}', status=500)
221
+ with pytest.raises(urllib3.exceptions.HTTPError):
222
+ api._process_resp("", mock_resp)
223
+
224
+
225
+ def test_Platform_raise_status_whitelist():
226
+ """
227
+ Check the response status code 400 doesn't raises an exception when whitelisted.
228
+ """
229
+ api = cruds.Client(host="https://localhost")
230
+ api = cruds.Client(host="https://localhost", retries=0)
231
+ api.status_whitelist.append(400)
232
+ mock_resp = urllib3.HTTPResponse(body=b'{"name": "test"}', status=400)
233
+ api._process_resp("", mock_resp)