Skip to content

Commit

Permalink
Merge pull request #34 from mmahacek/develop
Browse files Browse the repository at this point in the history
v0.1.3 release
  • Loading branch information
mmahacek authored Feb 26, 2024
2 parents 7d01ee3 + c3859f5 commit 546a247
Show file tree
Hide file tree
Showing 42 changed files with 1,687 additions and 394 deletions.
25 changes: 25 additions & 0 deletions .github/workflows/testing.yml.old
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Python Tests

on: [push]

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11"]

steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install --upgrade pytest pytest-vcr
if [ -f requirements.txt ]; then pip install --upgrade -r requirements.txt; fi
- name: Test with pytest
run: |
pytest
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,4 @@ test.py
openapi/
public/
html/
play_*
19 changes: 18 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,29 @@
# Changelog


## [0.1.3] TBD

### What's Changed

* Revert nodes get functions to default components to `NodeComponents.NONE` instead of `ALL`.
* Added `Enlkind` endpoint and models.
* Update local testing/linting configuration.
* Add testing for `utils` methods.
* Add `mypy` for type checking during development.
* Add `pyonms.utils.check_ip_address()` to validate IP addresses.
* Fix type hint for `models.Info`'s `version` and `datetimeformatConfig` attributes.
* Rename all `_to_dict()` methods to `to_dict()`. Added stub method reference to keep backward compatibility.

**Full Changelog**: https://github.com/mmahacek/PyONMS/compare/v0.1.2...v0.1.3

## [0.1.2] 2023-12-21

### Breaking Change

* Updated `ApiPayloadError` exception to trigger when API returns HTTP 400+.

**Full Changelog**: https://github.com/mmahacek/PyONMS/compare/v0.1.1...v0.1.2

## [0.1.1] 2023-12-21

### Breaking Change
Expand All @@ -26,7 +43,7 @@
* Fix type hint on `Endpoint_post.data` attribute.
* Add Node/IP/Service metadata modification to the `Nodes` endpoint.

**Full Changelog**: https://github.com/mmahacek/PyONMS/compare/v0.0.13...v0.0.14
**Full Changelog**: https://github.com/mmahacek/PyONMS/compare/v0.0.13...v0.1.1


