unifi-network-maps 1.4.9__tar.gz → 1.4.10__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.
Files changed (130) hide show
  1. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/CHANGELOG.md +6 -1
  2. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/PKG-INFO +2 -2
  3. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/pyproject.toml +2 -2
  4. unifi_network_maps-1.4.10/src/unifi_network_maps/__init__.py +1 -0
  5. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/model/topology.py +48 -5
  6. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps.egg-info/PKG-INFO +2 -2
  7. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps.egg-info/requires.txt +1 -1
  8. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/tests/test_clients.py +7 -0
  9. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/tests/test_topology.py +16 -0
  10. unifi_network_maps-1.4.9/src/unifi_network_maps/__init__.py +0 -1
  11. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/CONTRIBUTING.md +0 -0
  12. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/LICENSE +0 -0
  13. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/LICENSES.md +0 -0
  14. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/MANIFEST.in +0 -0
  15. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/README.md +0 -0
  16. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/RELEASING.md +0 -0
  17. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/SECURITY.md +0 -0
  18. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/setup.cfg +0 -0
  19. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/__main__.py +0 -0
  20. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/adapters/__init__.py +0 -0
  21. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/adapters/config.py +0 -0
  22. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/adapters/unifi.py +0 -0
  23. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/__init__.py +0 -0
  24. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/__init__.py +0 -0
  25. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/access-point.svg +0 -0
  26. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/isometric/ISOPACKS_LICENSE +0 -0
  27. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/isometric/block.svg +0 -0
  28. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/isometric/cache.svg +0 -0
  29. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/isometric/cardterminal.svg +0 -0
  30. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/isometric/cloud.svg +0 -0
  31. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/isometric/cronjob.svg +0 -0
  32. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/isometric/cube.svg +0 -0
  33. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/isometric/desktop.svg +0 -0
  34. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/isometric/diamond.svg +0 -0
  35. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/isometric/dns.svg +0 -0
  36. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/isometric/document.svg +0 -0
  37. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/isometric/firewall.svg +0 -0
  38. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/isometric/function-module.svg +0 -0
  39. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/isometric/image.svg +0 -0
  40. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/isometric/laptop.svg +0 -0
  41. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/isometric/loadbalancer.svg +0 -0
  42. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/isometric/lock.svg +0 -0
  43. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/isometric/mail.svg +0 -0
  44. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/isometric/mailmultiple.svg +0 -0
  45. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/isometric/mobiledevice.svg +0 -0
  46. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/isometric/office.svg +0 -0
  47. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/isometric/package-module.svg +0 -0
  48. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/isometric/paymentcard.svg +0 -0
  49. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/isometric/plane.svg +0 -0
  50. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/isometric/printer.svg +0 -0
  51. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/isometric/pyramid.svg +0 -0
  52. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/isometric/queue.svg +0 -0
  53. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/isometric/router.svg +0 -0
  54. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/isometric/server.svg +0 -0
  55. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/isometric/speech.svg +0 -0
  56. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/isometric/sphere.svg +0 -0
  57. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/isometric/storage.svg +0 -0
  58. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/isometric/switch-module.svg +0 -0
  59. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/isometric/tower.svg +0 -0
  60. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/isometric/truck-2.svg +0 -0
  61. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/isometric/truck.svg +0 -0
  62. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/isometric/user.svg +0 -0
  63. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/isometric/vm.svg +0 -0
  64. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/laptop.svg +0 -0
  65. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/router-network.svg +0 -0
  66. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/server-network.svg +0 -0
  67. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/icons/server.svg +0 -0
  68. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/themes/dark.yaml +0 -0
  69. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/assets/themes/default.yaml +0 -0
  70. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/cli/__init__.py +0 -0
  71. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/cli/__main__.py +0 -0
  72. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/cli/args.py +0 -0
  73. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/cli/main.py +0 -0
  74. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/cli/render.py +0 -0
  75. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/cli/runtime.py +0 -0
  76. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/io/__init__.py +0 -0
  77. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/io/debug.py +0 -0
  78. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/io/export.py +0 -0
  79. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/io/mkdocs_assets.py +0 -0
  80. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/io/mock_data.py +0 -0
  81. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/io/mock_generate.py +0 -0
  82. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/model/__init__.py +0 -0
  83. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/model/labels.py +0 -0
  84. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/model/lldp.py +0 -0
  85. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/model/mock.py +0 -0
  86. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/model/ports.py +0 -0
  87. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/render/__init__.py +0 -0
  88. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/render/device_ports_md.py +0 -0
  89. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/render/legend.py +0 -0
  90. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/render/lldp_md.py +0 -0
  91. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/render/markdown_tables.py +0 -0
  92. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/render/mermaid.py +0 -0
  93. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/render/mermaid_theme.py +0 -0
  94. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/render/mkdocs.py +0 -0
  95. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/render/svg.py +0 -0
  96. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/render/svg_theme.py +0 -0
  97. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/render/templates/device_port_block.md.j2 +0 -0
  98. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/render/templates/legend_compact.html.j2 +0 -0
  99. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/render/templates/lldp_device_section.md.j2 +0 -0
  100. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/render/templates/markdown_section.md.j2 +0 -0
  101. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/render/templates/mermaid_legend.mmd.j2 +0 -0
  102. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/render/templates/mkdocs_document.md.j2 +0 -0
  103. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/render/templates/mkdocs_dual_theme_style.html.j2 +0 -0
  104. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/render/templates/mkdocs_html_block.html.j2 +0 -0
  105. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/render/templates/mkdocs_legend.css.j2 +0 -0
  106. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/render/templates/mkdocs_legend.js.j2 +0 -0
  107. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/render/templates/mkdocs_mermaid_block.md.j2 +0 -0
  108. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/render/templating.py +0 -0
  109. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps/render/theme.py +0 -0
  110. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps.egg-info/SOURCES.txt +0 -0
  111. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps.egg-info/dependency_links.txt +0 -0
  112. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps.egg-info/entry_points.txt +0 -0
  113. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/src/unifi_network_maps.egg-info/top_level.txt +0 -0
  114. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/tests/test_cli.py +0 -0
  115. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/tests/test_config.py +0 -0
  116. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/tests/test_contract_unifi.py +0 -0
  117. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/tests/test_contract_unifi_live.py +0 -0
  118. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/tests/test_debug.py +0 -0
  119. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/tests/test_device_ports_md.py +0 -0
  120. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/tests/test_export.py +0 -0
  121. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/tests/test_groups.py +0 -0
  122. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/tests/test_labels.py +0 -0
  123. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/tests/test_lldp.py +0 -0
  124. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/tests/test_lldp_md.py +0 -0
  125. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/tests/test_mermaid.py +0 -0
  126. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/tests/test_mock_generate.py +0 -0
  127. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/tests/test_svg.py +0 -0
  128. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/tests/test_svg_iso.py +0 -0
  129. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/tests/test_theme.py +0 -0
  130. {unifi_network_maps-1.4.9 → unifi_network_maps-1.4.10}/tests/test_unifi.py +0 -0
