Skip to content

Commit 506c5ac

Browse files
authored
PENG-6243 - Introduce support for billing-usage APIs (#134)
* PENG-6243 - Introduce support for billing-usage APIs
1 parent 80f28d7 commit 506c5ac

15 files changed

+311
-20
lines changed

.github/workflows/release.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
uses: actions/checkout@v2
1212

1313
- name: Set up Python 3.8
14-
uses: actions/setup-python@v1
14+
uses: actions/setup-python@v4
1515
with:
1616
python-version: 3.8
1717

.github/workflows/verify.yml

+4-4
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ jobs:
99
steps:
1010
- uses: actions/checkout@v1
1111
- name: Set up Python
12-
uses: actions/setup-python@v1
12+
uses: actions/setup-python@v4
1313
with:
14-
python-version: 3.7
14+
python-version: 3.8
1515
- name: Install dependencies
1616
run: |
1717
python setup.py install
@@ -32,12 +32,12 @@ jobs:
3232
strategy:
3333
matrix:
3434
os: [ubuntu-latest]
35-
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
35+
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13']
3636

3737
steps:
3838
- uses: actions/checkout@v1
3939
- name: Set up Python
40-
uses: actions/setup-python@v1
40+
uses: actions/setup-python@v4
4141
with:
4242
python-version: ${{ matrix.python-version }}
4343
- name: Install dependencies

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,7 @@ target/
5656

5757
# Virtual environments
5858
venv/
59+
60+
# Editors
61+
.vscode/
62+
.idea

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 0.24.0 (March 20th, 2025)
2+
3+
ENHANCEMENTS:
4+
* Adds support for BillingUsage
5+
16
## 0.23.0 (Dec 9th, 2024)
27

38
ENHANCEMENTS:

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ and includes both a simple NS1 REST API wrapper as well as a higher level
1515
interface for managing zones, records, data feeds, and more.
1616
It supports synchronous and asynchronous transports.
1717

18-
Both python 2.7 and 3.3+ are supported. Automated tests are currently run
19-
against 2.7, 3.7, 3.8, 3.9 and 3.10.
18+
Python 3.8+ is supported. Automated tests are currently run
19+
against 3.8, 3.9, 3.10, 3.11, 3.12 and 3.13
2020

2121
Installation
2222
============

examples/billing_usage.py

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#
2+
# Copyright (c) 2025 NSONE, Inc.
3+
#
4+
# License under The MIT License (MIT). See LICENSE in project root.
5+
#
6+
7+
from ns1 import NS1
8+
import datetime
9+
10+
# NS1 will use config in ~/.nsone by default
11+
api = NS1()
12+
13+
# to specify an apikey here instead, use:
14+
15+
# from ns1 import Config
16+
# config = Config()
17+
# config.createFromAPIKey('<<CLEARTEXT API KEY>>')
18+
# api = NS1(config=config)
19+
20+
config = api.config
21+
22+
from_unix = int(
23+
datetime.datetime.fromisoformat("2025-02-01 00:00:00").strftime("%s")
24+
)
25+
to_unix = int(
26+
datetime.datetime.fromisoformat("2025-02-28 23:59:59").strftime("%s")
27+
)
28+
29+
############################
30+
# GET BILLING USAGE LIMITS #
31+
############################
32+
33+
limits = api.billing_usage().getLimits(from_unix, to_unix)
34+
print("### USAGE LIMITS ###")
35+
print(limits)
36+
print("####################")
37+
38+
###################################
39+
# GET BILLING USAGE FOR QUERIES #
40+
###################################
41+
42+
usg = api.billing_usage().getQueriesUsage(from_unix, to_unix)
43+
print("### QUERIES USAGE ###")
44+
print(usg)
45+
print("####################")
46+
47+
###################################
48+
# GET BILLING USAGE FOR DECISIONS #
49+
###################################
50+
51+
usg = api.billing_usage().getDecisionsUsage(from_unix, to_unix)
52+
print("### DECISIONS USAGE ###")
53+
print(usg)
54+
print("####################")
55+
56+
###################################
57+
# GET BILLING USAGE FOR MONITORS #
58+
###################################
59+
60+
usg = api.billing_usage().getMonitorsUsage()
61+
print("### MONITORS USAGE ###")
62+
print(usg)
63+
print("####################")
64+
65+
###################################
66+
# GET BILLING USAGE FOR FILER CHAINS #
67+
###################################
68+
69+
usg = api.billing_usage().getMonitorsUsage()
70+
print("### FILTER CHAINS USAGE ###")
71+
print(usg)
72+
print("####################")
73+
74+
###################################
75+
# GET BILLING USAGE FOR RECORDS #
76+
###################################
77+
78+
usg = api.billing_usage().getRecordsUsage()
79+
print("### RECORDS USAGE ###")
80+
print(usg)
81+
print("####################")

ns1/__init__.py

+11-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
#
2-
# Copyright (c) 2014, 2024 NSONE, Inc.
2+
# Copyright (c) 2014, 2025 NSONE, Inc.
33
#
44
# License under The MIT License (MIT). See LICENSE in project root.
55
#
66
from .config import Config
77

8-
version = "0.23.0"
8+
version = "0.24.0"
99

1010

1111
class NS1:
@@ -242,6 +242,15 @@ def alerts(self):
242242

243243
return ns1.rest.alerts.Alerts(self.config)
244244

245+
def billing_usage(self):
246+
"""
247+
Return a new raw REST interface to BillingUsage resources
248+
:rtype: :py:class:`ns1.rest.billing_usage.BillingUsage`
249+
"""
250+
import ns1.rest.billing_usage
251+
252+
return ns1.rest.billing_usage.BillingUsage(self.config)
253+
245254
# HIGH LEVEL INTERFACE
246255
def loadZone(self, zone, callback=None, errback=None):
247256
"""

ns1/config.py

+11-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#
2-
# Copyright (c) 2014 NSONE, Inc.
2+
# Copyright (c) 2014, 2025 NSONE, Inc.
33
#
44
# License under The MIT License (MIT). See LICENSE in project root.
55
#
@@ -32,7 +32,6 @@ class ConfigException(Exception):
3232

3333

3434
class Config:
35-
3635
"""A simple object for accessing and manipulating config files. These
3736
contains options and credentials for accessing the NS1 REST API.
3837
Config files are simple JSON text files.
@@ -45,6 +44,10 @@ class Config:
4544

4645
API_VERSION = "v1"
4746

47+
# API_VERSION_BEFORE_RESOURCE is a flag that determines whether the API_VERSION should go before the resource in the URL
48+
# Example: https://api.nsone.net/v1/zones vs https://api.nsone.net/zones/v1
49+
API_VERSION_BEFORE_RESOURCE = True
50+
4851
DEFAULT_CONFIG_FILE = "~/.nsone"
4952

5053
def __init__(self, path=None):
@@ -72,6 +75,11 @@ def _doDefaults(self):
7275
if "api_version" not in self._data:
7376
self._data["api_version"] = self.API_VERSION
7477

78+
if "api_version_before_resource" not in self._data:
79+
self._data["api_version_before_resource"] = (
80+
self.API_VERSION_BEFORE_RESOURCE
81+
)
82+
7583
if "cli" not in self._data:
7684
self._data["cli"] = {}
7785

@@ -243,7 +251,7 @@ def getEndpoint(self):
243251
else:
244252
endpoint = self._data["endpoint"]
245253

246-
return "https://%s%s/%s/" % (endpoint, port, self._data["api_version"])
254+
return f"https://{endpoint}{port}"
247255

248256
def getRateLimitingFunc(self):
249257
"""

ns1/monitoring.py

-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ class MonitorException(Exception):
1212

1313

1414
class Monitor(object):
15-
1615
"""
1716
High level object representing a Monitor
1817
"""

ns1/records.py

-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ class RecordException(Exception):
1313

1414

1515
class Record(object):
16-
1716
"""
1817
High level object representing a Record
1918
"""

ns1/rest/billing_usage.py

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#
2+
# Copyright (c) 2025 NSONE, Inc.
3+
#
4+
# License under The MIT License (MIT). See LICENSE in project root.
5+
#
6+
from . import resource
7+
import copy
8+
9+
10+
class BillingUsage(resource.BaseResource):
11+
ROOT = "billing-usage"
12+
13+
def __init__(self, config):
14+
config = copy.deepcopy(config)
15+
config["api_version_before_resource"] = False
16+
super(BillingUsage, self).__init__(config)
17+
18+
def getQueriesUsage(self, from_unix, to_unix, callback=None, errback=None):
19+
return self._make_request(
20+
"GET",
21+
f"{self.ROOT}/queries",
22+
callback=callback,
23+
errback=errback,
24+
params={"from": from_unix, "to": to_unix},
25+
)
26+
27+
def getDecisionsUsage(
28+
self, from_unix, to_unix, callback=None, errback=None
29+
):
30+
return self._make_request(
31+
"GET",
32+
f"{self.ROOT}/decisions",
33+
callback=callback,
34+
errback=errback,
35+
params={"from": from_unix, "to": to_unix},
36+
)
37+
38+
def getRecordsUsage(self, callback=None, errback=None):
39+
return self._make_request(
40+
"GET",
41+
f"{self.ROOT}/records",
42+
callback=callback,
43+
errback=errback,
44+
params={},
45+
)
46+
47+
def getMonitorsUsage(self, callback=None, errback=None):
48+
return self._make_request(
49+
"GET",
50+
f"{self.ROOT}/monitors",
51+
callback=callback,
52+
errback=errback,
53+
params={},
54+
)
55+
56+
def getFilterChainsUsage(self, callback=None, errback=None):
57+
return self._make_request(
58+
"GET",
59+
f"{self.ROOT}/filter-chains",
60+
callback=callback,
61+
errback=errback,
62+
params={},
63+
)
64+
65+
def getLimits(self, from_unix, to_unix, callback=None, errback=None):
66+
return self._make_request(
67+
"GET",
68+
f"{self.ROOT}/limits",
69+
callback=callback,
70+
errback=errback,
71+
params={"from": from_unix, "to": to_unix},
72+
)

ns1/rest/resource.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#
2-
# Copyright (c) 2014 NSONE, Inc.
2+
# Copyright (c) 2014, 2025 NSONE, Inc.
33
#
44
# License under The MIT License (MIT). See LICENSE in project root.
55
#
@@ -56,7 +56,12 @@ def _buildStdBody(self, body, fields):
5656
body[f] = fields[f]
5757

5858
def _make_url(self, path):
59-
return self._config.getEndpoint() + path
59+
if self._config["api_version_before_resource"]:
60+
return f"{self._config.getEndpoint()}/{self._config['api_version']}/{path}"
61+
62+
resource, sub_resource = path.split("/", 1)
63+
64+
return f"{self._config.getEndpoint()}/{resource}/{self._config['api_version']}/{sub_resource}"
6065

6166
def _make_request(self, type, path, **kwargs):
6267
VERBS = ["GET", "POST", "DELETE", "PUT"]

ns1/rest/transport/twisted.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -247,9 +247,9 @@ def _request_func(self, method, headers, data, files):
247247

248248
if headers is None:
249249
headers = {}
250-
headers[
251-
"Content-Type"
252-
] = "multipart/form-data; boundary={}".format(boundary)
250+
headers["Content-Type"] = (
251+
"multipart/form-data; boundary={}".format(boundary)
252+
)
253253
bProducer = FileBodyProducer(StringIO.StringIO(body))
254254

255255
theaders = (

0 commit comments

Comments
 (0)