netsight-sdk 1.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.
Files changed (117) hide show
  1. netsight_sdk-1.0.0/LICENSE +21 -0
  2. netsight_sdk-1.0.0/PKG-INFO +153 -0
  3. netsight_sdk-1.0.0/README.md +102 -0
  4. netsight_sdk-1.0.0/netsight/__init__.py +75 -0
  5. netsight_sdk-1.0.0/netsight/auth.py +226 -0
  6. netsight_sdk-1.0.0/netsight/base.py +427 -0
  7. netsight_sdk-1.0.0/netsight/cli.py +1579 -0
  8. netsight_sdk-1.0.0/netsight/config.py +307 -0
  9. netsight_sdk-1.0.0/netsight/config_mgmt/__init__.py +21 -0
  10. netsight_sdk-1.0.0/netsight/config_mgmt/_defaults/__init__.py +0 -0
  11. netsight_sdk-1.0.0/netsight/config_mgmt/_defaults/auth.toml +3 -0
  12. netsight_sdk-1.0.0/netsight/config_mgmt/_defaults/connection.toml +4 -0
  13. netsight_sdk-1.0.0/netsight/config_mgmt/_defaults/devices.yaml.example +32 -0
  14. netsight_sdk-1.0.0/netsight/config_mgmt/_defaults/resilience.toml +5 -0
  15. netsight_sdk-1.0.0/netsight/config_mgmt/compiler.py +1007 -0
  16. netsight_sdk-1.0.0/netsight/config_mgmt/merger.py +109 -0
  17. netsight_sdk-1.0.0/netsight/config_mgmt/reader.py +290 -0
  18. netsight_sdk-1.0.0/netsight/config_mgmt/schemas.py +400 -0
  19. netsight_sdk-1.0.0/netsight/config_mgmt/store.py +250 -0
  20. netsight_sdk-1.0.0/netsight/config_mgmt/user_config.py +125 -0
  21. netsight_sdk-1.0.0/netsight/config_mgmt/validator.py +163 -0
  22. netsight_sdk-1.0.0/netsight/data/__init__.py +22 -0
  23. netsight_sdk-1.0.0/netsight/data/manager.py +204 -0
  24. netsight_sdk-1.0.0/netsight/data/models.py +192 -0
  25. netsight_sdk-1.0.0/netsight/data/store.py +386 -0
  26. netsight_sdk-1.0.0/netsight/docs/__init__.py +22 -0
  27. netsight_sdk-1.0.0/netsight/docs/decorators.py +167 -0
  28. netsight_sdk-1.0.0/netsight/docs/export.py +82 -0
  29. netsight_sdk-1.0.0/netsight/docs/introspect.py +189 -0
  30. netsight_sdk-1.0.0/netsight/docs/patterns.py +93 -0
  31. netsight_sdk-1.0.0/netsight/docs/relations.py +83 -0
  32. netsight_sdk-1.0.0/netsight/exceptions.py +170 -0
  33. netsight_sdk-1.0.0/netsight/gate.py +77 -0
  34. netsight_sdk-1.0.0/netsight/integrations/__init__.py +0 -0
  35. netsight_sdk-1.0.0/netsight/integrations/base.py +87 -0
  36. netsight_sdk-1.0.0/netsight/integrations/enrichment/__init__.py +0 -0
  37. netsight_sdk-1.0.0/netsight/integrations/inventory/__init__.py +0 -0
  38. netsight_sdk-1.0.0/netsight/integrations/inventory/yaml_file.py +58 -0
  39. netsight_sdk-1.0.0/netsight/integrations/registry.py +193 -0
  40. netsight_sdk-1.0.0/netsight/integrations/sinks/__init__.py +0 -0
  41. netsight_sdk-1.0.0/netsight/integrations/sinks/json_file.py +115 -0
  42. netsight_sdk-1.0.0/netsight/mcp_dev/__init__.py +1 -0
  43. netsight_sdk-1.0.0/netsight/mcp_dev/cache.py +74 -0
  44. netsight_sdk-1.0.0/netsight/mcp_dev/cli.py +36 -0
  45. netsight_sdk-1.0.0/netsight/mcp_dev/doctor.py +387 -0
  46. netsight_sdk-1.0.0/netsight/mcp_dev/path_security.py +270 -0
  47. netsight_sdk-1.0.0/netsight/mcp_dev/resources/__init__.py +70 -0
  48. netsight_sdk-1.0.0/netsight/mcp_dev/resources/allowlists.py +232 -0
  49. netsight_sdk-1.0.0/netsight/mcp_dev/resources/api.py +100 -0
  50. netsight_sdk-1.0.0/netsight/mcp_dev/resources/conventions.py +113 -0
  51. netsight_sdk-1.0.0/netsight/mcp_dev/resources/docs.py +194 -0
  52. netsight_sdk-1.0.0/netsight/mcp_dev/resources/fixtures.py +62 -0
  53. netsight_sdk-1.0.0/netsight/mcp_dev/resources/operations.py +229 -0
  54. netsight_sdk-1.0.0/netsight/mcp_dev/resources/packs.py +331 -0
  55. netsight_sdk-1.0.0/netsight/mcp_dev/resources/parsers.py +249 -0
  56. netsight_sdk-1.0.0/netsight/mcp_dev/resources/patterns.py +73 -0
  57. netsight_sdk-1.0.0/netsight/mcp_dev/resources/recipes.py +161 -0
  58. netsight_sdk-1.0.0/netsight/mcp_dev/resources/registries.py +242 -0
  59. netsight_sdk-1.0.0/netsight/mcp_dev/resources/security.py +76 -0
  60. netsight_sdk-1.0.0/netsight/mcp_dev/resources/source.py +242 -0
  61. netsight_sdk-1.0.0/netsight/mcp_dev/resources/vendors.py +387 -0
  62. netsight_sdk-1.0.0/netsight/mcp_dev/server.py +129 -0
  63. netsight_sdk-1.0.0/netsight/mcp_dev/tools/__init__.py +55 -0
  64. netsight_sdk-1.0.0/netsight/mcp_dev/tools/inspection.py +823 -0
  65. netsight_sdk-1.0.0/netsight/mcp_dev/tools/overview.py +315 -0
  66. netsight_sdk-1.0.0/netsight/mcp_dev/tools/recipes.py +70 -0
  67. netsight_sdk-1.0.0/netsight/mcp_dev/tools/scaffolding.py +904 -0
  68. netsight_sdk-1.0.0/netsight/mcp_dev/tools/security.py +131 -0
  69. netsight_sdk-1.0.0/netsight/mcp_dev/tools/validation.py +291 -0
  70. netsight_sdk-1.0.0/netsight/mcp_dev/transport.py +161 -0
  71. netsight_sdk-1.0.0/netsight/mcp_dev/watch.py +166 -0
  72. netsight_sdk-1.0.0/netsight/output.py +178 -0
  73. netsight_sdk-1.0.0/netsight/packs/__init__.py +61 -0
  74. netsight_sdk-1.0.0/netsight/packs/_index.toml +18 -0
  75. netsight_sdk-1.0.0/netsight/packs/_traversable.py +92 -0
  76. netsight_sdk-1.0.0/netsight/packs/allowlist.py +180 -0
  77. netsight_sdk-1.0.0/netsight/packs/allowlist_cli.py +435 -0
  78. netsight_sdk-1.0.0/netsight/packs/cli.py +602 -0
  79. netsight_sdk-1.0.0/netsight/packs/exceptions.py +103 -0
  80. netsight_sdk-1.0.0/netsight/packs/index.py +108 -0
  81. netsight_sdk-1.0.0/netsight/packs/loader.py +152 -0
  82. netsight_sdk-1.0.0/netsight/packs/registry.py +406 -0
  83. netsight_sdk-1.0.0/netsight/parsers/__init__.py +66 -0
  84. netsight_sdk-1.0.0/netsight/parsers/base.py +300 -0
  85. netsight_sdk-1.0.0/netsight/parsers/engine.py +67 -0
  86. netsight_sdk-1.0.0/netsight/parsers/engines/__init__.py +1 -0
  87. netsight_sdk-1.0.0/netsight/parsers/engines/jmespath_engine.py +232 -0
  88. netsight_sdk-1.0.0/netsight/parsers/engines/passthrough.py +27 -0
  89. netsight_sdk-1.0.0/netsight/parsers/exceptions.py +25 -0
  90. netsight_sdk-1.0.0/netsight/parsers/loader.py +128 -0
  91. netsight_sdk-1.0.0/netsight/parsers/registry.py +130 -0
  92. netsight_sdk-1.0.0/netsight/parsers/resolver.py +151 -0
  93. netsight_sdk-1.0.0/netsight/parsers/spec.py +121 -0
  94. netsight_sdk-1.0.0/netsight/parsers/transforms.py +148 -0
  95. netsight_sdk-1.0.0/netsight/parsers/types.py +176 -0
  96. netsight_sdk-1.0.0/netsight/py.typed +0 -0
  97. netsight_sdk-1.0.0/netsight/registry.py +404 -0
  98. netsight_sdk-1.0.0/netsight/resilience.py +211 -0
  99. netsight_sdk-1.0.0/netsight/sdk/__init__.py +19 -0
  100. netsight_sdk-1.0.0/netsight/sdk/client.py +205 -0
  101. netsight_sdk-1.0.0/netsight/sdk/device.py +205 -0
  102. netsight_sdk-1.0.0/netsight/sdk/local.py +296 -0
  103. netsight_sdk-1.0.0/netsight/sdk/remote.py +242 -0
  104. netsight_sdk-1.0.0/netsight/sdk/types.py +18 -0
  105. netsight_sdk-1.0.0/netsight/security/__init__.py +1 -0
  106. netsight_sdk-1.0.0/netsight/security/audit.py +98 -0
  107. netsight_sdk-1.0.0/netsight/security/code_patterns.py +297 -0
  108. netsight_sdk-1.0.0/netsight/security/dependencies.py +137 -0
  109. netsight_sdk-1.0.0/netsight/security/findings.py +37 -0
  110. netsight_sdk-1.0.0/netsight_sdk.egg-info/PKG-INFO +153 -0
  111. netsight_sdk-1.0.0/netsight_sdk.egg-info/SOURCES.txt +115 -0
  112. netsight_sdk-1.0.0/netsight_sdk.egg-info/dependency_links.txt +1 -0
  113. netsight_sdk-1.0.0/netsight_sdk.egg-info/entry_points.txt +2 -0
  114. netsight_sdk-1.0.0/netsight_sdk.egg-info/requires.txt +24 -0
  115. netsight_sdk-1.0.0/netsight_sdk.egg-info/top_level.txt +1 -0
  116. netsight_sdk-1.0.0/pyproject.toml +170 -0
  117. netsight_sdk-1.0.0/setup.cfg +4 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 NetSight contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,153 @@
