ubersmith-api 1.0.0__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.
- ubersmith_api-1.0.0/LICENSE +21 -0
- ubersmith_api-1.0.0/PKG-INFO +109 -0
- ubersmith_api-1.0.0/README.md +81 -0
- ubersmith_api-1.0.0/pyproject.toml +49 -0
- ubersmith_api-1.0.0/setup.cfg +4 -0
- ubersmith_api-1.0.0/src/ubersmith/__init__.py +5 -0
- ubersmith_api-1.0.0/src/ubersmith/__version__.py +2 -0
- ubersmith_api-1.0.0/src/ubersmith/api/core.py +96 -0
- ubersmith_api-1.0.0/src/ubersmith/client/__init__.py +6 -0
- ubersmith_api-1.0.0/src/ubersmith/client/api.py +42 -0
- ubersmith_api-1.0.0/src/ubersmith/client/base.py +74 -0
- ubersmith_api-1.0.0/src/ubersmith/config.py +101 -0
- ubersmith_api-1.0.0/src/ubersmith/util/__init__.py +0 -0
- ubersmith_api-1.0.0/src/ubersmith/util/cleaners.py +48 -0
- ubersmith_api-1.0.0/src/ubersmith/util/exceptions.py +19 -0
- ubersmith_api-1.0.0/src/ubersmith/util/files.py +16 -0
- ubersmith_api-1.0.0/src/ubersmith/util/parse.py +41 -0
- ubersmith_api-1.0.0/src/ubersmith_api.egg-info/PKG-INFO +109 -0
- ubersmith_api-1.0.0/src/ubersmith_api.egg-info/SOURCES.txt +20 -0
- ubersmith_api-1.0.0/src/ubersmith_api.egg-info/dependency_links.txt +1 -0
- ubersmith_api-1.0.0/src/ubersmith_api.egg-info/requires.txt +6 -0
- ubersmith_api-1.0.0/src/ubersmith_api.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Ben Nassif
|
|
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.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ubersmith-api
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Version-specific Python 3 wrapper for the Ubersmith API
|
|
5
|
+
Author-email: Ben Nassif <bennassif@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: homepage, https://github.com/bnassif/ubersmith-api
|
|
8
|
+
Project-URL: issues, https://github.com/bnassif/ubersmith-api/issues
|
|
9
|
+
Keywords: api,wrapper,ubersmith,client
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Natural Language :: English
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: requests>=1.0.4
|
|
23
|
+
Requires-Dist: pydantic-settings>=2.1.0
|
|
24
|
+
Requires-Dist: pydantic>=2.5.2
|
|
25
|
+
Provides-Extra: build
|
|
26
|
+
Requires-Dist: jinja2; extra == "build"
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# Python Ubersmith
|
|
30
|
+
[](https://pypi.org/project/ubersmith-api)
|
|
31
|
+
[](https://raw.githubusercontent.com/bnassif/ubersmith-api/main/LICENSE)
|
|
32
|
+

|
|
33
|
+
|
|
34
|
+
A fully-featured API wrapper for the [Ubersmith](https://ubersmith.com/) API
|
|
35
|
+
|
|
36
|
+
Python wrappers exist on PyPI and across GitHub for interacting with the Ubersmith API, but each of them fails to provide full compatibility.
|
|
37
|
+
|
|
38
|
+
In comes `ubersmith-api`...
|
|
39
|
+
|
|
40
|
+
## Overview
|
|
41
|
+
|
|
42
|
+
At its core, this package provides a generic API wrapper class, `UbersmithClient` which allows for uncontrolled calls to an Ubersmith instance.
|
|
43
|
+
|
|
44
|
+
Built atop this wrapper, a templating suite is available for generating classes for each API section in Ubersmith, specific to each Ubersmith version to maximize compatibility.
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# PyPi Installation
|
|
50
|
+
pip install ubersmith-api
|
|
51
|
+
# GitHub Installation
|
|
52
|
+
pip install git+'https://github.com/bnassif/ubersmith-api.git'
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Getting Started
|
|
56
|
+
|
|
57
|
+
### Instantiating the Base Client
|
|
58
|
+
```python
|
|
59
|
+
from ubersmith_api import *
|
|
60
|
+
|
|
61
|
+
config = UbersmithConfig(
|
|
62
|
+
host='target-hostname-or-address',
|
|
63
|
+
username='username-to-use',
|
|
64
|
+
password='api-token-for-user',
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
client = UbersmithClient(config)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Making Calls
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
response_obj = client.request(
|
|
74
|
+
'uber.method_get',
|
|
75
|
+
data={
|
|
76
|
+
'method_name': 'client.get',
|
|
77
|
+
},
|
|
78
|
+
#raw=True,
|
|
79
|
+
)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
By default, the `request()` method will parse the response from the API, checking for HTTP error codes, as well as error messages from Ubersmith itself. If no errors are found, the returned `data` field is returned.
|
|
83
|
+
|
|
84
|
+
Alternatively, you can parse `raw=True` to return the `requests.Response` object for manual parsing and error checking.
|
|
85
|
+
|
|
86
|
+
### Shipped Methods
|
|
87
|
+
The `UbersmithClient` class comes with three (3) core methods shipped.
|
|
88
|
+
These offer a simplified entrypoint to gathering parsed information of an Ubersmith system.
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
# Get system information: calls `uber.system_info`
|
|
92
|
+
sys_info = client.system_info()
|
|
93
|
+
|
|
94
|
+
# Get all available methods: calls `uber.method_list`
|
|
95
|
+
all_methods = client.method_list()
|
|
96
|
+
|
|
97
|
+
# Get details of one method: calls `uber.method_get`
|
|
98
|
+
method_details = client.method_get('client.get')
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Future Compatibility
|
|
102
|
+
|
|
103
|
+
**NOTE**:
|
|
104
|
+
|
|
105
|
+
Additional code is shipped in this repository, along with version-specific schemas for the Ubersmith API.
|
|
106
|
+
These features will be enabled in later releases of the package.
|
|
107
|
+
|
|
108
|
+
## License
|
|
109
|
+
MIT - Feel free to use, extend, and contribute.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Python Ubersmith
|
|
2
|
+
[](https://pypi.org/project/ubersmith-api)
|
|
3
|
+
[](https://raw.githubusercontent.com/bnassif/ubersmith-api/main/LICENSE)
|
|
4
|
+

|
|
5
|
+
|
|
6
|
+
A fully-featured API wrapper for the [Ubersmith](https://ubersmith.com/) API
|
|
7
|
+
|
|
8
|
+
Python wrappers exist on PyPI and across GitHub for interacting with the Ubersmith API, but each of them fails to provide full compatibility.
|
|
9
|
+
|
|
10
|
+
In comes `ubersmith-api`...
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
|
|
14
|
+
At its core, this package provides a generic API wrapper class, `UbersmithClient` which allows for uncontrolled calls to an Ubersmith instance.
|
|
15
|
+
|
|
16
|
+
Built atop this wrapper, a templating suite is available for generating classes for each API section in Ubersmith, specific to each Ubersmith version to maximize compatibility.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# PyPi Installation
|
|
22
|
+
pip install ubersmith-api
|
|
23
|
+
# GitHub Installation
|
|
24
|
+
pip install git+'https://github.com/bnassif/ubersmith-api.git'
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Getting Started
|
|
28
|
+
|
|
29
|
+
### Instantiating the Base Client
|
|
30
|
+
```python
|
|
31
|
+
from ubersmith_api import *
|
|
32
|
+
|
|
33
|
+
config = UbersmithConfig(
|
|
34
|
+
host='target-hostname-or-address',
|
|
35
|
+
username='username-to-use',
|
|
36
|
+
password='api-token-for-user',
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
client = UbersmithClient(config)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Making Calls
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
response_obj = client.request(
|
|
46
|
+
'uber.method_get',
|
|
47
|
+
data={
|
|
48
|
+
'method_name': 'client.get',
|
|
49
|
+
},
|
|
50
|
+
#raw=True,
|
|
51
|
+
)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
By default, the `request()` method will parse the response from the API, checking for HTTP error codes, as well as error messages from Ubersmith itself. If no errors are found, the returned `data` field is returned.
|
|
55
|
+
|
|
56
|
+
Alternatively, you can parse `raw=True` to return the `requests.Response` object for manual parsing and error checking.
|
|
57
|
+
|
|
58
|
+
### Shipped Methods
|
|
59
|
+
The `UbersmithClient` class comes with three (3) core methods shipped.
|
|
60
|
+
These offer a simplified entrypoint to gathering parsed information of an Ubersmith system.
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
# Get system information: calls `uber.system_info`
|
|
64
|
+
sys_info = client.system_info()
|
|
65
|
+
|
|
66
|
+
# Get all available methods: calls `uber.method_list`
|
|
67
|
+
all_methods = client.method_list()
|
|
68
|
+
|
|
69
|
+
# Get details of one method: calls `uber.method_get`
|
|
70
|
+
method_details = client.method_get('client.get')
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Future Compatibility
|
|
74
|
+
|
|
75
|
+
**NOTE**:
|
|
76
|
+
|
|
77
|
+
Additional code is shipped in this repository, along with version-specific schemas for the Ubersmith API.
|
|
78
|
+
These features will be enabled in later releases of the package.
|
|
79
|
+
|
|
80
|
+
## License
|
|
81
|
+
MIT - Feel free to use, extend, and contribute.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "ubersmith-api"
|
|
3
|
+
authors = [
|
|
4
|
+
{ name="Ben Nassif", email="bennassif@gmail.com" }
|
|
5
|
+
]
|
|
6
|
+
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
|
|
9
|
+
description = "Version-specific Python 3 wrapper for the Ubersmith API"
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Intended Audience :: Developers",
|
|
15
|
+
"Natural Language :: English",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.8",
|
|
19
|
+
"Programming Language :: Python :: 3.9",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
]
|
|
24
|
+
keywords = [
|
|
25
|
+
"api", "wrapper", "ubersmith", "client"
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
license = "MIT"
|
|
29
|
+
license-files = [
|
|
30
|
+
"LICEN[CS]E"
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
dependencies = [
|
|
34
|
+
"requests >= 1.0.4",
|
|
35
|
+
"pydantic-settings >= 2.1.0",
|
|
36
|
+
"pydantic >= 2.5.2",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
[tool.setuptools.dynamic]
|
|
40
|
+
version = { attr = "ubersmith.__version__.__version__" }
|
|
41
|
+
|
|
42
|
+
[project.urls]
|
|
43
|
+
homepage = "https://github.com/bnassif/ubersmith-api"
|
|
44
|
+
issues = "https://github.com/bnassif/ubersmith-api/issues"
|
|
45
|
+
|
|
46
|
+
[project.optional-dependencies]
|
|
47
|
+
build = [
|
|
48
|
+
"jinja2",
|
|
49
|
+
]
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from ubersmith.client.api import *
|
|
2
|
+
import inspect
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
'UbersmithClient',
|
|
6
|
+
'UbersmithConfig',
|
|
7
|
+
'Section',
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
class Section:
|
|
11
|
+
"""
|
|
12
|
+
Base API Section Class
|
|
13
|
+
|
|
14
|
+
This class is used to represent an API section in the Ubersmith API. It's a simple entry to binding
|
|
15
|
+
a client to an arbitrary class, which will then have methods which call on the bound client.
|
|
16
|
+
"""
|
|
17
|
+
_client: UbersmithClient
|
|
18
|
+
|
|
19
|
+
def __init__(self, client: UbersmithClient = None, config: UbersmithConfig = None):
|
|
20
|
+
"""Instantiate a Client Section by binding a client to it.
|
|
21
|
+
|
|
22
|
+
If a client is not supplied, attempt loading the configuration from the environment
|
|
23
|
+
and binding a client bound to that configuration instead. Optionally, provide a configuration
|
|
24
|
+
instead of a client.
|
|
25
|
+
|
|
26
|
+
An error will be thrown if both a client and config are supplied.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
client (UbersmithClient, optional): The client to bind to the class. Defaults to None.
|
|
30
|
+
config (UbersmithConfig, optional): The config to instantiate a client from to bind to the class. Defaults to None.
|
|
31
|
+
|
|
32
|
+
Raises:
|
|
33
|
+
Exception: A base Exception is raised when both a client and config are supplied
|
|
34
|
+
"""
|
|
35
|
+
if config and client:
|
|
36
|
+
raise Exception("Both a client and config cannot be supplied")
|
|
37
|
+
|
|
38
|
+
if client:
|
|
39
|
+
# Bind the client, if supplied
|
|
40
|
+
self._client = client
|
|
41
|
+
else:
|
|
42
|
+
# Binds a supplied config, or allows the underlying client to instantiate one
|
|
43
|
+
self._client = UbersmithClient(config=config)
|
|
44
|
+
|
|
45
|
+
def _get_parameters(self, method, inputs: dict) -> dict:
|
|
46
|
+
"""Returns the Signature of the target Method
|
|
47
|
+
|
|
48
|
+
This method is used to simplify how parameters in a method are passed to the underlying client
|
|
49
|
+
as keyword arguments to reduce templating/development work needed.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
method (method): The method to inspect
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
dict: The dictionary of *args **kwargs passed to a method
|
|
56
|
+
"""
|
|
57
|
+
data = {}
|
|
58
|
+
meta = None
|
|
59
|
+
# Get the parameters of the provided method
|
|
60
|
+
params = list(inspect.signature(method).parameters.keys())
|
|
61
|
+
|
|
62
|
+
# If meta is in the parameters, remove it prior to looping
|
|
63
|
+
# We'll add these with Ubersmith's desired structure after handlilng the initial bit
|
|
64
|
+
if 'meta' in params:
|
|
65
|
+
# Get the meta inputs separate from the rest
|
|
66
|
+
meta = inputs.pop('meta')
|
|
67
|
+
# Remove meta from the params to inspect
|
|
68
|
+
params.remove('meta')
|
|
69
|
+
|
|
70
|
+
for p in params:
|
|
71
|
+
data.update({p: inputs[p]})
|
|
72
|
+
|
|
73
|
+
if meta:
|
|
74
|
+
# If meta is supplied, add those under the `meta_` arguments
|
|
75
|
+
for k, v in meta.items():
|
|
76
|
+
data.update({f'meta_{k}': v})
|
|
77
|
+
|
|
78
|
+
return data
|
|
79
|
+
|
|
80
|
+
def _get_section_name(self):
|
|
81
|
+
return self.__class__.__name__.rstrip('API').lower()
|
|
82
|
+
|
|
83
|
+
def _request(self, method, inputs):
|
|
84
|
+
"""Sends a request using the underlying client
|
|
85
|
+
|
|
86
|
+
This method takes the calling method at the first parameter to perform inspection on it.
|
|
87
|
+
Locals are also passed from the calling parameter so they can be formed and passed to the API.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
method (method): The calling method, passed through raw
|
|
91
|
+
data (dict, optional): _description_. Defaults to None.
|
|
92
|
+
"""
|
|
93
|
+
data = self._get_parameters(method, inputs)
|
|
94
|
+
command=f'{self._get_section_name()}.{method.__name__}'
|
|
95
|
+
|
|
96
|
+
return self._client.request(command, data=data, parsed=True)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from ubersmith.client.base import *
|
|
2
|
+
|
|
3
|
+
__all__ = [
|
|
4
|
+
'UbersmithConfig',
|
|
5
|
+
'UbersmithClient',
|
|
6
|
+
]
|
|
7
|
+
|
|
8
|
+
class UbersmithClient(BaseClient):
|
|
9
|
+
"""
|
|
10
|
+
API Client for the Ubersmith API
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
config (UbersmithConfig): The configuration object for the client.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def method_list(self) -> dict:
|
|
17
|
+
"""
|
|
18
|
+
Lists all methods available in the target Ubersmith instance.
|
|
19
|
+
This is the `data` field as returned by `uber.method_list`
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
list[dict]: The list of methods available in the instance. Keys are the methods/commands and values are short descriptions.
|
|
23
|
+
"""
|
|
24
|
+
return self.request('uber.method_list')
|
|
25
|
+
|
|
26
|
+
def method_get(self, method_name: str) -> dict:
|
|
27
|
+
"""
|
|
28
|
+
Gets the details of a specific API method.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
method_name (str): The method name to gather details on (i.e. `uber.method_get`)
|
|
32
|
+
"""
|
|
33
|
+
return self.request('uber.method_get', data={'method_name': method_name})
|
|
34
|
+
|
|
35
|
+
def system_info(self) -> dict:
|
|
36
|
+
"""
|
|
37
|
+
Gets the Ubersmith service version and latest version available
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
dict: The response dictionary containing the version and latest_version
|
|
41
|
+
"""
|
|
42
|
+
return self.request('uber.system_info')
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import urllib3
|
|
3
|
+
|
|
4
|
+
from ubersmith.config import UbersmithConfig
|
|
5
|
+
from ubersmith.util.cleaners import *
|
|
6
|
+
from ubersmith.util.parse import parse_response
|
|
7
|
+
from ubersmith.util.files import get_files
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
'UbersmithConfig',
|
|
11
|
+
'BaseClient',
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
class BaseClient:
|
|
15
|
+
"""
|
|
16
|
+
API Client for the Ubersmith API
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
config (Config): The configuration object for the client.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, config: UbersmithConfig = None):
|
|
23
|
+
"""
|
|
24
|
+
Initialize the client with the provided config.
|
|
25
|
+
If a config is not supplied, attempt loading one from the environment.
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
config (Config, optional): The Config to bind to the client. If not supplied, one is loaded from the environment.
|
|
30
|
+
"""
|
|
31
|
+
self.config = config if config else UbersmithConfig()
|
|
32
|
+
|
|
33
|
+
def request(self, command: str, data: dict = None, files: dict = None, raw: bool = False) -> requests.Response:
|
|
34
|
+
"""
|
|
35
|
+
Makes a request to the Ubersmith API
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
command (str): The Ubersmith API command to call
|
|
39
|
+
data (dict, optional): The POST data to send in the request. Defaults to None.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
requests.Response: The resposne from the Ubersmith API
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
if not self.config.verify:
|
|
46
|
+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
47
|
+
|
|
48
|
+
# Clean input data, if provided
|
|
49
|
+
if data:
|
|
50
|
+
clean_all(data)
|
|
51
|
+
|
|
52
|
+
tries = 0
|
|
53
|
+
for _ in range(tries, self.config.api_tries):
|
|
54
|
+
try:
|
|
55
|
+
response = requests.post(
|
|
56
|
+
# API URL of the request
|
|
57
|
+
url=f"{self.config.api_url}/?method={command}",
|
|
58
|
+
# Pass through data, if supplied
|
|
59
|
+
data=data,
|
|
60
|
+
# Attach files, if supplied
|
|
61
|
+
files=get_files(files),
|
|
62
|
+
# Add basic authentication using the user/pass
|
|
63
|
+
auth=(self.config.username, self.config.password),
|
|
64
|
+
# Add the timeout from the config
|
|
65
|
+
timeout=self.config.api_timeout,
|
|
66
|
+
verify=self.config.verify,
|
|
67
|
+
)
|
|
68
|
+
if not raw:
|
|
69
|
+
return parse_response(response)
|
|
70
|
+
return response
|
|
71
|
+
except requests.exceptions.Timeout:
|
|
72
|
+
# Increment the tries counter and continue remaining attempts
|
|
73
|
+
tries += 1
|
|
74
|
+
continue
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from pydantic import Field
|
|
4
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
5
|
+
from os import getenv
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
'UbersmithConfig',
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
# Set the default .env file path -- this can be overridden upon instantiation with _env_file=<path>
|
|
12
|
+
default_dotenv_path = Path(
|
|
13
|
+
getenv(
|
|
14
|
+
key='UBERSMITH_API_ENV_FILE', # Allows users to specify an env variable for the config file
|
|
15
|
+
default=f"{str(Path.home())}/.ubersmith.env") # Defaults to a home directory config file
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class UbersmithConfig(BaseSettings):
|
|
20
|
+
"""
|
|
21
|
+
A class to hold the configuration settings for the PowerDNS API client.
|
|
22
|
+
"""
|
|
23
|
+
model_config = SettingsConfigDict(
|
|
24
|
+
case_sensitive=False,
|
|
25
|
+
env_prefix='UBERSMITH_',
|
|
26
|
+
env_file=default_dotenv_path.resolve(),
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
username: str = Field(
|
|
30
|
+
title="API Username",
|
|
31
|
+
description="The API username to authenticate with.",
|
|
32
|
+
repr=False
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
password: str = Field(
|
|
36
|
+
title="API Password",
|
|
37
|
+
description="The API password for the API Username.",
|
|
38
|
+
repr=False,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
host: str = Field(
|
|
42
|
+
title="API Host",
|
|
43
|
+
description="The IP address or hostname of the API server to connect to.",
|
|
44
|
+
default='localhost'
|
|
45
|
+
)
|
|
46
|
+
port: int = Field(
|
|
47
|
+
title="API Port",
|
|
48
|
+
description="The Port number of the API server to connect to.",
|
|
49
|
+
ge=1, le=65535, default=443
|
|
50
|
+
)
|
|
51
|
+
secure: bool = Field(
|
|
52
|
+
title="HTTP Security",
|
|
53
|
+
description="Whether to make secure (HTTPS) or insecure (HTTP) requests",
|
|
54
|
+
default=True,
|
|
55
|
+
)
|
|
56
|
+
version: str = Field(
|
|
57
|
+
title="API Version",
|
|
58
|
+
description="The PowerDNS API verion to use.",
|
|
59
|
+
default='2.0', repr=False
|
|
60
|
+
)
|
|
61
|
+
verify: bool = Field(
|
|
62
|
+
title="Verify Certificate",
|
|
63
|
+
description="Whether to verify the server certificate if secure is True",
|
|
64
|
+
default=True,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# API Client Settings
|
|
68
|
+
api_delay: float = Field(
|
|
69
|
+
title="API Delay",
|
|
70
|
+
description="The time to wait between retries.",
|
|
71
|
+
gt=0, le=60, default=15, repr=False
|
|
72
|
+
)
|
|
73
|
+
api_timeout: int = Field(
|
|
74
|
+
title="API Timeout",
|
|
75
|
+
description="The time to wait for a response after making a request before failing.",
|
|
76
|
+
ge=1, le=900, default=30, repr=False
|
|
77
|
+
)
|
|
78
|
+
api_tries: int = Field(
|
|
79
|
+
title="API Retries",
|
|
80
|
+
description="The amount of times to (re)attempt call before failing.",
|
|
81
|
+
gt=0, le=25, default=1, repr=False
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def _schema(self):
|
|
86
|
+
if self.secure:
|
|
87
|
+
return 'https'
|
|
88
|
+
return 'http'
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def api_url(self):
|
|
92
|
+
return f"{self._schema}://{self.host}:{self.port}/api/{self.version}"
|
|
93
|
+
|
|
94
|
+
def update(self, data: dict = None, **kwargs):
|
|
95
|
+
if data and not isinstance(data, dict):
|
|
96
|
+
raise RuntimeError(f'Input data must be a dict. Received: {data}')
|
|
97
|
+
elif kwargs and not data:
|
|
98
|
+
data = kwargs
|
|
99
|
+
elif kwargs and data:
|
|
100
|
+
data.update(kwargs)
|
|
101
|
+
self.__dict__.update(data)
|
|
File without changes
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
def bool_to_int(data: dict):
|
|
2
|
+
"""
|
|
3
|
+
Converts boolean values in a dict to integers for parsing by the Ubersmith API
|
|
4
|
+
|
|
5
|
+
Args:
|
|
6
|
+
data (dict): The dict you want cleaned
|
|
7
|
+
"""
|
|
8
|
+
for k, v in data.items():
|
|
9
|
+
# Handle all sub-items recursively
|
|
10
|
+
if isinstance(v, dict):
|
|
11
|
+
bool_to_int(v)
|
|
12
|
+
# Convert any boolean inputs to integers
|
|
13
|
+
elif isinstance(v, bool):
|
|
14
|
+
data[k] = int(v)
|
|
15
|
+
|
|
16
|
+
def passwd_to_pass(data: dict):
|
|
17
|
+
"""
|
|
18
|
+
Converts the `passwd` keyword argument to `pass` as required by the API
|
|
19
|
+
|
|
20
|
+
This allows circumventing conflicts with the keyword argument pass and Python's pass directive
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
data (dict): The dict you want cleaned
|
|
24
|
+
"""
|
|
25
|
+
for k in data.keys():
|
|
26
|
+
if k == 'passwd':
|
|
27
|
+
data['pass'] = data.pop(k)
|
|
28
|
+
|
|
29
|
+
def from_address_to_from(data: dict):
|
|
30
|
+
for k in data.keys():
|
|
31
|
+
if k == 'from_address':
|
|
32
|
+
data['from'] = data.pop(k)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def clean_all(data: dict):
|
|
36
|
+
"""
|
|
37
|
+
Runs all cleaners against inputs
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
data (dict): The dict you want cleaned
|
|
41
|
+
"""
|
|
42
|
+
cleaners = [
|
|
43
|
+
bool_to_int,
|
|
44
|
+
passwd_to_pass,
|
|
45
|
+
from_address_to_from,
|
|
46
|
+
]
|
|
47
|
+
for c in cleaners:
|
|
48
|
+
c(data)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# ubersmith/util/exceptions.py
|
|
2
|
+
|
|
3
|
+
# This module contains the core Exception class that is used to wrap error responses from
|
|
4
|
+
# the Ubersmith API. This includes the error_code and error_message as provided from the API
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
'UbersmithException',
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
class UbersmithException(Exception):
|
|
11
|
+
def __init__(self, error_code: int, error_message: str):
|
|
12
|
+
self.error_code = error_code
|
|
13
|
+
self.error_message = error_message
|
|
14
|
+
# Pass a formatted string up to the base Exception
|
|
15
|
+
super().__init__(f"{error_code}; {error_message}")
|
|
16
|
+
|
|
17
|
+
def __str__(self):
|
|
18
|
+
# Controls how it prints when raised
|
|
19
|
+
return f"UbersmithException: {self.error_code}; {self.error_message}"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'get_files',
|
|
3
|
+
]
|
|
4
|
+
|
|
5
|
+
def get_files(attachments: dict):
|
|
6
|
+
if not attachments:
|
|
7
|
+
return None
|
|
8
|
+
files = dict()
|
|
9
|
+
for k, v in attachments.items():
|
|
10
|
+
if type(v) is str:
|
|
11
|
+
files[k] = open(v, 'rb')
|
|
12
|
+
elif type(v) is bytes:
|
|
13
|
+
files[k] = v
|
|
14
|
+
else:
|
|
15
|
+
raise Exception(f'Invalid attachment {k}: Type {type(v)}')
|
|
16
|
+
return files
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# ubersmith.util.parse.py
|
|
2
|
+
import requests
|
|
3
|
+
from typing import Union
|
|
4
|
+
|
|
5
|
+
from ubersmith.util.exceptions import UbersmithException
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
'parse_response',
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
# This module contains the items needed for parsing objects from Ubersmith's API in a centralized
|
|
12
|
+
# manner. This is imported into ubersmith.core.api.py for the `APIClient.request_parsed()` method
|
|
13
|
+
|
|
14
|
+
def parse_response(response: requests.Response) -> Union[dict|list]:
|
|
15
|
+
"""
|
|
16
|
+
Parse a response from the returned `requests.Reponse` object per Ubersmith's API structure
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
response (requests.Response): The response from the Ubersmith API
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
Union[dict|list]: The nested `data` field in the response, if returned
|
|
23
|
+
Raises:
|
|
24
|
+
ubersmith.core.exceptions.UbersmithException: Raised when the response has an error
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
# Base HTTP response exceptions
|
|
28
|
+
response.raise_for_status()
|
|
29
|
+
|
|
30
|
+
# Gather the response body from the Response object
|
|
31
|
+
body = response.json()
|
|
32
|
+
|
|
33
|
+
# Raise UbersmithException if the status is not successful
|
|
34
|
+
if not body['status']:
|
|
35
|
+
raise UbersmithException(
|
|
36
|
+
error_code=int(body['error_code']),
|
|
37
|
+
error_message=str(body['error_message']),
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Otherwise, return the `data` field from the body
|
|
41
|
+
return body['data']
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ubersmith-api
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Version-specific Python 3 wrapper for the Ubersmith API
|
|
5
|
+
Author-email: Ben Nassif <bennassif@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: homepage, https://github.com/bnassif/ubersmith-api
|
|
8
|
+
Project-URL: issues, https://github.com/bnassif/ubersmith-api/issues
|
|
9
|
+
Keywords: api,wrapper,ubersmith,client
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Natural Language :: English
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: requests>=1.0.4
|
|
23
|
+
Requires-Dist: pydantic-settings>=2.1.0
|
|
24
|
+
Requires-Dist: pydantic>=2.5.2
|
|
25
|
+
Provides-Extra: build
|
|
26
|
+
Requires-Dist: jinja2; extra == "build"
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# Python Ubersmith
|
|
30
|
+
[](https://pypi.org/project/ubersmith-api)
|
|
31
|
+
[](https://raw.githubusercontent.com/bnassif/ubersmith-api/main/LICENSE)
|
|
32
|
+

|
|
33
|
+
|
|
34
|
+
A fully-featured API wrapper for the [Ubersmith](https://ubersmith.com/) API
|
|
35
|
+
|
|
36
|
+
Python wrappers exist on PyPI and across GitHub for interacting with the Ubersmith API, but each of them fails to provide full compatibility.
|
|
37
|
+
|
|
38
|
+
In comes `ubersmith-api`...
|
|
39
|
+
|
|
40
|
+
## Overview
|
|
41
|
+
|
|
42
|
+
At its core, this package provides a generic API wrapper class, `UbersmithClient` which allows for uncontrolled calls to an Ubersmith instance.
|
|
43
|
+
|
|
44
|
+
Built atop this wrapper, a templating suite is available for generating classes for each API section in Ubersmith, specific to each Ubersmith version to maximize compatibility.
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# PyPi Installation
|
|
50
|
+
pip install ubersmith-api
|
|
51
|
+
# GitHub Installation
|
|
52
|
+
pip install git+'https://github.com/bnassif/ubersmith-api.git'
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Getting Started
|
|
56
|
+
|
|
57
|
+
### Instantiating the Base Client
|
|
58
|
+
```python
|
|
59
|
+
from ubersmith_api import *
|
|
60
|
+
|
|
61
|
+
config = UbersmithConfig(
|
|
62
|
+
host='target-hostname-or-address',
|
|
63
|
+
username='username-to-use',
|
|
64
|
+
password='api-token-for-user',
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
client = UbersmithClient(config)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Making Calls
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
response_obj = client.request(
|
|
74
|
+
'uber.method_get',
|
|
75
|
+
data={
|
|
76
|
+
'method_name': 'client.get',
|
|
77
|
+
},
|
|
78
|
+
#raw=True,
|
|
79
|
+
)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
By default, the `request()` method will parse the response from the API, checking for HTTP error codes, as well as error messages from Ubersmith itself. If no errors are found, the returned `data` field is returned.
|
|
83
|
+
|
|
84
|
+
Alternatively, you can parse `raw=True` to return the `requests.Response` object for manual parsing and error checking.
|
|
85
|
+
|
|
86
|
+
### Shipped Methods
|
|
87
|
+
The `UbersmithClient` class comes with three (3) core methods shipped.
|
|
88
|
+
These offer a simplified entrypoint to gathering parsed information of an Ubersmith system.
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
# Get system information: calls `uber.system_info`
|
|
92
|
+
sys_info = client.system_info()
|
|
93
|
+
|
|
94
|
+
# Get all available methods: calls `uber.method_list`
|
|
95
|
+
all_methods = client.method_list()
|
|
96
|
+
|
|
97
|
+
# Get details of one method: calls `uber.method_get`
|
|
98
|
+
method_details = client.method_get('client.get')
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Future Compatibility
|
|
102
|
+
|
|
103
|
+
**NOTE**:
|
|
104
|
+
|
|
105
|
+
Additional code is shipped in this repository, along with version-specific schemas for the Ubersmith API.
|
|
106
|
+
These features will be enabled in later releases of the package.
|
|
107
|
+
|
|
108
|
+
## License
|
|
109
|
+
MIT - Feel free to use, extend, and contribute.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/ubersmith/__init__.py
|
|
5
|
+
src/ubersmith/__version__.py
|
|
6
|
+
src/ubersmith/config.py
|
|
7
|
+
src/ubersmith/api/core.py
|
|
8
|
+
src/ubersmith/client/__init__.py
|
|
9
|
+
src/ubersmith/client/api.py
|
|
10
|
+
src/ubersmith/client/base.py
|
|
11
|
+
src/ubersmith/util/__init__.py
|
|
12
|
+
src/ubersmith/util/cleaners.py
|
|
13
|
+
src/ubersmith/util/exceptions.py
|
|
14
|
+
src/ubersmith/util/files.py
|
|
15
|
+
src/ubersmith/util/parse.py
|
|
16
|
+
src/ubersmith_api.egg-info/PKG-INFO
|
|
17
|
+
src/ubersmith_api.egg-info/SOURCES.txt
|
|
18
|
+
src/ubersmith_api.egg-info/dependency_links.txt
|
|
19
|
+
src/ubersmith_api.egg-info/requires.txt
|
|
20
|
+
src/ubersmith_api.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ubersmith
|