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

Files changed (196) hide show
  1. {annet-1.1.1/annet.egg-info → annet-2.0.0}/PKG-INFO +1 -2
  2. {annet-1.1.1 → annet-2.0.0}/annet/annet.py +2 -1
  3. {annet-1.1.1 → annet-2.0.0}/annet/annlib/lib.py +0 -14
  4. {annet-1.1.1 → annet-2.0.0}/annet/annlib/rbparser/platform.py +2 -2
  5. {annet-1.1.1 → annet-2.0.0}/annet/annlib/tabparser.py +46 -21
  6. {annet-1.1.1 → annet-2.0.0}/annet/api/__init__.py +35 -29
  7. {annet-1.1.1 → annet-2.0.0}/annet/diff.py +71 -2
  8. {annet-1.1.1 → annet-2.0.0}/annet/gen.py +0 -46
  9. {annet-1.1.1 → annet-2.0.0}/annet/generators/entire.py +0 -4
  10. {annet-1.1.1 → annet-2.0.0}/annet/lib.py +6 -2
  11. {annet-1.1.1 → annet-2.0.0}/annet/output.py +9 -2
  12. {annet-1.1.1 → annet-2.0.0}/annet/rpl/__init__.py +2 -1
  13. {annet-1.1.1 → annet-2.0.0}/annet/rpl/match_builder.py +10 -6
  14. {annet-1.1.1 → annet-2.0.0}/annet/rpl_generators/__init__.py +3 -1
  15. {annet-1.1.1 → annet-2.0.0}/annet/rpl_generators/cumulus_frr.py +24 -51
  16. annet-2.0.0/annet/rpl_generators/entities.py +126 -0
  17. {annet-1.1.1 → annet-2.0.0}/annet/rpl_generators/policy.py +61 -55
  18. annet-2.0.0/annet/rpl_generators/prefix_lists.py +122 -0
  19. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/juniper/__init__.py +1 -1
  20. {annet-1.1.1 → annet-2.0.0/annet.egg-info}/PKG-INFO +1 -2
  21. {annet-1.1.1 → annet-2.0.0}/annet.egg-info/requires.txt +0 -1
  22. {annet-1.1.1 → annet-2.0.0}/annet_generators/rpl_example/items.py +3 -3
  23. {annet-1.1.1 → annet-2.0.0}/requirements.txt +0 -1
  24. annet-1.1.1/annet/rpl_generators/entities.py +0 -83
  25. annet-1.1.1/annet/rpl_generators/prefix_lists.py +0 -167
  26. {annet-1.1.1 → annet-2.0.0}/AUTHORS +0 -0
  27. {annet-1.1.1 → annet-2.0.0}/LICENSE +0 -0
  28. {annet-1.1.1 → annet-2.0.0}/MANIFEST.in +0 -0
  29. {annet-1.1.1 → annet-2.0.0}/README.md +0 -0
  30. {annet-1.1.1 → annet-2.0.0}/annet/__init__.py +0 -0
  31. {annet-1.1.1 → annet-2.0.0}/annet/adapters/__init__.py +0 -0
  32. {annet-1.1.1 → annet-2.0.0}/annet/adapters/fetchers/__init__.py +0 -0
  33. {annet-1.1.1 → annet-2.0.0}/annet/adapters/fetchers/stub/__init__.py +0 -0
  34. {annet-1.1.1 → annet-2.0.0}/annet/adapters/fetchers/stub/fetcher.py +0 -0
  35. {annet-1.1.1 → annet-2.0.0}/annet/adapters/file/__init__.py +0 -0
  36. {annet-1.1.1 → annet-2.0.0}/annet/adapters/file/provider.py +0 -0
  37. {annet-1.1.1 → annet-2.0.0}/annet/adapters/netbox/__init__.py +0 -0
  38. {annet-1.1.1 → annet-2.0.0}/annet/adapters/netbox/common/__init__.py +0 -0
  39. {annet-1.1.1 → annet-2.0.0}/annet/adapters/netbox/common/client.py +0 -0
  40. {annet-1.1.1 → annet-2.0.0}/annet/adapters/netbox/common/manufacturer.py +0 -0
  41. {annet-1.1.1 → annet-2.0.0}/annet/adapters/netbox/common/models.py +0 -0
  42. {annet-1.1.1 → annet-2.0.0}/annet/adapters/netbox/common/query.py +0 -0
  43. {annet-1.1.1 → annet-2.0.0}/annet/adapters/netbox/common/status_client.py +0 -0
  44. {annet-1.1.1 → annet-2.0.0}/annet/adapters/netbox/common/storage_opts.py +0 -0
  45. {annet-1.1.1 → annet-2.0.0}/annet/adapters/netbox/provider.py +0 -0
  46. {annet-1.1.1 → annet-2.0.0}/annet/adapters/netbox/v24/__init__.py +0 -0
  47. {annet-1.1.1 → annet-2.0.0}/annet/adapters/netbox/v24/storage.py +0 -0
  48. {annet-1.1.1 → annet-2.0.0}/annet/adapters/netbox/v37/__init__.py +0 -0
  49. {annet-1.1.1 → annet-2.0.0}/annet/adapters/netbox/v37/storage.py +0 -0
  50. {annet-1.1.1 → annet-2.0.0}/annet/annlib/__init__.py +0 -0
  51. {annet-1.1.1 → annet-2.0.0}/annet/annlib/command.py +0 -0
  52. {annet-1.1.1 → annet-2.0.0}/annet/annlib/diff.py +0 -0
  53. {annet-1.1.1 → annet-2.0.0}/annet/annlib/errors.py +0 -0
  54. {annet-1.1.1 → annet-2.0.0}/annet/annlib/filter_acl.py +0 -0
  55. {annet-1.1.1 → annet-2.0.0}/annet/annlib/jsontools.py +0 -0
  56. {annet-1.1.1 → annet-2.0.0}/annet/annlib/netdev/__init__.py +0 -0
  57. {annet-1.1.1 → annet-2.0.0}/annet/annlib/netdev/db.py +0 -0
  58. {annet-1.1.1 → annet-2.0.0}/annet/annlib/netdev/devdb/__init__.py +0 -0
  59. {annet-1.1.1 → annet-2.0.0}/annet/annlib/netdev/devdb/data/devdb.json +0 -0
  60. {annet-1.1.1 → annet-2.0.0}/annet/annlib/netdev/views/__init__.py +0 -0
  61. {annet-1.1.1 → annet-2.0.0}/annet/annlib/netdev/views/dump.py +0 -0
  62. {annet-1.1.1 → annet-2.0.0}/annet/annlib/netdev/views/hardware.py +0 -0
  63. {annet-1.1.1 → annet-2.0.0}/annet/annlib/output.py +0 -0
  64. {annet-1.1.1 → annet-2.0.0}/annet/annlib/patching.py +0 -0
  65. {annet-1.1.1 → annet-2.0.0}/annet/annlib/rbparser/__init__.py +0 -0
  66. {annet-1.1.1 → annet-2.0.0}/annet/annlib/rbparser/acl.py +0 -0
  67. {annet-1.1.1 → annet-2.0.0}/annet/annlib/rbparser/deploying.py +0 -0
  68. {annet-1.1.1 → annet-2.0.0}/annet/annlib/rbparser/ordering.py +0 -0
  69. {annet-1.1.1 → annet-2.0.0}/annet/annlib/rbparser/syntax.py +0 -0
  70. {annet-1.1.1 → annet-2.0.0}/annet/annlib/rulebook/__init__.py +0 -0
  71. {annet-1.1.1 → annet-2.0.0}/annet/annlib/rulebook/common.py +0 -0
  72. {annet-1.1.1 → annet-2.0.0}/annet/annlib/types.py +0 -0
  73. {annet-1.1.1 → annet-2.0.0}/annet/argparse.py +0 -0
  74. {annet-1.1.1 → annet-2.0.0}/annet/bgp_models.py +0 -0
  75. {annet-1.1.1 → annet-2.0.0}/annet/cli.py +0 -0
  76. {annet-1.1.1 → annet-2.0.0}/annet/cli_args.py +0 -0
  77. {annet-1.1.1 → annet-2.0.0}/annet/configs/context.yml +0 -0
  78. {annet-1.1.1 → annet-2.0.0}/annet/configs/logging.yaml +0 -0
  79. {annet-1.1.1 → annet-2.0.0}/annet/connectors.py +0 -0
  80. {annet-1.1.1 → annet-2.0.0}/annet/deploy.py +0 -0
  81. {annet-1.1.1 → annet-2.0.0}/annet/deploy_ui.py +0 -0
  82. {annet-1.1.1 → annet-2.0.0}/annet/executor.py +0 -0
  83. {annet-1.1.1 → annet-2.0.0}/annet/filtering.py +0 -0
  84. {annet-1.1.1 → annet-2.0.0}/annet/generators/__init__.py +0 -0
  85. {annet-1.1.1 → annet-2.0.0}/annet/generators/base.py +0 -0
  86. {annet-1.1.1 → annet-2.0.0}/annet/generators/common/__init__.py +0 -0
  87. {annet-1.1.1 → annet-2.0.0}/annet/generators/common/initial.py +0 -0
  88. {annet-1.1.1 → annet-2.0.0}/annet/generators/exceptions.py +0 -0
  89. {annet-1.1.1 → annet-2.0.0}/annet/generators/jsonfragment.py +0 -0
  90. {annet-1.1.1 → annet-2.0.0}/annet/generators/partial.py +0 -0
  91. {annet-1.1.1 → annet-2.0.0}/annet/generators/perf.py +0 -0
  92. {annet-1.1.1 → annet-2.0.0}/annet/generators/ref.py +0 -0
  93. {annet-1.1.1 → annet-2.0.0}/annet/generators/result.py +0 -0
  94. {annet-1.1.1 → annet-2.0.0}/annet/hardware.py +0 -0
  95. {annet-1.1.1 → annet-2.0.0}/annet/implicit.py +0 -0
  96. {annet-1.1.1 → annet-2.0.0}/annet/mesh/__init__.py +0 -0
  97. {annet-1.1.1 → annet-2.0.0}/annet/mesh/basemodel.py +0 -0
  98. {annet-1.1.1 → annet-2.0.0}/annet/mesh/device_models.py +0 -0
  99. {annet-1.1.1 → annet-2.0.0}/annet/mesh/executor.py +0 -0
  100. {annet-1.1.1 → annet-2.0.0}/annet/mesh/match_args.py +0 -0
  101. {annet-1.1.1 → annet-2.0.0}/annet/mesh/models_converter.py +0 -0
  102. {annet-1.1.1 → annet-2.0.0}/annet/mesh/peer_models.py +0 -0
  103. {annet-1.1.1 → annet-2.0.0}/annet/mesh/port_processor.py +0 -0
  104. {annet-1.1.1 → annet-2.0.0}/annet/mesh/registry.py +0 -0
  105. {annet-1.1.1 → annet-2.0.0}/annet/parallel.py +0 -0
  106. {annet-1.1.1 → annet-2.0.0}/annet/patching.py +0 -0
  107. {annet-1.1.1 → annet-2.0.0}/annet/reference.py +0 -0
  108. {annet-1.1.1 → annet-2.0.0}/annet/rpl/action.py +0 -0
  109. {annet-1.1.1 → annet-2.0.0}/annet/rpl/condition.py +0 -0
  110. {annet-1.1.1 → annet-2.0.0}/annet/rpl/policy.py +0 -0
  111. {annet-1.1.1 → annet-2.0.0}/annet/rpl/result.py +0 -0
  112. {annet-1.1.1 → annet-2.0.0}/annet/rpl/routemap.py +0 -0
  113. {annet-1.1.1 → annet-2.0.0}/annet/rpl/statement_builder.py +0 -0
  114. {annet-1.1.1 → annet-2.0.0}/annet/rpl_generators/aspath.py +0 -0
  115. {annet-1.1.1 → annet-2.0.0}/annet/rpl_generators/community.py +0 -0
  116. {annet-1.1.1 → annet-2.0.0}/annet/rpl_generators/execute.py +0 -0
  117. {annet-1.1.1 → annet-2.0.0}/annet/rpl_generators/rd.py +0 -0
  118. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/__init__.py +0 -0
  119. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/arista/__init__.py +0 -0
  120. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/arista/aaa.py +0 -0
  121. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/arista/iface.py +0 -0
  122. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/aruba/__init__.py +0 -0
  123. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/aruba/ap_env.py +0 -0
  124. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/aruba/misc.py +0 -0
  125. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/b4com/__init__.py +0 -0
  126. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/b4com/file.py +0 -0
  127. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/b4com/iface.py +0 -0
  128. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/cisco/__init__.py +0 -0
  129. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/cisco/iface.py +0 -0
  130. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/cisco/misc.py +0 -0
  131. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/cisco/vlandb.py +0 -0
  132. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/common.py +0 -0
  133. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/deploying.py +0 -0
  134. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/huawei/__init__.py +0 -0
  135. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/huawei/aaa.py +0 -0
  136. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/huawei/bgp.py +0 -0
  137. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/huawei/iface.py +0 -0
  138. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/huawei/misc.py +0 -0
  139. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/huawei/vlandb.py +0 -0
  140. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/nexus/__init__.py +0 -0
  141. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/nexus/iface.py +0 -0
  142. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/patching.py +0 -0
  143. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/routeros/__init__.py +0 -0
  144. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/routeros/file.py +0 -0
  145. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/texts/arista.deploy +0 -0
  146. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/texts/arista.order +0 -0
  147. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/texts/arista.rul +0 -0
  148. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/texts/aruba.deploy +0 -0
  149. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/texts/aruba.order +0 -0
  150. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/texts/aruba.rul +0 -0
  151. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/texts/b4com.deploy +0 -0
  152. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/texts/b4com.order +0 -0
  153. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/texts/b4com.rul +0 -0
  154. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/texts/cisco.deploy +0 -0
  155. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/texts/cisco.order +0 -0
  156. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/texts/cisco.rul +0 -0
  157. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/texts/huawei.deploy +0 -0
  158. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/texts/huawei.order +0 -0
  159. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/texts/huawei.rul +0 -0
  160. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/texts/juniper.order +0 -0
  161. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/texts/juniper.rul +0 -0
  162. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/texts/nexus.deploy +0 -0
  163. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/texts/nexus.order +0 -0
  164. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/texts/nexus.rul +0 -0
  165. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/texts/nokia.rul +0 -0
  166. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/texts/optixtrans.deploy +0 -0
  167. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/texts/optixtrans.order +0 -0
  168. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/texts/optixtrans.rul +0 -0
  169. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/texts/pc.deploy +0 -0
  170. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/texts/pc.order +0 -0
  171. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/texts/pc.rul +0 -0
  172. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/texts/ribbon.deploy +0 -0
  173. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/texts/ribbon.rul +0 -0
  174. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/texts/routeros.order +0 -0
  175. {annet-1.1.1 → annet-2.0.0}/annet/rulebook/texts/routeros.rul +0 -0
  176. {annet-1.1.1 → annet-2.0.0}/annet/storage.py +0 -0
  177. {annet-1.1.1 → annet-2.0.0}/annet/tabparser.py +0 -0
  178. {annet-1.1.1 → annet-2.0.0}/annet/text_term_format.py +0 -0
  179. {annet-1.1.1 → annet-2.0.0}/annet/tracing.py +0 -0
  180. {annet-1.1.1 → annet-2.0.0}/annet/types.py +0 -0
  181. {annet-1.1.1 → annet-2.0.0}/annet.egg-info/SOURCES.txt +0 -0
  182. {annet-1.1.1 → annet-2.0.0}/annet.egg-info/dependency_links.txt +0 -0
  183. {annet-1.1.1 → annet-2.0.0}/annet.egg-info/entry_points.txt +0 -0
  184. {annet-1.1.1 → annet-2.0.0}/annet.egg-info/top_level.txt +0 -0
  185. {annet-1.1.1 → annet-2.0.0}/annet_generators/__init__.py +0 -0
  186. {annet-1.1.1 → annet-2.0.0}/annet_generators/example/__init__.py +0 -0
  187. {annet-1.1.1 → annet-2.0.0}/annet_generators/example/lldp.py +0 -0
  188. {annet-1.1.1 → annet-2.0.0}/annet_generators/mesh_example/__init__.py +0 -0
  189. {annet-1.1.1 → annet-2.0.0}/annet_generators/mesh_example/bgp.py +0 -0
  190. {annet-1.1.1 → annet-2.0.0}/annet_generators/mesh_example/mesh_logic.py +0 -0
  191. {annet-1.1.1 → annet-2.0.0}/annet_generators/rpl_example/__init__.py +0 -0
  192. {annet-1.1.1 → annet-2.0.0}/annet_generators/rpl_example/generator.py +0 -0
  193. {annet-1.1.1 → annet-2.0.0}/annet_generators/rpl_example/mesh.py +0 -0
  194. {annet-1.1.1 → annet-2.0.0}/annet_generators/rpl_example/route_policy.py +0 -0
  195. {annet-1.1.1 → annet-2.0.0}/setup.cfg +0 -0
  196. {annet-1.1.1 → annet-2.0.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: annet
3
- Version: 1.1.1
3
+ Version: 2.0.0
4
4
  Summary: annet
5
5
  Home-page: https://github.com/annetutil/annet
6
6
  License: MIT
@@ -14,7 +14,6 @@ Requires-Dist: jsonpointer>=2.4
14
14
  Requires-Dist: PyYAML>=6.0.1
15
15
  Requires-Dist: Pygments>=2.14.0
16
16
  Requires-Dist: Mako>=1.2.4
17
- Requires-Dist: Jinja2>=3.1.2
18
17
  Requires-Dist: packaging>=23.2
19
18
  Requires-Dist: contextlog>=1.1
20
19
  Requires-Dist: valkit>=0.1.4
@@ -2,7 +2,7 @@
2
2
  import sys
3
3
 
4
4
  import annet
5
- from annet import argparse, cli, generators, hardware, lib, rulebook
5
+ from annet import argparse, cli, generators, hardware, lib, rulebook, diff
6
6
 
7
7
 
8
8
  # =====
@@ -13,6 +13,7 @@ def main():
13
13
  cli.fill_base_args(parser, annet.__name__, "configs/logging.yaml")
14
14
  rulebook.rulebook_provider_connector.set(rulebook.DefaultRulebookProvider)
15
15
  hardware.hardware_connector.set(hardware.AnnetHardwareProvider)
16
+ diff.file_differ_connector.set(diff.UnifiedFileDiffer)
16
17
 
17
18
  parser.add_commands(parser.find_subcommands(cli.list_subcommands()))
18
19
  try:
@@ -18,7 +18,6 @@ from functools import lru_cache
18
18
  from typing import List, NamedTuple, Optional, Tuple, Union
19
19
 
20
20
  import contextlog
21
- import jinja2
22
21
  import mako.template
23
22
 
24
23
  _logger = contextlog.get_logger()
@@ -292,19 +291,6 @@ def mako_render(template, dedent=False, **kwargs):
292
291
  return ret
293
292
 
294
293
 
295
- def jinja_render(template, dedent=False, **kwargs):
296
- @lru_cache(None)
297
- def _compile_jinja(template, dedent):
298
- if dedent:
299
- template = textwrap.dedent(template).strip()
300
- return jinja2.Template(template)
301
-
302
- ret = _compile_jinja(template, dedent).render(**kwargs)
303
- if dedent:
304
- ret = ret.strip()
305
- return ret
306
-
307
-
308
294
  # =====
309
295
  def find_exc_in_stack(
310
296
  container_exc: Exception,
@@ -25,7 +25,7 @@ VENDOR_DIFF = {
25
25
  "routeros": "common.default_diff",
26
26
  "aruba": "aruba.default_diff",
27
27
  "pc": "common.default_diff",
28
- "ribbon": "common.default_diff",
28
+ "ribbon": "juniper.default_diff",
29
29
  "b4com": "common.default_diff",
30
30
  }
31
31
 
@@ -40,7 +40,7 @@ VENDOR_DIFF_ORDERED = {
40
40
  "routeros": "common.ordered_diff",
41
41
  "aruba": "common.ordered_diff",
42
42
  "pc": "common.ordered_diff",
43
- "ribbon": "common.ordered_diff",
43
+ "ribbon": "juniper.ordered_diff",
44
44
  "b4com": "common.ordered_diff",
45
45
  }
46
46
 
@@ -387,6 +387,24 @@ class AsrFormatter(BlockExitFormatter):
387
387
  yield from super().block_exit(context)
388
388
 
389
389
 
390
+ class JuniperPatch:
391
+ def __init__(self):
392
+ """In the case of comments, odict is not suitable: there may be several identical edit and exit"""
393
+ self._items = []
394
+
395
+ def __setitem__(self, key, value):
396
+ self._items.append((key, value))
397
+
398
+ def keys(self):
399
+ return list(self)
400
+
401
+ def items(self):
402
+ return self._items
403
+
404
+ def __iter__(self):
405
+ return iter(item[0] for item in self._items)
406
+
407
+
390
408
  class JuniperFormatter(CommonFormatter):
391
409
  patch_set_prefix = "set"
392
410
 
@@ -399,7 +417,7 @@ class JuniperFormatter(CommonFormatter):
399
417
  comment: str
400
418
 
401
419
  def __post_init__(self):
402
- self.row = self.row.strip()
420
+ self.row = " ".join(map(lambda x: x.strip("\"'"), self.row.strip().split(" ")))
403
421
  self.comment = self.comment.strip()
404
422
 
405
423
  @classmethod
@@ -485,7 +503,8 @@ class JuniperFormatter(CommonFormatter):
485
503
  yield line + self._statement_end
486
504
 
487
505
  def cmd_paths(self, patch, _prev=tuple()):
488
- commands = odict()
506
+ commands = JuniperPatch()
507
+
489
508
  for item in patch.itms:
490
509
  key, childs, context = item.row, item.child, item.context
491
510
 
@@ -497,33 +516,39 @@ class JuniperFormatter(CommonFormatter):
497
516
  value = (
498
517
  ""
499
518
  if key.startswith("delete")
500
- else context["comment"]
519
+ else key.removeprefix(self.Comment.begin).removesuffix(self.Comment.end).strip()
501
520
  )
502
-
503
- cmd = "\n".join(
504
- (
505
- "edit " + " ".join(_prev),
506
- " ".join(("annotate", context["row"].split(" ")[0], f'"{value}"')),
507
- "exit"
508
- )
521
+ cmds = (
522
+ f"edit {' '.join(_prev)}",
523
+ " ".join(("annotate", context["row"].split(" ")[0], f'"{value}"')),
524
+ "exit"
509
525
  )
510
526
  elif key.startswith("delete"):
511
- cmd = " ".join(("delete", *_prev, key.replace("delete", "", 1).strip()))
527
+ cmds = (
528
+ " ".join(("delete", *_prev, key.replace("delete", "", 1).strip())),
529
+ )
512
530
  elif key.startswith("activate"):
513
- cmd = " ".join(("activate", *_prev, key.replace("activate", "", 1).strip()))
531
+ cmds = (
532
+ " ".join(("activate", *_prev, key.replace("activate", "", 1).strip())),
533
+ )
514
534
  elif key.startswith("deactivate"):
515
- cmd = " ".join(("deactivate", *_prev, key.replace("deactivate", "", 1).strip()))
535
+ cmds = (
536
+ " ".join(("deactivate", *_prev, key.replace("deactivate", "", 1).strip())),
537
+ )
516
538
  else:
517
- cmd = " ".join((self.patch_set_prefix, *_prev, key.strip()))
539
+ cmds = (
540
+ " ".join((self.patch_set_prefix, *_prev, key.strip())),
541
+ )
518
542
 
519
543
  # Expanding [ a b c ] junipers list of arguments
520
- if matches := re.search(r"^(.*)\s+\[(.+)\]$", cmd):
521
- for c in matches.group(2).split(" "):
522
- if c.strip():
523
- cmd = " ".join([matches.group(1), c])
524
- commands[(cmd,)] = context
525
- else:
526
- commands[(cmd,)] = context
544
+ for cmd in cmds:
545
+ if matches := re.search(r"^(.*)\s+\[(.+)\]$", cmd):
546
+ for c in matches.group(2).split(" "):
547
+ if c.strip():
548
+ items = " ".join([matches.group(1), c])
549
+ commands[(items,)] = context
550
+ else:
551
+ commands[(cmd,)] = context
527
552
 
528
553
  return commands
529
554
 
@@ -1,5 +1,4 @@
1
1
  import abc
2
- import difflib
3
2
  import os
4
3
  import re
5
4
  import sys
@@ -37,6 +36,7 @@ from annet import diff as ann_diff
37
36
  from annet import filtering
38
37
  from annet import gen as ann_gen
39
38
  from annet import patching, rulebook, tabparser, tracing
39
+ from annet.diff import file_differ_connector
40
40
  from annet.rulebook import deploying
41
41
  from annet.filtering import Filterer
42
42
  from annet.hardware import hardware_connector
@@ -52,8 +52,6 @@ from annet.storage import Device, get_storage
52
52
  from annet.types import Diff, ExitCode, OldNewResult, Op, PCDiff, PCDiffFile
53
53
 
54
54
 
55
- live_configs = ann_gen.live_configs
56
-
57
55
  DEFAULT_INDENT = " "
58
56
 
59
57
 
@@ -242,29 +240,22 @@ def gen(args: cli_args.ShowGenOptions, loader: ann_gen.Loader):
242
240
 
243
241
 
244
242
  # =====
245
- def _diff_file(old_text: Optional[str], new_text: Optional[str], context=3):
246
- old_lines = old_text.splitlines() if old_text else []
247
- new_lines = new_text.splitlines() if new_text else []
248
- context = max(len(old_lines), len(new_lines)) if context is None else context
249
- return list(difflib.unified_diff(old_lines, new_lines, n=context, lineterm=""))
250
-
251
-
252
- def _diff_files(old_files, new_files, context=3):
243
+ def _diff_files(hw, old_files, new_files):
253
244
  ret = {}
245
+ differ = file_differ_connector.get()
254
246
  for (path, (new_text, reload_data)) in new_files.items():
255
247
  old_text = old_files.get(path)
256
248
  is_new = old_text is None
257
- diff_lines = _diff_file(old_text, new_text, context=context)
249
+ diff_lines = differ.diff_file(hw, path, old_text, new_text)
258
250
  ret[path] = (diff_lines, reload_data, is_new)
259
251
  return ret
260
252
 
261
253
 
262
254
  def patch(args: cli_args.ShowPatchOptions, loader: ann_gen.Loader):
263
255
  """ Сгенерировать патч для устройств """
264
- global live_configs # pylint: disable=global-statement
265
256
  if args.config == "running":
266
257
  fetcher = annet.deploy.get_fetcher()
267
- live_configs = annet.lib.do_async(fetcher.fetch(loader.devices, processes=args.parallel))
258
+ ann_gen.live_configs = annet.lib.do_async(fetcher.fetch(loader.devices, processes=args.parallel))
268
259
  stdin = args.stdin(filter_acl=args.filter_acl, config=args.config)
269
260
 
270
261
  filterer = filtering.filterer_connector.get()
@@ -355,9 +346,9 @@ def diff(
355
346
 
356
347
  pc_diff_files = []
357
348
  if res.old_files or new_files:
358
- pc_diff_files.extend(_pc_diff(device.hostname, res.old_files, new_files))
349
+ pc_diff_files.extend(_pc_diff(res.device.hw, device.hostname, res.old_files, new_files))
359
350
  if res.old_json_fragment_files or new_json_fragment_files:
360
- pc_diff_files.extend(_json_fragment_diff(device.hostname, res.old_json_fragment_files, new_json_fragment_files))
351
+ pc_diff_files.extend(_json_fragment_diff(res.device.hw, device.hostname, res.old_json_fragment_files, new_json_fragment_files))
361
352
 
362
353
  if pc_diff_files:
363
354
  pc_diff_files.sort(key=lambda f: f.label)
@@ -478,18 +469,19 @@ class PCDeployerJob(DeployerJob):
478
469
  upload_files: Dict[str, bytes] = {}
479
470
  reload_cmds: Dict[str, bytes] = {}
480
471
  generator_types: Dict[str, GeneratorType] = {}
472
+ differ = file_differ_connector.get()
481
473
  for generator_type, pc_files in [(GeneratorType.ENTIRE, new_files), (GeneratorType.JSON_FRAGMENT, new_json_fragment_files)]:
482
474
  for file, (file_content_or_json_cfg, cmds) in pc_files.items():
483
475
  if generator_type == GeneratorType.ENTIRE:
484
476
  file_content: str = file_content_or_json_cfg
485
- diff_content = "\n".join(_diff_file(old_files.get(file), file_content))
477
+ diff_content = "\n".join(differ.diff_file(res.device.hw, file, old_files.get(file), file_content))
486
478
  else: # generator_type == GeneratorType.JSON_FRAGMENT
487
479
  old_json_cfg = old_json_fragment_files[file]
488
480
  json_patch = jsontools.make_patch(old_json_cfg, file_content_or_json_cfg)
489
481
  file_content = jsontools.format_json(json_patch)
490
482
  old_text = jsontools.format_json(old_json_cfg)
491
483
  new_text = jsontools.format_json(file_content_or_json_cfg)
492
- diff_content = "\n".join(_diff_file(old_text, new_text))
484
+ diff_content = "\n".join(differ.diff_file(res.device.hw, file, old_text, new_text))
493
485
 
494
486
  if diff_content or force_reload:
495
487
  self._has_diff |= True
@@ -624,7 +616,6 @@ class Deployer:
624
616
  return ans
625
617
 
626
618
  def check_diff(self, result: annet.deploy.DeployResult, loader: ann_gen.Loader):
627
- global live_configs # pylint: disable=global-statement
628
619
  success_device_ids = []
629
620
  for host, hres in result.results.items():
630
621
  device = self.fqdn_to_device[host]
@@ -639,7 +630,7 @@ class Deployer:
639
630
  config="running",
640
631
  )
641
632
  if diff_args.query:
642
- live_configs = None
633
+ ann_gen.live_configs = None
643
634
  diffs = diff(diff_args, loader, success_device_ids, self._filterer)
644
635
  non_pc_diffs = {dev: diff for dev, diff in diffs.items() if not isinstance(diff, PCDiff)}
645
636
  devices_to_diff = ann_diff.collapse_diffs(non_pc_diffs)
@@ -682,13 +673,23 @@ async def adeploy(
682
673
  ) -> ExitCode:
683
674
  """ Сгенерировать конфиг для устройств и задеплоить его """
684
675
  ret: ExitCode = 0
685
- global live_configs # pylint: disable=global-statement
686
- live_configs = await fetcher.fetch(devices=loader.devices, processes=args.parallel)
687
- pool = ann_gen.OldNewParallel(args, loader, filterer)
676
+ ann_gen.live_configs = await fetcher.fetch(devices=loader.devices, processes=args.parallel)
688
677
 
689
- for res in pool.generated_configs(loader.devices):
678
+ device_ids = [d.id for d in loader.devices]
679
+ for res in ann_gen.old_new(
680
+ args,
681
+ config=args.config,
682
+ loader=loader,
683
+ no_new=args.clear,
684
+ stdin=args.stdin(filter_acl=args.filter_acl, config=args.config),
685
+ do_files_download=True,
686
+ device_ids=device_ids,
687
+ filterer=filterer,
688
+ ):
690
689
  # Меняем exit code если хоть один device ловил exception
691
690
  if res.err is not None:
691
+ if not args.tolerate_fails:
692
+ raise res.err
692
693
  get_logger(res.device.hostname).error("error generating configs", exc_info=res.err)
693
694
  ret |= 2 ** 3
694
695
  job = DeployerJob.from_device(res.device, args)
@@ -700,7 +701,7 @@ async def adeploy(
700
701
  ans = deployer.ask_deploy()
701
702
  if ans != "y":
702
703
  return 2 ** 2
703
- progress_bar = None
704
+
704
705
  if sys.stdout.isatty() and not args.no_progress:
705
706
  progress_bar = annet.deploy_ui.ProgressBars(odict([(device.fqdn, {}) for device in deploy_cmds]))
706
707
  progress_bar.init()
@@ -746,12 +747,16 @@ def file_diff(args: cli_args.FileDiffOptions):
746
747
  def file_diff_worker(old_new: Tuple[str, str], args: cli_args.FileDiffOptions) -> Generator[
747
748
  Tuple[str, str, bool], None, None]:
748
749
  old_path, new_path = old_new
750
+ hw = args.hw
751
+ if isinstance(args.hw, str):
752
+ hw = HardwareView(args.hw, "")
753
+
749
754
  if os.path.isdir(old_path) and os.path.isdir(new_path):
750
755
  hostname = os.path.basename(new_path)
751
756
  new_files = {relative_cfg_path: (cfg_text, "") for relative_cfg_path, cfg_text in
752
757
  ann_gen.load_pc_config(new_path).items()}
753
758
  old_files = ann_gen.load_pc_config(old_path)
754
- for diff_file in _pc_diff(hostname, old_files, new_files):
759
+ for diff_file in _pc_diff(hw, hostname, old_files, new_files):
755
760
  diff_text = (
756
761
  "\n".join(diff_file.diff_lines)
757
762
  if args.no_color
@@ -791,8 +796,8 @@ def file_patch_worker(old_new: Tuple[str, str], args: cli_args.FileDiffOptions)
791
796
  yield dest_name, patch_text, False
792
797
 
793
798
 
794
- def _pc_diff(hostname: str, old_files: Dict[str, str], new_files: Dict[str, str]) -> Generator[PCDiffFile, None, None]:
795
- sorted_lines = sorted(_diff_files(old_files, new_files).items())
799
+ def _pc_diff(hw, hostname: str, old_files: Dict[str, str], new_files: Dict[str, str]) -> Generator[PCDiffFile, None, None]:
800
+ sorted_lines = sorted(_diff_files(hw, old_files, new_files).items())
796
801
  for (path, (diff_lines, _reload_data, is_new)) in sorted_lines:
797
802
  if not diff_lines:
798
803
  continue
@@ -803,6 +808,7 @@ def _pc_diff(hostname: str, old_files: Dict[str, str], new_files: Dict[str, str]
803
808
 
804
809
 
805
810
  def _json_fragment_diff(
811
+ hw,
806
812
  hostname: str,
807
813
  old_files: Dict[str, Any],
808
814
  new_files: Dict[str, Tuple[Any, Optional[str]]],
@@ -820,7 +826,7 @@ def _json_fragment_diff(
820
826
  ret[path] = (jsontools.format_json(cfg), reload_cmd)
821
827
  return ret
822
828
  jold, jnew = jsonify_multi(old_files), jsonify_multi_with_cmd(new_files)
823
- return _pc_diff(hostname, jold, jnew)
829
+ return _pc_diff(hw, hostname, jold, jnew)
824
830
 
825
831
 
826
832
  def guess_hw(config_text: str):
@@ -1,6 +1,9 @@
1
+ import abc
2
+ import difflib
1
3
  import re
2
4
  from itertools import groupby
3
- from typing import Generator, List, Mapping, Tuple, Union
5
+ from pathlib import Path
6
+ from typing import Generator, List, Mapping, Tuple, Union, Protocol
4
7
 
5
8
  from annet.annlib.diff import ( # pylint: disable=unused-import
6
9
  colorize_line,
@@ -9,10 +12,12 @@ from annet.annlib.diff import ( # pylint: disable=unused-import
9
12
  gen_pre_as_diff,
10
13
  resort_diff,
11
14
  )
15
+ from annet.annlib.netdev.views.hardware import HardwareView
12
16
  from annet.annlib.output import format_file_diff
13
17
 
14
- from annet import patching
18
+ from annet import patching, rulebook, tabparser, hardware
15
19
  from annet.cli_args import ShowDiffOptions
20
+ from annet.connectors import CachedConnector
16
21
  from annet.output import output_driver_connector
17
22
  from annet.storage import Device
18
23
  from annet.tabparser import make_formatter
@@ -82,3 +87,67 @@ def collapse_diffs(diffs: Mapping[Device, Diff]) -> Mapping[Tuple[Device, ...],
82
87
  res[tuple(x[0] for x in collapsed_diff)] = collapsed_diff[0][1][0]
83
88
 
84
89
  return res
90
+
91
+
92
+ class FileDiffer(Protocol):
93
+ @abc.abstractmethod
94
+ def diff_file(self, hw: HardwareView, path: str | Path, old: str, new: str) -> list[str]:
95
+ raise NotImplementedError
96
+
97
+
98
+ class UnifiedFileDiffer(FileDiffer):
99
+ def __init__(self):
100
+ self.context: int = 3
101
+
102
+ def diff_file(self, hw: HardwareView, path: str | Path, old: str, new: str) -> list[str]:
103
+ """Calculate the differences for config files.
104
+
105
+ Args:
106
+ hw: device hardware info
107
+ path: path to file on a device
108
+ old (Optional[str]): The old file content.
109
+ new (Optional[str]): The new file content.
110
+
111
+ Returns:
112
+ List[str]: List of difference lines.
113
+ """
114
+ return self._diff_text_file(old, new)
115
+
116
+ def _diff_text_file(self, old, new):
117
+ """Calculate the differences for plaintext files."""
118
+ context = self.context
119
+ old_lines = old.splitlines() if old else []
120
+ new_lines = new.splitlines() if new else []
121
+ context = max(len(old_lines), len(new_lines)) if context is None else context
122
+ return list(difflib.unified_diff(old_lines, new_lines, n=context, lineterm=""))
123
+
124
+
125
+ class FrrFileDiffer(UnifiedFileDiffer):
126
+ def diff_file(self, hw: HardwareView, path: str | Path, old: str, new: str) -> list[str]:
127
+ if (hw.PC.Mellanox or hw.PC.NVIDIA) and (path == "/etc/frr/frr.conf"):
128
+ return self._diff_frr_conf(hw, old, new)
129
+ return super().diff_file(hw, path, old, new)
130
+
131
+ def _diff_frr_conf(self, hw: HardwareView, old_text: str | None, new_text: str | None) -> list[str]:
132
+ """Calculate the differences for frr.conf files."""
133
+ indent = " "
134
+ rb = rulebook.rulebook_provider_connector.get()
135
+ rulebook_data = rb.get_rulebook(hw)
136
+ formatter = tabparser.make_formatter(hw, indent=indent)
137
+
138
+ old_tree = tabparser.parse_to_tree(old_text or "", splitter=formatter.split)
139
+ new_tree = tabparser.parse_to_tree(new_text or "", splitter=formatter.split)
140
+
141
+ diff_tree = patching.make_diff(old_tree, new_tree, rulebook_data, [])
142
+ pre_diff = patching.make_pre(diff_tree)
143
+ diff_iterator = gen_pre_as_diff(pre_diff, show_rules=False, indent=indent, no_color=True)
144
+
145
+ return [line.rstrip() for line in diff_iterator if "frr version" not in line]
146
+
147
+
148
+ class _FileDifferConnector(CachedConnector[FileDiffer]):
149
+ name = "Device file diff processor"
150
+ ep_name = "file_differ"
151
+
152
+
153
+ file_differ_connector = _FileDifferConnector()
@@ -526,52 +526,6 @@ def worker(device_id, args: ShowGenOptions, stdin, loader: "Loader", filterer: F
526
526
  False)
527
527
 
528
528
 
529
- def old_new_worker(device_id, args: DeployOptions, config, stdin, loader: "Loader", filterer: Filterer):
530
- for res in old_new(
531
- args,
532
- config=config,
533
- loader=loader,
534
- filterer=filterer,
535
- stdin=stdin,
536
- device_ids=[device_id],
537
- no_new=args.clear,
538
- do_files_download=True,
539
- ):
540
- if res.err is not None and not args.tolerate_fails:
541
- raise res.err
542
- yield res
543
-
544
-
545
- class OldNewParallel(Parallel):
546
- def __init__(self, args: DeployOptions, loader: "Loader", filterer: Filterer):
547
- stdin = args.stdin(filter_acl=args.filter_acl, config=args.config)
548
- super().__init__(
549
- old_new_worker,
550
- args,
551
- config=args.config,
552
- stdin=stdin,
553
- loader=loader,
554
- filterer=filterer,
555
- )
556
- self.tune_args(args)
557
- self.tolerate_fails = args.tolerate_fails
558
-
559
- def generated_configs(self, devices: List[Device]) -> Generator[OldNewResult, None, None]:
560
- devices_by_id = {device.id: device for device in devices}
561
- device_ids = list(devices_by_id)
562
-
563
- for task_result in self.irun(device_ids, self.tolerate_fails):
564
- if task_result.exc is not None:
565
- device = devices_by_id.pop(task_result.device_id)
566
- yield OldNewResult(device=device, err=task_result.exc)
567
- elif task_result.result is not None:
568
- yield from task_result.result
569
- devices_by_id.pop(task_result.device_id)
570
-
571
- for device in devices_by_id.values():
572
- yield OldNewResult(device=device, err=Exception(f"No config returned for {device.hostname}"))
573
-
574
-
575
529
  @dataclasses.dataclass
576
530
  class DeviceFilesToDownload:
577
531
  entire: List[str] = dataclasses.field(default_factory=list)
@@ -14,7 +14,6 @@ from typing import (
14
14
 
15
15
  from annet.lib import (
16
16
  flatten,
17
- jinja_render,
18
17
  mako_render,
19
18
  )
20
19
  from .base import BaseGenerator, _filter_str
@@ -70,9 +69,6 @@ class Entire(BaseGenerator):
70
69
  def mako(self, text, **kwargs) -> str:
71
70
  return mako_render(text, dedent=True, device=self.__device, **kwargs)
72
71
 
73
- def jinja(self, text, **kwargs) -> str:
74
- return jinja_render(text, dedent=True, device=self.__device, **kwargs)
75
-
76
72
  # =====
77
73
 
78
74
  @classmethod
@@ -24,7 +24,6 @@ from annet.annlib.lib import ( # pylint: disable=unused-import
24
24
  huawei_expand_vlandb,
25
25
  huawei_iface_ranges,
26
26
  is_relative,
27
- jinja_render,
28
27
  jun_activate,
29
28
  jun_is_inactive,
30
29
  juniper_fmt_prefix_lists_acl,
@@ -151,10 +150,15 @@ def do_async(coro: Awaitable[ReturnType], new_thread=False) -> ReturnType:
151
150
 
152
151
  def wrapper(main):
153
152
  nonlocal res
154
- res = asyncio.run(main)
153
+ try:
154
+ res = asyncio.run(main)
155
+ except BaseException as e:
156
+ res = e
155
157
  thread = threading.Thread(target=wrapper, args=(coro,))
156
158
  thread.start()
157
159
  thread.join()
160
+ if isinstance(res, BaseException):
161
+ raise res
158
162
  return res
159
163
  else:
160
164
  return asyncio.run(coro)
@@ -155,8 +155,15 @@ class OutputDriverBasic(OutputDriver):
155
155
  ret.append((label, getattr(exc, "formatted_output", f"{repr(exc)} (formatted_output is absent)"), True))
156
156
  return ret
157
157
 
158
- def cfg_file_names(self, device: Device) -> List[str]:
159
- return [f"{device.hostname}.cfg"]
158
+ def cfg_file_names(self, device: Device) -> list[str]:
159
+ res = []
160
+ if device.hostname:
161
+ res.append(f"{device.hostname}.cfg")
162
+ if device.id is not None and device.id != "":
163
+ res.append(f"_id_{device.id}.cfg")
164
+ if not res:
165
+ raise RuntimeError("Neither hostname nor id is known for device")
166
+ return res
160
167
 
161
168
  def entire_config_dest_path(self, device, config_path: str) -> str:
162
169
  """Формирует путь к конфигу в директории destname.
@@ -16,11 +16,12 @@ __all__ = [
16
16
  "RoutingPolicy",
17
17
  "CommunityActionValue",
18
18
  "PrefixMatchValue",
19
+ "OrLonger",
19
20
  ]
20
21
 
21
22
  from .action import Action, ActionType, SingleAction
22
23
  from .condition import AndCondition, Condition, ConditionOperator, SingleCondition
23
- from .match_builder import R, MatchField, PrefixMatchValue
24
+ from .match_builder import R, MatchField, PrefixMatchValue, OrLonger
24
25
  from .policy import RoutingPolicyStatement, RoutingPolicy
25
26
  from .result import ResultType
26
27
  from .routemap import RouteMap, Route
@@ -63,11 +63,15 @@ class SetConditionFactory(Generic[ValueT]):
63
63
  return SingleCondition(self.field, ConditionOperator.HAS_ANY, values)
64
64
 
65
65
 
66
+ # OrLonger represents a pair of (le, ge)
67
+ # for prefix mask length match in prefix-lists
68
+ OrLonger = tuple[Optional[int], Optional[int]]
69
+
70
+
66
71
  @dataclass(frozen=True)
67
72
  class PrefixMatchValue:
68
73
  names: tuple[str, ...]
69
- greater_equal: Optional[int]
70
- less_equal: Optional[int]
74
+ or_longer: OrLonger = (None, None)
71
75
 
72
76
 
73
77
  class Checkable:
@@ -91,23 +95,23 @@ class Checkable:
91
95
  def match_v6(
92
96
  self,
93
97
  *names: str,
94
- or_longer: tuple[Optional[int], Optional[int]] = (None, None),
98
+ or_longer: OrLonger = (None, None),
95
99
  ) -> SingleCondition[PrefixMatchValue]:
96
100
  return SingleCondition(
97
101
  MatchField.ipv6_prefix,
98
102
  ConditionOperator.CUSTOM,
99
- PrefixMatchValue(names, greater_equal=or_longer[0], less_equal=or_longer[1]),
103
+ PrefixMatchValue(names, or_longer),
100
104
  )
101
105
 
102
106
  def match_v4(
103
107
  self,
104
108
  *names: str,
105
- or_longer: tuple[Optional[int], Optional[int]] = (None, None),
109
+ or_longer: OrLonger = (None, None),
106
110
  ) -> SingleCondition[PrefixMatchValue]:
107
111
  return SingleCondition(
108
112
  MatchField.ip_prefix,
109
113
  ConditionOperator.CUSTOM,
110
- PrefixMatchValue(names, greater_equal=or_longer[0], less_equal=or_longer[1]),
114
+ PrefixMatchValue(names, or_longer),
111
115
  )
112
116
 
113
117
 
@@ -10,14 +10,16 @@ __all__ = [
10
10
  "RDFilterFilterGenerator",
11
11
  "RDFilter",
12
12
  "IpPrefixList",
13
+ "IpPrefixListMember",
13
14
  "PrefixListFilterGenerator",
14
15
  "get_policies",
16
+ "ip_prefix_list",
15
17
  ]
16
18
 
17
19
  from .aspath import AsPathFilterGenerator
18
20
  from .community import CommunityListGenerator
19
21
  from .cumulus_frr import CumulusPolicyGenerator
20
- from .entities import CommunityList, AsPathFilter, CommunityType, CommunityLogic, RDFilter, IpPrefixList
22
+ from .entities import CommunityList, AsPathFilter, CommunityType, CommunityLogic, RDFilter, IpPrefixList, IpPrefixListMember, ip_prefix_list
21
23
  from .execute import get_policies
22
24
  from .policy import RoutingPolicyGenerator
23
25
  from .prefix_lists import PrefixListFilterGenerator