devaci 0.1.0__py3-none-any.whl

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.
devaci/__init__.py ADDED
@@ -0,0 +1,20 @@
1
+ """devaci - Generate and push Cisco ACI configuration via the Cobra SDK."""
2
+
3
+ from devaci.cobra import CobraBuilder
4
+ from devaci.deploy import DeployClass
5
+ from devaci.exceptions import CobraError, DataError, DeployError, DevaciError, JinjaError
6
+ from devaci.jinja import JinjaRenderer
7
+
8
+ __version__ = "0.1.0"
9
+
10
+ __all__ = [
11
+ "CobraBuilder",
12
+ "DeployClass",
13
+ "JinjaRenderer",
14
+ "DevaciError",
15
+ "CobraError",
16
+ "JinjaError",
17
+ "DataError",
18
+ "DeployError",
19
+ "__version__",
20
+ ]
@@ -0,0 +1,93 @@
1
+ """Cobra model builder for devaci."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from typing import Any, cast
7
+
8
+ import cobra.mit.request
9
+ import cobra.model.pol
10
+
11
+ from devaci.cobra.registry import REGISTRY
12
+ from devaci.console import logger
13
+ from devaci.results import CobraResult
14
+
15
+
16
+ class CobraBuilder:
17
+ """Build an ACI configuration (:class:`ConfigRequest`) from a rendered dict.
18
+
19
+ The dict's top-level keys map to registered handlers (see
20
+ :mod:`devaci.cobra.builders`). Each handler receives the builder and the
21
+ list of objects and is responsible for adding Mo instances to
22
+ :attr:`config`.
23
+ """
24
+
25
+ def __init__(self) -> None:
26
+ self._root = ""
27
+ self._uni = cobra.model.pol.Uni(self._root)
28
+ self.config = cobra.mit.request.ConfigRequest()
29
+ self.result: CobraResult | None = None
30
+
31
+ @property
32
+ def root(self) -> str:
33
+ """Return the ACI root (policy universe DN)."""
34
+ return self._root
35
+
36
+ @property
37
+ def uni(self) -> cobra.model.pol.Uni:
38
+ """Return the root ``pol.Uni`` managed object."""
39
+ return self._uni
40
+
41
+ @property
42
+ def xml(self) -> str | None:
43
+ """Return the rendered XML payload."""
44
+ return cast(str | None, self.config.xmldata)
45
+
46
+ @property
47
+ def json(self) -> Any:
48
+ """Return the rendered JSON payload as a dict."""
49
+ return json.loads(self.config.data)
50
+
51
+ def render(self, output: dict[str, Any]) -> CobraResult:
52
+ """Render ``output`` through the registered handlers.
53
+
54
+ Keys with empty values are skipped; unknown keys and handler errors
55
+ are logged and reported via the returned :class:`CobraResult`.
56
+ """
57
+ logs: list[str] = []
58
+ success = True
59
+
60
+ for key, value in output.items():
61
+ if value in (None, [], {}, ""):
62
+ continue
63
+
64
+ handler = REGISTRY.get(key)
65
+ if handler is None:
66
+ success = False
67
+ msg = f"[Cobra] -> [ConfigError]: Class {key} does not exist."
68
+ logger.warning(msg)
69
+ logs.append(msg)
70
+ continue
71
+
72
+ try:
73
+ handler(self, value)
74
+ msg = f"[Cobra]: Class {key} was rendered successfully."
75
+ logger.info(msg)
76
+ logs.append(msg)
77
+ except Exception as exc:
78
+ success = False
79
+ msg = f"[Cobra] -> [{type(exc).__name__}]: Class {key} failed: {exc}"
80
+ logger.error(msg)
81
+ logs.append(msg)
82
+
83
+ if not self.config.configMos:
84
+ success = False
85
+ msg = "[Cobra] -> [ConfigError]: No object was found in configuration."
86
+ logger.warning(msg)
87
+ logs.append(msg)
88
+
89
+ self.result = CobraResult(success=success, log=logs, config=self.config)
90
+ return self.result
91
+
92
+
93
+ from devaci.cobra import builders # noqa: E402, F401
devaci/cobra/base.py ADDED
@@ -0,0 +1,28 @@
1
+ """Shared helpers for Cobra object builders."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Iterable, Mapping
6
+ from math import isnan
7
+ from typing import Any
8
+
9
+
10
+ def not_nan_str(value: Mapping[str, Any], keys: Iterable[str]) -> bool:
11
+ """Return True when none of the given keys hold an invalid value.
12
+
13
+ A value is considered invalid when it is ``None``, an empty/whitespace
14
+ string, the string ``nan`` (case-insensitive), or a float NaN. Missing
15
+ keys are ignored.
16
+ """
17
+
18
+ def is_invalid(v: Any) -> bool:
19
+ if v is None:
20
+ return True
21
+ if isinstance(v, str):
22
+ v = v.strip()
23
+ return not v or v.lower() == "nan"
24
+ if isinstance(v, float):
25
+ return isnan(v)
26
+ return False
27
+
28
+ return not any(is_invalid(value[k]) for k in keys if k in value)