annet 0.16.16__tar.gz → 0.16.18__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 annet might be problematic. Click here for more details.

Files changed (181) hide show
  1. {annet-0.16.16/annet.egg-info → annet-0.16.18}/PKG-INFO +1 -1
  2. {annet-0.16.16 → annet-0.16.18}/annet/configs/context.yml +1 -0
  3. annet-0.16.18/annet/rpl/__init__.py +27 -0
  4. annet-0.16.18/annet/rpl/action.py +51 -0
  5. annet-0.16.18/annet/rpl/condition.py +94 -0
  6. annet-0.16.18/annet/rpl/match_builder.py +103 -0
  7. annet-0.16.18/annet/rpl/policy.py +22 -0
  8. annet-0.16.18/annet/rpl/result.py +8 -0
  9. annet-0.16.18/annet/rpl/routemap.py +76 -0
  10. annet-0.16.18/annet/rpl/statement_builder.py +267 -0
  11. annet-0.16.18/annet/rulebook/cisco/misc.py +149 -0
  12. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/texts/arista.rul +1 -0
  13. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/texts/cisco.rul +1 -1
  14. {annet-0.16.16 → annet-0.16.18/annet.egg-info}/PKG-INFO +1 -1
  15. {annet-0.16.16 → annet-0.16.18}/annet.egg-info/SOURCES.txt +13 -1
  16. annet-0.16.18/annet_generators/rpl_example/__init__.py +9 -0
  17. annet-0.16.18/annet_generators/rpl_example/items.py +31 -0
  18. annet-0.16.18/annet_generators/rpl_example/policy_generator.py +233 -0
  19. annet-0.16.18/annet_generators/rpl_example/route_policy.py +33 -0
  20. annet-0.16.16/annet/rulebook/cisco/misc.py +0 -57
  21. {annet-0.16.16 → annet-0.16.18}/AUTHORS +0 -0
  22. {annet-0.16.16 → annet-0.16.18}/LICENSE +0 -0
  23. {annet-0.16.16 → annet-0.16.18}/MANIFEST.in +0 -0
  24. {annet-0.16.16 → annet-0.16.18}/README.md +0 -0
  25. {annet-0.16.16 → annet-0.16.18}/annet/__init__.py +0 -0
  26. {annet-0.16.16 → annet-0.16.18}/annet/adapters/__init__.py +0 -0
  27. {annet-0.16.16 → annet-0.16.18}/annet/adapters/fetchers/__init__.py +0 -0
  28. {annet-0.16.16 → annet-0.16.18}/annet/adapters/fetchers/stub/__init__.py +0 -0
  29. {annet-0.16.16 → annet-0.16.18}/annet/adapters/fetchers/stub/fetcher.py +0 -0
  30. {annet-0.16.16 → annet-0.16.18}/annet/adapters/file/__init__.py +0 -0
  31. {annet-0.16.16 → annet-0.16.18}/annet/adapters/file/provider.py +0 -0
  32. {annet-0.16.16 → annet-0.16.18}/annet/adapters/netbox/__init__.py +0 -0
  33. {annet-0.16.16 → annet-0.16.18}/annet/adapters/netbox/common/__init__.py +0 -0
  34. {annet-0.16.16 → annet-0.16.18}/annet/adapters/netbox/common/client.py +0 -0
  35. {annet-0.16.16 → annet-0.16.18}/annet/adapters/netbox/common/manufacturer.py +0 -0
  36. {annet-0.16.16 → annet-0.16.18}/annet/adapters/netbox/common/models.py +0 -0
  37. {annet-0.16.16 → annet-0.16.18}/annet/adapters/netbox/common/query.py +0 -0
  38. {annet-0.16.16 → annet-0.16.18}/annet/adapters/netbox/common/status_client.py +0 -0
  39. {annet-0.16.16 → annet-0.16.18}/annet/adapters/netbox/common/storage_opts.py +0 -0
  40. {annet-0.16.16 → annet-0.16.18}/annet/adapters/netbox/provider.py +0 -0
  41. {annet-0.16.16 → annet-0.16.18}/annet/adapters/netbox/v24/__init__.py +0 -0
  42. {annet-0.16.16 → annet-0.16.18}/annet/adapters/netbox/v24/storage.py +0 -0
  43. {annet-0.16.16 → annet-0.16.18}/annet/adapters/netbox/v37/__init__.py +0 -0
  44. {annet-0.16.16 → annet-0.16.18}/annet/adapters/netbox/v37/storage.py +0 -0
  45. {annet-0.16.16 → annet-0.16.18}/annet/annet.py +0 -0
  46. {annet-0.16.16 → annet-0.16.18}/annet/annlib/__init__.py +0 -0
  47. {annet-0.16.16 → annet-0.16.18}/annet/annlib/command.py +0 -0
  48. {annet-0.16.16 → annet-0.16.18}/annet/annlib/diff.py +0 -0
  49. {annet-0.16.16 → annet-0.16.18}/annet/annlib/errors.py +0 -0
  50. {annet-0.16.16 → annet-0.16.18}/annet/annlib/filter_acl.py +0 -0
  51. {annet-0.16.16 → annet-0.16.18}/annet/annlib/jsontools.py +0 -0
  52. {annet-0.16.16 → annet-0.16.18}/annet/annlib/lib.py +0 -0
  53. {annet-0.16.16 → annet-0.16.18}/annet/annlib/netdev/__init__.py +0 -0
  54. {annet-0.16.16 → annet-0.16.18}/annet/annlib/netdev/db.py +0 -0
  55. {annet-0.16.16 → annet-0.16.18}/annet/annlib/netdev/devdb/__init__.py +0 -0
  56. {annet-0.16.16 → annet-0.16.18}/annet/annlib/netdev/devdb/data/devdb.json +0 -0
  57. {annet-0.16.16 → annet-0.16.18}/annet/annlib/netdev/views/__init__.py +0 -0
  58. {annet-0.16.16 → annet-0.16.18}/annet/annlib/netdev/views/dump.py +0 -0
  59. {annet-0.16.16 → annet-0.16.18}/annet/annlib/netdev/views/hardware.py +0 -0
  60. {annet-0.16.16 → annet-0.16.18}/annet/annlib/output.py +0 -0
  61. {annet-0.16.16 → annet-0.16.18}/annet/annlib/patching.py +0 -0
  62. {annet-0.16.16 → annet-0.16.18}/annet/annlib/rbparser/__init__.py +0 -0
  63. {annet-0.16.16 → annet-0.16.18}/annet/annlib/rbparser/acl.py +0 -0
  64. {annet-0.16.16 → annet-0.16.18}/annet/annlib/rbparser/deploying.py +0 -0
  65. {annet-0.16.16 → annet-0.16.18}/annet/annlib/rbparser/ordering.py +0 -0
  66. {annet-0.16.16 → annet-0.16.18}/annet/annlib/rbparser/platform.py +0 -0
  67. {annet-0.16.16 → annet-0.16.18}/annet/annlib/rbparser/syntax.py +0 -0
  68. {annet-0.16.16 → annet-0.16.18}/annet/annlib/rulebook/__init__.py +0 -0
  69. {annet-0.16.16 → annet-0.16.18}/annet/annlib/rulebook/common.py +0 -0
  70. {annet-0.16.16 → annet-0.16.18}/annet/annlib/tabparser.py +0 -0
  71. {annet-0.16.16 → annet-0.16.18}/annet/annlib/types.py +0 -0
  72. {annet-0.16.16 → annet-0.16.18}/annet/api/__init__.py +0 -0
  73. {annet-0.16.16 → annet-0.16.18}/annet/argparse.py +0 -0
  74. {annet-0.16.16 → annet-0.16.18}/annet/bgp_models.py +0 -0
  75. {annet-0.16.16 → annet-0.16.18}/annet/cli.py +0 -0
  76. {annet-0.16.16 → annet-0.16.18}/annet/cli_args.py +0 -0
  77. {annet-0.16.16 → annet-0.16.18}/annet/configs/logging.yaml +0 -0
  78. {annet-0.16.16 → annet-0.16.18}/annet/connectors.py +0 -0
  79. {annet-0.16.16 → annet-0.16.18}/annet/deploy.py +0 -0
  80. {annet-0.16.16 → annet-0.16.18}/annet/diff.py +0 -0
  81. {annet-0.16.16 → annet-0.16.18}/annet/executor.py +0 -0
  82. {annet-0.16.16 → annet-0.16.18}/annet/filtering.py +0 -0
  83. {annet-0.16.16 → annet-0.16.18}/annet/gen.py +0 -0
  84. {annet-0.16.16 → annet-0.16.18}/annet/generators/__init__.py +0 -0
  85. {annet-0.16.16 → annet-0.16.18}/annet/generators/base.py +0 -0
  86. {annet-0.16.16 → annet-0.16.18}/annet/generators/common/__init__.py +0 -0
  87. {annet-0.16.16 → annet-0.16.18}/annet/generators/common/initial.py +0 -0
  88. {annet-0.16.16 → annet-0.16.18}/annet/generators/entire.py +0 -0
  89. {annet-0.16.16 → annet-0.16.18}/annet/generators/exceptions.py +0 -0
  90. {annet-0.16.16 → annet-0.16.18}/annet/generators/jsonfragment.py +0 -0
  91. {annet-0.16.16 → annet-0.16.18}/annet/generators/partial.py +0 -0
  92. {annet-0.16.16 → annet-0.16.18}/annet/generators/perf.py +0 -0
  93. {annet-0.16.16 → annet-0.16.18}/annet/generators/ref.py +0 -0
  94. {annet-0.16.16 → annet-0.16.18}/annet/generators/result.py +0 -0
  95. {annet-0.16.16 → annet-0.16.18}/annet/hardware.py +0 -0
  96. {annet-0.16.16 → annet-0.16.18}/annet/implicit.py +0 -0
  97. {annet-0.16.16 → annet-0.16.18}/annet/lib.py +0 -0
  98. {annet-0.16.16 → annet-0.16.18}/annet/mesh/__init__.py +0 -0
  99. {annet-0.16.16 → annet-0.16.18}/annet/mesh/basemodel.py +0 -0
  100. {annet-0.16.16 → annet-0.16.18}/annet/mesh/device_models.py +0 -0
  101. {annet-0.16.16 → annet-0.16.18}/annet/mesh/executor.py +0 -0
  102. {annet-0.16.16 → annet-0.16.18}/annet/mesh/match_args.py +0 -0
  103. {annet-0.16.16 → annet-0.16.18}/annet/mesh/models_converter.py +0 -0
  104. {annet-0.16.16 → annet-0.16.18}/annet/mesh/peer_models.py +0 -0
  105. {annet-0.16.16 → annet-0.16.18}/annet/mesh/registry.py +0 -0
  106. {annet-0.16.16 → annet-0.16.18}/annet/output.py +0 -0
  107. {annet-0.16.16 → annet-0.16.18}/annet/parallel.py +0 -0
  108. {annet-0.16.16 → annet-0.16.18}/annet/patching.py +0 -0
  109. {annet-0.16.16 → annet-0.16.18}/annet/reference.py +0 -0
  110. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/__init__.py +0 -0
  111. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/arista/__init__.py +0 -0
  112. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/arista/iface.py +0 -0
  113. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/aruba/__init__.py +0 -0
  114. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/aruba/ap_env.py +0 -0
  115. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/aruba/misc.py +0 -0
  116. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/b4com/__init__.py +0 -0
  117. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/b4com/file.py +0 -0
  118. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/cisco/__init__.py +0 -0
  119. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/cisco/iface.py +0 -0
  120. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/cisco/vlandb.py +0 -0
  121. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/common.py +0 -0
  122. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/deploying.py +0 -0
  123. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/huawei/__init__.py +0 -0
  124. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/huawei/aaa.py +0 -0
  125. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/huawei/bgp.py +0 -0
  126. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/huawei/iface.py +0 -0
  127. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/huawei/misc.py +0 -0
  128. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/huawei/vlandb.py +0 -0
  129. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/juniper/__init__.py +0 -0
  130. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/nexus/__init__.py +0 -0
  131. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/nexus/iface.py +0 -0
  132. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/patching.py +0 -0
  133. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/ribbon/__init__.py +0 -0
  134. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/routeros/__init__.py +0 -0
  135. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/routeros/file.py +0 -0
  136. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/texts/arista.deploy +0 -0
  137. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/texts/arista.order +0 -0
  138. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/texts/aruba.deploy +0 -0
  139. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/texts/aruba.order +0 -0
  140. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/texts/aruba.rul +0 -0
  141. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/texts/b4com.deploy +0 -0
  142. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/texts/b4com.order +0 -0
  143. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/texts/b4com.rul +0 -0
  144. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/texts/cisco.deploy +0 -0
  145. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/texts/cisco.order +0 -0
  146. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/texts/huawei.deploy +0 -0
  147. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/texts/huawei.order +0 -0
  148. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/texts/huawei.rul +0 -0
  149. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/texts/juniper.rul +0 -0
  150. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/texts/nexus.deploy +0 -0
  151. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/texts/nexus.order +0 -0
  152. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/texts/nexus.rul +0 -0
  153. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/texts/nokia.rul +0 -0
  154. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/texts/optixtrans.deploy +0 -0
  155. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/texts/optixtrans.order +0 -0
  156. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/texts/optixtrans.rul +0 -0
  157. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/texts/pc.deploy +0 -0
  158. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/texts/pc.order +0 -0
  159. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/texts/pc.rul +0 -0
  160. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/texts/ribbon.deploy +0 -0
  161. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/texts/ribbon.rul +0 -0
  162. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/texts/routeros.order +0 -0
  163. {annet-0.16.16 → annet-0.16.18}/annet/rulebook/texts/routeros.rul +0 -0
  164. {annet-0.16.16 → annet-0.16.18}/annet/storage.py +0 -0
  165. {annet-0.16.16 → annet-0.16.18}/annet/tabparser.py +0 -0
  166. {annet-0.16.16 → annet-0.16.18}/annet/text_term_format.py +0 -0
  167. {annet-0.16.16 → annet-0.16.18}/annet/tracing.py +0 -0
  168. {annet-0.16.16 → annet-0.16.18}/annet/types.py +0 -0
  169. {annet-0.16.16 → annet-0.16.18}/annet.egg-info/dependency_links.txt +0 -0
  170. {annet-0.16.16 → annet-0.16.18}/annet.egg-info/entry_points.txt +0 -0
  171. {annet-0.16.16 → annet-0.16.18}/annet.egg-info/requires.txt +0 -0
  172. {annet-0.16.16 → annet-0.16.18}/annet.egg-info/top_level.txt +0 -0
  173. {annet-0.16.16 → annet-0.16.18}/annet_generators/__init__.py +0 -0
  174. {annet-0.16.16 → annet-0.16.18}/annet_generators/example/__init__.py +0 -0
  175. {annet-0.16.16 → annet-0.16.18}/annet_generators/example/lldp.py +0 -0
  176. {annet-0.16.16 → annet-0.16.18}/annet_generators/mesh_example/__init__.py +0 -0
  177. {annet-0.16.16 → annet-0.16.18}/annet_generators/mesh_example/bgp.py +0 -0
  178. {annet-0.16.16 → annet-0.16.18}/annet_generators/mesh_example/mesh_logic.py +0 -0
  179. {annet-0.16.16 → annet-0.16.18}/requirements.txt +0 -0
  180. {annet-0.16.16 → annet-0.16.18}/setup.cfg +0 -0
  181. {annet-0.16.16 → annet-0.16.18}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: annet