@@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.4.10] - 2026-01-18
9
+ ### Added
10
+ - Add speed and channel fields to Edge dataclass
11
+
8
12
  ## [1.4.9] - 2026-01-15
9
13
  ### Changed
10
14
  - Declared support for Python 3.12+ (3.13 preferred) and added CI coverage for 3.12.
@@ -165,7 +169,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
165
169
  - Introduced SVG renderer and tree layout fixes.
166
170
  - Increased test coverage and added coverage tooling.
167
171
 
168
- [Unreleased]: https://github.com/merlijntishauser/unifi-network-maps/compare/v1.4.9...HEAD
172
+ [Unreleased]: https://github.com/merlijntishauser/unifi-network-maps/compare/v1.4.10...HEAD
173
+ [1.4.10]:https://github.com/merlijntishauser/unifi-network-maps/compare/v1.4.9...v1.4.10
169
174
  [1.4.9]: https://github.com/merlijntishauser/unifi-network-maps/compare/v1.4.8...v1.4.9
170
175
  [1.4.8]: https://github.com/merlijntishauser/unifi-network-maps/compare/v1.4.7...v1.4.8
171
176
  [1.4.7]: https://github.com/merlijntishauser/unifi-network-maps/compare/v1.4.6...v1.4.7
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: unifi-network-maps
3
- Version: 1.4.9
3
+ Version: 1.4.10
4
4
  Summary: Dynamic UniFi -> network maps in mermaid or svg
