From b96928c06197eb25763997c8130fd5c3923e5f8d Mon Sep 17 00:00:00 2001 From: Cristian Pontes Date: Wed, 19 Mar 2025 17:59:19 +0000 Subject: [PATCH 01/11] PENG-6243 - Introduce support for billing-usage APIs --- CHANGELOG.md | 5 ++ examples/billing_usage.py | 73 ++++++++++++++++++++ ns1/__init__.py | 13 +++- ns1/config.py | 11 ++- ns1/rest/billing_usage.py | 70 +++++++++++++++++++ ns1/rest/resource.py | 18 ++++- tests/unit/test_billing_usage.py | 112 +++++++++++++++++++++++++++++++ tests/unit/test_config.py | 3 +- 8 files changed, 298 insertions(+), 7 deletions(-) create mode 100644 examples/billing_usage.py create mode 100644 ns1/rest/billing_usage.py create mode 100644 tests/unit/test_billing_usage.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 61b1a06..3668a9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.24.0 (March 20th, 2025) + +ENHANCEMENTS: +* Adds support for BillingUsage + ## 0.23.0 (Dec 9th, 2024) ENHANCEMENTS: diff --git a/examples/billing_usage.py b/examples/billing_usage.py new file mode 100644 index 0000000..073c64e --- /dev/null +++ b/examples/billing_usage.py @@ -0,0 +1,73 @@ +# +# Copyright (c) 2025 NSONE, Inc. +# +# License under The MIT License (MIT). See LICENSE in project root. +# + +from ns1 import NS1 + +# NS1 will use config in ~/.nsone by default +api = NS1() + +# to specify an apikey here instead, use: + +# from ns1 import Config +# config = Config() +# config.createFromAPIKey('<>') +# api = NS1(config=config) + +config = api.config + +############################ +# GET BILLING USAGE LIMITS # +############################ + +limits = api.billing_usage().getLimits(fromUnix=1738368000, toUnix=1740614400) +print("### USAGE LIMITS ###") +print(limits) +print("####################") + +################################### +# GET BILLING USAGE FOR QUERIES # +################################### + +usg = api.billing_usage().getQueriesUsage(fromUnix=1738368000, toUnix=1740614400) +print("### QUERIES USAGE ###") +print(usg) +print("####################") + +################################### +# GET BILLING USAGE FOR DECISIONS # +################################### + +usg = api.billing_usage().getDecisionsUsage(fromUnix=1738368000, toUnix=1740614400) +print("### DECISIONS USAGE ###") +print(usg) +print("####################") + +################################### +# GET BILLING USAGE FOR MONITORS # +################################### + +usg = api.billing_usage().getMonitorsUsage() +print("### MONITORS USAGE ###") +print(usg) +print("####################") + +################################### +# GET BILLING USAGE FOR FILER CHAINS # +################################### + +usg = api.billing_usage().getMonitorsUsage() +print("### FILTER CHAINS USAGE ###") +print(usg) +print("####################") + +################################### +# GET BILLING USAGE FOR RECORDS # +################################### + +usg = api.billing_usage().getRecordsUsage() +print("### RECORDS USAGE ###") +print(usg) +print("####################") diff --git a/ns1/__init__.py b/ns1/__init__.py index b75dc18..e96296e 100644 --- a/ns1/__init__.py +++ b/ns1/__init__.py @@ -1,11 +1,11 @@ # -# Copyright (c) 2014, 2024 NSONE, Inc. +# Copyright (c) 2014, 2025 NSONE, Inc. # # License under The MIT License (MIT). See LICENSE in project root. # from .config import Config -version = "0.23.0" +version = "0.24.0" class NS1: @@ -242,6 +242,15 @@ def alerts(self): return ns1.rest.alerts.Alerts(self.config) + def billing_usage(self): + """ + Return a new raw REST interface to BillingUsage resources + :rtype: :py:class:`ns1.rest.billing_usage.BillingUsage` + """ + import ns1.rest.billing_usage + + return ns1.rest.billing_usage.BillingUsage(self.config) + # HIGH LEVEL INTERFACE def loadZone(self, zone, callback=None, errback=None): """ diff --git a/ns1/config.py b/ns1/config.py index 2728b67..e7eed4a 100644 --- a/ns1/config.py +++ b/ns1/config.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2014 NSONE, Inc. +# Copyright (c) 2014, 2025 NSONE, Inc. # # License under The MIT License (MIT). See LICENSE in project root. # @@ -45,6 +45,10 @@ class Config: API_VERSION = "v1" + # API_VERSION_BEFORE_RESOURCE is a flag that determines whether the API_VERSION should go before the resource in the URL + # Example: https://api.nsone.net/v1/zones vs https://api.nsone.net/zones/v1 + API_VERSION_BEFORE_RESOURCE = True + DEFAULT_CONFIG_FILE = "~/.nsone" def __init__(self, path=None): @@ -72,6 +76,9 @@ def _doDefaults(self): if "api_version" not in self._data: self._data["api_version"] = self.API_VERSION + if "api_version_before_resource" not in self._data: + self._data["api_version_before_resource"] = self.API_VERSION_BEFORE_RESOURCE + if "cli" not in self._data: self._data["cli"] = {} @@ -243,7 +250,7 @@ def getEndpoint(self): else: endpoint = self._data["endpoint"] - return "https://%s%s/%s/" % (endpoint, port, self._data["api_version"]) + return "https://%s%s" % (endpoint, port) def getRateLimitingFunc(self): """ diff --git a/ns1/rest/billing_usage.py b/ns1/rest/billing_usage.py new file mode 100644 index 0000000..d6f8f26 --- /dev/null +++ b/ns1/rest/billing_usage.py @@ -0,0 +1,70 @@ +# +# Copyright (c) 2025 NSONE, Inc. +# +# License under The MIT License (MIT). See LICENSE in project root. +# +from . import resource +import copy + + +class BillingUsage(resource.BaseResource): + ROOT = "billing-usage" + + def __init__(self, config): + config = copy.deepcopy(config) + config['api_version_before_resource'] = False + super(BillingUsage, self).__init__(config) + + def getQueriesUsage(self, fromUnix, toUnix, callback=None, errback=None): + return self._make_request( + "GET", + "%s/queries" % self.ROOT, + callback=callback, + errback=errback, + params={'from': fromUnix, 'to': toUnix}, + ) + + def getDecisionsUsage(self, fromUnix, toUnix, callback=None, errback=None): + return self._make_request( + "GET", + "%s/decisions" % self.ROOT, + callback=callback, + errback=errback, + params={'from': fromUnix, 'to': toUnix}, + ) + + def getRecordsUsage(self, callback=None, errback=None): + return self._make_request( + "GET", + "%s/records" % self.ROOT, + callback=callback, + errback=errback, + params={}, + ) + + def getMonitorsUsage(self, callback=None, errback=None): + return self._make_request( + "GET", + "%s/monitors" % self.ROOT, + callback=callback, + errback=errback, + params={}, + ) + + def getFilterChainsUsage(self, callback=None, errback=None): + return self._make_request( + "GET", + "%s/filter-chains" % self.ROOT, + callback=callback, + errback=errback, + params={}, + ) + + def getLimits(self, fromUnix, toUnix, callback=None, errback=None): + return self._make_request( + "GET", + "%s/limits" % self.ROOT, + callback=callback, + errback=errback, + params={'from': fromUnix, 'to': toUnix}, + ) diff --git a/ns1/rest/resource.py b/ns1/rest/resource.py index 66aa5e1..006e812 100644 --- a/ns1/rest/resource.py +++ b/ns1/rest/resource.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2014 NSONE, Inc. +# Copyright (c) 2014, 2025 NSONE, Inc. # # License under The MIT License (MIT). See LICENSE in project root. # @@ -56,7 +56,21 @@ def _buildStdBody(self, body, fields): body[f] = fields[f] def _make_url(self, path): - return self._config.getEndpoint() + path + if self._config['api_version_before_resource']: + return "%s/%s/%s" % ( + self._config.getEndpoint(), + self._config['api_version'], + path, + ) + + resource, sub_resource = path.split("/", 1) + + return "%s/%s/%s/%s" % ( + self._config.getEndpoint(), + resource, + self._config['api_version'], + sub_resource, + ) def _make_request(self, type, path, **kwargs): VERBS = ["GET", "POST", "DELETE", "PUT"] diff --git a/tests/unit/test_billing_usage.py b/tests/unit/test_billing_usage.py new file mode 100644 index 0000000..e7dc015 --- /dev/null +++ b/tests/unit/test_billing_usage.py @@ -0,0 +1,112 @@ +import pytest + +import ns1.rest.billing_usage +from ns1 import NS1 + +try: # Python 3.3 + + import unittest.mock as mock +except ImportError: + import mock + + +@pytest.fixture +def billing_usage_config(config): + config.loadFromDict( + { + "endpoint": "api.nsone.net", + "default_key": "test1", + "keys": { + "test1": { + "key": "key-1", + "desc": "test key number 1", + "writeLock": True, + } + }, + } + ) + + return config + + +@pytest.mark.parametrize("url", [("billing-usage/queries")]) +def test_rest_get_billing_usage_for_queries(billing_usage_config, url): + z = NS1(config=billing_usage_config).billing_usage() + z._make_request = mock.MagicMock() + z.getQueriesUsage(fromUnix=123, toUnix=456) + z._make_request.assert_called_once_with( + "GET", + url, + callback=None, + errback=None, + params={"from": 123, "to": 456}, + ) + + +@pytest.mark.parametrize("url", [("billing-usage/decisions")]) +def test_rest_get_billing_usage_for_decisions(billing_usage_config, url): + z = NS1(config=billing_usage_config).billing_usage() + z._make_request = mock.MagicMock() + z.getDecisionsUsage(fromUnix=123, toUnix=456) + z._make_request.assert_called_once_with( + "GET", + url, + callback=None, + errback=None, + params={"from": 123, "to": 456}, + ) + + +@pytest.mark.parametrize("url", [("billing-usage/records")]) +def test_rest_get_billing_usage_for_records(billing_usage_config, url): + z = NS1(config=billing_usage_config).billing_usage() + z._make_request = mock.MagicMock() + z.getRecordsUsage() + z._make_request.assert_called_once_with( + "GET", + url, + callback=None, + errback=None, + params={}, + ) + + +@pytest.mark.parametrize("url", [("billing-usage/filter-chains")]) +def test_rest_get_billing_usage_for_filter_chains(billing_usage_config, url): + z = NS1(config=billing_usage_config).billing_usage() + z._make_request = mock.MagicMock() + z.getFilterChainsUsage() + z._make_request.assert_called_once_with( + "GET", + url, + callback=None, + errback=None, + params={}, + ) + + +@pytest.mark.parametrize("url", [("billing-usage/monitors")]) +def test_rest_get_billing_usage_for_monitors(billing_usage_config, url): + z = NS1(config=billing_usage_config).billing_usage() + z._make_request = mock.MagicMock() + z.getMonitorsUsage() + z._make_request.assert_called_once_with( + "GET", + url, + callback=None, + errback=None, + params={}, + ) + + +@pytest.mark.parametrize("url", [("billing-usage/limits")]) +def test_rest_get_billing_usage_for_monitors(billing_usage_config, url): + z = NS1(config=billing_usage_config).billing_usage() + z._make_request = mock.MagicMock() + z.getLimits(fromUnix=123, toUnix=456) + z._make_request.assert_called_once_with( + "GET", + url, + callback=None, + errback=None, + params={"from": 123, "to": 456}, + ) diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py index 33bd821..cba3ca0 100644 --- a/tests/unit/test_config.py +++ b/tests/unit/test_config.py @@ -10,6 +10,7 @@ "endpoint": "api.nsone.net", "port": 443, "api_version": "v1", + "api_version_before_resource": True, "cli": {}, "ddi": False, "follow_pagination": False, @@ -81,5 +82,5 @@ def test_load_from_str(config): assert config.getCurrentKeyID() == key_cfg["default_key"] assert config["keys"] == key_cfg["keys"] assert config.getAPIKey() == key_cfg["keys"][key_cfg["default_key"]]["key"] - endpoint = "https://%s/v1/" % defaults["endpoint"] + endpoint = "https://%s" % defaults["endpoint"] assert config.getEndpoint() == endpoint From 03eedc8b6fd394e1f29e82430c44527e8fe66305 Mon Sep 17 00:00:00 2001 From: Cristian Pontes Date: Thu, 20 Mar 2025 11:24:12 +0000 Subject: [PATCH 02/11] PENG-6243 - Introduce support for billing-usage APIs --- .github/workflows/release.yml | 2 +- .github/workflows/verify.yml | 4 ++-- .gitignore | 4 ++++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9463969..c7c8c49 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,7 +11,7 @@ jobs: uses: actions/checkout@v2 - name: Set up Python 3.8 - uses: actions/setup-python@v1 + uses: actions/setup-python@v4 with: python-version: 3.8 diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 5078976..f54f046 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -9,7 +9,7 @@ jobs: steps: - uses: actions/checkout@v1 - name: Set up Python - uses: actions/setup-python@v1 + uses: actions/setup-python@v4 with: python-version: 3.7 - name: Install dependencies @@ -37,7 +37,7 @@ jobs: steps: - uses: actions/checkout@v1 - name: Set up Python - uses: actions/setup-python@v1 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/.gitignore b/.gitignore index 2b4554b..47c2f19 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,7 @@ target/ # Virtual environments venv/ + +# Editors +.vscode/ +.idea From 94fa1ccf9c1f7be2e4425896d8a482453497f1b9 Mon Sep 17 00:00:00 2001 From: Cristian Pontes Date: Thu, 20 Mar 2025 11:30:36 +0000 Subject: [PATCH 03/11] PENG-6243 - Introduce support for billing-usage APIs --- .github/workflows/verify.yml | 4 ++-- README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index f54f046..1dd8485 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -11,7 +11,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: 3.7 + python-version: 3.8 - name: Install dependencies run: | python setup.py install @@ -32,7 +32,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13', '3.14'] steps: - uses: actions/checkout@v1 diff --git a/README.md b/README.md index 3b6a52e..e40649f 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,8 @@ and includes both a simple NS1 REST API wrapper as well as a higher level interface for managing zones, records, data feeds, and more. It supports synchronous and asynchronous transports. -Both python 2.7 and 3.3+ are supported. Automated tests are currently run -against 2.7, 3.7, 3.8, 3.9 and 3.10. +Python 3.8+ is supported. Automated tests are currently run +against 3.8, 3.9, 3.10, 3.11, 3.12, 3.13 and 3.14. Installation ============ From 645cf979cdd52bddb6194e88ccec7684ee873a8a Mon Sep 17 00:00:00 2001 From: Cristian Pontes Date: Thu, 20 Mar 2025 11:34:40 +0000 Subject: [PATCH 04/11] PENG-6243 - Introduce support for billing-usage APIs --- .github/workflows/verify.yml | 2 +- README.md | 2 +- tests/unit/test_billing_usage.py | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 1dd8485..37bb6df 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -32,7 +32,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13', '3.14'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13'] steps: - uses: actions/checkout@v1 diff --git a/README.md b/README.md index e40649f..560b615 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ interface for managing zones, records, data feeds, and more. It supports synchronous and asynchronous transports. Python 3.8+ is supported. Automated tests are currently run -against 3.8, 3.9, 3.10, 3.11, 3.12, 3.13 and 3.14. +against 3.8, 3.9, 3.10, 3.11, 3.12 and 3.13 Installation ============ diff --git a/tests/unit/test_billing_usage.py b/tests/unit/test_billing_usage.py index e7dc015..f17ccde 100644 --- a/tests/unit/test_billing_usage.py +++ b/tests/unit/test_billing_usage.py @@ -1,6 +1,5 @@ import pytest -import ns1.rest.billing_usage from ns1 import NS1 try: # Python 3.3 + @@ -99,7 +98,7 @@ def test_rest_get_billing_usage_for_monitors(billing_usage_config, url): @pytest.mark.parametrize("url", [("billing-usage/limits")]) -def test_rest_get_billing_usage_for_monitors(billing_usage_config, url): +def test_rest_get_billing_usage_limits(billing_usage_config, url): z = NS1(config=billing_usage_config).billing_usage() z._make_request = mock.MagicMock() z.getLimits(fromUnix=123, toUnix=456) From 4681781dbc8afef2fa92e14003d7c714e619b6e1 Mon Sep 17 00:00:00 2001 From: Cristian Pontes Date: Thu, 20 Mar 2025 11:46:27 +0000 Subject: [PATCH 05/11] PENG-6243 - Introduce support for billing-usage APIs --- examples/billing_usage.py | 8 ++++++-- ns1/config.py | 4 +++- ns1/rest/billing_usage.py | 8 ++++---- ns1/rest/resource.py | 6 +++--- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/examples/billing_usage.py b/examples/billing_usage.py index 073c64e..c5dde36 100644 --- a/examples/billing_usage.py +++ b/examples/billing_usage.py @@ -31,7 +31,9 @@ # GET BILLING USAGE FOR QUERIES # ################################### -usg = api.billing_usage().getQueriesUsage(fromUnix=1738368000, toUnix=1740614400) +usg = api.billing_usage().getQueriesUsage( + fromUnix=1738368000, toUnix=1740614400 +) print("### QUERIES USAGE ###") print(usg) print("####################") @@ -40,7 +42,9 @@ # GET BILLING USAGE FOR DECISIONS # ################################### -usg = api.billing_usage().getDecisionsUsage(fromUnix=1738368000, toUnix=1740614400) +usg = api.billing_usage().getDecisionsUsage( + fromUnix=1738368000, toUnix=1740614400 +) print("### DECISIONS USAGE ###") print(usg) print("####################") diff --git a/ns1/config.py b/ns1/config.py index e7eed4a..c642774 100644 --- a/ns1/config.py +++ b/ns1/config.py @@ -77,7 +77,9 @@ def _doDefaults(self): self._data["api_version"] = self.API_VERSION if "api_version_before_resource" not in self._data: - self._data["api_version_before_resource"] = self.API_VERSION_BEFORE_RESOURCE + self._data[ + "api_version_before_resource" + ] = self.API_VERSION_BEFORE_RESOURCE if "cli" not in self._data: self._data["cli"] = {} diff --git a/ns1/rest/billing_usage.py b/ns1/rest/billing_usage.py index d6f8f26..16dfc23 100644 --- a/ns1/rest/billing_usage.py +++ b/ns1/rest/billing_usage.py @@ -12,7 +12,7 @@ class BillingUsage(resource.BaseResource): def __init__(self, config): config = copy.deepcopy(config) - config['api_version_before_resource'] = False + config["api_version_before_resource"] = False super(BillingUsage, self).__init__(config) def getQueriesUsage(self, fromUnix, toUnix, callback=None, errback=None): @@ -21,7 +21,7 @@ def getQueriesUsage(self, fromUnix, toUnix, callback=None, errback=None): "%s/queries" % self.ROOT, callback=callback, errback=errback, - params={'from': fromUnix, 'to': toUnix}, + params={"from": fromUnix, "to": toUnix}, ) def getDecisionsUsage(self, fromUnix, toUnix, callback=None, errback=None): @@ -30,7 +30,7 @@ def getDecisionsUsage(self, fromUnix, toUnix, callback=None, errback=None): "%s/decisions" % self.ROOT, callback=callback, errback=errback, - params={'from': fromUnix, 'to': toUnix}, + params={"from": fromUnix, "to": toUnix}, ) def getRecordsUsage(self, callback=None, errback=None): @@ -66,5 +66,5 @@ def getLimits(self, fromUnix, toUnix, callback=None, errback=None): "%s/limits" % self.ROOT, callback=callback, errback=errback, - params={'from': fromUnix, 'to': toUnix}, + params={"from": fromUnix, "to": toUnix}, ) diff --git a/ns1/rest/resource.py b/ns1/rest/resource.py index 006e812..73fec3b 100644 --- a/ns1/rest/resource.py +++ b/ns1/rest/resource.py @@ -56,10 +56,10 @@ def _buildStdBody(self, body, fields): body[f] = fields[f] def _make_url(self, path): - if self._config['api_version_before_resource']: + if self._config["api_version_before_resource"]: return "%s/%s/%s" % ( self._config.getEndpoint(), - self._config['api_version'], + self._config["api_version"], path, ) @@ -68,7 +68,7 @@ def _make_url(self, path): return "%s/%s/%s/%s" % ( self._config.getEndpoint(), resource, - self._config['api_version'], + self._config["api_version"], sub_resource, ) From 3302963b13bab91fdada09807c4bf7a329287528 Mon Sep 17 00:00:00 2001 From: Cristian Pontes Date: Thu, 20 Mar 2025 11:53:31 +0000 Subject: [PATCH 06/11] PENG-6243 - Introduce support for billing-usage APIs --- ns1/config.py | 7 +++---- ns1/monitoring.py | 1 - ns1/records.py | 1 - ns1/rest/transport/twisted.py | 6 +++--- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/ns1/config.py b/ns1/config.py index c642774..a390d3c 100644 --- a/ns1/config.py +++ b/ns1/config.py @@ -32,7 +32,6 @@ class ConfigException(Exception): class Config: - """A simple object for accessing and manipulating config files. These contains options and credentials for accessing the NS1 REST API. Config files are simple JSON text files. @@ -77,9 +76,9 @@ def _doDefaults(self): self._data["api_version"] = self.API_VERSION if "api_version_before_resource" not in self._data: - self._data[ - "api_version_before_resource" - ] = self.API_VERSION_BEFORE_RESOURCE + self._data["api_version_before_resource"] = ( + self.API_VERSION_BEFORE_RESOURCE + ) if "cli" not in self._data: self._data["cli"] = {} diff --git a/ns1/monitoring.py b/ns1/monitoring.py index afddc82..6553591 100644 --- a/ns1/monitoring.py +++ b/ns1/monitoring.py @@ -12,7 +12,6 @@ class MonitorException(Exception): class Monitor(object): - """ High level object representing a Monitor """ diff --git a/ns1/records.py b/ns1/records.py index 346f9e9..9862b16 100644 --- a/ns1/records.py +++ b/ns1/records.py @@ -13,7 +13,6 @@ class RecordException(Exception): class Record(object): - """ High level object representing a Record """ diff --git a/ns1/rest/transport/twisted.py b/ns1/rest/transport/twisted.py index d4c8c78..2ff8d92 100644 --- a/ns1/rest/transport/twisted.py +++ b/ns1/rest/transport/twisted.py @@ -247,9 +247,9 @@ def _request_func(self, method, headers, data, files): if headers is None: headers = {} - headers[ - "Content-Type" - ] = "multipart/form-data; boundary={}".format(boundary) + headers["Content-Type"] = ( + "multipart/form-data; boundary={}".format(boundary) + ) bProducer = FileBodyProducer(StringIO.StringIO(body)) theaders = ( From d75c82f4055bd7c188fcafba870d84197367a1a4 Mon Sep 17 00:00:00 2001 From: Cristian Pontes Date: Thu, 20 Mar 2025 14:57:30 +0000 Subject: [PATCH 07/11] PENG-6243 - Introduce support for billing-usage APIs --- examples/billing_usage.py | 14 +++++++------- ns1/config.py | 2 +- ns1/rest/billing_usage.py | 24 ++++++++++++------------ ns1/rest/resource.py | 13 ++----------- tests/unit/test_billing_usage.py | 6 +----- tests/unit/test_config.py | 2 +- 6 files changed, 24 insertions(+), 37 deletions(-) diff --git a/examples/billing_usage.py b/examples/billing_usage.py index c5dde36..60cd8c7 100644 --- a/examples/billing_usage.py +++ b/examples/billing_usage.py @@ -5,6 +5,7 @@ # from ns1 import NS1 +import datetime # NS1 will use config in ~/.nsone by default api = NS1() @@ -18,11 +19,14 @@ config = api.config +from_unix = int(datetime.datetime.fromisoformat("2025-02-01 00:00:00").strftime("%s")) +to_unix = int(datetime.datetime.fromisoformat("2025-02-28 23:59:59").strftime("%s")) + ############################ # GET BILLING USAGE LIMITS # ############################ -limits = api.billing_usage().getLimits(fromUnix=1738368000, toUnix=1740614400) +limits = api.billing_usage().getLimits(from_unix, to_unix) print("### USAGE LIMITS ###") print(limits) print("####################") @@ -31,9 +35,7 @@ # GET BILLING USAGE FOR QUERIES # ################################### -usg = api.billing_usage().getQueriesUsage( - fromUnix=1738368000, toUnix=1740614400 -) +usg = api.billing_usage().getQueriesUsage(from_unix, to_unix) print("### QUERIES USAGE ###") print(usg) print("####################") @@ -42,9 +44,7 @@ # GET BILLING USAGE FOR DECISIONS # ################################### -usg = api.billing_usage().getDecisionsUsage( - fromUnix=1738368000, toUnix=1740614400 -) +usg = api.billing_usage().getDecisionsUsage(from_unix, to_unix) print("### DECISIONS USAGE ###") print(usg) print("####################") diff --git a/ns1/config.py b/ns1/config.py index a390d3c..303b3aa 100644 --- a/ns1/config.py +++ b/ns1/config.py @@ -251,7 +251,7 @@ def getEndpoint(self): else: endpoint = self._data["endpoint"] - return "https://%s%s" % (endpoint, port) + return f"https://{endpoint}{port}" def getRateLimitingFunc(self): """ diff --git a/ns1/rest/billing_usage.py b/ns1/rest/billing_usage.py index 16dfc23..7574f45 100644 --- a/ns1/rest/billing_usage.py +++ b/ns1/rest/billing_usage.py @@ -15,28 +15,28 @@ def __init__(self, config): config["api_version_before_resource"] = False super(BillingUsage, self).__init__(config) - def getQueriesUsage(self, fromUnix, toUnix, callback=None, errback=None): + def getQueriesUsage(self, from_unix, to_unix, callback=None, errback=None): return self._make_request( "GET", - "%s/queries" % self.ROOT, + f"{self.ROOT}/queries", callback=callback, errback=errback, - params={"from": fromUnix, "to": toUnix}, + params={"from": from_unix, "to": to_unix}, ) - def getDecisionsUsage(self, fromUnix, toUnix, callback=None, errback=None): + def getDecisionsUsage(self, from_unix, to_unix, callback=None, errback=None): return self._make_request( "GET", - "%s/decisions" % self.ROOT, + f"{self.ROOT}/decisions", callback=callback, errback=errback, - params={"from": fromUnix, "to": toUnix}, + params={"from": from_unix, "to": to_unix}, ) def getRecordsUsage(self, callback=None, errback=None): return self._make_request( "GET", - "%s/records" % self.ROOT, + f"{self.ROOT}/records", callback=callback, errback=errback, params={}, @@ -45,7 +45,7 @@ def getRecordsUsage(self, callback=None, errback=None): def getMonitorsUsage(self, callback=None, errback=None): return self._make_request( "GET", - "%s/monitors" % self.ROOT, + f"{self.ROOT}/monitors", callback=callback, errback=errback, params={}, @@ -54,17 +54,17 @@ def getMonitorsUsage(self, callback=None, errback=None): def getFilterChainsUsage(self, callback=None, errback=None): return self._make_request( "GET", - "%s/filter-chains" % self.ROOT, + f"{self.ROOT}/filter-chains", callback=callback, errback=errback, params={}, ) - def getLimits(self, fromUnix, toUnix, callback=None, errback=None): + def getLimits(self, from_unix, to_unix, callback=None, errback=None): return self._make_request( "GET", - "%s/limits" % self.ROOT, + f"{self.ROOT}/limits", callback=callback, errback=errback, - params={"from": fromUnix, "to": toUnix}, + params={"from": from_unix, "to": to_unix}, ) diff --git a/ns1/rest/resource.py b/ns1/rest/resource.py index 73fec3b..c50bd28 100644 --- a/ns1/rest/resource.py +++ b/ns1/rest/resource.py @@ -57,20 +57,11 @@ def _buildStdBody(self, body, fields): def _make_url(self, path): if self._config["api_version_before_resource"]: - return "%s/%s/%s" % ( - self._config.getEndpoint(), - self._config["api_version"], - path, - ) + return f"{self._config.getEndpoint()}/{self._config['api_version']}/{path}" resource, sub_resource = path.split("/", 1) - return "%s/%s/%s/%s" % ( - self._config.getEndpoint(), - resource, - self._config["api_version"], - sub_resource, - ) + return f"{self._config.getEndpoint()}/{resource}/{self._config['api_version']}/{sub_resource}" def _make_request(self, type, path, **kwargs): VERBS = ["GET", "POST", "DELETE", "PUT"] diff --git a/tests/unit/test_billing_usage.py b/tests/unit/test_billing_usage.py index f17ccde..95c0fe8 100644 --- a/tests/unit/test_billing_usage.py +++ b/tests/unit/test_billing_usage.py @@ -2,11 +2,7 @@ from ns1 import NS1 -try: # Python 3.3 + - import unittest.mock as mock -except ImportError: - import mock - +import unittest.mock as mock @pytest.fixture def billing_usage_config(config): diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py index cba3ca0..1c9eebc 100644 --- a/tests/unit/test_config.py +++ b/tests/unit/test_config.py @@ -82,5 +82,5 @@ def test_load_from_str(config): assert config.getCurrentKeyID() == key_cfg["default_key"] assert config["keys"] == key_cfg["keys"] assert config.getAPIKey() == key_cfg["keys"][key_cfg["default_key"]]["key"] - endpoint = "https://%s" % defaults["endpoint"] + endpoint = f'https://{defaults["endpoint"]}' assert config.getEndpoint() == endpoint From 15a57026bd98c321c8acf5d8e51991bfb3f09b54 Mon Sep 17 00:00:00 2001 From: Cristian Pontes Date: Thu, 20 Mar 2025 14:58:43 +0000 Subject: [PATCH 08/11] PENG-6243 - Introduce support for billing-usage APIs --- tests/unit/test_billing_usage.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/test_billing_usage.py b/tests/unit/test_billing_usage.py index 95c0fe8..3edbb4a 100644 --- a/tests/unit/test_billing_usage.py +++ b/tests/unit/test_billing_usage.py @@ -4,6 +4,7 @@ import unittest.mock as mock + @pytest.fixture def billing_usage_config(config): config.loadFromDict( From 18a368a57ca0967ebb50406144d70b4864cf8ed5 Mon Sep 17 00:00:00 2001 From: Cristian Pontes Date: Thu, 20 Mar 2025 15:00:32 +0000 Subject: [PATCH 09/11] PENG-6243 - Introduce support for billing-usage APIs --- tests/unit/test_billing_usage.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/unit/test_billing_usage.py b/tests/unit/test_billing_usage.py index 3edbb4a..f784b01 100644 --- a/tests/unit/test_billing_usage.py +++ b/tests/unit/test_billing_usage.py @@ -24,7 +24,7 @@ def billing_usage_config(config): return config -@pytest.mark.parametrize("url", [("billing-usage/queries")]) +@pytest.mark.parametrize("url", ["billing-usage/queries"]) def test_rest_get_billing_usage_for_queries(billing_usage_config, url): z = NS1(config=billing_usage_config).billing_usage() z._make_request = mock.MagicMock() @@ -38,7 +38,7 @@ def test_rest_get_billing_usage_for_queries(billing_usage_config, url): ) -@pytest.mark.parametrize("url", [("billing-usage/decisions")]) +@pytest.mark.parametrize("url", ["billing-usage/decisions"]) def test_rest_get_billing_usage_for_decisions(billing_usage_config, url): z = NS1(config=billing_usage_config).billing_usage() z._make_request = mock.MagicMock() @@ -52,7 +52,7 @@ def test_rest_get_billing_usage_for_decisions(billing_usage_config, url): ) -@pytest.mark.parametrize("url", [("billing-usage/records")]) +@pytest.mark.parametrize("url", ["billing-usage/records"]) def test_rest_get_billing_usage_for_records(billing_usage_config, url): z = NS1(config=billing_usage_config).billing_usage() z._make_request = mock.MagicMock() @@ -66,7 +66,7 @@ def test_rest_get_billing_usage_for_records(billing_usage_config, url): ) -@pytest.mark.parametrize("url", [("billing-usage/filter-chains")]) +@pytest.mark.parametrize("url", ["billing-usage/filter-chains"]) def test_rest_get_billing_usage_for_filter_chains(billing_usage_config, url): z = NS1(config=billing_usage_config).billing_usage() z._make_request = mock.MagicMock() @@ -80,7 +80,7 @@ def test_rest_get_billing_usage_for_filter_chains(billing_usage_config, url): ) -@pytest.mark.parametrize("url", [("billing-usage/monitors")]) +@pytest.mark.parametrize("url", ["billing-usage/monitors"]) def test_rest_get_billing_usage_for_monitors(billing_usage_config, url): z = NS1(config=billing_usage_config).billing_usage() z._make_request = mock.MagicMock() @@ -94,7 +94,7 @@ def test_rest_get_billing_usage_for_monitors(billing_usage_config, url): ) -@pytest.mark.parametrize("url", [("billing-usage/limits")]) +@pytest.mark.parametrize("url", ["billing-usage/limits"]) def test_rest_get_billing_usage_limits(billing_usage_config, url): z = NS1(config=billing_usage_config).billing_usage() z._make_request = mock.MagicMock() From 828e29f49df95aee6ec351f7d165c88bba802ad0 Mon Sep 17 00:00:00 2001 From: Cristian Pontes Date: Thu, 20 Mar 2025 15:02:47 +0000 Subject: [PATCH 10/11] PENG-6243 - Introduce support for billing-usage APIs --- examples/billing_usage.py | 8 ++++++-- ns1/rest/billing_usage.py | 4 +++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/examples/billing_usage.py b/examples/billing_usage.py index 60cd8c7..71fcad7 100644 --- a/examples/billing_usage.py +++ b/examples/billing_usage.py @@ -19,8 +19,12 @@ config = api.config -from_unix = int(datetime.datetime.fromisoformat("2025-02-01 00:00:00").strftime("%s")) -to_unix = int(datetime.datetime.fromisoformat("2025-02-28 23:59:59").strftime("%s")) +from_unix = int( + datetime.datetime.fromisoformat("2025-02-01 00:00:00").strftime("%s") +) +to_unix = int( + datetime.datetime.fromisoformat("2025-02-28 23:59:59").strftime("%s") +) ############################ # GET BILLING USAGE LIMITS # diff --git a/ns1/rest/billing_usage.py b/ns1/rest/billing_usage.py index 7574f45..8efb11b 100644 --- a/ns1/rest/billing_usage.py +++ b/ns1/rest/billing_usage.py @@ -24,7 +24,9 @@ def getQueriesUsage(self, from_unix, to_unix, callback=None, errback=None): params={"from": from_unix, "to": to_unix}, ) - def getDecisionsUsage(self, from_unix, to_unix, callback=None, errback=None): + def getDecisionsUsage( + self, from_unix, to_unix, callback=None, errback=None + ): return self._make_request( "GET", f"{self.ROOT}/decisions", From 8849e58dc8d368b73625f6b5f8ee1af5fbb2b674 Mon Sep 17 00:00:00 2001 From: Cristian Pontes Date: Thu, 20 Mar 2025 15:05:13 +0000 Subject: [PATCH 11/11] PENG-6243 - Introduce support for billing-usage APIs --- tests/unit/test_billing_usage.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unit/test_billing_usage.py b/tests/unit/test_billing_usage.py index f784b01..cd443d8 100644 --- a/tests/unit/test_billing_usage.py +++ b/tests/unit/test_billing_usage.py @@ -28,7 +28,7 @@ def billing_usage_config(config): def test_rest_get_billing_usage_for_queries(billing_usage_config, url): z = NS1(config=billing_usage_config).billing_usage() z._make_request = mock.MagicMock() - z.getQueriesUsage(fromUnix=123, toUnix=456) + z.getQueriesUsage(from_unix=123, to_unix=456) z._make_request.assert_called_once_with( "GET", url, @@ -42,7 +42,7 @@ def test_rest_get_billing_usage_for_queries(billing_usage_config, url): def test_rest_get_billing_usage_for_decisions(billing_usage_config, url): z = NS1(config=billing_usage_config).billing_usage() z._make_request = mock.MagicMock() - z.getDecisionsUsage(fromUnix=123, toUnix=456) + z.getDecisionsUsage(from_unix=123, to_unix=456) z._make_request.assert_called_once_with( "GET", url, @@ -98,7 +98,7 @@ def test_rest_get_billing_usage_for_monitors(billing_usage_config, url): def test_rest_get_billing_usage_limits(billing_usage_config, url): z = NS1(config=billing_usage_config).billing_usage() z._make_request = mock.MagicMock() - z.getLimits(fromUnix=123, toUnix=456) + z.getLimits(from_unix=123, to_unix=456) z._make_request.assert_called_once_with( "GET", url,