wayscloud 0.1.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.
- wayscloud-0.1.0/LICENSE +21 -0
- wayscloud-0.1.0/PKG-INFO +112 -0
- wayscloud-0.1.0/README.md +91 -0
- wayscloud-0.1.0/pyproject.toml +28 -0
- wayscloud-0.1.0/setup.cfg +33 -0
- wayscloud-0.1.0/tests/test_smoke.py +241 -0
- wayscloud-0.1.0/wayscloud/__init__.py +5 -0
- wayscloud-0.1.0/wayscloud/_version.py +1 -0
- wayscloud-0.1.0/wayscloud/client.py +303 -0
- wayscloud-0.1.0/wayscloud/exceptions.py +28 -0
- wayscloud-0.1.0/wayscloud/py.typed +0 -0
- wayscloud-0.1.0/wayscloud/services/__init__.py +0 -0
- wayscloud-0.1.0/wayscloud/services/account.py +35 -0
- wayscloud-0.1.0/wayscloud/services/apps.py +132 -0
- wayscloud-0.1.0/wayscloud/services/database.py +69 -0
- wayscloud-0.1.0/wayscloud/services/dns.py +97 -0
- wayscloud-0.1.0/wayscloud/services/iot.py +131 -0
- wayscloud-0.1.0/wayscloud/services/redis.py +67 -0
- wayscloud-0.1.0/wayscloud/services/sms.py +39 -0
- wayscloud-0.1.0/wayscloud/services/storage.py +58 -0
- wayscloud-0.1.0/wayscloud/services/vps.py +127 -0
- wayscloud-0.1.0/wayscloud.egg-info/PKG-INFO +112 -0
- wayscloud-0.1.0/wayscloud.egg-info/SOURCES.txt +25 -0
- wayscloud-0.1.0/wayscloud.egg-info/dependency_links.txt +1 -0
- wayscloud-0.1.0/wayscloud.egg-info/requires.txt +1 -0
- wayscloud-0.1.0/wayscloud.egg-info/top_level.txt +1 -0
wayscloud-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 WAYSCloud AS
|
|
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.
|
wayscloud-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: wayscloud
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for the WAYSCloud API
|
|
5
|
+
Home-page: https://wayscloud.services
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://wayscloud.services
|
|
8
|
+
Project-URL: Documentation, https://docs.wayscloud.services
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: httpx>=0.25
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
# WAYSCloud Python SDK
|
|
23
|
+
|
|
24
|
+
Official Python SDK for the [WAYSCloud](https://wayscloud.services) API.
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install wayscloud
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Authentication
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from wayscloud import WaysCloudClient
|
|
36
|
+
|
|
37
|
+
# Personal Access Token
|
|
38
|
+
client = WaysCloudClient(token="wayscloud_pat_...")
|
|
39
|
+
|
|
40
|
+
# API key
|
|
41
|
+
client = WaysCloudClient(api_key="wayscloud_api_...")
|
|
42
|
+
|
|
43
|
+
# Environment variables (WAYSCLOUD_TOKEN or WAYSCLOUD_API_KEY)
|
|
44
|
+
client = WaysCloudClient()
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Priority: explicit arguments > environment variables.
|
|
48
|
+
|
|
49
|
+
## Usage
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
# VPS
|
|
53
|
+
for vm in client.vps.list():
|
|
54
|
+
print(vm["hostname"], vm["status"])
|
|
55
|
+
|
|
56
|
+
# DNS
|
|
57
|
+
client.dns.create_record(
|
|
58
|
+
"example.com",
|
|
59
|
+
record_type="A",
|
|
60
|
+
name="www",
|
|
61
|
+
value="192.0.2.1",
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Database
|
|
65
|
+
db = client.database.create(name="prod", db_type="postgresql")
|
|
66
|
+
|
|
67
|
+
# Apps
|
|
68
|
+
app = client.apps.create(name="my-app", region="eu")
|
|
69
|
+
|
|
70
|
+
# Storage
|
|
71
|
+
client.storage.create_bucket("my-bucket")
|
|
72
|
+
|
|
73
|
+
# SMS
|
|
74
|
+
client.sms.send(to="+4712345678", message="Hello from WAYSCloud")
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Error handling
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from wayscloud import NotFoundError, AuthenticationError
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
client.vps.get("id")
|
|
84
|
+
except NotFoundError:
|
|
85
|
+
print("Not found")
|
|
86
|
+
except AuthenticationError:
|
|
87
|
+
print("Invalid credentials")
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
All exceptions inherit from `WaysCloudError`.
|
|
91
|
+
|
|
92
|
+
## Configuration
|
|
93
|
+
|
|
94
|
+
| Parameter | Environment variable | Default |
|
|
95
|
+
|-----------|---------------------|---------|
|
|
96
|
+
| `token` | `WAYSCLOUD_TOKEN` | — |
|
|
97
|
+
| `api_key` | `WAYSCLOUD_API_KEY` | — |
|
|
98
|
+
| `base_url` | `WAYSCLOUD_API_URL` | `https://api.wayscloud.services` |
|
|
99
|
+
| `timeout` | — | `30.0` |
|
|
100
|
+
|
|
101
|
+
## Retries
|
|
102
|
+
|
|
103
|
+
Automatic retries on 429, 502, 503, 504 with exponential backoff. Respects `Retry-After` headers.
|
|
104
|
+
|
|
105
|
+
## Requirements
|
|
106
|
+
|
|
107
|
+
- Python 3.10+
|
|
108
|
+
- httpx
|
|
109
|
+
|
|
110
|
+
## License
|
|
111
|
+
|
|
112
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# WAYSCloud Python SDK
|
|
2
|
+
|
|
3
|
+
Official Python SDK for the [WAYSCloud](https://wayscloud.services) API.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install wayscloud
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Authentication
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from wayscloud import WaysCloudClient
|
|
15
|
+
|
|
16
|
+
# Personal Access Token
|
|
17
|
+
client = WaysCloudClient(token="wayscloud_pat_...")
|
|
18
|
+
|
|
19
|
+
# API key
|
|
20
|
+
client = WaysCloudClient(api_key="wayscloud_api_...")
|
|
21
|
+
|
|
22
|
+
# Environment variables (WAYSCLOUD_TOKEN or WAYSCLOUD_API_KEY)
|
|
23
|
+
client = WaysCloudClient()
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Priority: explicit arguments > environment variables.
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
# VPS
|
|
32
|
+
for vm in client.vps.list():
|
|
33
|
+
print(vm["hostname"], vm["status"])
|
|
34
|
+
|
|
35
|
+
# DNS
|
|
36
|
+
client.dns.create_record(
|
|
37
|
+
"example.com",
|
|
38
|
+
record_type="A",
|
|
39
|
+
name="www",
|
|
40
|
+
value="192.0.2.1",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
# Database
|
|
44
|
+
db = client.database.create(name="prod", db_type="postgresql")
|
|
45
|
+
|
|
46
|
+
# Apps
|
|
47
|
+
app = client.apps.create(name="my-app", region="eu")
|
|
48
|
+
|
|
49
|
+
# Storage
|
|
50
|
+
client.storage.create_bucket("my-bucket")
|
|
51
|
+
|
|
52
|
+
# SMS
|
|
53
|
+
client.sms.send(to="+4712345678", message="Hello from WAYSCloud")
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Error handling
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from wayscloud import NotFoundError, AuthenticationError
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
client.vps.get("id")
|
|
63
|
+
except NotFoundError:
|
|
64
|
+
print("Not found")
|
|
65
|
+
except AuthenticationError:
|
|
66
|
+
print("Invalid credentials")
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
All exceptions inherit from `WaysCloudError`.
|
|
70
|
+
|
|
71
|
+
## Configuration
|
|
72
|
+
|
|
73
|
+
| Parameter | Environment variable | Default |
|
|
74
|
+
|-----------|---------------------|---------|
|
|
75
|
+
| `token` | `WAYSCLOUD_TOKEN` | — |
|
|
76
|
+
| `api_key` | `WAYSCLOUD_API_KEY` | — |
|
|
77
|
+
| `base_url` | `WAYSCLOUD_API_URL` | `https://api.wayscloud.services` |
|
|
78
|
+
| `timeout` | — | `30.0` |
|
|
79
|
+
|
|
80
|
+
## Retries
|
|
81
|
+
|
|
82
|
+
Automatic retries on 429, 502, 503, 504 with exponential backoff. Respects `Retry-After` headers.
|
|
83
|
+
|
|
84
|
+
## Requirements
|
|
85
|
+
|
|
86
|
+
- Python 3.10+
|
|
87
|
+
- httpx
|
|
88
|
+
|
|
89
|
+
## License
|
|
90
|
+
|
|
91
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "wayscloud"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Official Python SDK for the WAYSCloud API"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = {text = "MIT"}
|
|
7
|
+
requires-python = ">=3.10"
|
|
8
|
+
dependencies = ["httpx>=0.25"]
|
|
9
|
+
classifiers = [
|
|
10
|
+
"Development Status :: 4 - Beta",
|
|
11
|
+
"Intended Audience :: Developers",
|
|
12
|
+
"License :: OSI Approved :: MIT License",
|
|
13
|
+
"Programming Language :: Python :: 3",
|
|
14
|
+
"Programming Language :: Python :: 3.10",
|
|
15
|
+
"Programming Language :: Python :: 3.11",
|
|
16
|
+
"Programming Language :: Python :: 3.12",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[project.urls]
|
|
20
|
+
Homepage = "https://wayscloud.services"
|
|
21
|
+
Documentation = "https://docs.wayscloud.services"
|
|
22
|
+
|
|
23
|
+
[tool.setuptools.packages.find]
|
|
24
|
+
include = ["wayscloud*"]
|
|
25
|
+
|
|
26
|
+
[build-system]
|
|
27
|
+
requires = ["setuptools>=68"]
|
|
28
|
+
build-backend = "setuptools.build_meta"
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[metadata]
|
|
2
|
+
name = wayscloud
|
|
3
|
+
version = 0.1.0
|
|
4
|
+
description = Official Python SDK for the WAYSCloud API
|
|
5
|
+
long_description = file: README.md
|
|
6
|
+
long_description_content_type = text/markdown
|
|
7
|
+
license = MIT
|
|
8
|
+
url = https://wayscloud.services
|
|
9
|
+
classifiers =
|
|
10
|
+
Development Status :: 4 - Beta
|
|
11
|
+
Intended Audience :: Developers
|
|
12
|
+
License :: OSI Approved :: MIT License
|
|
13
|
+
Programming Language :: Python :: 3
|
|
14
|
+
Programming Language :: Python :: 3.10
|
|
15
|
+
Programming Language :: Python :: 3.11
|
|
16
|
+
Programming Language :: Python :: 3.12
|
|
17
|
+
|
|
18
|
+
[options]
|
|
19
|
+
packages = find:
|
|
20
|
+
python_requires = >=3.10
|
|
21
|
+
install_requires =
|
|
22
|
+
httpx>=0.25
|
|
23
|
+
|
|
24
|
+
[options.packages.find]
|
|
25
|
+
include = wayscloud*
|
|
26
|
+
|
|
27
|
+
[options.package_data]
|
|
28
|
+
wayscloud = py.typed
|
|
29
|
+
|
|
30
|
+
[egg_info]
|
|
31
|
+
tag_build =
|
|
32
|
+
tag_date = 0
|
|
33
|
+
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"""SDK smoke tests — verify public API surface, auth, lifecycle, and error handling."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
import pytest
|
|
7
|
+
import respx
|
|
8
|
+
|
|
9
|
+
from wayscloud import (
|
|
10
|
+
WaysCloudClient,
|
|
11
|
+
WaysCloudError,
|
|
12
|
+
AuthenticationError,
|
|
13
|
+
NotFoundError,
|
|
14
|
+
ValidationError,
|
|
15
|
+
ServerError,
|
|
16
|
+
__version__,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# ── Import & version ────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
def test_version_is_string():
|
|
23
|
+
assert isinstance(__version__, str)
|
|
24
|
+
assert "." in __version__
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# ── Auth: explicit args ─────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
def test_token_sets_bearer_header():
|
|
30
|
+
c = WaysCloudClient(token="wayscloud_pat_test123")
|
|
31
|
+
assert c._headers["Authorization"] == "Bearer wayscloud_pat_test123"
|
|
32
|
+
c.close()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_api_key_token_sets_x_api_key():
|
|
36
|
+
c = WaysCloudClient(token="wayscloud_api_test123")
|
|
37
|
+
assert c._headers["X-API-Key"] == "wayscloud_api_test123"
|
|
38
|
+
assert "Authorization" not in c._headers
|
|
39
|
+
c.close()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_explicit_api_key_sets_header():
|
|
43
|
+
c = WaysCloudClient(api_key="wayscloud_api_abc")
|
|
44
|
+
assert c._headers["X-API-Key"] == "wayscloud_api_abc"
|
|
45
|
+
c.close()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_both_token_and_api_key():
|
|
49
|
+
c = WaysCloudClient(token="wayscloud_pat_x", api_key="wayscloud_api_y")
|
|
50
|
+
assert c._headers["Authorization"] == "Bearer wayscloud_pat_x"
|
|
51
|
+
assert c._headers["X-API-Key"] == "wayscloud_api_y"
|
|
52
|
+
c.close()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# ── Auth: env var fallback ──────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
def test_env_var_token(monkeypatch):
|
|
58
|
+
monkeypatch.setenv("WAYSCLOUD_TOKEN", "wayscloud_pat_env")
|
|
59
|
+
monkeypatch.delenv("WAYSCLOUD_API_KEY", raising=False)
|
|
60
|
+
c = WaysCloudClient()
|
|
61
|
+
assert c._headers["Authorization"] == "Bearer wayscloud_pat_env"
|
|
62
|
+
c.close()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_env_var_api_key(monkeypatch):
|
|
66
|
+
monkeypatch.delenv("WAYSCLOUD_TOKEN", raising=False)
|
|
67
|
+
monkeypatch.setenv("WAYSCLOUD_API_KEY", "wayscloud_api_env")
|
|
68
|
+
c = WaysCloudClient()
|
|
69
|
+
assert c._headers["X-API-Key"] == "wayscloud_api_env"
|
|
70
|
+
c.close()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_env_var_base_url(monkeypatch):
|
|
74
|
+
monkeypatch.setenv("WAYSCLOUD_API_URL", "https://custom.example.com")
|
|
75
|
+
c = WaysCloudClient(token="t")
|
|
76
|
+
assert c.base_url == "https://custom.example.com"
|
|
77
|
+
c.close()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_explicit_overrides_env(monkeypatch):
|
|
81
|
+
monkeypatch.setenv("WAYSCLOUD_TOKEN", "wayscloud_pat_env")
|
|
82
|
+
c = WaysCloudClient(token="wayscloud_pat_explicit")
|
|
83
|
+
assert c._headers["Authorization"] == "Bearer wayscloud_pat_explicit"
|
|
84
|
+
c.close()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# ── Client lifecycle ────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
def test_context_manager():
|
|
90
|
+
with WaysCloudClient(token="t") as c:
|
|
91
|
+
assert c._http is not None
|
|
92
|
+
assert c._http.is_closed
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_close():
|
|
96
|
+
c = WaysCloudClient(token="t")
|
|
97
|
+
assert not c._http.is_closed
|
|
98
|
+
c.close()
|
|
99
|
+
assert c._http.is_closed
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# ── Service properties ──────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
def test_all_services_instantiate():
|
|
105
|
+
c = WaysCloudClient(token="t")
|
|
106
|
+
services = [c.vps, c.dns, c.storage, c.database, c.redis, c.apps, c.iot, c.sms, c.account]
|
|
107
|
+
assert len(services) == 9
|
|
108
|
+
assert all(s is not None for s in services)
|
|
109
|
+
# Lazy — same instance on second access
|
|
110
|
+
assert c.vps is c.vps
|
|
111
|
+
c.close()
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# ── HTTP: success ───────────────────────────────────────────────
|
|
115
|
+
|
|
116
|
+
@respx.mock
|
|
117
|
+
def test_get_returns_json():
|
|
118
|
+
respx.get("https://api.wayscloud.services/api/v1/dashboard/vps").mock(
|
|
119
|
+
return_value=httpx.Response(200, json={"instances": []})
|
|
120
|
+
)
|
|
121
|
+
with WaysCloudClient(token="t") as c:
|
|
122
|
+
result = c.vps.list()
|
|
123
|
+
assert result == []
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@respx.mock
|
|
127
|
+
def test_post_sends_json_body():
|
|
128
|
+
route = respx.post("https://api.wayscloud.services/api/v1/dashboard/dns/zones").mock(
|
|
129
|
+
return_value=httpx.Response(201, json={"id": "z1", "name": "example.com"})
|
|
130
|
+
)
|
|
131
|
+
with WaysCloudClient(token="t") as c:
|
|
132
|
+
result = c.dns.create_zone("example.com")
|
|
133
|
+
assert result["name"] == "example.com"
|
|
134
|
+
assert route.calls[0].request.headers["content-type"] == "application/json"
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@respx.mock
|
|
138
|
+
def test_get_does_not_send_content_type():
|
|
139
|
+
route = respx.get("https://api.wayscloud.services/api/v1/dashboard/vps").mock(
|
|
140
|
+
return_value=httpx.Response(200, json={"instances": []})
|
|
141
|
+
)
|
|
142
|
+
with WaysCloudClient(token="t") as c:
|
|
143
|
+
c.vps.list()
|
|
144
|
+
assert "content-type" not in route.calls[0].request.headers
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@respx.mock
|
|
148
|
+
def test_204_returns_ok():
|
|
149
|
+
respx.delete("https://api.wayscloud.services/api/v1/dashboard/vps/abc").mock(
|
|
150
|
+
return_value=httpx.Response(204)
|
|
151
|
+
)
|
|
152
|
+
with WaysCloudClient(token="t") as c:
|
|
153
|
+
result = c.vps.delete("abc")
|
|
154
|
+
assert result == {"status": "ok"}
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# ── HTTP: error mapping ────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
@respx.mock
|
|
160
|
+
def test_401_raises_authentication_error():
|
|
161
|
+
respx.get("https://api.wayscloud.services/api/v1/dashboard/vps").mock(
|
|
162
|
+
return_value=httpx.Response(401, json={"detail": "Invalid token"})
|
|
163
|
+
)
|
|
164
|
+
with WaysCloudClient(token="t") as c:
|
|
165
|
+
with pytest.raises(AuthenticationError) as exc:
|
|
166
|
+
c.vps.list()
|
|
167
|
+
assert exc.value.status_code == 401
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@respx.mock
|
|
171
|
+
def test_404_raises_not_found():
|
|
172
|
+
respx.get("https://api.wayscloud.services/api/v1/dashboard/vps/bad").mock(
|
|
173
|
+
return_value=httpx.Response(404, json={"detail": "Not found"})
|
|
174
|
+
)
|
|
175
|
+
with WaysCloudClient(token="t") as c:
|
|
176
|
+
with pytest.raises(NotFoundError):
|
|
177
|
+
c.vps.get("bad")
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@respx.mock
|
|
181
|
+
def test_422_raises_validation_error():
|
|
182
|
+
respx.post("https://api.wayscloud.services/api/v1/dashboard/dns/zones").mock(
|
|
183
|
+
return_value=httpx.Response(422, json={"detail": "Invalid zone name"})
|
|
184
|
+
)
|
|
185
|
+
with WaysCloudClient(token="t") as c:
|
|
186
|
+
with pytest.raises(ValidationError):
|
|
187
|
+
c.dns.create_zone("")
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@respx.mock
|
|
191
|
+
def test_500_raises_server_error():
|
|
192
|
+
respx.get("https://api.wayscloud.services/api/v1/dashboard/vps").mock(
|
|
193
|
+
return_value=httpx.Response(500, json={"detail": "Internal error"})
|
|
194
|
+
)
|
|
195
|
+
with WaysCloudClient(token="t") as c:
|
|
196
|
+
with pytest.raises(ServerError):
|
|
197
|
+
c.vps.list()
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
# ── HTTP: retry ─────────────────────────────────────────────────
|
|
201
|
+
|
|
202
|
+
@respx.mock
|
|
203
|
+
def test_retries_on_429_then_succeeds():
|
|
204
|
+
route = respx.get("https://api.wayscloud.services/api/v1/dashboard/vps").mock(
|
|
205
|
+
side_effect=[
|
|
206
|
+
httpx.Response(429),
|
|
207
|
+
httpx.Response(200, json={"instances": [{"hostname": "ok"}]}),
|
|
208
|
+
]
|
|
209
|
+
)
|
|
210
|
+
with WaysCloudClient(token="t") as c:
|
|
211
|
+
c.BACKOFF_FACTOR = 0.01 # Speed up test
|
|
212
|
+
result = c.vps.list()
|
|
213
|
+
assert len(result) == 1
|
|
214
|
+
assert route.call_count == 2
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
# ── Service method signatures ───────────────────────────────────
|
|
218
|
+
|
|
219
|
+
def test_dns_create_record_uses_record_type():
|
|
220
|
+
"""Verify renamed param — no Python builtin shadow."""
|
|
221
|
+
import inspect
|
|
222
|
+
from wayscloud.services.dns import DNSService
|
|
223
|
+
sig = inspect.signature(DNSService.create_record)
|
|
224
|
+
assert "record_type" in sig.parameters
|
|
225
|
+
assert "type" not in sig.parameters
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def test_database_create_uses_db_type():
|
|
229
|
+
import inspect
|
|
230
|
+
from wayscloud.services.database import DatabaseService
|
|
231
|
+
sig = inspect.signature(DatabaseService.create)
|
|
232
|
+
assert "db_type" in sig.parameters
|
|
233
|
+
assert "type" not in sig.parameters
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def test_iot_create_rule_uses_rule_type():
|
|
237
|
+
import inspect
|
|
238
|
+
from wayscloud.services.iot import IoTService
|
|
239
|
+
sig = inspect.signature(IoTService.create_rule)
|
|
240
|
+
assert "rule_type" in sig.parameters
|
|
241
|
+
assert "type" not in sig.parameters
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
from .client import WaysCloudClient
|
|
2
|
+
from .exceptions import WaysCloudError, AuthenticationError, NotFoundError, ValidationError, ServerError
|
|
3
|
+
from ._version import __version__
|
|
4
|
+
|
|
5
|
+
__all__ = ["WaysCloudClient", "WaysCloudError", "AuthenticationError", "NotFoundError", "ValidationError", "ServerError", "__version__"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|