Skip to content

Commit

Permalink
Merge pull request #62 from netboxlabs/OBS-411-diode-sdk-python
Browse files Browse the repository at this point in the history
  • Loading branch information
mfiedorowicz authored Apr 12, 2024
2 parents 1d4e5ed + d76c48d commit 156ba9f
Show file tree
Hide file tree
Showing 35 changed files with 1,100 additions and 150 deletions.
9 changes: 9 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.PHONY: gen-diode-sdk-go gen-diode-sdk-python

gen-diode-sdk-go:
@cd diode-proto/ && buf format -w && buf generate --template buf.gen.go.yaml

gen-diode-sdk-python:
@cd diode-proto/ && buf format -w && buf generate --template buf.gen.py.yaml --include-imports
@find diode-sdk-python/netboxlabs/diode/sdk \( -name '*.py' -o -name '*.pyi' \) \
-exec sed -i '' 's/^from diode.v1/from netboxlabs.diode.sdk.diode.v1/; s/^from validate/from netboxlabs.diode.sdk.validate/' {} \;
4 changes: 0 additions & 4 deletions diode-proto/buf.gen.yaml → diode-proto/buf.gen.go.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ plugins:
- plugin: buf.build/bufbuild/validate-go:v1.0.4
out: ../diode-sdk-go/
opt: module=github.com/netboxlabs/diode/diode-sdk-go
- plugin: buf.build/protocolbuffers/python:v25.1
out: ../diode-sdk-python/netboxlabs/diode/sdk/
- plugin: buf.build/grpc/python:v1.60.0
out: ../diode-sdk-python/netboxlabs/diode/sdk/
- plugin: buf.build/community/pseudomuto-doc:v1.5.1
out: ../docs/
opt:
Expand Down
8 changes: 8 additions & 0 deletions diode-proto/buf.gen.py.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
version: v1
plugins:
- plugin: buf.build/protocolbuffers/python:v26.1
out: ../diode-sdk-python/netboxlabs/diode/sdk/
- plugin: buf.build/protocolbuffers/pyi:v26.1
out: ../diode-sdk-python/netboxlabs/diode/sdk/
- plugin: buf.build/grpc/python:v1.62.1
out: ../diode-sdk-python/netboxlabs/diode/sdk/
37 changes: 37 additions & 0 deletions diode-sdk-python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,40 @@ pip install netboxlabs-diode-sdk
ruff netboxlabs/
black netboxlabs/
```

## Usage

### Environment variables

* `DIODE_API_KEY` - API key for the Diode service
* `DIODE_SDK_LOG_LEVEL` - Log level for the SDK (default: `INFO`)

### Example

```python

from netboxlabs.diode.sdk import DiodeClient
from netboxlabs.diode.sdk.diode.v1.device_type_pb2 import DeviceType
from netboxlabs.diode.sdk.diode.v1.distributor_pb2 import IngestEntity
from netboxlabs.diode.sdk.diode.v1.manufacturer_pb2 import Manufacturer
from netboxlabs.diode.sdk.diode.v1.site_pb2 import Site


def main():
with DiodeClient(target="localhost:8081", app_name="my-test-app", app_version="0.0.1") as client:
entities = [
IngestEntity(site=Site(name="Site 1")),
IngestEntity(
device_type=DeviceType(
model="ISR4321",
manufacturer=Manufacturer(name="Cisco"),
)
),
]

resp = client.ingest(entities=entities)
print(f"Response: {resp}")