## [0.0.13] 2023-12-1
Expand Down
2 changes: 1 addition & 1 deletion LICENSE.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2023 mmahacek
Copyright (c) 2024 mmahacek

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
![package version](https://img.shields.io/pypi/v/pyonms)
![python version](https://img.shields.io/pypi/pyversions/pyonms)
![package version](https://img.shields.io/pypi/v/pyonms?logo=pypi)
![python version](https://img.shields.io/pypi/pyversions/pyonms?logo=python)
![license](https://img.shields.io/github/license/mmahacek/pyonms)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![linting: pylint](https://img.shields.io/badge/linting-pylint-yellowgreen)](https://github.com/pylint-dev/pylint)
[![type check: mypy](https://img.shields.io/badge/type%20checker-mypy-blue)](https://www.mypy-lang.org/)

# PyONMS

Expand All @@ -8,7 +12,7 @@ A Python library for accessing the OpenNMS REST API.
This is being developed with Python 3.11 and OpenNMS 32.
It may work on older versions, but they haven't been tested yet.

- [OpenNMS REST API documentation](https://docs.opennms.com/horizon/31/development/rest/rest-api.html)
- [OpenNMS REST API documentation](https://docs.opennms.com/horizon/32/development/rest/rest-api.html)
- [PyONMS documentation](https://mmahacek.github.io/PyONMS/)
- [PyPi Library](https://pypi.org/project/pyonms/)

Expand All @@ -24,6 +28,7 @@ Currently supported endpoints include:

* Alarms (read-write)
* Business Services (read-write)
* Enlinkd (read-only)
* Events (read, send)
* Foreign Sources (read-write)
* Health (read-only)
Expand Down
21 changes: 14 additions & 7 deletions pyonms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@
.. include:: ../README.md
"""

__version__ = "0.1.2"
__version__ = "0.1.3"

from multiprocessing import current_process
from typing import Optional
from urllib.parse import urlsplit

from pyonms import dao, models
Expand All @@ -23,9 +24,9 @@ def __init__(
hostname: str,
username: str,
password: str,
name: str = None,
verify_ssl: bool = True,
timeout: int = 30,
name: Optional[str] = None,
verify_ssl: Optional[bool] = True,
timeout: Optional[int] = 30,
):
"""Attributes:
hostname (str): OpenNMS URL
Expand Down Expand Up @@ -67,6 +68,8 @@ def __init__(
"""`pyonms.dao.alarms.AlarmAPI` endpoint"""
self.bsm = dao.business_services.BSMAPI(args)
"""`pyonms.dao.business_services.BSMAPI` endpoint"""
self.enlinkd = dao.enlinkd.EnlinkdAPI(args)
"""`pyonms.dao.enlinkd.EnlinkdAPI` endpoint"""
self.events = dao.events.EventAPI(args)
"""`pyonms.dao.events.EventAPI` endpoint"""
self.fs = dao.foreign_sources.ForeignSourceAPI(args)
Expand All @@ -83,7 +86,7 @@ def __init__(
def __repr__(self):
return self.hostname

def reload_daemon(self, name: str):
def reload_daemon(self, name: str) -> bool:
"""Send event to reload a given daemon
Attributes:
name (str): Daemon name
Expand All @@ -99,5 +102,9 @@ def reload_daemon(self, name: str):
uei="uei.opennms.org/internal/reloadDaemonConfig", source="pyonms"
)
reload_event.set_parameter(name="daemonName", value=name, type="string")
self.events.send_event(reload_event)
print(f"Sending event to trigger reload of the {name} daemon.")
success = self.events.send_event(reload_event)
if success:
print(f"Sending event to trigger reload of the {name} daemon.")
else:
print(f"Sending event to trigger reload of the {name} daemon failed.")
return success
3 changes: 3 additions & 0 deletions pyonms/dao/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
# dao.__init__.py

"DAO module"

from pyonms.dao import (
alarms,
business_services,
enlinkd,
events,
foreign_sources,
health,
Expand Down
25 changes: 18 additions & 7 deletions pyonms/dao/alarms.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# dao.alarms.py

"Alarms data access"

from typing import List, Optional

import pyonms.models.alarm
Expand All @@ -8,21 +10,27 @@


class AlarmAPI(Endpoint):
"""Alarms API endpoint"""

def __init__(self, kwargs):
super().__init__(**kwargs)
self.url = self.base_v2 + "alarms"

def get_alarm(self, id: int) -> Optional[pyonms.models.alarm.Alarm]:
"""Get alarm by ID number."""
record = self._get(url=f"{self.url}/{id}")
if record is not None:
return self._process_alarm(record)
else:
return None

def get_alarms(
self, fiql: str = None, limit: int = 100, batch_size: int = 100
) -> List[Optional[pyonms.models.alarm.Alarm]]:
alarms = []
self,
fiql: Optional[str] = None,
limit: int = 100,
batch_size: int = 100,
) -> List[pyonms.models.alarm.Alarm]:
"""Get all matching alarms."""
params = {}
if fiql:
params["_s"] = fiql
Expand All @@ -33,30 +41,33 @@ def get_alarms(
batch_size=batch_size,
params=params,
)
if records == [None]:
return [None]
alarms = []
for record in records:
alarms.append(self._process_alarm(record))
if record:
alarms.append(self._process_alarm(record))
return alarms

def _process_alarm(self, data: dict) -> pyonms.models.alarm.Alarm:
return pyonms.models.alarm.Alarm(**data)

def ack_alarm(self, id: int, ack: bool):
"""Acknowledge alarm by ID number."""
if not isinstance(ack, bool):
raise exceptions.InvalidValueError(
name="ack", value=ack, valid="[True, False]"
name="ack", value=ack, valid=[True, False]
)
params = {"ack": ack}
self._put(url=f"{self.url}/{id}", params=params, data=params)
return

def clear_alarm(self, id: int):
"""Clear alarm by ID number."""
params = {"clear": True}
self._put(url=f"{self.url}/{id}", params=params, data=params)
return

def escalate_alarm(self, id: int):
"""Escalate alarm severity by ID number."""
params = {"escalate": True}
self._put(url=f"{self.url}/{id}", params=params, data=params)
return
56 changes: 31 additions & 25 deletions pyonms/dao/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,16 @@

"""Base classes for DAO objects"""

from typing import List
from typing import List, Optional, Union

import requests
from requests.auth import HTTPBasicAuth
from requests.packages import urllib3
from requests.packages import urllib3 # type: ignore
from tqdm import tqdm
from urllib3.exceptions import InsecureRequestWarning

import pyonms.utils
from pyonms.models.exceptions import (
ApiPayloadError,
AuthenticationError,
InvalidValueError,
)
from pyonms.models.exceptions import ApiPayloadError, AuthenticationError

urllib3.disable_warnings(category=InsecureRequestWarning)

Expand Down Expand Up @@ -47,7 +43,7 @@ def _get_batch(
endpoint: str,
limit: int = 0,
batch_size: int = 100,
params: dict = None,
params: Optional[dict] = None,
hide_progress: bool = False,
) -> List[dict]:
if not params:
Expand All @@ -58,7 +54,7 @@ def _get_batch(
desc=f"Pulling {self.name} {endpoint} data",
disable=hide_progress,
) as pbar:
result = []
result: List[dict] = []
params["offset"] = 0
if limit > batch_size:
params["limit"] = batch_size
Expand All @@ -71,7 +67,7 @@ def _get_batch(
headers=self.headers,
)
if records.get(endpoint, [None]) in [[None], []]:
return [None]
return result
if limit == 0 or records["totalCount"] < limit:
target_count = records["totalCount"]
pbar.total = target_count
Expand All @@ -88,7 +84,11 @@ def _get_batch(
return result

def _get(
self, url: str, headers: dict = None, params: dict = None, endpoint: str = None
self,
url: str,
headers: Optional[dict] = None,
params: Optional[dict] = None,
endpoint: Optional[str] = None,
):
# if self.base_v1 in url:
# return self._get_v1(
Expand All @@ -110,7 +110,7 @@ def _get(
timeout=self.timeout,
)
if response.status_code == 200:
if response.encoding in ("ISO-8859-1") or url[-5:] in ["probe"]:
if response.encoding in ["ISO-8859-1"] or url[-5:] in ["probe"]:
return response.text
elif "was not found" not in response.text:
return response.json()
Expand All @@ -121,8 +121,12 @@ def _get(
return {}

def _get_v1(
self, url: str, endpoint: str, headers: dict = None, params: dict = None
) -> dict:
self,
url: str,
endpoint: str,
headers: Optional[dict] = None,
params: Optional[dict] = None,
) -> Union[dict, str]:
if not headers:
headers = {}
if not params:
Expand All @@ -142,19 +146,19 @@ def _get_v1(
if endpoint == "raw":
return response.text
else:
xml_data = pyonms.utils.convert_xml(response.text)
return self._convert_v1_to_v2(endpoint, xml_data)
xml_data = pyonms.utils.convert_xml(data=response.text)
return self._convert_v1_to_v2(endpoint=endpoint, data=xml_data)
elif response.status_code >= 400:
raise ApiPayloadError(message=response.text)
return {}

def _post(
self,
url: str,
headers: dict = None,
data: str = None,
json: dict = None,
params: dict = None,
headers: Optional[dict] = None,
data: Optional[str] = None,
json: Optional[dict] = None,
params: Optional[dict] = None,
) -> requests.Response:
if not headers:
headers = {}
Expand Down Expand Up @@ -195,10 +199,10 @@ def _post(
def _put(
self,
url: str,
data: dict = None,
json: dict = None,
headers: dict = None,
params: dict = None,
data: Optional[dict] = None,
json: Optional[dict] = None,
headers: Optional[dict] = None,
params: Optional[dict] = None,
) -> requests.Response:
if not headers:
headers = {}
Expand Down Expand Up @@ -254,7 +258,9 @@ def _convert_v1_to_v2(self, endpoint: str, data: dict) -> dict:
v2_data[key] = [value["model_import"]]
return v2_data

def _delete(self, url: str, headers: dict = None, params: dict = None) -> dict:
def _delete(
self, url: str, headers: Optional[dict] = None, params: Optional[dict] = None
) -> dict:
if not headers:
headers = {}
if not params:
Expand Down
Loading

0 comments on commit 546a247

Please sign in to comment.