-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
0f3fc44
commit e3ba48f
Showing
15 changed files
with
315 additions
and
233 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,17 @@ | ||
[build-system] | ||
requires = ["hatchling"] | ||
build-backend = "hatchling.build" | ||
|
||
[project] | ||
name = "registration-harvester-worker" | ||
version = "0.1" | ||
authors = [] | ||
name = "worker" | ||
version = "2.0.0-beta1" | ||
authors = [ | ||
{ name="Mario Winkler", email="[email protected]" }, | ||
{ name="Jonas Eberle", email="[email protected]" } | ||
] | ||
description = "A Flowable External Worker implementation with FastAPI acting as worker for Registration Harvester component" | ||
readme = "README.md" | ||
license = {file = "LICENSE"} | ||
requires-python = ">=3.8" | ||
classifiers = [ | ||
"Programming Language :: Python :: 3", | ||
|
@@ -14,8 +22,8 @@ dependencies = [ | |
"requests", | ||
"fastapi[standard]", | ||
"flowable.external_worker_client", | ||
"registration-library @ git+https://github.com/EOEPCA/registration-library" | ||
# "flowable.external-worker-client @ git+https://github.com/EOEPCA/eoepca-flowable-external-client-python" | ||
# "registration-library @ git+https://github.com/EOEPCA/registration-library", | ||
# "flowable.external-worker-client @ git+https://github.com/EOEPCA/eoepca-flowable-external-client-python@develop" | ||
] | ||
|
||
[project.optional-dependencies] | ||
|
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import os | ||
from typing import get_type_hints, Union | ||
from dotenv import load_dotenv | ||
|
||
# Load configuration | ||
# The value of a variable is the first of the values found in: | ||
# - the environment | ||
# - the .env file | ||
# - the default value, if provided | ||
load_dotenv(".env") | ||
|
||
|
||
class WorkerConfigError(Exception): | ||
pass | ||
|
||
|
||
def _parse_bool(val: Union[str, bool]) -> bool: # pylint: disable=E1136 | ||
return val if type(val) is bool else val.lower() in ["true", "yes", "1"] | ||
|
||
|
||
class WorkerConfig: | ||
# Fields and default values | ||
FLOWABLE_HOST: str = "https://registration-harvester-api.develop.eoepca.org/flowable-rest" | ||
FLOWABLE_REST_USER: str = "eoepca" | ||
FLOWABLE_REST_PASSWORD: str = "eoepca" | ||
FLOWABLE_HOST_CACERT: str = "etc/eoepca-ca-chain.pem" | ||
FLOWABLE_USE_TLS: bool = True | ||
|
||
""" | ||
Map environment variables to class fields according to these rules: | ||
- Field won't be parsed unless it has a type annotation | ||
- Field will be skipped if not in all caps | ||
- Class field and environment variable name are the same | ||
""" | ||
|
||
def __init__(self, env): | ||
for field in self.__annotations__: | ||
if not field.isupper(): | ||
continue | ||
|
||
# Raise AppConfigError if required field not supplied | ||
default_value = getattr(self, field, None) | ||
if default_value is None and env.get(field) is None: | ||
raise WorkerConfigError("The {} field is required".format(field)) | ||
|
||
# Cast env var value to expected type and raise AppConfigError on failure | ||
try: | ||
var_type = get_type_hints(WorkerConfig)[field] | ||
if var_type == bool: | ||
value = _parse_bool(env.get(field, default_value)) | ||
else: | ||
value = var_type(env.get(field, default_value)) | ||
self.__setattr__(field, value) | ||
except ValueError: | ||
raise WorkerConfigError( | ||
'Unable to cast value of "{}" to type "{}" for "{}" field'.format(env[field], var_type, field) | ||
) | ||
|
||
def __repr__(self): | ||
return str(self.__dict__) | ||
|
||
|
||
# Expose Config object for app to import | ||
Config = WorkerConfig(os.environ) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
from flowable.external_worker_client import ExternalWorkerSubscription | ||
|
||
|
||
class SubscriptionManager: | ||
""" | ||
Diese Klassen-Implementierung sorgt dafür, dass nur eine Instanz der Klasse SubscriptionManager existiert. | ||
Wenn versucht wird, zusätzlich eine neue Instanz zu erstellen, | ||
wird immer die zu Beginn erstellte Instanz zurückgegeben. Auf diese Weise verhält sich das SubscriptionManagerObjekt | ||
wie eine globale Variable, kann aber Vorteile von OOP nutzen (siehe Singleton Class). | ||
""" | ||
|
||
# Statische Klassenvariable, die die einzige Instanz der Klasse speichert | ||
_instance = None | ||
|
||
# __new__ wird aufgerufen und dient zur Erstellung einer neuen Instanz der Klasse | ||
def __new__(cls, *args, **kwargs): | ||
# Prüfen, ob bereits eine Instanz der Klasse existiert. Überprüfung kann aber auch | ||
# an get_instance() delegiert werden, da SubscriptionManager() nicht zum Instanziieren | ||
# verwendet werden sollte (best practice) | ||
if not cls._instance: | ||
# Wenn keine Instanz existiert, wird eine neue erstellt | ||
cls._instance = super(SubscriptionManager, cls).__new__(cls, *args, **kwargs) | ||
# Initialisieren eines leeren Dictionaries für Abonnements | ||
cls._instance._subscriptions = {} | ||
# Gibt die einzige Instanz zurück (entweder eine neue oder die bereits existierende) | ||
return cls._instance | ||
|
||
@classmethod | ||
def get_instance(cls): | ||
# Gibt die Singleton-Instanz von SubscriptionManager zurück und instanziiert sie vorher, falls nötig. | ||
if cls._instance is None: | ||
cls._instance = cls() | ||
return cls._instance | ||
|
||
def add_subscription(self, topic: str, worker_id: str, subscription: ExternalWorkerSubscription, timestamp: str): | ||
# Erstelle einen neuen Key für das Topic, falls es noch nicht existiert | ||
if topic not in self._subscriptions: | ||
self._subscriptions[topic] = {} | ||
|
||
if worker_id not in self._subscriptions[topic]: | ||
# Füge die Subscription für die gegebene worker_id unter dem jeweiligen Topic hinzu | ||
self._subscriptions[topic][worker_id] = {"sub_obj": subscription, "jobs_done": 0, "start_time": timestamp} | ||
|
||
def remove_subscription(self, topic: str, worker_id: str): | ||
# Überprüfen, ob zu löschender Worker existiert und ihn dann löschen | ||
if self.worker_exists(topic, worker_id): | ||
# Worker aus manager löschen | ||
subscription = self._subscriptions[topic].pop(worker_id) | ||
subscription["sub_obj"].unsubscribe() | ||
return True | ||
return False | ||
|
||
def worker_exists(self, topic: str, worker_id: str): | ||
# checkt für entsprechenden Topic, ob ein gegebener Worker exisitert | ||
return topic in self._subscriptions and worker_id in self._subscriptions[topic] | ||
|
||
def _count_subscriptions(self): | ||
# zählt die subscriptions über die subscribed worker ids (keys) | ||
all_subs = set(key for d in self._subscriptions.values() for key in d.keys()) | ||
return len(all_subs) | ||
|
||
def get_subscription_objects(self): | ||
# gibt ein Array mit allen Subscription-Objects des Managers zurück | ||
return [worker_id["sub_obj"] for topic in self._subscriptions.values() for worker_id in topic.values()] | ||
|
||
def increment_job(self, current_worker): | ||
# zählt "jobs_done"-Variable des jeweiligen Workerdictionaries um eins nach oben | ||
current_topic = current_worker.split("_")[1] | ||
if self.worker_exists(current_topic, current_worker): | ||
self._subscriptions[current_topic][current_worker]["jobs_done"] += 1 | ||
|
||
def get_subscription_info(self): | ||
# erstellt auf Basis der im Manager gespeicherten Worker-Infos ein Output Dictionary | ||
result_dict = { | ||
topic: { | ||
worker_id: {"jobs_done": worker_data["jobs_done"], "start_time": worker_data["start_time"]} | ||
for worker_id, worker_data in workers.items() | ||
} | ||
for topic, workers in self._subscriptions.items() | ||
} | ||
count_dict = {"worker currently working": self._count_subscriptions()} | ||
return {**count_dict, **result_dict} |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.