cisco-eox-query 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.
@@ -0,0 +1,18 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+
12
+ # Testing
13
+ .pytest_cache/
14
+ .tox/
15
+ .coverage
16
+ coverage.xml
17
+ htmlcov/
18
+ .envrc
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dave Heckman
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,171 @@
1
+ Metadata-Version: 2.5
2
+ Name: cisco-eox-query
3
+ Version: 1.0.0
4
+ Summary: Python client for the Cisco End-of-Life (EOX) API
5
+ Project-URL: Homepage, https://github.com/dheckman-dev/cisco-eox-query
6
+ Project-URL: Repository, https://github.com/dheckman-dev/cisco-eox-query
7
+ Project-URL: Documentation, https://github.com/dheckman-dev/cisco-eox-query#readme
8
+ Project-URL: Issues, https://github.com/dheckman-dev/cisco-eox-query/issues
9
+ Author-email: Dave Heckman <david@heckman.network>
10
+ Maintainer-email: Dave Heckman <david@heckman.network>
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: cisco,end-of-life,end-of-support,eox,httpx,pydantic
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Requires-Python: >=3.11
23
+ Requires-Dist: httpx>=0.28.1
24
+ Requires-Dist: pydantic>=2.7
25
+ Description-Content-Type: text/markdown
26
+
27
+ # cisco-eox-query
28
+
29
+ Python client for the [Cisco End-of-Life (EOX) API](https://developer.cisco.com/docs/support-apis/eox).
30
+
31
+ Built on `httpx` for transport and `pydantic` for response models. This package
32
+ implements the Cisco EOX API v5 as described by
33
+ `Cisco-End-of-Life-EOX-v5_0.wadl`.
34
+
35
+ ![Python versions](https://img.shields.io/pypi/pyversions/cisco-eox-query)
36
+ ![Coverage](https://img.shields.io/codecov/c/github/dheckman-dev/cisco-eox-query)
37
+
38
+ ## Versioning
39
+
40
+ Library major versions track the EOX API version:
41
+
42
+ | `cisco-eox-query` | EOX API | Status |
43
+ | --- | --- | --- |
44
+ | 1.x | v5 | current |
45
+ | 2.x | v6 | future, when released |
46
+
47
+ The versioned implementation lives under `cisco_eox_query.v5`. The top-level
48
+ package re-exports the latest supported version for convenience.
49
+
50
+ ## Install
51
+
52
+ ```bash
53
+ pip install cisco-eox-query
54
+ ```
55
+
56
+ ## Authentication
57
+
58
+ EOX is one of the Cisco Support APIs and requires a Smart Net Total Care (SNTC)
59
+ or Partner Support Service (PSS) entitlement. Register an application at the
60
+ [Cisco API Console](https://apiconsole.cisco.com/) to obtain a client ID and
61
+ client secret. The client exchanges them for a bearer token (client-credentials
62
+ grant) and refreshes it automatically before expiry.
63
+
64
+ ## Usage
65
+
66
+ ```python
67
+ from cisco_eox_query import EOXClient
68
+
69
+ with EOXClient(client_id="...", client_secret="...") as client:
70
+ records = list(client.iter_product_ids(["WS-C2960X-48TS-L", "WIC-1T="]))
71
+ for record in records:
72
+ print(record.eol_product_id, record.last_date_of_support)
73
+ ```
74
+
75
+ All four WADL endpoints are exposed as `search_*` methods returning an
76
+ `EOXResponse` (with `records`, `pagination`, and optional `error`), plus
77
+ `iter_*` variants that follow pagination automatically:
78
+
79
+ | Endpoint | Method |
80
+ | --- | --- |
81
+ | `EOXByDates/{pageIndex}/{startDate}/{endDate}` | `search_by_dates` / `iter_dates` |
82
+ | `EOXByProductID/{pageIndex}/{productIDs}` | `search_by_product_ids` / `iter_product_ids` |
83
+ | `EOXBySerialNumber/{pageIndex}/{serialNumbers}` | `search_by_serial_numbers` / `iter_serial_numbers` |
84
+ | `EOXBySWReleaseString/{pageIndex}` | `search_by_software_releases` / `iter_software_releases` |
85
+
86
+ The `iter_*` variants accept an optional `max_pages` keyword (default 1000) as
87
+ a safety valve against runaway pagination; if the API never reports a final
88
+ page, a `PaginationError` is raised instead of looping forever.
89
+
90
+ If you already hold a token:
91
+
92
+ ```python
93
+ client = EOXClient(access_token="...")
94
+ ```
95
+
96
+ ## Command line
97
+
98
+ The package installs an `eox-query` console script. Credentials may be passed
99
+ as flags or via `EOX_CLIENT_ID`, `EOX_CLIENT_SECRET`, and `EOX_ACCESS_TOKEN`
100
+ environment variables.
101
+
102
+ ```bash
103
+ eox-query --client-id ... --client-secret ... pid WS-C2960X-48TS-L WIC-1T=
104
+ eox-query --client-id ... --client-secret ... serial JAE11108ESH SAD11510738
105
+ eox-query --client-id ... --client-secret ... software 12.4\(15\)T,IOS
106
+ eox-query --client-id ... --client-secret ... dates 2011-01-01 2015-12-31 --attribs EO_SALES_DATE
107
+ ```
108
+
109
+ Run `eox-query --examples` for annotated usage examples, or
110
+ `eox-query <command> --examples` (e.g. `eox-query pid --examples`) for
111
+ command-specific examples. Examples are also embedded in `--help` output.
112
+
113
+ Logs go to stderr with the format `YYYY-MM-DD HH:MM:SS - SEVERITY - module - message`; queried records print to stdout so they can be piped. Diagnostics are silent by default and increase with `-v` (info) / `-vv` (debug).
114
+
115
+ ```bash
116
+ eox-query -vv --client-id ... --client-secret ... pid WIC-1T=
117
+ ```
118
+
119
+ Library users get the same structured loggers (`cisco_eox_query._base`, `cisco_eox_query.v5.client`, ...) without any handler configuration.
120
+
121
+ ## Rate limiting and retries
122
+
123
+ The EOX API is limited to 5 requests/second and 5000 requests/day. The client
124
+ handles this automatically:
125
+
126
+ - Requests are throttled to stay under the per-second limit (default 0.25s
127
+ between requests, configurable via `min_request_interval`).
128
+ - HTTP 429/408/425/5xx responses and transport errors are retried up to
129
+ `max_retries` times (default 3) with `retry_delay` seconds between attempts
130
+ (default 5s), honoring the server's `Retry-After` header (both delay-seconds
131
+ and RFC 7231 HTTP-date formats) when present.
132
+ - After retries are exhausted on a 429, a `RateLimitError` is raised; the CLI
133
+ prints a message about hitting the daily limit and exits with code 2.
134
+ - Non-retryable errors (e.g. HTTP 403) are raised immediately.
135
+
136
+ ```python
137
+ from cisco_eox_query import EOXClient, RateLimitError
138
+
139
+ with EOXClient(client_id="...", client_secret="...", max_retries=5, retry_delay=2.0) as client:
140
+ try:
141
+ for record in client.iter_product_ids(["WIC-1T="]):
142
+ print(record.eol_product_id)
143
+ except RateLimitError as exc:
144
+ print(f"Hit the daily request limit: {exc}")
145
+ ```
146
+
147
+ ## Error handling
148
+
149
+ - `EOXAPIError` — raised when a response contains an `EOXError` payload. Call
150
+ `response.raise_for_error()` on `search_*` results; the `iter_*` variants
151
+ raise automatically.
152
+ - `RetryError` — a request failed after exhausting all retries.
153
+ - `RateLimitError` — a rate-limited request failed after exhausting retries
154
+ (subclass of `RetryError`).
155
+ - `PaginationError` — pagination did not terminate within `max_pages`
156
+ (subclass of `RetryError`).
157
+ - `ValueError` — invalid input, a malformed JSON response body, or an
158
+ unexpected response shape (possible API schema change). The message includes
159
+ the request path and the first validation error.
160
+
161
+ The client tolerates common API quirks: a `null` `EOXRecord` is treated as an
162
+ empty result set, and a single record returned as a bare object (not wrapped
163
+ in a list) is accepted.
164
+
165
+ ## Development
166
+
167
+ ```bash
168
+ uv sync
169
+ uv run pytest
170
+ uv build
171
+ ```
@@ -0,0 +1,145 @@
1
+ # cisco-eox-query
2
+
3
+ Python client for the [Cisco End-of-Life (EOX) API](https://developer.cisco.com/docs/support-apis/eox).
4
+
5
+ Built on `httpx` for transport and `pydantic` for response models. This package
6
+ implements the Cisco EOX API v5 as described by
7
+ `Cisco-End-of-Life-EOX-v5_0.wadl`.
8
+
9
+ ![Python versions](https://img.shields.io/pypi/pyversions/cisco-eox-query)
10
+ ![Coverage](https://img.shields.io/codecov/c/github/dheckman-dev/cisco-eox-query)
11
+
12
+ ## Versioning
13
+
14
+ Library major versions track the EOX API version:
15
+
16
+ | `cisco-eox-query` | EOX API | Status |
17
+ | --- | --- | --- |
18
+ | 1.x | v5 | current |
19
+ | 2.x | v6 | future, when released |
20
+
21
+ The versioned implementation lives under `cisco_eox_query.v5`. The top-level
22
+ package re-exports the latest supported version for convenience.
23
+
24
+ ## Install
25
+
26
+ ```bash
27
+ pip install cisco-eox-query
28
+ ```
29
+
30
+ ## Authentication
31
+
32
+ EOX is one of the Cisco Support APIs and requires a Smart Net Total Care (SNTC)
33
+ or Partner Support Service (PSS) entitlement. Register an application at the
34
+ [Cisco API Console](https://apiconsole.cisco.com/) to obtain a client ID and
35
+ client secret. The client exchanges them for a bearer token (client-credentials
36
+ grant) and refreshes it automatically before expiry.
37
+
38
+ ## Usage
39
+
40
+ ```python
41
+ from cisco_eox_query import EOXClient
42
+
43
+ with EOXClient(client_id="...", client_secret="...") as client:
44
+ records = list(client.iter_product_ids(["WS-C2960X-48TS-L", "WIC-1T="]))
45
+ for record in records:
46
+ print(record.eol_product_id, record.last_date_of_support)
47
+ ```
48
+
49
+ All four WADL endpoints are exposed as `search_*` methods returning an
50
+ `EOXResponse` (with `records`, `pagination`, and optional `error`), plus
51
+ `iter_*` variants that follow pagination automatically:
52
+
53
+ | Endpoint | Method |
54
+ | --- | --- |
55
+ | `EOXByDates/{pageIndex}/{startDate}/{endDate}` | `search_by_dates` / `iter_dates` |
56
+ | `EOXByProductID/{pageIndex}/{productIDs}` | `search_by_product_ids` / `iter_product_ids` |
57
+ | `EOXBySerialNumber/{pageIndex}/{serialNumbers}` | `search_by_serial_numbers` / `iter_serial_numbers` |
58
+ | `EOXBySWReleaseString/{pageIndex}` | `search_by_software_releases` / `iter_software_releases` |
59
+
60
+ The `iter_*` variants accept an optional `max_pages` keyword (default 1000) as
61
+ a safety valve against runaway pagination; if the API never reports a final
62
+ page, a `PaginationError` is raised instead of looping forever.
63
+
64
+ If you already hold a token:
65
+
66
+ ```python
67
+ client = EOXClient(access_token="...")
68
+ ```
69
+
70
+ ## Command line
71
+
72
+ The package installs an `eox-query` console script. Credentials may be passed
73
+ as flags or via `EOX_CLIENT_ID`, `EOX_CLIENT_SECRET`, and `EOX_ACCESS_TOKEN`
74
+ environment variables.
75
+
76
+ ```bash
77
+ eox-query --client-id ... --client-secret ... pid WS-C2960X-48TS-L WIC-1T=
78
+ eox-query --client-id ... --client-secret ... serial JAE11108ESH SAD11510738
79
+ eox-query --client-id ... --client-secret ... software 12.4\(15\)T,IOS
80
+ eox-query --client-id ... --client-secret ... dates 2011-01-01 2015-12-31 --attribs EO_SALES_DATE
81
+ ```
82
+
83
+ Run `eox-query --examples` for annotated usage examples, or
84
+ `eox-query <command> --examples` (e.g. `eox-query pid --examples`) for
85
+ command-specific examples. Examples are also embedded in `--help` output.
86
+
87
+ Logs go to stderr with the format `YYYY-MM-DD HH:MM:SS - SEVERITY - module - message`; queried records print to stdout so they can be piped. Diagnostics are silent by default and increase with `-v` (info) / `-vv` (debug).
88
+
89
+ ```bash
90
+ eox-query -vv --client-id ... --client-secret ... pid WIC-1T=
91
+ ```
92
+
93
+ Library users get the same structured loggers (`cisco_eox_query._base`, `cisco_eox_query.v5.client`, ...) without any handler configuration.
94
+
95
+ ## Rate limiting and retries
96
+
97
+ The EOX API is limited to 5 requests/second and 5000 requests/day. The client
98
+ handles this automatically:
99
+
100
+ - Requests are throttled to stay under the per-second limit (default 0.25s
101
+ between requests, configurable via `min_request_interval`).
102
+ - HTTP 429/408/425/5xx responses and transport errors are retried up to
103
+ `max_retries` times (default 3) with `retry_delay` seconds between attempts
104
+ (default 5s), honoring the server's `Retry-After` header (both delay-seconds
105
+ and RFC 7231 HTTP-date formats) when present.
106
+ - After retries are exhausted on a 429, a `RateLimitError` is raised; the CLI
107
+ prints a message about hitting the daily limit and exits with code 2.
108
+ - Non-retryable errors (e.g. HTTP 403) are raised immediately.
109
+
110
+ ```python
111
+ from cisco_eox_query import EOXClient, RateLimitError
112
+
113
+ with EOXClient(client_id="...", client_secret="...", max_retries=5, retry_delay=2.0) as client:
114
+ try:
115
+ for record in client.iter_product_ids(["WIC-1T="]):
116
+ print(record.eol_product_id)
117
+ except RateLimitError as exc:
118
+ print(f"Hit the daily request limit: {exc}")
119
+ ```
120
+
121
+ ## Error handling
122
+
123
+ - `EOXAPIError` — raised when a response contains an `EOXError` payload. Call
124
+ `response.raise_for_error()` on `search_*` results; the `iter_*` variants
125
+ raise automatically.
126
+ - `RetryError` — a request failed after exhausting all retries.
127
+ - `RateLimitError` — a rate-limited request failed after exhausting retries
128
+ (subclass of `RetryError`).
129
+ - `PaginationError` — pagination did not terminate within `max_pages`
130
+ (subclass of `RetryError`).
131
+ - `ValueError` — invalid input, a malformed JSON response body, or an
132
+ unexpected response shape (possible API schema change). The message includes
133
+ the request path and the first validation error.
134
+
135
+ The client tolerates common API quirks: a `null` `EOXRecord` is treated as an
136
+ empty result set, and a single record returned as a bare object (not wrapped
137
+ in a list) is accepted.
138
+
139
+ ## Development
140
+
141
+ ```bash
142
+ uv sync
143
+ uv run pytest
144
+ uv build
145
+ ```
@@ -0,0 +1,28 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ Only the latest 1.x release receives security fixes.
6
+
7
+ | Version | Supported |
8
+ | ------- | --------- |
9
+ | latest 1.x | Yes |
10
+ | older 1.x | No |
11
+
12
+ ## Reporting a Vulnerability
13
+
14
+ Please do **not** open a public issue for a security vulnerability.
15
+
16
+ Instead, report it privately via one of:
17
+
18
+ - **GitHub private security advisory:** <https://github.com/dheckman-dev/cisco-eox-query/security/advisories/new>
19
+ - **Email:** [david@heckman.network](mailto:david@heckman.network)
20
+
21
+ We will acknowledge receipt within **48 hours** and work toward coordinated disclosure, generally publishing a fix and advisory within 90 days or as agreed with the reporter.
22
+
23
+ ## Security Considerations for Users
24
+
25
+ - **Credentials:** Cisco EOX API credentials (`client_id`/`client_secret`) must be supplied at runtime via environment variables (`EOX_CLIENT_ID`, `EOX_CLIENT_SECRET`, `EOX_ACCESS_TOKEN`) or constructor arguments. Never hard-code or commit credentials.
26
+ - **TLS:** All traffic to the Cisco API is over HTTPS. The client does not disable TLS certificate verification.
27
+ - **Network posture:** The package only makes outbound requests to the Cisco EOX API endpoints. It does not accept inbound connections.
28
+ - **Dependencies:** Dependencies are pinned via `uv.lock`. Keep the package updated to receive dependency security fixes.
@@ -0,0 +1,79 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "cisco-eox-query"
7
+ version = "1.0.0"
8
+ description = "Python client for the Cisco End-of-Life (EOX) API"
9
+ readme = "README.md"
10
+ authors = [
11
+ { name = "Dave Heckman", email="david@heckman.network"},
12
+ ]
13
+ maintainers = [
14
+ { name = "Dave Heckman", email="david@heckman.network"},
15
+ ]
16
+ requires-python = ">=3.11"
17
+ license = "MIT"
18
+ keywords = ["cisco", "eox", "end-of-life", "end-of-support", "httpx", "pydantic"]
19
+ classifiers = [
20
+ "Intended Audience :: Developers",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.13",
25
+ "Programming Language :: Python :: 3.14",
26
+ "License :: OSI Approved :: MIT License",
27
+ "Topic :: Software Development :: Libraries :: Python Modules",
28
+ ]
29
+ dependencies = [
30
+ "httpx>=0.28.1",
31
+ "pydantic>=2.7",
32
+ ]
33
+
34
+ [project.scripts]
35
+ eox-query = "cisco_eox_query.cli:main"
36
+
37
+ [project.urls]
38
+ Homepage = "https://github.com/dheckman-dev/cisco-eox-query"
39
+ Repository = "https://github.com/dheckman-dev/cisco-eox-query"
40
+ Documentation = "https://github.com/dheckman-dev/cisco-eox-query#readme"
41
+ Issues = "https://github.com/dheckman-dev/cisco-eox-query/issues"
42
+
43
+ [dependency-groups]
44
+ dev = [
45
+ "pytest>=8.0",
46
+ "pytest-cov>=4.0",
47
+ ]
48
+
49
+ [tool.hatch.build.targets.wheel]
50
+ packages = ["src/cisco_eox_query"]
51
+
52
+ [tool.hatch.build.targets.sdist]
53
+ exclude = [
54
+ "/.github",
55
+ "/.envrc",
56
+ "/.gitignore",
57
+ "/.pytest_cache",
58
+ "/.python-version",
59
+ "/.tox",
60
+ "/.venv",
61
+ "/.coverage",
62
+ "/coverage.xml",
63
+ "/dist",
64
+ "/tests",
65
+ "/tox.ini",
66
+ "/uv.lock",
67
+ ]
68
+
69
+ [tool.pytest.ini_options]
70
+ testpaths = ["tests"]
71
+ pythonpath = ["src"]
72
+
73
+ [tool.coverage.run]
74
+ source = ["cisco_eox_query"]
75
+ branch = true
76
+
77
+ [tool.coverage.report]
78
+ fail_under = 80
79
+ show_missing = true
@@ -0,0 +1,55 @@
1
+ """Client for the Cisco End-of-Life (EOX) API.
2
+
3
+ Versioning
4
+ ----------
5
+ Library major versions track the EOX API version:
6
+
7
+ - ``cisco-eox-query`` 1.x implements the EOX API **v5**.
8
+ - ``cisco-eox-query`` 2.x will implement the EOX API **v6** (when released).
9
+
10
+ The versioned implementation lives in :mod:`cisco_eox_query.v5`; this module
11
+ re-exports it as the default. Pin an explicit version with::
12
+
13
+ from cisco_eox_query.v5 import EOXClient
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from importlib.metadata import PackageNotFoundError, version
19
+
20
+ from cisco_eox_query._base import PaginationError, RateLimitError, RetryError
21
+ from cisco_eox_query.v5 import (
22
+ EOX_ATTRIBS,
23
+ OS_TYPES,
24
+ EOXAPIError,
25
+ EOXClient,
26
+ EOXErrorInfo,
27
+ EOXRecord,
28
+ EOXResponse,
29
+ MigrationDetails,
30
+ PaginationResponseRecord,
31
+ )
32
+
33
+ try:
34
+ __version__ = version("cisco-eox-query")
35
+ except PackageNotFoundError:
36
+ __version__ = "1.0.0"
37
+
38
+ SUPPORTED_API_VERSION = 5
39
+
40
+ __all__ = [
41
+ "EOXClient",
42
+ "EOXRecord",
43
+ "EOXResponse",
44
+ "EOXErrorInfo",
45
+ "EOXAPIError",
46
+ "MigrationDetails",
47
+ "PaginationResponseRecord",
48
+ "RetryError",
49
+ "RateLimitError",
50
+ "PaginationError",
51
+ "EOX_ATTRIBS",
52
+ "OS_TYPES",
53
+ "SUPPORTED_API_VERSION",
54
+ "__version__",
55
+ ]