rabbitmq-management-sdk 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.
- rabbitmq_management_sdk-0.1.0/PKG-INFO +37 -0
- rabbitmq_management_sdk-0.1.0/README.md +14 -0
- rabbitmq_management_sdk-0.1.0/pyproject.toml +121 -0
- rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/__init__.py +44 -0
- rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/client/__init__.py +0 -0
- rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/client/config.py +128 -0
- rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/client/rabbitmq_client.py +149 -0
- rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/client/utils.py +34 -0
- rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/domains/__init__.py +0 -0
- rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/domains/base.py +50 -0
- rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/domains/v4/__init__.py +0 -0
- rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/domains/v4/admin/__init__.py +0 -0
- rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/domains/v4/admin/schemas/__init__.py +0 -0
- rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/domains/v4/admin/schemas/vhost_request.py +37 -0
- rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/domains/v4/admin/schemas/vhost_response.py +35 -0
- rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/domains/v4/admin/services.py +55 -0
- rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/domains/v4/bindings/__init__.py +0 -0
- rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/domains/v4/bindings/schemas/__init__.py +0 -0
- rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/domains/v4/bindings/schemas/binding_request.py +28 -0
- rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/domains/v4/bindings/schemas/binding_response.py +30 -0
- rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/domains/v4/bindings/schemas/common.py +8 -0
- rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/domains/v4/bindings/services.py +199 -0
- rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/domains/v4/exchanges/__init__.py +0 -0
- rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/domains/v4/exchanges/schemas/__init__.py +0 -0
- rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/domains/v4/exchanges/schemas/common.py +20 -0
- rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/domains/v4/exchanges/schemas/exchange_request.py +49 -0
- rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/domains/v4/exchanges/schemas/exchange_response.py +44 -0
- rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/domains/v4/exchanges/services.py +98 -0
- rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/domains/v4/queues/__init__.py +0 -0
- rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/domains/v4/queues/schemas/__init__.py +0 -0
- rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/domains/v4/queues/schemas/queue_request.py +168 -0
- rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/domains/v4/queues/schemas/queue_response.py +131 -0
- rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/domains/v4/queues/services.py +50 -0
- rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/domains/v4/shovels/__init__.py +0 -0
- rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/exceptions.py +171 -0
- rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/http_adapter/__init__.py +18 -0
- rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/http_adapter/base.py +52 -0
- rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/http_adapter/config.py +61 -0
- rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/http_adapter/factory.py +39 -0
- rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/http_adapter/httpx.py +114 -0
- rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/http_adapter/retry.py +75 -0
- rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/py.typed +0 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rabbitmq-management-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for the RabbitMQ Management HTTP API to manage queues, exchanges, bindings, users, and policies.
|
|
5
|
+
Keywords: rabbitmq,rabbitmq management api,rabbitmq http api,rabbitmq admin,rabbitmq management,rabbitmq provisioning
|
|
6
|
+
Author: brianmori
|
|
7
|
+
Author-email: brianmori <52781411+brianmori@users.noreply.github.com>
|
|
8
|
+
License-Expression: Apache-2.0
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
12
|
+
Classifier: Typing :: Typed
|
|
13
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
14
|
+
Classifier: Topic :: System :: Systems Administration
|
|
15
|
+
Requires-Dist: pydantic>=2.12.5,<3.0.0
|
|
16
|
+
Requires-Dist: httpx>=0.28.1,<1.0.0
|
|
17
|
+
Requires-Python: >=3.14
|
|
18
|
+
Project-URL: Homepage, https://github.com/brianmori/rabbitmq-management-sdk
|
|
19
|
+
Project-URL: Documentation, https://brianmori.github.io/rabbitmq-management-sdk
|
|
20
|
+
Project-URL: Repository, https://github.com/brianmori/rabbitmq-management-sdk
|
|
21
|
+
Project-URL: Issues, https://github.com/brianmori/rabbitmq-management-sdk/issues
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# rabbitmq-admin-sdk
|
|
25
|
+
|
|
26
|
+
A modern, typed Python SDK for the RabbitMQ HTTP Management API.
|
|
27
|
+
|
|
28
|
+
## Development setup
|
|
29
|
+
|
|
30
|
+
This project uses [uv](https://docs.astral.sh/uv/) for dependency and environment management.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
uv sync --group dev
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
That command creates/syncs the local environment with all development dependencies needed for linting, typing, and
|
|
37
|
+
tests (for example: `ruff`, `pytest`, `mypy`, and `python-dotenv`).
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# rabbitmq-admin-sdk
|
|
2
|
+
|
|
3
|
+
A modern, typed Python SDK for the RabbitMQ HTTP Management API.
|
|
4
|
+
|
|
5
|
+
## Development setup
|
|
6
|
+
|
|
7
|
+
This project uses [uv](https://docs.astral.sh/uv/) for dependency and environment management.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
uv sync --group dev
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
That command creates/syncs the local environment with all development dependencies needed for linting, typing, and
|
|
14
|
+
tests (for example: `ruff`, `pytest`, `mypy`, and `python-dotenv`).
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "rabbitmq-management-sdk"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Python SDK for the RabbitMQ Management HTTP API to manage queues, exchanges, bindings, users, and policies."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "brianmori", email = "52781411+brianmori@users.noreply.github.com" }
|
|
8
|
+
]
|
|
9
|
+
license = "Apache-2.0"
|
|
10
|
+
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Development Status :: 3 - Alpha",
|
|
13
|
+
"Intended Audience :: Developers",
|
|
14
|
+
"Programming Language :: Python :: 3.14",
|
|
15
|
+
"Typing :: Typed",
|
|
16
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
17
|
+
"Topic :: System :: Systems Administration",
|
|
18
|
+
]
|
|
19
|
+
keywords = [
|
|
20
|
+
"rabbitmq",
|
|
21
|
+
"rabbitmq management api",
|
|
22
|
+
"rabbitmq http api",
|
|
23
|
+
"rabbitmq admin",
|
|
24
|
+
"rabbitmq management",
|
|
25
|
+
"rabbitmq provisioning",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
requires-python = ">=3.14"
|
|
29
|
+
dependencies = [
|
|
30
|
+
"pydantic>=2.12.5,<3.0.0",
|
|
31
|
+
"httpx>=0.28.1,<1.0.0",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://github.com/brianmori/rabbitmq-management-sdk"
|
|
36
|
+
Documentation = "https://brianmori.github.io/rabbitmq-management-sdk"
|
|
37
|
+
Repository = "https://github.com/brianmori/rabbitmq-management-sdk"
|
|
38
|
+
Issues = "https://github.com/brianmori/rabbitmq-management-sdk/issues"
|
|
39
|
+
|
|
40
|
+
[build-system]
|
|
41
|
+
requires = ["uv_build>=0.11.1,<0.12.0"]
|
|
42
|
+
build-backend = "uv_build"
|
|
43
|
+
|
|
44
|
+
[dependency-groups]
|
|
45
|
+
dev = [
|
|
46
|
+
"ruff>=0.15.2,<1.0.0",
|
|
47
|
+
"pytest>=9.0.3,<10.0.0",
|
|
48
|
+
"pytest-cov>=7.1.0,<8.0.0",
|
|
49
|
+
"mypy>=2.1.0,<3.0.0",
|
|
50
|
+
"pip-audit>=2.10.0,<3.0.0",
|
|
51
|
+
"python-dotenv>=1.2.2,<2.0.0",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
[tool.ruff]
|
|
55
|
+
line-length = 120
|
|
56
|
+
target-version = "py314"
|
|
57
|
+
src = ["src", "tests"]
|
|
58
|
+
|
|
59
|
+
[tool.ruff.lint]
|
|
60
|
+
select = [
|
|
61
|
+
"E", # pycodestyle errors
|
|
62
|
+
"W", # pycodestyle warnings
|
|
63
|
+
"F", # pyflakes
|
|
64
|
+
"I", # isort
|
|
65
|
+
"B", # flake8-bugbear
|
|
66
|
+
"C4", # flake8-comprehensions
|
|
67
|
+
"UP", # pyupgrade
|
|
68
|
+
"SIM", # flake8-simplify
|
|
69
|
+
"TCH", # flake8-type-checking — moves pure-annotation imports into TYPE_CHECKING blocks
|
|
70
|
+
"ANN", # flake8-annotations — enforces type hint coverage
|
|
71
|
+
"PTH", # flake8-use-pathlib — prefers pathlib over os.path
|
|
72
|
+
"RUF", # ruff-specific rules
|
|
73
|
+
"PL", # pylint
|
|
74
|
+
"TID", # flake8-tidy-imports
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
[tool.ruff.format]
|
|
78
|
+
quote-style = "double"
|
|
79
|
+
indent-style = "space"
|
|
80
|
+
docstring-code-format = true
|
|
81
|
+
|
|
82
|
+
[tool.ruff.lint.pydocstyle]
|
|
83
|
+
convention = "google"
|
|
84
|
+
|
|
85
|
+
[tool.ruff.lint.isort]
|
|
86
|
+
known-first-party = ["rabbitmq_management_sdk"]
|
|
87
|
+
|
|
88
|
+
[tool.ruff.lint.pylint]
|
|
89
|
+
max-args = 8
|
|
90
|
+
|
|
91
|
+
[tool.ruff.lint.flake8-tidy-imports]
|
|
92
|
+
ban-relative-imports = "all"
|
|
93
|
+
|
|
94
|
+
[tool.ruff.lint.flake8-type-checking]
|
|
95
|
+
runtime-evaluated-base-classes = ["rabbitmq_management_sdk.domains.base.RabbitMQBase"]
|
|
96
|
+
|
|
97
|
+
[tool.ruff.lint.per-file-ignores]
|
|
98
|
+
"tests/**/*" = ["PLR2004"]
|
|
99
|
+
|
|
100
|
+
[tool.uv.workspace]
|
|
101
|
+
members = [
|
|
102
|
+
"rabbitmq-management-sdk",
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
[tool.pytest.ini_options]
|
|
106
|
+
pythonpath = ["src"]
|
|
107
|
+
testpaths = ["tests"]
|
|
108
|
+
markers = [
|
|
109
|
+
"unit: Unit tests (fast, no I/O)",
|
|
110
|
+
"integration: Integration tests with mocked HTTP (fast, no real I/O)",
|
|
111
|
+
"live: Live tests against a real RabbitMQ instance (slow, requires Docker)",
|
|
112
|
+
]
|
|
113
|
+
env_files = [".env"]
|
|
114
|
+
addopts = "--import-mode=importlib"
|
|
115
|
+
|
|
116
|
+
[tool.mypy]
|
|
117
|
+
python_version = "3.14"
|
|
118
|
+
strict = true
|
|
119
|
+
files = ["src", "tests"]
|
|
120
|
+
ignore_missing_imports = true
|
|
121
|
+
plugins = ["pydantic.mypy"]
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from rabbitmq_management_sdk.client.config import Config, SSLConfig
|
|
2
|
+
from rabbitmq_management_sdk.client.rabbitmq_client import RabbitMQClient
|
|
3
|
+
from rabbitmq_management_sdk.exceptions import (
|
|
4
|
+
APIError,
|
|
5
|
+
BadRequestError,
|
|
6
|
+
ConflictError,
|
|
7
|
+
ConnectionError,
|
|
8
|
+
ForbiddenError,
|
|
9
|
+
MalformedResponseError,
|
|
10
|
+
MethodNotAllowedError,
|
|
11
|
+
NotFoundError,
|
|
12
|
+
PreconditionFailedError,
|
|
13
|
+
RabbitMQError,
|
|
14
|
+
ServerError,
|
|
15
|
+
ServiceUnavailableError,
|
|
16
|
+
TimeoutError,
|
|
17
|
+
TooManyRequestsError,
|
|
18
|
+
TransportError,
|
|
19
|
+
UnauthorizedError,
|
|
20
|
+
UnprocessableEntityError,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"APIError",
|
|
25
|
+
"BadRequestError",
|
|
26
|
+
"Config",
|
|
27
|
+
"ConflictError",
|
|
28
|
+
"ConnectionError",
|
|
29
|
+
"ForbiddenError",
|
|
30
|
+
"MalformedResponseError",
|
|
31
|
+
"MethodNotAllowedError",
|
|
32
|
+
"NotFoundError",
|
|
33
|
+
"PreconditionFailedError",
|
|
34
|
+
"RabbitMQClient",
|
|
35
|
+
"RabbitMQError",
|
|
36
|
+
"SSLConfig",
|
|
37
|
+
"ServerError",
|
|
38
|
+
"ServiceUnavailableError",
|
|
39
|
+
"TimeoutError",
|
|
40
|
+
"TooManyRequestsError",
|
|
41
|
+
"TransportError",
|
|
42
|
+
"UnauthorizedError",
|
|
43
|
+
"UnprocessableEntityError",
|
|
44
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import ssl
|
|
2
|
+
from enum import IntEnum
|
|
3
|
+
from urllib.parse import quote
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field, FilePath, SecretStr, model_validator
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SSLConfig(BaseModel):
|
|
9
|
+
"""SSLContext for internal PKI or mutual TLS.
|
|
10
|
+
|
|
11
|
+
Attributes:
|
|
12
|
+
ca_bundle: Path to a custom CA bundle (.pem) for internal PKI.
|
|
13
|
+
client_cert: Tuple of (cert_path, key_path) for mTLS.
|
|
14
|
+
verify: Set to False to disable verification entirely (dev only).
|
|
15
|
+
min_version: Minimum TLS version. Defaults to TLSv1.2.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
verify: bool = True
|
|
19
|
+
# FilePath validates that the file actually exists on the system
|
|
20
|
+
ca_bundle: FilePath | None = None
|
|
21
|
+
# Tuple of (cert_file, key_file)
|
|
22
|
+
client_cert: tuple[FilePath, FilePath] | None = None
|
|
23
|
+
min_version: ssl.TLSVersion = Field(
|
|
24
|
+
default=ssl.TLSVersion.TLSv1_2, description="Minimum TLS version. Defaults to TLSv1.2."
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Config(BaseModel):
|
|
29
|
+
"""Configuration for the RabbitMQ management API client.
|
|
30
|
+
|
|
31
|
+
Attributes:
|
|
32
|
+
host: Hostname or IP of the RabbitMQ node.
|
|
33
|
+
port: Management API port. Defaults to 15672 (plain).
|
|
34
|
+
username: RabbitMQ management user.
|
|
35
|
+
password: Password for the management user.
|
|
36
|
+
ssl_context: TLS settings. When None the connection uses plain HTTP.
|
|
37
|
+
virtual_host: Default virtual host for vhost-scoped API calls. Defaults to '/'.
|
|
38
|
+
version_override: Pin a specific RabbitMQ version (e.g. '4.3.0') instead of relying on automatic detection.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
host: str = Field(description="Hostname or IP of the RabbitMQ node.")
|
|
42
|
+
port: int = Field(
|
|
43
|
+
default=15672,
|
|
44
|
+
gt=1024,
|
|
45
|
+
lt=65536,
|
|
46
|
+
description="Management API port. Defaults to 15672 (plain).",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
username: str | None = Field(description="RabbitMQ management user.")
|
|
50
|
+
password: SecretStr | None = Field(description="Password for the management user.")
|
|
51
|
+
|
|
52
|
+
ssl_context: SSLConfig | None = Field(
|
|
53
|
+
default=None,
|
|
54
|
+
description="TLS settings. When None the connection uses plain HTTP.",
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
virtual_host: str = Field(
|
|
58
|
+
default="/",
|
|
59
|
+
description="Defaults to '/', the RabbitMQ built-in virtual host.",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def virtual_host_safe(self) -> str:
|
|
64
|
+
"""Return the virtual host name URL-encoded for use in API paths.
|
|
65
|
+
|
|
66
|
+
The result is percent-encoded with ``safe=""`` so that ``/`` becomes
|
|
67
|
+
``%2F``. Managers receive this value and must NOT encode it again.
|
|
68
|
+
"""
|
|
69
|
+
return quote(self.virtual_host, safe="")
|
|
70
|
+
|
|
71
|
+
strict: bool = Field(
|
|
72
|
+
default=False,
|
|
73
|
+
description="If True, all default arguments are sent explicitly to RabbitMQ. "
|
|
74
|
+
"Note: Enabling this on existing queues will cause a 406 Precondition Failed "
|
|
75
|
+
"conflict if they were originally created without explicit defaults.",
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
version_override: RabbitMQVersion | None = Field(
|
|
79
|
+
default=None,
|
|
80
|
+
description=(
|
|
81
|
+
"Pin a specific RabbitMQ version string (e.g. '4.3.0') instead of "
|
|
82
|
+
"relying on automatic detection. Useful when the management API is "
|
|
83
|
+
"behind a proxy that strips version headers."
|
|
84
|
+
),
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def base_url(self) -> str:
|
|
89
|
+
"""The base URL for API calls."""
|
|
90
|
+
scheme = "https" if self.ssl_context is not None else "http"
|
|
91
|
+
return f"{scheme}://{self.host}:{self.port}/api"
|
|
92
|
+
|
|
93
|
+
@model_validator(mode="after")
|
|
94
|
+
def validate_config(self) -> Config:
|
|
95
|
+
has_basic_auth = self.username is not None and self.password is not None
|
|
96
|
+
has_mtls = self.ssl_context is not None and self.ssl_context.client_cert is not None
|
|
97
|
+
|
|
98
|
+
if not (has_basic_auth or has_mtls):
|
|
99
|
+
raise ValueError("Provide either username/password or a client certificate.")
|
|
100
|
+
|
|
101
|
+
if has_basic_auth and has_mtls:
|
|
102
|
+
raise ValueError("Choose Basic Auth (username/password) or mTLS (client_cert). ")
|
|
103
|
+
|
|
104
|
+
return self
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class RabbitMQVersion(BaseModel):
|
|
108
|
+
"""Semantic version of the RabbitMQ server."""
|
|
109
|
+
|
|
110
|
+
major: int
|
|
111
|
+
minor: int
|
|
112
|
+
patch: int
|
|
113
|
+
|
|
114
|
+
@classmethod
|
|
115
|
+
def parse(cls, version: str) -> RabbitMQVersion:
|
|
116
|
+
try:
|
|
117
|
+
major, minor, patch = version.split(".")
|
|
118
|
+
return cls(major=int(major), minor=int(minor), patch=int(patch))
|
|
119
|
+
except ValueError:
|
|
120
|
+
raise ValueError(f"Unrecognised RabbitMQ version string: {version!r}") from None
|
|
121
|
+
|
|
122
|
+
def __str__(self) -> str:
|
|
123
|
+
return f"{self.major}.{self.minor}.{self.patch}"
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class RabbitMQMajorVersion(IntEnum):
|
|
127
|
+
V4 = 4
|
|
128
|
+
V5 = 5
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from http import HTTPMethod
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from rabbitmq_management_sdk.client.config import RabbitMQMajorVersion, RabbitMQVersion
|
|
6
|
+
from rabbitmq_management_sdk.client.utils import create_ssl_context
|
|
7
|
+
from rabbitmq_management_sdk.domains.v4.admin.services import AdminManagerV4
|
|
8
|
+
from rabbitmq_management_sdk.domains.v4.bindings.services import BindingManagerV4
|
|
9
|
+
from rabbitmq_management_sdk.domains.v4.exchanges.services import ExchangeManagerV4
|
|
10
|
+
from rabbitmq_management_sdk.domains.v4.queues.services import QueueManagerV4
|
|
11
|
+
from rabbitmq_management_sdk.exceptions import MalformedResponseError, RabbitMQError
|
|
12
|
+
from rabbitmq_management_sdk.http_adapter import HttpAdapter, HttpResponse, factory
|
|
13
|
+
from rabbitmq_management_sdk.http_adapter.config import BasicAuthentication
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
import ssl
|
|
17
|
+
|
|
18
|
+
from rabbitmq_management_sdk.client.config import Config
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class RabbitMQClient:
|
|
24
|
+
"""Client for the RabbitMQ Management API.
|
|
25
|
+
|
|
26
|
+
Attributes:
|
|
27
|
+
_config: Configuration for the client.
|
|
28
|
+
_ha: HTTP adapter for making API requests.
|
|
29
|
+
_basic_auth: Basic authentication for API requests.
|
|
30
|
+
_version: Semantic version of the RabbitMQ server.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(self, config: Config) -> None:
|
|
34
|
+
self._config = config
|
|
35
|
+
self._ha: HttpAdapter
|
|
36
|
+
self._basic_auth: BasicAuthentication
|
|
37
|
+
|
|
38
|
+
default_headers: dict[str, str] = {}
|
|
39
|
+
ssl_context: ssl.SSLContext | None = None
|
|
40
|
+
|
|
41
|
+
if config.password and config.username:
|
|
42
|
+
self._basic_auth = BasicAuthentication(
|
|
43
|
+
username=config.username, password=config.password.get_secret_value()
|
|
44
|
+
)
|
|
45
|
+
default_headers["Authorization"] = self._basic_auth.auth_header
|
|
46
|
+
|
|
47
|
+
if config.ssl_context:
|
|
48
|
+
ssl_context = create_ssl_context(config.ssl_context)
|
|
49
|
+
|
|
50
|
+
self._ha = factory.create_adapter(
|
|
51
|
+
host=config.host, port=config.port, default_headers=default_headers, ssl_context=ssl_context
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
self._version: RabbitMQVersion = self._get_version()
|
|
55
|
+
|
|
56
|
+
logger.debug(
|
|
57
|
+
"RabbitMQClient initialized",
|
|
58
|
+
extra={"host": config.host, "port": config.port, "version": self._version},
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
def _get_version(self) -> RabbitMQVersion:
|
|
62
|
+
"""
|
|
63
|
+
Retrieves the RabbitMQ version from the server.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
RabbitMQVersion: The semantic version of the RabbitMQ server.
|
|
67
|
+
|
|
68
|
+
Raises:
|
|
69
|
+
RabbitMQError: If there is a problem communicating with the server or parsing the version.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
if self._config.version_override is not None:
|
|
73
|
+
return self._config.version_override
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
hr: HttpResponse = self._ha.request(method=HTTPMethod.GET, path="/api/overview")
|
|
77
|
+
data = hr.json()
|
|
78
|
+
except RabbitMQError as e:
|
|
79
|
+
logger.error(
|
|
80
|
+
"Failed to reach RabbitMQ Management API during version detection. "
|
|
81
|
+
"Consider setting version_override in Config and/or proxy settings.",
|
|
82
|
+
extra={"host": self._config.host, "port": self._config.port},
|
|
83
|
+
exc_info=e,
|
|
84
|
+
)
|
|
85
|
+
raise
|
|
86
|
+
|
|
87
|
+
rabbitmq_version = data.get("rabbitmq_version")
|
|
88
|
+
|
|
89
|
+
if not isinstance(rabbitmq_version, str):
|
|
90
|
+
logger.error(
|
|
91
|
+
"RabbitMQ version field missing or not a string in /api/overview response. "
|
|
92
|
+
"Consider setting version_override in Config.",
|
|
93
|
+
extra={
|
|
94
|
+
"host": self._config.host,
|
|
95
|
+
"port": self._config.port,
|
|
96
|
+
"rabbitmq_version": rabbitmq_version,
|
|
97
|
+
},
|
|
98
|
+
)
|
|
99
|
+
raise MalformedResponseError(
|
|
100
|
+
f"Expected a string for rabbitmq_version, got {type(rabbitmq_version).__name__}. "
|
|
101
|
+
f"Set version_override in Config to bypass detection."
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
return RabbitMQVersion.parse(rabbitmq_version)
|
|
106
|
+
except ValueError as e:
|
|
107
|
+
logger.error(
|
|
108
|
+
"RabbitMQ version string could not be parsed.",
|
|
109
|
+
extra={
|
|
110
|
+
"host": self._config.host,
|
|
111
|
+
"port": self._config.port,
|
|
112
|
+
"rabbitmq_version": rabbitmq_version,
|
|
113
|
+
},
|
|
114
|
+
exc_info=e,
|
|
115
|
+
)
|
|
116
|
+
raise MalformedResponseError(
|
|
117
|
+
f"Could not parse rabbitmq_version {rabbitmq_version!r}. "
|
|
118
|
+
f"Set version_override in Config to bypass detection."
|
|
119
|
+
) from e
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def queues(self) -> QueueManagerV4:
|
|
123
|
+
if self._version.major == RabbitMQMajorVersion.V4:
|
|
124
|
+
return QueueManagerV4(
|
|
125
|
+
http_client=self._ha, vhost=self._config.virtual_host_safe, strict=self._config.strict
|
|
126
|
+
)
|
|
127
|
+
raise NotImplementedError(f"Version {self._version} not supported")
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def admin(self) -> AdminManagerV4:
|
|
131
|
+
if self._version.major == RabbitMQMajorVersion.V4:
|
|
132
|
+
return AdminManagerV4(http_client=self._ha, strict=self._config.strict)
|
|
133
|
+
raise NotImplementedError(f"Version {self._version} not supported")
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def exchanges(self) -> ExchangeManagerV4:
|
|
137
|
+
if self._version.major == RabbitMQMajorVersion.V4:
|
|
138
|
+
return ExchangeManagerV4(
|
|
139
|
+
http_client=self._ha, vhost=self._config.virtual_host_safe, strict=self._config.strict
|
|
140
|
+
)
|
|
141
|
+
raise NotImplementedError(f"Version {self._version} not supported")
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def bindings(self) -> BindingManagerV4:
|
|
145
|
+
if self._version.major == RabbitMQMajorVersion.V4:
|
|
146
|
+
return BindingManagerV4(
|
|
147
|
+
http_client=self._ha, vhost=self._config.virtual_host_safe, strict=self._config.strict
|
|
148
|
+
)
|
|
149
|
+
raise NotImplementedError(f"Version {self._version} not supported")
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import ssl
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from rabbitmq_management_sdk.client.config import SSLConfig
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def create_ssl_context(sc: SSLConfig) -> ssl.SSLContext:
|
|
9
|
+
"""Creates a SSL context for the RabbitMQ Management API.
|
|
10
|
+
|
|
11
|
+
TLS Version defaults to TLSv1.2 and hostname verification is enabled.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
sc: SSL configuration for the client.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
An SSL context configured with the provided SSL settings.
|
|
18
|
+
"""
|
|
19
|
+
ctx = ssl.create_default_context(
|
|
20
|
+
purpose=ssl.Purpose.SERVER_AUTH,
|
|
21
|
+
cafile=sc.ca_bundle,
|
|
22
|
+
)
|
|
23
|
+
ctx.verify_mode = ssl.CERT_REQUIRED
|
|
24
|
+
ctx.minimum_version = sc.min_version
|
|
25
|
+
ctx.check_hostname = True
|
|
26
|
+
|
|
27
|
+
if not sc.verify:
|
|
28
|
+
ctx.check_hostname = False
|
|
29
|
+
ctx.verify_mode = ssl.CERT_NONE
|
|
30
|
+
|
|
31
|
+
if sc.client_cert:
|
|
32
|
+
ctx.load_cert_chain(*sc.client_cert)
|
|
33
|
+
|
|
34
|
+
return ctx
|
|
File without changes
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, ConfigDict, ValidationError
|
|
4
|
+
|
|
5
|
+
from rabbitmq_management_sdk.exceptions import MalformedResponseError
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from rabbitmq_management_sdk.http_adapter.base import HttpResponse
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RabbitMQBase(BaseModel):
|
|
12
|
+
model_config = ConfigDict(extra="ignore", frozen=True, populate_by_name=True, use_enum_values=True)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def parse_one[T: RabbitMQBase](response: HttpResponse, model: type[T]) -> T:
|
|
16
|
+
"""Parse a single model from an HTTP response body.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
response: The HTTP response to parse.
|
|
20
|
+
model: The Pydantic model class to validate against.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
A validated model instance.
|
|
24
|
+
|
|
25
|
+
Raises:
|
|
26
|
+
MalformedResponseError: If the body cannot be decoded or does not match the schema.
|
|
27
|
+
"""
|
|
28
|
+
try:
|
|
29
|
+
return model.model_validate(response.json())
|
|
30
|
+
except ValidationError as e:
|
|
31
|
+
raise MalformedResponseError(f"Response body did not match {model.__name__}") from e
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def parse_list[T: RabbitMQBase](response: HttpResponse, model: type[T]) -> list[T]:
|
|
35
|
+
"""Parse a list of models from an HTTP response body.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
response: The HTTP response to parse.
|
|
39
|
+
model: The Pydantic model class to validate each item against.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
A list of validated model instances.
|
|
43
|
+
|
|
44
|
+
Raises:
|
|
45
|
+
MalformedResponseError: If the body cannot be decoded or an item does not match the schema.
|
|
46
|
+
"""
|
|
47
|
+
try:
|
|
48
|
+
return [model.model_validate(item) for item in response.json()]
|
|
49
|
+
except ValidationError as e:
|
|
50
|
+
raise MalformedResponseError(f"Response body did not match list[{model.__name__}]") from e
|
|
File without changes
|
|
File without changes
|
rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/domains/v4/admin/schemas/__init__.py
ADDED
|
File without changes
|
rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/domains/v4/admin/schemas/vhost_request.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# domains/v4/vhost/schemas/vhost_request.py
|
|
2
|
+
from enum import StrEnum
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
from pydantic import Field
|
|
6
|
+
|
|
7
|
+
from rabbitmq_management_sdk.domains.base import RabbitMQBase
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class VhostLimitName(StrEnum):
|
|
11
|
+
"""Valid vhost limit names accepted by the RabbitMQ Management API."""
|
|
12
|
+
|
|
13
|
+
MAX_CONNECTIONS = "max-connections"
|
|
14
|
+
MAX_QUEUES = "max-queues"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class VhostRequest(RabbitMQBase):
|
|
18
|
+
description: str | None = None
|
|
19
|
+
tags: list[str] = Field(default_factory=list)
|
|
20
|
+
tracing: bool = False
|
|
21
|
+
default_queue_type: Literal["classic", "quorum", "stream"] = "classic"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class VhostLimitRequest(RabbitMQBase):
|
|
25
|
+
"""Payload for ``PUT /api/vhost-limits/{vhost}/{limit}``.
|
|
26
|
+
|
|
27
|
+
The vhost and limit name are part of the URL — only the value is sent
|
|
28
|
+
in the request body.
|
|
29
|
+
|
|
30
|
+
Attributes:
|
|
31
|
+
value (int): The value to be set for the specific vhost limit.
|
|
32
|
+
|
|
33
|
+
Example:
|
|
34
|
+
>>> VhostLimitRequest(value=100)
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
value: int = Field(ge=-1, description="Limit must be -1 (unlimited), 0 (block) or a positive integer.")
|
rabbitmq_management_sdk-0.1.0/src/rabbitmq_management_sdk/domains/v4/admin/schemas/vhost_response.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# domains/v4/vhost/schemas/vhost_response.py
|
|
2
|
+
from typing import Literal
|
|
3
|
+
|
|
4
|
+
from pydantic import Field
|
|
5
|
+
|
|
6
|
+
from rabbitmq_management_sdk.domains.base import RabbitMQBase
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class VhostResponse(RabbitMQBase):
|
|
10
|
+
name: str
|
|
11
|
+
description: str | None = None
|
|
12
|
+
tags: list[str] = Field(default_factory=list)
|
|
13
|
+
tracing: bool
|
|
14
|
+
default_queue_type: Literal["classic", "quorum", "stream"] | None = None
|
|
15
|
+
cluster_state: dict[str, str] | None = None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class VhostLimitValues(RabbitMQBase):
|
|
19
|
+
max_connections: int | None = Field(alias="max-connections", default=None)
|
|
20
|
+
max_queues: int | None = Field(alias="max-queues", default=None)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class VhostLimitResponse(RabbitMQBase):
|
|
24
|
+
"""A single vhost limit as returned by the Management API.
|
|
25
|
+
|
|
26
|
+
Returned by both `GET /api/vhost-limits/{vhost}` and `GET /api/vhost-limits`.
|
|
27
|
+
The `vhost` field is always present, which is useful when listing limits
|
|
28
|
+
across the entire cluster.
|
|
29
|
+
|
|
30
|
+
Example:
|
|
31
|
+
>>> {"vhost": "test", "value": {"max-connections": 3, "max-queues": 3}}
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
vhost: str
|
|
35
|
+
value: VhostLimitValues
|