osp-provider-runtime 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.

Potentially problematic release.


This version of osp-provider-runtime might be problematic. Click here for more details.

@@ -0,0 +1,25 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches: [main]
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: astral-sh/setup-uv@v6
14
+ - name: Sync dev dependencies
15
+ run: uv sync --extra dev
16
+ - name: Ruff
17
+ run: uv run ruff check .
18
+ - name: Mypy
19
+ run: uv run mypy src tests
20
+ - name: Pytest
21
+ run: uv run pytest
22
+ - name: Build artifacts
23
+ run: uv run python -m build
24
+ - name: Validate artifacts
25
+ run: uv run twine check dist/*
@@ -0,0 +1,32 @@
1
+ name: Release Artifacts
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ permissions:
9
+ contents: write
10
+
11
+ jobs:
12
+ build-and-attach:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: astral-sh/setup-uv@v6
17
+ - name: Sync dev dependencies
18
+ run: uv sync --extra dev
19
+ - name: Verify quality gates
20
+ run: |
21
+ uv run ruff check .
22
+ uv run mypy src tests
23
+ uv run pytest
24
+ - name: Build artifacts
25
+ run: uv run python -m build
26
+ - name: Validate artifacts
27
+ run: uv run twine check dist/*
28
+ - name: Create GitHub release and upload artifacts
29
+ uses: softprops/action-gh-release@v2
30
+ with:
31
+ files: dist/*
32
+ generate_release_notes: true
@@ -0,0 +1,9 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .pytest_cache/
5
+ .mypy_cache/
6
+ .ruff_cache/
7
+ .venv/
8
+ dist/
9
+ build/
@@ -0,0 +1,8 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 - 2026-02-12
4
+
5
+ - Initial alpha runtime package.
6
+ - Added thin provider execution runtime and RabbitMQ runner.
7
+ - Added envelope parsing/serialization and explicit ack decision model.
8
+ - Added tests for envelope validation and retry/ack semantics.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 IT-OPS
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,78 @@
1
+ Metadata-Version: 2.4
2
+ Name: osp-provider-runtime
3
+ Version: 0.1.0
4
+ Summary: Thin runtime harness for OSP providers (RabbitMQ transport + contract execution).
5
+ Author: OSP Team
6
+ License: MIT
7
+ License-File: LICENSE
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Typing :: Typed
14
+ Requires-Python: >=3.12
15
+ Requires-Dist: loguru<1,>=0.7
16
+ Requires-Dist: osp-provider-contracts<0.2,>=0.1
17
+ Requires-Dist: pika<2,>=1.3
18
+ Provides-Extra: dev
19
+ Requires-Dist: build<2,>=1.2; extra == 'dev'
20
+ Requires-Dist: hatch<2,>=1.14; extra == 'dev'
21
+ Requires-Dist: mypy<2,>=1.11; extra == 'dev'
22
+ Requires-Dist: pytest<9,>=8.3; extra == 'dev'
23
+ Requires-Dist: ruff<1,>=0.6; extra == 'dev'
24
+ Requires-Dist: twine<7,>=6; extra == 'dev'
25
+ Description-Content-Type: text/markdown
26
+
27
+ # osp-provider-runtime
28
+
29
+ Thin, boring runtime harness for OSP providers.
30
+
31
+ This package handles RabbitMQ message plumbing so provider implementations can
32
+ focus on business logic.
33
+
34
+ ## What it does (v0.1)
35
+
36
+ - Parses a versioned request envelope.
37
+ - Builds provider `RequestContext`/`ProviderRequest` and calls `execute(...)`.
38
+ - Serializes a standard response envelope.
39
+ - Applies explicit ack/requeue/dead-letter decisions.
40
+ - Emits structured logs for delivery decisions.
41
+
42
+ ## What it does not do
43
+
44
+ - No provider framework.
45
+ - No plugin system.
46
+ - No workflow orchestration.
47
+
48
+ ## Install
49
+
50
+ ```bash
51
+ pip install osp-provider-runtime
52
+ ```
53
+
54
+ ## Development
55
+
56
+ ```bash
57
+ env -u VIRTUAL_ENV uv sync --extra dev
58
+ hatch shell
59
+ hatch run check
60
+ hatch run build
61
+ hatch run verify
62
+ ```
63
+
64
+ Note: before `osp-provider-contracts` is published to your index, use `uv` for
65
+ local checks in this monorepo:
66
+
67
+ ```bash
68
+ env -u VIRTUAL_ENV uv run ruff check .
69
+ env -u VIRTUAL_ENV uv run mypy src tests
70
+ env -u VIRTUAL_ENV uv run pytest
71
+ ```
72
+
73
+ Tag and push:
74
+
75
+ ```bash
76
+ git tag v0.1.0
77
+ git push origin v0.1.0
78
+ ```
@@ -0,0 +1,52 @@
1
+ # osp-provider-runtime
2
+
3
+ Thin, boring runtime harness for OSP providers.
4
+
5
+ This package handles RabbitMQ message plumbing so provider implementations can
6
+ focus on business logic.
7
+
8
+ ## What it does (v0.1)
9
+
10
+ - Parses a versioned request envelope.
11
+ - Builds provider `RequestContext`/`ProviderRequest` and calls `execute(...)`.
12
+ - Serializes a standard response envelope.
13
+ - Applies explicit ack/requeue/dead-letter decisions.
14
+ - Emits structured logs for delivery decisions.
15
+
16
+ ## What it does not do
17
+
18
+ - No provider framework.
19
+ - No plugin system.
20
+ - No workflow orchestration.
21
+
22
+ ## Install
23
+
24
+ ```bash
25
+ pip install osp-provider-runtime
26
+ ```
27
+
28
+ ## Development
29
+
30
+ ```bash
31
+ env -u VIRTUAL_ENV uv sync --extra dev
32
+ hatch shell
33
+ hatch run check
34
+ hatch run build
35
+ hatch run verify
36
+ ```
37
+
38
+ Note: before `osp-provider-contracts` is published to your index, use `uv` for
39
+ local checks in this monorepo:
40
+
41
+ ```bash
42
+ env -u VIRTUAL_ENV uv run ruff check .
43
+ env -u VIRTUAL_ENV uv run mypy src tests
44
+ env -u VIRTUAL_ENV uv run pytest
45
+ ```
46
+
47
+ Tag and push:
48
+
49
+ ```bash
50
+ git tag v0.1.0
51
+ git push origin v0.1.0
52
+ ```
@@ -0,0 +1,36 @@
1
+ # Release Guide
2
+
3
+ Automated on tag `vX.Y.Z`:
4
+ - run lint, type-check, tests
5
+ - build sdist + wheel
6
+ - run `twine check dist/*`
7
+ - attach artifacts to GitHub Release
8
+
9
+ Manual/gated publish:
10
+
11
+ ```bash
12
+ env -u VIRTUAL_ENV uv sync --extra dev
13
+ hatch shell
14
+ hatch run check
15
+ hatch run build
16
+ hatch run verify
17
+ hatch run publish
18
+ ```
19
+
20
+ Pre-publish note:
21
+ - `osp-provider-runtime` depends on `osp-provider-contracts`.
22
+ - Publish contracts first (or use local `uv` checks in monorepo before publish).
23
+
24
+ For internal index or other repository:
25
+
26
+ ```bash
27
+ hatch publish -r <repo-name>
28
+ ```
29
+
30
+ Credentials should be stored in `~/.pypirc` (or keyring), not committed.
31
+
32
+ TestPyPI dry run publish:
33
+
34
+ ```bash
35
+ hatch run publish-test
36
+ ```
@@ -0,0 +1,52 @@
1
+ # Runtime Messaging Contract (v0.1)
2
+
3
+ Request envelope required fields:
4
+ - `envelope_version` (string)
5
+ - `contract_version` (string)
6
+ - `message_id` (string)
7
+ - `correlation_id` (string)
8
+ - `task_id` (string)
9
+ - `action` (string)
10
+ - `resource_kind` (string)
11
+ - `payload` (object)
12
+
13
+ Optional fields:
14
+ - `attempt` (int, defaults to 1)
15
+ - `idempotency_key` (string)
16
+ - `provider` (string)
17
+
18
+ Response envelope:
19
+ - `envelope_version`
20
+ - `contract_version`
21
+ - `message_id`
22
+ - `correlation_id`
23
+ - `task_id`
24
+ - `ok` (bool)
25
+ - `provider` (string)
26
+ - `action` (string)
27
+ - `result` (object, only when `ok=true`)
28
+ - `error` (object, only when `ok=false`)
29
+
30
+ Error object shape:
31
+ - `code` (string)
32
+ - `detail` (string | null)
33
+ - `retryable` (bool)
34
+ - `extra` (object)
35
+
36
+ Ack policy:
37
+ - Success: `ack`.
38
+ - `ProviderError(retryable=false)`: `ack`.
39
+ - `ProviderError(retryable=true)`: `requeue` until max attempts; then dead-letter.
40
+ - Unknown exception: treat as retryable transient error with same attempt policy.
41
+
42
+ Legacy compatibility:
43
+ - Runtime also accepts legacy orchestrator outbox payloads:
44
+ - `{"id": <int>, "action": <str>, ...}`
45
+ - For legacy input, runtime can emit provider update events to `osp.to_orch`
46
+ using `status_code=200` on success and `status_code=580` on terminal errors.
47
+
48
+ Runtime topology behavior:
49
+ - Runtime declares and binds its request queue on startup.
50
+ - Default request exchange: `osp.from_orch` (topic).
51
+ - Default request binding: `<provider_name>.#`.
52
+ - Default updates exchange: `osp.to_orch` (topic).
@@ -0,0 +1,85 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.27.0"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "osp-provider-runtime"
7
+ version = "0.1.0"
8
+ description = "Thin runtime harness for OSP providers (RabbitMQ transport + contract execution)."
9
+ readme = "README.md"
10
+ requires-python = ">=3.12"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "OSP Team" }]
13
+ dependencies = [
14
+ "loguru>=0.7,<1",
15
+ "osp-provider-contracts>=0.1,<0.2",
16
+ "pika>=1.3,<2",
17
+ ]
18
+ classifiers = [
19
+ "Development Status :: 3 - Alpha",
20
+ "Intended Audience :: Developers",
21
+ "License :: OSI Approved :: MIT License",
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Typing :: Typed",
25
+ ]
26
+
27
+ [project.optional-dependencies]
28
+ dev = [
29
+ "build>=1.2,<2",
30
+ "hatch>=1.14,<2",
31
+ "mypy>=1.11,<2",
32
+ "pytest>=8.3,<9",
33
+ "ruff>=0.6,<1",
34
+ "twine>=6,<7",
35
+ ]
36
+
37
+ [tool.hatch.build.targets.wheel]
38
+ packages = ["src/osp_provider_runtime"]
39
+
40
+ [tool.ruff]
41
+ line-length = 100
42
+ target-version = "py312"
43
+
44
+ [tool.ruff.lint]
45
+ select = ["E", "F", "I", "UP", "B"]
46
+
47
+ [tool.pytest.ini_options]
48
+ addopts = "-q"
49
+ testpaths = ["tests"]
50
+
51
+ [tool.mypy]
52
+ python_version = "3.12"
53
+ warn_return_any = true
54
+ warn_unused_configs = true
55
+ disallow_untyped_defs = true
56
+ no_implicit_optional = true
57
+ strict_equality = true
58
+
59
+ [[tool.mypy.overrides]]
60
+ module = "pika"
61
+ ignore_missing_imports = true
62
+
63
+ [[tool.mypy.overrides]]
64
+ module = "pika.*"
65
+ ignore_missing_imports = true
66
+
67
+ [tool.hatch.envs.default]
68
+ python = "3.12"
69
+ installer = "uv"
70
+ path = ".venv"
71
+ features = ["dev"]
72
+
73
+ [tool.hatch.envs.default.scripts]
74
+ check = [
75
+ "ruff check .",
76
+ "mypy src tests",
77
+ "pytest",
78
+ ]
79
+ build = "python -m build"
80
+ verify = "twine check dist/*"
81
+ publish-test = "twine upload -r testpypi dist/*"
82
+ publish = "twine upload dist/*"
83
+
84
+ [tool.uv.sources]
85
+ osp-provider-contracts = { path = "../osp-provider-contracts", editable = true }
@@ -0,0 +1,12 @@
1
+ from .app import run_provider, run_provider_with_config
2
+ from .config import RuntimeConfig
3
+ from .runtime import ProviderRuntime
4
+ from .version import __version__
5
+
6
+ __all__ = [
7
+ "__version__",
8
+ "ProviderRuntime",
9
+ "RuntimeConfig",
10
+ "run_provider",
11
+ "run_provider_with_config",
12
+ ]
@@ -0,0 +1,17 @@
1
+ from __future__ import annotations
2
+
3
+ from osp_provider_contracts import Provider
4
+
5
+ from .config import RuntimeConfig
6
+ from .rabbitmq import RabbitMQRunner
7
+ from .runtime import ProviderRuntime
8
+
9
+
10
+ def run_provider(provider: Provider) -> None:
11
+ run_provider_with_config(provider, RuntimeConfig.from_env())
12
+
13
+
14
+ def run_provider_with_config(provider: Provider, config: RuntimeConfig) -> None:
15
+ runtime = ProviderRuntime(provider=provider, config=config)
16
+ runner = RabbitMQRunner(config)
17
+ runner.run(runtime.handle_delivery)
@@ -0,0 +1,36 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from dataclasses import dataclass
5
+
6
+
7
+ @dataclass(frozen=True, slots=True)
8
+ class RuntimeConfig:
9
+ rabbitmq_url: str
10
+ request_queue: str
11
+ request_exchange: str = "osp.from_orch"
12
+ request_binding: str | None = None
13
+ prefetch_count: int = 1
14
+ max_attempts: int = 5
15
+ provider_name: str = "unknown-provider"
16
+ updates_exchange: str = "osp.to_orch"
17
+ updates_routing_key: str = "unknown-provider"
18
+ emit_legacy_updates: bool = True
19
+
20
+ @classmethod
21
+ def from_env(cls) -> RuntimeConfig:
22
+ provider_name = os.getenv("OSP_RUNTIME_PROVIDER_NAME", "unknown-provider")
23
+ request_binding = os.getenv("OSP_RUNTIME_REQUEST_BINDING") or f"{provider_name}.#"
24
+ return cls(
25
+ rabbitmq_url=os.environ["OSP_RUNTIME_RABBITMQ_URL"],
26
+ request_queue=os.environ["OSP_RUNTIME_REQUEST_QUEUE"],
27
+ request_exchange=os.getenv("OSP_RUNTIME_REQUEST_EXCHANGE", "osp.from_orch"),
28
+ request_binding=request_binding,
29
+ prefetch_count=int(os.getenv("OSP_RUNTIME_PREFETCH_COUNT", "1")),
30
+ max_attempts=int(os.getenv("OSP_RUNTIME_MAX_ATTEMPTS", "5")),
31
+ provider_name=provider_name,
32
+ updates_exchange=os.getenv("OSP_RUNTIME_UPDATES_EXCHANGE", "osp.to_orch"),
33
+ updates_routing_key=os.getenv("OSP_RUNTIME_UPDATES_ROUTING_KEY", "unknown-provider"),
34
+ emit_legacy_updates=os.getenv("OSP_RUNTIME_EMIT_LEGACY_UPDATES", "1").lower()
35
+ in {"1", "true", "yes", "y"},
36
+ )
@@ -0,0 +1,115 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from dataclasses import asdict, dataclass
5
+ from typing import Any
6
+
7
+
8
+ @dataclass(frozen=True, slots=True)
9
+ class RequestEnvelope:
10
+ envelope_version: str
11
+ contract_version: str
12
+ message_id: str
13
+ correlation_id: str
14
+ task_id: str
15
+ action: str
16
+ resource_kind: str
17
+ payload: dict[str, Any]
18
+ attempt: int = 1
19
+ idempotency_key: str | None = None
20
+ provider: str | None = None
21
+ source_format: str = "contract_v1"
22
+ legacy_task_id: int | None = None
23
+
24
+
25
+ @dataclass(frozen=True, slots=True)
26
+ class ResponseEnvelope:
27
+ envelope_version: str
28
+ contract_version: str
29
+ message_id: str
30
+ correlation_id: str
31
+ task_id: str
32
+ ok: bool
33
+ provider: str
34
+ action: str
35
+ result: dict[str, Any] | None = None
36
+ error: dict[str, Any] | None = None
37
+
38
+
39
+ REQUIRED_REQUEST_FIELDS = {
40
+ "envelope_version",
41
+ "contract_version",
42
+ "message_id",
43
+ "correlation_id",
44
+ "task_id",
45
+ "action",
46
+ "resource_kind",
47
+ "payload",
48
+ }
49
+
50
+
51
+ def parse_request_envelope(raw_body: bytes) -> RequestEnvelope:
52
+ data = json.loads(raw_body.decode("utf-8"))
53
+
54
+ if not isinstance(data, dict):
55
+ raise ValueError("request envelope must be a JSON object")
56
+
57
+ missing = sorted(REQUIRED_REQUEST_FIELDS - set(data.keys()))
58
+ if not missing:
59
+ payload = data["payload"]
60
+ if not isinstance(payload, dict):
61
+ raise ValueError("payload must be a JSON object")
62
+
63
+ attempt = data.get("attempt", 1)
64
+ if not isinstance(attempt, int) or attempt < 1:
65
+ raise ValueError("attempt must be an integer >= 1")
66
+
67
+ return RequestEnvelope(
68
+ envelope_version=str(data["envelope_version"]),
69
+ contract_version=str(data["contract_version"]),
70
+ message_id=str(data["message_id"]),
71
+ correlation_id=str(data["correlation_id"]),
72
+ task_id=str(data["task_id"]),
73
+ action=str(data["action"]),
74
+ resource_kind=str(data["resource_kind"]),
75
+ payload=payload,
76
+ attempt=attempt,
77
+ idempotency_key=(str(data["idempotency_key"]) if "idempotency_key" in data else None),
78
+ provider=(str(data["provider"]) if "provider" in data else None),
79
+ source_format="contract_v1",
80
+ legacy_task_id=None,
81
+ )
82
+
83
+ # Legacy orchestrator outbox payload:
84
+ # {"id": int, "action": str, ...} with provider-specific fields at top level.
85
+ if "id" not in data or "action" not in data:
86
+ raise ValueError(f"missing required envelope fields: {missing}")
87
+ task_id = data.get("id")
88
+ action = data.get("action")
89
+ if not isinstance(task_id, int) or not isinstance(action, str) or not action.strip():
90
+ raise ValueError("legacy message requires id:int and action:str")
91
+ attempt = data.get("attempt", 1)
92
+ if not isinstance(attempt, int) or attempt < 1:
93
+ raise ValueError("attempt must be an integer >= 1")
94
+ provider = data.get("provider")
95
+ message_id = str(data.get("message_id") or f"legacy-task-{task_id}")
96
+ correlation_id = str(data.get("correlation_id") or message_id)
97
+ return RequestEnvelope(
98
+ envelope_version="legacy_v1",
99
+ contract_version="legacy_v1",
100
+ message_id=message_id,
101
+ correlation_id=correlation_id,
102
+ task_id=str(task_id),
103
+ action=action.strip(),
104
+ resource_kind=str(data.get("resource_kind") or "task"),
105
+ payload=data,
106
+ attempt=attempt,
107
+ idempotency_key=(str(data["idempotency_key"]) if "idempotency_key" in data else None),
108
+ provider=(str(provider) if provider is not None else None),
109
+ source_format="legacy_orchestrator",
110
+ legacy_task_id=task_id,
111
+ )
112
+
113
+
114
+ def dump_response_envelope(envelope: ResponseEnvelope) -> bytes:
115
+ return json.dumps(asdict(envelope), separators=(",", ":")).encode("utf-8")
@@ -0,0 +1,83 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ import pika # type: ignore[import-untyped]
6
+ from loguru import logger
7
+
8
+ from .config import RuntimeConfig
9
+ from .transport import AckAction, Delivery, DeliveryHandler
10
+
11
+
12
+ class RabbitMQRunner:
13
+ def __init__(self, config: RuntimeConfig) -> None:
14
+ self.config = config
15
+
16
+ def run(self, handler: DeliveryHandler) -> None:
17
+ params = pika.URLParameters(self.config.rabbitmq_url)
18
+ connection = pika.BlockingConnection(params)
19
+ channel = connection.channel()
20
+ channel.basic_qos(prefetch_count=self.config.prefetch_count)
21
+ channel.exchange_declare(
22
+ exchange=self.config.request_exchange,
23
+ exchange_type="topic",
24
+ durable=True,
25
+ )
26
+ channel.exchange_declare(
27
+ exchange=self.config.updates_exchange,
28
+ exchange_type="topic",
29
+ durable=True,
30
+ )
31
+ channel.queue_declare(queue=self.config.request_queue, durable=True)
32
+ if self.config.request_binding:
33
+ channel.queue_bind(
34
+ queue=self.config.request_queue,
35
+ exchange=self.config.request_exchange,
36
+ routing_key=self.config.request_binding,
37
+ )
38
+
39
+ def _on_message(ch: Any, method: Any, properties: Any, body: bytes) -> None:
40
+ delivery = Delivery(
41
+ body=body,
42
+ reply_to=properties.reply_to,
43
+ routing_key=getattr(method, "routing_key", None),
44
+ )
45
+ result = handler(delivery)
46
+
47
+ if result.response_body and result.response_routing_key:
48
+ channel.basic_publish(
49
+ exchange="",
50
+ routing_key=result.response_routing_key,
51
+ body=result.response_body,
52
+ properties=pika.BasicProperties(
53
+ content_type="application/json",
54
+ correlation_id=result.response_correlation_id,
55
+ ),
56
+ )
57
+ if result.update_body and result.update_exchange and result.update_routing_key:
58
+ channel.basic_publish(
59
+ exchange=result.update_exchange,
60
+ routing_key=result.update_routing_key,
61
+ body=result.update_body,
62
+ properties=pika.BasicProperties(
63
+ content_type="application/json",
64
+ ),
65
+ )
66
+
67
+ if result.ack_action == AckAction.ACK:
68
+ ch.basic_ack(delivery_tag=method.delivery_tag)
69
+ elif result.ack_action == AckAction.REQUEUE:
70
+ ch.basic_nack(delivery_tag=method.delivery_tag, requeue=True)
71
+ else:
72
+ ch.basic_nack(delivery_tag=method.delivery_tag, requeue=False)
73
+
74
+ channel.basic_consume(queue=self.config.request_queue, on_message_callback=_on_message)
75
+ logger.info(
76
+ "Starting runtime consumer",
77
+ queue=self.config.request_queue,
78
+ exchange=self.config.request_exchange,
79
+ binding=self.config.request_binding,
80
+ updates_exchange=self.config.updates_exchange,
81
+ updates_routing_key=self.config.updates_routing_key,
82
+ )
83
+ channel.start_consuming()