diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d74559f..fb7de62 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- - uses: actions/setup-python@v4
+ - uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- run: pip install poetry==${{ env.POETRY_VERSION }}
@@ -53,7 +53,7 @@ jobs:
org_id: TESTING_STAGING_ORG_ID
steps:
- uses: actions/checkout@v4
- - uses: actions/setup-python@v4
+ - uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
- run: pip install poetry==${{ env.POETRY_VERSION }}
@@ -72,7 +72,7 @@ jobs:
- test-integration
steps:
- uses: actions/checkout@v4
- - uses: actions/setup-python@v4
+ - uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- run: pip install poetry==${{ env.POETRY_VERSION }}
diff --git a/README.md b/README.md
index e1ef381..14446e0 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,19 @@
-![axiom-py: The official Python bindings for the Axiom API](.github/images/banner-dark.svg#gh-dark-mode-only)
-![axiom-py: The official Python bindings for the Axiom API](.github/images/banner-light.svg#gh-light-mode-only)
-
-
+# axiom-py
+
+
+
+
+
[![CI][ci_badge]][ci]
[![PyPI version][pypi_badge]][pypi]
[![Python version][version_badge]][pypi]
-
-
[Axiom](https://axiom.co) unlocks observability at any scale.
- **Ingest with ease, store without limits:** Axiom’s next-generation datastore enables ingesting petabytes of data with ultimate efficiency. Ship logs from Kubernetes, AWS, Azure, Google Cloud, DigitalOcean, Nomad, and others.
diff --git a/axiom/client.py b/axiom/client.py
index 6dfa59e..5897aea 100644
--- a/axiom/client.py
+++ b/axiom/client.py
@@ -10,7 +10,7 @@
from .util import Util
from enum import Enum
from humps import decamelize
-from typing import Optional, List, Dict, Any
+from typing import Optional, List, Dict
from logging import getLogger
from dataclasses import asdict, dataclass, field
from datetime import datetime
@@ -100,11 +100,18 @@ class WrongQueryKindException(Exception):
class AplOptions:
"""AplOptions specifies the optional parameters for the apl query method."""
+ # Start time for the interval to query.
start_time: Optional[datetime] = field(default=None)
+ # End time for the interval to query.
end_time: Optional[datetime] = field(default=None)
- no_cache: bool = field(default=False)
- save: bool = field(default=False)
+ # The result format.
format: AplResultFormat = field(default=AplResultFormat.Legacy)
+ # Cursor is the query cursor. It should be set to the Cursor returned with
+ # a previous query result if it was partial.
+ cursor: Optional[str] = field(default=None)
+ # IncludeCursor will return the Cursor as part of the query result, if set
+ # to true.
+ includeCursor: bool = field(default=False)
def raise_response_error(r):
@@ -273,11 +280,8 @@ def query(self, apl: str, opts: Optional[AplOptions] = None) -> QueryResult:
def create_api_token(self, opts: TokenAttributes) -> Token:
"""Creates a new API token with permissions specified in a TokenAttributes object."""
res = self.session.post(
- '/v2/tokens',
- data=ujson.dumps(
- asdict(opts),
- default=Util.handle_json_serialization
- )
+ "/v2/tokens",
+ data=ujson.dumps(asdict(opts), default=Util.handle_json_serialization),
)
# Return the new token and ID.
@@ -286,9 +290,9 @@ def create_api_token(self, opts: TokenAttributes) -> Token:
def delete_api_token(self, token_id: str) -> None:
"""Delete an API token using its ID string."""
- self.session.delete(f'/v2/tokens/{token_id}')
+ self.session.delete(f"/v2/tokens/{token_id}")
- def _prepare_query_options(self, opts: QueryOptions) -> Dict[str, Any]:
+ def _prepare_query_options(self, opts: QueryOptions) -> Dict[str, object]:
"""returns the query options as a Dict, handles any renaming for key fields."""
if opts is None:
return {}
@@ -304,7 +308,9 @@ def _prepare_query_options(self, opts: QueryOptions) -> Dict[str, Any]:
return params
- def _prepare_ingest_options(self, opts: Optional[IngestOptions]) -> Dict[str, Any]:
+ def _prepare_ingest_options(
+ self, opts: Optional[IngestOptions]
+ ) -> Dict[str, object]:
"""the query params for ingest api are expected in a format
that couldn't be defined as a variable name because it has a dash.
As a work around, we create the params dict manually."""
@@ -322,34 +328,31 @@ def _prepare_ingest_options(self, opts: Optional[IngestOptions]) -> Dict[str, An
return params
- def _prepare_apl_options(self, opts: Optional[AplOptions]) -> Dict[str, Any]:
+ def _prepare_apl_options(self, opts: Optional[AplOptions]) -> Dict[str, object]:
"""Prepare the apl query options for the request."""
- params = {}
+ params = {"format": AplResultFormat.Legacy.value}
- if opts is None:
- params["format"] = AplResultFormat.Legacy.value
- return params
-
- if opts.no_cache:
- params["nocache"] = opts.no_cache.__str__()
- if opts.save:
- params["save"] = opts.save
- if opts.format:
- params["format"] = opts.format.value
+ if opts is not None:
+ if opts.format:
+ params["format"] = opts.format.value
return params
def _prepare_apl_payload(
self, apl: str, opts: Optional[AplOptions]
- ) -> Dict[str, Any]:
+ ) -> Dict[str, object]:
"""Prepare the apl query options for the request."""
params = {}
params["apl"] = apl
if opts is not None:
- if opts.start_time:
+ if opts.start_time is not None:
params["startTime"] = opts.start_time
- if opts.end_time:
+ if opts.end_time is not None:
params["endTime"] = opts.end_time
+ if opts.cursor is not None:
+ params["cursor"] = opts.cursor
+ if opts.includeCursor:
+ params["includeCursor"] = opts.includeCursor
return params
diff --git a/axiom/query/result.py b/axiom/query/result.py
index 3917230..95a585c 100644
--- a/axiom/query/result.py
+++ b/axiom/query/result.py
@@ -1,6 +1,6 @@
from dataclasses import dataclass, field
from datetime import datetime, timedelta
-from typing import List, Dict, Any, Optional
+from typing import List, Dict, Optional
from enum import Enum
from .query import QueryLegacy
@@ -87,7 +87,7 @@ class Entry:
_rowId: str
# contains the raw data of the event (with filters and aggregations
# applied).
- data: Dict[str, Any]
+ data: Dict[str, object]
@dataclass
@@ -96,7 +96,7 @@ class EntryGroupAgg:
# alias is the aggregations alias. If it wasn't specified at query time, it
# is the uppercased string representation of the aggregation operation.
- value: Any
+ value: object
op: str = field(default="")
# value is the result value of the aggregation.
@@ -108,7 +108,7 @@ class EntryGroup:
# the unique id of the group.
id: int
# group maps the fieldnames to the unique values for the entry.
- group: Dict[str, Any]
+ group: Dict[str, object]
# aggregations of the group.
aggregations: List[EntryGroupAgg]
diff --git a/axiom/tokens.py b/axiom/tokens.py
index 4ab8369..fc8fdf3 100644
--- a/axiom/tokens.py
+++ b/axiom/tokens.py
@@ -1,9 +1,10 @@
-from dataclasses import dataclass
-from typing import Literal
+from dataclasses import dataclass, field
+from typing import Literal, Optional
@dataclass
class TokenDatasetCapabilities:
+ # pylint: disable=unsubscriptable-object
"""
TokenDatasetCapabilities describes the dataset-level permissions
which a token can be assigned.
@@ -12,52 +13,80 @@ class TokenDatasetCapabilities:
"""
# Ability to ingest data. Optional.
- ingest: list[Literal["create"]] | None = None
+ ingest: Optional[list[Literal["create"]]] = field(default=None)
# Ability to query data. Optional.
- query: list[Literal["read"]] | None = None
+ query: Optional[list[Literal["read"]]] = field(default=None)
# Ability to use starred queries. Optional.
- starredQueries: list[Literal["create", "read", "update", "delete"]] | None = None
+ starredQueries: Optional[list[Literal["create", "read", "update", "delete"]]] = (
+ field(default=None)
+ )
# Ability to use virtual fields. Optional.
- virtualFields: list[Literal["create", "read", "update", "delete"]] | None = None
+ virtualFields: Optional[list[Literal["create", "read", "update", "delete"]]] = (
+ field(default=None)
+ )
@dataclass
class TokenOrganizationCapabilities:
+ # pylint: disable=unsubscriptable-object
"""
TokenOrganizationCapabilities describes the org-level permissions
which a token can be assigned.
"""
# Ability to use annotations. Optional.
- annotations: list[Literal["create", "read", "update", "delete"]] | None = None
+ annotations: Optional[list[Literal["create", "read", "update", "delete"]]] = field(
+ default=None
+ )
# Ability to use api tokens. Optional.
- apiTokens: list[Literal["create", "read", "update", "delete"]] | None = None
+ apiTokens: Optional[list[Literal["create", "read", "update", "delete"]]] = field(
+ default=None
+ )
# Ability to access billing. Optional.
- billing: list[Literal["read", "update"]] | None = None
+ billing: Optional[list[Literal["read", "update"]]] = field(default=None)
# Ability to use dashboards. Optional.
- dashboards: list[Literal["create", "read", "update", "delete"]] | None = None
+ dashboards: Optional[list[Literal["create", "read", "update", "delete"]]] = field(
+ default=None
+ )
# Ability to use datasets. Optional.
- datasets: list[Literal["create", "read", "update", "delete"]] | None = None
+ datasets: Optional[list[Literal["create", "read", "update", "delete"]]] = field(
+ default=None
+ )
# Ability to use endpoints. Optional.
- endpoints: list[Literal["create", "read", "update", "delete"]] | None = None
+ endpoints: Optional[list[Literal["create", "read", "update", "delete"]]] = field(
+ default=None
+ )
# Ability to use flows. Optional.
- flows: list[Literal["create", "read", "update", "delete"]] | None = None
+ flows: Optional[list[Literal["create", "read", "update", "delete"]]] = field(
+ default=None
+ )
# Ability to use integrations. Optional.
- integrations: list[Literal["create", "read", "update", "delete"]] | None = None
+ integrations: Optional[list[Literal["create", "read", "update", "delete"]]] = field(
+ default=None
+ )
# Ability to use monitors. Optional.
- monitors: list[Literal["create", "read", "update", "delete"]] | None = None
+ monitors: Optional[list[Literal["create", "read", "update", "delete"]]] = field(
+ default=None
+ )
# Ability to use notifiers. Optional.
- notifiers: list[Literal["create", "read", "update", "delete"]] | None = None
+ notifiers: Optional[list[Literal["create", "read", "update", "delete"]]] = field(
+ default=None
+ )
# Ability to use role-based access controls. Optional.
- rbac: list[Literal["create", "read", "update", "delete"]] | None = None
+ rbac: Optional[list[Literal["create", "read", "update", "delete"]]] = field(
+ default=None
+ )
# Ability to use shared access keys. Optional.
- sharedAccessKeys: list[Literal["read", "update"]] | None = None
+ sharedAccessKeys: Optional[list[Literal["read", "update"]]] = field(default=None)
# Ability to use users. Optional.
- users: list[Literal["create", "read", "update", "delete"]] | None = None
+ users: Optional[list[Literal["create", "read", "update", "delete"]]] = field(
+ default=None
+ )
@dataclass
class TokenAttributes:
+ # pylint: disable=unsubscriptable-object
"""
TokenAttributes describes the set of input parameters that the
POST /tokens API accepts.
@@ -66,13 +95,15 @@ class TokenAttributes:
# Name for the token. Required.
name: str
# The token's dataset-level capabilities. Keyed on dataset name. Optional.
- datasetCapabilities: dict[str, TokenDatasetCapabilities] | None = None
+ datasetCapabilities: Optional[dict[str, TokenDatasetCapabilities]] = field(
+ default=None
+ )
# Description for the API token. Optional.
- description: str | None = None
+ description: Optional[str] = field(default=None)
# Expiration date for the API token. Optional.
- expiresAt: str | None = None
+ expiresAt: Optional[str] = field(default=None)
# The token's organization-level capabilities. Optional.
- orgCapabilities: TokenOrganizationCapabilities | None = None
+ orgCapabilities: Optional[TokenOrganizationCapabilities] = field(default=None)
@dataclass
@@ -81,5 +112,6 @@ class Token:
Token contains the response from a call to POST /tokens.
It includes the API token itself, and an ID which can be used to reference it later.
"""
+
id: str
token: str
diff --git a/pyproject.toml b/pyproject.toml
index 8c5c540..e1000fe 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "axiom-py"
-version = "0.4.0"
+version = "0.5.0"
description = "Axiom API Python bindings."
authors = ["Axiom, Inc."]
license = "MIT"
@@ -22,10 +22,10 @@ rfc3339 = "^6.2"
iso8601 = ">=1.0.2,<3.0.0"
[tool.poetry.dev-dependencies]
-black = "^23.3.0"
-pytest = "^7.3.2"
-pylint = "^2.7.2"
-responses = "^0.23.1"
+black = "^24.4.2"
+pytest = "^8.2.1"
+pylint = "^3.2.1"
+responses = "^0.25.0"
[build-system]
requires = ["poetry-core>=1.0.0"]
diff --git a/tests/test_annotations.py b/tests/test_annotations.py
index ed23490..e658372 100644
--- a/tests/test_annotations.py
+++ b/tests/test_annotations.py
@@ -3,7 +3,7 @@
import os
import unittest
-from typing import List, Dict, Any, Optional
+from typing import List, Dict, Optional
from logging import getLogger
from requests.exceptions import HTTPError
from datetime import timedelta
diff --git a/tests/test_client.py b/tests/test_client.py
index d2869d7..42dfd96 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -23,7 +23,7 @@
WrongQueryKindException,
DatasetCreateRequest,
TokenAttributes,
- TokenOrganizationCapabilities
+ TokenOrganizationCapabilities,
)
from axiom.query import (
QueryLegacy,
@@ -171,8 +171,6 @@ def test_step005_apl_query(self):
opts = AplOptions(
start_time=startTime,
end_time=endTime,
- no_cache=True,
- save=False,
format=AplResultFormat.Legacy,
)
qr = self.client.query(apl, opts)
@@ -228,9 +226,7 @@ def test_api_tokens(self):
"""Test creating and deleting an API token"""
token_attrs = TokenAttributes(
name=f"PytestToken-{uuid.uuid4()}",
- orgCapabilities=TokenOrganizationCapabilities(
- apiTokens=["read"]
- )
+ orgCapabilities=TokenOrganizationCapabilities(apiTokens=["read"]),
)
token_values = self.client.create_api_token(token_attrs)
diff --git a/tests/test_datasets.py b/tests/test_datasets.py
index 8375f61..c7c4c33 100644
--- a/tests/test_datasets.py
+++ b/tests/test_datasets.py
@@ -3,7 +3,7 @@
import os
import unittest
-from typing import List, Dict, Any
+from typing import List, Dict
from logging import getLogger
from requests.exceptions import HTTPError
from datetime import timedelta
@@ -17,7 +17,7 @@
class TestDatasets(unittest.TestCase):
dataset_name: str
- events: List[Dict[str, Any]]
+ events: List[Dict[str, object]]
client: Client
events_time_format = "%d/%b/%Y:%H:%M:%S +0000"