5
5
  Author: Merlijn
6
6
  License-Expression: MIT
@@ -32,7 +32,7 @@ Requires-Dist: pre-commit==4.5.1; extra == "dev"
32
32
  Requires-Dist: pytest==9.0.2; extra == "dev"
33
33
  Requires-Dist: pytest-cov==7.0.0; extra == "dev"
34
34
  Requires-Dist: pyright==1.1.408; extra == "dev"
35
- Requires-Dist: ruff==0.14.11; extra == "dev"
35
+ Requires-Dist: ruff==0.14.13; extra == "dev"
36
36
  Dynamic: license-file
37
37
 
38
38
  # unifi-network-maps
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "unifi-network-maps"
7
- version = "1.4.9"
7
+ version = "1.4.10"
8
8
  description = "Dynamic UniFi -> network maps in mermaid or svg"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -44,7 +44,7 @@ dev = [
44
44
  "pytest==9.0.2",
45
45
  "pytest-cov==7.0.0",
46
46
  "pyright==1.1.408",
47
- "ruff==0.14.11"
47
+ "ruff==0.14.13"
48
48
  ]
49
49
 
50
50
  [project.scripts]
@@ -0,0 +1 @@
1
+ __version__ = "1.4.10"
@@ -37,6 +37,8 @@ class Edge:
37
37
  label: str | None = None
38
38
  poe: bool = False
39
39
  wireless: bool = False
40
+ speed: int | None = None
41
+ channel: int | None = None
40
42
 
41
43
 
42
44
  type DeviceSource = object
@@ -64,6 +66,7 @@ class PortInfo:
64
66
 
65
67
  type PortMap = dict[tuple[str, str], str]
66
68
  type PoeMap = dict[tuple[str, str], bool]
69
+ type SpeedMap = dict[tuple[str, str], int]
67
70
  type ClientPortMap = dict[str, list[tuple[int, str]]]
68
71
 
69
72
 
