netriskscan 0.1.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.
- netriskscan-0.1.0/.github/workflows/ci.yml +29 -0
- netriskscan-0.1.0/.github/workflows/publish.yml +46 -0
- netriskscan-0.1.0/.gitignore +15 -0
- netriskscan-0.1.0/CHANGELOG.md +14 -0
- netriskscan-0.1.0/CONTRIBUTING.md +49 -0
- netriskscan-0.1.0/LICENSE +21 -0
- netriskscan-0.1.0/PKG-INFO +349 -0
- netriskscan-0.1.0/README.md +308 -0
- netriskscan-0.1.0/SECURITY.md +31 -0
- netriskscan-0.1.0/examples/anonymous.py +22 -0
- netriskscan-0.1.0/examples/async_quickstart.py +16 -0
- netriskscan-0.1.0/examples/error_handling.py +44 -0
- netriskscan-0.1.0/examples/quickstart.py +17 -0
- netriskscan-0.1.0/examples/usage_quota.py +21 -0
- netriskscan-0.1.0/pyproject.toml +90 -0
- netriskscan-0.1.0/src/netriskscan/__init__.py +76 -0
- netriskscan-0.1.0/src/netriskscan/_internal.py +253 -0
- netriskscan-0.1.0/src/netriskscan/_meta_registry.py +34 -0
- netriskscan-0.1.0/src/netriskscan/async_client.py +151 -0
- netriskscan-0.1.0/src/netriskscan/client.py +147 -0
- netriskscan-0.1.0/src/netriskscan/exceptions.py +163 -0
- netriskscan-0.1.0/src/netriskscan/models/__init__.py +30 -0
- netriskscan-0.1.0/src/netriskscan/models/meta.py +42 -0
- netriskscan-0.1.0/src/netriskscan/models/risk.py +234 -0
- netriskscan-0.1.0/src/netriskscan/models/usage.py +57 -0
- netriskscan-0.1.0/src/netriskscan/py.typed +0 -0
- netriskscan-0.1.0/src/netriskscan/version.py +1 -0
- netriskscan-0.1.0/tests/__init__.py +0 -0
- netriskscan-0.1.0/tests/conftest.py +100 -0
- netriskscan-0.1.0/tests/test_async_client.py +78 -0
- netriskscan-0.1.0/tests/test_client.py +189 -0
- netriskscan-0.1.0/tests/test_errors.py +239 -0
- netriskscan-0.1.0/tests/test_internal.py +107 -0
- netriskscan-0.1.0/tests/test_models.py +92 -0
- netriskscan-0.1.0/tests/test_retry.py +116 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
test:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
strategy:
|
|
15
|
+
matrix:
|
|
16
|
+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v7
|
|
19
|
+
- uses: actions/setup-python@v7
|
|
20
|
+
with:
|
|
21
|
+
python-version: ${{ matrix.python-version }}
|
|
22
|
+
cache: pip
|
|
23
|
+
- run: python -m pip install --upgrade pip
|
|
24
|
+
- run: pip install -e ".[dev]"
|
|
25
|
+
- run: ruff check .
|
|
26
|
+
- run: ruff format --check .
|
|
27
|
+
- run: mypy src
|
|
28
|
+
- run: pytest
|
|
29
|
+
- run: python -m build
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*.*.*"
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
build:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v7
|
|
16
|
+
- uses: actions/setup-python@v7
|
|
17
|
+
with:
|
|
18
|
+
python-version: "3.12"
|
|
19
|
+
- run: python -m pip install --upgrade pip build
|
|
20
|
+
- run: pip install -e ".[dev]"
|
|
21
|
+
- run: ruff check .
|
|
22
|
+
- run: mypy src
|
|
23
|
+
- run: pytest
|
|
24
|
+
- run: python -m build
|
|
25
|
+
- uses: actions/upload-artifact@v4
|
|
26
|
+
with:
|
|
27
|
+
name: dist
|
|
28
|
+
path: dist/
|
|
29
|
+
|
|
30
|
+
# Uses PyPI Trusted Publishing (OIDC) -- no long-lived API token is stored
|
|
31
|
+
# as a repository secret. Before the first release, a maintainer must
|
|
32
|
+
# register this repo/workflow/environment as a "pending trusted publisher"
|
|
33
|
+
# for the `netriskscan` project at https://pypi.org/manage/account/publishing/
|
|
34
|
+
# (the project does not exist on PyPI yet, so this is a one-time manual step).
|
|
35
|
+
publish:
|
|
36
|
+
needs: build
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
environment: pypi
|
|
39
|
+
permissions:
|
|
40
|
+
id-token: write
|
|
41
|
+
steps:
|
|
42
|
+
- uses: actions/download-artifact@v4
|
|
43
|
+
with:
|
|
44
|
+
name: dist
|
|
45
|
+
path: dist/
|
|
46
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
## [0.1.0] - 2026-09-16
|
|
6
|
+
|
|
7
|
+
Initial release.
|
|
8
|
+
|
|
9
|
+
- `NetRiskScan` (sync) and `AsyncNetRiskScan` (async) clients for `GET /v1/ip-risk/{ip}` and `GET /v1/usage`.
|
|
10
|
+
- Anonymous access (no API key) and API key authentication via `NETRISKSCAN_API_KEY` or an explicit argument.
|
|
11
|
+
- Typed, immutable dataclass models preserving tri-state (`True`/`False`/`None`) detection flags.
|
|
12
|
+
- Full exception hierarchy mapped to the documented `/v1` error codes.
|
|
13
|
+
- Automatic retry with backoff and `Retry-After` support for `429`/`502`/`503`/`504`.
|
|
14
|
+
- Rate limit and quota metadata via `get_response_meta()`.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
## Setup
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
python -m venv .venv
|
|
7
|
+
source .venv/bin/activate # .venv\Scripts\activate on Windows
|
|
8
|
+
pip install -e ".[dev]"
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Workflow
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
ruff check . # lint
|
|
15
|
+
ruff format --check . # formatting
|
|
16
|
+
mypy src # type check
|
|
17
|
+
pytest # tests
|
|
18
|
+
python -m build # build the sdist/wheel
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
All four must pass before a pull request is merged; CI runs the same checks.
|
|
22
|
+
|
|
23
|
+
## Ground rules
|
|
24
|
+
|
|
25
|
+
- This SDK is a typed API client, not a risk-scoring engine. Do not add client-side logic that
|
|
26
|
+
recomputes, derives, or overrides `risk.index`, `risk.band`, or any detection flag -- those values
|
|
27
|
+
come from the server only.
|
|
28
|
+
- Preserve tri-state semantics (`True` / `False` / `None`) on every detection flag. Never coerce
|
|
29
|
+
`None` to `False`, and never coerce a missing/`null` `risk.index` to `0`.
|
|
30
|
+
- Unknown fields returned by the API must be ignored, not raise -- this keeps older SDK versions
|
|
31
|
+
working against a server that has added new fields.
|
|
32
|
+
- Do not read `process.env`-style configuration beyond what's documented (`NETRISKSCAN_API_KEY`).
|
|
33
|
+
|
|
34
|
+
## Releasing
|
|
35
|
+
|
|
36
|
+
Version is set in `pyproject.toml` (`[project].version`) and mirrored in `src/netriskscan/version.py`.
|
|
37
|
+
To release: bump both, update `CHANGELOG.md`, commit, then push a `vX.Y.Z` tag -- `.github/workflows/publish.yml`
|
|
38
|
+
builds and publishes to PyPI via Trusted Publishing (OIDC), no stored token required. Before the very
|
|
39
|
+
first release, a maintainer must register this repository as a pending trusted publisher for the
|
|
40
|
+
`netriskscan` project at https://pypi.org/manage/account/publishing/ (one-time manual step, since the
|
|
41
|
+
project does not exist on PyPI yet).
|
|
42
|
+
|
|
43
|
+
## Reporting bugs / requesting features
|
|
44
|
+
|
|
45
|
+
Open a [GitHub issue](https://github.com/TeamQQ/netriskscan-sdk-python/issues).
|
|
46
|
+
|
|
47
|
+
## Security issues
|
|
48
|
+
|
|
49
|
+
See [SECURITY.md](SECURITY.md) -- do not open a public issue.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 NetRiskScan
|
|
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,349 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: netriskscan
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for IP risk, reputation, proxy, VPN, Tor and network intelligence using the NetRiskScan API.
|
|
5
|
+
Project-URL: Homepage, https://www.netriskscan.com/
|
|
6
|
+
Project-URL: Documentation, https://github.com/TeamQQ/netriskscan-sdk-python#readme
|
|
7
|
+
Project-URL: Developer API, https://www.netriskscan.com/
|
|
8
|
+
Project-URL: Repository, https://github.com/TeamQQ/netriskscan-sdk-python
|
|
9
|
+
Project-URL: Issues, https://github.com/TeamQQ/netriskscan-sdk-python/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/TeamQQ/netriskscan-sdk-python/blob/main/CHANGELOG.md
|
|
11
|
+
Project-URL: JavaScript SDK, https://www.npmjs.com/package/@netriskscan/sdk
|
|
12
|
+
Project-URL: CLI, https://www.npmjs.com/package/netriskscan-cli
|
|
13
|
+
Author: NetRiskScan
|
|
14
|
+
License-Expression: MIT
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Keywords: abuse-detection,anonymous-ip,api-client,cybersecurity,datacenter-detection,fraud-detection,ip-intelligence,ip-lookup,ip-reputation,ip-risk,ip-security,netriskscan,network-intelligence,network-security,proxy-detection,risk-scoring,threat-intelligence,tor-detection,vpn-detection
|
|
17
|
+
Classifier: Development Status :: 4 - Beta
|
|
18
|
+
Classifier: Intended Audience :: Developers
|
|
19
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
20
|
+
Classifier: Operating System :: OS Independent
|
|
21
|
+
Classifier: Programming Language :: Python :: 3
|
|
22
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
26
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
27
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
28
|
+
Classifier: Topic :: Internet
|
|
29
|
+
Classifier: Topic :: Security
|
|
30
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
31
|
+
Classifier: Typing :: Typed
|
|
32
|
+
Requires-Python: >=3.9
|
|
33
|
+
Requires-Dist: httpx<1,>=0.24
|
|
34
|
+
Provides-Extra: dev
|
|
35
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
36
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
37
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
38
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
39
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
40
|
+
Description-Content-Type: text/markdown
|
|
41
|
+
|
|
42
|
+
# NetRiskScan Python SDK
|
|
43
|
+
|
|
44
|
+
Official Python SDK for the [NetRiskScan](https://www.netriskscan.com/) IP Risk & Network Intelligence API: IP reputation, proxy/VPN/Tor detection, datacenter and search-crawler identification, and network intelligence.
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install netriskscan
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from netriskscan import NetRiskScan
|
|
52
|
+
|
|
53
|
+
client = NetRiskScan() # no signup, no API key required
|
|
54
|
+
|
|
55
|
+
result = client.ip_risk("8.8.8.8")
|
|
56
|
+
|
|
57
|
+
print(result.risk.index) # 0-100, higher = cleaner. Example output; live scores can change.
|
|
58
|
+
print(result.risk.band) # "excellent" | "good" | "fair" | "poor" | "high_risk" | "unknown"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Table of contents
|
|
62
|
+
|
|
63
|
+
- [Installation](#installation)
|
|
64
|
+
- [Quick start](#quick-start)
|
|
65
|
+
- [Anonymous usage](#anonymous-usage)
|
|
66
|
+
- [Authentication](#authentication)
|
|
67
|
+
- [IP risk lookup](#ip-risk-lookup)
|
|
68
|
+
- [Usage / quota](#usage--quota)
|
|
69
|
+
- [Async client](#async-client)
|
|
70
|
+
- [Error handling](#error-handling)
|
|
71
|
+
- [Rate limits](#rate-limits)
|
|
72
|
+
- [Configuration](#configuration)
|
|
73
|
+
- [Type hints](#type-hints)
|
|
74
|
+
- [Use cases](#use-cases)
|
|
75
|
+
- [API documentation](#api-documentation)
|
|
76
|
+
- [NetRiskScan ecosystem](#netriskscan-ecosystem)
|
|
77
|
+
- [Examples](#examples)
|
|
78
|
+
- [Security](#security)
|
|
79
|
+
- [License](#license)
|
|
80
|
+
|
|
81
|
+
## Installation
|
|
82
|
+
|
|
83
|
+
Requires Python 3.9+.
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
pip install netriskscan
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Quick start
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from netriskscan import NetRiskScan
|
|
93
|
+
|
|
94
|
+
client = NetRiskScan()
|
|
95
|
+
result = client.ip_risk("8.8.8.8")
|
|
96
|
+
|
|
97
|
+
print(result.risk.index, result.risk.band)
|
|
98
|
+
print(result.network.organization, result.network.asn)
|
|
99
|
+
print(result.flags.proxy, result.flags.vpn, result.flags.tor)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Anonymous usage
|
|
103
|
+
|
|
104
|
+
The API can be called without an account, metered by the server (a daily allowance per source IP, currently 30 requests/day -- the SDK does not hardcode this limit; read it from the response):
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from netriskscan import NetRiskScan
|
|
108
|
+
|
|
109
|
+
client = NetRiskScan()
|
|
110
|
+
result = client.ip_risk("8.8.8.8")
|
|
111
|
+
|
|
112
|
+
if result.usage is not None: # only present on anonymous calls
|
|
113
|
+
print(result.usage.remaining, "of", result.usage.daily_limit, "requests left today")
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Anonymous quota, rate limits, and eligibility are decided entirely by the server -- the SDK never re-implements or assumes those business rules.
|
|
117
|
+
|
|
118
|
+
## Authentication
|
|
119
|
+
|
|
120
|
+
Pass an API key explicitly, or set the `NETRISKSCAN_API_KEY` environment variable. Precedence: explicit argument > environment variable > anonymous.
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from netriskscan import NetRiskScan
|
|
124
|
+
|
|
125
|
+
client = NetRiskScan(api_key="nrs_live_xxxxxxxxxxxxxxxxxxxx")
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
export NETRISKSCAN_API_KEY="nrs_live_xxxxxxxxxxxxxxxxxxxx"
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
from netriskscan import NetRiskScan
|
|
134
|
+
|
|
135
|
+
client = NetRiskScan() # reads NETRISKSCAN_API_KEY if set, else anonymous
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The key is always sent as `Authorization: Bearer <api-key>` -- never as a URL query parameter, never logged, and never included in exception messages.
|
|
139
|
+
|
|
140
|
+
## IP risk lookup
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
result = client.ip_risk("8.8.8.8")
|
|
144
|
+
|
|
145
|
+
result.risk.index # int | None -- 0-100 cleanliness score (higher = cleaner), None if unscoreable
|
|
146
|
+
result.risk.band # str | None -- "excellent" | "good" | "fair" | "poor" | "high_risk" | "unknown"
|
|
147
|
+
result.risk.assessment_grade # str -- "complete" | "partial" | "limited" | "insufficient"
|
|
148
|
+
result.risk.reasons # list[RiskReason] -- may be empty, never a signal by itself
|
|
149
|
+
|
|
150
|
+
result.network.type # "residential" | "mobile" | "hosting" | "datacenter" | "public_infrastructure" | ...
|
|
151
|
+
result.network.connection_type # "direct" | "vpn" | "proxy" | "tor" | ...
|
|
152
|
+
result.network.asn # e.g. "AS15169"
|
|
153
|
+
result.network.organization # e.g. "Google LLC"
|
|
154
|
+
|
|
155
|
+
result.flags.proxy # bool | None -- see "null is not false" below
|
|
156
|
+
result.flags.proxy_type # populated only when flags.proxy is True
|
|
157
|
+
result.flags.vpn # bool | None
|
|
158
|
+
result.flags.tor # bool | None -- Tor *exit* node specifically
|
|
159
|
+
result.flags.datacenter # bool | None
|
|
160
|
+
result.flags.scanner # bool | None -- behavioral scanner/bot activity
|
|
161
|
+
result.flags.abuse # bool | None
|
|
162
|
+
result.flags.search_crawler # bool | None -- verified search-engine crawler identity
|
|
163
|
+
result.flags.search_crawler_name # e.g. "Google", populated only when search_crawler is True
|
|
164
|
+
|
|
165
|
+
result.location # IpLocation | None -- network-level geolocation, not device GPS
|
|
166
|
+
result.tor # TorInfo | None -- present only when the address is a Tor relay
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### The index is a cleanliness score, not a threat score
|
|
170
|
+
|
|
171
|
+
`risk.index` runs 0-100 where **higher means cleaner / more trustworthy**. It is not a fraud or threat score where higher is worse. Never invert or rescale it client-side.
|
|
172
|
+
|
|
173
|
+
### `None` is not `False`
|
|
174
|
+
|
|
175
|
+
Every detection flag (`proxy`, `vpn`, `tor`, `datacenter`, `scanner`, `abuse`, `search_crawler`) is a three-valued signal:
|
|
176
|
+
|
|
177
|
+
- `True` -- detected
|
|
178
|
+
- `False` -- checked, and confirmed not detected
|
|
179
|
+
- `None` -- unknown / not evaluated this round
|
|
180
|
+
|
|
181
|
+
Treating `None` as `False` turns "we don't know" into "we checked and it's clean," which is a different and stronger claim than the data supports. This SDK never performs that coercion, and code consuming these fields should not either.
|
|
182
|
+
|
|
183
|
+
### An unscoreable address is a success, not an error
|
|
184
|
+
|
|
185
|
+
Private, loopback, and other special-purpose addresses return `200 OK` with `risk.index is None`, `risk.band is None`, and `risk.assessment_grade == "insufficient"` -- not an exception. Check for `None` explicitly rather than assuming every successful call returns a numeric score.
|
|
186
|
+
|
|
187
|
+
### Search crawler identity vs. scanner behavior
|
|
188
|
+
|
|
189
|
+
`flags.search_crawler` answers a narrow question: is this address in a range list the search-engine operator itself publishes? It is independent of `flags.scanner`, which tracks behavioral scanning/bot activity. A verified crawler is not automatically "not a scanner," and vice versa -- read both.
|
|
190
|
+
|
|
191
|
+
## Usage / quota
|
|
192
|
+
|
|
193
|
+
Requires an API key (there is no anonymous account to report usage for):
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
usage = client.usage()
|
|
197
|
+
|
|
198
|
+
print(usage.plan)
|
|
199
|
+
print(usage.units.used, "/", usage.units.limit)
|
|
200
|
+
print(usage.rate_limit.requests_per_minute)
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Calling `usage()` without an API key raises `ValidationError` immediately, without a network call.
|
|
204
|
+
|
|
205
|
+
## Async client
|
|
206
|
+
|
|
207
|
+
Identical API, `httpx`-based async transport:
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
from netriskscan import AsyncNetRiskScan
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
async def main():
|
|
214
|
+
async with AsyncNetRiskScan() as client:
|
|
215
|
+
result = await client.ip_risk("8.8.8.8")
|
|
216
|
+
print(result.risk.index)
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Error handling
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
from netriskscan import (
|
|
223
|
+
NetRiskScan,
|
|
224
|
+
ValidationError,
|
|
225
|
+
AuthenticationError,
|
|
226
|
+
RateLimitError,
|
|
227
|
+
QuotaExceededError,
|
|
228
|
+
NotFoundError,
|
|
229
|
+
FeatureNotAvailableError,
|
|
230
|
+
ApiError,
|
|
231
|
+
TimeoutError,
|
|
232
|
+
NetworkError,
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
client = NetRiskScan(api_key="nrs_live_xxxxxxxxxxxxxxxxxxxx")
|
|
236
|
+
|
|
237
|
+
try:
|
|
238
|
+
result = client.ip_risk("8.8.8.8")
|
|
239
|
+
except ValidationError as e:
|
|
240
|
+
... # bad IP address / bad request (HTTP 400)
|
|
241
|
+
except AuthenticationError as e:
|
|
242
|
+
... # missing, invalid, or disabled API key (HTTP 401/403)
|
|
243
|
+
except QuotaExceededError as e:
|
|
244
|
+
... # billing-period quota or anonymous daily limit exhausted (HTTP 429)
|
|
245
|
+
except RateLimitError as e:
|
|
246
|
+
... # short-lived per-minute rate limit (HTTP 429); e.retry_after in seconds
|
|
247
|
+
except (NotFoundError, FeatureNotAvailableError):
|
|
248
|
+
... # unknown route, or a documented capability not open yet (HTTP 404)
|
|
249
|
+
except ApiError as e:
|
|
250
|
+
... # any other non-2xx response, e.g. HTTP 503 temporarily_unavailable
|
|
251
|
+
except TimeoutError as e:
|
|
252
|
+
... # request did not complete in time; never retried automatically
|
|
253
|
+
except NetworkError as e:
|
|
254
|
+
... # DNS/connection failure -- never reached the server
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Every exception carries `.message`, `.status_code`, `.code` (the server's open-vocabulary error code), and `.request_id` where available -- include `request_id` when reporting issues. `QuotaExceededError` is a subclass of `RateLimitError`, so `except RateLimitError` alone catches both.
|
|
258
|
+
|
|
259
|
+
## Rate limits
|
|
260
|
+
|
|
261
|
+
```python
|
|
262
|
+
from netriskscan import get_response_meta
|
|
263
|
+
|
|
264
|
+
result = client.ip_risk("8.8.8.8")
|
|
265
|
+
meta = get_response_meta(result)
|
|
266
|
+
|
|
267
|
+
if meta:
|
|
268
|
+
print(meta.rate_limit.remaining, "/", meta.rate_limit.limit)
|
|
269
|
+
print(meta.quota.remaining, "/", meta.quota.limit)
|
|
270
|
+
print(meta.request_id)
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
`get_response_meta()` returns `None` for a header that was never sent -- it is never coerced to `0`.
|
|
274
|
+
|
|
275
|
+
### Automatic retries
|
|
276
|
+
|
|
277
|
+
`GET` requests are retried automatically for HTTP `429`/`502`/`503`/`504` and for transient network failures, honoring the server's `Retry-After` header when present, otherwise using exponential backoff with jitter. Retries are capped by `max_retries` (default `2`) and `max_retry_delay` (default `10` seconds); a `Retry-After` longer than `max_retry_delay` raises immediately instead of blocking. `400`/`401`/`403`/`404` responses and request timeouts are never retried.
|
|
278
|
+
|
|
279
|
+
## Configuration
|
|
280
|
+
|
|
281
|
+
```python
|
|
282
|
+
from netriskscan import NetRiskScan
|
|
283
|
+
|
|
284
|
+
client = NetRiskScan(
|
|
285
|
+
api_key="nrs_live_xxxxxxxxxxxxxxxxxxxx", # optional; falls back to NETRISKSCAN_API_KEY, then anonymous
|
|
286
|
+
base_url="https://api.netriskscan.com", # override for testing/staging/mocking
|
|
287
|
+
timeout=10.0, # seconds, per attempt
|
|
288
|
+
max_retries=2,
|
|
289
|
+
max_retry_delay=10.0, # seconds
|
|
290
|
+
)
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Pass `http_client=httpx.Client(...)` (or `httpx.AsyncClient(...)` for `AsyncNetRiskScan`) to inject your own transport, for example `httpx.MockTransport` in tests.
|
|
294
|
+
|
|
295
|
+
## Type hints
|
|
296
|
+
|
|
297
|
+
The package ships `py.typed` and is fully annotated. Results are plain, immutable `dataclasses` -- no ORM-style magic, easy to log, cache, or serialize with `dataclasses.asdict()`.
|
|
298
|
+
|
|
299
|
+
## Use cases
|
|
300
|
+
|
|
301
|
+
- Detect proxy, VPN, and Tor infrastructure before signup or login
|
|
302
|
+
- Evaluate IP reputation as one signal in a fraud-prevention pipeline
|
|
303
|
+
- Distinguish verified search-engine crawlers from generic bot/scanner traffic
|
|
304
|
+
- Inspect datacenter and hosting traffic separately from residential networks
|
|
305
|
+
- Add network intelligence (ASN, organization, connection type) to abuse-prevention systems
|
|
306
|
+
- Gate CI/CD or infrastructure checks on a minimum risk index
|
|
307
|
+
|
|
308
|
+
## API documentation
|
|
309
|
+
|
|
310
|
+
Base URL: `https://api.netriskscan.com`
|
|
311
|
+
|
|
312
|
+
- `GET /v1/ip-risk/{ip}` -- IP risk, reputation, and network intelligence (works with or without an API key)
|
|
313
|
+
- `GET /v1/usage` -- current billing-period usage and quota (requires an API key)
|
|
314
|
+
|
|
315
|
+
Full endpoint and error-code reference: [Developer API documentation](https://www.netriskscan.com/).
|
|
316
|
+
|
|
317
|
+
## NetRiskScan ecosystem
|
|
318
|
+
|
|
319
|
+
- **Python SDK** (this package) -- `pip install netriskscan`
|
|
320
|
+
- **JavaScript / TypeScript SDK** -- [`@netriskscan/sdk`](https://www.npmjs.com/package/@netriskscan/sdk) on npm
|
|
321
|
+
- **CLI** -- [`netriskscan-cli`](https://www.npmjs.com/package/netriskscan-cli) (`npx netriskscan-cli check 8.8.8.8`)
|
|
322
|
+
- **Website / Developer API** -- [netriskscan.com](https://www.netriskscan.com/)
|
|
323
|
+
- **GitHub** -- [github.com/TeamQQ](https://github.com/TeamQQ)
|
|
324
|
+
|
|
325
|
+
Need JavaScript or TypeScript instead? See [`@netriskscan/sdk`](https://www.npmjs.com/package/@netriskscan/sdk).
|
|
326
|
+
|
|
327
|
+
## Examples
|
|
328
|
+
|
|
329
|
+
Runnable scripts in [`examples/`](examples/):
|
|
330
|
+
|
|
331
|
+
| File | Demonstrates |
|
|
332
|
+
| --- | --- |
|
|
333
|
+
| `examples/quickstart.py` | Sync client, anonymous IP risk lookup |
|
|
334
|
+
| `examples/async_quickstart.py` | Async client |
|
|
335
|
+
| `examples/anonymous.py` | Reading the anonymous daily allowance from a response |
|
|
336
|
+
| `examples/usage_quota.py` | Authenticated usage/quota lookup |
|
|
337
|
+
| `examples/error_handling.py` | Catching the full exception hierarchy |
|
|
338
|
+
|
|
339
|
+
## Security
|
|
340
|
+
|
|
341
|
+
- The API key is only ever sent as an `Authorization: Bearer` header, never in a URL, log line, or exception message.
|
|
342
|
+
- This SDK makes no calls to any host other than the configured `base_url`.
|
|
343
|
+
- No telemetry of any kind is collected or transmitted by this package.
|
|
344
|
+
|
|
345
|
+
Found a security issue? See [SECURITY.md](SECURITY.md).
|
|
346
|
+
|
|
347
|
+
## License
|
|
348
|
+
|
|
349
|
+
MIT -- see [LICENSE](LICENSE).
|