From 5a7e1590e9913e232dac30d4d086a5998bca102e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Scharnitzky=20Don=C3=A1t?= Date: Wed, 1 Apr 2020 01:03:50 +0200 Subject: [PATCH 1/4] added minimal excpetion handling to communicator.py --- communicator.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/communicator.py b/communicator.py index 8328ad1..be1fc67 100644 --- a/communicator.py +++ b/communicator.py @@ -43,11 +43,14 @@ class Communicator: Get the list of available consumer from the current primary consumer. :return: """ - currentconsumer = self.currenctconsumer - response = requests.get(f'http://{currentconsumer}/consumer') - json = response.json() - LOGGER.debug(f"List of currently available consumers: {json}") - return json + try: + currentconsumer = self.currenctconsumer + response = requests.get(f'http://{currentconsumer}/consumer') + json = response.json() + LOGGER.debug(f"List of currently available consumers: {json}") + return json + except Exception: + return [] def isconsumeravailable(self) -> bool: """ -- 2.45.2 From 9040cf898b842da9fc1c4244bc7c8b33e7484bb6 Mon Sep 17 00:00:00 2001 From: schdwlk Date: Mon, 6 Apr 2020 20:48:36 +0200 Subject: [PATCH 2/4] Added/update docstring --- app.py | 7 +++++- communicator.py | 40 ++++++++++++++------------------ consumerlocator.py | 31 ++++++++++++++++--------- messagesender.py | 22 +++++++++++------- test.py | 58 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 115 insertions(+), 43 deletions(-) diff --git a/app.py b/app.py index a45e734..6def60d 100644 --- a/app.py +++ b/app.py @@ -11,7 +11,7 @@ from consumerlocator import ConsumerLocator from messagesender import MessageSender """ -Main entrypoint +Main entry point, This module builds the producer from the submodules. """ __author__ = "@tormakris" @@ -29,6 +29,11 @@ LOGGER = logging.getLogger(__name__) KNOWNCONSUMER = os.getenv("PRODUCER_KNOWNCONSUMER",'10.69.42.1') +""" +This is the producers entry point, initializes all the components (:class:'communicator.Communicator', +:class:'consumerlocator.ConsumerLocator' and :class:'messagesender.MessageSender') and sends infinite random +messages. +""" if __name__ == "__main__": LOGGER.info("Producer started") generateduuid = str(uuid.uuid4()) diff --git a/communicator.py b/communicator.py index 8ca528e..e009f73 100644 --- a/communicator.py +++ b/communicator.py @@ -20,19 +20,17 @@ class Communicator: """ def __init__(self, currentconsumer: str, uuid: str): - """ - Initialize object - :param consumerlocator: - :param uuid: + """Initialize object + :param consumerlocator: the current consumer's IP address as a string + :param uuid: string typed UUID. """ self.currenctconsumer=currentconsumer self.uuid = uuid def sendmessage(self, message: str) -> None: - """ - Send message to consumer. - :param message: - :return: none + """Send message to the current consumer. Logs the process. + :param message: the message of type string that will be sent. + :return: None """ currentconsumer=self.currenctconsumer LOGGER.info(f"Sending message to {currentconsumer}") @@ -40,9 +38,8 @@ class Communicator: LOGGER.debug(f"Message status code is:{postresponse.status_code}") def discoveravailableconsumers(self) -> list: - """ - Get the list of available consumer from the current primary consumer. - :return: + """Get the list of available consumer from the current primary consumer. Logs the received list. + :return: list of consumers' IP addresses """ try: currentconsumer = self.currenctconsumer @@ -55,9 +52,8 @@ class Communicator: return [] def isconsumeravailable(self) -> bool: - """ - Readiness probe primary consumer. - :return: + """Readiness probe current consumer. Logs the result. + :return: True if available, False otherwise """ currentconsumer = self.currenctconsumer try: @@ -70,10 +66,9 @@ class Communicator: return isavailable def checkconsumer(self, consumer: str) -> bool: - """ - Readiness probe of a prticular consumer. - :param consumer: - :return: + """Readiness probe of a particular consumer. Logs the result. + :param consumer: the consumer's IP address + :return: True if available, False otherwise """ try: response = requests.get(f'http://{consumer}/consumers') @@ -84,10 +79,9 @@ class Communicator: LOGGER.info(f"Consumer {consumer} availability: {isavailable}") return isavailable - def set_currentconsumer(self,currenctconsumer): - """ - Set current consumer - :param currenctconsumer: - :return: + def set_currentconsumer(self,currenctconsumer) -> None: + """Set current consumer + :param currenctconsumer: the consumer's IP address + :return: None """ self.currenctconsumer=currenctconsumer diff --git a/consumerlocator.py b/consumerlocator.py index 83988b4..b005c7f 100644 --- a/consumerlocator.py +++ b/consumerlocator.py @@ -18,20 +18,25 @@ KNOWNCONSUMER = os.getenv("PRODUCER_KNOWNCONSUMER",'10.69.42.1') class ConsumerLocator: """ - Manages the list of consumers. + Component responsible for managing the list of consumers. Requires an instance of :class:'communicator.Communicator' """ def __init__(self, uuid: str, communicator: Communicator): - """ - Initialize class. + """Initializes the object. + Gets the known consumer's IP address from the PRODUCER_KNOWNCONSUMER envar. + :param: uuid: Not used + :param: communicator: the :class:'communicator.Communicator' instance that will be used for the low level + communication. """ self.consumerlist = [{"Host": KNOWNCONSUMER, "State": True, "LastOk": datetime.datetime.now()}] self.currentconsumer = self.consumerlist[0] self.communicator = communicator def learnconsumerlist(self) -> None: - """" - Learns the list of consumers. + """"Learns the list of consumers from the current consumer. + Calls :func:'~communicator.Communicator.didiscoveravailableconsumers', adds the learned consumers to the list + if are not present, and then calls :func:'~consumerlocator.ConsumerLocator.updateconsumerlist' + :return: None """ recievedconsumerlist = self.communicator.discoveravailableconsumers() if recievedconsumerlist is None: @@ -48,8 +53,10 @@ class ConsumerLocator: self.updateconsumerlist() def updateconsumerlist(self) -> None: - """ - Updates the consumer list based on their availability. + """ Updates the consumer list based on their availability. + Marks for each consumer if they are available or not. If a consumer is not available for some time (1 hour), + the it will be deleted from the list. + :return: None """ removelist = [] for consumer in self.consumerlist: @@ -64,8 +71,10 @@ class ConsumerLocator: self.consumerlist.remove(rem) def updateconsumer(self): - """ - If the current consumer is not available, checks all the consumers in the list and updates the current one. + """If the current consumer is not available, checks all the consumers in the list and updates the current one. + Calls :func:'~consumerlocator.ConsumerLocator.checkcurrentconsumer' and if needed + :func:'~consumerlocator.ConsumerLocator.updateconsumerlist'. Sets the :class:'communicator.Communicator' + current instance with :func:'~communicator.Communicator.set_currentconsumer'. :return: the current consumer or None if there are no available customers at the moment. """ @@ -90,14 +99,14 @@ class ConsumerLocator: def getcurrentconsumer(self) -> str: """ - Returns the currently selected consumer. + Returns the currently selected consumer's IP address. :return: the current consumer """ return self.currentconsumer["Host"] def checkcurrentconsumer(self) -> bool: """ - Check the consumers health. + Check the current consumer's health. :return: True if OK, False if fail """ if self.currentconsumer is None: diff --git a/messagesender.py b/messagesender.py index 27992ae..881cde3 100644 --- a/messagesender.py +++ b/messagesender.py @@ -19,25 +19,31 @@ LOGGER = logging.getLogger(__name__) class MessageSender: """ - Üzenetek küldéséért felelős komponens. + Component responsible for sending the messages. Requires an instance of :class:'communicator.Communicator' """ def __init__(self, communicator: Communicator): - """ - Inicializálja az osztályt. + """Initializes the object. + :param: communicator: an instance of :class:'communicator.Communicator' """ self.communicator = communicator def randomstring(self, stringlength: int) -> str: - """Generate a random string of fixed length """ + """ + Generate a random string of fixed length + :param stringlength: the length of the string + :return: the generated string + """ letters = string.ascii_lowercase return ''.join(random.choice(letters) for i in range(stringlength)) def sendmessage(self, message: str = "") -> None: - """ - Uzenet letrehozasa - :param message: - :return: str tipus + """Sends the given message. + If the message is omitted (empty), then a random message will be generated with length 23 (with + :func:'~messagesender.MessageSender.randomstring'. Calls :func:'~communicator.Communicator.sendmessage' + to send the message. + :param message: the message of type string that will be sent + :return: None """ if not message: data = self.randomstring(32) diff --git a/test.py b/test.py index 8e053af..f20cd6d 100644 --- a/test.py +++ b/test.py @@ -18,6 +18,10 @@ generateduuid = 'c959ad81-58f9-4445-aab4-8f3d68aee1ad' def test_generate_string(mocker): + """ + Tests :func:'~messagesender.MessageSender.randomstring'. + :param: mocker: + """ mocker.patch('communicator.Communicator') comm = communicator.Communicator( currentconsumer="localhost", @@ -29,6 +33,11 @@ def test_generate_string(mocker): def test_sendmessage(httpserver): + """ + Tests :func:'~communicator.Communicator.sendmessage'. + :param httpserver: simple HTTP server + :return: None + """ httpserver.expect_oneshot_request( uri="/log", method='POST', @@ -46,6 +55,11 @@ def test_sendmessage(httpserver): def test_send_message(mocker): + """ + Tests :func:'~messagesender.MessageSender.sendmessage'. + :param mocker: + :return: None + """ mocker.patch('communicator.Communicator') comm = communicator.Communicator( currentconsumer="127.0.0.1", @@ -57,6 +71,11 @@ def test_send_message(mocker): def test_discoveravailableconsumers(httpserver): + """ + Tests :func:'~communicator.Communicator.discoveravailableconsumers' + :param httpserver: simple HTTP server + :return: None + """ httpserver.expect_oneshot_request( uri="/consumers", method='GET', @@ -73,6 +92,11 @@ def test_discoveravailableconsumers(httpserver): def test_isconsumeravailable(httpserver): + """ + Tests :func:'~communicator.Communicator.isconsumeravailable' + :param httpserver: simple HTTP server + :return: None + """ httpserver.expect_oneshot_request( uri="/consumers", method='GET', @@ -101,6 +125,11 @@ def test_isconsumeravailable(httpserver): def test_checkconsumer(httpserver): + """ + Tests :func:'~communicator.Communicator.checkconsumer' + :param httpserver: simple HTTP server + :return: None + """ httpserver.expect_oneshot_request( uri="/consumers", method='GET', @@ -129,6 +158,10 @@ def test_checkconsumer(httpserver): def test_setcurrentconsumer(): + """ + Tests :func:'~communicator.Communicator.set_currentconsumer' + :return: None + """ comm = communicator.Communicator( currentconsumer="127.0.0.1", uuid=generateduuid) @@ -137,6 +170,11 @@ def test_setcurrentconsumer(): def test_learnconsumerlist(httpserver): + """ + Tests :func:'~consumerlocator.ConsumerLocator.learnconsumerlist' + :param httpserver: simple HTTP server + :return: None + """ httpserver.expect_request( uri="/consumers", method='GET', @@ -155,6 +193,11 @@ def test_learnconsumerlist(httpserver): def test_getcurrentconsumer(mocker): + """ + Tests :func:'~consumerlocator.ConsumerLocator.getcurrentconsumer' + :param mocker: + :return: None + """ mocker.patch('communicator.Communicator') comm = communicator.Communicator( currentconsumer="127.0.0.1", @@ -165,6 +208,11 @@ def test_getcurrentconsumer(mocker): def test_checkcurrentconsumer(httpserver): + """ + Tests :func:'~consumerlocator.ConsumerLocator.checkcurrentconsumer' + :param httpserver: simple HTTP server + :return: None + """ httpserver.expect_oneshot_request( uri="/consumers", method='GET', @@ -183,6 +231,11 @@ def test_checkcurrentconsumer(httpserver): def test_updateconsumer(httpserver): + """ + Tests :func:'~consumerlocator.ConsumerLocator.updateconsumer' + :param httpserver: simple HTTP server + :return: None + """ httpserver.expect_oneshot_request( uri="/consumers", method='GET', @@ -202,6 +255,11 @@ def test_updateconsumer(httpserver): def test_updateconsumerlist(httpserver): + """ + Tests :func:'~consumerlocator.ConsumerLocator.updateconsumerlist' + :param httpserver: simple HTTP server + :return: None + """ httpserver.expect_oneshot_request( uri="/consumers", method='GET', -- 2.45.2 From e2a6c9602537be9f6b37d512776f5d60b0643326 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Scharnitzky=20Don=C3=A1t?= Date: Tue, 7 Apr 2020 17:56:38 +0200 Subject: [PATCH 3/4] adde sphinx commands --- .drone.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.drone.yml b/.drone.yml index a3e6b8c..9838b6c 100644 --- a/.drone.yml +++ b/.drone.yml @@ -35,6 +35,15 @@ steps: - docker build -t="$DOCKER_USERNAME/producer:$DRONE_BUILD_NUMBER" . - docker push "$DOCKER_USERNAME/producer" - docker push "$DOCKER_USERNAME/producer:$DRONE_BUILD_NUMBER" + +- name: build_docs + image: python:3 + commands: + - pip3 install Sphinx + - pip3 install -r requirements.txt + - cd docs + - sphinx-apidoc -o source/ ../ + - make html - name: slack image: plugins/slack -- 2.45.2 From 9e45b4751e1669d3aa51174e19c54ab33f0938d5 Mon Sep 17 00:00:00 2001 From: schdwlk Date: Tue, 7 Apr 2020 18:01:06 +0200 Subject: [PATCH 4/4] Added sphinx files --- docs/Makefile | 20 +++++++++++++++++++ docs/conf.py | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++ docs/index.rst | 20 +++++++++++++++++++ docs/make.bat | 35 +++++++++++++++++++++++++++++++++ 4 files changed, 127 insertions(+) create mode 100644 docs/Makefile create mode 100644 docs/conf.py create mode 100644 docs/index.rst create mode 100644 docs/make.bat diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..d4bb2cb --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..505863b --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,52 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import sys +sys.path.insert(0, os.path.abspath('../')) + + +# -- Project information ----------------------------------------------------- + +project = 'Producer' +copyright = '2020, Torma Kristóf, Scharnitzky Donát, Kovács Bence' +author = 'Torma Kristóf, Scharnitzky Donát, Kovács Bence' + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = ['sphinx.ext.autodoc' +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'alabaster' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..b86c023 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,20 @@ +.. Producer documentation master file, created by + sphinx-quickstart on Tue Apr 7 17:01:40 2020. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to Producer's documentation! +==================================== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..2119f51 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd -- 2.45.2