@@ -421,7 +424,15 @@ def _tree_edges_from_parent(
421
424
  tree_edges.append(Edge(left=parent_name, right=child))
422
425
  else:
423
426
  tree_edges.append(
424
- Edge(left=parent_name, right=child, label=original.label, poe=original.poe)
427
+ Edge(
428
+ left=parent_name,
429
+ right=child,
430
+ label=original.label,
431
+ poe=original.poe,
432
+ wireless=original.wireless,
433
+ speed=original.speed,
434
+ channel=original.channel,
435
+ )
425
436
  )
426
437
  return tree_edges
427
438
 
@@ -492,6 +503,16 @@ def _client_is_wired(client: object) -> bool:
492
503
  return bool(_client_field(client, "is_wired"))
493
504
 
494
505
 
506
+ def _client_channel(client: object) -> int | None:
507
+ for key in ("channel", "radio_channel", "wifi_channel"):
508
+ value = _client_field(client, key)
509
+ if isinstance(value, int):
510
+ return value
511
+ if isinstance(value, str) and value.isdigit():
512
+ return int(value)
513
+ return None
514
+
515
+
495
516
  def _client_matches_mode(client: object, mode: str) -> bool:
496
517
  wired = _client_is_wired(client)
497
518
  if mode == "all":
@@ -528,12 +549,15 @@ def build_client_edges(
528
549
  key = (device_name, name)
529
550
  if key in seen:
530
551
  continue
552
+ is_wireless = not _client_is_wired(client)
553
+ channel = _client_channel(client) if is_wireless else None
531
554
  edges.append(
532
555
  Edge(
533
556
  left=device_name,
534
557
  right=name,
535
558
  label=label,
536
- wireless=not _client_is_wired(client),
559
+ wireless=is_wireless,
560
+ channel=channel,
537
561
  )
538
562
  )
539
563
  seen.add(key)
@@ -572,12 +596,14 @@ def build_edges(
572
596
  seen: set[frozenset[str]] = set()
573
597
  port_map: PortMap = {}
574
598
  poe_map: PoeMap = {}
599
+ speed_map: SpeedMap = {}
575
600
 
576
601
  devices_with_lldp_edges = _collect_lldp_links(
577
602
  ordered_devices,
578
603
  index,
579
604
  port_map,
580
605
  poe_map,
606
+ speed_map,
581
607
  raw_links,
582
608
  seen,
583
609
  only_unifi=only_unifi,
@@ -597,6 +623,7 @@ def build_edges(
597
623
  raw_links,
598
624
  port_map,
599
625
  poe_map,
626
+ speed_map,
600
627
  device_by_name,
601
628
  include_ports=include_ports,
602
629
  )
@@ -614,12 +641,14 @@ def build_port_map(devices: Iterable[Device], *, only_unifi: bool = True) -> Por
614
641
  seen: set[frozenset[str]] = set()
615
642
  port_map: PortMap = {}
616
643
  poe_map: PoeMap = {}
644
+ speed_map: SpeedMap = {}
617
645
 
618
646
  devices_with_lldp_edges = _collect_lldp_links(
619
647
  ordered_devices,
620
648
  index,
621
649
  port_map,
622
650
  poe_map,
651
+ speed_map,
623
652
  raw_links,
624
653
  seen,
625
654
  only_unifi=only_unifi,
@@ -661,11 +690,19 @@ def build_client_port_map(
661
690
  return port_map
662
691
 
663
692
 
693
+ def _port_speed_by_idx(port_table: list[PortInfo], port_idx: int) -> int | None:
694
+ for port in port_table:
695
+ if port.port_idx == port_idx:
696
+ return port.speed
697
+ return None
698
+
699
+
664
700
  def _collect_lldp_links(
665
701
  devices: list[Device],
666
702
  index: dict[str, str],
667
703
  port_map: PortMap,
668
704
  poe_map: PoeMap,
705
+ speed_map: SpeedMap,
669
706
  raw_links: list[tuple[str, str]],
670
707
  seen: set[frozenset[str]],
671
708
  *,
@@ -704,8 +741,12 @@ def _collect_lldp_links(
704
741
  label = local_port_label(entry_for_label)
705
742
  if label:
706
743
  port_map[(device.name, peer_name)] = label
707
- if resolved_port_idx is not None and resolved_port_idx in poe_ports:
708
- poe_map[(device.name, peer_name)] = poe_ports[resolved_port_idx]
744
+ if resolved_port_idx is not None:
745
+ if resolved_port_idx in poe_ports:
746
+ poe_map[(device.name, peer_name)] = poe_ports[resolved_port_idx]
747
+ port_speed = _port_speed_by_idx(device.port_table, resolved_port_idx)
748
+ if port_speed is not None:
749
+ speed_map[(device.name, peer_name)] = port_speed
709
750
 
710
751
  key = frozenset({device.name, peer_name})
711
752
  if key in seen:
@@ -794,6 +835,7 @@ def _build_ordered_edges(
794
835
  raw_links: list[tuple[str, str]],
795
836
  port_map: PortMap,
796
837
  poe_map: PoeMap,
838
+ speed_map: SpeedMap,
797
839
  device_by_name: dict[str, Device],
798
840
  *,
799
841
  include_ports: bool,
@@ -820,8 +862,9 @@ def _build_ordered_edges(
820
862
  poe = poe_map.get((left_name, right_name), False) or poe_map.get(
821
863
  (right_name, left_name), False
822
864
  )
865
+ speed = speed_map.get((left_name, right_name)) or speed_map.get((right_name, left_name))
823
866
  label = compose_port_label(left_name, right_name, port_map) if include_ports else None
824
- edges.append(Edge(left=left_name, right=right_name, label=label, poe=poe))
867
+ edges.append(Edge(left=left_name, right=right_name, label=label, poe=poe, speed=speed))
825
868
  return edges
826
869
 
827
870
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: unifi-network-maps
3
- Version: 1.4.9
3
+ Version: 1.4.10
4
4
  Summary: Dynamic UniFi -> network maps in mermaid or svg
5
5
  Author: Merlijn
6
6
  License-Expression: MIT
@@ -32,7 +32,7 @@ Requires-Dist: pre-commit==4.5.1; extra == "dev"
32
32
  Requires-Dist: pytest==9.0.2; extra == "dev"
33
33
  Requires-Dist: pytest-cov==7.0.0; extra == "dev"
34
34
  Requires-Dist: pyright==1.1.408; extra == "dev"
35
- Requires-Dist: ruff==0.14.11; extra == "dev"
35
+ Requires-Dist: ruff==0.14.13; extra == "dev"
36
36
  Dynamic: license-file
37
37
 
38
38
  # unifi-network-maps
@@ -10,4 +10,4 @@ pre-commit==4.5.1
10
10
  pytest==9.0.2
11
11
  pytest-cov==7.0.0
12
12
  pyright==1.1.408
13
- ruff==0.14.11
13
+ ruff==0.14.13
@@ -36,6 +36,13 @@ def test_build_client_edges_includes_wireless_when_requested():
36
36
  assert edges[0].wireless is True
37
37
 
38
38
 
39
+ def test_build_client_edges_includes_channel_for_wireless():
40
+ device_index = {"aa:bb:cc:dd:ee:ff": "AP One"}
41
+ clients = [{"name": "Phone", "ap_mac": "aa:bb:cc:dd:ee:ff", "is_wired": False, "channel": 36}]
42
+ edges = build_client_edges(clients, device_index, client_mode="wireless")
43
+ assert edges[0].channel == 36
44
+
45
+
39
46
  def test_build_client_edges_includes_uplink_port_label():
40
47
  device_index = {"aa:bb:cc:dd:ee:ff": "Switch A"}
41
48
  clients = [
@@ -181,6 +181,22 @@ def test_build_edges_sets_poe_with_port_poe():
181
181
  assert edges[0].poe is True
182
182
 
183
183
 
184
+ def test_build_edges_sets_speed_from_port():
185
+ dev_switch = DummyDevice(
186
+ "Switch A",
187
+ "aa:bb:cc:dd:ee:01",
188
+ [LLDPEntry("aa:bb:cc:dd:ee:02", "eth1", local_port_idx=1)],
189
+ port_table=[{"port_idx": 1, "speed": 1000}],
190
+ )
191
+ dev_ap = DummyDevice(
192
+ "AP One",
193
+ "aa:bb:cc:dd:ee:02",
194
+ [LLDPEntry("aa:bb:cc:dd:ee:01", "eth0")],
195
+ )
196
+ edges = build_edges(normalize_devices([dev_switch, dev_ap]))
197
+ assert edges[0].speed == 1000
198
+
199
+
184
200
  def test_coerce_device_uses_lldp_fallback():
185
201
  class DeviceWithLldp:
186
202
  name = "Device"
@@ -1 +0,0 @@
1
- __version__ = "1.4.9"