3
- Version: 0.16.16
3
+ Version: 0.16.18
4
4
  Summary: annet
5
5
  Home-page: https://github.com/annetutil/annet
6
6
  License: MIT
@@ -2,6 +2,7 @@ generators:
2
2
  default:
3
3
  - annet_generators.example
4
4
  - annet_generators.mesh_example
5
+ - annet_generators.rpl_example
5
6
 
6
7
  storage:
7
8
  default:
@@ -0,0 +1,27 @@
1
+ __all__ = [
2
+ "MatchField",
3
+ "ThenField",
4
+ "RouteMap",
5
+ "Route",
6
+ "ResultType",
7
+ "ActionType",
8
+ "Action",
9
+ "SingleAction",
10
+ "AndCondition",
11
+ "R",
12
+ "ConditionOperator",
13
+ "Condition",
14
+ "SingleCondition",
15
+ "RoutingPolicyStatement",
16
+ "RoutingPolicy",
17
+ "CommunityActionValue",
18
+ "PrefixMatchValue",
19
+ ]
20
+
21
+ from .action import Action, ActionType, SingleAction
22
+ from .condition import AndCondition, Condition, ConditionOperator, SingleCondition
23
+ from .match_builder import R, MatchField, PrefixMatchValue
24
+ from .policy import RoutingPolicyStatement, RoutingPolicy
25
+ from .result import ResultType
26
+ from .routemap import RouteMap, Route
27
+ from .statement_builder import ThenField, CommunityActionValue
@@ -0,0 +1,51 @@
1
+ from collections.abc import Iterator, Iterable
2
+ from dataclasses import dataclass
3
+ from enum import Enum
4
+ from typing import Generic, TypeVar, Any
5
+
6
+
7
+ class ActionType(Enum):
8
+ SET = "set"
9
+ ADD = "add"
10
+ REMOVE = "delete"
11
+
12
+ CUSTOM = "custom"
13
+
14
+
15
+ ValueT = TypeVar("ValueT")
16
+
17
+
18
+ @dataclass
19
+ class SingleAction(Generic[ValueT]):
20
+ field: str
21
+ type: ActionType
22
+ value: ValueT
23
+
24
+
25
+ class Action:
26
+ def __init__(self) -> None:
27
+ self.actions: list[SingleAction[Any]] = []
28
+
29
+ def append(self, action: SingleAction[Any]) -> None:
30
+ self.actions.append(action)
31
+
32
+ def __repr__(self):
33
+ actions = ", ".join(repr(c) for c in self.actions)
34
+ return f"Action({actions})"
35
+
36
+ def __getitem__(self, item: str) -> SingleAction[Any]:
37
+ return next(c for c in self.actions if c.field == item)
38
+
39
+ def __contains__(self, item: str) -> bool:
40
+ return any(c.field == item for c in self.actions)
41
+
42
+ def __len__(self) -> int:
43
+ return len(self.actions)
44
+
45
+ def __iter__(self) -> Iterator[SingleAction[Any]]:
46
+ return iter(self.actions)
47
+
48
+ def find_all(self, item: str) -> Iterable[SingleAction[Any]]:
49
+ for action in self.actions:
50
+ if action.field == item:
51
+ yield action
@@ -0,0 +1,94 @@
1
+ from collections.abc import Iterator, Iterable
2
+ from dataclasses import dataclass
3
+ from enum import Enum
4
+ from typing import Generic, TypeVar, Sequence, Union, Any
5
+
6
+
7
+ class ConditionOperator(Enum):
8
+ EQ = "=="
9
+ GE = ">="
10
+ GT = ">"
11
+ LE = "<="
12
+ LT = "<"
13
+ BETWEEN_INCLUDED = "BETWEEN_INCLUDED"
14
+
15
+ HAS = "has"
16
+ HAS_ANY = "has_any"
17
+
18
+ CUSTOM = "custom"
19
+
20
+
21
+ ValueT = TypeVar("ValueT")
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class SingleCondition(Generic[ValueT]):
26
+ field: str
27
+ operator: ConditionOperator
28
+ value: ValueT
29
+
30
+ def __and__(self, other: "Condition") -> "Condition":
31
+ return AndCondition(self, other)
32
+
33
+ def merge(self, other: "SingleCondition[Any]") -> "SingleCondition[Any]":
34
+ if other.field != self.field:
35
+ raise ValueError(f"Cannot merge conditions with different fields: {self.field} != {other.field}")
36
+ if self.operator is ConditionOperator.LE:
37
+ if other.operator is ConditionOperator.GE:
38
+ return SingleCondition(self.field, ConditionOperator.BETWEEN_INCLUDED, (other.value, self.value))
39
+ elif other.operator is ConditionOperator.LE:
40
+ return SingleCondition(self.field, ConditionOperator.LE, min(self.value, other.value))
41
+ elif self.operator is ConditionOperator.GE:
42
+ if other.operator is ConditionOperator.LE:
43
+ return other.merge(self)
44
+ elif other.operator is ConditionOperator.GE:
45
+ return SingleCondition(self.field, ConditionOperator.GE, max(self.value, other.value))
46
+ elif self.operator is ConditionOperator.LT:
47
+ if other.operator is ConditionOperator.LT:
48
+ return SingleCondition(self.field, ConditionOperator.LT, min(self.value, other.value))
49
+ elif self.operator is ConditionOperator.GT:
50
+ if other.operator is ConditionOperator.GT:
51
+ return SingleCondition(self.field, ConditionOperator.GT, max(self.value, other.value))
52
+ raise ValueError(f"Cannot merge condition with operator {self.operator} and {other.operator}")
53
+
54
+
55
+ Condition = Union[SingleCondition, "AndCondition"]
56
+
57
+
58
+ class AndCondition:
59
+ def __init__(self, *conditions: Condition):
60
+ self.conditions: list[SingleCondition[Any]] = []
61
+ for c in conditions:
62
+ self.conditions.extend(self._unpack(c))
63
+
64
+ def _unpack(self, other: Condition) -> Sequence[SingleCondition]:
65
+ if isinstance(other, AndCondition):
66
+ return other.conditions
67
+ return [other]
68
+
69
+ def __and__(self, other: Condition) -> "AndCondition":
70
+ return AndCondition(*self.conditions, other)
71
+
72
+ def __iadd__(self, other):
73
+ self.conditions.extend(self._unpack(other))
74
+
75
+ def __repr__(self):
76
+ conditions = ", ".join(repr(c) for c in self.conditions)
77
+ return f"AndCondition({conditions})"
78
+
79
+ def __getitem__(self, item: str) -> SingleCondition[Any]:
80
+ return next(c for c in self.conditions if c.field == item)
81
+
82
+ def __contains__(self, item: str) -> bool:
83
+ return any(c.field == item for c in self.conditions)
84
+
85
+ def __len__(self) -> int:
86
+ return len(self.conditions)
87
+
88
+ def __iter__(self) -> Iterator[SingleCondition[Any]]:
89
+ return iter(self.conditions)
90
+
91
+ def find_all(self, item: str) -> Iterable[SingleCondition[Any]]:
92
+ for condition in self.conditions:
93
+ if condition.field == item:
94
+ yield condition
@@ -0,0 +1,103 @@
1
+ from dataclasses import dataclass
2
+ from enum import Enum
3
+ from typing import Generic, Sequence, Callable, Optional, TypeVar, Any
4
+
5
+ from .condition import SingleCondition, ConditionOperator, AndCondition
6
+
7
+
8
+ class MatchField(str, Enum):
9
+ community = "community"
10
+ extcommunity = "extcommunity"
11
+ rd = "rd"
12
+ interface = "interface"
13
+ protocol = "protocol"
14
+ net_len = "net_len"
15
+ local_pref = "local_pref"
16
+ metric = "metric"
17
+ family = "family"
18
+
19
+ as_path_length = "as_path_length"
20
+ as_path_filter = "as_path_filter"
21
+ ipv6_prefix = "ipv6_prefix"
22
+ ip_prefix = "ip_prefix"
23
+
24
+
25
+ ValueT = TypeVar("ValueT")
26
+ _ConditionMethod = Callable[["ConditionFactory[ValueT]", ValueT], SingleCondition[ValueT]]
27
+
28
+
29
+ def condition_method(operator: ConditionOperator) -> _ConditionMethod:
30
+ def method(self: "ConditionFactory[ValueT]", other: ValueT) -> SingleCondition[ValueT]:
31
+ if operator.value not in self.supported_ops:
32
+ raise NotImplementedError(f"Operator {operator.value} is not supported for field {self.field}")
33
+ return SingleCondition(self.field, operator, other)
34
+
35
+ method.__name__ = operator.value
36
+ return method
37
+
38
+
39
+ class ConditionFactory(Generic[ValueT]):
40
+ def __init__(self, field: str, supported_ops: list[str]):
41
+ self.field = field
42
+ self.supported_ops = supported_ops
43
+
44
+ # https://github.com/python/typeshed/issues/3685
45
+ eq = __eq__ = condition_method(ConditionOperator.EQ) # type: ignore[assignment]
46
+ gt = __gt__ = condition_method(ConditionOperator.GT)
47
+ ge = __ge__ = condition_method(ConditionOperator.GE)
48
+ lt = __lt__ = condition_method(ConditionOperator.LT)
49
+ le = __le__ = condition_method(ConditionOperator.LE)
50
+ between_included = condition_method(ConditionOperator.BETWEEN_INCLUDED)
51
+
52
+
53
+ class SetConditionFactory(Generic[ValueT]):
54
+ def __init__(self, field: str) -> None:
55
+ self.field = field
56
+
57
+ def has(self, *values: ValueT) -> SingleCondition[Sequence[ValueT]]:
58
+ return SingleCondition(self.field, ConditionOperator.HAS, values)
59
+
60
+ def has_any(self, *values: ValueT) -> SingleCondition[Sequence[ValueT]]:
61
+ return SingleCondition(self.field, ConditionOperator.HAS_ANY, values)
62
+
63
+
64
+ @dataclass(frozen=True)
65
+ class PrefixMatchValue:
66
+ names: Sequence[str]
67
+ or_longer: Optional[tuple[int, int]] # ????
68
+
69
+
70
+ class Checkable:
71
+ def __init__(self):
72
+ self.community = SetConditionFactory[str](MatchField.community)
73
+ self.extcommunity = SetConditionFactory[str](MatchField.extcommunity)
74
+ self.rd = SetConditionFactory[str](MatchField.rd)
75
+ self.interface = ConditionFactory[str](MatchField.interface, ["=="])
76
+ self.protocol = ConditionFactory[str](MatchField.protocol, ["=="])
77
+ self.net_len = ConditionFactory[int](MatchField.net_len, ["==", "!="])
78
+ self.local_pref = ConditionFactory[int](MatchField.local_pref, ["<"])
79
+ self.metric = ConditionFactory[int](MatchField.metric, ["=="])
80
+ self.family = ConditionFactory[int](MatchField.family, ["=="])
81
+ self.as_path_length = ConditionFactory[int](MatchField.as_path_length, ["==", ">=", "<=", "BETWEEN_INCLUDED"])
82
+
83
+ def as_path_filter(self, name: str) -> SingleCondition[str]:
84
+ return SingleCondition(MatchField.as_path_filter, ConditionOperator.EQ, name)
85
+
86
+ def match_v6(self, *names: str, or_longer: Optional[tuple[int, int]] = None) -> SingleCondition[PrefixMatchValue]:
87
+ return SingleCondition(MatchField.ipv6_prefix, ConditionOperator.CUSTOM, PrefixMatchValue(names, or_longer))
88
+
89
+ def match_v4(self, *names: str, or_longer: Optional[tuple[int, int]] = None) -> SingleCondition[PrefixMatchValue]:
90
+ return SingleCondition(MatchField.ip_prefix, ConditionOperator.CUSTOM, PrefixMatchValue(names, or_longer))
91
+
92
+
93
+ def merge_conditions(and_condition: AndCondition) -> AndCondition:
94
+ conditions: dict[str, SingleCondition[Any]] = {}
95
+ for condition in and_condition.conditions:
96
+ if condition.field in conditions:
97
+ conditions[condition.field] = conditions[condition.field].merge(condition)
98
+ else:
99
+ conditions[condition.field] = condition
100
+ return AndCondition(*conditions.values())
101
+
102
+
103
+ R = Checkable()
@@ -0,0 +1,22 @@
1
+ from collections.abc import Sequence
2
+ from dataclasses import dataclass
3
+ from typing import Optional
4
+
5
+ from .action import Action
6
+ from .condition import AndCondition
7
+ from .result import ResultType
8
+
9
+
10
+ @dataclass
11
+ class RoutingPolicyStatement:
12
+ name: Optional[str]
13
+ number: Optional[int]
14
+ match: AndCondition
15
+ then: Action
16
+ result: ResultType
17
+
18
+
19
+ @dataclass
20
+ class RoutingPolicy:
21
+ name: str
22
+ statements: Sequence[RoutingPolicyStatement]
@@ -0,0 +1,8 @@
1
+ from enum import Enum
2
+
3
+
4
+ class ResultType(str, Enum):
5
+ ALLOW = "allow"
6
+ DENY = "deny"
7
+ NEXT = "next"
8
+ NEXT_POLICY = "next_policy"
@@ -0,0 +1,76 @@
1
+ from dataclasses import dataclass
2
+ from typing import Optional, Callable, Generic, TypeVar, Union
3
+
4
+ from .action import Action
5
+ from .condition import AndCondition, Condition
6
+ from .match_builder import merge_conditions
7
+ from .policy import RoutingPolicy, RoutingPolicyStatement
8
+ from .result import ResultType
9
+ from .statement_builder import StatementBuilder
10
+
11
+
12
+ class Route:
13
+ def __init__(self, name: str):
14
+ self.name = name
15
+ self.statements: list[RoutingPolicyStatement] = []
16
+
17
+ def __call__(
18
+ self,
19
+ *conditions: Condition,
20
+ name: Optional[str] = None,
21
+ number: Optional[int] = None,
22
+ ) -> "StatementBuilder":
23
+ statement = RoutingPolicyStatement(
24
+ name=name,
25
+ number=number,
26
+ match=merge_conditions(AndCondition(*conditions)),
27
+ then=Action(),
28
+ result=ResultType.NEXT,
29
+ )
30
+ self.statements.append(statement)
31
+ return StatementBuilder(statement=statement)
32
+
33
+
34
+ DeviceT = TypeVar("DeviceT")
35
+ RouteHandlerFunc = Callable[[DeviceT, Route], None]
36
+ Decorator = Callable[[RouteHandlerFunc[DeviceT]], RouteHandlerFunc[DeviceT]]
37
+
38
+
39
+ @dataclass
40
+ class Handler(Generic[DeviceT]):
41
+ name: str
42
+ func: RouteHandlerFunc[DeviceT]
43
+
44
+
45
+ class RouteMap(Generic[DeviceT]):
46
+ def __init__(self) -> None:
47
+ self.handlers: list[Handler[DeviceT]] = []
48
+ self.submaps: list[RouteMap[DeviceT]] = []
49
+
50
+ def __call__(
51
+ self, func: Optional[RouteHandlerFunc[DeviceT]] = None, *, name: str = "",
52
+ ) -> Union[RouteHandlerFunc[DeviceT], Decorator[DeviceT]]:
53
+ def decorator(func: RouteHandlerFunc[DeviceT]) -> RouteHandlerFunc[DeviceT]:
54
+ nonlocal name
55
+ if not name:
56
+ name = func.__name__
57
+ self.handlers.append(Handler(name, func))
58
+ return func
59
+
60
+ if func is None:
61
+ return decorator
62
+ return decorator(func)
63
+
64
+ def include(self, other: "RouteMap[DeviceT]") -> None:
65
+ self.submaps.append(other)
66
+
67
+ def apply(self, device: DeviceT) -> list[RoutingPolicy]:
68
+ result: list[RoutingPolicy] = []
69
+
70
+ for handler in self.handlers:
71
+ route = Route(handler.name)
72
+ handler.func(device, route)
73
+ result.append(RoutingPolicy(route.name, route.statements))
74
+ for submap in self.submaps:
75
+ result.extend(submap.apply(device))
76
+ return result
@@ -0,0 +1,267 @@
1
+ from collections.abc import Callable
2
+ from dataclasses import dataclass
3
+ from dataclasses import field
4
+ from enum import Enum
5
+ from typing import Optional, Literal, TypeVar, Union, Any
6
+
7
+ from .action import SingleAction, ActionType
8
+ from .policy import RoutingPolicyStatement
9
+ from .result import ResultType
10
+
11
+
12
+ class ThenField(str, Enum):
13
+ community = "community"
14
+ extcommunity = "extcommunity"
15
+ as_path = "as_path"
16
+ local_pref = "local_pref"
17
+ metric = "metric"
18
+ rpki_valid_state = "rpki_valid_state"
19
+ resolution = "resolution"
20
+ mpls_label = "mpls_label"
21
+ metric_type = "metric_type"
22
+ origin = "origin"
23
+ tag = "tag"
24
+
25
+ next_hop = "next_hop"
26
+
27
+
28
+ ValueT = TypeVar("ValueT")
29
+ _Setter = Callable[[ValueT], SingleAction[ValueT]]
30
+
31
+
32
+ @dataclass
33
+ class CommunityActionValue:
34
+ replaced: Optional[list[str]] = None # None means no replacement is done
35
+ added: list[str] = field(default_factory=list)
36
+ removed: list[str] = field(default_factory=list)
37
+
38
+ def __bool__(self) -> bool: # check if any action required
39
+ return bool(self.replaced is not None or self.added or self.removed)
40
+
41
+
42
+ class CommunityActionBuilder:
43
+ def __init__(self, community: CommunityActionValue):
44
+ self._community = community
45
+
46
+ def add(self, *community: str) -> None:
47
+ for c in community:
48
+ self._community.added.append(c)
49
+
50
+ def remove(self, *community: str) -> None:
51
+ for c in community:
52
+ self._community.removed.append(c)
53
+
54
+ def set(self, *community: str) -> None:
55
+ self._community.added.clear()
56
+ self._community.removed.clear()
57
+ self._community.replaced = list(community)
58
+
59
+
60
+ @dataclass
61
+ class AsPathActionValue:
62
+ set: Optional[list[str]] = None # None means no replacement is done
63
+ prepend: list[str] = field(default_factory=list)
64
+ expand: list[str] = field(default_factory=list)
65
+ expand_last_as: str = ""
66
+ delete: list[str] = field(default_factory=list)
67
+
68
+ def __bool__(self) -> bool: # check if any action required
69
+ return bool(
70
+ self.set is not None or self.prepend or self.expand or self.expand_last_as or self.delete
71
+ )
72
+
73
+
74
+ RawAsNum = Union[str, int]
75
+
76
+
77
+ class AsPathActionBuilder:
78
+ def __init__(self, as_path_value: AsPathActionValue):
79
+ self._as_path_value = as_path_value
80
+
81
+ def prepend(self, *values: RawAsNum) -> None:
82
+ self._as_path_value.prepend = list(map(str, values))
83
+
84
+ def delete(self, *values: RawAsNum) -> None:
85
+ self._as_path_value.delete = list(map(str, values))
86
+
87
+ def expand(self, *values: RawAsNum) -> None:
88
+ self._as_path_value.expand = list(map(str, values))
89
+
90
+ def expand_last_as(self, value: RawAsNum) -> None:
91
+ self._as_path_value.expand_last_as = str(value)
92
+
93
+ def set(self, *values: RawAsNum) -> None:
94
+ self._as_path_value.expand.clear()
95
+ self._as_path_value.expand_last_as = ""
96
+ self._as_path_value.delete.clear()
97
+ self._as_path_value.prepend.clear()
98
+ self._as_path_value.set = list(map(str, values))
99
+
100
+
101
+ @dataclass
102
+ class NextHopActionValue:
103
+ target: Optional[Literal["self", "discard", "peer", "ipv4_addr", "ipv6_addr", "mapped_ipv4"]] = None
104
+ addr: str = ""
105
+
106
+ def __bool__(self) -> bool:
107
+ return bool(self.target)
108
+
109
+
110
+ class NextHopActionBuilder:
111
+ def __init__(self, next_hop_value: NextHopActionValue):
112
+ self._next_hop_value = next_hop_value
113
+
114
+ def ipv4_addr(self, value: str) -> None:
115
+ self._next_hop_value.target = "ipv4_addr"
116
+ self._next_hop_value.addr = value
117
+
118
+ def ipv6_addr(self, value: str) -> None:
119
+ self._next_hop_value.target = "ipv6_addr"
120
+ self._next_hop_value.addr = value
121
+
122
+ def mapped_ipv4(self, value: str) -> None:
123
+ self._next_hop_value.target = "mapped_ipv4"
124
+ self._next_hop_value.addr = value
125
+
126
+ def self(self) -> None:
127
+ self._next_hop_value.target = "self"
128
+ self._next_hop_value.addr = ""
129
+
130
+ def peer(self) -> None:
131
+ self._next_hop_value.target = "peer"
132
+ self._next_hop_value.addr = ""
133
+
134
+ def discard(self) -> None:
135
+ self._next_hop_value.target = "discard"
136
+ self._next_hop_value.addr = ""
137
+
138
+
139
+ class StatementBuilder:
140
+ def __init__(self, statement: RoutingPolicyStatement) -> None:
141
+ self._statement = statement
142
+ self._added_as_path: list[int] = []
143
+ self._community = CommunityActionValue()
144
+ self._extcommunity = CommunityActionValue()
145
+ self._as_path = AsPathActionValue()
146
+ self._next_hop = NextHopActionValue()
147
+
148
+ @property
149
+ def next_hop(self) -> NextHopActionBuilder:
150
+ return NextHopActionBuilder(self._next_hop)
151
+
152
+ @property
153
+ def as_path(self) -> AsPathActionBuilder:
154
+ return AsPathActionBuilder(self._as_path)
155
+
156
+ @property
157
+ def community(self) -> CommunityActionBuilder:
158
+ return CommunityActionBuilder(self._community)
159
+
160
+ @property
161
+ def extcommunity(self) -> CommunityActionBuilder:
162
+ return CommunityActionBuilder(self._extcommunity)
163
+
164
+ def _set(self, field: str, value: ValueT) -> None:
165
+ action = self._statement.then
166
+ if field in action:
167
+ action[field].type = ActionType.SET
168
+ action[field].value = value
169
+ else:
170
+ action.append(SingleAction(
171
+ field=field,
172
+ type=ActionType.SET,
173
+ value=value,
174
+ ))
175
+
176
+ def set_local_pref(self, value: int) -> None:
177
+ self._set(ThenField.local_pref, value)
178
+
179
+ def set_metric_type(self, value: str) -> None:
180
+ self._set(ThenField.metric_type, value)
181
+
182
+ def set_metric(self, value: int) -> None:
183
+ self._set(ThenField.metric, value)
184
+
185
+ def add_metric(self, value: int) -> None:
186
+ action = self._statement.then
187
+ field = ThenField.metric
188
+ if field in action:
189
+ old_action = action[field]
190
+ if old_action.type == ActionType.SET:
191
+ action[field].value += value
192
+ elif old_action.type == ActionType.ADD:
193
+ action[field].value = value
194
+ else:
195
+ raise RuntimeError(f"Unknown action type {old_action.type} for metric")
196
+ else:
197
+ action.append(SingleAction(
198
+ field=field,
199
+ type=ActionType.ADD,
200
+ value=value,
201
+ ))
202
+
203
+ def set_rpki_valid_state(self, value: str) -> None:
204
+ self._set(ThenField.rpki_valid_state, value)
205
+
206
+ def set_resolution(self, value: str) -> None:
207
+ self._set(ThenField.resolution, value)
208
+
209
+ def set_mpls_label(self) -> None:
210
+ self._set(ThenField.mpls_label, True)
211
+
212
+ def set_origin(self, value: str) -> None:
213
+ self._set(ThenField.origin, value)
214
+
215
+ def set_tag(self, value: int) -> None:
216
+ self._set(ThenField.tag, value)
217
+
218
+ def set_next_hop(self, value: Literal["self", "peer"]) -> None: # ???
219
+ self._set(ThenField.next_hop, value)
220
+
221
+ def __enter__(self) -> "StatementBuilder":
222
+ return self
223
+
224
+ def __exit__(self, exc_type, exc_val, exc_tb):
225
+ if self._community:
226
+ self._statement.then.append(SingleAction(
227
+ field=ThenField.community,
228
+ type=ActionType.CUSTOM,
229
+ value=self._community,
230
+ ))
231
+ if self._extcommunity:
232
+ self._statement.then.append(SingleAction(
233
+ field=ThenField.extcommunity,
234
+ type=ActionType.CUSTOM,
235
+ value=self._extcommunity,
236
+ ))
237
+ if self._as_path:
238
+ self._statement.then.append(SingleAction(
239
+ field=ThenField.as_path,
240
+ type=ActionType.CUSTOM,
241
+ value=self._as_path,
242
+ ))
243
+ if self._next_hop:
244
+ self._statement.then.append(SingleAction(
245
+ field=ThenField.next_hop,
246
+ type=ActionType.CUSTOM,
247
+ value=self._next_hop,
248
+ ))
249
+ return None
250
+
251
+ def allow(self) -> None:
252
+ self._statement.result = ResultType.ALLOW
253
+
254
+ def deny(self) -> None:
255
+ self._statement.result = ResultType.DENY
256
+
257
+ def next(self) -> None:
258
+ self._statement.result = ResultType.NEXT
259
+
260
+ def next_policy(self) -> None:
261
+ self._statement.result = ResultType.NEXT_POLICY
262
+
263
+ def add_as_path(self, *as_path: int) -> None:
264
+ self._added_as_path.extend(as_path)
265
+
266
+ def custom_action(self, action: SingleAction[Any]) -> None:
267
+ self._statement.then.append(action)