From aba213f95169728ab663ca0d0ea56779a4adfa21 Mon Sep 17 00:00:00 2001 From: Gabriele Date: Mon, 23 Oct 2017 11:57:48 +0200 Subject: [PATCH 001/145] removed download badge it's not working anymore --- README.markdown | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.markdown b/README.markdown index 0395042..89505b7 100644 --- a/README.markdown +++ b/README.markdown @@ -4,10 +4,9 @@ [![Build Status](https://travis-ci.org/lukecyca/pyzabbix.png?branch=master)](https://travis-ci.org/lukecyca/pyzabbix) [![PyPi version](https://img.shields.io/pypi/v/pyzabbix.svg)](https://pypi.python.org/pypi/pyzabbix/) -[![PyPi downloads](https://img.shields.io/pypi/dm/pyzabbix.svg)](https://pypi.python.org/pypi/pyzabbix/) ## Requirements -* Tested against Zabbix 1.8 through 3.0 +* Tested against Zabbix 1.8 through 3.4 ## Documentation ## ### Getting Started From a251ae5c90e50024c74b4cec09bf49c0a43ed920 Mon Sep 17 00:00:00 2001 From: Gabriele Date: Mon, 23 Oct 2017 12:17:04 +0200 Subject: [PATCH 002/145] removed import of future module since we don't support python 3.2 anymore, there is no need to use the future module, we can specify the error string is "unicode" freely. --- pyzabbix/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py index 27142b6..a997b07 100644 --- a/pyzabbix/__init__.py +++ b/pyzabbix/__init__.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals import logging import requests import json @@ -132,7 +131,7 @@ def do_request(self, method, params=None): if 'error' in response_json: # some exception if 'data' not in response_json['error']: # some errors don't contain 'data': workaround for ZBX-9340 response_json['error']['data'] = "No data" - msg = "Error {code}: {message}, {data}".format( + msg = u"Error {code}: {message}, {data}".format( code=response_json['error']['code'], message=response_json['error']['message'], data=response_json['error']['data'] From fc56184bfdec85334e07aa64c74318b927e7d440 Mon Sep 17 00:00:00 2001 From: colttt Date: Fri, 9 Feb 2018 14:49:29 +0100 Subject: [PATCH 003/145] a working example for zabbix 3.4 a working example programm, it needs a xml file or an xml-folder as an argument --- examples/import_templates.py | 105 +++++++++++++++++++++-------------- 1 file changed, 64 insertions(+), 41 deletions(-) diff --git a/examples/import_templates.py b/examples/import_templates.py index cf095e5..5f31600 100644 --- a/examples/import_templates.py +++ b/examples/import_templates.py @@ -1,79 +1,102 @@ -""" +""" Import Zabbix XML templates -""" - +""" + from pyzabbix import ZabbixAPI, ZabbixAPIException -import glob +import os +import sys + +if len(sys.argv) <= 1: + print('Please provide directory with templates as first ARG or the XML file with template.') + exit(1) + +path = sys.argv[1] # The hostname at which the Zabbix web interface is available -ZABBIX_SERVER = 'https://zabbix.example.com' +ZABBIX_SERVER = 'https://zabbix.example.org' zapi = ZabbixAPI(ZABBIX_SERVER) # Login to the Zabbix API -zapi.login("admin", "zabbix") +#zapi.session.verify = False +zapi.login("Admin", "zabbix") rules = { 'applications': { - 'createMissing': 'true', - 'updateExisting': 'true' + 'createMissing': True, }, 'discoveryRules': { - 'createMissing': 'true', - 'updateExisting': 'true' + 'createMissing': True, + 'updateExisting': True }, 'graphs': { - 'createMissing': 'true', - 'updateExisting': 'true' + 'createMissing': True, + 'updateExisting': True }, 'groups': { - 'createMissing': 'true' + 'createMissing': True }, 'hosts': { - 'createMissing': 'true', - 'updateExisting': 'true' + 'createMissing': True, + 'updateExisting': True }, 'images': { - 'createMissing': 'true', - 'updateExisting': 'true' + 'createMissing': True, + 'updateExisting': True }, 'items': { - 'createMissing': 'true', - 'updateExisting': 'true' + 'createMissing': True, + 'updateExisting': True }, 'maps': { - 'createMissing': 'true', - 'updateExisting': 'true' + 'createMissing': True, + 'updateExisting': True }, 'screens': { - 'createMissing': 'true', - 'updateExisting': 'true' + 'createMissing': True, + 'updateExisting': True }, 'templateLinkage': { - 'createMissing': 'true', - 'updateExisting': 'true' + 'createMissing': True, }, 'templates': { - 'createMissing': 'true', - 'updateExisting': 'true' + 'createMissing': True, + 'updateExisting': True }, 'templateScreens': { - 'createMissing': 'true', - 'updateExisting': 'true' + 'createMissing': True, + 'updateExisting': True }, 'triggers': { - 'createMissing': 'true', - 'updateExisting': 'true' + 'createMissing': True, + 'updateExisting': True + }, + 'valueMaps': { + 'createMissing': True, + 'updateExisting': True }, } -path = './templates/*.xml' -files = glob.glob(path) - -for file in files: - with open(file, 'r') as f: - template = f.read() - try: - zapi.confimport('xml', template, rules) - except ZabbixAPIException as e: - print(e) +if os.path.isdir(path): + #path = path/*.xml + files = glob.glob(path+'/*.xml') + for file in files: + print(file) + with open(file, 'r') as f: + template = f.read() + try: + zapi.confimport('xml', template, rules) + except ZabbixAPIException as e: + print(e) + print('') +elif os.path.isfile(path): + files = glob.glob(path) + for file in files: + with open(file, 'r') as f: + template = f.read() + try: + zapi.confimport('xml', template, rules) + except ZabbixAPIException as e: + print(e) +else: + print('I need a xml file') From 1dd367e7960d415c83f187dc201fc25e64fad42c Mon Sep 17 00:00:00 2001 From: Max Grechnev Date: Thu, 13 Sep 2018 13:14:24 +0300 Subject: [PATCH 004/145] Fixed support of user.checkAuthentication method This method must be called without the 'auth' parameter in the JSON-RPC request --- pyzabbix/__init__.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py index a997b07..6564b63 100644 --- a/pyzabbix/__init__.py +++ b/pyzabbix/__init__.py @@ -71,6 +71,10 @@ def login(self, user='', password=''): else: self.auth = self.user.login(user=user, password=password) + def check_authentication(self): + """Convenience method for calling user.checkAuthentication of the current session""" + return self.user.checkAuthentication(sessionid=self.auth) + def confimport(self, confformat='', source='', rules=''): """Alias for configuration.import because it clashes with Python's import reserved keyword @@ -95,8 +99,8 @@ def do_request(self, method, params=None): 'id': self.id, } - # We don't have to pass the auth token if asking for the apiinfo.version - if self.auth and method != 'apiinfo.version': + # We don't have to pass the auth token if asking for the apiinfo.version or user.checkAuthentication + if self.auth and method != 'apiinfo.version' and method != 'user.checkAuthentication': request_json['auth'] = self.auth logger.debug("Sending: %s", json.dumps(request_json, From 0a7f1407c63aa9fc77b9c1ea6df8918bfc398fca Mon Sep 17 00:00:00 2001 From: Gabriele Date: Sat, 3 Nov 2018 13:11:24 +0100 Subject: [PATCH 005/145] added 4.0 compatibility --- README.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index 89505b7..f56c4cd 100644 --- a/README.markdown +++ b/README.markdown @@ -6,7 +6,7 @@ [![PyPi version](https://img.shields.io/pypi/v/pyzabbix.svg)](https://pypi.python.org/pypi/pyzabbix/) ## Requirements -* Tested against Zabbix 1.8 through 3.4 +* Tested against Zabbix 1.8 through 4.0 ## Documentation ## ### Getting Started From e1c1a0e6ca7d696206d05dee063184693faf35a5 Mon Sep 17 00:00:00 2001 From: Gabriele Date: Sat, 3 Nov 2018 13:14:44 +0100 Subject: [PATCH 006/145] bump to 0.7.5 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ccc53a3..6acb052 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name="pyzabbix", - version="0.7.4", + version="0.7.5", install_requires=[ "requests>=1.0", ], From 73751100f11202072569cecb2f1002a740dde069 Mon Sep 17 00:00:00 2001 From: Robert Szulist Date: Mon, 1 Jul 2019 19:43:16 +0000 Subject: [PATCH 007/145] Add context manager to ZabbixAPI class In some sceranios it is easier to use the 'with' keyword than to manually logout the API user. --- pyzabbix/__init__.py | 9 +++++++++ tests/test_api.py | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py index 6564b63..4e60d3a 100644 --- a/pyzabbix/__init__.py +++ b/pyzabbix/__init__.py @@ -55,6 +55,15 @@ def __init__(self, self.url = server + '/api_jsonrpc.php' logger.info("JSON-RPC Server Endpoint: %s", self.url) + def __enter__(self): + return self + + def __exit__(self, exception_type, exception_value, traceback): + if isinstance(exception_value, (ZabbixAPIException, type(None))): + if self.check_authentication(): + self.user.logout() + return True + def login(self, user='', password=''): """Convenience method for calling user.authenticate and storing the resulting auth token for further commands. diff --git a/tests/test_api.py b/tests/test_api.py index c9f860f..9131d5e 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -109,3 +109,21 @@ def test_host_delete(self): # Check response self.assertEqual(set(result["itemids"]), set(["22982", "22986"])) + + + @httpretty.activate + def test_login_with_context(self): + httpretty.register_uri( + httpretty.POST, + "http://example.com/api_jsonrpc.php", + body=json.dumps({ + "jsonrpc": "2.0", + "result": "0424bd59b807674191e7d77572075f33", + "id": 0 + }), + ) + + with ZabbixAPI('http://example.com') as zapi: + zapi.login('mylogin', 'mypass') + self.assertEqual(zapi.auth, "0424bd59b807674191e7d77572075f33") + From a55186cb757f964c878f52b91e75b62d6e2d2047 Mon Sep 17 00:00:00 2001 From: Robert Szulist Date: Mon, 1 Jul 2019 19:46:17 +0000 Subject: [PATCH 008/145] Add an example using context manager --- examples/with_context.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 examples/with_context.py diff --git a/examples/with_context.py b/examples/with_context.py new file mode 100644 index 0000000..bebd936 --- /dev/null +++ b/examples/with_context.py @@ -0,0 +1,14 @@ +""" +Prints hostnames for all known hosts. +""" + +from pyzabbix import ZabbixAPI + +ZABBIX_SERVER = 'https://zabbix.example.com' + +# Use context manager to auto-logout after request is done. +with ZabbixAPI(ZABBIX_SERVER) as zapi: + zapi.login('api_username', 'api_password') + hosts = zapi.host.get(output=['name']) + for host in hosts: + print(host['name']) From e0944adf94f731d9124ef044132bebd2f9a0c20d Mon Sep 17 00:00:00 2001 From: Robert Szulist Date: Thu, 4 Jul 2019 12:52:55 +0000 Subject: [PATCH 009/145] Update docstring with error codes New codes were presented during Zabbix 4.0 Expert course. --- pyzabbix/__init__.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py index 6564b63..d515242 100644 --- a/pyzabbix/__init__.py +++ b/pyzabbix/__init__.py @@ -14,8 +14,14 @@ def emit(self, record): class ZabbixAPIException(Exception): """ generic zabbix api exception code list: - -32602 - Invalid params (eg already exists) - -32500 - no permissions + -32700 - invalid JSON. An error occurred on the server while parsing the JSON text (typo, wrong quotes, etc.) + -32600 - received JSON is not a valid JSON-RPC Request + -32601 - requested remote-procedure does not exist + -32602 - invalid method parameters + -32603 - Internal JSON-RPC error + -32400 - System error + -32300 - Transport error + -32500 - Application error """ pass From c54677d560e30b3d2527cd13138b0d4a43acdbe8 Mon Sep 17 00:00:00 2001 From: Robert Szulist Date: Fri, 5 Jul 2019 10:22:05 +0000 Subject: [PATCH 010/145] Add api_jsonrpc.php to url only if not present --- pyzabbix/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py index 6564b63..8fa1152 100644 --- a/pyzabbix/__init__.py +++ b/pyzabbix/__init__.py @@ -52,7 +52,7 @@ def __init__(self, self.timeout = timeout - self.url = server + '/api_jsonrpc.php' + self.url = server + '/api_jsonrpc.php' if not server.endswith('/api_jsonrpc.php') else server logger.info("JSON-RPC Server Endpoint: %s", self.url) def login(self, user='', password=''): From a098260b56017a096dd01c13f944e2362aaca353 Mon Sep 17 00:00:00 2001 From: Robert Szulist Date: Fri, 5 Jul 2019 11:34:20 +0000 Subject: [PATCH 011/145] Add is_authenticated property Property returns a nice bool that represent an active session --- pyzabbix/__init__.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py index 8fa1152..237b2a6 100644 --- a/pyzabbix/__init__.py +++ b/pyzabbix/__init__.py @@ -75,6 +75,14 @@ def check_authentication(self): """Convenience method for calling user.checkAuthentication of the current session""" return self.user.checkAuthentication(sessionid=self.auth) + @property + def is_authenticated(self): + try: + self.user.checkAuthentication(sessionid=self.auth) + except ZabbixAPIException: + return False + return True + def confimport(self, confformat='', source='', rules=''): """Alias for configuration.import because it clashes with Python's import reserved keyword From 25dcf8ab5dbd0974856c621977cac00f50812dc1 Mon Sep 17 00:00:00 2001 From: Robert Szulist Date: Sat, 7 Sep 2019 17:09:38 +0200 Subject: [PATCH 012/145] Use is_authenticated in __exit__ check_authentication will throw an exception if user session expires. --- pyzabbix/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py index d70c9ad..e5e74f2 100644 --- a/pyzabbix/__init__.py +++ b/pyzabbix/__init__.py @@ -66,7 +66,7 @@ def __enter__(self): def __exit__(self, exception_type, exception_value, traceback): if isinstance(exception_value, (ZabbixAPIException, type(None))): - if self.check_authentication(): + if self.is_authenticated: self.user.logout() return True From a0b850f63c0b3cecf1ceedb38216339f3acdb158 Mon Sep 17 00:00:00 2001 From: Robert Szulist Date: Sat, 7 Sep 2019 17:15:10 +0200 Subject: [PATCH 013/145] Add python versions for testing --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 5538df2..ad09bd0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,9 @@ python: - "2.7" - "3.3" - "3.4" + - "3.5" + - "3.6" + - "3.7" install: - pip install requests script: python setup.py nosetests From 28207300676d2a40b204ac74c5100a9de61c80d3 Mon Sep 17 00:00:00 2001 From: Robert Szulist Date: Sat, 7 Sep 2019 17:26:58 +0200 Subject: [PATCH 014/145] Remove python versions that cause tests to fail At this moment travis fails with the following messages: Unable to download 2.6 archive. Unable to download 3.3 archive. As such it is not possible to test against those versions. --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index ad09bd0..aed400a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,6 @@ language: python python: - - "2.6" - "2.7" - - "3.3" - "3.4" - "3.5" - "3.6" From d8e3bdd1d099445beaaaada7a6bd3e1c63f5e991 Mon Sep 17 00:00:00 2001 From: Paal Braathen Date: Tue, 15 Oct 2019 14:54:53 +0200 Subject: [PATCH 015/145] Store JSON-RPC errors in ZabbixAPIException --- pyzabbix/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py index e5e74f2..86112e4 100644 --- a/pyzabbix/__init__.py +++ b/pyzabbix/__init__.py @@ -23,7 +23,10 @@ class ZabbixAPIException(Exception): -32300 - Transport error -32500 - Application error """ - pass + def __init__(self, *args, **kwargs): + super(ZabbixAPIException, self).__init__(*args) + + self.error = kwargs.get("error", None) class ZabbixAPI(object): @@ -163,7 +166,7 @@ def do_request(self, method, params=None): message=response_json['error']['message'], data=response_json['error']['data'] ) - raise ZabbixAPIException(msg, response_json['error']['code']) + raise ZabbixAPIException(msg, response_json['error']['code'], error=response_json['error']) return response_json From 157bdce150546f4561929501355077323bfda5d5 Mon Sep 17 00:00:00 2001 From: Stefan Verhoeff <813871+stefanverhoeff@users.noreply.github.com> Date: Fri, 8 May 2020 14:02:57 +0300 Subject: [PATCH 016/145] Fix README links to requests Python library --- README.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index f56c4cd..56bcda1 100644 --- a/README.markdown +++ b/README.markdown @@ -33,7 +33,7 @@ for h in zapi.host.get(output="extend"): Refer to the [Zabbix API Documentation](https://www.zabbix.com/documentation/3.0/manual/api/reference) and the [PyZabbix Examples](https://github.com/lukecyca/pyzabbix/tree/master/examples) for more information. ### Customizing the HTTP request -PyZabbix uses the [requests](http://www.python-requests.org/en/latest/) library for http. You can customize the request parameters by configuring the [requests Session](http://docs.python-requests.org/en/latest/user/advanced/#session-objects) object used by PyZabbix. +PyZabbix uses the [requests](https://requests.readthedocs.io/en/master/) library for HTTP. You can customize the request parameters by configuring the [requests Session](https://requests.readthedocs.io/en/master/user/advanced/#session-objects) object used by PyZabbix. This is useful for: * Customizing headers From a0d0898c01bace8c496a80b308962a454548081e Mon Sep 17 00:00:00 2001 From: Gabriele Date: Mon, 25 May 2020 18:42:35 +0200 Subject: [PATCH 017/145] Update README.markdown 5.0 compatibility and link to latest version of Zabbix API doc --- README.markdown | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.markdown b/README.markdown index 56bcda1..ad6a254 100644 --- a/README.markdown +++ b/README.markdown @@ -1,12 +1,12 @@ # PyZabbix # -**PyZabbix** is a Python module for working with the [Zabbix API](https://www.zabbix.com/documentation/3.0/manual/api/reference). +**PyZabbix** is a Python module for working with the [Zabbix API](https://www.zabbix.com/documentation/current/manual/api/reference). [![Build Status](https://travis-ci.org/lukecyca/pyzabbix.png?branch=master)](https://travis-ci.org/lukecyca/pyzabbix) [![PyPi version](https://img.shields.io/pypi/v/pyzabbix.svg)](https://pypi.python.org/pypi/pyzabbix/) ## Requirements -* Tested against Zabbix 1.8 through 4.0 +* Tested against Zabbix 1.8 through 5.0 ## Documentation ## ### Getting Started @@ -30,7 +30,7 @@ for h in zapi.host.get(output="extend"): print(h['hostid']) ``` -Refer to the [Zabbix API Documentation](https://www.zabbix.com/documentation/3.0/manual/api/reference) and the [PyZabbix Examples](https://github.com/lukecyca/pyzabbix/tree/master/examples) for more information. +Refer to the [Zabbix API Documentation](https://www.zabbix.com/documentation/current/manual/api/reference) and the [PyZabbix Examples](https://github.com/lukecyca/pyzabbix/tree/master/examples) for more information. ### Customizing the HTTP request PyZabbix uses the [requests](https://requests.readthedocs.io/en/master/) library for HTTP. You can customize the request parameters by configuring the [requests Session](https://requests.readthedocs.io/en/master/user/advanced/#session-objects) object used by PyZabbix. From bceca71e7ae2b1150751f2bd7776403439da3e72 Mon Sep 17 00:00:00 2001 From: Gabriele Date: Mon, 25 May 2020 18:49:59 +0200 Subject: [PATCH 018/145] bump to 0.8 fixes #136 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6acb052..111664f 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name="pyzabbix", - version="0.7.5", + version="0.8", install_requires=[ "requests>=1.0", ], From 193e0d6315ae0437492ef01d16f75a9a74bc79f3 Mon Sep 17 00:00:00 2001 From: Gabriele Date: Mon, 25 May 2020 18:57:06 +0200 Subject: [PATCH 019/145] bump to 0.8.1 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 111664f..7fd8072 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name="pyzabbix", - version="0.8", + version="0.8.1", install_requires=[ "requests>=1.0", ], From ed388112151f7a05127439a932f48982fb7b2051 Mon Sep 17 00:00:00 2001 From: Gabriele Date: Mon, 25 May 2020 19:25:46 +0200 Subject: [PATCH 020/145] added long_description to pypi --- setup.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/setup.py b/setup.py index 7fd8072..0a5595e 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,8 @@ from setuptools import setup +with open("README.markdown", "r") as fh: + long_description = fh.read() + setup( name="pyzabbix", version="0.8.1", @@ -7,6 +10,8 @@ "requests>=1.0", ], description="Zabbix API Python interface", + long_description=long_description, + long_description_content_type="text/markdown", author="Luke Cyca", author_email="me@lukecyca.com", license="LGPL", From bbb83b8c3a500b79537b4238a7bf08a62aa4321b Mon Sep 17 00:00:00 2001 From: Vincent Philippon Date: Mon, 25 May 2020 17:20:59 -0400 Subject: [PATCH 021/145] Add missing README.markdown, required by setup.py to build --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) create mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..1bfca8d --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include README.markdown \ No newline at end of file From 8ee2c3a5fe66d06a4ce2a1ad18b8ce7d44b1c64c Mon Sep 17 00:00:00 2001 From: neothematrix Date: Tue, 26 May 2020 09:43:33 +0200 Subject: [PATCH 022/145] bump to version 0.8.2 --- packaging/rpm/python-pyzabbix.spec | 4 ++-- setup.py | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packaging/rpm/python-pyzabbix.spec b/packaging/rpm/python-pyzabbix.spec index 81865b8..26a7692 100644 --- a/packaging/rpm/python-pyzabbix.spec +++ b/packaging/rpm/python-pyzabbix.spec @@ -17,7 +17,7 @@ Name: python-pyzabbix -Version: 0.7.4 +Version: 0.8.2 Release: 1 Url: http://github.com/lukecyca/pyzabbix Summary: Zabbix API Python interface @@ -32,7 +32,7 @@ Requires: python-requests >= 1.0 %description PyZabbix is a Python module for working with the Zabbix API. -Tested against Zabbix 1.8 through 3.0. +Tested against Zabbix 1.8 through 5.0. %prep %setup -q -n pyzabbix-%{version} diff --git a/setup.py b/setup.py index 0a5595e..8536711 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name="pyzabbix", - version="0.8.1", + version="0.8.2", install_requires=[ "requests>=1.0", ], @@ -19,9 +19,11 @@ url="http://github.com/lukecyca/pyzabbix", classifiers=[ "Programming Language :: Python", - "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3,5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", "Operating System :: OS Independent", "Development Status :: 4 - Beta", From 11a22076ceb8684d7a24c31e8c6e722fbfed81be Mon Sep 17 00:00:00 2001 From: neothematrix Date: Tue, 26 May 2020 09:48:02 +0200 Subject: [PATCH 023/145] fixed typo --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 8536711..cf919d5 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ "Programming Language :: Python", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.4", - "Programming Language :: Python :: 3,5", + "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", From cad42b7ff71e7d2bd58d5996d983cd5e96966aca Mon Sep 17 00:00:00 2001 From: Atif Ahmad <68680561+The-01@users.noreply.github.com> Date: Fri, 2 Oct 2020 18:48:43 +0500 Subject: [PATCH 024/145] Update README.markdown --- README.markdown | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.markdown b/README.markdown index ad6a254..177b9a6 100644 --- a/README.markdown +++ b/README.markdown @@ -1,4 +1,6 @@ +============= # PyZabbix # +============= **PyZabbix** is a Python module for working with the [Zabbix API](https://www.zabbix.com/documentation/current/manual/api/reference). From 2bd86d85afc0d1304bd9f54c5828f549a1087329 Mon Sep 17 00:00:00 2001 From: Gabriele Date: Fri, 2 Oct 2020 18:06:39 +0200 Subject: [PATCH 025/145] Revert "improved docs" --- README.markdown | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.markdown b/README.markdown index 177b9a6..ad6a254 100644 --- a/README.markdown +++ b/README.markdown @@ -1,6 +1,4 @@ -============= # PyZabbix # -============= **PyZabbix** is a Python module for working with the [Zabbix API](https://www.zabbix.com/documentation/current/manual/api/reference). From a9ad7cb996cc69cf36de1ba84daeacf1db41414c Mon Sep 17 00:00:00 2001 From: taroguru Date: Sat, 6 Feb 2021 01:45:56 +0900 Subject: [PATCH 026/145] Add an example code that export history to csv file --- examples/export_history_csv.py | 163 +++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 examples/export_history_csv.py diff --git a/examples/export_history_csv.py b/examples/export_history_csv.py new file mode 100644 index 0000000..0e05ca0 --- /dev/null +++ b/examples/export_history_csv.py @@ -0,0 +1,163 @@ +#!/usr/bin/python + +# The research leading to these results has received funding from the +# European Commission's Seventh Framework Programme (FP7/2007-13) +# under grant agreement no 257386. +# http://www.bonfire-project.eu/ +# Copyright 2012 Yahya Al-Hazmi, TU Berlin +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License + +from pyzabbix import ZabbixAPI +import sys +import datetime +import time +import argparse + + +def login(zapi, username, password): + try: + zapi.login(username, password) + print ("login succeed.") + except: + print ("zabbix server is not reachable: ") + sys.exit() + + +def getHostId(zapi, hostname, server): + if(hostname == ''): + print ('hostname is missed') + sys.exit() + host = zapi.host.get(filter={"host":hostname}, output="extend") + if(len(host)==0): + print ("hostname: %s not found in zabbix server: %s, exit" % (hostname,server)) + sys.exit() + else: + return host[0]["hostid"] + + +def getItems(zapi, key, hostid, hostname): + items = zapi.item.get(search={"key_":key}, hostids=hostid , output="extend") + if(len(items)==0): + print ("item key: %s not found in hostname: %s" % (key,hostname)) + sys.exit() + else: + return items + + +def convertTimeStamp(inputTime): + if(inputTime == ''): + return '' + try: + tempDate=datetime.datetime.strptime(inputTime,'%Y-%m-%d %H:%M:%S') + timestamp = int(time.mktime(tempDate.timetuple())) + except: + print ("time data %s does not match format Y-m-d H:M:S, exit" % (datetime)) + sys.exit() + + return timestamp + + +def generateOutputFilename(output, hostname, key): + if(output == ''): + return hostname+"_"+key+".csv" + else: + return output + +def exportToCSV(historys, key, output): + f = open(output, 'w') + inc = 0 + f.write('key;timestamp;valuei\n') # csv header + for history in historys: + f.write('%s;%s;%s\n' % (key, history["clock"], history["value"] ) ) + inc = inc + 1 + print( 'exported %i history to %s' % (inc, output) ) + f.close() + + +def assignTimeRange(inputParameters, datetime1, datetime2): + timestamp1 = convertTimeStamp(datetime1) + timestamp2 = convertTimeStamp(datetime2) + + # only timestamp1 + if (timestamp1 and not timestamp2): + inputParameters["time_from"] = timestamp1 + inputParameters["time_till"] = convertTimeStamp(time.time()) # current time + # only timestamp2 + elif(not timestamp1 and timestamp2): + inputParameters["time_from"] = timestamp2 + inputParameters["time_till"] = timestamp2 + # no inserted both timestamps + elif(not timestamp1 and not timestamp2): + inputParameters["time_from"] = convertTimeStamp(time.time()) # current time + inputParameters["time_till"] = convertTimeStamp(time.time()) # current time + # inserted both timestamps + else: + inputParameters["time_from"] = timestamp1 + inputParameters["time_till"] = timestamp2 + + +def fetch_to_csv(username,password,server,hostname,key,output,datetime1,datetime2,debuglevel): + + print("login to zabbix server %s" % server) + zapi = ZabbixAPI(server+'/zabbix') + login(zapi, username, password) + hostid = getHostId(zapi, hostname, server) + + # find itemid using key + print ("key is: %s" %(key)) + items = getItems(zapi, key, hostid, hostname) + item = items[0] + + # parameter validation + inputParameters = {} + inputParameters["history"] = item["value_type"] + inputParameters["output"] = "extend" + inputParameters["itemids"] = [item["itemid"]] + + assignTimeRange(inputParameters, datetime1, datetime2) + + # get history + print('get history using this parameter') + print( inputParameters ) + history = zapi.history.get( **inputParameters ) + + # export to File + output = generateOutputFilename(output, hostname, key) + exportToCSV(history, key, output) + + +#Parsing Parameters +parser = argparse.ArgumentParser(description='Fetch history from aggregator and save it into CSV file') +parser.add_argument('-s', dest='server_IP', required=True, + help='aggregator IP address') +parser.add_argument('-n', dest='hostname', required=True, + help='name of the monitored host') +parser.add_argument('-u', dest='username', default='Admin', required=True, + help='zabbix username, default Admin') +parser.add_argument('-p', dest='password', default='zabbix', required=True, + help='zabbix password') +parser.add_argument('-k', dest='key',default='', required=True, + help='zabbix item key, if not specified the script will fetch all keys for the specified hostname') +parser.add_argument('-o', dest='output', default='', + help='output file name, default hostname.csv') +parser.add_argument('-t1', dest='datetime1', default='', + help='begin date-time, use this pattern \'2011-11-08 14:49:43\' if only t1 specified then time period will be t1-now ') +parser.add_argument('-t2', dest='datetime2', default='', + help='end date-time, use this pattern \'2011-11-08 14:49:43\'') +parser.add_argument('-v', dest='debuglevel', default=0, type=int, + help='log level, default 0') +args = parser.parse_args() + +#Calling fetching function +fetch_to_csv(args.username, args.password, args.server_IP, args.hostname, args.key, args.output, args.datetime1,args.datetime2,args.debuglevel) + From ba4338148976e1a0aad01a49d8503d1a882d135c Mon Sep 17 00:00:00 2001 From: szuro Date: Fri, 12 Mar 2021 22:46:17 +0000 Subject: [PATCH 027/145] Add autodetecting of API version --- pyzabbix/__init__.py | 10 +++++++++- setup.py | 1 + tests/test_api.py | 16 ++++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py index 86112e4..bd3a176 100644 --- a/pyzabbix/__init__.py +++ b/pyzabbix/__init__.py @@ -1,6 +1,7 @@ import logging import requests import json +import semantic_version class _NullHandler(logging.Handler): @@ -64,6 +65,11 @@ def __init__(self, self.url = server + '/api_jsonrpc.php' if not server.endswith('/api_jsonrpc.php') else server logger.info("JSON-RPC Server Endpoint: %s", self.url) + self._version = semantic_version.Version( + self.apiinfo.version() + ) + logger.info("Zabbix API version is: %s", self.api_version()) + def __enter__(self): return self @@ -86,6 +92,8 @@ def login(self, user='', password=''): self.auth = '' if self.use_authenticate: self.auth = self.user.authenticate(user=user, password=password) + elif self._version >= semantic_version.Version('5.4.0'): + self.auth = self.user.login(username=user, password=password) else: self.auth = self.user.login(user=user, password=password) @@ -115,7 +123,7 @@ def confimport(self, confformat='', source='', rules=''): )['result'] def api_version(self): - return self.apiinfo.version() + return str(self._version) def do_request(self, method, params=None): request_json = { diff --git a/setup.py b/setup.py index cf919d5..7c017c5 100644 --- a/setup.py +++ b/setup.py @@ -8,6 +8,7 @@ version="0.8.2", install_requires=[ "requests>=1.0", + "semantic-version>=2.8" ], description="Zabbix API Python interface", long_description=long_description, diff --git a/tests/test_api.py b/tests/test_api.py index 9131d5e..cb6579b 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -2,6 +2,7 @@ import httpretty import json from pyzabbix import ZabbixAPI +import semantic_version class TestPyZabbix(unittest.TestCase): @@ -127,3 +128,18 @@ def test_login_with_context(self): zapi.login('mylogin', 'mypass') self.assertEqual(zapi.auth, "0424bd59b807674191e7d77572075f33") + @httpretty.activate + def test_detecting_version(self): + httpretty.register_uri( + httpretty.POST, + "http://example.com/api_jsonrpc.php", + body=json.dumps({ + "jsonrpc": "2.0", + "result": "4.0.0", + "id": 0 + }), + ) + + zapi_detect = ZabbixAPI('http://example.com') + self.assertEqual(zapi_detect.api_version(), '4.0.0') + self.assertEqual(zapi_detect._version, semantic_version.Version('4.0.0')) From 0143e80d9ff97b2cae930e4b9cea4954d69c7e46 Mon Sep 17 00:00:00 2001 From: szuro Date: Fri, 12 Mar 2021 23:11:45 +0000 Subject: [PATCH 028/145] Make detecting configurable but on by default, unbreak tests --- pyzabbix/__init__.py | 17 ++++++++++------- tests/test_api.py | 10 +++++----- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py index bd3a176..e9e4e0f 100644 --- a/pyzabbix/__init__.py +++ b/pyzabbix/__init__.py @@ -35,7 +35,8 @@ def __init__(self, server='http://localhost/zabbix', session=None, use_authenticate=False, - timeout=None): + timeout=None, + detect_version=True): """ Parameters: server: Base URI for zabbix web interface (omitting /api_jsonrpc.php) @@ -65,10 +66,12 @@ def __init__(self, self.url = server + '/api_jsonrpc.php' if not server.endswith('/api_jsonrpc.php') else server logger.info("JSON-RPC Server Endpoint: %s", self.url) - self._version = semantic_version.Version( - self.apiinfo.version() - ) - logger.info("Zabbix API version is: %s", self.api_version()) + self.version = '' + if detect_version: + self.version = semantic_version.Version( + self.api_version() + ) + logger.info("Zabbix API version is: %s", self.api_version()) def __enter__(self): return self @@ -92,7 +95,7 @@ def login(self, user='', password=''): self.auth = '' if self.use_authenticate: self.auth = self.user.authenticate(user=user, password=password) - elif self._version >= semantic_version.Version('5.4.0'): + elif self.version and self.version >= semantic_version.Version('5.4.0'): self.auth = self.user.login(username=user, password=password) else: self.auth = self.user.login(user=user, password=password) @@ -123,7 +126,7 @@ def confimport(self, confformat='', source='', rules=''): )['result'] def api_version(self): - return str(self._version) + return self.apiinfo.version() def do_request(self, method, params=None): request_json = { diff --git a/tests/test_api.py b/tests/test_api.py index cb6579b..42c5489 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -19,7 +19,7 @@ def test_login(self): }), ) - zapi = ZabbixAPI('http://example.com') + zapi = ZabbixAPI('http://example.com', detect_version=False) zapi.login('mylogin', 'mypass') # Check request @@ -56,7 +56,7 @@ def test_host_get(self): }), ) - zapi = ZabbixAPI('http://example.com') + zapi = ZabbixAPI('http://example.com', detect_version=False) zapi.auth = "123" result = zapi.host.get() @@ -92,7 +92,7 @@ def test_host_delete(self): }), ) - zapi = ZabbixAPI('http://example.com') + zapi = ZabbixAPI('http://example.com', detect_version=False) zapi.auth = "123" result = zapi.host.delete("22982", "22986") @@ -124,7 +124,7 @@ def test_login_with_context(self): }), ) - with ZabbixAPI('http://example.com') as zapi: + with ZabbixAPI('http://example.com', detect_version=False) as zapi: zapi.login('mylogin', 'mypass') self.assertEqual(zapi.auth, "0424bd59b807674191e7d77572075f33") @@ -142,4 +142,4 @@ def test_detecting_version(self): zapi_detect = ZabbixAPI('http://example.com') self.assertEqual(zapi_detect.api_version(), '4.0.0') - self.assertEqual(zapi_detect._version, semantic_version.Version('4.0.0')) + self.assertEqual(zapi_detect.version, semantic_version.Version('4.0.0')) From 16957cbcbf409c8ac566c8c1508f097aeec6f0fe Mon Sep 17 00:00:00 2001 From: WagPTech <49325967+WagPTech@users.noreply.github.com> Date: Sat, 17 Apr 2021 21:00:50 -0300 Subject: [PATCH 029/145] additemcsv MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Alteração do additem para incluir itens de um arquivo csv. --- examples/additemcsv.py | 78 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 examples/additemcsv.py diff --git a/examples/additemcsv.py b/examples/additemcsv.py new file mode 100644 index 0000000..015bd2c --- /dev/null +++ b/examples/additemcsv.py @@ -0,0 +1,78 @@ +""" +Desenvolvimento: WagPTech + +Programa para incluir Item no Zabbix em um Host específico. + +Sintaxe: python ZBXadditem.py host_name arquivo.csv + +Entrar com login e senha do zabbix (necessário ser ADM) + +Formato do arquivo: +key +item1 +item2 +... +""" + +from pyzabbix import ZabbixAPI, ZabbixAPIException +import sys +import csv +import os +import getpass + +host_name = sys.argv[1] +arquivo = sys.argv[2] +open (arquivo, newline='', encoding='utf-8') + +# Zabbix server + +zapi = ZabbixAPI("http:///zabbix") + +# Login com o Zabbix API + +login = input("Insira seu login: ") +passwd = getpass.getpass("Digite sua senha: ") + +zapi.login(login, passwd) + +add = int(0) +nadd = int(0) + +hosts = zapi.host.get(filter={"host": host_name}, selectInterfaces=["interfaceid"]) +if hosts: + host_id = hosts[0]["hostid"] + print("host_name "+ host_name + " @ host id {0}".format(host_id)) + with open (arquivo, newline='', encoding='utf-8') as csvfile: #sys.argv[2]/'zbx_l15_k10.csv' + reader = csv.DictReader(csvfile) + for row in reader: + try: + #print ("Add Item: " + row['key']) + item = zapi.item.create( + hostid=host_id, + name = row['key'], + key_ = row['key'], + type=2, #0-Zabbix agent; 2-Zabbix trapper; 3-simple check; 5-Zabbix internal; + #7-Zabbix agent (active); 8-Zabbix aggregate; 9-web item; + #10-external check; 11-database monitor; 12-IPMI agent; + #13-SSH agent; 14-TELNET agent; 15-calculated; + #16-JMX agent; 17-SNMP trap; 18-Dependent item; 19-HTTP agent; 20-SNMP agent; 21-Script. + value_type=3, #0-numeric float; 1-character; 2-log; 3-numeric unsigned; 4-text. + interfaceid=hosts[0]["interfaces"][0]["interfaceid"], + delay=60, + status=1 #0-enabled item; 1-disabled item. + ) + add = add + 1; + if add > 0: + print("Adicionados: " + str(add)) + except ZabbixAPIException as error: + nadd = nadd +1 + if nadd > 0: + print("Recusados: " + str(nadd)) + #print(error) + if add > 0: + print("Total de itens adicionados: "+ str(add) + " itens.") + if nadd > 0: + print("Total de itens recusados: "+ str(nadd) + " itens.") + sys.exit() +else: + print("No hosts found") From a3a8997c7388c6a607a822a676ef4dde7201308a Mon Sep 17 00:00:00 2001 From: wagnerportela <49325967+wagnerportela@users.noreply.github.com> Date: Sat, 17 Apr 2021 21:40:52 -0300 Subject: [PATCH 030/145] Update additemcsv.py --- examples/additemcsv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/additemcsv.py b/examples/additemcsv.py index 015bd2c..9c5d443 100644 --- a/examples/additemcsv.py +++ b/examples/additemcsv.py @@ -26,7 +26,7 @@ # Zabbix server -zapi = ZabbixAPI("http:///zabbix") +zapi = ZabbixAPI("http://30.0.0.47/zabbix") # Login com o Zabbix API From a963be4093ae1aa165ef0aaffba2e595422dad5e Mon Sep 17 00:00:00 2001 From: Frank Klaassen <639906+syphernl@users.noreply.github.com> Date: Wed, 2 Jun 2021 11:58:37 +0200 Subject: [PATCH 031/145] feat: add support for api tokens --- README.markdown | 10 +++++++++- pyzabbix/__init__.py | 20 ++++++++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/README.markdown b/README.markdown index ad6a254..da4a4f7 100644 --- a/README.markdown +++ b/README.markdown @@ -24,12 +24,13 @@ from pyzabbix import ZabbixAPI zapi = ZabbixAPI("http://zabbixserver.example.com") zapi.login("zabbix user", "zabbix pass") +# You can also authenticate using an API token instead of user/pass with Zabbix >= 5.4 +# zapi.login(api_token='xxxxx') print("Connected to Zabbix API Version %s" % zapi.api_version()) for h in zapi.host.get(output="extend"): print(h['hostid']) ``` - Refer to the [Zabbix API Documentation](https://www.zabbix.com/documentation/current/manual/api/reference) and the [PyZabbix Examples](https://github.com/lukecyca/pyzabbix/tree/master/examples) for more information. ### Customizing the HTTP request @@ -57,6 +58,9 @@ zapi.timeout = 5.1 # Login (in case of HTTP Auth, only the username is needed, the password, if passed, will be ignored) zapi.login("http user", "http password") + +# You can also authenticate using an API token instead of user/pass with Zabbix >= 5.4 +# zapi.login(api_token='xxxxx') ``` ### Enabling debug logging @@ -75,6 +79,10 @@ log.setLevel(logging.DEBUG) zapi = ZabbixAPI("http://zabbixserver.example.com") zapi.login('admin','password') + +# You can also authenticate using an API token instead of user/pass with Zabbix >= 5.4 +# zapi.login(api_token='xxxxx') + ``` The expected output is as following: diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py index 86112e4..ec09c14 100644 --- a/pyzabbix/__init__.py +++ b/pyzabbix/__init__.py @@ -56,6 +56,7 @@ def __init__(self, }) self.use_authenticate = use_authenticate + self.use_api_token = False self.auth = '' self.id = 0 @@ -69,18 +70,26 @@ def __enter__(self): def __exit__(self, exception_type, exception_value, traceback): if isinstance(exception_value, (ZabbixAPIException, type(None))): - if self.is_authenticated: + if self.is_authenticated and not self.use_api_token: + """ Logout the user if they are authenticated using username + password.""" self.user.logout() return True - def login(self, user='', password=''): + def login(self, user='', password='', api_token=None): """Convenience method for calling user.authenticate and storing the resulting auth token for further commands. If use_authenticate is set, it uses the older (Zabbix 1.8) authentication command :param password: Password used to login into Zabbix :param user: Username used to login into Zabbix + :param api_token: API Token to authenticate with """ + # If the API token is explicitly provided, use this instead. + if api_token is not None: + self.use_api_token = True + self.auth = api_token + return + # If we have an invalid auth token, we are not allowed to send a login # request. Clear it before trying. self.auth = '' @@ -90,11 +99,18 @@ def login(self, user='', password=''): self.auth = self.user.login(user=user, password=password) def check_authentication(self): + if self.use_api_token: + """We cannot use this call using an API Token""" + return True """Convenience method for calling user.checkAuthentication of the current session""" return self.user.checkAuthentication(sessionid=self.auth) @property def is_authenticated(self): + if self.use_api_token: + """We cannot use this call using an API Token""" + return True + try: self.user.checkAuthentication(sessionid=self.auth) except ZabbixAPIException: From 48a992e5b35d1bf7852ec5a920130afc14a7655c Mon Sep 17 00:00:00 2001 From: Luke Cyca Date: Wed, 23 Jun 2021 07:55:07 -0700 Subject: [PATCH 032/145] Release 1.0.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 7c017c5..6bebc60 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name="pyzabbix", - version="0.8.2", + version="1.0.0", install_requires=[ "requests>=1.0", "semantic-version>=2.8" From e32e955a45eeff2ea5915e0231e8dba21c1a0ef7 Mon Sep 17 00:00:00 2001 From: Robert Szulist Date: Wed, 30 Jun 2021 18:40:55 +0000 Subject: [PATCH 033/145] Move detecting version to login method --- pyzabbix/__init__.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py index c6ffa02..932c5ef 100644 --- a/pyzabbix/__init__.py +++ b/pyzabbix/__init__.py @@ -68,11 +68,7 @@ def __init__(self, logger.info("JSON-RPC Server Endpoint: %s", self.url) self.version = '' - if detect_version: - self.version = semantic_version.Version( - self.api_version() - ) - logger.info("Zabbix API version is: %s", self.api_version()) + self._detect_version = detect_version def __enter__(self): return self @@ -93,6 +89,12 @@ def login(self, user='', password='', api_token=None): :param api_token: API Token to authenticate with """ + if self._detect_version: + self.version = semantic_version.Version( + self.api_version() + ) + logger.info("Zabbix API version is: %s", self.api_version()) + # If the API token is explicitly provided, use this instead. if api_token is not None: self.use_api_token = True From cfca521faf649d3ec79749889351ddb2fa237175 Mon Sep 17 00:00:00 2001 From: Robert Szulist Date: Wed, 30 Jun 2021 18:43:35 +0000 Subject: [PATCH 034/145] Add missing parameter description --- pyzabbix/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py index 932c5ef..fdd9f8b 100644 --- a/pyzabbix/__init__.py +++ b/pyzabbix/__init__.py @@ -43,6 +43,7 @@ def __init__(self, session: optional pre-configured requests.Session instance use_authenticate: Use old (Zabbix 1.8) style authentication timeout: optional connect and read timeout in seconds, default: None (if you're using Requests >= 2.4 you can set it as tuple: "(connect, read)" which is used to set individual connect and read timeouts.) + detect_version: autodetect Zabbix API version """ if session: From b08ebc64dfe16eaf301dd2d136bc9873adf2e463 Mon Sep 17 00:00:00 2001 From: Robert Szulist Date: Wed, 30 Jun 2021 18:45:36 +0000 Subject: [PATCH 035/145] Reduce the number of API calls --- pyzabbix/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py index fdd9f8b..205af21 100644 --- a/pyzabbix/__init__.py +++ b/pyzabbix/__init__.py @@ -94,7 +94,7 @@ def login(self, user='', password='', api_token=None): self.version = semantic_version.Version( self.api_version() ) - logger.info("Zabbix API version is: %s", self.api_version()) + logger.info("Zabbix API version is: %s", str(self.version)) # If the API token is explicitly provided, use this instead. if api_token is not None: From 40cce0b9d83ec28d808509cd6aa6a4629d3f9e7b Mon Sep 17 00:00:00 2001 From: Robert Szulist Date: Wed, 30 Jun 2021 21:42:36 +0000 Subject: [PATCH 036/145] Detecting of version is no longer happening on init --- tests/test_api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_api.py b/tests/test_api.py index 42c5489..ac5c445 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -142,4 +142,3 @@ def test_detecting_version(self): zapi_detect = ZabbixAPI('http://example.com') self.assertEqual(zapi_detect.api_version(), '4.0.0') - self.assertEqual(zapi_detect.version, semantic_version.Version('4.0.0')) From 2f5ac6dd2e8a7920cf1e91be356a23232259e33e Mon Sep 17 00:00:00 2001 From: jo Date: Thu, 21 Jul 2022 16:25:47 +0200 Subject: [PATCH 037/145] chore: update .gitignore --- .gitignore | 169 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 166 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index cd45890..02eb1f7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,167 @@ -build +## Custom .gitignore +################################################################################ + +## Github Python .gitignore +## See https://github.com/github/gitignore/blob/main/Python.gitignore +################################################################################ + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ dist/ -pyzabbix.egg-info/ -pyzabbix/*.pyc +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ From 321da42e22ac2c0a7a521f1e48ee6871e4f5f343 Mon Sep 17 00:00:00 2001 From: jo Date: Thu, 21 Jul 2022 16:26:39 +0200 Subject: [PATCH 038/145] chore: rename README.markdown to README.md --- MANIFEST.in | 2 +- README.markdown => README.md | 0 setup.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename README.markdown => README.md (100%) diff --git a/MANIFEST.in b/MANIFEST.in index 1bfca8d..bb3ec5f 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1 @@ -include README.markdown \ No newline at end of file +include README.md diff --git a/README.markdown b/README.md similarity index 100% rename from README.markdown rename to README.md diff --git a/setup.py b/setup.py index 6bebc60..a04bbce 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ from setuptools import setup -with open("README.markdown", "r") as fh: +with open("README.md", "r") as fh: long_description = fh.read() setup( From c26e3b7dc26c4beb235c0b4c3cb6eb57eec18548 Mon Sep 17 00:00:00 2001 From: jo Date: Thu, 21 Jul 2022 16:28:06 +0200 Subject: [PATCH 039/145] chore: add pre-commit config --- .pre-commit-config.yaml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..0d8e302 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,35 @@ +--- +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.3.0 + hooks: + - id: check-added-large-files + - id: check-case-conflict + - id: check-executables-have-shebangs + - id: check-shebang-scripts-are-executable + - id: check-symlinks + - id: destroyed-symlinks + + - id: check-json + - id: check-yaml + - id: check-toml + + - id: check-merge-conflict + - id: end-of-file-fixer + - id: mixed-line-ending + - id: trailing-whitespace + + - id: name-tests-test + + - repo: https://github.com/pre-commit/mirrors-prettier + rev: v2.7.1 + hooks: + - id: prettier + files: \.(md|yml|yaml|json)$ + + - repo: https://github.com/codespell-project/codespell + rev: v2.1.0 + hooks: + - id: codespell From cb7d568ce0129c16ab2c0cadda615525aeac5262 Mon Sep 17 00:00:00 2001 From: jo Date: Thu, 21 Jul 2022 16:34:27 +0200 Subject: [PATCH 040/145] chore: remove shebang for non executable files --- examples/export_history_csv.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/export_history_csv.py b/examples/export_history_csv.py index 0e05ca0..400fd6b 100644 --- a/examples/export_history_csv.py +++ b/examples/export_history_csv.py @@ -1,5 +1,3 @@ -#!/usr/bin/python - # The research leading to these results has received funding from the # European Commission's Seventh Framework Programme (FP7/2007-13) # under grant agreement no 257386. From e63b73b8e9cef56533f3f98a300a3e20a633aa96 Mon Sep 17 00:00:00 2001 From: jo Date: Thu, 21 Jul 2022 16:35:10 +0200 Subject: [PATCH 041/145] chore: rename tests to *_test.py --- tests/{test_api.py => api_test.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{test_api.py => api_test.py} (100%) diff --git a/tests/test_api.py b/tests/api_test.py similarity index 100% rename from tests/test_api.py rename to tests/api_test.py From 4179de4fe59cf7adafe7842bd12e14e76682769e Mon Sep 17 00:00:00 2001 From: jo Date: Thu, 21 Jul 2022 16:39:10 +0200 Subject: [PATCH 042/145] chore: fix codespell --- examples/export_history_csv.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/export_history_csv.py b/examples/export_history_csv.py index 400fd6b..9a4a03a 100644 --- a/examples/export_history_csv.py +++ b/examples/export_history_csv.py @@ -56,8 +56,8 @@ def convertTimeStamp(inputTime): if(inputTime == ''): return '' try: - tempDate=datetime.datetime.strptime(inputTime,'%Y-%m-%d %H:%M:%S') - timestamp = int(time.mktime(tempDate.timetuple())) + tmpDate=datetime.datetime.strptime(inputTime,'%Y-%m-%d %H:%M:%S') + timestamp = int(time.mktime(tmpDate.timetuple())) except: print ("time data %s does not match format Y-m-d H:M:S, exit" % (datetime)) sys.exit() From 2916bfeb4ddedfe7c934fb9e23dd8ef387dbc942 Mon Sep 17 00:00:00 2001 From: jo Date: Thu, 21 Jul 2022 16:39:40 +0200 Subject: [PATCH 043/145] chore: cleanup files using pre-commit hooks --- .travis.yml | 2 +- README.md | 31 ++++++++++++++++++++----------- examples/export_history_csv.py | 15 +++++++-------- examples/import_templates.py | 8 ++++---- pyzabbix/__init__.py | 2 +- 5 files changed, 33 insertions(+), 25 deletions(-) diff --git a/.travis.yml b/.travis.yml index aed400a..6eb85c3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,4 +7,4 @@ python: - "3.7" install: - pip install requests -script: python setup.py nosetests +script: python setup.py nosetests diff --git a/README.md b/README.md index da4a4f7..3b00e5b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# PyZabbix # +# PyZabbix **PyZabbix** is a Python module for working with the [Zabbix API](https://www.zabbix.com/documentation/current/manual/api/reference). @@ -6,9 +6,11 @@ [![PyPi version](https://img.shields.io/pypi/v/pyzabbix.svg)](https://pypi.python.org/pypi/pyzabbix/) ## Requirements -* Tested against Zabbix 1.8 through 5.0 -## Documentation ## +- Tested against Zabbix 1.8 through 5.0 + +## Documentation + ### Getting Started Install PyZabbix using pip: @@ -31,16 +33,19 @@ print("Connected to Zabbix API Version %s" % zapi.api_version()) for h in zapi.host.get(output="extend"): print(h['hostid']) ``` + Refer to the [Zabbix API Documentation](https://www.zabbix.com/documentation/current/manual/api/reference) and the [PyZabbix Examples](https://github.com/lukecyca/pyzabbix/tree/master/examples) for more information. ### Customizing the HTTP request + PyZabbix uses the [requests](https://requests.readthedocs.io/en/master/) library for HTTP. You can customize the request parameters by configuring the [requests Session](https://requests.readthedocs.io/en/master/user/advanced/#session-objects) object used by PyZabbix. This is useful for: -* Customizing headers -* Enabling HTTP authentication -* Enabling Keep-Alive -* Disabling SSL certificate verification + +- Customizing headers +- Enabling HTTP authentication +- Enabling Keep-Alive +- Disabling SSL certificate verification ```python from pyzabbix import ZabbixAPI @@ -64,7 +69,9 @@ zapi.login("http user", "http password") ``` ### Enabling debug logging + If you need to debug some issue with the Zabbix API, you can enable the output of logging, pyzabbix already uses the default python logging facility but by default, it logs to "Null", you can change this behavior on your program, here's an example: + ```python import sys import logging @@ -84,6 +91,7 @@ zapi.login('admin','password') # zapi.login(api_token='xxxxx') ``` + The expected output is as following: ``` @@ -105,8 +113,9 @@ Response Body: { >>> ``` -## License ## -LGPL 2.1 http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html +## License + +LGPL 2.1 http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html Zabbix API Python Library. @@ -121,9 +130,9 @@ version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA diff --git a/examples/export_history_csv.py b/examples/export_history_csv.py index 9a4a03a..90d7b6b 100644 --- a/examples/export_history_csv.py +++ b/examples/export_history_csv.py @@ -3,13 +3,13 @@ # under grant agreement no 257386. # http://www.bonfire-project.eu/ # Copyright 2012 Yahya Al-Hazmi, TU Berlin -# Licensed under the Apache License, Version 2.0 (the "License"); +# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # -# Unless required by applicable law or agreed to in writing, software +# Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and @@ -32,7 +32,7 @@ def login(zapi, username, password): def getHostId(zapi, hostname, server): - if(hostname == ''): + if(hostname == ''): print ('hostname is missed') sys.exit() host = zapi.host.get(filter={"host":hostname}, output="extend") @@ -115,7 +115,7 @@ def fetch_to_csv(username,password,server,hostname,key,output,datetime1,datetime print ("key is: %s" %(key)) items = getItems(zapi, key, hostid, hostname) item = items[0] - + # parameter validation inputParameters = {} inputParameters["history"] = item["value_type"] @@ -123,8 +123,8 @@ def fetch_to_csv(username,password,server,hostname,key,output,datetime1,datetime inputParameters["itemids"] = [item["itemid"]] assignTimeRange(inputParameters, datetime1, datetime2) - - # get history + + # get history print('get history using this parameter') print( inputParameters ) history = zapi.history.get( **inputParameters ) @@ -158,4 +158,3 @@ def fetch_to_csv(username,password,server,hostname,key,output,datetime1,datetime #Calling fetching function fetch_to_csv(args.username, args.password, args.server_IP, args.hostname, args.key, args.output, args.datetime1,args.datetime2,args.debuglevel) - diff --git a/examples/import_templates.py b/examples/import_templates.py index 5f31600..e418870 100644 --- a/examples/import_templates.py +++ b/examples/import_templates.py @@ -1,10 +1,10 @@ -""" +""" Import Zabbix XML templates -""" - +""" + from pyzabbix import ZabbixAPI, ZabbixAPIException import os -import sys +import sys if len(sys.argv) <= 1: print('Please provide directory with templates as first ARG or the XML file with template.') diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py index 205af21..cb121f6 100644 --- a/pyzabbix/__init__.py +++ b/pyzabbix/__init__.py @@ -16,7 +16,7 @@ class ZabbixAPIException(Exception): """ generic zabbix api exception code list: -32700 - invalid JSON. An error occurred on the server while parsing the JSON text (typo, wrong quotes, etc.) - -32600 - received JSON is not a valid JSON-RPC Request + -32600 - received JSON is not a valid JSON-RPC Request -32601 - requested remote-procedure does not exist -32602 - invalid method parameters -32603 - Internal JSON-RPC error From 607c9ce35421ef8eb49a2a0dd7490823419d22b4 Mon Sep 17 00:00:00 2001 From: jo Date: Thu, 21 Jul 2022 16:42:58 +0200 Subject: [PATCH 044/145] ci: add pre-commit check --- .github/workflows/ci.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..3f22a61 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,20 @@ +name: CI + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + pre-commit: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-python@v4 + with: + python-version: ${{ inputs.python-version }} + + - uses: pre-commit/action@v3.0.0 From 96a209a139505683a419b1e25bb1e80c74ee438a Mon Sep 17 00:00:00 2001 From: jo Date: Thu, 21 Jul 2022 17:19:01 +0200 Subject: [PATCH 045/145] chore: use dev extra for dependencies --- setup.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/setup.py b/setup.py index a04bbce..a3538ac 100644 --- a/setup.py +++ b/setup.py @@ -6,10 +6,6 @@ setup( name="pyzabbix", version="1.0.0", - install_requires=[ - "requests>=1.0", - "semantic-version>=2.8" - ], description="Zabbix API Python interface", long_description=long_description, long_description_content_type="text/markdown", @@ -21,10 +17,10 @@ classifiers=[ "Programming Language :: Python", "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", "Operating System :: OS Independent", "Development Status :: 4 - Beta", @@ -34,7 +30,13 @@ "Topic :: System :: Systems Administration", ], packages=["pyzabbix"], - tests_require=[ - "httpretty<0.8.7", + install_requires=[ + "requests>=1.0", + "semantic-version>=2.8", ], + extras_require={ + "dev": [ + "httpretty<0.8.7", + ], + }, ) From f40d4df39606d2d15cecf27feaf78eef9216192a Mon Sep 17 00:00:00 2001 From: jo Date: Thu, 21 Jul 2022 17:20:24 +0200 Subject: [PATCH 046/145] ci: fix pre-commit python-version --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3f22a61..b3fab09 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,6 +15,6 @@ jobs: - uses: actions/setup-python@v4 with: - python-version: ${{ inputs.python-version }} + python-version: 3.x - uses: pre-commit/action@v3.0.0 From 5bebc76a290ef18f1fb05a15bae2513eff088505 Mon Sep 17 00:00:00 2001 From: jo Date: Thu, 21 Jul 2022 17:28:59 +0200 Subject: [PATCH 047/145] test: use pytest for testing --- Makefile | 30 ++++++++++++++++++++++++++++++ setup.py | 3 +++ 2 files changed, 33 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3c65691 --- /dev/null +++ b/Makefile @@ -0,0 +1,30 @@ +all: lint test + +.ONESHELL: +.DEFAULT_GOAL: install + +SHELL = bash +CPU_CORES = $(shell nproc) + +VENV = .venv +$(VENV): + python3 -m venv $(VENV) + source $(VENV)/bin/activate + $(MAKE) install + +install: $(VENV) + source $(VENV)/bin/activate + pip install --upgrade pip setuptools wheel + pip install --editable .[dev] + +test: $(VENV) + source $(VENV)/bin/activate + pytest -v \ + --numprocesses=$(CPU_CORES) \ + --color=yes \ + --cov-report=term \ + --cov=pyzabbix \ + tests + +clean: + rm -Rf $(VENV) diff --git a/setup.py b/setup.py index a3538ac..dc09a8b 100644 --- a/setup.py +++ b/setup.py @@ -36,6 +36,9 @@ ], extras_require={ "dev": [ + "pytest", + "pytest-cov", + "pytest-xdist", "httpretty<0.8.7", ], }, From e8b7c15802c2899fc1414146687a26f1bb6b0281 Mon Sep 17 00:00:00 2001 From: jo Date: Thu, 21 Jul 2022 17:29:33 +0200 Subject: [PATCH 048/145] ci: test using github actions and replace travis --- .github/workflows/ci.yml | 24 ++++++++++++++++++++++++ .travis.yml | 10 ---------- 2 files changed, 24 insertions(+), 10 deletions(-) delete mode 100644 .travis.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b3fab09..95a036e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,3 +18,27 @@ jobs: python-version: 3.x - uses: pre-commit/action@v3.0.0 + + test: + runs-on: ubuntu-latest + strategy: + matrix: + os: [ubuntu-latest] + python-version: ["2.7", "3.4", "3.5", "3.6", "3.7"] + + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - uses: actions/cache@v3 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }} + restore-keys: | + ${{ runner.os }}-pip- + + - run: make install + - run: make test diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 6eb85c3..0000000 --- a/.travis.yml +++ /dev/null @@ -1,10 +0,0 @@ -language: python -python: - - "2.7" - - "3.4" - - "3.5" - - "3.6" - - "3.7" -install: - - pip install requests -script: python setup.py nosetests From 778a5a08b37dd44b918b7e0a4fa64418f1bce349 Mon Sep 17 00:00:00 2001 From: jo Date: Thu, 21 Jul 2022 17:32:21 +0200 Subject: [PATCH 049/145] chore: ignore codespell for non english file --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0d8e302..a5bb8d9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -33,3 +33,4 @@ repos: rev: v2.1.0 hooks: - id: codespell + exclude: ^examples/additemcsv.py$ From ce34982dce3632a186ecb048b14b2c6e2f83a51b Mon Sep 17 00:00:00 2001 From: jo Date: Thu, 21 Jul 2022 17:34:23 +0200 Subject: [PATCH 050/145] test: only on supported python versions --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 95a036e..82b7a8c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - python-version: ["2.7", "3.4", "3.5", "3.6", "3.7"] + python-version: ["3.7", "3.8", "3.9", "3.10"] steps: - uses: actions/checkout@v3 From 4ef4cd77b29d749b3b06aada8ecd934699a3383f Mon Sep 17 00:00:00 2001 From: jo Date: Thu, 21 Jul 2022 17:45:03 +0200 Subject: [PATCH 051/145] chore: setup format using black and isort --- Makefile | 5 +++++ setup.py | 6 ++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 3c65691..4559a91 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,11 @@ install: $(VENV) pip install --upgrade pip setuptools wheel pip install --editable .[dev] +format: $(VENV) + source $(VENV)/bin/activate + black . + isort . --profile black + test: $(VENV) source $(VENV)/bin/activate pytest -v \ diff --git a/setup.py b/setup.py index dc09a8b..e1e88c6 100644 --- a/setup.py +++ b/setup.py @@ -36,10 +36,12 @@ ], extras_require={ "dev": [ - "pytest", + "black", + "httpretty<0.8.7", + "isort", "pytest-cov", "pytest-xdist", - "httpretty<0.8.7", + "pytest", ], }, ) From 4bb88c66fe0392298f3f49186031ac4d19076915 Mon Sep 17 00:00:00 2001 From: jo Date: Thu, 21 Jul 2022 17:47:27 +0200 Subject: [PATCH 052/145] style: format code using black --- examples/add_item.py | 19 ++-- examples/additemcsv.py | 53 +++++------ examples/current_issues.py | 58 ++++++------ examples/export_history_csv.py | 155 ++++++++++++++++++++------------- examples/fix_host_ips.py | 32 ++++--- examples/history_data.py | 47 +++++----- examples/import_templates.py | 94 +++++++------------- examples/timeout.py | 4 +- examples/with_context.py | 8 +- pyzabbix/__init__.py | 143 ++++++++++++++++-------------- tests/api_test.py | 134 ++++++++++++++-------------- 11 files changed, 393 insertions(+), 354 deletions(-) diff --git a/examples/add_item.py b/examples/add_item.py index 4b6b3bf..41a94df 100644 --- a/examples/add_item.py +++ b/examples/add_item.py @@ -2,18 +2,19 @@ Looks up a host based on its name, and then adds an item to it """ -from pyzabbix import ZabbixAPI, ZabbixAPIException import sys +from pyzabbix import ZabbixAPI, ZabbixAPIException + # The hostname at which the Zabbix web interface is available -ZABBIX_SERVER = 'https://zabbix.example.com' +ZABBIX_SERVER = "https://zabbix.example.com" zapi = ZabbixAPI(ZABBIX_SERVER) # Login to the Zabbix API -zapi.login('Admin', 'zabbix') +zapi.login("Admin", "zabbix") -host_name = 'example.com' +host_name = "example.com" hosts = zapi.host.get(filter={"host": host_name}, selectInterfaces=["interfaceid"]) if hosts: @@ -23,16 +24,18 @@ try: item = zapi.item.create( hostid=host_id, - name='Used disk space on $1 in %', - key_='vfs.fs.size[/,pused]', + name="Used disk space on $1 in %", + key_="vfs.fs.size[/,pused]", type=0, value_type=3, interfaceid=hosts[0]["interfaces"][0]["interfaceid"], - delay=30 + delay=30, ) except ZabbixAPIException as e: print(e) sys.exit() - print("Added item with itemid {0} to host: {1}".format(item["itemids"][0], host_name)) + print( + "Added item with itemid {0} to host: {1}".format(item["itemids"][0], host_name) + ) else: print("No hosts found") diff --git a/examples/additemcsv.py b/examples/additemcsv.py index 9c5d443..38f3fce 100644 --- a/examples/additemcsv.py +++ b/examples/additemcsv.py @@ -14,15 +14,16 @@ ... """ -from pyzabbix import ZabbixAPI, ZabbixAPIException -import sys import csv -import os import getpass +import os +import sys + +from pyzabbix import ZabbixAPI, ZabbixAPIException host_name = sys.argv[1] arquivo = sys.argv[2] -open (arquivo, newline='', encoding='utf-8') +open(arquivo, newline="", encoding="utf-8") # Zabbix server @@ -41,38 +42,40 @@ hosts = zapi.host.get(filter={"host": host_name}, selectInterfaces=["interfaceid"]) if hosts: host_id = hosts[0]["hostid"] - print("host_name "+ host_name + " @ host id {0}".format(host_id)) - with open (arquivo, newline='', encoding='utf-8') as csvfile: #sys.argv[2]/'zbx_l15_k10.csv' + print("host_name " + host_name + " @ host id {0}".format(host_id)) + with open( + arquivo, newline="", encoding="utf-8" + ) as csvfile: # sys.argv[2]/'zbx_l15_k10.csv' reader = csv.DictReader(csvfile) for row in reader: try: - #print ("Add Item: " + row['key']) - item = zapi.item.create( + # print ("Add Item: " + row['key']) + item = zapi.item.create( hostid=host_id, - name = row['key'], - key_ = row['key'], - type=2, #0-Zabbix agent; 2-Zabbix trapper; 3-simple check; 5-Zabbix internal; - #7-Zabbix agent (active); 8-Zabbix aggregate; 9-web item; - #10-external check; 11-database monitor; 12-IPMI agent; - #13-SSH agent; 14-TELNET agent; 15-calculated; - #16-JMX agent; 17-SNMP trap; 18-Dependent item; 19-HTTP agent; 20-SNMP agent; 21-Script. - value_type=3, #0-numeric float; 1-character; 2-log; 3-numeric unsigned; 4-text. + name=row["key"], + key_=row["key"], + type=2, # 0-Zabbix agent; 2-Zabbix trapper; 3-simple check; 5-Zabbix internal; + # 7-Zabbix agent (active); 8-Zabbix aggregate; 9-web item; + # 10-external check; 11-database monitor; 12-IPMI agent; + # 13-SSH agent; 14-TELNET agent; 15-calculated; + # 16-JMX agent; 17-SNMP trap; 18-Dependent item; 19-HTTP agent; 20-SNMP agent; 21-Script. + value_type=3, # 0-numeric float; 1-character; 2-log; 3-numeric unsigned; 4-text. interfaceid=hosts[0]["interfaces"][0]["interfaceid"], delay=60, - status=1 #0-enabled item; 1-disabled item. - ) - add = add + 1; - if add > 0: - print("Adicionados: " + str(add)) + status=1, # 0-enabled item; 1-disabled item. + ) + add = add + 1 + if add > 0: + print("Adicionados: " + str(add)) except ZabbixAPIException as error: - nadd = nadd +1 + nadd = nadd + 1 if nadd > 0: print("Recusados: " + str(nadd)) - #print(error) + # print(error) if add > 0: - print("Total de itens adicionados: "+ str(add) + " itens.") + print("Total de itens adicionados: " + str(add) + " itens.") if nadd > 0: - print("Total de itens recusados: "+ str(nadd) + " itens.") + print("Total de itens recusados: " + str(nadd) + " itens.") sys.exit() else: print("No hosts found") diff --git a/examples/current_issues.py b/examples/current_issues.py index 5798f57..0a69933 100644 --- a/examples/current_issues.py +++ b/examples/current_issues.py @@ -5,42 +5,46 @@ from pyzabbix import ZabbixAPI # The hostname at which the Zabbix web interface is available -ZABBIX_SERVER = 'https://zabbix.example.com' +ZABBIX_SERVER = "https://zabbix.example.com" zapi = ZabbixAPI(ZABBIX_SERVER) # Login to the Zabbix API -zapi.login('api_username', 'api_password') +zapi.login("api_username", "api_password") # Get a list of all issues (AKA tripped triggers) -triggers = zapi.trigger.get(only_true=1, - skipDependent=1, - monitored=1, - active=1, - output='extend', - expandDescription=1, - selectHosts=['host'], - ) +triggers = zapi.trigger.get( + only_true=1, + skipDependent=1, + monitored=1, + active=1, + output="extend", + expandDescription=1, + selectHosts=["host"], +) # Do another query to find out which issues are Unacknowledged -unack_triggers = zapi.trigger.get(only_true=1, - skipDependent=1, - monitored=1, - active=1, - output='extend', - expandDescription=1, - selectHosts=['host'], - withLastEventUnacknowledged=1, - ) -unack_trigger_ids = [t['triggerid'] for t in unack_triggers] +unack_triggers = zapi.trigger.get( + only_true=1, + skipDependent=1, + monitored=1, + active=1, + output="extend", + expandDescription=1, + selectHosts=["host"], + withLastEventUnacknowledged=1, +) +unack_trigger_ids = [t["triggerid"] for t in unack_triggers] for t in triggers: - t['unacknowledged'] = True if t['triggerid'] in unack_trigger_ids \ - else False + t["unacknowledged"] = True if t["triggerid"] in unack_trigger_ids else False # Print a list containing only "tripped" triggers for t in triggers: - if int(t['value']) == 1: - print("{0} - {1} {2}".format(t['hosts'][0]['host'], - t['description'], - '(Unack)' if t['unacknowledged'] else '') - ) + if int(t["value"]) == 1: + print( + "{0} - {1} {2}".format( + t["hosts"][0]["host"], + t["description"], + "(Unack)" if t["unacknowledged"] else "", + ) + ) diff --git a/examples/export_history_csv.py b/examples/export_history_csv.py index 90d7b6b..900dc1d 100644 --- a/examples/export_history_csv.py +++ b/examples/export_history_csv.py @@ -1,13 +1,13 @@ # The research leading to these results has received funding from the # European Commission's Seventh Framework Programme (FP7/2007-13) # under grant agreement no 257386. -# http://www.bonfire-project.eu/ +# http://www.bonfire-project.eu/ # Copyright 2012 Yahya Al-Hazmi, TU Berlin # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, @@ -15,70 +15,72 @@ # See the License for the specific language governing permissions and # limitations under the License -from pyzabbix import ZabbixAPI -import sys +import argparse import datetime +import sys import time -import argparse + +from pyzabbix import ZabbixAPI def login(zapi, username, password): try: zapi.login(username, password) - print ("login succeed.") + print("login succeed.") except: - print ("zabbix server is not reachable: ") + print("zabbix server is not reachable: ") sys.exit() def getHostId(zapi, hostname, server): - if(hostname == ''): - print ('hostname is missed') + if hostname == "": + print("hostname is missed") sys.exit() - host = zapi.host.get(filter={"host":hostname}, output="extend") - if(len(host)==0): - print ("hostname: %s not found in zabbix server: %s, exit" % (hostname,server)) + host = zapi.host.get(filter={"host": hostname}, output="extend") + if len(host) == 0: + print("hostname: %s not found in zabbix server: %s, exit" % (hostname, server)) sys.exit() else: return host[0]["hostid"] def getItems(zapi, key, hostid, hostname): - items = zapi.item.get(search={"key_":key}, hostids=hostid , output="extend") - if(len(items)==0): - print ("item key: %s not found in hostname: %s" % (key,hostname)) + items = zapi.item.get(search={"key_": key}, hostids=hostid, output="extend") + if len(items) == 0: + print("item key: %s not found in hostname: %s" % (key, hostname)) sys.exit() else: return items def convertTimeStamp(inputTime): - if(inputTime == ''): - return '' + if inputTime == "": + return "" try: - tmpDate=datetime.datetime.strptime(inputTime,'%Y-%m-%d %H:%M:%S') + tmpDate = datetime.datetime.strptime(inputTime, "%Y-%m-%d %H:%M:%S") timestamp = int(time.mktime(tmpDate.timetuple())) except: - print ("time data %s does not match format Y-m-d H:M:S, exit" % (datetime)) + print("time data %s does not match format Y-m-d H:M:S, exit" % (datetime)) sys.exit() return timestamp def generateOutputFilename(output, hostname, key): - if(output == ''): - return hostname+"_"+key+".csv" + if output == "": + return hostname + "_" + key + ".csv" else: return output + def exportToCSV(historys, key, output): - f = open(output, 'w') + f = open(output, "w") inc = 0 - f.write('key;timestamp;valuei\n') # csv header + f.write("key;timestamp;valuei\n") # csv header for history in historys: - f.write('%s;%s;%s\n' % (key, history["clock"], history["value"] ) ) + f.write("%s;%s;%s\n" % (key, history["clock"], history["value"])) inc = inc + 1 - print( 'exported %i history to %s' % (inc, output) ) + print("exported %i history to %s" % (inc, output)) f.close() @@ -87,32 +89,34 @@ def assignTimeRange(inputParameters, datetime1, datetime2): timestamp2 = convertTimeStamp(datetime2) # only timestamp1 - if (timestamp1 and not timestamp2): + if timestamp1 and not timestamp2: inputParameters["time_from"] = timestamp1 - inputParameters["time_till"] = convertTimeStamp(time.time()) # current time + inputParameters["time_till"] = convertTimeStamp(time.time()) # current time # only timestamp2 - elif(not timestamp1 and timestamp2): + elif not timestamp1 and timestamp2: inputParameters["time_from"] = timestamp2 inputParameters["time_till"] = timestamp2 # no inserted both timestamps - elif(not timestamp1 and not timestamp2): - inputParameters["time_from"] = convertTimeStamp(time.time()) # current time - inputParameters["time_till"] = convertTimeStamp(time.time()) # current time + elif not timestamp1 and not timestamp2: + inputParameters["time_from"] = convertTimeStamp(time.time()) # current time + inputParameters["time_till"] = convertTimeStamp(time.time()) # current time # inserted both timestamps else: inputParameters["time_from"] = timestamp1 inputParameters["time_till"] = timestamp2 -def fetch_to_csv(username,password,server,hostname,key,output,datetime1,datetime2,debuglevel): +def fetch_to_csv( + username, password, server, hostname, key, output, datetime1, datetime2, debuglevel +): print("login to zabbix server %s" % server) - zapi = ZabbixAPI(server+'/zabbix') + zapi = ZabbixAPI(server + "/zabbix") login(zapi, username, password) hostid = getHostId(zapi, hostname, server) # find itemid using key - print ("key is: %s" %(key)) + print("key is: %s" % (key)) items = getItems(zapi, key, hostid, hostname) item = items[0] @@ -125,36 +129,69 @@ def fetch_to_csv(username,password,server,hostname,key,output,datetime1,datetime assignTimeRange(inputParameters, datetime1, datetime2) # get history - print('get history using this parameter') - print( inputParameters ) - history = zapi.history.get( **inputParameters ) + print("get history using this parameter") + print(inputParameters) + history = zapi.history.get(**inputParameters) # export to File output = generateOutputFilename(output, hostname, key) exportToCSV(history, key, output) -#Parsing Parameters -parser = argparse.ArgumentParser(description='Fetch history from aggregator and save it into CSV file') -parser.add_argument('-s', dest='server_IP', required=True, - help='aggregator IP address') -parser.add_argument('-n', dest='hostname', required=True, - help='name of the monitored host') -parser.add_argument('-u', dest='username', default='Admin', required=True, - help='zabbix username, default Admin') -parser.add_argument('-p', dest='password', default='zabbix', required=True, - help='zabbix password') -parser.add_argument('-k', dest='key',default='', required=True, - help='zabbix item key, if not specified the script will fetch all keys for the specified hostname') -parser.add_argument('-o', dest='output', default='', - help='output file name, default hostname.csv') -parser.add_argument('-t1', dest='datetime1', default='', - help='begin date-time, use this pattern \'2011-11-08 14:49:43\' if only t1 specified then time period will be t1-now ') -parser.add_argument('-t2', dest='datetime2', default='', - help='end date-time, use this pattern \'2011-11-08 14:49:43\'') -parser.add_argument('-v', dest='debuglevel', default=0, type=int, - help='log level, default 0') +# Parsing Parameters +parser = argparse.ArgumentParser( + description="Fetch history from aggregator and save it into CSV file" +) +parser.add_argument("-s", dest="server_IP", required=True, help="aggregator IP address") +parser.add_argument( + "-n", dest="hostname", required=True, help="name of the monitored host" +) +parser.add_argument( + "-u", + dest="username", + default="Admin", + required=True, + help="zabbix username, default Admin", +) +parser.add_argument( + "-p", dest="password", default="zabbix", required=True, help="zabbix password" +) +parser.add_argument( + "-k", + dest="key", + default="", + required=True, + help="zabbix item key, if not specified the script will fetch all keys for the specified hostname", +) +parser.add_argument( + "-o", dest="output", default="", help="output file name, default hostname.csv" +) +parser.add_argument( + "-t1", + dest="datetime1", + default="", + help="begin date-time, use this pattern '2011-11-08 14:49:43' if only t1 specified then time period will be t1-now ", +) +parser.add_argument( + "-t2", + dest="datetime2", + default="", + help="end date-time, use this pattern '2011-11-08 14:49:43'", +) +parser.add_argument( + "-v", dest="debuglevel", default=0, type=int, help="log level, default 0" +) args = parser.parse_args() -#Calling fetching function -fetch_to_csv(args.username, args.password, args.server_IP, args.hostname, args.key, args.output, args.datetime1,args.datetime2,args.debuglevel) +# Calling fetching function +fetch_to_csv( + args.username, + args.password, + args.server_IP, + args.hostname, + args.key, + args.output, + args.datetime1, + args.datetime2, + args.debuglevel, +) diff --git a/examples/fix_host_ips.py b/examples/fix_host_ips.py index cbc5220..9e4c5dc 100644 --- a/examples/fix_host_ips.py +++ b/examples/fix_host_ips.py @@ -7,10 +7,11 @@ """ import socket + from pyzabbix import ZabbixAPI, ZabbixAPIException # The hostname at which the Zabbix web interface is available -ZABBIX_SERVER = 'https://zabbix.example.com' +ZABBIX_SERVER = "https://zabbix.example.com" zapi = ZabbixAPI(ZABBIX_SERVER) @@ -18,37 +19,40 @@ zapi.session.verify = False # Login to the Zabbix API -zapi.login('Admin', 'zabbix') +zapi.login("Admin", "zabbix") # Loop through all hosts interfaces, getting only "main" interfaces of type "agent" -for h in zapi.hostinterface.get(output=["dns", "ip", "useip"], selectHosts=["host"], filter={"main": 1, "type": 1}): +for h in zapi.hostinterface.get( + output=["dns", "ip", "useip"], selectHosts=["host"], filter={"main": 1, "type": 1} +): # Make sure the hosts are named according to their FQDN - if h['dns'] != h['hosts'][0]['host']: - print('Warning: %s has dns "%s"' % (h['hosts'][0]['host'], h['dns'])) + if h["dns"] != h["hosts"][0]["host"]: + print('Warning: %s has dns "%s"' % (h["hosts"][0]["host"], h["dns"])) # Make sure they are using hostnames to connect rather than IPs (could be also filtered in the get request) - if h['useip'] == '1': - print('%s is using IP instead of hostname. Skipping.' % h['hosts'][0]['host']) + if h["useip"] == "1": + print("%s is using IP instead of hostname. Skipping." % h["hosts"][0]["host"]) continue # Do a DNS lookup for the host's DNS name try: - lookup = socket.gethostbyaddr(h['dns']) + lookup = socket.gethostbyaddr(h["dns"]) except socket.gaierror as e: - print(h['dns'], e) + print(h["dns"], e) continue actual_ip = lookup[2][0] # Check whether the looked-up IP matches the one stored in the host's IP # field - if actual_ip != h['ip']: - print("%s has the wrong IP: %s. Changing it to: %s" % (h['hosts'][0]['host'], - h['ip'], - actual_ip)) + if actual_ip != h["ip"]: + print( + "%s has the wrong IP: %s. Changing it to: %s" + % (h["hosts"][0]["host"], h["ip"], actual_ip) + ) # Set the host's IP field to match what the DNS lookup said it should # be try: - zapi.hostinterface.update(interfaceid=h['interfaceid'], ip=actual_ip) + zapi.hostinterface.update(interfaceid=h["interfaceid"], ip=actual_ip) except ZabbixAPIException as e: print(e) diff --git a/examples/history_data.py b/examples/history_data.py index b4f6cac..ba691b0 100644 --- a/examples/history_data.py +++ b/examples/history_data.py @@ -2,43 +2,50 @@ Retrieves history data for a given numeric (either int or float) item_id """ -from pyzabbix import ZabbixAPI -from datetime import datetime import time +from datetime import datetime + +from pyzabbix import ZabbixAPI # The hostname at which the Zabbix web interface is available -ZABBIX_SERVER = 'http://localhost/zabbix' +ZABBIX_SERVER = "http://localhost/zabbix" zapi = ZabbixAPI(ZABBIX_SERVER) # Login to the Zabbix API -zapi.login('Admin', 'zabbix') +zapi.login("Admin", "zabbix") -item_id = 'item_id' +item_id = "item_id" # Create a time range time_till = time.mktime(datetime.now().timetuple()) time_from = time_till - 60 * 60 * 4 # 4 hours # Query item's history (integer) data -history = zapi.history.get(itemids=[item_id], - time_from=time_from, - time_till=time_till, - output='extend', - limit='5000', - ) +history = zapi.history.get( + itemids=[item_id], + time_from=time_from, + time_till=time_till, + output="extend", + limit="5000", +) # If nothing was found, try getting it from history (float) data if not len(history): - history = zapi.history.get(itemids=[item_id], - time_from=time_from, - time_till=time_till, - output='extend', - limit='5000', - history=0, - ) + history = zapi.history.get( + itemids=[item_id], + time_from=time_from, + time_till=time_till, + output="extend", + limit="5000", + history=0, + ) # Print out each datapoint for point in history: - print("{0}: {1}".format(datetime.fromtimestamp(int(point['clock'])) - .strftime("%x %X"), point['value'])) + print( + "{0}: {1}".format( + datetime.fromtimestamp(int(point["clock"])).strftime("%x %X"), + point["value"], + ) + ) diff --git a/examples/import_templates.py b/examples/import_templates.py index e418870..ff287d1 100644 --- a/examples/import_templates.py +++ b/examples/import_templates.py @@ -2,101 +2,67 @@ Import Zabbix XML templates """ -from pyzabbix import ZabbixAPI, ZabbixAPIException import os import sys +from pyzabbix import ZabbixAPI, ZabbixAPIException + if len(sys.argv) <= 1: - print('Please provide directory with templates as first ARG or the XML file with template.') + print( + "Please provide directory with templates as first ARG or the XML file with template." + ) exit(1) path = sys.argv[1] # The hostname at which the Zabbix web interface is available -ZABBIX_SERVER = 'https://zabbix.example.org' +ZABBIX_SERVER = "https://zabbix.example.org" zapi = ZabbixAPI(ZABBIX_SERVER) # Login to the Zabbix API -#zapi.session.verify = False +# zapi.session.verify = False zapi.login("Admin", "zabbix") rules = { - 'applications': { - 'createMissing': True, - }, - 'discoveryRules': { - 'createMissing': True, - 'updateExisting': True - }, - 'graphs': { - 'createMissing': True, - 'updateExisting': True - }, - 'groups': { - 'createMissing': True - }, - 'hosts': { - 'createMissing': True, - 'updateExisting': True - }, - 'images': { - 'createMissing': True, - 'updateExisting': True - }, - 'items': { - 'createMissing': True, - 'updateExisting': True - }, - 'maps': { - 'createMissing': True, - 'updateExisting': True - }, - 'screens': { - 'createMissing': True, - 'updateExisting': True - }, - 'templateLinkage': { - 'createMissing': True, - }, - 'templates': { - 'createMissing': True, - 'updateExisting': True - }, - 'templateScreens': { - 'createMissing': True, - 'updateExisting': True - }, - 'triggers': { - 'createMissing': True, - 'updateExisting': True - }, - 'valueMaps': { - 'createMissing': True, - 'updateExisting': True + "applications": { + "createMissing": True, }, + "discoveryRules": {"createMissing": True, "updateExisting": True}, + "graphs": {"createMissing": True, "updateExisting": True}, + "groups": {"createMissing": True}, + "hosts": {"createMissing": True, "updateExisting": True}, + "images": {"createMissing": True, "updateExisting": True}, + "items": {"createMissing": True, "updateExisting": True}, + "maps": {"createMissing": True, "updateExisting": True}, + "screens": {"createMissing": True, "updateExisting": True}, + "templateLinkage": {"createMissing": True}, + "templates": {"createMissing": True, "updateExisting": True}, + "templateScreens": {"createMissing": True, "updateExisting": True}, + "triggers": {"createMissing": True, "updateExisting": True}, + "valueMaps": {"createMissing": True, "updateExisting": True}, } if os.path.isdir(path): - #path = path/*.xml - files = glob.glob(path+'/*.xml') + # path = path/*.xml + files = glob.glob(path + "/*.xml") for file in files: print(file) - with open(file, 'r') as f: + with open(file, "r") as f: template = f.read() try: - zapi.confimport('xml', template, rules) + zapi.confimport("xml", template, rules) except ZabbixAPIException as e: print(e) - print('') + print("") elif os.path.isfile(path): files = glob.glob(path) for file in files: - with open(file, 'r') as f: + with open(file, "r") as f: template = f.read() try: - zapi.confimport('xml', template, rules) + zapi.confimport("xml", template, rules) except ZabbixAPIException as e: print(e) else: - print('I need a xml file') + print("I need a xml file") diff --git a/examples/timeout.py b/examples/timeout.py index 7b30b6b..58617ab 100644 --- a/examples/timeout.py +++ b/examples/timeout.py @@ -5,7 +5,7 @@ from pyzabbix import ZabbixAPI # The hostname at which the Zabbix web interface is available -ZABBIX_SERVER = 'https://zabbix.example.com' +ZABBIX_SERVER = "https://zabbix.example.com" # Timeout (float) in seconds # By default this timeout affects both the "connect" and "read", but @@ -17,7 +17,7 @@ zapi = ZabbixAPI(ZABBIX_SERVER, timeout=TIMEOUT) # Login to the Zabbix API -zapi.login('api_username', 'api_password') +zapi.login("api_username", "api_password") # Or you can re-define it after zapi.timeout = TIMEOUT diff --git a/examples/with_context.py b/examples/with_context.py index bebd936..97f4fde 100644 --- a/examples/with_context.py +++ b/examples/with_context.py @@ -4,11 +4,11 @@ from pyzabbix import ZabbixAPI -ZABBIX_SERVER = 'https://zabbix.example.com' +ZABBIX_SERVER = "https://zabbix.example.com" # Use context manager to auto-logout after request is done. with ZabbixAPI(ZABBIX_SERVER) as zapi: - zapi.login('api_username', 'api_password') - hosts = zapi.host.get(output=['name']) + zapi.login("api_username", "api_password") + hosts = zapi.host.get(output=["name"]) for host in hosts: - print(host['name']) + print(host["name"]) diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py index cb121f6..a0dc974 100644 --- a/pyzabbix/__init__.py +++ b/pyzabbix/__init__.py @@ -1,6 +1,7 @@ +import json import logging + import requests -import json import semantic_version @@ -8,12 +9,13 @@ class _NullHandler(logging.Handler): def emit(self, record): pass + logger = logging.getLogger(__name__) logger.addHandler(_NullHandler()) class ZabbixAPIException(Exception): - """ generic zabbix api exception + """generic zabbix api exception code list: -32700 - invalid JSON. An error occurred on the server while parsing the JSON text (typo, wrong quotes, etc.) -32600 - received JSON is not a valid JSON-RPC Request @@ -24,6 +26,7 @@ class ZabbixAPIException(Exception): -32300 - Transport error -32500 - Application error """ + def __init__(self, *args, **kwargs): super(ZabbixAPIException, self).__init__(*args) @@ -31,12 +34,14 @@ def __init__(self, *args, **kwargs): class ZabbixAPI(object): - def __init__(self, - server='http://localhost/zabbix', - session=None, - use_authenticate=False, - timeout=None, - detect_version=True): + def __init__( + self, + server="http://localhost/zabbix", + session=None, + use_authenticate=False, + timeout=None, + detect_version=True, + ): """ Parameters: server: Base URI for zabbix web interface (omitting /api_jsonrpc.php) @@ -52,23 +57,29 @@ def __init__(self, self.session = requests.Session() # Default headers for all requests - self.session.headers.update({ - 'Content-Type': 'application/json-rpc', - 'User-Agent': 'python/pyzabbix', - 'Cache-Control': 'no-cache' - }) + self.session.headers.update( + { + "Content-Type": "application/json-rpc", + "User-Agent": "python/pyzabbix", + "Cache-Control": "no-cache", + } + ) self.use_authenticate = use_authenticate self.use_api_token = False - self.auth = '' + self.auth = "" self.id = 0 self.timeout = timeout - self.url = server + '/api_jsonrpc.php' if not server.endswith('/api_jsonrpc.php') else server + self.url = ( + server + "/api_jsonrpc.php" + if not server.endswith("/api_jsonrpc.php") + else server + ) logger.info("JSON-RPC Server Endpoint: %s", self.url) - self.version = '' + self.version = "" self._detect_version = detect_version def __enter__(self): @@ -77,23 +88,21 @@ def __enter__(self): def __exit__(self, exception_type, exception_value, traceback): if isinstance(exception_value, (ZabbixAPIException, type(None))): if self.is_authenticated and not self.use_api_token: - """ Logout the user if they are authenticated using username + password.""" + """Logout the user if they are authenticated using username + password.""" self.user.logout() return True - def login(self, user='', password='', api_token=None): + def login(self, user="", password="", api_token=None): """Convenience method for calling user.authenticate and storing the resulting auth token - for further commands. - If use_authenticate is set, it uses the older (Zabbix 1.8) authentication command - :param password: Password used to login into Zabbix - :param user: Username used to login into Zabbix - :param api_token: API Token to authenticate with + for further commands. + If use_authenticate is set, it uses the older (Zabbix 1.8) authentication command + :param password: Password used to login into Zabbix + :param user: Username used to login into Zabbix + :param api_token: API Token to authenticate with """ if self._detect_version: - self.version = semantic_version.Version( - self.api_version() - ) + self.version = semantic_version.Version(self.api_version()) logger.info("Zabbix API version is: %s", str(self.version)) # If the API token is explicitly provided, use this instead. @@ -104,10 +113,10 @@ def login(self, user='', password='', api_token=None): # If we have an invalid auth token, we are not allowed to send a login # request. Clear it before trying. - self.auth = '' + self.auth = "" if self.use_authenticate: self.auth = self.user.authenticate(user=user, password=password) - elif self.version and self.version >= semantic_version.Version('5.4.0'): + elif self.version and self.version >= semantic_version.Version("5.4.0"): self.auth = self.user.login(username=user, password=password) else: self.auth = self.user.login(user=user, password=password) @@ -131,41 +140,43 @@ def is_authenticated(self): return False return True - def confimport(self, confformat='', source='', rules=''): + def confimport(self, confformat="", source="", rules=""): """Alias for configuration.import because it clashes with - Python's import reserved keyword - :param rules: - :param source: - :param confformat: + Python's import reserved keyword + :param rules: + :param source: + :param confformat: """ return self.do_request( method="configuration.import", - params={"format": confformat, "source": source, "rules": rules} - )['result'] + params={"format": confformat, "source": source, "rules": rules}, + )["result"] def api_version(self): return self.apiinfo.version() def do_request(self, method, params=None): request_json = { - 'jsonrpc': '2.0', - 'method': method, - 'params': params or {}, - 'id': self.id, + "jsonrpc": "2.0", + "method": method, + "params": params or {}, + "id": self.id, } # We don't have to pass the auth token if asking for the apiinfo.version or user.checkAuthentication - if self.auth and method != 'apiinfo.version' and method != 'user.checkAuthentication': - request_json['auth'] = self.auth - - logger.debug("Sending: %s", json.dumps(request_json, - indent=4, - separators=(',', ': '))) + if ( + self.auth + and method != "apiinfo.version" + and method != "user.checkAuthentication" + ): + request_json["auth"] = self.auth + + logger.debug( + "Sending: %s", json.dumps(request_json, indent=4, separators=(",", ": ")) + ) response = self.session.post( - self.url, - data=json.dumps(request_json), - timeout=self.timeout + self.url, data=json.dumps(request_json), timeout=self.timeout ) logger.debug("Response Code: %s", str(response.status_code)) @@ -179,24 +190,27 @@ def do_request(self, method, params=None): try: response_json = json.loads(response.text) except ValueError: - raise ZabbixAPIException( - "Unable to parse json: %s" % response.text - ) - logger.debug("Response Body: %s", json.dumps(response_json, - indent=4, - separators=(',', ': '))) + raise ZabbixAPIException("Unable to parse json: %s" % response.text) + logger.debug( + "Response Body: %s", + json.dumps(response_json, indent=4, separators=(",", ": ")), + ) self.id += 1 - if 'error' in response_json: # some exception - if 'data' not in response_json['error']: # some errors don't contain 'data': workaround for ZBX-9340 - response_json['error']['data'] = "No data" - msg = u"Error {code}: {message}, {data}".format( - code=response_json['error']['code'], - message=response_json['error']['message'], - data=response_json['error']['data'] + if "error" in response_json: # some exception + if ( + "data" not in response_json["error"] + ): # some errors don't contain 'data': workaround for ZBX-9340 + response_json["error"]["data"] = "No data" + msg = "Error {code}: {message}, {data}".format( + code=response_json["error"]["code"], + message=response_json["error"]["message"], + data=response_json["error"]["data"], + ) + raise ZabbixAPIException( + msg, response_json["error"]["code"], error=response_json["error"] ) - raise ZabbixAPIException(msg, response_json['error']['code'], error=response_json['error']) return response_json @@ -218,8 +232,7 @@ def fn(*args, **kwargs): raise TypeError("Found both args and kwargs") return self.parent.do_request( - '{0}.{1}'.format(self.name, attr), - args or kwargs - )['result'] + "{0}.{1}".format(self.name, attr), args or kwargs + )["result"] return fn diff --git a/tests/api_test.py b/tests/api_test.py index ac5c445..13d1fc7 100644 --- a/tests/api_test.py +++ b/tests/api_test.py @@ -1,44 +1,45 @@ +import json import unittest + import httpretty -import json -from pyzabbix import ZabbixAPI import semantic_version +from pyzabbix import ZabbixAPI -class TestPyZabbix(unittest.TestCase): +class TestPyZabbix(unittest.TestCase): @httpretty.activate def test_login(self): httpretty.register_uri( httpretty.POST, "http://example.com/api_jsonrpc.php", - body=json.dumps({ - "jsonrpc": "2.0", - "result": "0424bd59b807674191e7d77572075f33", - "id": 0 - }), + body=json.dumps( + { + "jsonrpc": "2.0", + "result": "0424bd59b807674191e7d77572075f33", + "id": 0, + } + ), ) - zapi = ZabbixAPI('http://example.com', detect_version=False) - zapi.login('mylogin', 'mypass') + zapi = ZabbixAPI("http://example.com", detect_version=False) + zapi.login("mylogin", "mypass") # Check request self.assertEqual( - json.loads(httpretty.last_request().body.decode('utf-8')), + json.loads(httpretty.last_request().body.decode("utf-8")), { - 'jsonrpc': '2.0', - 'method': 'user.login', - 'params': {'user': 'mylogin', 'password': 'mypass'}, - 'id': 0, - } + "jsonrpc": "2.0", + "method": "user.login", + "params": {"user": "mylogin", "password": "mypass"}, + "id": 0, + }, ) self.assertEqual( - httpretty.last_request().headers['content-type'], - 'application/json-rpc' + httpretty.last_request().headers["content-type"], "application/json-rpc" ) self.assertEqual( - httpretty.last_request().headers['user-agent'], - 'python/pyzabbix' + httpretty.last_request().headers["user-agent"], "python/pyzabbix" ) # Check response @@ -49,27 +50,23 @@ def test_host_get(self): httpretty.register_uri( httpretty.POST, "http://example.com/api_jsonrpc.php", - body=json.dumps({ - "jsonrpc": "2.0", - "result": [{"hostid": 1234}], - "id": 0 - }), + body=json.dumps({"jsonrpc": "2.0", "result": [{"hostid": 1234}], "id": 0}), ) - zapi = ZabbixAPI('http://example.com', detect_version=False) + zapi = ZabbixAPI("http://example.com", detect_version=False) zapi.auth = "123" result = zapi.host.get() # Check request self.assertEqual( - json.loads(httpretty.last_request().body.decode('utf-8')), + json.loads(httpretty.last_request().body.decode("utf-8")), { - 'jsonrpc': '2.0', - 'method': 'host.get', - 'params': {}, - 'auth': '123', - 'id': 0, - } + "jsonrpc": "2.0", + "method": "host.get", + "params": {}, + "auth": "123", + "id": 0, + }, ) # Check response @@ -80,52 +77,55 @@ def test_host_delete(self): httpretty.register_uri( httpretty.POST, "http://example.com/api_jsonrpc.php", - body=json.dumps({ - "jsonrpc": "2.0", - "result": { - "itemids": [ - "22982", - "22986" - ] - }, - "id": 0 - }), + body=json.dumps( + { + "jsonrpc": "2.0", + "result": { + "itemids": [ + "22982", + "22986", + ] + }, + "id": 0, + } + ), ) - zapi = ZabbixAPI('http://example.com', detect_version=False) + zapi = ZabbixAPI("http://example.com", detect_version=False) zapi.auth = "123" result = zapi.host.delete("22982", "22986") # Check request self.assertEqual( - json.loads(httpretty.last_request().body.decode('utf-8')), + json.loads(httpretty.last_request().body.decode("utf-8")), { - 'jsonrpc': '2.0', - 'method': 'host.delete', - 'params': ["22982", "22986"], - 'auth': '123', - 'id': 0, - } + "jsonrpc": "2.0", + "method": "host.delete", + "params": ["22982", "22986"], + "auth": "123", + "id": 0, + }, ) # Check response self.assertEqual(set(result["itemids"]), set(["22982", "22986"])) - @httpretty.activate def test_login_with_context(self): httpretty.register_uri( httpretty.POST, "http://example.com/api_jsonrpc.php", - body=json.dumps({ - "jsonrpc": "2.0", - "result": "0424bd59b807674191e7d77572075f33", - "id": 0 - }), + body=json.dumps( + { + "jsonrpc": "2.0", + "result": "0424bd59b807674191e7d77572075f33", + "id": 0, + } + ), ) - with ZabbixAPI('http://example.com', detect_version=False) as zapi: - zapi.login('mylogin', 'mypass') + with ZabbixAPI("http://example.com", detect_version=False) as zapi: + zapi.login("mylogin", "mypass") self.assertEqual(zapi.auth, "0424bd59b807674191e7d77572075f33") @httpretty.activate @@ -133,12 +133,14 @@ def test_detecting_version(self): httpretty.register_uri( httpretty.POST, "http://example.com/api_jsonrpc.php", - body=json.dumps({ - "jsonrpc": "2.0", - "result": "4.0.0", - "id": 0 - }), + body=json.dumps( + { + "jsonrpc": "2.0", + "result": "4.0.0", + "id": 0, + } + ), ) - zapi_detect = ZabbixAPI('http://example.com') - self.assertEqual(zapi_detect.api_version(), '4.0.0') + zapi_detect = ZabbixAPI("http://example.com") + self.assertEqual(zapi_detect.api_version(), "4.0.0") From 9113d4495c2e50fdac9988dc50e603c4298c42da Mon Sep 17 00:00:00 2001 From: jo Date: Thu, 21 Jul 2022 18:19:17 +0200 Subject: [PATCH 053/145] chore: update ci badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3b00e5b..84273e8 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ **PyZabbix** is a Python module for working with the [Zabbix API](https://www.zabbix.com/documentation/current/manual/api/reference). -[![Build Status](https://travis-ci.org/lukecyca/pyzabbix.png?branch=master)](https://travis-ci.org/lukecyca/pyzabbix) +[![CI](https://github.com/lukecyca/pyzabbix/actions/workflows/ci.yml/badge.svg)](https://github.com/lukecyca/pyzabbix/actions/workflows/ci.yml) [![PyPi version](https://img.shields.io/pypi/v/pyzabbix.svg)](https://pypi.python.org/pypi/pyzabbix/) ## Requirements From 64e876dc139b85565d4556155c9cd536c24bbdbe Mon Sep 17 00:00:00 2001 From: jo Date: Thu, 21 Jul 2022 17:51:52 +0200 Subject: [PATCH 054/145] feat: require >=python3.6 Drop support for python versions below python3.6 --- .github/workflows/ci.yml | 2 +- setup.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 82b7a8c..187fc0a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - python-version: ["3.7", "3.8", "3.9", "3.10"] + python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"] steps: - uses: actions/checkout@v3 diff --git a/setup.py b/setup.py index e1e88c6..b171dcf 100644 --- a/setup.py +++ b/setup.py @@ -16,11 +16,11 @@ url="http://github.com/lukecyca/pyzabbix", classifiers=[ "Programming Language :: Python", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3.4", - "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", "Operating System :: OS Independent", "Development Status :: 4 - Beta", @@ -30,6 +30,7 @@ "Topic :: System :: Systems Administration", ], packages=["pyzabbix"], + python_requires=">=3.6", install_requires=[ "requests>=1.0", "semantic-version>=2.8", From 0fd44b62956d58cd80b93607cf506ff3ec7e64f8 Mon Sep 17 00:00:00 2001 From: jo Date: Thu, 21 Jul 2022 17:52:54 +0200 Subject: [PATCH 055/145] chore: setup pyupgrade pre-commit hook --- .pre-commit-config.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a5bb8d9..60d0e3f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -34,3 +34,9 @@ repos: hooks: - id: codespell exclude: ^examples/additemcsv.py$ + + - repo: https://github.com/asottile/pyupgrade + rev: v2.34.0 + hooks: + - id: pyupgrade + args: [--py3-plus, --py36-plus] From 3b1c79a0d9cc9eabb08caed01aee826e76e035aa Mon Sep 17 00:00:00 2001 From: jo Date: Fri, 22 Jul 2022 11:18:58 +0200 Subject: [PATCH 056/145] chore: upgrade code to python3.6 --- examples/add_item.py | 6 ++---- examples/additemcsv.py | 2 +- examples/current_issues.py | 2 +- examples/export_history_csv.py | 6 +++--- examples/fix_host_ips.py | 2 +- examples/history_data.py | 2 +- examples/import_templates.py | 4 ++-- pyzabbix/__init__.py | 12 ++++++------ setup.py | 2 +- tests/api_test.py | 2 +- 10 files changed, 19 insertions(+), 21 deletions(-) diff --git a/examples/add_item.py b/examples/add_item.py index 41a94df..fdc8b37 100644 --- a/examples/add_item.py +++ b/examples/add_item.py @@ -19,7 +19,7 @@ hosts = zapi.host.get(filter={"host": host_name}, selectInterfaces=["interfaceid"]) if hosts: host_id = hosts[0]["hostid"] - print("Found host id {0}".format(host_id)) + print(f"Found host id {host_id}") try: item = zapi.item.create( @@ -34,8 +34,6 @@ except ZabbixAPIException as e: print(e) sys.exit() - print( - "Added item with itemid {0} to host: {1}".format(item["itemids"][0], host_name) - ) + print("Added item with itemid {} to host: {}".format(item["itemids"][0], host_name)) else: print("No hosts found") diff --git a/examples/additemcsv.py b/examples/additemcsv.py index 38f3fce..5e024c8 100644 --- a/examples/additemcsv.py +++ b/examples/additemcsv.py @@ -42,7 +42,7 @@ hosts = zapi.host.get(filter={"host": host_name}, selectInterfaces=["interfaceid"]) if hosts: host_id = hosts[0]["hostid"] - print("host_name " + host_name + " @ host id {0}".format(host_id)) + print("host_name " + host_name + f" @ host id {host_id}") with open( arquivo, newline="", encoding="utf-8" ) as csvfile: # sys.argv[2]/'zbx_l15_k10.csv' diff --git a/examples/current_issues.py b/examples/current_issues.py index 0a69933..3e3f104 100644 --- a/examples/current_issues.py +++ b/examples/current_issues.py @@ -42,7 +42,7 @@ for t in triggers: if int(t["value"]) == 1: print( - "{0} - {1} {2}".format( + "{} - {} {}".format( t["hosts"][0]["host"], t["description"], "(Unack)" if t["unacknowledged"] else "", diff --git a/examples/export_history_csv.py b/examples/export_history_csv.py index 900dc1d..b46e450 100644 --- a/examples/export_history_csv.py +++ b/examples/export_history_csv.py @@ -38,7 +38,7 @@ def getHostId(zapi, hostname, server): sys.exit() host = zapi.host.get(filter={"host": hostname}, output="extend") if len(host) == 0: - print("hostname: %s not found in zabbix server: %s, exit" % (hostname, server)) + print(f"hostname: {hostname} not found in zabbix server: {server}, exit") sys.exit() else: return host[0]["hostid"] @@ -47,7 +47,7 @@ def getHostId(zapi, hostname, server): def getItems(zapi, key, hostid, hostname): items = zapi.item.get(search={"key_": key}, hostids=hostid, output="extend") if len(items) == 0: - print("item key: %s not found in hostname: %s" % (key, hostname)) + print(f"item key: {key} not found in hostname: {hostname}") sys.exit() else: return items @@ -78,7 +78,7 @@ def exportToCSV(historys, key, output): inc = 0 f.write("key;timestamp;valuei\n") # csv header for history in historys: - f.write("%s;%s;%s\n" % (key, history["clock"], history["value"])) + f.write("{};{};{}\n".format(key, history["clock"], history["value"])) inc = inc + 1 print("exported %i history to %s" % (inc, output)) f.close() diff --git a/examples/fix_host_ips.py b/examples/fix_host_ips.py index 9e4c5dc..17f1b0c 100644 --- a/examples/fix_host_ips.py +++ b/examples/fix_host_ips.py @@ -27,7 +27,7 @@ ): # Make sure the hosts are named according to their FQDN if h["dns"] != h["hosts"][0]["host"]: - print('Warning: %s has dns "%s"' % (h["hosts"][0]["host"], h["dns"])) + print('Warning: {} has dns "{}"'.format(h["hosts"][0]["host"], h["dns"])) # Make sure they are using hostnames to connect rather than IPs (could be also filtered in the get request) if h["useip"] == "1": diff --git a/examples/history_data.py b/examples/history_data.py index ba691b0..b0b2e44 100644 --- a/examples/history_data.py +++ b/examples/history_data.py @@ -44,7 +44,7 @@ # Print out each datapoint for point in history: print( - "{0}: {1}".format( + "{}: {}".format( datetime.fromtimestamp(int(point["clock"])).strftime("%x %X"), point["value"], ) diff --git a/examples/import_templates.py b/examples/import_templates.py index ff287d1..9cf6022 100644 --- a/examples/import_templates.py +++ b/examples/import_templates.py @@ -48,7 +48,7 @@ files = glob.glob(path + "/*.xml") for file in files: print(file) - with open(file, "r") as f: + with open(file) as f: template = f.read() try: zapi.confimport("xml", template, rules) @@ -58,7 +58,7 @@ elif os.path.isfile(path): files = glob.glob(path) for file in files: - with open(file, "r") as f: + with open(file) as f: template = f.read() try: zapi.confimport("xml", template, rules) diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py index a0dc974..89620ac 100644 --- a/pyzabbix/__init__.py +++ b/pyzabbix/__init__.py @@ -28,12 +28,12 @@ class ZabbixAPIException(Exception): """ def __init__(self, *args, **kwargs): - super(ZabbixAPIException, self).__init__(*args) + super().__init__(*args) self.error = kwargs.get("error", None) -class ZabbixAPI(object): +class ZabbixAPI: def __init__( self, server="http://localhost/zabbix", @@ -219,7 +219,7 @@ def __getattr__(self, attr): return ZabbixAPIObjectClass(attr, self) -class ZabbixAPIObjectClass(object): +class ZabbixAPIObjectClass: def __init__(self, name, parent): self.name = name self.parent = parent @@ -231,8 +231,8 @@ def fn(*args, **kwargs): if args and kwargs: raise TypeError("Found both args and kwargs") - return self.parent.do_request( - "{0}.{1}".format(self.name, attr), args or kwargs - )["result"] + return self.parent.do_request(f"{self.name}.{attr}", args or kwargs)[ + "result" + ] return fn diff --git a/setup.py b/setup.py index b171dcf..b9f8129 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ from setuptools import setup -with open("README.md", "r") as fh: +with open("README.md") as fh: long_description = fh.read() setup( diff --git a/tests/api_test.py b/tests/api_test.py index 13d1fc7..ac59646 100644 --- a/tests/api_test.py +++ b/tests/api_test.py @@ -108,7 +108,7 @@ def test_host_delete(self): ) # Check response - self.assertEqual(set(result["itemids"]), set(["22982", "22986"])) + self.assertEqual(set(result["itemids"]), {"22982", "22986"}) @httpretty.activate def test_login_with_context(self): From 333c27d313a82341103508dafe2de56594636e29 Mon Sep 17 00:00:00 2001 From: jo Date: Fri, 22 Jul 2022 21:33:23 +0200 Subject: [PATCH 057/145] test: assert empty response raises --- tests/api_test.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/api_test.py b/tests/api_test.py index ac59646..d3e4f8c 100644 --- a/tests/api_test.py +++ b/tests/api_test.py @@ -2,9 +2,10 @@ import unittest import httpretty +import pytest import semantic_version -from pyzabbix import ZabbixAPI +from pyzabbix import ZabbixAPI, ZabbixAPIException class TestPyZabbix(unittest.TestCase): @@ -144,3 +145,16 @@ def test_detecting_version(self): zapi_detect = ZabbixAPI("http://example.com") self.assertEqual(zapi_detect.api_version(), "4.0.0") + + +@httpretty.activate +def test_empty_response(): + httpretty.register_uri( + httpretty.POST, + "http://example.com/api_jsonrpc.php", + body="", + ) + + zapi = ZabbixAPI("http://example.com") + with pytest.raises(ZabbixAPIException, match="Received empty response"): + zapi.login("mylogin", "mypass") From 993a639a8be409cbc88bf43831f14dfc5c276377 Mon Sep 17 00:00:00 2001 From: jo Date: Fri, 22 Jul 2022 11:32:11 +0200 Subject: [PATCH 058/145] chore: pyproject.toml with setuptools backend --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..9787c3b --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta" From af691a71bd723b38a35d2198f9b24b7a7d9d7fa1 Mon Sep 17 00:00:00 2001 From: jo Date: Thu, 21 Jul 2022 17:47:53 +0200 Subject: [PATCH 059/145] chore: setup linting using pylint --- Makefile | 6 ++++++ pyproject.toml | 7 +++++++ setup.py | 1 + 3 files changed, 14 insertions(+) diff --git a/Makefile b/Makefile index 4559a91..925afce 100644 --- a/Makefile +++ b/Makefile @@ -22,6 +22,12 @@ format: $(VENV) black . isort . --profile black +lint: $(VENV) + source $(VENV)/bin/activate + black . --check + isort . --profile black --check + pylint --jobs=$(CPU_CORES) --output-format=colorized pyzabbix tests + test: $(VENV) source $(VENV)/bin/activate pytest -v \ diff --git a/pyproject.toml b/pyproject.toml index 9787c3b..7dee18d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,10 @@ +[tool.pylint.messages_control] +disable = [ + "missing-class-docstring", + "missing-function-docstring", + "missing-module-docstring", +] + [build-system] requires = ["setuptools", "wheel"] build-backend = "setuptools.build_meta" diff --git a/setup.py b/setup.py index b9f8129..6f7dde3 100644 --- a/setup.py +++ b/setup.py @@ -40,6 +40,7 @@ "black", "httpretty<0.8.7", "isort", + "pylint", "pytest-cov", "pytest-xdist", "pytest", From 976a87eef6a906e9d1687b72101e991cc31517fb Mon Sep 17 00:00:00 2001 From: jo Date: Thu, 21 Jul 2022 17:49:11 +0200 Subject: [PATCH 060/145] ci: add job to enforce linting --- .github/workflows/ci.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 187fc0a..058bf8d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,6 +19,25 @@ jobs: - uses: pre-commit/action@v3.0.0 + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-python@v4 + with: + python-version: 3.x + + - uses: actions/cache@v3 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }} + restore-keys: | + ${{ runner.os }}-pip- + + - run: make install + - run: make lint + test: runs-on: ubuntu-latest strategy: From d7e59385b17e02c9d01f931bfb9a5b921f9676de Mon Sep 17 00:00:00 2001 From: jo Date: Fri, 22 Jul 2022 11:33:00 +0200 Subject: [PATCH 061/145] refactor: fix linting errors --- pyzabbix/__init__.py | 93 +++++++++++++++++++++++++------------------- tests/api_test.py | 1 - 2 files changed, 53 insertions(+), 41 deletions(-) diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py index 89620ac..7d873b5 100644 --- a/pyzabbix/__init__.py +++ b/pyzabbix/__init__.py @@ -15,16 +15,18 @@ def emit(self, record): class ZabbixAPIException(Exception): - """generic zabbix api exception - code list: - -32700 - invalid JSON. An error occurred on the server while parsing the JSON text (typo, wrong quotes, etc.) - -32600 - received JSON is not a valid JSON-RPC Request - -32601 - requested remote-procedure does not exist - -32602 - invalid method parameters - -32603 - Internal JSON-RPC error - -32400 - System error - -32300 - Transport error - -32500 - Application error + """Generic Zabbix API exception + + Codes: + - 32700: invalid JSON. An error occurred on the server while + parsing the JSON text (typo, wrong quotes, etc.) + - 32600: received JSON is not a valid JSON-RPC Request + - 32601: requested remote-procedure does not exist + - 32602: invalid method parameters + - 32603: Internal JSON-RPC error + - 32400: System error + - 32300: Transport error + - 32500: Application error """ def __init__(self, *args, **kwargs): @@ -33,7 +35,9 @@ def __init__(self, *args, **kwargs): self.error = kwargs.get("error", None) +# pylint: disable=too-many-instance-attributes class ZabbixAPI: + # pylint: disable=too-many-arguments def __init__( self, server="http://localhost/zabbix", @@ -43,12 +47,14 @@ def __init__( detect_version=True, ): """ - Parameters: - server: Base URI for zabbix web interface (omitting /api_jsonrpc.php) - session: optional pre-configured requests.Session instance - use_authenticate: Use old (Zabbix 1.8) style authentication - timeout: optional connect and read timeout in seconds, default: None (if you're using Requests >= 2.4 you can set it as tuple: "(connect, read)" which is used to set individual connect and read timeouts.) - detect_version: autodetect Zabbix API version + :param server: Base URI for zabbix web interface (omitting /api_jsonrpc.php) + :param session: optional pre-configured requests.Session instance + :param use_authenticate: Use old (Zabbix 1.8) style authentication + :param timeout: optional connect and read timeout in seconds, default: None + If you're using Requests >= 2.4 you can set it as + tuple: "(connect, read)" which is used to set individual + connect and read timeouts. + :param detect_version: autodetect Zabbix API version """ if session: @@ -68,7 +74,7 @@ def __init__( self.use_authenticate = use_authenticate self.use_api_token = False self.auth = "" - self.id = 0 + self.id = 0 # pylint: disable=invalid-name self.timeout = timeout @@ -85,17 +91,21 @@ def __init__( def __enter__(self): return self + # pylint: disable=inconsistent-return-statements def __exit__(self, exception_type, exception_value, traceback): if isinstance(exception_value, (ZabbixAPIException, type(None))): if self.is_authenticated and not self.use_api_token: - """Logout the user if they are authenticated using username + password.""" + # Logout the user if they are authenticated using username + password. self.user.logout() return True def login(self, user="", password="", api_token=None): - """Convenience method for calling user.authenticate and storing the resulting auth token - for further commands. - If use_authenticate is set, it uses the older (Zabbix 1.8) authentication command + """Convenience method for calling user.authenticate + and storing the resulting auth token for further commands. + + If use_authenticate is set, it uses the older (Zabbix 1.8) + authentication command + :param password: Password used to login into Zabbix :param user: Username used to login into Zabbix :param api_token: API Token to authenticate with @@ -123,15 +133,15 @@ def login(self, user="", password="", api_token=None): def check_authentication(self): if self.use_api_token: - """We cannot use this call using an API Token""" + # We cannot use this call using an API Token return True - """Convenience method for calling user.checkAuthentication of the current session""" + # Convenience method for calling user.checkAuthentication of the current session return self.user.checkAuthentication(sessionid=self.auth) @property def is_authenticated(self): if self.use_api_token: - """We cannot use this call using an API Token""" + # We cannot use this call using an API Token return True try: @@ -164,7 +174,8 @@ def do_request(self, method, params=None): "id": self.id, } - # We don't have to pass the auth token if asking for the apiinfo.version or user.checkAuthentication + # We don't have to pass the auth token if asking for + # the apiinfo.version or user.checkAuthentication if ( self.auth and method != "apiinfo.version" @@ -184,13 +195,15 @@ def do_request(self, method, params=None): # list of allowed headers. response.raise_for_status() - if not len(response.text): + if not response.text: raise ZabbixAPIException("Received empty response") try: response_json = json.loads(response.text) - except ValueError: - raise ZabbixAPIException("Unable to parse json: %s" % response.text) + except ValueError as exception: + raise ZabbixAPIException( + f"Unable to parse json: {response.text}" + ) from exception logger.debug( "Response Body: %s", json.dumps(response_json, indent=4, separators=(",", ": ")), @@ -199,17 +212,16 @@ def do_request(self, method, params=None): self.id += 1 if "error" in response_json: # some exception - if ( - "data" not in response_json["error"] - ): # some errors don't contain 'data': workaround for ZBX-9340 - response_json["error"]["data"] = "No data" - msg = "Error {code}: {message}, {data}".format( - code=response_json["error"]["code"], - message=response_json["error"]["message"], - data=response_json["error"]["data"], - ) + error = response_json["error"] + + # some errors don't contain 'data': workaround for ZBX-9340 + if "data" not in error: + error["data"] = "No data" + raise ZabbixAPIException( - msg, response_json["error"]["code"], error=response_json["error"] + f"Error {error['code']}: {error['message']}, {error['data']}", + error["code"], + error=error, ) return response_json @@ -219,6 +231,7 @@ def __getattr__(self, attr): return ZabbixAPIObjectClass(attr, self) +# pylint: disable=too-few-public-methods class ZabbixAPIObjectClass: def __init__(self, name, parent): self.name = name @@ -227,7 +240,7 @@ def __init__(self, name, parent): def __getattr__(self, attr): """Dynamically create a method (ie: get)""" - def fn(*args, **kwargs): + def func(*args, **kwargs): if args and kwargs: raise TypeError("Found both args and kwargs") @@ -235,4 +248,4 @@ def fn(*args, **kwargs): "result" ] - return fn + return func diff --git a/tests/api_test.py b/tests/api_test.py index d3e4f8c..40f1807 100644 --- a/tests/api_test.py +++ b/tests/api_test.py @@ -3,7 +3,6 @@ import httpretty import pytest -import semantic_version from pyzabbix import ZabbixAPI, ZabbixAPIException From ab373a6b5b0a98ef3ff5b805da99c858f2f89243 Mon Sep 17 00:00:00 2001 From: jo Date: Fri, 22 Jul 2022 21:46:33 +0200 Subject: [PATCH 062/145] test: rewrite tests to pytest --- tests/api_test.py | 247 ++++++++++++++++++++++------------------------ 1 file changed, 119 insertions(+), 128 deletions(-) diff --git a/tests/api_test.py b/tests/api_test.py index 40f1807..57be785 100644 --- a/tests/api_test.py +++ b/tests/api_test.py @@ -1,5 +1,4 @@ import json -import unittest import httpretty import pytest @@ -7,143 +6,135 @@ from pyzabbix import ZabbixAPI, ZabbixAPIException -class TestPyZabbix(unittest.TestCase): - @httpretty.activate - def test_login(self): - httpretty.register_uri( - httpretty.POST, - "http://example.com/api_jsonrpc.php", - body=json.dumps( - { - "jsonrpc": "2.0", - "result": "0424bd59b807674191e7d77572075f33", - "id": 0, - } - ), - ) - - zapi = ZabbixAPI("http://example.com", detect_version=False) - zapi.login("mylogin", "mypass") +@httpretty.activate +def test_login(): + httpretty.register_uri( + httpretty.POST, + "http://example.com/api_jsonrpc.php", + body=json.dumps( + { + "jsonrpc": "2.0", + "result": "0424bd59b807674191e7d77572075f33", + "id": 0, + } + ), + ) + + zapi = ZabbixAPI("http://example.com", detect_version=False) + zapi.login("mylogin", "mypass") + + # Check request + assert json.loads(httpretty.last_request().body.decode("utf-8")) == { + "jsonrpc": "2.0", + "method": "user.login", + "params": {"user": "mylogin", "password": "mypass"}, + "id": 0, + } + + assert httpretty.last_request().headers["content-type"] == "application/json-rpc" + assert httpretty.last_request().headers["user-agent"] == "python/pyzabbix" + + # Check response + assert zapi.auth == "0424bd59b807674191e7d77572075f33" - # Check request - self.assertEqual( - json.loads(httpretty.last_request().body.decode("utf-8")), + +@httpretty.activate +def test_host_get(): + httpretty.register_uri( + httpretty.POST, + "http://example.com/api_jsonrpc.php", + body=json.dumps({"jsonrpc": "2.0", "result": [{"hostid": 1234}], "id": 0}), + ) + + zapi = ZabbixAPI("http://example.com", detect_version=False) + zapi.auth = "123" + result = zapi.host.get() + + # Check request + assert json.loads(httpretty.last_request().body.decode("utf-8")) == { + "jsonrpc": "2.0", + "method": "host.get", + "params": {}, + "auth": "123", + "id": 0, + } + + # Check response + assert result == [{"hostid": 1234}] + + +@httpretty.activate +def test_host_delete(): + httpretty.register_uri( + httpretty.POST, + "http://example.com/api_jsonrpc.php", + body=json.dumps( { "jsonrpc": "2.0", - "method": "user.login", - "params": {"user": "mylogin", "password": "mypass"}, + "result": { + "itemids": [ + "22982", + "22986", + ] + }, "id": 0, - }, - ) - self.assertEqual( - httpretty.last_request().headers["content-type"], "application/json-rpc" - ) - self.assertEqual( - httpretty.last_request().headers["user-agent"], "python/pyzabbix" - ) - - # Check response - self.assertEqual(zapi.auth, "0424bd59b807674191e7d77572075f33") - - @httpretty.activate - def test_host_get(self): - httpretty.register_uri( - httpretty.POST, - "http://example.com/api_jsonrpc.php", - body=json.dumps({"jsonrpc": "2.0", "result": [{"hostid": 1234}], "id": 0}), - ) - - zapi = ZabbixAPI("http://example.com", detect_version=False) - zapi.auth = "123" - result = zapi.host.get() - - # Check request - self.assertEqual( - json.loads(httpretty.last_request().body.decode("utf-8")), + } + ), + ) + + zapi = ZabbixAPI("http://example.com", detect_version=False) + zapi.auth = "123" + result = zapi.host.delete("22982", "22986") + + # Check request + + assert json.loads(httpretty.last_request().body.decode("utf-8")) == { + "jsonrpc": "2.0", + "method": "host.delete", + "params": ["22982", "22986"], + "auth": "123", + "id": 0, + } + + # Check response + assert set(result["itemids"]) == {"22982", "22986"} + + +@httpretty.activate +def test_login_with_context(): + httpretty.register_uri( + httpretty.POST, + "http://example.com/api_jsonrpc.php", + body=json.dumps( { "jsonrpc": "2.0", - "method": "host.get", - "params": {}, - "auth": "123", + "result": "0424bd59b807674191e7d77572075f33", "id": 0, - }, - ) - - # Check response - self.assertEqual(result, [{"hostid": 1234}]) - - @httpretty.activate - def test_host_delete(self): - httpretty.register_uri( - httpretty.POST, - "http://example.com/api_jsonrpc.php", - body=json.dumps( - { - "jsonrpc": "2.0", - "result": { - "itemids": [ - "22982", - "22986", - ] - }, - "id": 0, - } - ), - ) - - zapi = ZabbixAPI("http://example.com", detect_version=False) - zapi.auth = "123" - result = zapi.host.delete("22982", "22986") - - # Check request - self.assertEqual( - json.loads(httpretty.last_request().body.decode("utf-8")), + } + ), + ) + + with ZabbixAPI("http://example.com", detect_version=False) as zapi: + zapi.login("mylogin", "mypass") + assert zapi.auth == "0424bd59b807674191e7d77572075f33" + + +@httpretty.activate +def test_detecting_version(): + httpretty.register_uri( + httpretty.POST, + "http://example.com/api_jsonrpc.php", + body=json.dumps( { "jsonrpc": "2.0", - "method": "host.delete", - "params": ["22982", "22986"], - "auth": "123", + "result": "4.0.0", "id": 0, - }, - ) - - # Check response - self.assertEqual(set(result["itemids"]), {"22982", "22986"}) - - @httpretty.activate - def test_login_with_context(self): - httpretty.register_uri( - httpretty.POST, - "http://example.com/api_jsonrpc.php", - body=json.dumps( - { - "jsonrpc": "2.0", - "result": "0424bd59b807674191e7d77572075f33", - "id": 0, - } - ), - ) - - with ZabbixAPI("http://example.com", detect_version=False) as zapi: - zapi.login("mylogin", "mypass") - self.assertEqual(zapi.auth, "0424bd59b807674191e7d77572075f33") - - @httpretty.activate - def test_detecting_version(self): - httpretty.register_uri( - httpretty.POST, - "http://example.com/api_jsonrpc.php", - body=json.dumps( - { - "jsonrpc": "2.0", - "result": "4.0.0", - "id": 0, - } - ), - ) - - zapi_detect = ZabbixAPI("http://example.com") - self.assertEqual(zapi_detect.api_version(), "4.0.0") + } + ), + ) + + zapi_detect = ZabbixAPI("http://example.com") + assert zapi_detect.api_version() == "4.0.0" @httpretty.activate From 3b5143f73bee02578c6d54fa84a6730530c85a55 Mon Sep 17 00:00:00 2001 From: jo Date: Fri, 22 Jul 2022 21:50:53 +0200 Subject: [PATCH 063/145] chore: setup type linting using mypy --- Makefile | 1 + pyproject.toml | 4 ++++ pyzabbix/__init__.py | 2 +- setup.py | 2 ++ tests/api_test.py | 2 +- 5 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 925afce..e2099a9 100644 --- a/Makefile +++ b/Makefile @@ -27,6 +27,7 @@ lint: $(VENV) black . --check isort . --profile black --check pylint --jobs=$(CPU_CORES) --output-format=colorized pyzabbix tests + mypy pyzabbix tests || true test: $(VENV) source $(VENV)/bin/activate diff --git a/pyproject.toml b/pyproject.toml index 7dee18d..4764fb1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,6 +5,10 @@ disable = [ "missing-module-docstring", ] +[tool.mypy] +allow_redefinition = true +disallow_incomplete_defs= true + [build-system] requires = ["setuptools", "wheel"] build-backend = "setuptools.build_meta" diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py index 7d873b5..44e9b3c 100644 --- a/pyzabbix/__init__.py +++ b/pyzabbix/__init__.py @@ -2,7 +2,7 @@ import logging import requests -import semantic_version +import semantic_version # type: ignore class _NullHandler(logging.Handler): diff --git a/setup.py b/setup.py index 6f7dde3..9b4b538 100644 --- a/setup.py +++ b/setup.py @@ -40,6 +40,8 @@ "black", "httpretty<0.8.7", "isort", + "mypy", + "types-requests", "pylint", "pytest-cov", "pytest-xdist", diff --git a/tests/api_test.py b/tests/api_test.py index 57be785..c907533 100644 --- a/tests/api_test.py +++ b/tests/api_test.py @@ -1,6 +1,6 @@ import json -import httpretty +import httpretty # type: ignore import pytest from pyzabbix import ZabbixAPI, ZabbixAPIException From be13310a70d61ddd4762fe54d0f3696c8842cb79 Mon Sep 17 00:00:00 2001 From: jo Date: Fri, 22 Jul 2022 22:31:31 +0200 Subject: [PATCH 064/145] chore: start adding typings --- pyzabbix/__init__.py | 40 ++++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py index 44e9b3c..90edb29 100644 --- a/pyzabbix/__init__.py +++ b/pyzabbix/__init__.py @@ -1,7 +1,8 @@ import json import logging +from typing import Optional, Tuple, Union -import requests +from requests import Session import semantic_version # type: ignore @@ -40,11 +41,11 @@ class ZabbixAPI: # pylint: disable=too-many-arguments def __init__( self, - server="http://localhost/zabbix", - session=None, - use_authenticate=False, - timeout=None, - detect_version=True, + server: str = "http://localhost/zabbix", + session: Optional[Session] = None, + use_authenticate: bool = False, + timeout: Optional[Union[float, int, Tuple[int, int]]] = None, + detect_version: bool = True, ): """ :param server: Base URI for zabbix web interface (omitting /api_jsonrpc.php) @@ -60,7 +61,7 @@ def __init__( if session: self.session = session else: - self.session = requests.Session() + self.session = Session() # Default headers for all requests self.session.headers.update( @@ -88,7 +89,7 @@ def __init__( self.version = "" self._detect_version = detect_version - def __enter__(self): + def __enter__(self) -> "ZabbixAPI": return self # pylint: disable=inconsistent-return-statements @@ -98,8 +99,14 @@ def __exit__(self, exception_type, exception_value, traceback): # Logout the user if they are authenticated using username + password. self.user.logout() return True + return None - def login(self, user="", password="", api_token=None): + def login( + self, + user: str = "", + password: str = "", + api_token: Optional[str] = None, + ) -> None: """Convenience method for calling user.authenticate and storing the resulting auth token for further commands. @@ -139,7 +146,7 @@ def check_authentication(self): return self.user.checkAuthentication(sessionid=self.auth) @property - def is_authenticated(self): + def is_authenticated(self) -> bool: if self.use_api_token: # We cannot use this call using an API Token return True @@ -150,7 +157,12 @@ def is_authenticated(self): return False return True - def confimport(self, confformat="", source="", rules=""): + def confimport( + self, + confformat: str = "", + source: str = "", + rules: str = "", + ) -> dict: """Alias for configuration.import because it clashes with Python's import reserved keyword :param rules: @@ -166,7 +178,7 @@ def confimport(self, confformat="", source="", rules=""): def api_version(self): return self.apiinfo.version() - def do_request(self, method, params=None): + def do_request(self, method: str, params: dict = None) -> dict: request_json = { "jsonrpc": "2.0", "method": method, @@ -226,14 +238,14 @@ def do_request(self, method, params=None): return response_json - def __getattr__(self, attr): + def __getattr__(self, attr: str) -> "ZabbixAPIObjectClass": """Dynamically create an object class (ie: host)""" return ZabbixAPIObjectClass(attr, self) # pylint: disable=too-few-public-methods class ZabbixAPIObjectClass: - def __init__(self, name, parent): + def __init__(self, name: str, parent: Union["ZabbixAPI", "ZabbixAPIObjectClass"]): self.name = name self.parent = parent From 2637e252a956cde88e167dcc042f34a4e6be6009 Mon Sep 17 00:00:00 2001 From: jo Date: Sat, 23 Jul 2022 00:23:46 +0200 Subject: [PATCH 065/145] chore: fix typings --- pyzabbix/__init__.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py index 90edb29..4193464 100644 --- a/pyzabbix/__init__.py +++ b/pyzabbix/__init__.py @@ -1,9 +1,9 @@ import json import logging -from typing import Optional, Tuple, Union +from typing import Mapping, Optional, Sequence, Tuple, Union -from requests import Session import semantic_version # type: ignore +from requests import Session class _NullHandler(logging.Handler): @@ -178,7 +178,11 @@ def confimport( def api_version(self): return self.apiinfo.version() - def do_request(self, method: str, params: dict = None) -> dict: + def do_request( + self, + method: str, + params: Optional[Union[Mapping, Sequence]] = None, + ) -> dict: request_json = { "jsonrpc": "2.0", "method": method, @@ -245,7 +249,7 @@ def __getattr__(self, attr: str) -> "ZabbixAPIObjectClass": # pylint: disable=too-few-public-methods class ZabbixAPIObjectClass: - def __init__(self, name: str, parent: Union["ZabbixAPI", "ZabbixAPIObjectClass"]): + def __init__(self, name: str, parent: ZabbixAPI): self.name = name self.parent = parent From 438b8fa11b7e60265a2dacc573b590f7c88aac27 Mon Sep 17 00:00:00 2001 From: jo Date: Sat, 23 Jul 2022 00:50:55 +0200 Subject: [PATCH 066/145] feat: rename ZabbixAPIObjectClass to ZabbixAPIObject --- pyzabbix/__init__.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py index 4193464..1e4b049 100644 --- a/pyzabbix/__init__.py +++ b/pyzabbix/__init__.py @@ -1,6 +1,7 @@ import json import logging from typing import Mapping, Optional, Sequence, Tuple, Union +from warnings import warn import semantic_version # type: ignore from requests import Session @@ -242,13 +243,13 @@ def do_request( return response_json - def __getattr__(self, attr: str) -> "ZabbixAPIObjectClass": + def __getattr__(self, attr: str) -> "ZabbixAPIObject": """Dynamically create an object class (ie: host)""" - return ZabbixAPIObjectClass(attr, self) + return ZabbixAPIObject(attr, self) # pylint: disable=too-few-public-methods -class ZabbixAPIObjectClass: +class ZabbixAPIObject: def __init__(self, name: str, parent: ZabbixAPI): self.name = name self.parent = parent @@ -265,3 +266,13 @@ def func(*args, **kwargs): ] return func + + +class ZabbixAPIObjectClass(ZabbixAPIObject): + def __init__(self, *args, **kwargs): + warn( + "ZabbixAPIObjectClass has been renamed to ZabbixAPIObject", + DeprecationWarning, + 2, + ) + super().__init__(*args, **kwargs) From 825f1f50095326a1f10357c4f1061c6cf7b602a1 Mon Sep 17 00:00:00 2001 From: jo Date: Sat, 23 Jul 2022 00:57:18 +0200 Subject: [PATCH 067/145] feat: replace dynamic func with ZabbixAPIMethod --- pyzabbix/__init__.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py index 1e4b049..1ea791d 100644 --- a/pyzabbix/__init__.py +++ b/pyzabbix/__init__.py @@ -248,6 +248,19 @@ def __getattr__(self, attr: str) -> "ZabbixAPIObject": return ZabbixAPIObject(attr, self) +# pylint: disable=too-few-public-methods +class ZabbixAPIMethod: + def __init__(self, method: str, parent: ZabbixAPI): + self.method = method + self.parent = parent + + def __call__(self, *args, **kwargs): + if args and kwargs: + raise TypeError("Found both args and kwargs") + + return self.parent.do_request(self.method, args or kwargs)["result"] + + # pylint: disable=too-few-public-methods class ZabbixAPIObject: def __init__(self, name: str, parent: ZabbixAPI): @@ -256,16 +269,7 @@ def __init__(self, name: str, parent: ZabbixAPI): def __getattr__(self, attr): """Dynamically create a method (ie: get)""" - - def func(*args, **kwargs): - if args and kwargs: - raise TypeError("Found both args and kwargs") - - return self.parent.do_request(f"{self.name}.{attr}", args or kwargs)[ - "result" - ] - - return func + return ZabbixAPIMethod(f"{self.name}.{attr}", self.parent) class ZabbixAPIObjectClass(ZabbixAPIObject): From dd26015fa4aa60b187a97b2cfb9bedc7005b5b0f Mon Sep 17 00:00:00 2001 From: jo Date: Sat, 23 Jul 2022 00:59:22 +0200 Subject: [PATCH 068/145] docs: fix ZabbixAPIException codes docstring --- pyzabbix/__init__.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py index 1ea791d..94baa4d 100644 --- a/pyzabbix/__init__.py +++ b/pyzabbix/__init__.py @@ -20,15 +20,15 @@ class ZabbixAPIException(Exception): """Generic Zabbix API exception Codes: - - 32700: invalid JSON. An error occurred on the server while - parsing the JSON text (typo, wrong quotes, etc.) - - 32600: received JSON is not a valid JSON-RPC Request - - 32601: requested remote-procedure does not exist - - 32602: invalid method parameters - - 32603: Internal JSON-RPC error - - 32400: System error - - 32300: Transport error - - 32500: Application error + -32700: invalid JSON. An error occurred on the server while + parsing the JSON text (typo, wrong quotes, etc.) + -32600: received JSON is not a valid JSON-RPC Request + -32601: requested remote-procedure does not exist + -32602: invalid method parameters + -32603: Internal JSON-RPC error + -32400: System error + -32300: Transport error + -32500: Application error """ def __init__(self, *args, **kwargs): From 7a36ab13c2fd7c3ff408b1f141095c15197eb071 Mon Sep 17 00:00:00 2001 From: jo Date: Sat, 23 Jul 2022 01:44:53 +0200 Subject: [PATCH 069/145] feat: allow creating calls using dict syntax --- pyzabbix/__init__.py | 16 ++++++++++++++-- tests/api_test.py | 14 ++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py index 94baa4d..3c782ef 100644 --- a/pyzabbix/__init__.py +++ b/pyzabbix/__init__.py @@ -243,10 +243,16 @@ def do_request( return response_json - def __getattr__(self, attr: str) -> "ZabbixAPIObject": + def _object(self, attr: str) -> "ZabbixAPIObject": """Dynamically create an object class (ie: host)""" return ZabbixAPIObject(attr, self) + def __getattr__(self, attr: str) -> "ZabbixAPIObject": + return self._object(attr) + + def __getitem__(self, attr: str) -> "ZabbixAPIObject": + return self._object(attr) + # pylint: disable=too-few-public-methods class ZabbixAPIMethod: @@ -267,10 +273,16 @@ def __init__(self, name: str, parent: ZabbixAPI): self.name = name self.parent = parent - def __getattr__(self, attr): + def _method(self, attr: str) -> ZabbixAPIMethod: """Dynamically create a method (ie: get)""" return ZabbixAPIMethod(f"{self.name}.{attr}", self.parent) + def __getattr__(self, attr: str) -> ZabbixAPIMethod: + return self._method(attr) + + def __getitem__(self, attr: str) -> ZabbixAPIMethod: + return self._method(attr) + class ZabbixAPIObjectClass(ZabbixAPIObject): def __init__(self, *args, **kwargs): diff --git a/tests/api_test.py b/tests/api_test.py index c907533..16a7a99 100644 --- a/tests/api_test.py +++ b/tests/api_test.py @@ -63,6 +63,20 @@ def test_host_get(): assert result == [{"hostid": 1234}] +@httpretty.activate +def test_dict_like_access(): + httpretty.register_uri( + httpretty.POST, + "http://example.com/api_jsonrpc.php", + body=json.dumps({"jsonrpc": "2.0", "result": [{"hostid": 1234}], "id": 0}), + ) + + zapi = ZabbixAPI("http://example.com", detect_version=False) + result = zapi["host"]["get"]() + + assert result == [{"hostid": 1234}] + + @httpretty.activate def test_host_delete(): httpretty.register_uri( From 80b943553402d9da6bcc338c92bce7a8e6c94485 Mon Sep 17 00:00:00 2001 From: jo Date: Sat, 23 Jul 2022 11:50:27 +0200 Subject: [PATCH 070/145] feat: deprecate ZabbixAPI.confimport alias please use `ZabbixAPI.configuration['import']()` instead. --- pyzabbix/__init__.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py index 3c782ef..b54e442 100644 --- a/pyzabbix/__init__.py +++ b/pyzabbix/__init__.py @@ -170,11 +170,18 @@ def confimport( :param source: :param confformat: """ + warn( + "ZabbixAPI.confimport() has been deprecated, please use " + "ZabbixAPI.configuration['import']() instead", + DeprecationWarning, + 2, + ) - return self.do_request( - method="configuration.import", - params={"format": confformat, "source": source, "rules": rules}, - )["result"] + return self.configuration["import"]( + format=confformat, + source=source, + rules=rules, + ) def api_version(self): return self.apiinfo.version() From 226fcf044f6174bc6529ef574f0fe7e4c529e915 Mon Sep 17 00:00:00 2001 From: jo Date: Sat, 23 Jul 2022 11:57:16 +0200 Subject: [PATCH 071/145] chore: disable pylint logging-fstring-interpolation --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 4764fb1..ae0a861 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,6 +5,9 @@ disable = [ "missing-module-docstring", ] +[tool.pylint.format] +disable = "logging-fstring-interpolation" + [tool.mypy] allow_redefinition = true disallow_incomplete_defs= true From 3bdf418b99cf5e2e4de1ea214f92b6fa5ea061b7 Mon Sep 17 00:00:00 2001 From: jo Date: Sat, 23 Jul 2022 12:06:13 +0200 Subject: [PATCH 072/145] refactor: use requests embed json encoder in do_request --- pyzabbix/__init__.py | 38 ++++++++++++++++---------------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py index b54e442..624f8ee 100644 --- a/pyzabbix/__init__.py +++ b/pyzabbix/__init__.py @@ -1,10 +1,10 @@ -import json import logging from typing import Mapping, Optional, Sequence, Tuple, Union from warnings import warn import semantic_version # type: ignore from requests import Session +from requests.exceptions import JSONDecodeError class _NullHandler(logging.Handler): @@ -191,7 +191,7 @@ def do_request( method: str, params: Optional[Union[Mapping, Sequence]] = None, ) -> dict: - request_json = { + payload = { "jsonrpc": "2.0", "method": method, "params": params or {}, @@ -205,38 +205,32 @@ def do_request( and method != "apiinfo.version" and method != "user.checkAuthentication" ): - request_json["auth"] = self.auth + payload["auth"] = self.auth - logger.debug( - "Sending: %s", json.dumps(request_json, indent=4, separators=(",", ": ")) - ) - response = self.session.post( - self.url, data=json.dumps(request_json), timeout=self.timeout - ) - logger.debug("Response Code: %s", str(response.status_code)) + logger.debug(f"Sending: {payload}") + resp = self.session.post(self.url, json=payload, timeout=self.timeout) + logger.debug(f"Response Code: {resp.status_code}") # NOTE: Getting a 412 response code means the headers are not in the # list of allowed headers. - response.raise_for_status() + resp.raise_for_status() - if not response.text: + if not resp.text: raise ZabbixAPIException("Received empty response") try: - response_json = json.loads(response.text) - except ValueError as exception: + response = resp.json() + except JSONDecodeError as exception: raise ZabbixAPIException( - f"Unable to parse json: {response.text}" + f"Unable to parse json: {resp.text}" ) from exception - logger.debug( - "Response Body: %s", - json.dumps(response_json, indent=4, separators=(",", ": ")), - ) + + logger.debug(f"Response Body: {response}") self.id += 1 - if "error" in response_json: # some exception - error = response_json["error"] + if "error" in response: # some exception + error = response["error"] # some errors don't contain 'data': workaround for ZBX-9340 if "data" not in error: @@ -248,7 +242,7 @@ def do_request( error=error, ) - return response_json + return response def _object(self, attr: str) -> "ZabbixAPIObject": """Dynamically create an object class (ie: host)""" From 78945b015717f5d3a491e087043be11fc8d5327a Mon Sep 17 00:00:00 2001 From: jo Date: Sat, 23 Jul 2022 12:06:46 +0200 Subject: [PATCH 073/145] test: enable logging in tests --- pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index ae0a861..3ca9f32 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,10 @@ disable = "logging-fstring-interpolation" allow_redefinition = true disallow_incomplete_defs= true +[tool.pytest.ini_options] +log_cli = true +log_cli_level = "DEBUG" + [build-system] requires = ["setuptools", "wheel"] build-backend = "setuptools.build_meta" From 8e886c42ae2925e7d3053c5f02d4f7c4d5239db1 Mon Sep 17 00:00:00 2001 From: jo Date: Sat, 23 Jul 2022 12:21:07 +0200 Subject: [PATCH 074/145] fix: auto correct server url trailing slash --- pyzabbix/__init__.py | 10 ++++------ tests/api_test.py | 13 +++++++++++++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py index 624f8ee..504f0fe 100644 --- a/pyzabbix/__init__.py +++ b/pyzabbix/__init__.py @@ -80,12 +80,10 @@ def __init__( self.timeout = timeout - self.url = ( - server + "/api_jsonrpc.php" - if not server.endswith("/api_jsonrpc.php") - else server - ) - logger.info("JSON-RPC Server Endpoint: %s", self.url) + if not server.endswith("/api_jsonrpc.php"): + server = server.rstrip("/") + "/api_jsonrpc.php" + self.url = server + logger.info(f"JSON-RPC Server Endpoint: {self.url}") self.version = "" self._detect_version = detect_version diff --git a/tests/api_test.py b/tests/api_test.py index 16a7a99..50ca8ff 100644 --- a/tests/api_test.py +++ b/tests/api_test.py @@ -63,6 +63,19 @@ def test_host_get(): assert result == [{"hostid": 1234}] +@pytest.mark.parametrize( + "server_url, expected", + [ + ("http://example.com", "http://example.com/api_jsonrpc.php"), + ("http://example.com/", "http://example.com/api_jsonrpc.php"), + ("http://example.com/base", "http://example.com/base/api_jsonrpc.php"), + ("http://example.com/base/", "http://example.com/base/api_jsonrpc.php"), + ], +) +def test_server_url_update(server_url, expected): + assert ZabbixAPI(server_url).url == expected + + @httpretty.activate def test_dict_like_access(): httpretty.register_uri( From 235b7f73a2f449a6ec374bba5688fa7195d844d8 Mon Sep 17 00:00:00 2001 From: jo Date: Sat, 23 Jul 2022 12:22:54 +0200 Subject: [PATCH 075/145] refactor: simplify ZabbixApi.__init__ --- pyzabbix/__init__.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py index 504f0fe..0edf7d2 100644 --- a/pyzabbix/__init__.py +++ b/pyzabbix/__init__.py @@ -58,11 +58,7 @@ def __init__( connect and read timeouts. :param detect_version: autodetect Zabbix API version """ - - if session: - self.session = session - else: - self.session = Session() + self.session = session or Session() # Default headers for all requests self.session.headers.update( From a712669fac3f649852498e96e23ad3c57d72cf9e Mon Sep 17 00:00:00 2001 From: jo Date: Sat, 23 Jul 2022 12:34:08 +0200 Subject: [PATCH 076/145] refactor: improve version detection --- pyzabbix/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py index 0edf7d2..39235b4 100644 --- a/pyzabbix/__init__.py +++ b/pyzabbix/__init__.py @@ -2,9 +2,9 @@ from typing import Mapping, Optional, Sequence, Tuple, Union from warnings import warn -import semantic_version # type: ignore from requests import Session from requests.exceptions import JSONDecodeError +from semantic_version import Version # type: ignore class _NullHandler(logging.Handler): @@ -114,8 +114,8 @@ def login( """ if self._detect_version: - self.version = semantic_version.Version(self.api_version()) - logger.info("Zabbix API version is: %s", str(self.version)) + self.version = Version(self.api_version()) + logger.info(f"Zabbix API version is: {self.version}") # If the API token is explicitly provided, use this instead. if api_token is not None: @@ -128,7 +128,7 @@ def login( self.auth = "" if self.use_authenticate: self.auth = self.user.authenticate(user=user, password=password) - elif self.version and self.version >= semantic_version.Version("5.4.0"): + elif self.version and self.version >= Version("5.4.0"): self.auth = self.user.login(username=user, password=password) else: self.auth = self.user.login(user=user, password=password) From 5246e2d852157bdaedcf834befd1ca48740e1f2f Mon Sep 17 00:00:00 2001 From: jo Date: Sat, 23 Jul 2022 12:34:24 +0200 Subject: [PATCH 077/145] chore: always specify encoding --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 9b4b538..45a5e37 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ from setuptools import setup -with open("README.md") as fh: +with open("README.md", encoding="utf-8") as fh: long_description = fh.read() setup( From c20a93842c179838f86337fde0d3715237afbd6a Mon Sep 17 00:00:00 2001 From: jo Date: Sat, 23 Jul 2022 13:49:37 +0200 Subject: [PATCH 078/145] test: rewrite tests using requests_mock --- setup.py | 4 +- tests/api_test.py | 277 +++++++++++++++++++++++++++------------------- 2 files changed, 163 insertions(+), 118 deletions(-) diff --git a/setup.py b/setup.py index 45a5e37..10c691a 100644 --- a/setup.py +++ b/setup.py @@ -38,14 +38,14 @@ extras_require={ "dev": [ "black", - "httpretty<0.8.7", "isort", "mypy", - "types-requests", "pylint", "pytest-cov", "pytest-xdist", "pytest", + "requests-mock", + "types-requests", ], }, ) diff --git a/tests/api_test.py b/tests/api_test.py index 50ca8ff..96d7d42 100644 --- a/tests/api_test.py +++ b/tests/api_test.py @@ -1,61 +1,94 @@ -import json - -import httpretty # type: ignore import pytest from pyzabbix import ZabbixAPI, ZabbixAPIException -@httpretty.activate -def test_login(): - httpretty.register_uri( - httpretty.POST, +@pytest.mark.parametrize( + "server, expected", + [ + ("http://example.com", "http://example.com/api_jsonrpc.php"), + ("http://example.com/", "http://example.com/api_jsonrpc.php"), + ("http://example.com/base", "http://example.com/base/api_jsonrpc.php"), + ("http://example.com/base/", "http://example.com/base/api_jsonrpc.php"), + ], +) +def test_server_url_correction(server, expected): + assert ZabbixAPI(server).url == expected + + +def _zabbix_requests_mock_factory(requests_mock, response, **kwargs): + requests_mock.post( "http://example.com/api_jsonrpc.php", - body=json.dumps( - { - "jsonrpc": "2.0", - "result": "0424bd59b807674191e7d77572075f33", - "id": 0, - } - ), + request_headers={ + "Content-Type": "application/json-rpc", + "User-Agent": "python/pyzabbix", + "Cache-Control": "no-cache", + }, + json=response, + **kwargs, + ) + + +def test_login(requests_mock): + _zabbix_requests_mock_factory( + requests_mock, + response={ + "jsonrpc": "2.0", + "result": "0424bd59b807674191e7d77572075f33", + "id": 0, + }, ) zapi = ZabbixAPI("http://example.com", detect_version=False) zapi.login("mylogin", "mypass") # Check request - assert json.loads(httpretty.last_request().body.decode("utf-8")) == { + assert requests_mock.last_request.json() == { "jsonrpc": "2.0", "method": "user.login", "params": {"user": "mylogin", "password": "mypass"}, "id": 0, } - assert httpretty.last_request().headers["content-type"] == "application/json-rpc" - assert httpretty.last_request().headers["user-agent"] == "python/pyzabbix" - - # Check response + # Check login assert zapi.auth == "0424bd59b807674191e7d77572075f33" -@httpretty.activate -def test_host_get(): - httpretty.register_uri( - httpretty.POST, - "http://example.com/api_jsonrpc.php", - body=json.dumps({"jsonrpc": "2.0", "result": [{"hostid": 1234}], "id": 0}), +def test_login_with_context(requests_mock): + _zabbix_requests_mock_factory( + requests_mock, + response={ + "jsonrpc": "2.0", + "result": "0424bd59b807674191e7d77572075f33", + "id": 0, + }, + ) + + with ZabbixAPI("http://example.com", detect_version=False) as zapi: + zapi.login("mylogin", "mypass") + assert zapi.auth == "0424bd59b807674191e7d77572075f33" + + +def test_attr_syntax_kwargs(requests_mock): + _zabbix_requests_mock_factory( + requests_mock, + response={ + "jsonrpc": "2.0", + "result": [{"hostid": 1234}], + "id": 0, + }, ) zapi = ZabbixAPI("http://example.com", detect_version=False) - zapi.auth = "123" - result = zapi.host.get() + zapi.auth = "some_auth_key" + result = zapi.host.get(hostids=5) # Check request - assert json.loads(httpretty.last_request().body.decode("utf-8")) == { + assert requests_mock.last_request.json() == { "jsonrpc": "2.0", "method": "host.get", - "params": {}, - "auth": "123", + "params": {"hostids": 5}, + "auth": "some_auth_key", "id": 0, } @@ -63,115 +96,127 @@ def test_host_get(): assert result == [{"hostid": 1234}] -@pytest.mark.parametrize( - "server_url, expected", - [ - ("http://example.com", "http://example.com/api_jsonrpc.php"), - ("http://example.com/", "http://example.com/api_jsonrpc.php"), - ("http://example.com/base", "http://example.com/base/api_jsonrpc.php"), - ("http://example.com/base/", "http://example.com/base/api_jsonrpc.php"), - ], -) -def test_server_url_update(server_url, expected): - assert ZabbixAPI(server_url).url == expected - - -@httpretty.activate -def test_dict_like_access(): - httpretty.register_uri( - httpretty.POST, - "http://example.com/api_jsonrpc.php", - body=json.dumps({"jsonrpc": "2.0", "result": [{"hostid": 1234}], "id": 0}), - ) - - zapi = ZabbixAPI("http://example.com", detect_version=False) - result = zapi["host"]["get"]() - - assert result == [{"hostid": 1234}] - - -@httpretty.activate -def test_host_delete(): - httpretty.register_uri( - httpretty.POST, - "http://example.com/api_jsonrpc.php", - body=json.dumps( - { - "jsonrpc": "2.0", - "result": { - "itemids": [ - "22982", - "22986", - ] - }, - "id": 0, - } - ), +def test_attr_syntax_args(requests_mock): + _zabbix_requests_mock_factory( + requests_mock, + response={ + "jsonrpc": "2.0", + "result": {"itemids": ["22982", "22986"]}, + "id": 0, + }, ) zapi = ZabbixAPI("http://example.com", detect_version=False) - zapi.auth = "123" + zapi.auth = "some_auth_key" result = zapi.host.delete("22982", "22986") # Check request - - assert json.loads(httpretty.last_request().body.decode("utf-8")) == { + assert requests_mock.last_request.json() == { "jsonrpc": "2.0", "method": "host.delete", "params": ["22982", "22986"], - "auth": "123", + "auth": "some_auth_key", "id": 0, } # Check response - assert set(result["itemids"]) == {"22982", "22986"} - - -@httpretty.activate -def test_login_with_context(): - httpretty.register_uri( - httpretty.POST, - "http://example.com/api_jsonrpc.php", - body=json.dumps( - { - "jsonrpc": "2.0", - "result": "0424bd59b807674191e7d77572075f33", - "id": 0, - } - ), + assert result == {"itemids": ["22982", "22986"]} + + +def test_attr_syntax_args_and_kwargs_raises(): + with pytest.raises( + TypeError, + match="Found both args and kwargs", + ): + zapi = ZabbixAPI("http://example.com") + zapi.host.delete("22982", hostids=5) + + +def test_detecting_version(requests_mock): + _zabbix_requests_mock_factory( + requests_mock, + response={ + "jsonrpc": "2.0", + "result": "4.0.0", + "id": 0, + }, ) - with ZabbixAPI("http://example.com", detect_version=False) as zapi: - zapi.login("mylogin", "mypass") - assert zapi.auth == "0424bd59b807674191e7d77572075f33" + zapi = ZabbixAPI("http://example.com") + assert zapi.api_version() == "4.0.0" -@httpretty.activate -def test_detecting_version(): - httpretty.register_uri( - httpretty.POST, - "http://example.com/api_jsonrpc.php", - body=json.dumps( - { - "jsonrpc": "2.0", - "result": "4.0.0", - "id": 0, - } - ), +@pytest.mark.parametrize( + "data", + [ + (None), + ('No groups for host "Linux server".'), + ], +) +def test_error_response(requests_mock, data): + _zabbix_requests_mock_factory( + requests_mock, + response={ + "jsonrpc": "2.0", + "error": { + "code": -32602, + "message": "Invalid params.", + **({} if data is None else {"data": data}), + }, + "id": 0, + }, ) - zapi_detect = ZabbixAPI("http://example.com") - assert zapi_detect.api_version() == "4.0.0" + with pytest.raises( + ZabbixAPIException, + match="Error -32602: Invalid params., No data." + if data is None + else f"Error -32602: Invalid params., {data}", + ): + zapi = ZabbixAPI("http://example.com") + zapi.host.get() -@httpretty.activate -def test_empty_response(): - httpretty.register_uri( - httpretty.POST, - "http://example.com/api_jsonrpc.php", +def test_empty_response(requests_mock): + _zabbix_requests_mock_factory( + requests_mock, + response=None, body="", ) - zapi = ZabbixAPI("http://example.com") with pytest.raises(ZabbixAPIException, match="Received empty response"): + zapi = ZabbixAPI("http://example.com") zapi.login("mylogin", "mypass") + + +@pytest.mark.parametrize( + "obj, method, params, expected", + [ + ("host", "get", {}, [{"hostid": 1234}]), + ], +) +def test_calls(requests_mock, obj, method, params, expected): + _zabbix_requests_mock_factory( + requests_mock, + response={ + "jsonrpc": "2.0", + "result": expected, + "id": 0, + }, + ) + + zapi = ZabbixAPI("http://example.com", detect_version=False) + zapi.auth = "some_auth_key" + result = zapi[obj][method](**params) + + # Check request + assert requests_mock.last_request.json() == { + "jsonrpc": "2.0", + "method": f"{obj}.{method}", + "params": params, + "auth": "some_auth_key", + "id": 0, + } + + # Check response + assert result == expected From e7af43ea07b044ee2006f30e95df5c79021ed4ee Mon Sep 17 00:00:00 2001 From: jo Date: Sat, 23 Jul 2022 13:49:54 +0200 Subject: [PATCH 079/145] test: generate xml coverage report --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index e2099a9..f849ddb 100644 --- a/Makefile +++ b/Makefile @@ -35,6 +35,7 @@ test: $(VENV) --numprocesses=$(CPU_CORES) \ --color=yes \ --cov-report=term \ + --cov-report=xml:./coverage.xml \ --cov=pyzabbix \ tests From 9c709fb8d01ef8c146cc4ae3444fb85272141cc1 Mon Sep 17 00:00:00 2001 From: jo Date: Sat, 23 Jul 2022 14:41:29 +0200 Subject: [PATCH 080/145] chore: add more typings --- pyzabbix/__init__.py | 30 ++++++++++++++++++++++++++---- setup.py | 1 + 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py index 39235b4..de38b13 100644 --- a/pyzabbix/__init__.py +++ b/pyzabbix/__init__.py @@ -1,10 +1,11 @@ import logging -from typing import Mapping, Optional, Sequence, Tuple, Union +from typing import Any, Mapping, Optional, Sequence, Tuple, Union from warnings import warn from requests import Session from requests.exceptions import JSONDecodeError from semantic_version import Version # type: ignore +from typing_extensions import NotRequired, TypedDict class _NullHandler(logging.Handler): @@ -37,6 +38,27 @@ def __init__(self, *args, **kwargs): self.error = kwargs.get("error", None) +class ZabbixAPIRequest(TypedDict): + jsonrpc: str + method: str + params: Union[Sequence, Mapping] + auth: NotRequired[str] + id: int + + +class Error(TypedDict): + code: int + message: str + data: NotRequired[str] + + +class ZabbixAPIResponse(TypedDict): + jsonrpc: str + result: Any + error: NotRequired[Error] + id: int + + # pylint: disable=too-many-instance-attributes class ZabbixAPI: # pylint: disable=too-many-arguments @@ -184,8 +206,8 @@ def do_request( self, method: str, params: Optional[Union[Mapping, Sequence]] = None, - ) -> dict: - payload = { + ) -> ZabbixAPIResponse: + payload: ZabbixAPIRequest = { "jsonrpc": "2.0", "method": method, "params": params or {}, @@ -213,7 +235,7 @@ def do_request( raise ZabbixAPIException("Received empty response") try: - response = resp.json() + response: ZabbixAPIResponse = resp.json() except JSONDecodeError as exception: raise ZabbixAPIException( f"Unable to parse json: {resp.text}" diff --git a/setup.py b/setup.py index 10c691a..84f49e5 100644 --- a/setup.py +++ b/setup.py @@ -34,6 +34,7 @@ install_requires=[ "requests>=1.0", "semantic-version>=2.8", + "typing_extensions", ], extras_require={ "dev": [ From 13e6b8b8a1ff6c949a58190fde86dcf968fe11c6 Mon Sep 17 00:00:00 2001 From: jo Date: Sat, 23 Jul 2022 14:42:57 +0200 Subject: [PATCH 081/145] feat: package is typed --- pyzabbix/py.typed | 1 + setup.py | 1 + 2 files changed, 2 insertions(+) create mode 100644 pyzabbix/py.typed diff --git a/pyzabbix/py.typed b/pyzabbix/py.typed new file mode 100644 index 0000000..1242d43 --- /dev/null +++ b/pyzabbix/py.typed @@ -0,0 +1 @@ +# Marker file for PEP 561. diff --git a/setup.py b/setup.py index 84f49e5..e69a598 100644 --- a/setup.py +++ b/setup.py @@ -30,6 +30,7 @@ "Topic :: System :: Systems Administration", ], packages=["pyzabbix"], + package_data={"": ["py.typed"]}, python_requires=">=3.6", install_requires=[ "requests>=1.0", From f6214c880ca5d20a38c27818bfd11d25d587ae8e Mon Sep 17 00:00:00 2001 From: jo Date: Sat, 23 Jul 2022 14:45:21 +0200 Subject: [PATCH 082/145] chore: remove unused MANIFEST.in --- MANIFEST.in | 1 - 1 file changed, 1 deletion(-) delete mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index bb3ec5f..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1 +0,0 @@ -include README.md From e568a1ce8ab3e8da29afc24c537ef829fd2a7a91 Mon Sep 17 00:00:00 2001 From: jo Date: Sat, 23 Jul 2022 16:09:41 +0200 Subject: [PATCH 083/145] test: rename main test file --- tests/__init__.py | 0 tests/{api_test.py => pyzabbix_test.py} | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/__init__.py rename tests/{api_test.py => pyzabbix_test.py} (100%) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/api_test.py b/tests/pyzabbix_test.py similarity index 100% rename from tests/api_test.py rename to tests/pyzabbix_test.py From b5484050b65d88d5f3055e95d607989d4a386e05 Mon Sep 17 00:00:00 2001 From: jo Date: Sat, 23 Jul 2022 17:24:47 +0200 Subject: [PATCH 084/145] chore: add api_version typing --- pyzabbix/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py index de38b13..ebf578f 100644 --- a/pyzabbix/__init__.py +++ b/pyzabbix/__init__.py @@ -199,7 +199,7 @@ def confimport( rules=rules, ) - def api_version(self): + def api_version(self) -> str: return self.apiinfo.version() def do_request( From 5597605c4d0325ed959da84fbeac1f0f47588329 Mon Sep 17 00:00:00 2001 From: jo Date: Sat, 23 Jul 2022 16:10:56 +0200 Subject: [PATCH 085/145] test: add e2e tests using zabbix-docker --- .github/workflows/ci.yml | 28 ++++++++++++++++++++++++++ Makefile | 15 +++++++++++--- docker-compose.yml | 43 ++++++++++++++++++++++++++++++++++++++++ e2e/__init__.py | 0 e2e/conftest.py | 33 ++++++++++++++++++++++++++++++ e2e/e2e_test.py | 33 ++++++++++++++++++++++++++++++ pyproject.toml | 6 ++++++ 7 files changed, 155 insertions(+), 3 deletions(-) create mode 100644 docker-compose.yml create mode 100644 e2e/__init__.py create mode 100644 e2e/conftest.py create mode 100644 e2e/e2e_test.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 058bf8d..505138d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,3 +61,31 @@ jobs: - run: make install - run: make test + + e2e: + runs-on: ubuntu-latest + strategy: + matrix: + zabbix-version: ["4.0", "5.0", "6.0", "6.2"] + + env: + ZABBIX_VERSION: ${{ matrix.zabbix-version }} + + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-python@v4 + with: + python-version: 3.x + + - uses: actions/cache@v3 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }} + restore-keys: | + ${{ runner.os }}-pip- + + - run: docker-compose up -d + - run: docker-compose images + - run: make install + - run: make e2e diff --git a/Makefile b/Makefile index f849ddb..87a1b4a 100644 --- a/Makefile +++ b/Makefile @@ -29,15 +29,24 @@ lint: $(VENV) pylint --jobs=$(CPU_CORES) --output-format=colorized pyzabbix tests mypy pyzabbix tests || true + +PYTEST_CMD = pytest -v \ + --numprocesses=$(CPU_CORES) \ + --color=yes + test: $(VENV) source $(VENV)/bin/activate - pytest -v \ - --numprocesses=$(CPU_CORES) \ - --color=yes \ + $(PYTEST_CMD) \ + --cov-config=./pyproject.toml \ --cov-report=term \ --cov-report=xml:./coverage.xml \ --cov=pyzabbix \ tests +.PHONY: e2e +e2e: $(VENV) + source $(VENV)/bin/activate + $(PYTEST_CMD) e2e + clean: rm -Rf $(VENV) diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..dfa927a --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,43 @@ +--- +# For development purpose only +version: "3.8" + +services: + postgres-server: + image: postgres:13-alpine + environment: + POSTGRES_USER: zabbix + POSTGRES_PASSWORD: zabbix + + zabbix-server: + image: zabbix/zabbix-server-pgsql:alpine-${ZABBIX_VERSION:-6.2}-latest + ports: + - 10051:10051 + volumes: + - /etc/localtime:/etc/localtime:ro + environment: + POSTGRES_USER: zabbix + POSTGRES_PASSWORD: zabbix + depends_on: + - postgres-server + + zabbix-web: + image: zabbix/zabbix-web-nginx-pgsql:alpine-${ZABBIX_VERSION:-6.2}-latest + ports: + - 8888:8080 + volumes: + - /etc/localtime:/etc/localtime:ro + environment: + POSTGRES_USER: zabbix + POSTGRES_PASSWORD: zabbix + # https://github.com/zabbix/zabbix-docker/issues/991 + DB_SERVER_ROOT_USER: zabbix + depends_on: + - postgres-server + - zabbix-server + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8080/"] + interval: 10s + timeout: 5s + retries: 3 + start_period: 30s diff --git a/e2e/__init__.py b/e2e/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/e2e/conftest.py b/e2e/conftest.py new file mode 100644 index 0000000..6740411 --- /dev/null +++ b/e2e/conftest.py @@ -0,0 +1,33 @@ +from os import getenv +from time import sleep + +import pytest +from requests.exceptions import ConnectionError + +from pyzabbix import ZabbixAPI, ZabbixAPIException + +ZABBIX_SERVER = "http://localhost:8888" +ZABBIX_VERSION = getenv("ZABBIX_VERSION", "6.2") + + +@pytest.fixture(scope="session", autouse=True) +def wait_for_zabbix() -> None: + max_attempts = 30 + while max_attempts > 0: + try: + ZabbixAPI(ZABBIX_SERVER).apiinfo.version() + except (ConnectionError, ZabbixAPIException): + sleep(2) + max_attempts -= 1 + continue + break + + if max_attempts <= 0: + pytest.exit("waiting for zabbix failed!", 1) + + +@pytest.fixture() +def zapi() -> ZabbixAPI: + api = ZabbixAPI(ZABBIX_SERVER) + api.login("Admin", "zabbix") + return api diff --git a/e2e/e2e_test.py b/e2e/e2e_test.py new file mode 100644 index 0000000..a16a425 --- /dev/null +++ b/e2e/e2e_test.py @@ -0,0 +1,33 @@ +from pyzabbix import ZabbixAPI + +from .conftest import ZABBIX_VERSION + + +def test_login(zapi: ZabbixAPI) -> None: + assert zapi.auth + + +def test_version(zapi: ZabbixAPI) -> None: + assert zapi.api_version().startswith(ZABBIX_VERSION) + + +def test_host_get(zapi: ZabbixAPI) -> None: + hosts = zapi.host.get(filter={"host": ["Zabbix server"]}) + assert hosts[0]["host"] == "Zabbix server" + + +def test_host_update_interface(zapi: ZabbixAPI) -> None: + hosts = zapi.host.get(filter={"host": ["Zabbix server"]}, output="extend") + assert hosts[0]["host"] == "Zabbix server" + + interfaces = zapi.hostinterface.get(hostids=hosts[0]["hostid"]) + assert interfaces[0]["ip"] == "127.0.0.1" + + interfaces_update = zapi.hostinterface.update( + interfaceid=interfaces[0]["interfaceid"], + dns="zabbix-agent", + ) + assert interfaces_update["interfaceids"] == [interfaces[0]["interfaceid"]] + + interfaces = zapi.hostinterface.get(hostids=hosts[0]["hostid"]) + assert interfaces[0]["dns"] == "zabbix-agent" diff --git a/pyproject.toml b/pyproject.toml index 3ca9f32..17095ac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,12 @@ disallow_incomplete_defs= true log_cli = true log_cli_level = "DEBUG" +[tool.coverage.run] +omit = [ + "tests/*", + "e2e/*", +] + [build-system] requires = ["setuptools", "wheel"] build-backend = "setuptools.build_meta" From 216756479b832e19e8d7c2ef11442bf1a0cf4595 Mon Sep 17 00:00:00 2001 From: jo Date: Sat, 23 Jul 2022 18:06:08 +0200 Subject: [PATCH 086/145] docs: update tested zabbix versions --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 84273e8..190c59f 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ ## Requirements -- Tested against Zabbix 1.8 through 5.0 +- Tested against Zabbix 4.0 LTS, 5.0 LTS, 6.0 LTS and 6.2. ## Documentation From 834625f429ae6b7c55b9e0b854806e23450415b9 Mon Sep 17 00:00:00 2001 From: jo Date: Sat, 23 Jul 2022 22:12:09 +0200 Subject: [PATCH 087/145] chore: clean workaround for zabbix 4.0 db setup --- docker-compose.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index dfa927a..7cd86cb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -30,8 +30,6 @@ services: environment: POSTGRES_USER: zabbix POSTGRES_PASSWORD: zabbix - # https://github.com/zabbix/zabbix-docker/issues/991 - DB_SERVER_ROOT_USER: zabbix depends_on: - postgres-server - zabbix-server From 070b4dd3698db33565c58cd71f33f502106cdb5f Mon Sep 17 00:00:00 2001 From: jo Date: Sat, 23 Jul 2022 22:30:13 +0200 Subject: [PATCH 088/145] test: wait a bit more before e2e tests --- e2e/conftest.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/e2e/conftest.py b/e2e/conftest.py index 6740411..2594e4d 100644 --- a/e2e/conftest.py +++ b/e2e/conftest.py @@ -25,6 +25,9 @@ def wait_for_zabbix() -> None: if max_attempts <= 0: pytest.exit("waiting for zabbix failed!", 1) + # extra sleep if zabbix wasn't ready on first attempt + if max_attempts < 30: + sleep(5) @pytest.fixture() def zapi() -> ZabbixAPI: From 1fb37a776cd223b498c0c1e40d8a2eea6826dadc Mon Sep 17 00:00:00 2001 From: jo Date: Sat, 23 Jul 2022 22:39:32 +0200 Subject: [PATCH 089/145] feat: replace custom handler with logging.NullHandler --- pyzabbix/__init__.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py index ebf578f..3fb94da 100644 --- a/pyzabbix/__init__.py +++ b/pyzabbix/__init__.py @@ -8,13 +8,8 @@ from typing_extensions import NotRequired, TypedDict -class _NullHandler(logging.Handler): - def emit(self, record): - pass - - logger = logging.getLogger(__name__) -logger.addHandler(_NullHandler()) +logger.addHandler(logging.NullHandler()) class ZabbixAPIException(Exception): From 4067e450d741460a0f021c0321bfcf386fdcd68f Mon Sep 17 00:00:00 2001 From: jo Date: Sat, 23 Jul 2022 23:42:38 +0200 Subject: [PATCH 090/145] style: format code --- e2e/conftest.py | 1 + pyzabbix/__init__.py | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/conftest.py b/e2e/conftest.py index 2594e4d..8f998a1 100644 --- a/e2e/conftest.py +++ b/e2e/conftest.py @@ -29,6 +29,7 @@ def wait_for_zabbix() -> None: if max_attempts < 30: sleep(5) + @pytest.fixture() def zapi() -> ZabbixAPI: api = ZabbixAPI(ZABBIX_SERVER) diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py index 3fb94da..c24dc78 100644 --- a/pyzabbix/__init__.py +++ b/pyzabbix/__init__.py @@ -7,7 +7,6 @@ from semantic_version import Version # type: ignore from typing_extensions import NotRequired, TypedDict - logger = logging.getLogger(__name__) logger.addHandler(logging.NullHandler()) From 6ad9a8c3e8f14a2ba67d3ed12957cf623d82027b Mon Sep 17 00:00:00 2001 From: jo Date: Sat, 23 Jul 2022 23:43:45 +0200 Subject: [PATCH 091/145] chore: add black pre-commit hook --- .pre-commit-config.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 60d0e3f..1737a58 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -35,6 +35,11 @@ repos: - id: codespell exclude: ^examples/additemcsv.py$ + - repo: https://github.com/psf/black + rev: 22.3.0 + hooks: + - id: black + - repo: https://github.com/asottile/pyupgrade rev: v2.34.0 hooks: From 59e283f229add6c50ec304f488fa66b615383cec Mon Sep 17 00:00:00 2001 From: jo Date: Sun, 24 Jul 2022 00:46:45 +0200 Subject: [PATCH 092/145] test: wait for zabbix using login before e2e tests --- e2e/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/conftest.py b/e2e/conftest.py index 8f998a1..8625f12 100644 --- a/e2e/conftest.py +++ b/e2e/conftest.py @@ -15,7 +15,7 @@ def wait_for_zabbix() -> None: max_attempts = 30 while max_attempts > 0: try: - ZabbixAPI(ZABBIX_SERVER).apiinfo.version() + ZabbixAPI(ZABBIX_SERVER).login("Admin", "zabbix") except (ConnectionError, ZabbixAPIException): sleep(2) max_attempts -= 1 From f478cb69a9b63cc7709ab30af51b36fe1ef94f78 Mon Sep 17 00:00:00 2001 From: jo Date: Sat, 23 Jul 2022 14:01:50 +0200 Subject: [PATCH 093/145] fix: api object/method attributes should be private In both ZabbixAPIObject and ZabbixAPIMethod classes, the `name`, `parent` and `method`, `parent` attributes are now private. Please do not rely on these internal attributes. --- pyzabbix/__init__.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py index de38b13..acb0563 100644 --- a/pyzabbix/__init__.py +++ b/pyzabbix/__init__.py @@ -274,25 +274,25 @@ def __getitem__(self, attr: str) -> "ZabbixAPIObject": # pylint: disable=too-few-public-methods class ZabbixAPIMethod: def __init__(self, method: str, parent: ZabbixAPI): - self.method = method - self.parent = parent + self._method = method + self._parent = parent def __call__(self, *args, **kwargs): if args and kwargs: raise TypeError("Found both args and kwargs") - return self.parent.do_request(self.method, args or kwargs)["result"] + return self._parent.do_request(self._method, args or kwargs)["result"] # pylint: disable=too-few-public-methods class ZabbixAPIObject: def __init__(self, name: str, parent: ZabbixAPI): - self.name = name - self.parent = parent + self._name = name + self._parent = parent def _method(self, attr: str) -> ZabbixAPIMethod: """Dynamically create a method (ie: get)""" - return ZabbixAPIMethod(f"{self.name}.{attr}", self.parent) + return ZabbixAPIMethod(f"{self._name}.{attr}", self._parent) def __getattr__(self, attr: str) -> ZabbixAPIMethod: return self._method(attr) From 0f9a0c04686ef200efcfd901dedb7d2e9455b14b Mon Sep 17 00:00:00 2001 From: jo Date: Mon, 25 Jul 2022 20:13:46 +0200 Subject: [PATCH 094/145] Revert "chore: add more typings" This reverts commit 9c709fb8d01ef8c146cc4ae3444fb85272141cc1. --- pyzabbix/__init__.py | 30 ++++-------------------------- setup.py | 1 - 2 files changed, 4 insertions(+), 27 deletions(-) diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py index 2a4341a..4530b58 100644 --- a/pyzabbix/__init__.py +++ b/pyzabbix/__init__.py @@ -1,11 +1,10 @@ import logging -from typing import Any, Mapping, Optional, Sequence, Tuple, Union +from typing import Mapping, Optional, Sequence, Tuple, Union from warnings import warn from requests import Session from requests.exceptions import JSONDecodeError from semantic_version import Version # type: ignore -from typing_extensions import NotRequired, TypedDict logger = logging.getLogger(__name__) logger.addHandler(logging.NullHandler()) @@ -32,27 +31,6 @@ def __init__(self, *args, **kwargs): self.error = kwargs.get("error", None) -class ZabbixAPIRequest(TypedDict): - jsonrpc: str - method: str - params: Union[Sequence, Mapping] - auth: NotRequired[str] - id: int - - -class Error(TypedDict): - code: int - message: str - data: NotRequired[str] - - -class ZabbixAPIResponse(TypedDict): - jsonrpc: str - result: Any - error: NotRequired[Error] - id: int - - # pylint: disable=too-many-instance-attributes class ZabbixAPI: # pylint: disable=too-many-arguments @@ -200,8 +178,8 @@ def do_request( self, method: str, params: Optional[Union[Mapping, Sequence]] = None, - ) -> ZabbixAPIResponse: - payload: ZabbixAPIRequest = { + ) -> dict: + payload = { "jsonrpc": "2.0", "method": method, "params": params or {}, @@ -229,7 +207,7 @@ def do_request( raise ZabbixAPIException("Received empty response") try: - response: ZabbixAPIResponse = resp.json() + response = resp.json() except JSONDecodeError as exception: raise ZabbixAPIException( f"Unable to parse json: {resp.text}" diff --git a/setup.py b/setup.py index e69a598..176e8df 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,6 @@ install_requires=[ "requests>=1.0", "semantic-version>=2.8", - "typing_extensions", ], extras_require={ "dev": [ From 53b4fae9874f73aa498a5b004abfee5452398ef2 Mon Sep 17 00:00:00 2001 From: jo Date: Mon, 25 Jul 2022 20:16:30 +0200 Subject: [PATCH 095/145] chore: rename __init__ to api --- e2e/{e2e_test.py => api_test.py} | 2 +- pyzabbix/{__init__.py => api.py} | 0 tests/{pyzabbix_test.py => api_test.py} | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename e2e/{e2e_test.py => api_test.py} (96%) rename pyzabbix/{__init__.py => api.py} (100%) rename tests/{pyzabbix_test.py => api_test.py} (100%) diff --git a/e2e/e2e_test.py b/e2e/api_test.py similarity index 96% rename from e2e/e2e_test.py rename to e2e/api_test.py index a16a425..f08a8c9 100644 --- a/e2e/e2e_test.py +++ b/e2e/api_test.py @@ -1,4 +1,4 @@ -from pyzabbix import ZabbixAPI +from pyzabbix.api import ZabbixAPI from .conftest import ZABBIX_VERSION diff --git a/pyzabbix/__init__.py b/pyzabbix/api.py similarity index 100% rename from pyzabbix/__init__.py rename to pyzabbix/api.py diff --git a/tests/pyzabbix_test.py b/tests/api_test.py similarity index 100% rename from tests/pyzabbix_test.py rename to tests/api_test.py From a73cd7158117fa7a036220043ff7b68c92668288 Mon Sep 17 00:00:00 2001 From: jo Date: Mon, 25 Jul 2022 20:19:25 +0200 Subject: [PATCH 096/145] chore: export api classes --- pyzabbix/__init__.py | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 pyzabbix/__init__.py diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py new file mode 100644 index 0000000..5603ad6 --- /dev/null +++ b/pyzabbix/__init__.py @@ -0,0 +1,7 @@ +from .api import ( + ZabbixAPI, + ZabbixAPIException, + ZabbixAPIMethod, + ZabbixAPIObject, + ZabbixAPIObjectClass, +) From 762c2d8d8adc15c5874599db3ceb57bad5995351 Mon Sep 17 00:00:00 2001 From: jo Date: Sat, 23 Jul 2022 14:15:56 +0200 Subject: [PATCH 097/145] docs: add developement instructions --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 190c59f..0bd4755 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,17 @@ Response Body: { >>> ``` +## Development + +To develop this project, start by reading the `Makefile` to have a basic understanding of the possible tasks. + +Install the project and the dependencies in a virtual environment: + +```sh +make install +source .venv/bin/activate +``` + ## License LGPL 2.1 http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html From 3a2397e7b74e69e753abb712a4ec24e93f57a208 Mon Sep 17 00:00:00 2001 From: jo Date: Fri, 22 Jul 2022 22:35:59 +0200 Subject: [PATCH 098/145] ci: remove os from test matrix --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 505138d..fcf1d29 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,7 +42,6 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - os: [ubuntu-latest] python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"] steps: From b1412920ae738a8eb8bd31b3248806962a63969d Mon Sep 17 00:00:00 2001 From: jo Date: Fri, 22 Jul 2022 22:50:04 +0200 Subject: [PATCH 099/145] ci: publish releases to pypi using ci --- .github/workflows/ci.yml | 26 +++++++++++++++++++ Makefile | 11 ++++++-- README.md | 14 ++++++++++ scripts/release.sh | 56 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 105 insertions(+), 2 deletions(-) create mode 100755 scripts/release.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fcf1d29..cf7e78e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,6 +2,7 @@ name: CI on: push: + tags: ["*.*.*"] branches: [master] pull_request: branches: [master] @@ -88,3 +89,28 @@ jobs: - run: docker-compose images - run: make install - run: make e2e + + publish: + if: startsWith(github.ref, 'refs/tags') + needs: [pre-commit, lint, test, e2e] + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-python@v4 + with: + python-version: 3.x + + - uses: actions/cache@v3 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }} + restore-keys: | + ${{ runner.os }}-pip- + + - run: make clean build + + - uses: pypa/gh-action-pypi-publish@v1.5.0 + with: + password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/Makefile b/Makefile index 87a1b4a..3f76ef2 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ $(VENV): install: $(VENV) source $(VENV)/bin/activate - pip install --upgrade pip setuptools wheel + pip install --upgrade pip setuptools wheel build pip install --editable .[dev] format: $(VENV) @@ -48,5 +48,12 @@ e2e: $(VENV) source $(VENV)/bin/activate $(PYTEST_CMD) e2e +build: $(VENV) + source $(VENV)/bin/activate + python -m build . + +release: + ./scripts/release.sh + clean: - rm -Rf $(VENV) + rm -Rf $(VENV) dist diff --git a/README.md b/README.md index 0bd4755..06d939d 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,20 @@ make install source .venv/bin/activate ``` +### Releases + +To release a new version, first bump the version number in `setup.py` by hand and run the release target: + +```sh +make release +``` + +Finally, push the release commit and tag to publish them to Pypi: + +```sh +git push --follow-tags +``` + ## License LGPL 2.1 http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html diff --git a/scripts/release.sh b/scripts/release.sh new file mode 100755 index 0000000..698494d --- /dev/null +++ b/scripts/release.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +set -u + +error() { + echo >&2 "error: $*" + exit 1 +} + +# This scripts will: +# - Expect the setup.py file to have new version +# - Stash the setup.py file +# - Check for clean state (no uncommitted changes), exit if failed +# - Clean the project +# - Install the project, lint and runt tests, exit if failed +# - Unstash the pyproject version bump and commit a new Release +# - Tag the new release +# - Show instruction to push tags and changes to github + +command -v make > /dev/null || error "make command not found!" +command -v git > /dev/null || error "git command not found!" +command -v python3 > /dev/null || error "python3 command not found!" + +setup="setup.py" +default_branch="master" + +[[ "$(git rev-parse --show-toplevel)" == "$(pwd)" ]] || error "please go to the project root directory!" +[[ "$(git rev-parse --abbrev-ref HEAD)" == "$default_branch" ]] || error "please change to the $default_branch git branch!" + +pkg_version=$(python3 setup.py --version || error "could not determine package version in $setup!") +git_version=$(git describe --abbrev=0 --tags || error "could not determine git version!") + +# No version change +if [[ "$pkg_version" == "$git_version" ]]; then + echo "Latest git tag '$pkg_version' and package version '$git_version' match, edit your $setup to change the version before running this script!" + exit +fi + +git stash push --quiet -- "$setup" +trap 'e=$?; git stash pop --quiet; exit $e' EXIT + +[[ -z "$(git status --porcelain)" ]] || error "please commit or clean the changes before running this script!" + +git clean -xdf + +make lint test || error "tests failed, please correct the errors" + +new_tag="$pkg_version" +release="chore: release $new_tag" + +git stash pop --quiet +git add "$setup" || error "could not stage $setup!" +git commit -m "$release" --no-verify || error "could not commit the version bump!" +git tag "$new_tag" -a -m "$release" || error "could not tag the version bump!" + +echo "Run 'git push --follow-tags' in order to publish the release on Github!" From 79c1b4879028dd1c5389d6a76da9a009fe2dd3eb Mon Sep 17 00:00:00 2001 From: jo Date: Thu, 28 Jul 2022 15:09:18 +0200 Subject: [PATCH 100/145] chore: make api module private --- e2e/api_test.py | 2 +- pyzabbix/__init__.py | 2 +- pyzabbix/{api.py => _api.py} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename pyzabbix/{api.py => _api.py} (100%) diff --git a/e2e/api_test.py b/e2e/api_test.py index f08a8c9..a16a425 100644 --- a/e2e/api_test.py +++ b/e2e/api_test.py @@ -1,4 +1,4 @@ -from pyzabbix.api import ZabbixAPI +from pyzabbix import ZabbixAPI from .conftest import ZABBIX_VERSION diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py index 5603ad6..388e2ea 100644 --- a/pyzabbix/__init__.py +++ b/pyzabbix/__init__.py @@ -1,4 +1,4 @@ -from .api import ( +from ._api import ( ZabbixAPI, ZabbixAPIException, ZabbixAPIMethod, diff --git a/pyzabbix/api.py b/pyzabbix/_api.py similarity index 100% rename from pyzabbix/api.py rename to pyzabbix/_api.py From 5cb747a2c723fc6d144e6a69b47f3873629bb093 Mon Sep 17 00:00:00 2001 From: jo Date: Thu, 28 Jul 2022 15:22:45 +0200 Subject: [PATCH 101/145] chore: release 1.1.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 176e8df..8a0fa01 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name="pyzabbix", - version="1.0.0", + version="1.1.0", description="Zabbix API Python interface", long_description=long_description, long_description_content_type="text/markdown", From c21fa01c9dafeb8b16121d148e73bb09eddb26de Mon Sep 17 00:00:00 2001 From: jo Date: Thu, 28 Jul 2022 15:25:49 +0200 Subject: [PATCH 102/145] chore: regenerate CHANGELOG CHANGELOG is maintained from the version >=1.1.0. --- CHANGELOG.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..57d1bba --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,22 @@ + + +## [1.1.0](https://github.com/lukecyca/pyzabbix/compare/1.0.0...1.1.0) (2022-07-28) + +### :bug: Bug Fixes + +- api object/method attributes should be private +- auto correct server url trailing slash + +### :rocket: Features + +- replace custom handler with logging.NullHandler +- package is typed +- deprecate ZabbixAPI.confimport alias +- allow creating calls using dict syntax +- replace dynamic func with ZabbixAPIMethod +- rename ZabbixAPIObjectClass to ZabbixAPIObject +- require >=python3.6 + +### Reverts + +- chore: add more typings From 127e1200bbbbc93833d934b99195527d47e0d8c3 Mon Sep 17 00:00:00 2001 From: jo Date: Thu, 28 Jul 2022 16:03:38 +0200 Subject: [PATCH 103/145] test: simplify zabbix_request_mock_factory --- tests/api_test.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/tests/api_test.py b/tests/api_test.py index 96d7d42..8c179cc 100644 --- a/tests/api_test.py +++ b/tests/api_test.py @@ -16,7 +16,7 @@ def test_server_url_correction(server, expected): assert ZabbixAPI(server).url == expected -def _zabbix_requests_mock_factory(requests_mock, response, **kwargs): +def _zabbix_requests_mock_factory(requests_mock, *args, **kwargs): requests_mock.post( "http://example.com/api_jsonrpc.php", request_headers={ @@ -24,7 +24,7 @@ def _zabbix_requests_mock_factory(requests_mock, response, **kwargs): "User-Agent": "python/pyzabbix", "Cache-Control": "no-cache", }, - json=response, + *args, **kwargs, ) @@ -32,7 +32,7 @@ def _zabbix_requests_mock_factory(requests_mock, response, **kwargs): def test_login(requests_mock): _zabbix_requests_mock_factory( requests_mock, - response={ + json={ "jsonrpc": "2.0", "result": "0424bd59b807674191e7d77572075f33", "id": 0, @@ -57,7 +57,7 @@ def test_login(requests_mock): def test_login_with_context(requests_mock): _zabbix_requests_mock_factory( requests_mock, - response={ + json={ "jsonrpc": "2.0", "result": "0424bd59b807674191e7d77572075f33", "id": 0, @@ -72,7 +72,7 @@ def test_login_with_context(requests_mock): def test_attr_syntax_kwargs(requests_mock): _zabbix_requests_mock_factory( requests_mock, - response={ + json={ "jsonrpc": "2.0", "result": [{"hostid": 1234}], "id": 0, @@ -99,7 +99,7 @@ def test_attr_syntax_kwargs(requests_mock): def test_attr_syntax_args(requests_mock): _zabbix_requests_mock_factory( requests_mock, - response={ + json={ "jsonrpc": "2.0", "result": {"itemids": ["22982", "22986"]}, "id": 0, @@ -135,7 +135,7 @@ def test_attr_syntax_args_and_kwargs_raises(): def test_detecting_version(requests_mock): _zabbix_requests_mock_factory( requests_mock, - response={ + json={ "jsonrpc": "2.0", "result": "4.0.0", "id": 0, @@ -156,7 +156,7 @@ def test_detecting_version(requests_mock): def test_error_response(requests_mock, data): _zabbix_requests_mock_factory( requests_mock, - response={ + json={ "jsonrpc": "2.0", "error": { "code": -32602, @@ -180,7 +180,6 @@ def test_error_response(requests_mock, data): def test_empty_response(requests_mock): _zabbix_requests_mock_factory( requests_mock, - response=None, body="", ) @@ -198,7 +197,7 @@ def test_empty_response(requests_mock): def test_calls(requests_mock, obj, method, params, expected): _zabbix_requests_mock_factory( requests_mock, - response={ + json={ "jsonrpc": "2.0", "result": expected, "id": 0, From e9e8a52f159fb5241a262fac720c693af04a07fd Mon Sep 17 00:00:00 2001 From: jo Date: Thu, 28 Jul 2022 16:07:05 +0200 Subject: [PATCH 104/145] test: login with version detection --- tests/api_test.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/api_test.py b/tests/api_test.py index 8c179cc..3922643 100644 --- a/tests/api_test.py +++ b/tests/api_test.py @@ -69,6 +69,40 @@ def test_login_with_context(requests_mock): assert zapi.auth == "0424bd59b807674191e7d77572075f33" +@pytest.mark.parametrize( + "version", + [ + ("4.0.0"), + ("5.4.0"), + ("6.2.0"), + ], +) +def test_login_with_version_detect(requests_mock, version): + _zabbix_requests_mock_factory( + requests_mock, + [ + { + "json": { + "jsonrpc": "2.0", + "result": version, + "id": 0, + } + }, + { + "json": { + "jsonrpc": "2.0", + "result": "0424bd59b807674191e7d77572075f33", + "id": 0, + } + }, + ], + ) + + with ZabbixAPI("http://example.com") as zapi: + zapi.login("mylogin", "mypass") + assert zapi.auth == "0424bd59b807674191e7d77572075f33" + + def test_attr_syntax_kwargs(requests_mock): _zabbix_requests_mock_factory( requests_mock, From 88abdb40bf7c384c9943c7b8183bd774afffc7dc Mon Sep 17 00:00:00 2001 From: jo Date: Sat, 23 Jul 2022 23:38:14 +0200 Subject: [PATCH 105/145] feat: parse version using packaging.version Remove semantic-version dependency and use packaging.version to parse Zabbix version. Based on the zabbix source code tags, the version format seem to be PEP440 and parsing alpha|beta|rc versions fail using semantic-version. --- pyzabbix/_api.py | 6 ++++-- setup.py | 2 +- tests/api_test.py | 19 ++++++++++++++++--- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/pyzabbix/_api.py b/pyzabbix/_api.py index 4530b58..662c436 100644 --- a/pyzabbix/_api.py +++ b/pyzabbix/_api.py @@ -1,10 +1,12 @@ +# pylint: disable=wrong-import-order + import logging from typing import Mapping, Optional, Sequence, Tuple, Union from warnings import warn +from packaging.version import Version from requests import Session from requests.exceptions import JSONDecodeError -from semantic_version import Version # type: ignore logger = logging.getLogger(__name__) logger.addHandler(logging.NullHandler()) @@ -75,7 +77,7 @@ def __init__( self.url = server logger.info(f"JSON-RPC Server Endpoint: {self.url}") - self.version = "" + self.version: Optional[Version] = None self._detect_version = detect_version def __enter__(self) -> "ZabbixAPI": diff --git a/setup.py b/setup.py index 8a0fa01..b185915 100644 --- a/setup.py +++ b/setup.py @@ -34,7 +34,7 @@ python_requires=">=3.6", install_requires=[ "requests>=1.0", - "semantic-version>=2.8", + "packaging", ], extras_require={ "dev": [ diff --git a/tests/api_test.py b/tests/api_test.py index 3922643..dbf180c 100644 --- a/tests/api_test.py +++ b/tests/api_test.py @@ -75,6 +75,8 @@ def test_login_with_context(requests_mock): ("4.0.0"), ("5.4.0"), ("6.2.0"), + ("6.2.0beta1"), + ("6.2.2alpha1"), ], ) def test_login_with_version_detect(requests_mock, version): @@ -166,18 +168,29 @@ def test_attr_syntax_args_and_kwargs_raises(): zapi.host.delete("22982", hostids=5) -def test_detecting_version(requests_mock): +@pytest.mark.parametrize( + "version", + [ + ("4.0.0"), + ("4.0.0rc1"), + ("6.2.0beta1"), + ("6.2.2alpha1"), + ], +) +def test_detecting_version(requests_mock, version): _zabbix_requests_mock_factory( requests_mock, json={ "jsonrpc": "2.0", - "result": "4.0.0", + "result": version, "id": 0, }, ) zapi = ZabbixAPI("http://example.com") - assert zapi.api_version() == "4.0.0" + zapi.login("mylogin", "mypass") + + assert zapi.api_version() == version @pytest.mark.parametrize( From d5c619025b8563c2a8fa55edb17cd22513ba64e4 Mon Sep 17 00:00:00 2001 From: jo Date: Thu, 28 Jul 2022 16:46:33 +0200 Subject: [PATCH 106/145] chore: add supported python versions badge --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 06d939d..c8bb239 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,8 @@ **PyZabbix** is a Python module for working with the [Zabbix API](https://www.zabbix.com/documentation/current/manual/api/reference). [![CI](https://github.com/lukecyca/pyzabbix/actions/workflows/ci.yml/badge.svg)](https://github.com/lukecyca/pyzabbix/actions/workflows/ci.yml) -[![PyPi version](https://img.shields.io/pypi/v/pyzabbix.svg)](https://pypi.python.org/pypi/pyzabbix/) +[![PyPI Package Version](https://img.shields.io/pypi/v/pyzabbix.svg)](https://pypi.org/project/pyzabbix/) +[![PyPI Python Versions](https://img.shields.io/pypi/pyversions/pyzabbix.svg)](https://pypi.org/project/pyzabbix/) ## Requirements From 2ac17e63c80575e738ee8456449834e2e859cfb6 Mon Sep 17 00:00:00 2001 From: jo Date: Thu, 28 Jul 2022 23:24:27 +0200 Subject: [PATCH 107/145] chore: define exported symbols for api --- pyzabbix/__init__.py | 2 +- pyzabbix/{_api.py => api.py} | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) rename pyzabbix/{_api.py => api.py} (98%) diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py index 388e2ea..5603ad6 100644 --- a/pyzabbix/__init__.py +++ b/pyzabbix/__init__.py @@ -1,4 +1,4 @@ -from ._api import ( +from .api import ( ZabbixAPI, ZabbixAPIException, ZabbixAPIMethod, diff --git a/pyzabbix/_api.py b/pyzabbix/api.py similarity index 98% rename from pyzabbix/_api.py rename to pyzabbix/api.py index 662c436..16e7176 100644 --- a/pyzabbix/_api.py +++ b/pyzabbix/api.py @@ -8,6 +8,14 @@ from requests import Session from requests.exceptions import JSONDecodeError +__all__ = [ + "ZabbixAPI", + "ZabbixAPIException", + "ZabbixAPIMethod", + "ZabbixAPIObject", + "ZabbixAPIObjectClass", +] + logger = logging.getLogger(__name__) logger.addHandler(logging.NullHandler()) From 7ff4e9574647c5089645aa167f375a9b280d2e1c Mon Sep 17 00:00:00 2001 From: jo Date: Fri, 29 Jul 2022 00:48:54 +0200 Subject: [PATCH 108/145] ci: don't fails-fast for e2e tests --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cf7e78e..980c4cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,6 +65,7 @@ jobs: e2e: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: zabbix-version: ["4.0", "5.0", "6.0", "6.2"] From ed026227ebfc748b0afab1fd85e1ded071e37abd Mon Sep 17 00:00:00 2001 From: jo Date: Fri, 29 Jul 2022 01:09:49 +0200 Subject: [PATCH 109/145] chore: make zabbix more reactive to change --- docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.yml b/docker-compose.yml index 7cd86cb..fd7d4af 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,6 +18,7 @@ services: environment: POSTGRES_USER: zabbix POSTGRES_PASSWORD: zabbix + ZBX_CACHEUPDATEFREQUENCY: 1 depends_on: - postgres-server From d2412dc759b18a17b7ce93e8d5df2be35e8315f0 Mon Sep 17 00:00:00 2001 From: jo Date: Thu, 4 Aug 2022 12:15:51 +0200 Subject: [PATCH 110/145] fix: catch ValueError during json parsing Only recent versions of requests have the JSONDecodeError exceptions. https://github.com/psf/requests/issues/5794 https://requests.readthedocs.io/en/latest/community/updates/?highlight=JSONDecodeError#id4 --- pyzabbix/api.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyzabbix/api.py b/pyzabbix/api.py index 16e7176..fe12e69 100644 --- a/pyzabbix/api.py +++ b/pyzabbix/api.py @@ -6,7 +6,6 @@ from packaging.version import Version from requests import Session -from requests.exceptions import JSONDecodeError __all__ = [ "ZabbixAPI", @@ -218,7 +217,7 @@ def do_request( try: response = resp.json() - except JSONDecodeError as exception: + except ValueError as exception: raise ZabbixAPIException( f"Unable to parse json: {resp.text}" ) from exception From 8041d767e3161a9c56bfadaaf84b5b39fab183d8 Mon Sep 17 00:00:00 2001 From: jo Date: Thu, 4 Aug 2022 12:20:18 +0200 Subject: [PATCH 111/145] chore: release 1.2.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b185915..f2aa615 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name="pyzabbix", - version="1.1.0", + version="1.2.0", description="Zabbix API Python interface", long_description=long_description, long_description_content_type="text/markdown", From 2307437fd4c62bc34b88b08c1053db1fb1cfc611 Mon Sep 17 00:00:00 2001 From: jo Date: Thu, 4 Aug 2022 12:22:09 +0200 Subject: [PATCH 112/145] chore: regenerate CHANGELOG --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57d1bba..6865f72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ + + +## [1.2.0](https://github.com/lukecyca/pyzabbix/compare/1.1.0...1.2.0) (2022-08-04) + +### :bug: Bug Fixes + +- catch ValueError during json parsing + +### :rocket: Features + +- parse version using packaging.version + ## [1.1.0](https://github.com/lukecyca/pyzabbix/compare/1.0.0...1.1.0) (2022-07-28) From 779552b3da6c5848b7e4ecdb157d0b3e18f9612d Mon Sep 17 00:00:00 2001 From: jo Date: Thu, 25 Aug 2022 16:52:11 +0200 Subject: [PATCH 113/145] fix: improve deprecation message for confimport --- pyzabbix/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyzabbix/api.py b/pyzabbix/api.py index fe12e69..fae99ce 100644 --- a/pyzabbix/api.py +++ b/pyzabbix/api.py @@ -168,8 +168,8 @@ def confimport( :param confformat: """ warn( - "ZabbixAPI.confimport() has been deprecated, please use " - "ZabbixAPI.configuration['import']() instead", + "ZabbixAPI.confimport(format, source, rules) has been deprecated, please use " + "ZabbixAPI.configuration['import'](format=format, source=source, rules=rules) instead", DeprecationWarning, 2, ) From 5018ebc4cd407af4fc6783a076cb3065ce04421d Mon Sep 17 00:00:00 2001 From: jo Date: Thu, 25 Aug 2022 16:55:24 +0200 Subject: [PATCH 114/145] chore: release 1.2.1 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f2aa615..fe37ec1 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name="pyzabbix", - version="1.2.0", + version="1.2.1", description="Zabbix API Python interface", long_description=long_description, long_description_content_type="text/markdown", From 31032ca820daab49edeedae9c89dd73329874bd4 Mon Sep 17 00:00:00 2001 From: jo Date: Thu, 25 Aug 2022 16:56:43 +0200 Subject: [PATCH 115/145] chore: regenerate CHANGELOG --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6865f72..db6e272 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ + + +## [1.2.1](https://github.com/lukecyca/pyzabbix/compare/1.2.0...1.2.1) (2022-08-25) + +### :bug: Bug Fixes + +- improve deprecation message for confimport + ## [1.2.0](https://github.com/lukecyca/pyzabbix/compare/1.1.0...1.2.0) (2022-08-04) From 78ca11ddce9726b155accbe9e57a0e3246e8f9b9 Mon Sep 17 00:00:00 2001 From: jo Date: Thu, 25 Aug 2022 17:02:23 +0200 Subject: [PATCH 116/145] ci: don't publish package on forks --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 980c4cb..b3f822a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -112,6 +112,7 @@ jobs: - run: make clean build - - uses: pypa/gh-action-pypi-publish@v1.5.0 + - if: github.repository_owner == 'lukecyca' + uses: pypa/gh-action-pypi-publish@v1.5.0 with: password: ${{ secrets.PYPI_API_TOKEN }} From 900a60f32fbc71ad666c54dd5140075020c15524 Mon Sep 17 00:00:00 2001 From: jo Date: Thu, 27 Oct 2022 17:52:59 +0200 Subject: [PATCH 117/145] ci: test python3.11 --- .github/workflows/ci.yml | 2 +- setup.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b3f822a..439a0a7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,7 +43,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"] + python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11"] steps: - uses: actions/checkout@v3 diff --git a/setup.py b/setup.py index fe37ec1..0b2f7ea 100644 --- a/setup.py +++ b/setup.py @@ -21,6 +21,7 @@ "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", "Operating System :: OS Independent", "Development Status :: 4 - Beta", From b0979b8bfc2dddb76226c0b054e093fef32e0c81 Mon Sep 17 00:00:00 2001 From: jo Date: Tue, 21 Mar 2023 23:09:05 +0100 Subject: [PATCH 118/145] test: e2e on zabbix 6.4 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 439a0a7..698a92f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,7 +67,7 @@ jobs: strategy: fail-fast: false matrix: - zabbix-version: ["4.0", "5.0", "6.0", "6.2"] + zabbix-version: ["4.0", "5.0", "6.0", "6.4"] env: ZABBIX_VERSION: ${{ matrix.zabbix-version }} From f2ab9c24ca819347a7626b837eea504f56317df9 Mon Sep 17 00:00:00 2001 From: jo Date: Tue, 21 Mar 2023 23:10:42 +0100 Subject: [PATCH 119/145] ci: run on schedule (weekly) --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 698a92f..466de56 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,9 @@ on: pull_request: branches: [master] + schedule: + - cron: 37 1 * * 1 + jobs: pre-commit: runs-on: ubuntu-latest From f554e1461d97ae57b908d15e6111f7d1955cb9fa Mon Sep 17 00:00:00 2001 From: jo Date: Tue, 21 Mar 2023 23:13:12 +0100 Subject: [PATCH 120/145] ci: py3.6 is missing from ubuntu-22.04 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 466de56..9b42263 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,7 +43,7 @@ jobs: - run: make lint test: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 strategy: matrix: python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11"] From 22ee838d81a62825b4b33a775a7506c31a4a01b7 Mon Sep 17 00:00:00 2001 From: jo Date: Tue, 21 Mar 2023 23:25:17 +0100 Subject: [PATCH 121/145] chore: update tested zabbix versions --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c8bb239..eff078d 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ ## Requirements -- Tested against Zabbix 4.0 LTS, 5.0 LTS, 6.0 LTS and 6.2. +- Tested against Zabbix 4.0 LTS, 5.0 LTS, 6.0 LTS and 6.4. ## Documentation From 22efd1a2f14762cdd13138ad0e3df820b242d926 Mon Sep 17 00:00:00 2001 From: jo Date: Wed, 29 Mar 2023 17:59:51 +0200 Subject: [PATCH 122/145] refactor: move zabbix versions to constants --- pyzabbix/api.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyzabbix/api.py b/pyzabbix/api.py index fae99ce..96dd3ef 100644 --- a/pyzabbix/api.py +++ b/pyzabbix/api.py @@ -18,6 +18,8 @@ logger = logging.getLogger(__name__) logger.addHandler(logging.NullHandler()) +ZABBIX_5_4_0 = Version("5.4.0") + class ZabbixAPIException(Exception): """Generic Zabbix API exception @@ -131,7 +133,7 @@ def login( self.auth = "" if self.use_authenticate: self.auth = self.user.authenticate(user=user, password=password) - elif self.version and self.version >= Version("5.4.0"): + elif self.version and self.version >= ZABBIX_5_4_0: self.auth = self.user.login(username=user, password=password) else: self.auth = self.user.login(user=user, password=password) From c8631ac1a82868df89ee2afb6ea9ee03cc2c2b22 Mon Sep 17 00:00:00 2001 From: jo Date: Wed, 29 Mar 2023 18:00:58 +0200 Subject: [PATCH 123/145] chore: upgrade pre-commit hooks --- .pre-commit-config.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1737a58..0f79e54 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,7 @@ # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v4.4.0 hooks: - id: check-added-large-files - id: check-case-conflict @@ -30,18 +30,18 @@ repos: files: \.(md|yml|yaml|json)$ - repo: https://github.com/codespell-project/codespell - rev: v2.1.0 + rev: v2.2.4 hooks: - id: codespell exclude: ^examples/additemcsv.py$ - repo: https://github.com/psf/black - rev: 22.3.0 + rev: 23.3.0 hooks: - id: black - repo: https://github.com/asottile/pyupgrade - rev: v2.34.0 + rev: v3.3.1 hooks: - id: pyupgrade args: [--py3-plus, --py36-plus] From 92b5d00233d8ca39c394f3f55f5cdc02666c0dc6 Mon Sep 17 00:00:00 2001 From: jo Date: Wed, 29 Mar 2023 18:01:15 +0200 Subject: [PATCH 124/145] style: format code using black v23 --- examples/export_history_csv.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/export_history_csv.py b/examples/export_history_csv.py index b46e450..564d066 100644 --- a/examples/export_history_csv.py +++ b/examples/export_history_csv.py @@ -109,7 +109,6 @@ def assignTimeRange(inputParameters, datetime1, datetime2): def fetch_to_csv( username, password, server, hostname, key, output, datetime1, datetime2, debuglevel ): - print("login to zabbix server %s" % server) zapi = ZabbixAPI(server + "/zabbix") login(zapi, username, password) From 4684c7aa919c3d99d36890675e9b9055b5fcbac3 Mon Sep 17 00:00:00 2001 From: jo Date: Wed, 29 Mar 2023 18:04:33 +0200 Subject: [PATCH 125/145] chore: don't use venv activation in makefile --- Makefile | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/Makefile b/Makefile index 3f76ef2..c42a489 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,5 @@ all: lint test -.ONESHELL: .DEFAULT_GOAL: install SHELL = bash @@ -9,33 +8,28 @@ CPU_CORES = $(shell nproc) VENV = .venv $(VENV): python3 -m venv $(VENV) - source $(VENV)/bin/activate $(MAKE) install install: $(VENV) - source $(VENV)/bin/activate - pip install --upgrade pip setuptools wheel build - pip install --editable .[dev] + $(VENV)/bin/pip install --upgrade pip setuptools wheel build + $(VENV)/bin/pip install --editable .[dev] format: $(VENV) - source $(VENV)/bin/activate - black . - isort . --profile black + $(VENV)/bin/black . + $(VENV)/bin/isort . --profile black lint: $(VENV) - source $(VENV)/bin/activate - black . --check - isort . --profile black --check - pylint --jobs=$(CPU_CORES) --output-format=colorized pyzabbix tests - mypy pyzabbix tests || true + $(VENV)/bin/black . --check + $(VENV)/bin/isort . --profile black --check + $(VENV)/bin/pylint --jobs=$(CPU_CORES) --output-format=colorized pyzabbix tests + $(VENV)/bin/mypy pyzabbix tests || true -PYTEST_CMD = pytest -v \ +PYTEST_CMD = $(VENV)/bin/pytest -v \ --numprocesses=$(CPU_CORES) \ --color=yes test: $(VENV) - source $(VENV)/bin/activate $(PYTEST_CMD) \ --cov-config=./pyproject.toml \ --cov-report=term \ @@ -45,12 +39,10 @@ test: $(VENV) .PHONY: e2e e2e: $(VENV) - source $(VENV)/bin/activate $(PYTEST_CMD) e2e build: $(VENV) - source $(VENV)/bin/activate - python -m build . + $(VENV)/bin/python -m build . release: ./scripts/release.sh From f75872c771065a2ad7ee977fa2dfb5e2073f6beb Mon Sep 17 00:00:00 2001 From: jo Date: Wed, 29 Mar 2023 18:04:55 +0200 Subject: [PATCH 126/145] test: allow mypy failure --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c42a489..13b34c2 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ lint: $(VENV) $(VENV)/bin/black . --check $(VENV)/bin/isort . --profile black --check $(VENV)/bin/pylint --jobs=$(CPU_CORES) --output-format=colorized pyzabbix tests - $(VENV)/bin/mypy pyzabbix tests || true + $(VENV)/bin/mypy pyzabbix tests PYTEST_CMD = $(VENV)/bin/pytest -v \ From 37801b3ea76523e9aab81db23e29b92b789ba9f4 Mon Sep 17 00:00:00 2001 From: jo Date: Wed, 29 Mar 2023 18:07:22 +0200 Subject: [PATCH 127/145] style: format pyproject.toml --- pyproject.toml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 17095ac..6a6827f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,17 +10,14 @@ disable = "logging-fstring-interpolation" [tool.mypy] allow_redefinition = true -disallow_incomplete_defs= true +disallow_incomplete_defs = true [tool.pytest.ini_options] log_cli = true log_cli_level = "DEBUG" [tool.coverage.run] -omit = [ - "tests/*", - "e2e/*", -] +omit = ["tests/*", "e2e/*"] [build-system] requires = ["setuptools", "wheel"] From c69a0830a4009ee347e182b8d7e58ce388cb6888 Mon Sep 17 00:00:00 2001 From: jo Date: Wed, 29 Mar 2023 18:08:24 +0200 Subject: [PATCH 128/145] chore: move isort config to pyproject.toml --- Makefile | 4 ++-- pyproject.toml | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 13b34c2..7438e6f 100644 --- a/Makefile +++ b/Makefile @@ -16,11 +16,11 @@ install: $(VENV) format: $(VENV) $(VENV)/bin/black . - $(VENV)/bin/isort . --profile black + $(VENV)/bin/isort . lint: $(VENV) $(VENV)/bin/black . --check - $(VENV)/bin/isort . --profile black --check + $(VENV)/bin/isort . --check $(VENV)/bin/pylint --jobs=$(CPU_CORES) --output-format=colorized pyzabbix tests $(VENV)/bin/mypy pyzabbix tests diff --git a/pyproject.toml b/pyproject.toml index 6a6827f..b8ec283 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,7 @@ +[tool.isort] +profile = "black" +combine_as_imports = true + [tool.pylint.messages_control] disable = [ "missing-class-docstring", From 72ea1a65dff63c61c598a8badfff56bcb9a1d45f Mon Sep 17 00:00:00 2001 From: jo Date: Wed, 29 Mar 2023 18:08:35 +0200 Subject: [PATCH 129/145] chore: add isort pre-commit hook --- .pre-commit-config.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0f79e54..8e10645 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -35,6 +35,11 @@ repos: - id: codespell exclude: ^examples/additemcsv.py$ + - repo: https://github.com/pycqa/isort + rev: 5.12.0 + hooks: + - id: isort + - repo: https://github.com/psf/black rev: 23.3.0 hooks: From 60d99b9a35795e69f6ed64fab628e4fe0d5399bc Mon Sep 17 00:00:00 2001 From: jo Date: Sat, 8 Apr 2023 14:03:50 +0200 Subject: [PATCH 130/145] refactor: move anonymous methods in a set --- pyzabbix/api.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pyzabbix/api.py b/pyzabbix/api.py index 96dd3ef..e9ec2be 100644 --- a/pyzabbix/api.py +++ b/pyzabbix/api.py @@ -199,11 +199,11 @@ def do_request( # We don't have to pass the auth token if asking for # the apiinfo.version or user.checkAuthentication - if ( - self.auth - and method != "apiinfo.version" - and method != "user.checkAuthentication" - ): + anonymous_methods = { + "apiinfo.version", + "user.checkAuthentication", + } + if self.auth and method not in anonymous_methods: payload["auth"] = self.auth logger.debug(f"Sending: {payload}") From ec880e25ebff5c3976a2a602b14cf418ac6bfef4 Mon Sep 17 00:00:00 2001 From: jo Date: Sat, 8 Apr 2023 14:22:43 +0200 Subject: [PATCH 131/145] chore: limit parallel jobs to <=4 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 7438e6f..8e751c4 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ all: lint test .DEFAULT_GOAL: install SHELL = bash -CPU_CORES = $(shell nproc) +CPU_CORES = $(shell N=$$(nproc); echo $$(( $$N > 4 ? 4 : $$N ))) VENV = .venv $(VENV): From 821fefb4332c379b469f724c80cf002639c5cf1a Mon Sep 17 00:00:00 2001 From: jo Date: Sat, 8 Apr 2023 15:25:23 +0200 Subject: [PATCH 132/145] test: rewrite test_calls in test_do_request --- tests/api_test.py | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/tests/api_test.py b/tests/api_test.py index dbf180c..f0a9873 100644 --- a/tests/api_test.py +++ b/tests/api_test.py @@ -1,4 +1,5 @@ import pytest +from packaging.version import Version from pyzabbix import ZabbixAPI, ZabbixAPIException @@ -236,33 +237,45 @@ def test_empty_response(requests_mock): @pytest.mark.parametrize( - "obj, method, params, expected", + "version", [ - ("host", "get", {}, [{"hostid": 1234}]), + ("4.0.0"), + ("5.4.0"), + ("6.2.0"), ], ) -def test_calls(requests_mock, obj, method, params, expected): +def test_do_request(requests_mock, version): _zabbix_requests_mock_factory( requests_mock, json={ "jsonrpc": "2.0", - "result": expected, + "result": [{"hostid": 1234}], "id": 0, }, ) zapi = ZabbixAPI("http://example.com", detect_version=False) + zapi.version = Version(version) zapi.auth = "some_auth_key" - result = zapi[obj][method](**params) + result = zapi["host"]["get"]() + + # Check response + assert result == [{"hostid": 1234}] # Check request - assert requests_mock.last_request.json() == { + found = requests_mock.last_request + expect_json = { "jsonrpc": "2.0", - "method": f"{obj}.{method}", - "params": params, + "method": "host.get", + "params": {}, "auth": "some_auth_key", "id": 0, } + expect_headers = { + "Cache-Control": "no-cache", + "Content-Type": "application/json-rpc", + "User-Agent": "python/pyzabbix", + } - # Check response - assert result == expected + assert found.json() == expect_json + assert found.headers.items() >= expect_headers.items() From 3d0d86e1253ecd30c8b1d9c730fd61225432a6a6 Mon Sep 17 00:00:00 2001 From: jo Date: Sat, 8 Apr 2023 14:12:18 +0200 Subject: [PATCH 133/145] feat: add zabbix 6.4 header authentication In Zabbix 6.4, the 'auth' parameter to method calls is deprecated. Use the 'Authorization: Bearer' header instead. Co-authored-by: Christian Ullrich --- pyzabbix/api.py | 15 +++++++++++++-- tests/api_test.py | 6 +++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/pyzabbix/api.py b/pyzabbix/api.py index e9ec2be..d3d2fe1 100644 --- a/pyzabbix/api.py +++ b/pyzabbix/api.py @@ -19,6 +19,7 @@ logger.addHandler(logging.NullHandler()) ZABBIX_5_4_0 = Version("5.4.0") +ZABBIX_6_4_0 = Version("6.4.0") class ZabbixAPIException(Exception): @@ -196,18 +197,28 @@ def do_request( "params": params or {}, "id": self.id, } + headers = {} # We don't have to pass the auth token if asking for # the apiinfo.version or user.checkAuthentication anonymous_methods = { "apiinfo.version", "user.checkAuthentication", + "user.login", } if self.auth and method not in anonymous_methods: - payload["auth"] = self.auth + if self.version and self.version >= ZABBIX_6_4_0: + headers["Authorization"] = f"Bearer {self.auth}" + else: + payload["auth"] = self.auth logger.debug(f"Sending: {payload}") - resp = self.session.post(self.url, json=payload, timeout=self.timeout) + resp = self.session.post( + self.url, + json=payload, + headers=headers, + timeout=self.timeout, + ) logger.debug(f"Response Code: {resp.status_code}") # NOTE: Getting a 412 response code means the headers are not in the diff --git a/tests/api_test.py b/tests/api_test.py index f0a9873..1effe85 100644 --- a/tests/api_test.py +++ b/tests/api_test.py @@ -268,7 +268,6 @@ def test_do_request(requests_mock, version): "jsonrpc": "2.0", "method": "host.get", "params": {}, - "auth": "some_auth_key", "id": 0, } expect_headers = { @@ -277,5 +276,10 @@ def test_do_request(requests_mock, version): "User-Agent": "python/pyzabbix", } + if zapi.version < Version("6.4.0"): + expect_json["auth"] = "some_auth_key" + else: + expect_headers["Authorization"] = "Bearer some_auth_key" + assert found.json() == expect_json assert found.headers.items() >= expect_headers.items() From f962fef7dc6d573cf6dbc54dff5a22f7f38414c9 Mon Sep 17 00:00:00 2001 From: jo Date: Sat, 8 Apr 2023 15:42:04 +0200 Subject: [PATCH 134/145] chore: release 1.3.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 0b2f7ea..a0bde9a 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name="pyzabbix", - version="1.2.1", + version="1.3.0", description="Zabbix API Python interface", long_description=long_description, long_description_content_type="text/markdown", From 59bcb0ec902ed56ad657a74efbbf94d581115edd Mon Sep 17 00:00:00 2001 From: jo Date: Sat, 8 Apr 2023 15:47:17 +0200 Subject: [PATCH 135/145] chore: add changelog generator --- .chglog/CHANGELOG.md.tpl | 31 +++++++++++++++++++++++++++++++ .chglog/config.yml | 23 +++++++++++++++++++++++ Makefile | 6 ++++++ 3 files changed, 60 insertions(+) create mode 100644 .chglog/CHANGELOG.md.tpl create mode 100644 .chglog/config.yml diff --git a/.chglog/CHANGELOG.md.tpl b/.chglog/CHANGELOG.md.tpl new file mode 100644 index 0000000..d3b51aa --- /dev/null +++ b/.chglog/CHANGELOG.md.tpl @@ -0,0 +1,31 @@ +{{ range .Versions -}} + + +## {{ if .Tag.Previous }}[{{ .Tag.Name }}]({{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }}){{ else }}{{ .Tag.Name }}{{ end }} ({{ datetime "2006-01-02" .Tag.Date }}) + +{{ range .CommitGroups -}} +### {{ .Title }} + +{{ range .Commits -}} +- {{ .Subject }} +{{ end }} +{{ end -}} + +{{- if .RevertCommits -}} +### Reverts + +{{ range .RevertCommits -}} +- {{ .Revert.Header }} +{{ end }} +{{ end -}} + +{{- if .NoteGroups -}} +{{ range .NoteGroups -}} +### {{ .Title }} + +{{ range .Notes }} +{{ .Body }} +{{ end }} +{{ end -}} +{{ end -}} +{{ end -}} diff --git a/.chglog/config.yml b/.chglog/config.yml new file mode 100644 index 0000000..a61d73b --- /dev/null +++ b/.chglog/config.yml @@ -0,0 +1,23 @@ +style: github +template: CHANGELOG.md.tpl +info: + title: CHANGELOG + repository_url: https://github.com/lukecyca/pyzabbix +options: + commits: + filters: + Type: + - feat + - fix + commit_groups: + title_maps: + feat: ":rocket: Features" + fix: ":bug: Bug Fixes" + header: + pattern: "^(\\w*)\\:\\s(.*)$" + pattern_maps: + - Type + - Subject + notes: + keywords: + - BREAKING CHANGE diff --git a/Makefile b/Makefile index 8e751c4..00d07a7 100644 --- a/Makefile +++ b/Makefile @@ -47,5 +47,11 @@ build: $(VENV) release: ./scripts/release.sh +changelog: + git-chglog --output CHANGELOG.md 1.1.0.. + if command -v npx > /dev/null; then npx prettier --write CHANGELOG.md; fi + git add CHANGELOG.md + git commit -m "docs: regenerate changelog" + clean: rm -Rf $(VENV) dist From 8747c185503a74282df2b66d07ccb8f58a114d86 Mon Sep 17 00:00:00 2001 From: jo Date: Sat, 8 Apr 2023 15:49:42 +0200 Subject: [PATCH 136/145] docs: regenerate changelog --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index db6e272..ff62dbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ + + +## [1.3.0](https://github.com/lukecyca/pyzabbix/compare/1.2.1...1.3.0) (2023-04-08) + +### :rocket: Features + +- add zabbix 6.4 header authentication + ## [1.2.1](https://github.com/lukecyca/pyzabbix/compare/1.2.0...1.2.1) (2022-08-25) From 1f3a9afd75561e0a07cdbeb450bb42027ab9b0b2 Mon Sep 17 00:00:00 2001 From: jo Date: Sat, 8 Apr 2023 15:57:24 +0200 Subject: [PATCH 137/145] ci: create release note on tag --- .github/workflows/ci.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9b42263..6d63fbf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -119,3 +119,10 @@ jobs: uses: pypa/gh-action-pypi-publish@v1.5.0 with: password: ${{ secrets.PYPI_API_TOKEN }} + + - if: github.repository_owner == 'lukecyca' + uses: softprops/action-gh-release@v1 + with: + draft: true + body: | + Please see [CHANGELOG.md](https://github.com/lukecyca/pyzabbix/blob/master/CHANGELOG.md) From 9b8110fcc323ac52f6af3393839af4517359b95f Mon Sep 17 00:00:00 2001 From: jo Date: Sat, 6 May 2023 11:16:06 +0200 Subject: [PATCH 138/145] chore: upgrade pre-commit hooks --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8e10645..1e15b81 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -46,7 +46,7 @@ repos: - id: black - repo: https://github.com/asottile/pyupgrade - rev: v3.3.1 + rev: v3.3.2 hooks: - id: pyupgrade args: [--py3-plus, --py36-plus] From 511e3a3c473970acc84b706cfa5617e7e05c2fa0 Mon Sep 17 00:00:00 2001 From: jo Date: Sat, 6 May 2023 11:16:43 +0200 Subject: [PATCH 139/145] refactor: reenable logging-fstring-interpolation --- pyproject.toml | 3 --- pyzabbix/api.py | 10 +++++----- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b8ec283..dd8c9ef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,9 +9,6 @@ disable = [ "missing-module-docstring", ] -[tool.pylint.format] -disable = "logging-fstring-interpolation" - [tool.mypy] allow_redefinition = true disallow_incomplete_defs = true diff --git a/pyzabbix/api.py b/pyzabbix/api.py index d3d2fe1..4e2deea 100644 --- a/pyzabbix/api.py +++ b/pyzabbix/api.py @@ -85,7 +85,7 @@ def __init__( if not server.endswith("/api_jsonrpc.php"): server = server.rstrip("/") + "/api_jsonrpc.php" self.url = server - logger.info(f"JSON-RPC Server Endpoint: {self.url}") + logger.info("JSON-RPC Server Endpoint: %s", self.url) self.version: Optional[Version] = None self._detect_version = detect_version @@ -121,7 +121,7 @@ def login( if self._detect_version: self.version = Version(self.api_version()) - logger.info(f"Zabbix API version is: {self.version}") + logger.info("Zabbix API version is: %s", self.version) # If the API token is explicitly provided, use this instead. if api_token is not None: @@ -212,14 +212,14 @@ def do_request( else: payload["auth"] = self.auth - logger.debug(f"Sending: {payload}") + logger.debug("Sending: %s", payload) resp = self.session.post( self.url, json=payload, headers=headers, timeout=self.timeout, ) - logger.debug(f"Response Code: {resp.status_code}") + logger.debug("Response Code: %s", resp.status_code) # NOTE: Getting a 412 response code means the headers are not in the # list of allowed headers. @@ -235,7 +235,7 @@ def do_request( f"Unable to parse json: {resp.text}" ) from exception - logger.debug(f"Response Body: {response}") + logger.debug("Response Body: %s", response) self.id += 1 From a49401b17287a4fff06d422bf4b8a239ad543be2 Mon Sep 17 00:00:00 2001 From: jo Date: Sun, 9 Jul 2023 10:33:37 +0200 Subject: [PATCH 140/145] chore: upgrade pre-commit hooks --- .pre-commit-config.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1e15b81..41c715a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,13 +24,13 @@ repos: - id: name-tests-test - repo: https://github.com/pre-commit/mirrors-prettier - rev: v2.7.1 + rev: v3.0.0 hooks: - id: prettier files: \.(md|yml|yaml|json)$ - repo: https://github.com/codespell-project/codespell - rev: v2.2.4 + rev: v2.2.5 hooks: - id: codespell exclude: ^examples/additemcsv.py$ @@ -41,12 +41,12 @@ repos: - id: isort - repo: https://github.com/psf/black - rev: 23.3.0 + rev: 23.7.0 hooks: - id: black - repo: https://github.com/asottile/pyupgrade - rev: v3.3.2 + rev: v3.9.0 hooks: - id: pyupgrade args: [--py3-plus, --py36-plus] From 962693d88a7cb3c53aeb0b7d62fc7198921abca8 Mon Sep 17 00:00:00 2001 From: Jonas L Date: Sun, 17 Sep 2023 12:51:46 +0200 Subject: [PATCH 141/145] docs: add link to the zappix project (#217) * docs: add link to zappix project Related to https://github.com/lukecyca/pyzabbix/issues/197 * more links and fmt * remove gitlab link as the package already reference this. --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index eff078d..5555d25 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,9 @@ ## Documentation +> [!NOTE] +> If you are looking for a `Zabbix Sender`, please use the [`zappix` library](https://pypi.org/project/zappix). + ### Getting Started Install PyZabbix using pip: From b88b787e790d097daa225d00622703758f8810ab Mon Sep 17 00:00:00 2001 From: Jonas L Date: Tue, 3 Oct 2023 19:01:47 +0200 Subject: [PATCH 142/145] ci: add python 3.12 to the test matrix (#220) --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6d63fbf..d1106cd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,7 +46,7 @@ jobs: runs-on: ubuntu-20.04 strategy: matrix: - python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11"] + python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v3 From 9a41ee932f64df5e1c36e178d5ceb02a8b53fa0a Mon Sep 17 00:00:00 2001 From: Jonas L Date: Mon, 18 Dec 2023 21:47:22 +0100 Subject: [PATCH 143/145] chore: setup release-please (#221) --- .chglog/CHANGELOG.md.tpl | 31 --------------- .chglog/config.yml | 23 ------------ .github/release-please-config.json | 11 ++++++ .github/release-please-manifest.json | 1 + .github/workflows/release-please.yml | 16 ++++++++ .hgtags | 2 - .pre-commit-config.yaml | 1 + CHANGELOG.md | 2 + Makefile | 9 ----- README.md | 14 ------- scripts/release.sh | 56 ---------------------------- 11 files changed, 31 insertions(+), 135 deletions(-) delete mode 100644 .chglog/CHANGELOG.md.tpl delete mode 100644 .chglog/config.yml create mode 100644 .github/release-please-config.json create mode 100644 .github/release-please-manifest.json create mode 100644 .github/workflows/release-please.yml delete mode 100644 .hgtags delete mode 100755 scripts/release.sh diff --git a/.chglog/CHANGELOG.md.tpl b/.chglog/CHANGELOG.md.tpl deleted file mode 100644 index d3b51aa..0000000 --- a/.chglog/CHANGELOG.md.tpl +++ /dev/null @@ -1,31 +0,0 @@ -{{ range .Versions -}} - - -## {{ if .Tag.Previous }}[{{ .Tag.Name }}]({{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }}){{ else }}{{ .Tag.Name }}{{ end }} ({{ datetime "2006-01-02" .Tag.Date }}) - -{{ range .CommitGroups -}} -### {{ .Title }} - -{{ range .Commits -}} -- {{ .Subject }} -{{ end }} -{{ end -}} - -{{- if .RevertCommits -}} -### Reverts - -{{ range .RevertCommits -}} -- {{ .Revert.Header }} -{{ end }} -{{ end -}} - -{{- if .NoteGroups -}} -{{ range .NoteGroups -}} -### {{ .Title }} - -{{ range .Notes }} -{{ .Body }} -{{ end }} -{{ end -}} -{{ end -}} -{{ end -}} diff --git a/.chglog/config.yml b/.chglog/config.yml deleted file mode 100644 index a61d73b..0000000 --- a/.chglog/config.yml +++ /dev/null @@ -1,23 +0,0 @@ -style: github -template: CHANGELOG.md.tpl -info: - title: CHANGELOG - repository_url: https://github.com/lukecyca/pyzabbix -options: - commits: - filters: - Type: - - feat - - fix - commit_groups: - title_maps: - feat: ":rocket: Features" - fix: ":bug: Bug Fixes" - header: - pattern: "^(\\w*)\\:\\s(.*)$" - pattern_maps: - - Type - - Subject - notes: - keywords: - - BREAKING CHANGE diff --git a/.github/release-please-config.json b/.github/release-please-config.json new file mode 100644 index 0000000..7ea4a00 --- /dev/null +++ b/.github/release-please-config.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "bootstrap-sha": "f962fef7dc6d573cf6dbc54dff5a22f7f38414c9", + "include-component-in-tag": false, + "packages": { + ".": { + "release-type": "python", + "package-name": "pyzabbix" + } + } +} diff --git a/.github/release-please-manifest.json b/.github/release-please-manifest.json new file mode 100644 index 0000000..c17c08f --- /dev/null +++ b/.github/release-please-manifest.json @@ -0,0 +1 @@ +{".":"1.3.0"} diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 0000000..b56155c --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,16 @@ +name: Release please + +on: + push: + branches: [master] + +jobs: + release-please: + if: github.repository == 'lukecyca/pyzabbix' + + runs-on: ubuntu-latest + steps: + - uses: google-github-actions/release-please-action@v4 + with: + config-file: .github/release-please-config.json + manifest-file: .github/release-please-manifest.json diff --git a/.hgtags b/.hgtags deleted file mode 100644 index 030ff51..0000000 --- a/.hgtags +++ /dev/null @@ -1,2 +0,0 @@ -5211cf05c2f81c064e08075cdd2689760df0f7d1 0.1 -c70d2bb0ade06f5dbfb40a382a7bc17ad3d62bbc 0.1 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 41c715a..720208f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -28,6 +28,7 @@ repos: hooks: - id: prettier files: \.(md|yml|yaml|json)$ + exclude: (\.github/release-please-manifest\.json|CHANGELOG\.md)$ - repo: https://github.com/codespell-project/codespell rev: v2.2.5 diff --git a/CHANGELOG.md b/CHANGELOG.md index ff62dbb..a48301f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +# Changelog + ## [1.3.0](https://github.com/lukecyca/pyzabbix/compare/1.2.1...1.3.0) (2023-04-08) diff --git a/Makefile b/Makefile index 00d07a7..ccf99af 100644 --- a/Makefile +++ b/Makefile @@ -44,14 +44,5 @@ e2e: $(VENV) build: $(VENV) $(VENV)/bin/python -m build . -release: - ./scripts/release.sh - -changelog: - git-chglog --output CHANGELOG.md 1.1.0.. - if command -v npx > /dev/null; then npx prettier --write CHANGELOG.md; fi - git add CHANGELOG.md - git commit -m "docs: regenerate changelog" - clean: rm -Rf $(VENV) dist diff --git a/README.md b/README.md index 5555d25..5dcbb76 100644 --- a/README.md +++ b/README.md @@ -128,20 +128,6 @@ make install source .venv/bin/activate ``` -### Releases - -To release a new version, first bump the version number in `setup.py` by hand and run the release target: - -```sh -make release -``` - -Finally, push the release commit and tag to publish them to Pypi: - -```sh -git push --follow-tags -``` - ## License LGPL 2.1 http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html diff --git a/scripts/release.sh b/scripts/release.sh deleted file mode 100755 index 698494d..0000000 --- a/scripts/release.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env bash - -set -u - -error() { - echo >&2 "error: $*" - exit 1 -} - -# This scripts will: -# - Expect the setup.py file to have new version -# - Stash the setup.py file -# - Check for clean state (no uncommitted changes), exit if failed -# - Clean the project -# - Install the project, lint and runt tests, exit if failed -# - Unstash the pyproject version bump and commit a new Release -# - Tag the new release -# - Show instruction to push tags and changes to github - -command -v make > /dev/null || error "make command not found!" -command -v git > /dev/null || error "git command not found!" -command -v python3 > /dev/null || error "python3 command not found!" - -setup="setup.py" -default_branch="master" - -[[ "$(git rev-parse --show-toplevel)" == "$(pwd)" ]] || error "please go to the project root directory!" -[[ "$(git rev-parse --abbrev-ref HEAD)" == "$default_branch" ]] || error "please change to the $default_branch git branch!" - -pkg_version=$(python3 setup.py --version || error "could not determine package version in $setup!") -git_version=$(git describe --abbrev=0 --tags || error "could not determine git version!") - -# No version change -if [[ "$pkg_version" == "$git_version" ]]; then - echo "Latest git tag '$pkg_version' and package version '$git_version' match, edit your $setup to change the version before running this script!" - exit -fi - -git stash push --quiet -- "$setup" -trap 'e=$?; git stash pop --quiet; exit $e' EXIT - -[[ -z "$(git status --porcelain)" ]] || error "please commit or clean the changes before running this script!" - -git clean -xdf - -make lint test || error "tests failed, please correct the errors" - -new_tag="$pkg_version" -release="chore: release $new_tag" - -git stash pop --quiet -git add "$setup" || error "could not stage $setup!" -git commit -m "$release" --no-verify || error "could not commit the version bump!" -git tag "$new_tag" -a -m "$release" || error "could not tag the version bump!" - -echo "Run 'git push --follow-tags' in order to publish the release on Github!" From 52e5d1293c5479af115cc666cd023a092b58f87f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 21:52:54 +0100 Subject: [PATCH 144/145] chore(master): release 1.3.1 (#222) * chore(master): release 1.3.1 * chore: reorder anchor --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: jo --- .github/release-please-manifest.json | 2 +- CHANGELOG.md | 8 ++++++++ setup.py | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/release-please-manifest.json b/.github/release-please-manifest.json index c17c08f..3e8c001 100644 --- a/.github/release-please-manifest.json +++ b/.github/release-please-manifest.json @@ -1 +1 @@ -{".":"1.3.0"} +{".":"1.3.1"} diff --git a/CHANGELOG.md b/CHANGELOG.md index a48301f..282e54e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [1.3.1](https://github.com/lukecyca/pyzabbix/compare/1.3.0...v1.3.1) (2023-12-18) + + +### Documentation + +* add link to the zappix project ([#217](https://github.com/lukecyca/pyzabbix/issues/217)) ([962693d](https://github.com/lukecyca/pyzabbix/commit/962693d88a7cb3c53aeb0b7d62fc7198921abca8)) +* regenerate changelog ([8747c18](https://github.com/lukecyca/pyzabbix/commit/8747c185503a74282df2b66d07ccb8f58a114d86)) + ## [1.3.0](https://github.com/lukecyca/pyzabbix/compare/1.2.1...1.3.0) (2023-04-08) diff --git a/setup.py b/setup.py index a0bde9a..c562900 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name="pyzabbix", - version="1.3.0", + version="1.3.1", description="Zabbix API Python interface", long_description=long_description, long_description_content_type="text/markdown", From 59ecdf9074678b0bedcaaeaeb24edec1081f1841 Mon Sep 17 00:00:00 2001 From: Jonas L Date: Mon, 18 Dec 2023 21:59:51 +0100 Subject: [PATCH 145/145] chore: do not include v in tag (#223) --- .github/release-please-config.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/release-please-config.json b/.github/release-please-config.json index 7ea4a00..5ee1513 100644 --- a/.github/release-please-config.json +++ b/.github/release-please-config.json @@ -2,6 +2,7 @@ "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", "bootstrap-sha": "f962fef7dc6d573cf6dbc54dff5a22f7f38414c9", "include-component-in-tag": false, + "include-v-in-tag": false, "packages": { ".": { "release-type": "python",