if __name__ == "__main__":
main()
```
4 changes: 2 additions & 2 deletions diode-sdk-python/netboxlabs/diode/sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
# Copyright 2024 NetBox Labs Inc
"""NetBox Labs, Diode - SDK."""

from netboxlabs.diode.sdk.client_config import ClientConfiguration
from netboxlabs.diode.sdk.client import DiodeClient

assert ClientConfiguration
assert DiodeClient
120 changes: 120 additions & 0 deletions diode-sdk-python/netboxlabs/diode/sdk/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#!/usr/bin/env python
# Copyright 2024 NetBox Labs Inc
"""NetBox Labs, Diode - SDK - Client."""

import logging
import os
import uuid
from typing import Iterable, Optional

import grpc

from netboxlabs.diode.sdk.diode.v1 import distributor_pb2, distributor_pb2_grpc
from netboxlabs.diode.sdk.exceptions import DiodeClientError, DiodeConfigError

_DIODE_API_KEY_ENVVAR_NAME = "DIODE_API_KEY"
_DIODE_SDK_LOG_LEVEL_ENVVAR_NAME = "DIODE_SDK_LOG_LEVEL"
_DEFAULT_STREAM = "latest"
_LOGGER = logging.getLogger(__name__)


class DiodeClient:
"""Diode Client."""

_name = "diode-sdk-python"
_version = "0.0.1"
_app_name = None
_app_version = None
_channel = None
_stub = None

def __init__(
self,
target: str,
app_name: str,
app_version: str,
api_key: Optional[str] = None,
):
"""Initiate a new client."""
log_level = os.getenv(_DIODE_SDK_LOG_LEVEL_ENVVAR_NAME, "INFO").upper()
logging.basicConfig(level=log_level)

# TODO: validate target
self._target = target

self._app_name = app_name
self._app_version = app_version

if api_key is None:
api_key = os.getenv(_DIODE_API_KEY_ENVVAR_NAME)
if api_key is None:
raise DiodeConfigError("API key is required")

self._auth_metadata = (("diode-api-key", api_key),)
# TODO: add support for secure channel (TLS verify flag and cert)
self._channel = grpc.insecure_channel(target)
self._stub = distributor_pb2_grpc.DistributorServiceStub(self._channel)
# TODO: obtain meta data about the environment; Python version, CPU arch, OS

@property
def name(self) -> str:
"""Retrieve the name."""
return self._name

@property
def version(self) -> str:
"""Retrieve the version."""
return self._version

@property
def target(self) -> str:
"""Retrieve the target."""
return self._target

@property
def app_name(self) -> str:
"""Retrieve the app name."""
return self._app_name

@property
def app_version(self) -> str:
"""Retrieve the app version."""
return self._app_version

@property
def channel(self) -> grpc.Channel:
"""Retrieve the channel."""
return self._channel

def __enter__(self):
"""Enters the runtime context related to the channel object."""
return self

def __exit__(self, exc_type, exc_value, exc_traceback):
"""Exits the runtime context related to the channel object."""
self.close()

def close(self):
"""Close the channel."""
self._channel.close()

def ingest(
self,
entities: Iterable[distributor_pb2.IngestEntity],
stream: Optional[str] = _DEFAULT_STREAM,
) -> distributor_pb2.PushResponse:
"""Push a message."""
try:
request = distributor_pb2.PushRequest(
stream=stream,
id=str(uuid.uuid4()),
data=entities,
sdk_name=self.name,
sdk_version=self.version,
producer_app_name=self.app_name,
producer_app_version=self.app_version,
)

return self._stub.Push(request, metadata=self._auth_metadata)
except grpc.RpcError as err:
raise DiodeClientError(err) from err
27 changes: 0 additions & 27 deletions diode-sdk-python/netboxlabs/diode/sdk/client_config.py

This file was deleted.

Empty file.
Empty file.
24 changes: 12 additions & 12 deletions diode-sdk-python/netboxlabs/diode/sdk/diode/v1/device_pb2.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 30 additions & 0 deletions diode-sdk-python/netboxlabs/diode/sdk/diode/v1/device_pb2.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from netboxlabs.diode.sdk.diode.v1 import device_type_pb2 as _device_type_pb2
from netboxlabs.diode.sdk.diode.v1 import platform_pb2 as _platform_pb2
from netboxlabs.diode.sdk.diode.v1 import role_pb2 as _role_pb2
from netboxlabs.diode.sdk.diode.v1 import site_pb2 as _site_pb2
from netboxlabs.diode.sdk.validate import validate_pb2 as _validate_pb2
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from typing import ClassVar as _ClassVar, Mapping as _Mapping, Optional as _Optional, Union as _Union

DESCRIPTOR: _descriptor.FileDescriptor

class Device(_message.Message):
__slots__ = ("name", "device_fqdn", "device_type", "role", "platform", "serial", "site", "vc_position")
NAME_FIELD_NUMBER: _ClassVar[int]
DEVICE_FQDN_FIELD_NUMBER: _ClassVar[int]
DEVICE_TYPE_FIELD_NUMBER: _ClassVar[int]
ROLE_FIELD_NUMBER: _ClassVar[int]
PLATFORM_FIELD_NUMBER: _ClassVar[int]
SERIAL_FIELD_NUMBER: _ClassVar[int]
SITE_FIELD_NUMBER: _ClassVar[int]
VC_POSITION_FIELD_NUMBER: _ClassVar[int]
name: str
device_fqdn: str
device_type: _device_type_pb2.DeviceType
role: _role_pb2.Role
platform: _platform_pb2.Platform
serial: str
site: _site_pb2.Site
vc_position: int
def __init__(self, name: _Optional[str] = ..., device_fqdn: _Optional[str] = ..., device_type: _Optional[_Union[_device_type_pb2.DeviceType, _Mapping]] = ..., role: _Optional[_Union[_role_pb2.Role, _Mapping]] = ..., platform: _Optional[_Union[_platform_pb2.Platform, _Mapping]] = ..., serial: _Optional[str] = ..., site: _Optional[_Union[_site_pb2.Site, _Mapping]] = ..., vc_position: _Optional[int] = ...) -> None: ...
32 changes: 0 additions & 32 deletions diode-sdk-python/netboxlabs/diode/sdk/diode/v1/device_role_pb2.py

This file was deleted.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions diode-sdk-python/netboxlabs/diode/sdk/diode/v1/device_type_pb2.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from netboxlabs.diode.sdk.diode.v1 import manufacturer_pb2 as _manufacturer_pb2
from netboxlabs.diode.sdk.validate import validate_pb2 as _validate_pb2
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from typing import ClassVar as _ClassVar, Mapping as _Mapping, Optional as _Optional, Union as _Union

DESCRIPTOR: _descriptor.FileDescriptor

class DeviceType(_message.Message):
__slots__ = ("manufacturer", "model", "slug")
MANUFACTURER_FIELD_NUMBER: _ClassVar[int]
MODEL_FIELD_NUMBER: _ClassVar[int]
SLUG_FIELD_NUMBER: _ClassVar[int]
manufacturer: _manufacturer_pb2.Manufacturer
model: str
slug: str
def __init__(self, manufacturer: _Optional[_Union[_manufacturer_pb2.Manufacturer, _Mapping]] = ..., model: _Optional[str] = ..., slug: _Optional[str] = ...) -> None: ...
Loading

0 comments on commit 156ba9f

Please sign in to comment.