1
+ Metadata-Version: 2.4
2
+ Name: netsight-sdk
3
+ Version: 1.0.0
4
+ Summary: Read-only multi-vendor network device query SDK (core library)
5
+ Author: NetSight contributors
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/magicboxlab-ai/netsight-sdk
8
+ Project-URL: Repository, https://github.com/magicboxlab-ai/netsight-sdk
9
+ Project-URL: Changelog, https://github.com/magicboxlab-ai/netsight-sdk/blob/main/CHANGELOG.md
10
+ Project-URL: Issues, https://github.com/magicboxlab-ai/netsight-sdk/issues
11
+ Project-URL: Documentation, https://github.com/magicboxlab-ai/netsight-sdk/blob/main/docs/guides/user-guide.md
12
+ Keywords: network,automation,sdk,palo-alto,panos,read-only,multi-vendor
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: System Administrators
15
+ Classifier: Intended Audience :: Information Technology
16
+ Classifier: Operating System :: POSIX
17
+ Classifier: Operating System :: MacOS
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Topic :: System :: Networking
21
+ Classifier: Topic :: System :: Networking :: Monitoring
22
+ Classifier: Topic :: System :: Systems Administration
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.14
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: requests>=2.31.0
28
+ Requires-Dist: pyyaml>=6.0
29
+ Requires-Dist: tabulate>=0.9.0
30
+ Requires-Dist: rich>=13.0.0
31
+ Requires-Dist: xmltodict>=0.13.0
32
+ Requires-Dist: pydantic>=2.0
33
+ Requires-Dist: python-dotenv>=1.0.0
34
+ Requires-Dist: jmespath>=1.0.1
35
+ Requires-Dist: pip-audit>=2.7.0
36
+ Requires-Dist: packaging>=23.0
37
+ Provides-Extra: dev
38
+ Requires-Dist: pytest>=7.0; extra == "dev"
39
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
40
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
41
+ Requires-Dist: httpx>=0.24.0; extra == "dev"
42
+ Requires-Dist: mypy>=1.5; extra == "dev"
43
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
44
+ Requires-Dist: build>=1.0; extra == "dev"
45
+ Requires-Dist: twine>=4.0; extra == "dev"
46
+ Requires-Dist: types-PyYAML; extra == "dev"
47
+ Requires-Dist: types-tabulate; extra == "dev"
48
+ Requires-Dist: types-jmespath; extra == "dev"
49
+ Requires-Dist: watchdog>=4.0.0; extra == "dev"
50
+ Dynamic: license-file
51
+
52
+ # NetSight
53
+
54
+ **NetSight** is a read-only, multi-vendor network device query SDK written in Python 3.14. It connects to devices over vendor-native APIs (currently PAN-OS XML; more vendors planned), enforces a deny-all command allowlist, and returns structured data parsed from declarative YAML parser specs.
55
+
56
+ ## For users
57
+
58
+ See the [**User Guide**](docs/guides/user-guide.md) for installation, configuration, and day-to-day SDK usage — connecting to a device, running an operation, handling results, writing a template workflow.
59
+
60
+ ```python
61
+ from netsight import NetSight
62
+
63
+ ns = NetSight(config="config/devices.yaml")
64
+ device = ns.device("fw-01")
65
+ info = device.show_system_info()
66
+ routes = device.show_routing_table()
67
+ ```
68
+
69
+ ### Installing the core SDK
70
+
71
+ ```sh
72
+ # Minimal SDK install — just the device-query core
73
+ pip install netsight-sdk
74
+
75
+ # With the dev MCP server (for Claude Code contributors)
76
+ pip install 'netsight-sdk[dev]'
77
+ ```
78
+
79
+ Or, to track the development branch directly:
80
+
81
+ ```sh
82
+ pip install git+https://github.com/magicboxlab-ai/netsight-sdk.git
83
+ ```
84
+
85
+ ### First-time setup
86
+
87
+ After installing the core SDK, run `netsight init` to create your device
88
+ configuration file:
89
+
90
+ ```sh
91
+ # Create ~/.config/netsight/devices.yaml with a template
92
+ netsight init
93
+
94
+ # Edit the file to add your devices, then verify
95
+ netsight doctor
96
+ ```
97
+
98
+ The config file search order is: `$NETSIGHT_CONFIG_DIR` → `./config/`
99
+ (cwd, for project-local dev workflows) → `$XDG_CONFIG_HOME/netsight/`
100
+ → `~/.config/netsight/`.
101
+
102
+ ### Installing vendor packs
103
+
104
+ The core SDK is vendor-agnostic. Vendor support ships as separate pack packages installed alongside the core. Use the `netsight pack` CLI to discover and install packs:
105
+
106
+ ```sh
107
+ # List packs available in the bundled catalog
108
+ netsight pack list --available
109
+
110
+ # Install a pack
111
+ netsight pack install paloalto-firewall-xml
112
+
113
+ # Verify everything loaded correctly
114
+ netsight doctor
115
+ ```
116
+
117
+ Packs can also be installed directly from their source repositories:
118
+
119
+ ```sh
120
+ pip install git+https://github.com/magicboxlab-ai/netsight-packs-paloalto.git#subdirectory=packs/paloalto-firewall-xml
121
+ ```
122
+
123
+ After installation, the pack is auto-discovered at startup via the `netsight.packs` entry-point group — no config change required.
124
+
125
+ See [**User Guide → Vendor packs**](docs/guides/user-guide.md#vendor-packs) for the full install and configuration flow.
126
+
127
+ ## For contributors
128
+
129
+ NetSight ships a purpose-built MCP server (`netsight-dev`) that exposes the codebase to Claude Code as structured resources and validation tools. The bootstrap flow for a new clone is documented end-to-end in the developer guide:
130
+
131
+ See [**Developer Guide → Getting Started for Development**](docs/guides/developer-guide.md#getting-started-for-development).
132
+
133
+ **TL;DR:**
134
+
135
+ ```sh
136
+ git clone <repo-url> netsight
137
+ cd netsight
138
+ python3.14 -m venv .venv
139
+ source .venv/bin/activate
140
+ pip install -e .
141
+ netsight init --local # creates ./config/devices.yaml from the template
142
+ netsight doctor # verifies the dev env end-to-end
143
+ claude # approve the netsight-dev MCP server when prompted
144
+ ```
145
+
146
+ The rest of the developer guide covers adding vendor packs, writing parser specs, custom types and transforms, registry wiring, self-documentation conventions, testing, and the release workflow.
147
+
148
+ ## Project layout at a glance
149
+
150
+ - `netsight/` — SDK source (pack registry, parsers, config management, security module, dev MCP server).
151
+ - `UNITTEST/` — pytest test suite (not `tests/`).
152
+ - `TOOLS/` — shell utilities (`full_test_cycle.sh`, `security_check.sh`).
153
+ - `docs/guides/` — user guide and developer guide.
@@ -0,0 +1,102 @@
1
+ # NetSight
2
+
3
+ **NetSight** is a read-only, multi-vendor network device query SDK written in Python 3.14. It connects to devices over vendor-native APIs (currently PAN-OS XML; more vendors planned), enforces a deny-all command allowlist, and returns structured data parsed from declarative YAML parser specs.
4
+
5
+ ## For users
6
+
7
+ See the [**User Guide**](docs/guides/user-guide.md) for installation, configuration, and day-to-day SDK usage — connecting to a device, running an operation, handling results, writing a template workflow.
8
+
9
+ ```python
10
+ from netsight import NetSight
11
+
12
+ ns = NetSight(config="config/devices.yaml")
13
+ device = ns.device("fw-01")
14
+ info = device.show_system_info()
15
+ routes = device.show_routing_table()
16
+ ```
17
+
18
+ ### Installing the core SDK
19
+
20
+ ```sh
21
+ # Minimal SDK install — just the device-query core
22
+ pip install netsight-sdk
23
+
24
+ # With the dev MCP server (for Claude Code contributors)
25
+ pip install 'netsight-sdk[dev]'
26
+ ```
27
+
28
+ Or, to track the development branch directly:
29
+
30
+ ```sh
31
+ pip install git+https://github.com/magicboxlab-ai/netsight-sdk.git
32
+ ```
33
+
34
+ ### First-time setup
35
+
36
+ After installing the core SDK, run `netsight init` to create your device
37
+ configuration file:
38
+
39
+ ```sh
40
+ # Create ~/.config/netsight/devices.yaml with a template
41
+ netsight init
42
+
43
+ # Edit the file to add your devices, then verify
44
+ netsight doctor
45
+ ```
46
+
47
+ The config file search order is: `$NETSIGHT_CONFIG_DIR` → `./config/`
48
+ (cwd, for project-local dev workflows) → `$XDG_CONFIG_HOME/netsight/`
49
+ → `~/.config/netsight/`.
50
+
51
+ ### Installing vendor packs
52
+
53
+ The core SDK is vendor-agnostic. Vendor support ships as separate pack packages installed alongside the core. Use the `netsight pack` CLI to discover and install packs:
54
+
55
+ ```sh
56
+ # List packs available in the bundled catalog
57
+ netsight pack list --available
58
+
59
+ # Install a pack
60
+ netsight pack install paloalto-firewall-xml
61
+
62
+ # Verify everything loaded correctly
63
+ netsight doctor
64
+ ```
65
+
66
+ Packs can also be installed directly from their source repositories:
67
+
68
+ ```sh
69
+ pip install git+https://github.com/magicboxlab-ai/netsight-packs-paloalto.git#subdirectory=packs/paloalto-firewall-xml
70
+ ```
71
+
72
+ After installation, the pack is auto-discovered at startup via the `netsight.packs` entry-point group — no config change required.
73
+
74
+ See [**User Guide → Vendor packs**](docs/guides/user-guide.md#vendor-packs) for the full install and configuration flow.
75
+
76
+ ## For contributors
77
+
78
+ NetSight ships a purpose-built MCP server (`netsight-dev`) that exposes the codebase to Claude Code as structured resources and validation tools. The bootstrap flow for a new clone is documented end-to-end in the developer guide:
79
+
80
+ See [**Developer Guide → Getting Started for Development**](docs/guides/developer-guide.md#getting-started-for-development).
81
+
82
+ **TL;DR:**
83
+
84
+ ```sh
85
+ git clone <repo-url> netsight
86
+ cd netsight
87
+ python3.14 -m venv .venv
88
+ source .venv/bin/activate
89
+ pip install -e .
90
+ netsight init --local # creates ./config/devices.yaml from the template
91
+ netsight doctor # verifies the dev env end-to-end
92
+ claude # approve the netsight-dev MCP server when prompted
93
+ ```
94
+
95
+ The rest of the developer guide covers adding vendor packs, writing parser specs, custom types and transforms, registry wiring, self-documentation conventions, testing, and the release workflow.
96
+
97
+ ## Project layout at a glance
98
+
99
+ - `netsight/` — SDK source (pack registry, parsers, config management, security module, dev MCP server).
100
+ - `UNITTEST/` — pytest test suite (not `tests/`).
101
+ - `TOOLS/` — shell utilities (`full_test_cycle.sh`, `security_check.sh`).
102
+ - `docs/guides/` — user guide and developer guide.
@@ -0,0 +1,75 @@
1
+ """NetSight — Read-only multi-vendor network device query SDK.
2
+
3
+ The importable Python package is ``netsight`` (unchanged). The PyPI
4
+ distribution that ships this package is ``netsight-sdk``. Other
5
+ distributions — ``netsight-ops`` (runtime service platform) and
6
+ vendor packs such as ``netsight-pack-paloalto-firewall-xml`` —
7
+ depend on this core via standard pip dependencies.
8
+
9
+ The ``__version__`` string below is read from the installed
10
+ distribution metadata at import time, so it always matches what
11
+ ``pip show netsight-sdk`` reports. A source-tree import without an
12
+ install (e.g. during bootstrap of a fresh checkout) falls back to
13
+ the compile-time default declared in ``pyproject.toml``.
14
+ """
15
+
16
+ from importlib.metadata import PackageNotFoundError, version as _pkg_version
17
+
18
+ try:
19
+ __version__: str = _pkg_version("netsight-sdk")
20
+ except PackageNotFoundError: # pragma: no cover — source tree without install
21
+ __version__ = "1.0.0"
22
+
23
+ from netsight.config import DeviceConfig
24
+ from netsight.data.models import DiffResult, QueryResult, Snapshot
25
+ from netsight.exceptions import (
26
+ AuthenticationError,
27
+ CommandDeniedError,
28
+ ConfigValidationError,
29
+ DeviceConnectionError,
30
+ NetSightError,
31
+ PluginNotFoundError,
32
+ RateLimitError,
33
+ ResponseSizeError,
34
+ )
35
+ from netsight.sdk.client import NetSight
36
+
37
+
38
+ def bootstrap() -> None:
39
+ """Discover and register every installed NetSight pack.
40
+
41
+ Idempotent public entry point for pack discovery. Call this from any
42
+ embedding that does not go through :class:`~netsight.sdk.local.LocalBackend`,
43
+ the dev MCP server, or the ``netsight`` CLI — typically a custom
44
+ script that imports :class:`NetSight` and wants installed packs to be
45
+ usable before the first device query.
46
+
47
+ The underlying :meth:`netsight.packs.registry.PackRegistry.ensure_loaded`
48
+ uses double-checked locking and the ``_loaded`` flag to guarantee that
49
+ entry-point discovery runs exactly once per process, so calling
50
+ ``bootstrap()`` multiple times (from multiple entry points) is cheap
51
+ and safe. Per-pack registration errors are absorbed and surfaced via
52
+ :meth:`PackRegistry.load_errors` rather than raised.
53
+ """
54
+ from netsight.packs import pack_registry
55
+
56
+ pack_registry.ensure_loaded()
57
+
58
+
59
+ __all__ = [
60
+ "__version__",
61
+ "NetSight",
62
+ "DeviceConfig",
63
+ "QueryResult",
64
+ "Snapshot",
65
+ "DiffResult",
66
+ "AuthenticationError",
67
+ "CommandDeniedError",
68
+ "ConfigValidationError",
69
+ "DeviceConnectionError",
70
+ "NetSightError",
71
+ "PluginNotFoundError",
72
+ "RateLimitError",
73
+ "ResponseSizeError",
74
+ "bootstrap",
75
+ ]
@@ -0,0 +1,226 @@
1
+ """Authentication strategy and token-caching manager for NetSight.
2
+
3
+ Design
4
+ ------
5
+ ``AuthStrategy`` is an ABC that vendor-specific adapters implement — they
6
+ handle the actual network call to obtain or revoke a session token.
7
+
8
+ ``AuthManager`` owns a per-host token cache and enforces TTL-based expiry.
9
+ It deliberately never exposes raw token values in ``repr`` or log output.
10
+
11
+ Usage example::
12
+
13
+ class MyStrategy(AuthStrategy):
14
+ def acquire_token(self, host, username, password):
15
+ ... # POST /login, return token string
16
+ def revoke_token(self, host, token):
17
+ ... # POST /logout
18
+
19
+ manager = AuthManager(MyStrategy(), token_ttl_seconds=1800)
20
+ token = manager.get_token("192.168.1.1", "admin", "hunter2")
21
+ manager.close()
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ import logging
27
+ import time
28
+ from abc import ABC, abstractmethod
29
+
30
+ logger = logging.getLogger(__name__)
31
+
32
+
33
+ # ---------------------------------------------------------------------------
34
+ # Abstract strategy
35
+ # ---------------------------------------------------------------------------
36
+
37
+
38
+ class AuthStrategy(ABC):
39
+ """Contract that every authentication back-end must satisfy."""
40
+
41
+ @abstractmethod
42
+ def acquire_token(self, host: str, username: str, password: str) -> str:
43
+ """Obtain a new session token from *host*.
44
+
45
+ Parameters
46
+ ----------
47
+ host:
48
+ Hostname or IP address of the target device.
49
+ username:
50
+ Account name to authenticate with.
51
+ password:
52
+ Credential. Implementations must not log this value.
53
+
54
+ Returns
55
+ -------
56
+ str
57
+ An opaque token string suitable for subsequent API calls.
58
+
59
+ Raises
60
+ ------
61
+ netsight.exceptions.AuthenticationError
62
+ If the device rejects the credentials.
63
+ Any network/transport exception the caller may choose to handle.
64
+ """
65
+
66
+ @abstractmethod
67
+ def revoke_token(self, host: str, token: str) -> None:
68
+ """Invalidate *token* on *host*.
69
+
70
+ Implementations should be best-effort; ``AuthManager`` handles
71
+ failures by logging a warning rather than propagating exceptions.
72
+
73
+ Parameters
74
+ ----------
75
+ host:
76
+ Same host that issued the token.
77
+ token:
78
+ The token to invalidate.
79
+ """
80
+
81
+
82
+ # ---------------------------------------------------------------------------
83
+ # Internal cached-token container
84
+ # ---------------------------------------------------------------------------
85
+
86
+
87
+ class _CachedToken:
88
+ """Lightweight container for a token and its acquisition timestamp.
89
+
90
+ Uses ``__slots__`` to minimise per-instance memory overhead — there may
91
+ be hundreds of these alive at once in a large environment.
92
+ """
93
+
94
+ __slots__ = ("token", "acquired_at")
95
+
96
+ def __init__(self, token: str) -> None:
97
+ self.token: str = token
98
+ self.acquired_at: float = time.monotonic()
99
+
100
+ def is_expired(self, ttl: float) -> bool:
101
+ """Return ``True`` if the token is older than *ttl* seconds."""
102
+ return (time.monotonic() - self.acquired_at) >= ttl
103
+
104
+
105
+ # ---------------------------------------------------------------------------
106
+ # AuthManager
107
+ # ---------------------------------------------------------------------------
108
+
109
+
110
+ class AuthManager:
111
+ """Caching authentication manager.
112
+
113
+ Maintains one valid token per host; transparently reacquires when the
114
+ TTL expires or after an explicit ``invalidate`` call.
115
+
116
+ Parameters
117
+ ----------
118
+ strategy:
119
+ The ``AuthStrategy`` instance used to acquire/revoke tokens.
120
+ token_ttl_seconds:
121
+ How long (in seconds) a cached token is considered valid before a
122
+ new one is acquired. Defaults to 3600 (one hour).
123
+ """
124
+
125
+ def __init__(
126
+ self, strategy: AuthStrategy, token_ttl_seconds: float = 3600
127
+ ) -> None:
128
+ self._strategy = strategy
129
+ self._token_ttl: float = token_ttl_seconds
130
+ # Maps host -> _CachedToken
131
+ self._cache: dict[str, _CachedToken] = {}
132
+
133
+ # ------------------------------------------------------------------
134
+ # Public API
135
+ # ------------------------------------------------------------------
136
+
137
+ def get_token(self, host: str, username: str, password: str) -> str:
138
+ """Return a valid token for *host*, acquiring one if necessary.
139
+
140
+ The token is taken from the in-memory cache when it exists and has
141
+ not exceeded the TTL. Otherwise a new token is acquired from the
142
+ strategy.
143
+
144
+ Parameters
145
+ ----------
146
+ host:
147
+ Target device hostname or IP.
148
+ username:
149
+ Credential — passed through to the strategy unchanged.
150
+ password:
151
+ Credential — never logged.
152
+
153
+ Returns
154
+ -------
155
+ str
156
+ A non-empty token string.
157
+ """
158
+ cached = self._cache.get(host)
159
+ if cached is not None and not cached.is_expired(self._token_ttl):
160
+ logger.debug("Using cached token for host=%s", host)
161
+ return cached.token
162
+
163
+ logger.info("Acquiring new token for host=%s", host)
164
+ token = self._strategy.acquire_token(host, username, password)
165
+ self._cache[host] = _CachedToken(token)
166
+ return token
167
+
168
+ def invalidate(self, host: str) -> None:
169
+ """Remove the cached token for *host* and attempt to revoke it.
170
+
171
+ If the strategy raises during revocation the exception is caught
172
+ and a ``WARNING`` is emitted; the cache entry is removed regardless.
173
+
174
+ Parameters
175
+ ----------
176
+ host:
177
+ The host whose token should be invalidated.
178
+ """
179
+ cached = self._cache.pop(host, None)
180
+ if cached is None:
181
+ return
182
+
183
+ try:
184
+ self._strategy.revoke_token(host, cached.token)
185
+ except Exception as exc: # noqa: BLE001
186
+ logger.warning(
187
+ "Failed to revoke token for host=%s: %s", host, exc
188
+ )
189
+
190
+ def has_token(self, host: str) -> bool:
191
+ """Return ``True`` if a non-expired token is cached for *host*.
192
+
193
+ Parameters
194
+ ----------
195
+ host:
196
+ The host to query.
197
+ """
198
+ cached = self._cache.get(host)
199
+ if cached is None:
200
+ return False
201
+ return not cached.is_expired(self._token_ttl)
202
+
203
+ def close(self) -> None:
204
+ """Revoke all cached tokens and clear the cache.
205
+
206
+ Revocation failures are logged at ``WARNING`` and do not prevent the
207
+ remaining tokens from being processed.
208
+ """
209
+ # Snapshot keys so we can safely mutate the dict during iteration
210
+ hosts = list(self._cache.keys())
211
+ for host in hosts:
212
+ self.invalidate(host)
213
+
214
+ # ------------------------------------------------------------------
215
+ # Dunder helpers
216
+ # ------------------------------------------------------------------
217
+
218
+ def __repr__(self) -> str:
219
+ """Return a safe representation that never includes token values."""
220
+ hosts = list(self._cache.keys())
221
+ strategy_name = type(self._strategy).__name__
222
+ return (
223
+ f"AuthManager(strategy={strategy_name}, "
224
+ f"hosts={hosts!r}, "
225
+ f"ttl={self._token_ttl}s)"
226
+ )