Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PENG-6243 - Introduce support for billing-usage APIs #134

Merged
merged 11 commits into from
Mar 20, 2025
Merged
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
uses: actions/checkout@v2
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The v1 version doesn't support python 3.8 anymore it seems. Updating it to v4 solves the issue

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like all of our GitHub actions should be tracked with Dependabot. Probably our other dependencies too, since they all seem to be development or build related?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe yeah


- name: Set up Python 3.8
uses: actions/setup-python@v1
uses: actions/setup-python@v4
with:
python-version: 3.8

Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ 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
python-version: 3.8
- name: Install dependencies
run: |
python setup.py install
Expand All @@ -32,12 +32,12 @@ jobs:
strategy:
matrix:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see this versions, these are the ones we're actually testing against. Because of this, I'm updating the readme

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Python 3.8 was EOL almost 6 months ago:

https://devguide.python.org/versions/

I guess it's okay to continue to support it, although not too strenuously. 😉

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, thought about just doing 3.9 onwards. Let me know if you want me to do it or if we're happy to support 3.8 for now

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine, although if any of our CI tools stop working then we should happily drop 3.8 support!

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']

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
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,7 @@ target/

# Virtual environments
venv/

# Editors
.vscode/
.idea
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.24.0 (March 20th, 2025)

ENHANCEMENTS:
* Adds support for BillingUsage

## 0.23.0 (Dec 9th, 2024)

ENHANCEMENTS:
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updating based on my comment above

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 and 3.13

Installation
============
Expand Down
81 changes: 81 additions & 0 deletions examples/billing_usage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#
# Copyright (c) 2025 NSONE, Inc.
#
# License under The MIT License (MIT). See LICENSE in project root.
#

from ns1 import NS1
import datetime

# 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('<<CLEARTEXT API KEY>>')
# api = NS1(config=config)

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(from_unix, to_unix)
print("### USAGE LIMITS ###")
print(limits)
print("####################")

###################################
# GET BILLING USAGE FOR QUERIES #
###################################

usg = api.billing_usage().getQueriesUsage(from_unix, to_unix)
print("### QUERIES USAGE ###")
print(usg)
print("####################")

###################################
# GET BILLING USAGE FOR DECISIONS #
###################################

usg = api.billing_usage().getDecisionsUsage(from_unix, to_unix)
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("####################")
13 changes: 11 additions & 2 deletions ns1/__init__.py
Original file line number Diff line number Diff line change
@@ -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:
Expand Down Expand Up @@ -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):
"""
Expand Down
14 changes: 11 additions & 3 deletions ns1/config.py
Original file line number Diff line number Diff line change
@@ -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.
#
Expand Down Expand Up @@ -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.
Expand All @@ -45,6 +44,10 @@ class Config:

API_VERSION = "v1"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First time we're adding support for endpoint under the new schema, so I had to come up with a solution

https://api.nsone.net/v1/zones vs https://api.nsone.net/zones/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):
Expand Down Expand Up @@ -72,6 +75,11 @@ 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"] = {}

Expand Down Expand Up @@ -243,7 +251,7 @@ def getEndpoint(self):
else:
endpoint = self._data["endpoint"]

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

def getRateLimitingFunc(self):
"""
Expand Down
1 change: 0 additions & 1 deletion ns1/monitoring.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ class MonitorException(Exception):


class Monitor(object):

"""
High level object representing a Monitor
"""
Expand Down
1 change: 0 additions & 1 deletion ns1/records.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ class RecordException(Exception):


class Record(object):

"""
High level object representing a Record
"""
Expand Down
72 changes: 72 additions & 0 deletions ns1/rest/billing_usage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#
# 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, from_unix, to_unix, callback=None, errback=None):
return self._make_request(
"GET",
f"{self.ROOT}/queries",
callback=callback,
errback=errback,
params={"from": from_unix, "to": to_unix},
)

def getDecisionsUsage(
self, from_unix, to_unix, callback=None, errback=None
):
return self._make_request(
"GET",
f"{self.ROOT}/decisions",
callback=callback,
errback=errback,
params={"from": from_unix, "to": to_unix},
)

def getRecordsUsage(self, callback=None, errback=None):
return self._make_request(
"GET",
f"{self.ROOT}/records",
callback=callback,
errback=errback,
params={},
)

def getMonitorsUsage(self, callback=None, errback=None):
return self._make_request(
"GET",
f"{self.ROOT}/monitors",
callback=callback,
errback=errback,
params={},
)

def getFilterChainsUsage(self, callback=None, errback=None):
return self._make_request(
"GET",
f"{self.ROOT}/filter-chains",
callback=callback,
errback=errback,
params={},
)

def getLimits(self, from_unix, to_unix, callback=None, errback=None):
return self._make_request(
"GET",
f"{self.ROOT}/limits",
callback=callback,
errback=errback,
params={"from": from_unix, "to": to_unix},
)
9 changes: 7 additions & 2 deletions ns1/rest/resource.py
Original file line number Diff line number Diff line change
@@ -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.
#
Expand Down Expand Up @@ -56,7 +56,12 @@ 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 f"{self._config.getEndpoint()}/{self._config['api_version']}/{path}"

resource, sub_resource = path.split("/", 1)

return f"{self._config.getEndpoint()}/{resource}/{self._config['api_version']}/{sub_resource}"

def _make_request(self, type, path, **kwargs):
VERBS = ["GET", "POST", "DELETE", "PUT"]
Expand Down
6 changes: 3 additions & 3 deletions ns1/rest/transport/twisted.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,9 +247,9 @@ def _request_func(self, method, headers, data, files):

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Formatting highlighted by the linter

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 = (
Expand Down
Loading