├── .flake8 ├── .github └── workflows │ └── main.yaml ├── .gitignore ├── LICENSE ├── README.md ├── dave.jpg ├── docker-compose.yaml ├── fastlane-finale-service ├── Dockerfile ├── README.md ├── app │ ├── __init__.py │ ├── main.py │ ├── message_helper.py │ ├── message_parser.py │ └── mqtt_topic_helper.py ├── build-docker-image.sh ├── poetry.lock ├── pyproject.toml └── tests │ └── __init__.py ├── gateexit-guardian-service ├── Dockerfile ├── README.md ├── app │ ├── __init__.py │ ├── main.py │ ├── message_helper.py │ └── mqtt_topic_helper.py ├── build-docker-image.sh ├── poetry.lock ├── pyproject.toml └── tests │ └── __init__.py ├── gateway-guardian-service ├── Dockerfile ├── README.md ├── app │ ├── __init__.py │ ├── main.py │ ├── message_helper.py │ └── mqtt_topic_helper.py ├── build-docker-image.sh ├── poetry.lock ├── pyproject.toml └── tests │ └── __init__.py ├── hello-helper-service ├── Dockerfile ├── README.md ├── app │ ├── __init__.py │ ├── customer_name_db.py │ ├── main.py │ ├── message_parser.py │ └── mqtt_topic_helper.py ├── build-docker-image.sh ├── poetry.lock ├── pyproject.toml └── tests │ └── __init__.py ├── inventory-intel-service ├── Dockerfile ├── README.md ├── app │ ├── __init__.py │ ├── inventory.py │ ├── main.py │ ├── message_parser.py │ └── mqtt_topic_helper.py ├── build-docker-image.sh ├── poetry.lock ├── pyproject.toml └── tests │ └── __init__.py └── message-mediator-broker └── mosquitto.conf /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 90 3 | count = true -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | name: Python CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | build-and-deploy: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Check out code 17 | uses: actions/checkout@v3 18 | 19 | - name: Set up Python 20 | uses: actions/setup-python@v3 21 | with: 22 | python-version: 3.11.3 23 | 24 | - name: Build Docker image hello-helper-service 25 | working-directory: ./hello-helper-service 26 | run: | 27 | docker build . -t hello-helper-service 28 | 29 | - name: Build Docker image fastlane-finale-service 30 | working-directory: ./fastlane-finale-service 31 | run: | 32 | docker build . -t fastlane-finale-service 33 | 34 | - name: Build Docker image gateexit-guardian-service 35 | working-directory: ./gateexit-guardian-service 36 | run: | 37 | docker build . -t gateexit-guardian-service 38 | 39 | - name: Build Docker image gateway-guardian-service 40 | working-directory: ./gateway-guardian-service 41 | run: | 42 | docker build . -t gateway-guardian-service 43 | 44 | - name: Build Docker image inventory-intel-service 45 | working-directory: ./inventory-intel-service 46 | run: | 47 | docker build . -t inventory-intel-service 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | **/.DS_Store 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | share/python-wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .nox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | *.py,cover 52 | .hypothesis/ 53 | .pytest_cache/ 54 | cover/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | db.sqlite3-journal 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | .pybuilder/ 78 | target/ 79 | 80 | # Jupyter Notebook 81 | .ipynb_checkpoints 82 | 83 | # IPython 84 | profile_default/ 85 | ipython_config.py 86 | 87 | # pyenv 88 | # For a library or package, you might want to ignore these files since the code is 89 | # intended to run in multiple environments; otherwise, check them in: 90 | # .python-version 91 | 92 | # pipenv 93 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 94 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 95 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 96 | # install all needed dependencies. 97 | #Pipfile.lock 98 | 99 | # poetry 100 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 101 | # This is especially recommended for binary packages to ensure reproducibility, and is more 102 | # commonly ignored for libraries. 103 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 104 | #poetry.lock 105 | 106 | # pdm 107 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 108 | #pdm.lock 109 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 110 | # in version control. 111 | # https://pdm.fming.dev/#use-with-ide 112 | .pdm.toml 113 | 114 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 115 | __pypackages__/ 116 | 117 | # Celery stuff 118 | celerybeat-schedule 119 | celerybeat.pid 120 | 121 | # SageMath parsed files 122 | *.sage.py 123 | 124 | # Environments 125 | .env 126 | .venv 127 | env/ 128 | venv/ 129 | ENV/ 130 | env.bak/ 131 | venv.bak/ 132 | 133 | # Spyder project settings 134 | .spyderproject 135 | .spyproject 136 | 137 | # Rope project settings 138 | .ropeproject 139 | 140 | # mkdocs documentation 141 | /site 142 | 143 | # mypy 144 | .mypy_cache/ 145 | .dmypy.json 146 | dmypy.json 147 | 148 | # Pyre type checker 149 | .pyre/ 150 | 151 | # pytype static type analyzer 152 | .pytype/ 153 | 154 | # Cython debug symbols 155 | cython_debug/ 156 | 157 | # PyCharm 158 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 159 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 160 | # and can be added to the global gitignore or merged into this file. For a more nuclear 161 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 162 | #.idea/ 163 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Patrick Kalkman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # python-eda 2 | A repository that contains all the examples that go with [a Medium article called How To Create an Event Driven Architecture (EDA) in Python -- Build scalable and flexible systems using Python](https://medium.com/itnext/how-to-create-an-event-driven-architecture-eda-in-python-1c47666bc088) 3 | 4 | ![Dave](/dave.jpg "Dave searching for pasta sauce") -------------------------------------------------------------------------------- /dave.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatrickKalkman/python-eda/e98886cbb669d445b17d7a39ce05884c4fe734be/dave.jpg -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | services: 3 | message-mediator-broker: 4 | container_name: message-mediator-broker-container 5 | image: eclipse-mosquitto:2.0.15 6 | volumes: 7 | - ./message-mediator-broker/mosquitto.conf:/mosquitto/config/mosquitto.conf 8 | ports: 9 | - 1883:1883 10 | - 9001:9001 11 | networks: 12 | - fresh-frontier-network 13 | gateway-guardian-service: 14 | container_name: gateway-guardian-service-container 15 | image: pkalkman/gateway-guardian-service:0.0.9 16 | depends_on: 17 | - message-mediator-broker 18 | environment: 19 | - BROKER_ADDRESS=message-mediator-broker 20 | networks: 21 | - fresh-frontier-network 22 | hello-helper-service: 23 | container_name: hello-helper-service-container 24 | image: pkalkman/hello-helper-service:0.0.1 25 | depends_on: 26 | - message-mediator-broker 27 | environment: 28 | - BROKER_ADDRESS=message-mediator-broker 29 | networks: 30 | - fresh-frontier-network 31 | gateexit-guardian-service: 32 | container_name: gateexit-guardian-service-container 33 | image: pkalkman/gateexit-guardian-service:0.0.2 34 | depends_on: 35 | - message-mediator-broker 36 | environment: 37 | - BROKER_ADDRESS=message-mediator-broker 38 | networks: 39 | - fresh-frontier-network 40 | inventory-intel-service: 41 | container_name: inventory-intel-service-container 42 | image: pkalkman/inventory-intel-service:0.0.5 43 | depends_on: 44 | - message-mediator-broker 45 | environment: 46 | - BROKER_ADDRESS=message-mediator-broker 47 | networks: 48 | - fresh-frontier-network 49 | fastlane-finale-service: 50 | container_name: fastlane-finale-service-container 51 | image: pkalkman/fastlane-finale-service:0.0.10 52 | depends_on: 53 | - message-mediator-broker 54 | environment: 55 | - BROKER_ADDRESS=message-mediator-broker 56 | networks: 57 | - fresh-frontier-network 58 | 59 | networks: 60 | fresh-frontier-network: 61 | -------------------------------------------------------------------------------- /fastlane-finale-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11-alpine as requirements-stage 2 | 3 | ENV VERSION 1.0.0 4 | 5 | WORKDIR /tmp 6 | 7 | RUN apk add --no-cache gcc musl-dev libffi-dev openssl-dev && \ 8 | pip install poetry && \ 9 | poetry config virtualenvs.create false 10 | 11 | COPY ./pyproject.toml ./poetry.lock* /tmp/ 12 | 13 | RUN poetry export -f requirements.txt --output requirements.txt --without-hashes 14 | 15 | FROM python:3.11-alpine 16 | 17 | # Create a non-root user 18 | RUN adduser --disabled-password --gecos '' appuser 19 | 20 | WORKDIR / 21 | 22 | COPY --from=requirements-stage /tmp/requirements.txt /app/requirements.txt 23 | 24 | RUN apk add --no-cache libffi openssl && \ 25 | pip install --no-cache-dir --upgrade -r /app/requirements.txt && \ 26 | rm -rf /root/.cache && \ 27 | rm -rf /var/cache/apk/* 28 | 29 | COPY ./app /app/ 30 | 31 | # Change ownership of the app directory to the non-root user 32 | RUN chown -R appuser:appuser /app 33 | 34 | USER appuser 35 | 36 | WORKDIR /app 37 | 38 | # Expose the port the app runs in 39 | EXPOSE 1883 40 | 41 | # During debugging, this entry point will be overridden. 42 | CMD ["python", "main.py"] -------------------------------------------------------------------------------- /fastlane-finale-service/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatrickKalkman/python-eda/e98886cbb669d445b17d7a39ce05884c4fe734be/fastlane-finale-service/README.md -------------------------------------------------------------------------------- /fastlane-finale-service/app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatrickKalkman/python-eda/e98886cbb669d445b17d7a39ce05884c4fe734be/fastlane-finale-service/app/__init__.py -------------------------------------------------------------------------------- /fastlane-finale-service/app/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import random 3 | import signal 4 | import sys 5 | import time 6 | 7 | from loguru import logger 8 | from message_helper import MessageHelper 9 | from message_parser import MessageParser 10 | from mqtt_topic_helper import MqttTopicHelper 11 | from paho.mqtt import client as mqtt_client 12 | 13 | 14 | class ProductPricing: 15 | def __init__(self): 16 | self.prices = {i: round(random.uniform(1, 20), 2) for i in range(1, 101)} 17 | 18 | def get_price(self, product_id): 19 | return self.prices.get(product_id, 0) 20 | 21 | 22 | broker = os.getenv('BROKER_ADDRESS', 'localhost') 23 | port = 1883 24 | client_id = "fastlane_finale_service" 25 | topic_helper = MqttTopicHelper("spectrum-grocers", "fresh-frontier") 26 | message_helper = MessageHelper() 27 | message_parser = MessageParser() 28 | product_pricing = ProductPricing() 29 | 30 | 31 | def connect_mqtt(): 32 | def on_connect(client, userdata, flags, rc): 33 | if rc == 0: 34 | logger.info("Connected to MQTT Broker!") 35 | else: 36 | logger.error("Failed to connect, return code %d\n", rc) 37 | 38 | client = mqtt_client.Client(client_id) 39 | client.on_connect = on_connect 40 | client.connect(broker, port) 41 | return client 42 | 43 | 44 | def subscribe(client): 45 | def on_message(client, userdata, msg): 46 | customer_departure = message_parser.customer_departure(msg.payload) 47 | total_price = 0 48 | for product_id in customer_departure['product_ids']: 49 | total_price += product_pricing.get_price(product_id) 50 | payment_message = message_helper.payment_due(customer_departure['customer_id'], 51 | total_price) 52 | client.publish(topic_helper.payment_due(), payment_message) 53 | 54 | logger.info(f"Payment due for customer {customer_departure['customer_id']}:" + 55 | f" ${total_price:.2f}") 56 | 57 | client.subscribe(topic_helper.customer_departure()) 58 | client.on_message = on_message 59 | 60 | 61 | def handle_signal(signum, frame): 62 | logger.info("Gracefully shutting down...") 63 | client.disconnect() 64 | sys.exit(0) 65 | 66 | 67 | def run(): 68 | client = connect_mqtt() 69 | subscribe(client) 70 | client.loop_start() 71 | while True: 72 | time.sleep(1) # just to prevent the script from ending 73 | 74 | 75 | signal.signal(signal.SIGINT, handle_signal) 76 | signal.signal(signal.SIGTERM, handle_signal) 77 | 78 | 79 | if __name__ == "__main__": 80 | run() 81 | -------------------------------------------------------------------------------- /fastlane-finale-service/app/message_helper.py: -------------------------------------------------------------------------------- 1 | import json 2 | from datetime import datetime 3 | 4 | 5 | class MessageHelper: 6 | def _current_datetime(self): 7 | return datetime.now().isoformat() 8 | 9 | def customer_arrival(self, customer_id): 10 | return json.dumps({"timestamp": self._current_datetime(), 11 | "customer_id": customer_id}) 12 | 13 | def customer_departure(self, customer_id, product_ids): 14 | return json.dumps({"timestamp": self._current_datetime(), 15 | "customer_id": customer_id, 16 | "product_ids": product_ids}) 17 | 18 | def purchase_complete(self, customer_id, items): 19 | return json.dumps({"timestamp": self._current_datetime(), 20 | "customer_id": customer_id, 21 | "items": items}) 22 | 23 | def stock_update(self, item_id, quantity): 24 | return json.dumps({"timestamp": self._current_datetime(), 25 | "item_id": item_id, "quantity": quantity}) 26 | 27 | def restock_alert(self, item_id, quantity): 28 | return json.dumps({"timestamp": self._current_datetime(), 29 | "item_id": item_id, "quantity": quantity}) 30 | 31 | def payment_due(self, customer_id, amount): 32 | return json.dumps({"timestamp": self._current_datetime(), 33 | "customer_id": customer_id, "amount": amount}) 34 | -------------------------------------------------------------------------------- /fastlane-finale-service/app/message_parser.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | 4 | class MessageParser: 5 | def customer_arrival(self, message): 6 | data = json.loads(message) 7 | return {'timestamp': data['timestamp'], 'customer_id': data['customer_id']} 8 | 9 | def customer_departure(self, message): 10 | data = json.loads(message) 11 | return {'timestamp': data['timestamp'], 'customer_id': data['customer_id'], 12 | 'product_ids': data['product_ids']} 13 | -------------------------------------------------------------------------------- /fastlane-finale-service/app/mqtt_topic_helper.py: -------------------------------------------------------------------------------- 1 | class MqttTopicHelper: 2 | def __init__(self, chain_name, store_name): 3 | self.chain_name = chain_name 4 | self.store_name = store_name 5 | 6 | def customer_arrival(self): 7 | return f"{self.chain_name}/{self.store_name}/customer-arrival" 8 | 9 | def customer_departure(self): 10 | return f"{self.chain_name}/{self.store_name}/customer-departure" 11 | 12 | def display_welcome(self): 13 | return f"{self.chain_name}/{self.store_name}/display-welcome" 14 | 15 | def purchase_complete(self): 16 | return f"{self.chain_name}/{self.store_name}/purchase-complete" 17 | 18 | def stock_update(self): 19 | return f"{self.chain_name}/{self.store_name}/stock-update" 20 | 21 | def restock_alert(self): 22 | return f"{self.chain_name}/{self.store_name}/restock-alert" 23 | 24 | def payment_due(self): 25 | return f"{self.chain_name}/{self.store_name}/payment-due" 26 | -------------------------------------------------------------------------------- /fastlane-finale-service/build-docker-image.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | VERSION="0.0.10" 3 | APP="fastlane-finale-service" 4 | ACCOUNT="pkalkman" 5 | docker buildx build --platform linux/amd64,linux/arm64 -f ./Dockerfile -t $ACCOUNT/$APP:$VERSION --push . 6 | -------------------------------------------------------------------------------- /fastlane-finale-service/poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "colorama" 5 | version = "0.4.6" 6 | description = "Cross-platform colored terminal text." 7 | category = "main" 8 | optional = false 9 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 10 | files = [ 11 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 12 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 13 | ] 14 | 15 | [[package]] 16 | name = "loguru" 17 | version = "0.7.0" 18 | description = "Python logging made (stupidly) simple" 19 | category = "main" 20 | optional = false 21 | python-versions = ">=3.5" 22 | files = [ 23 | {file = "loguru-0.7.0-py3-none-any.whl", hash = "sha256:b93aa30099fa6860d4727f1b81f8718e965bb96253fa190fab2077aaad6d15d3"}, 24 | {file = "loguru-0.7.0.tar.gz", hash = "sha256:1612053ced6ae84d7959dd7d5e431a0532642237ec21f7fd83ac73fe539e03e1"}, 25 | ] 26 | 27 | [package.dependencies] 28 | colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} 29 | win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} 30 | 31 | [package.extras] 32 | dev = ["Sphinx (==5.3.0)", "colorama (==0.4.5)", "colorama (==0.4.6)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v0.990)", "pre-commit (==3.2.1)", "pytest (==6.1.2)", "pytest (==7.2.1)", "pytest-cov (==2.12.1)", "pytest-cov (==4.0.0)", "pytest-mypy-plugins (==1.10.1)", "pytest-mypy-plugins (==1.9.3)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.2.0)", "tox (==3.27.1)", "tox (==4.4.6)"] 33 | 34 | [[package]] 35 | name = "paho-mqtt" 36 | version = "1.6.1" 37 | description = "MQTT version 5.0/3.1.1 client class" 38 | category = "main" 39 | optional = false 40 | python-versions = "*" 41 | files = [ 42 | {file = "paho-mqtt-1.6.1.tar.gz", hash = "sha256:2a8291c81623aec00372b5a85558a372c747cbca8e9934dfe218638b8eefc26f"}, 43 | ] 44 | 45 | [package.extras] 46 | proxy = ["PySocks"] 47 | 48 | [[package]] 49 | name = "win32-setctime" 50 | version = "1.1.0" 51 | description = "A small Python utility to set file creation time on Windows" 52 | category = "main" 53 | optional = false 54 | python-versions = ">=3.5" 55 | files = [ 56 | {file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"}, 57 | {file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"}, 58 | ] 59 | 60 | [package.extras] 61 | dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] 62 | 63 | [metadata] 64 | lock-version = "2.0" 65 | python-versions = "^3.11" 66 | content-hash = "6b533f8fdc8f52a1957b9f7f3a72e332e9592cbd4bbaf93dbe6fcaa19146a60e" 67 | -------------------------------------------------------------------------------- /fastlane-finale-service/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "fastlane-finale-service" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Patrick Kalkman "] 6 | readme = "README.md" 7 | packages = [{include = "fastlane_finale_service"}] 8 | 9 | [tool.poetry.dependencies] 10 | python = "^3.11" 11 | loguru = "^0.7.0" 12 | paho-mqtt = "^1.6.1" 13 | 14 | 15 | [build-system] 16 | requires = ["poetry-core"] 17 | build-backend = "poetry.core.masonry.api" 18 | -------------------------------------------------------------------------------- /fastlane-finale-service/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatrickKalkman/python-eda/e98886cbb669d445b17d7a39ce05884c4fe734be/fastlane-finale-service/tests/__init__.py -------------------------------------------------------------------------------- /gateexit-guardian-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11-alpine as requirements-stage 2 | 3 | ENV VERSION 1.0.0 4 | 5 | WORKDIR /tmp 6 | 7 | RUN apk add --no-cache gcc musl-dev libffi-dev openssl-dev && \ 8 | pip install poetry && \ 9 | poetry config virtualenvs.create false 10 | 11 | COPY ./pyproject.toml ./poetry.lock* /tmp/ 12 | 13 | RUN poetry export -f requirements.txt --output requirements.txt --without-hashes 14 | 15 | FROM python:3.11-alpine 16 | 17 | # Create a non-root user 18 | RUN adduser --disabled-password --gecos '' appuser 19 | 20 | WORKDIR / 21 | 22 | COPY --from=requirements-stage /tmp/requirements.txt /app/requirements.txt 23 | 24 | RUN apk add --no-cache libffi openssl && \ 25 | pip install --no-cache-dir --upgrade -r /app/requirements.txt && \ 26 | rm -rf /root/.cache && \ 27 | rm -rf /var/cache/apk/* 28 | 29 | COPY ./app /app/ 30 | 31 | # Change ownership of the app directory to the non-root user 32 | RUN chown -R appuser:appuser /app 33 | 34 | USER appuser 35 | 36 | WORKDIR /app 37 | 38 | # Expose the port the app runs in 39 | EXPOSE 1883 40 | 41 | # During debugging, this entry point will be overridden. 42 | CMD ["python", "main.py"] -------------------------------------------------------------------------------- /gateexit-guardian-service/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatrickKalkman/python-eda/e98886cbb669d445b17d7a39ce05884c4fe734be/gateexit-guardian-service/README.md -------------------------------------------------------------------------------- /gateexit-guardian-service/app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatrickKalkman/python-eda/e98886cbb669d445b17d7a39ce05884c4fe734be/gateexit-guardian-service/app/__init__.py -------------------------------------------------------------------------------- /gateexit-guardian-service/app/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import random 3 | import signal 4 | import sys 5 | import time 6 | 7 | from loguru import logger 8 | from message_helper import MessageHelper 9 | from mqtt_topic_helper import MqttTopicHelper 10 | from paho.mqtt import client as mqtt_client 11 | 12 | broker = os.getenv('BROKER_ADDRESS', 'localhost') 13 | port = 1883 14 | client_id = "gateexit-guardian-service" 15 | 16 | topic_helper = MqttTopicHelper('spectrum-grocers', 'fresh-frontier') 17 | exit_topic = topic_helper.customer_departure() 18 | 19 | message_helper = MessageHelper() 20 | 21 | 22 | def connect_mqtt(): 23 | def on_connect(client, userdata, flags, rc): 24 | if rc == 0: 25 | logger.info(f"Connected with result code: {rc}") 26 | else: 27 | logger.info(f"Failed to connect, return code: {rc}") 28 | 29 | client = mqtt_client.Client(client_id) 30 | client.on_connect = on_connect 31 | client.connect(broker, port) 32 | return client 33 | 34 | 35 | def publish(client): 36 | while True: 37 | time.sleep(random.randint(2, 20)) 38 | customer_id = random.randint(1, 10) 39 | product_ids = [random.randint(1, 100) for _ in range(random.randint(1, 10))] 40 | message = message_helper.customer_departure(customer_id, product_ids) 41 | result = client.publish(exit_topic, message) 42 | status = result[0] 43 | if status == 0: 44 | logger.info(f"Published message to topic {exit_topic}") 45 | else: 46 | logger.info(f"Failed to publish message to topic {exit_topic}") 47 | 48 | 49 | def handle_exit(signum, frame): 50 | client.disconnect() 51 | logger.info("Gracefully shutting down...") 52 | sys.exit(0) 53 | 54 | 55 | signal.signal(signal.SIGINT, handle_exit) 56 | signal.signal(signal.SIGTERM, handle_exit) 57 | 58 | 59 | if __name__ == '__main__': 60 | client = connect_mqtt() 61 | client.loop_start() 62 | publish(client) 63 | -------------------------------------------------------------------------------- /gateexit-guardian-service/app/message_helper.py: -------------------------------------------------------------------------------- 1 | import json 2 | from datetime import datetime 3 | 4 | 5 | class MessageHelper: 6 | def _current_datetime(self): 7 | return datetime.now().isoformat() 8 | 9 | def customer_arrival(self, customer_id): 10 | return json.dumps({"timestamp": self._current_datetime(), 11 | "customer_id": customer_id}) 12 | 13 | def customer_departure(self, customer_id, product_ids): 14 | return json.dumps({ 15 | "timestamp": self._current_datetime(), 16 | "customer_id": customer_id, 17 | "product_ids": product_ids 18 | }) 19 | 20 | def purchase_complete(self, customer_id, items): 21 | return json.dumps({"timestamp": self._current_datetime(), 22 | "customer_id": customer_id, "items": items}) 23 | 24 | def stock_update(self, item_id, quantity): 25 | return json.dumps({"timestamp": self._current_datetime(), 26 | "item_id": item_id, "quantity": quantity}) 27 | 28 | def restock_alert(self, item_id, quantity): 29 | return json.dumps({"timestamp": self._current_datetime(), 30 | "item_id": item_id, "quantity": quantity}) 31 | -------------------------------------------------------------------------------- /gateexit-guardian-service/app/mqtt_topic_helper.py: -------------------------------------------------------------------------------- 1 | class MqttTopicHelper: 2 | def __init__(self, chain_name, store_name): 3 | self.chain_name = chain_name 4 | self.store_name = store_name 5 | 6 | def customer_departure(self): 7 | return f"{self.chain_name}/{self.store_name}/customer-departure" 8 | 9 | def display_welcome(self): 10 | return f"{self.chain_name}/{self.store_name}/display-welcome" 11 | 12 | def purchase_complete(self): 13 | return f"{self.chain_name}/{self.store_name}/purchase-complete" 14 | 15 | def stock_update(self): 16 | return f"{self.chain_name}/{self.store_name}/stock-update" 17 | 18 | def restock_alert(self): 19 | return f"{self.chain_name}/{self.store_name}/restock-alert" 20 | -------------------------------------------------------------------------------- /gateexit-guardian-service/build-docker-image.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | VERSION="0.0.2" 3 | APP="gateexit-guardian-service" 4 | ACCOUNT="pkalkman" 5 | docker buildx build --platform linux/amd64,linux/arm64 -f ./Dockerfile -t $ACCOUNT/$APP:$VERSION --push . 6 | -------------------------------------------------------------------------------- /gateexit-guardian-service/poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "colorama" 5 | version = "0.4.6" 6 | description = "Cross-platform colored terminal text." 7 | category = "main" 8 | optional = false 9 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 10 | files = [ 11 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 12 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 13 | ] 14 | 15 | [[package]] 16 | name = "loguru" 17 | version = "0.7.0" 18 | description = "Python logging made (stupidly) simple" 19 | category = "main" 20 | optional = false 21 | python-versions = ">=3.5" 22 | files = [ 23 | {file = "loguru-0.7.0-py3-none-any.whl", hash = "sha256:b93aa30099fa6860d4727f1b81f8718e965bb96253fa190fab2077aaad6d15d3"}, 24 | {file = "loguru-0.7.0.tar.gz", hash = "sha256:1612053ced6ae84d7959dd7d5e431a0532642237ec21f7fd83ac73fe539e03e1"}, 25 | ] 26 | 27 | [package.dependencies] 28 | colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} 29 | win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} 30 | 31 | [package.extras] 32 | dev = ["Sphinx (==5.3.0)", "colorama (==0.4.5)", "colorama (==0.4.6)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v0.990)", "pre-commit (==3.2.1)", "pytest (==6.1.2)", "pytest (==7.2.1)", "pytest-cov (==2.12.1)", "pytest-cov (==4.0.0)", "pytest-mypy-plugins (==1.10.1)", "pytest-mypy-plugins (==1.9.3)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.2.0)", "tox (==3.27.1)", "tox (==4.4.6)"] 33 | 34 | [[package]] 35 | name = "paho-mqtt" 36 | version = "1.6.1" 37 | description = "MQTT version 5.0/3.1.1 client class" 38 | category = "main" 39 | optional = false 40 | python-versions = "*" 41 | files = [ 42 | {file = "paho-mqtt-1.6.1.tar.gz", hash = "sha256:2a8291c81623aec00372b5a85558a372c747cbca8e9934dfe218638b8eefc26f"}, 43 | ] 44 | 45 | [package.extras] 46 | proxy = ["PySocks"] 47 | 48 | [[package]] 49 | name = "win32-setctime" 50 | version = "1.1.0" 51 | description = "A small Python utility to set file creation time on Windows" 52 | category = "main" 53 | optional = false 54 | python-versions = ">=3.5" 55 | files = [ 56 | {file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"}, 57 | {file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"}, 58 | ] 59 | 60 | [package.extras] 61 | dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] 62 | 63 | [metadata] 64 | lock-version = "2.0" 65 | python-versions = "^3.11" 66 | content-hash = "6b533f8fdc8f52a1957b9f7f3a72e332e9592cbd4bbaf93dbe6fcaa19146a60e" 67 | -------------------------------------------------------------------------------- /gateexit-guardian-service/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "gateexit-guardian-service" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Patrick Kalkman "] 6 | readme = "README.md" 7 | packages = [{include = "gateexit_guardian_service"}] 8 | 9 | [tool.poetry.dependencies] 10 | python = "^3.11" 11 | paho-mqtt = "^1.6.1" 12 | loguru = "^0.7.0" 13 | 14 | 15 | [build-system] 16 | requires = ["poetry-core"] 17 | build-backend = "poetry.core.masonry.api" 18 | -------------------------------------------------------------------------------- /gateexit-guardian-service/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatrickKalkman/python-eda/e98886cbb669d445b17d7a39ce05884c4fe734be/gateexit-guardian-service/tests/__init__.py -------------------------------------------------------------------------------- /gateway-guardian-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11-alpine as requirements-stage 2 | 3 | ENV VERSION 1.0.0 4 | 5 | WORKDIR /tmp 6 | 7 | RUN apk add --no-cache gcc musl-dev libffi-dev openssl-dev && \ 8 | pip install poetry && \ 9 | poetry config virtualenvs.create false 10 | 11 | COPY ./pyproject.toml ./poetry.lock* /tmp/ 12 | 13 | RUN poetry export -f requirements.txt --output requirements.txt --without-hashes 14 | 15 | FROM python:3.11-alpine 16 | 17 | # Create a non-root user 18 | RUN adduser --disabled-password --gecos '' appuser 19 | 20 | WORKDIR / 21 | 22 | COPY --from=requirements-stage /tmp/requirements.txt /app/requirements.txt 23 | 24 | RUN apk add --no-cache libffi openssl && \ 25 | pip install --no-cache-dir --upgrade -r /app/requirements.txt && \ 26 | rm -rf /root/.cache && \ 27 | rm -rf /var/cache/apk/* 28 | 29 | COPY ./app /app/ 30 | 31 | # Change ownership of the app directory to the non-root user 32 | RUN chown -R appuser:appuser /app 33 | 34 | USER appuser 35 | 36 | WORKDIR /app 37 | 38 | # Expose the port the app runs in 39 | EXPOSE 1883 40 | 41 | # During debugging, this entry point will be overridden. 42 | CMD ["python", "main.py"] -------------------------------------------------------------------------------- /gateway-guardian-service/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatrickKalkman/python-eda/e98886cbb669d445b17d7a39ce05884c4fe734be/gateway-guardian-service/README.md -------------------------------------------------------------------------------- /gateway-guardian-service/app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatrickKalkman/python-eda/e98886cbb669d445b17d7a39ce05884c4fe734be/gateway-guardian-service/app/__init__.py -------------------------------------------------------------------------------- /gateway-guardian-service/app/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import random 3 | import signal 4 | from threading import Event 5 | 6 | from loguru import logger 7 | from message_helper import MessageHelper 8 | from mqtt_topic_helper import MqttTopicHelper 9 | from paho.mqtt import client as mqtt_client 10 | 11 | broker = os.getenv('BROKER_ADDRESS', 'localhost') 12 | port = 1883 13 | client_id = "gateway-guardian-service" 14 | running = Event() # Event object to replace the running flag 15 | 16 | 17 | def connect_mqtt(): 18 | def on_connect(client, userdata, flags, rc): 19 | if rc == 0: 20 | print("Connected to MQTT Broker!") 21 | else: 22 | print("Failed to connect, return code %d\n", rc) 23 | 24 | client = mqtt_client.Client(client_id) 25 | client.on_connect = on_connect 26 | client.connect(broker, port) 27 | return client 28 | 29 | 30 | def publish(client): 31 | topic_helper = MqttTopicHelper("spectrum-grocers", "fresh-frontier") 32 | message_helper = MessageHelper() 33 | 34 | while not running.is_set(): 35 | customer_id = random.randint(1, 10) 36 | topic = topic_helper.customer_arrival() 37 | message = message_helper.customer_arrival(customer_id) 38 | 39 | logger.info(f"Pub to {topic}: {message}") 40 | client.publish(topic, message) 41 | 42 | running.wait(random.randint(2, 20)) 43 | 44 | client.disconnect() 45 | 46 | 47 | # Handle Signals for Graceful Shutdown 48 | def handle_signal(signum, frame): 49 | running.set() 50 | print("Gracefully shutting down...") 51 | 52 | 53 | signal.signal(signal.SIGINT, handle_signal) 54 | signal.signal(signal.SIGTERM, handle_signal) 55 | 56 | if __name__ == "__main__": 57 | client = connect_mqtt() 58 | client.loop_start() 59 | publish(client) 60 | -------------------------------------------------------------------------------- /gateway-guardian-service/app/message_helper.py: -------------------------------------------------------------------------------- 1 | import json 2 | from datetime import datetime 3 | 4 | 5 | class MessageHelper: 6 | def _current_datetime(self): 7 | return datetime.now().isoformat() 8 | 9 | def customer_arrival(self, customer_id): 10 | return json.dumps({"timestamp": self._current_datetime(), 11 | "customer_id": customer_id}) 12 | 13 | def purchase_complete(self, customer_id, items): 14 | return json.dumps({"timestamp": self._current_datetime(), 15 | "customer_id": customer_id, "items": items}) 16 | 17 | def stock_update(self, item_id, quantity): 18 | return json.dumps({"timestamp": self._current_datetime(), 19 | "item_id": item_id, "quantity": quantity}) 20 | 21 | def restock_alert(self, item_id, quantity): 22 | return json.dumps({"timestamp": self._current_datetime(), 23 | "item_id": item_id, "quantity": quantity}) 24 | -------------------------------------------------------------------------------- /gateway-guardian-service/app/mqtt_topic_helper.py: -------------------------------------------------------------------------------- 1 | class MqttTopicHelper: 2 | def __init__(self, chain_name, store_name): 3 | self.chain_name = chain_name 4 | self.store_name = store_name 5 | 6 | def customer_arrival(self): 7 | return f"{self.chain_name}/{self.store_name}/customer-arrival" 8 | 9 | def display_welcome(self): 10 | return f"{self.chain_name}/{self.store_name}/display-welcome" 11 | 12 | def purchase_complete(self): 13 | return f"{self.chain_name}/{self.store_name}/purchase-complete" 14 | 15 | def stock_update(self): 16 | return f"{self.chain_name}/{self.store_name}/stock-update" 17 | 18 | def restock_alert(self): 19 | return f"{self.chain_name}/{self.store_name}/restock-alert" 20 | -------------------------------------------------------------------------------- /gateway-guardian-service/build-docker-image.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | VERSION="0.0.9" 3 | APP="gateway-guardian-service" 4 | ACCOUNT="pkalkman" 5 | docker buildx build --platform linux/amd64,linux/arm64 -f ./Dockerfile -t $ACCOUNT/$APP:$VERSION --push . 6 | -------------------------------------------------------------------------------- /gateway-guardian-service/poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "colorama" 5 | version = "0.4.6" 6 | description = "Cross-platform colored terminal text." 7 | category = "main" 8 | optional = false 9 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 10 | files = [ 11 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 12 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 13 | ] 14 | 15 | [[package]] 16 | name = "loguru" 17 | version = "0.7.0" 18 | description = "Python logging made (stupidly) simple" 19 | category = "main" 20 | optional = false 21 | python-versions = ">=3.5" 22 | files = [ 23 | {file = "loguru-0.7.0-py3-none-any.whl", hash = "sha256:b93aa30099fa6860d4727f1b81f8718e965bb96253fa190fab2077aaad6d15d3"}, 24 | {file = "loguru-0.7.0.tar.gz", hash = "sha256:1612053ced6ae84d7959dd7d5e431a0532642237ec21f7fd83ac73fe539e03e1"}, 25 | ] 26 | 27 | [package.dependencies] 28 | colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} 29 | win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} 30 | 31 | [package.extras] 32 | dev = ["Sphinx (==5.3.0)", "colorama (==0.4.5)", "colorama (==0.4.6)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v0.990)", "pre-commit (==3.2.1)", "pytest (==6.1.2)", "pytest (==7.2.1)", "pytest-cov (==2.12.1)", "pytest-cov (==4.0.0)", "pytest-mypy-plugins (==1.10.1)", "pytest-mypy-plugins (==1.9.3)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.2.0)", "tox (==3.27.1)", "tox (==4.4.6)"] 33 | 34 | [[package]] 35 | name = "paho-mqtt" 36 | version = "1.6.1" 37 | description = "MQTT version 5.0/3.1.1 client class" 38 | category = "main" 39 | optional = false 40 | python-versions = "*" 41 | files = [ 42 | {file = "paho-mqtt-1.6.1.tar.gz", hash = "sha256:2a8291c81623aec00372b5a85558a372c747cbca8e9934dfe218638b8eefc26f"}, 43 | ] 44 | 45 | [package.extras] 46 | proxy = ["PySocks"] 47 | 48 | [[package]] 49 | name = "win32-setctime" 50 | version = "1.1.0" 51 | description = "A small Python utility to set file creation time on Windows" 52 | category = "main" 53 | optional = false 54 | python-versions = ">=3.5" 55 | files = [ 56 | {file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"}, 57 | {file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"}, 58 | ] 59 | 60 | [package.extras] 61 | dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] 62 | 63 | [metadata] 64 | lock-version = "2.0" 65 | python-versions = "^3.11" 66 | content-hash = "6b533f8fdc8f52a1957b9f7f3a72e332e9592cbd4bbaf93dbe6fcaa19146a60e" 67 | -------------------------------------------------------------------------------- /gateway-guardian-service/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "gateway-guardian-service" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Patrick Kalkman "] 6 | readme = "README.md" 7 | packages = [{include = "gateway_guardian_service"}] 8 | 9 | [tool.poetry.dependencies] 10 | python = "^3.11" 11 | paho-mqtt = "^1.6.1" 12 | loguru = "^0.7.0" 13 | 14 | 15 | [build-system] 16 | requires = ["poetry-core"] 17 | build-backend = "poetry.core.masonry.api" 18 | -------------------------------------------------------------------------------- /gateway-guardian-service/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatrickKalkman/python-eda/e98886cbb669d445b17d7a39ce05884c4fe734be/gateway-guardian-service/tests/__init__.py -------------------------------------------------------------------------------- /hello-helper-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11-alpine as requirements-stage 2 | 3 | ENV VERSION 1.0.0 4 | 5 | WORKDIR /tmp 6 | 7 | RUN apk add --no-cache gcc musl-dev libffi-dev openssl-dev && \ 8 | pip install poetry && \ 9 | poetry config virtualenvs.create false 10 | 11 | COPY ./pyproject.toml ./poetry.lock* /tmp/ 12 | 13 | RUN poetry export -f requirements.txt --output requirements.txt --without-hashes 14 | 15 | FROM python:3.11-alpine 16 | 17 | # Create a non-root user 18 | RUN adduser --disabled-password --gecos '' appuser 19 | 20 | WORKDIR / 21 | 22 | COPY --from=requirements-stage /tmp/requirements.txt /app/requirements.txt 23 | 24 | RUN apk add --no-cache libffi openssl && \ 25 | pip install --no-cache-dir --upgrade -r /app/requirements.txt && \ 26 | rm -rf /root/.cache && \ 27 | rm -rf /var/cache/apk/* 28 | 29 | COPY ./app /app/ 30 | 31 | # Change ownership of the app directory to the non-root user 32 | RUN chown -R appuser:appuser /app 33 | 34 | USER appuser 35 | 36 | WORKDIR /app 37 | 38 | # Expose the port the app runs in 39 | EXPOSE 1883 40 | 41 | # During debugging, this entry point will be overridden. 42 | CMD ["python", "main.py"] -------------------------------------------------------------------------------- /hello-helper-service/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatrickKalkman/python-eda/e98886cbb669d445b17d7a39ce05884c4fe734be/hello-helper-service/README.md -------------------------------------------------------------------------------- /hello-helper-service/app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatrickKalkman/python-eda/e98886cbb669d445b17d7a39ce05884c4fe734be/hello-helper-service/app/__init__.py -------------------------------------------------------------------------------- /hello-helper-service/app/customer_name_db.py: -------------------------------------------------------------------------------- 1 | class CustomerNameDb: 2 | def __init__(self): 3 | self._name_map = { 4 | 1: "Astra Nova", 5 | 2: "Baxter Quantum", 6 | 3: "Cassiopeia Starlight", 7 | 4: "Draco Cosmos", 8 | 5: "Echo Nebula", 9 | 6: "Falcon Orion", 10 | 7: "Galaxy Pulsar", 11 | 8: "Halley Comet", 12 | 9: "Ignis Solaris", 13 | 10: "Jupiter Zenith" 14 | } 15 | 16 | def get_name(self, id): 17 | return self._name_map.get(id, "Unknown") 18 | -------------------------------------------------------------------------------- /hello-helper-service/app/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import signal 3 | 4 | import paho.mqtt.client as mqtt 5 | from customer_name_db import CustomerNameDb 6 | from loguru import logger 7 | from message_parser import MessageParser 8 | from mqtt_topic_helper import MqttTopicHelper 9 | 10 | 11 | def on_connect(client, userdata, flags, rc): 12 | logger.info(f"Connected with result code: {rc}") 13 | topic_helper = MqttTopicHelper('spectrum-grocers', 'fresh-frontier') 14 | client.subscribe(topic_helper.customer_arrival()) 15 | 16 | 17 | def on_message(client, userdata, msg): 18 | parser = MessageParser() 19 | customer_id = parser.get_customer_id(msg.payload) 20 | customer_name = CustomerNameDb().get_name(customer_id) 21 | logger.info(f"Welcome, {customer_name}") 22 | 23 | 24 | def on_signal(signum, frame): 25 | logger.info("Received termination signal, disconnecting...") 26 | client.disconnect() 27 | 28 | 29 | def main(): 30 | global client 31 | client = mqtt.Client(client_id="hello-helper-service") 32 | client.on_connect = on_connect 33 | client.on_message = on_message 34 | 35 | # Register the signal handlers 36 | signal.signal(signal.SIGINT, on_signal) 37 | signal.signal(signal.SIGTERM, on_signal) 38 | 39 | message_broker_host = os.environ.get("BROKER_ADDRESS", "localhost") 40 | client.connect(message_broker_host, 1883, 60) 41 | client.loop_forever() 42 | 43 | 44 | if __name__ == '__main__': 45 | main() 46 | -------------------------------------------------------------------------------- /hello-helper-service/app/message_parser.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | 4 | class MessageParser: 5 | def parse_message(self, message): 6 | return json.loads(message) 7 | 8 | def get_customer_id(self, message): 9 | msg_dict = self.parse_message(message) 10 | return msg_dict.get('customer_id', None) 11 | 12 | def get_items(self, message): 13 | msg_dict = self.parse_message(message) 14 | return msg_dict.get('items', None) 15 | 16 | def get_item_id(self, message): 17 | msg_dict = self.parse_message(message) 18 | return msg_dict.get('item_id', None) 19 | 20 | def get_quantity(self, message): 21 | msg_dict = self.parse_message(message) 22 | return msg_dict.get('quantity', None) 23 | 24 | def get_timestamp(self, message): 25 | msg_dict = self.parse_message(message) 26 | return msg_dict.get('timestamp', None) 27 | -------------------------------------------------------------------------------- /hello-helper-service/app/mqtt_topic_helper.py: -------------------------------------------------------------------------------- 1 | class MqttTopicHelper: 2 | def __init__(self, chain_name, store_name): 3 | self.chain_name = chain_name 4 | self.store_name = store_name 5 | 6 | def customer_arrival(self): 7 | return f"{self.chain_name}/{self.store_name}/customer-arrival" 8 | 9 | def customer_departure(self): 10 | return f"{self.chain_name}/{self.store_name}/customer-departure" 11 | 12 | def display_welcome(self): 13 | return f"{self.chain_name}/{self.store_name}/display-welcome" 14 | 15 | def purchase_complete(self): 16 | return f"{self.chain_name}/{self.store_name}/purchase-complete" 17 | 18 | def stock_update(self): 19 | return f"{self.chain_name}/{self.store_name}/stock-update" 20 | 21 | def restock_alert(self): 22 | return f"{self.chain_name}/{self.store_name}/restock-alert" 23 | -------------------------------------------------------------------------------- /hello-helper-service/build-docker-image.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | VERSION="0.0.1" 3 | APP="hello-helper-service" 4 | ACCOUNT="pkalkman" 5 | docker buildx build --platform linux/amd64,linux/arm64 -f ./Dockerfile -t $ACCOUNT/$APP:$VERSION --push . 6 | -------------------------------------------------------------------------------- /hello-helper-service/poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "colorama" 5 | version = "0.4.6" 6 | description = "Cross-platform colored terminal text." 7 | category = "main" 8 | optional = false 9 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 10 | files = [ 11 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 12 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 13 | ] 14 | 15 | [[package]] 16 | name = "loguru" 17 | version = "0.7.0" 18 | description = "Python logging made (stupidly) simple" 19 | category = "main" 20 | optional = false 21 | python-versions = ">=3.5" 22 | files = [ 23 | {file = "loguru-0.7.0-py3-none-any.whl", hash = "sha256:b93aa30099fa6860d4727f1b81f8718e965bb96253fa190fab2077aaad6d15d3"}, 24 | {file = "loguru-0.7.0.tar.gz", hash = "sha256:1612053ced6ae84d7959dd7d5e431a0532642237ec21f7fd83ac73fe539e03e1"}, 25 | ] 26 | 27 | [package.dependencies] 28 | colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} 29 | win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} 30 | 31 | [package.extras] 32 | dev = ["Sphinx (==5.3.0)", "colorama (==0.4.5)", "colorama (==0.4.6)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v0.990)", "pre-commit (==3.2.1)", "pytest (==6.1.2)", "pytest (==7.2.1)", "pytest-cov (==2.12.1)", "pytest-cov (==4.0.0)", "pytest-mypy-plugins (==1.10.1)", "pytest-mypy-plugins (==1.9.3)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.2.0)", "tox (==3.27.1)", "tox (==4.4.6)"] 33 | 34 | [[package]] 35 | name = "paho-mqtt" 36 | version = "1.6.1" 37 | description = "MQTT version 5.0/3.1.1 client class" 38 | category = "main" 39 | optional = false 40 | python-versions = "*" 41 | files = [ 42 | {file = "paho-mqtt-1.6.1.tar.gz", hash = "sha256:2a8291c81623aec00372b5a85558a372c747cbca8e9934dfe218638b8eefc26f"}, 43 | ] 44 | 45 | [package.extras] 46 | proxy = ["PySocks"] 47 | 48 | [[package]] 49 | name = "win32-setctime" 50 | version = "1.1.0" 51 | description = "A small Python utility to set file creation time on Windows" 52 | category = "main" 53 | optional = false 54 | python-versions = ">=3.5" 55 | files = [ 56 | {file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"}, 57 | {file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"}, 58 | ] 59 | 60 | [package.extras] 61 | dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] 62 | 63 | [metadata] 64 | lock-version = "2.0" 65 | python-versions = "^3.11" 66 | content-hash = "6b533f8fdc8f52a1957b9f7f3a72e332e9592cbd4bbaf93dbe6fcaa19146a60e" 67 | -------------------------------------------------------------------------------- /hello-helper-service/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "hello-helper-service" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Patrick Kalkman "] 6 | readme = "README.md" 7 | packages = [{include = "hello_helper_service"}] 8 | 9 | [tool.poetry.dependencies] 10 | python = "^3.11" 11 | loguru = "^0.7.0" 12 | paho-mqtt = "^1.6.1" 13 | 14 | 15 | [build-system] 16 | requires = ["poetry-core"] 17 | build-backend = "poetry.core.masonry.api" 18 | -------------------------------------------------------------------------------- /hello-helper-service/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatrickKalkman/python-eda/e98886cbb669d445b17d7a39ce05884c4fe734be/hello-helper-service/tests/__init__.py -------------------------------------------------------------------------------- /inventory-intel-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11-alpine as requirements-stage 2 | 3 | ENV VERSION 1.0.0 4 | 5 | WORKDIR /tmp 6 | 7 | RUN apk add --no-cache gcc musl-dev libffi-dev openssl-dev && \ 8 | pip install poetry && \ 9 | poetry config virtualenvs.create false 10 | 11 | COPY ./pyproject.toml ./poetry.lock* /tmp/ 12 | 13 | RUN poetry export -f requirements.txt --output requirements.txt --without-hashes 14 | 15 | FROM python:3.11-alpine 16 | 17 | # Create a non-root user 18 | RUN adduser --disabled-password --gecos '' appuser 19 | 20 | WORKDIR / 21 | 22 | COPY --from=requirements-stage /tmp/requirements.txt /app/requirements.txt 23 | 24 | RUN apk add --no-cache libffi openssl && \ 25 | pip install --no-cache-dir --upgrade -r /app/requirements.txt && \ 26 | rm -rf /root/.cache && \ 27 | rm -rf /var/cache/apk/* 28 | 29 | COPY ./app /app/ 30 | 31 | # Change ownership of the app directory to the non-root user 32 | RUN chown -R appuser:appuser /app 33 | 34 | USER appuser 35 | 36 | WORKDIR /app 37 | 38 | # Expose the port the app runs in 39 | EXPOSE 1883 40 | 41 | # During debugging, this entry point will be overridden. 42 | CMD ["python", "main.py"] -------------------------------------------------------------------------------- /inventory-intel-service/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatrickKalkman/python-eda/e98886cbb669d445b17d7a39ce05884c4fe734be/inventory-intel-service/README.md -------------------------------------------------------------------------------- /inventory-intel-service/app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatrickKalkman/python-eda/e98886cbb669d445b17d7a39ce05884c4fe734be/inventory-intel-service/app/__init__.py -------------------------------------------------------------------------------- /inventory-intel-service/app/inventory.py: -------------------------------------------------------------------------------- 1 | from faker import Faker 2 | 3 | 4 | class Inventory: 5 | def __init__(self): 6 | self.fake = Faker() 7 | self.inventory = self._generate_inventory() 8 | 9 | def _generate_inventory(self): 10 | inventory = {} 11 | for i in range(1, 101): # generate 100 products 12 | inventory[i] = {'name': self.fake.commerce.product(), 'stock': 100} 13 | return inventory 14 | -------------------------------------------------------------------------------- /inventory-intel-service/app/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import signal 3 | import time 4 | 5 | import paho.mqtt.client as mqtt 6 | from inventory import Inventory 7 | from loguru import logger 8 | from message_parser import MessageParser 9 | from mqtt_topic_helper import MqttTopicHelper 10 | 11 | # Define MQTT client ID and Broker Address 12 | client_id = "inventory-intel-service" 13 | message_broker_host = os.environ.get("BROKER_ADDRESS", "localhost") 14 | 15 | # Initialize MQTT Helper 16 | mqtt_helper = MqttTopicHelper("spectrum-grocers", "fresh-frontier") 17 | 18 | # Initialize Message Parser 19 | message_parser = MessageParser() 20 | 21 | # Define Inventory 22 | inventory = Inventory() 23 | 24 | 25 | def on_connect(client, userdata, flags, rc): 26 | logger.info(f"Connected to MQTT Broker: {message_broker_host} with result code: {rc}") 27 | client.subscribe(mqtt_helper.customer_departure()) 28 | 29 | 30 | def on_message(client, userdata, msg): 31 | message = message_parser.customer_departure(msg.payload) 32 | customer_id = message['customer_id'] 33 | product_ids = message['product_ids'] 34 | for product_id in product_ids: 35 | inventory.inventory[product_id]['stock'] -= 1 36 | logger.info(f"Inventory updated for customer {customer_id}.") 37 | 38 | 39 | def log_inventory(): 40 | while True: 41 | logger.info("Inventory Check:") 42 | for product_id, product_info in inventory.inventory.items(): 43 | if int(product_info['stock']) < 100: 44 | logger.info(f"Id: {product_id}, Pr: {product_info['name']}," + 45 | f"St: {product_info['stock']}") 46 | time.sleep(60) 47 | 48 | 49 | def on_exit(signum, frame): 50 | logger.info("Received Exit Signal...Disconnecting from Broker") 51 | client.disconnect() 52 | exit(0) 53 | 54 | 55 | # MQTT client 56 | client = mqtt.Client(client_id) 57 | client.on_connect = on_connect 58 | client.on_message = on_message 59 | 60 | # Connect to MQTT Broker 61 | client.connect(message_broker_host) 62 | 63 | # Handle exit signals 64 | signal.signal(signal.SIGINT, on_exit) 65 | signal.signal(signal.SIGTERM, on_exit) 66 | 67 | # Start MQTT client loop 68 | client.loop_start() 69 | 70 | # Log inventory every 30 seconds 71 | log_inventory() 72 | -------------------------------------------------------------------------------- /inventory-intel-service/app/message_parser.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | 4 | class MessageParser: 5 | def customer_arrival(self, message): 6 | data = json.loads(message) 7 | return {'timestamp': data['timestamp'], 'customer_id': data['customer_id']} 8 | 9 | def customer_departure(self, message): 10 | data = json.loads(message) 11 | return {'timestamp': data['timestamp'], 'customer_id': data['customer_id'], 12 | 'product_ids': data['product_ids']} 13 | -------------------------------------------------------------------------------- /inventory-intel-service/app/mqtt_topic_helper.py: -------------------------------------------------------------------------------- 1 | class MqttTopicHelper: 2 | def __init__(self, chain_name, store_name): 3 | self.chain_name = chain_name 4 | self.store_name = store_name 5 | 6 | def customer_arrival(self): 7 | return f"{self.chain_name}/{self.store_name}/customer-arrival" 8 | 9 | def customer_departure(self): 10 | return f"{self.chain_name}/{self.store_name}/customer-departure" 11 | 12 | def display_welcome(self): 13 | return f"{self.chain_name}/{self.store_name}/display-welcome" 14 | 15 | def purchase_complete(self): 16 | return f"{self.chain_name}/{self.store_name}/purchase-complete" 17 | 18 | def stock_update(self): 19 | return f"{self.chain_name}/{self.store_name}/stock-update" 20 | 21 | def restock_alert(self): 22 | return f"{self.chain_name}/{self.store_name}/restock-alert" 23 | -------------------------------------------------------------------------------- /inventory-intel-service/build-docker-image.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | VERSION="0.0.6" 3 | APP="inventory-intel-service" 4 | ACCOUNT="pkalkman" 5 | docker buildx build --platform linux/amd64,linux/arm64 -f ./Dockerfile -t $ACCOUNT/$APP:$VERSION --push . 6 | -------------------------------------------------------------------------------- /inventory-intel-service/poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "colorama" 5 | version = "0.4.6" 6 | description = "Cross-platform colored terminal text." 7 | category = "main" 8 | optional = false 9 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 10 | files = [ 11 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 12 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 13 | ] 14 | 15 | [[package]] 16 | name = "faker" 17 | version = "18.9.0" 18 | description = "Faker is a Python package that generates fake data for you." 19 | category = "main" 20 | optional = false 21 | python-versions = ">=3.7" 22 | files = [ 23 | {file = "Faker-18.9.0-py3-none-any.whl", hash = "sha256:defe9ed618a67ebf0f3eb1895e198c2355a7128a09087a6dce342ef2253263ea"}, 24 | {file = "Faker-18.9.0.tar.gz", hash = "sha256:80a5ea1464556c06b98bf47ea3adc7f33811a1182518d847860b1874080bd3c9"}, 25 | ] 26 | 27 | [package.dependencies] 28 | python-dateutil = ">=2.4" 29 | 30 | [[package]] 31 | name = "loguru" 32 | version = "0.7.0" 33 | description = "Python logging made (stupidly) simple" 34 | category = "main" 35 | optional = false 36 | python-versions = ">=3.5" 37 | files = [ 38 | {file = "loguru-0.7.0-py3-none-any.whl", hash = "sha256:b93aa30099fa6860d4727f1b81f8718e965bb96253fa190fab2077aaad6d15d3"}, 39 | {file = "loguru-0.7.0.tar.gz", hash = "sha256:1612053ced6ae84d7959dd7d5e431a0532642237ec21f7fd83ac73fe539e03e1"}, 40 | ] 41 | 42 | [package.dependencies] 43 | colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} 44 | win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} 45 | 46 | [package.extras] 47 | dev = ["Sphinx (==5.3.0)", "colorama (==0.4.5)", "colorama (==0.4.6)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v0.990)", "pre-commit (==3.2.1)", "pytest (==6.1.2)", "pytest (==7.2.1)", "pytest-cov (==2.12.1)", "pytest-cov (==4.0.0)", "pytest-mypy-plugins (==1.10.1)", "pytest-mypy-plugins (==1.9.3)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.2.0)", "tox (==3.27.1)", "tox (==4.4.6)"] 48 | 49 | [[package]] 50 | name = "paho-mqtt" 51 | version = "1.6.1" 52 | description = "MQTT version 5.0/3.1.1 client class" 53 | category = "main" 54 | optional = false 55 | python-versions = "*" 56 | files = [ 57 | {file = "paho-mqtt-1.6.1.tar.gz", hash = "sha256:2a8291c81623aec00372b5a85558a372c747cbca8e9934dfe218638b8eefc26f"}, 58 | ] 59 | 60 | [package.extras] 61 | proxy = ["PySocks"] 62 | 63 | [[package]] 64 | name = "python-dateutil" 65 | version = "2.8.2" 66 | description = "Extensions to the standard Python datetime module" 67 | category = "main" 68 | optional = false 69 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 70 | files = [ 71 | {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, 72 | {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, 73 | ] 74 | 75 | [package.dependencies] 76 | six = ">=1.5" 77 | 78 | [[package]] 79 | name = "six" 80 | version = "1.16.0" 81 | description = "Python 2 and 3 compatibility utilities" 82 | category = "main" 83 | optional = false 84 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 85 | files = [ 86 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 87 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 88 | ] 89 | 90 | [[package]] 91 | name = "win32-setctime" 92 | version = "1.1.0" 93 | description = "A small Python utility to set file creation time on Windows" 94 | category = "main" 95 | optional = false 96 | python-versions = ">=3.5" 97 | files = [ 98 | {file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"}, 99 | {file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"}, 100 | ] 101 | 102 | [package.extras] 103 | dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] 104 | 105 | [metadata] 106 | lock-version = "2.0" 107 | python-versions = "^3.11" 108 | content-hash = "0c3012cfab1c5a7b9c8d88c646984d3a554fe18ffbd282ab29efc743fc8d0ec9" 109 | -------------------------------------------------------------------------------- /inventory-intel-service/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "inventory-intel-service" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Patrick Kalkman "] 6 | readme = "README.md" 7 | packages = [{include = "inventory_intel_service"}] 8 | 9 | [tool.poetry.dependencies] 10 | python = "^3.11" 11 | loguru = "^0.7.0" 12 | paho-mqtt = "^1.6.1" 13 | faker = "^18.9.0" 14 | 15 | 16 | [build-system] 17 | requires = ["poetry-core"] 18 | build-backend = "poetry.core.masonry.api" 19 | -------------------------------------------------------------------------------- /inventory-intel-service/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatrickKalkman/python-eda/e98886cbb669d445b17d7a39ce05884c4fe734be/inventory-intel-service/tests/__init__.py -------------------------------------------------------------------------------- /message-mediator-broker/mosquitto.conf: -------------------------------------------------------------------------------- 1 | persistence false 2 | allow_anonymous true 3 | connection_messages false 4 | log_type error 5 | listener 1883 6 | --------------------------------------------------------------------------------