Skip to content

Commit

Permalink
Merge pull request #3 from memfault/sync_fork_v050
Browse files Browse the repository at this point in the history
Update `axiom-py` fork to v0.5.0
  • Loading branch information
WRansohoff authored Sep 9, 2024
2 parents 079f096 + b7ed013 commit 45ca752
Show file tree
Hide file tree
Showing 9 changed files with 112 additions and 76 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down Expand Up @@ -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 }}
Expand All @@ -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 }}
Expand Down
17 changes: 11 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -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)

<div align="center">
# axiom-py

<a href="https://axiom.co">
<picture>
<source media="(prefers-color-scheme: dark) and (min-width: 600px)" srcset="https://axiom.co/assets/github/axiom-github-banner-light-vertical.svg">
<source media="(prefers-color-scheme: light) and (min-width: 600px)" srcset="https://axiom.co/assets/github/axiom-github-banner-dark-vertical.svg">
<source media="(prefers-color-scheme: dark) and (max-width: 599px)" srcset="https://axiom.co/assets/github/axiom-github-banner-light-horizontal.svg">
<img alt="Axiom.co banner" src="https://axiom.co/assets/github/axiom-github-banner-dark-horizontal.svg" align="right">
</picture>
</a>
&nbsp;

[![CI][ci_badge]][ci]
[![PyPI version][pypi_badge]][pypi]
[![Python version][version_badge]][pypi]

</div>

[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.
Expand Down
55 changes: 29 additions & 26 deletions axiom/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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.
Expand All @@ -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 {}
Expand All @@ -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."""
Expand All @@ -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
8 changes: 4 additions & 4 deletions axiom/query/result.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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.

Expand All @@ -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]

Expand Down
78 changes: 55 additions & 23 deletions axiom/tokens.py
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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.
Expand All @@ -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
Expand All @@ -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
10 changes: 5 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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"]
Expand Down
2 changes: 1 addition & 1 deletion tests/test_annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit 45ca752

Please sign in to comment.