diff --git a/Dockerfile b/Dockerfile index 23aff71..78f1503 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM osimis/orthanc:22.9.0-full +FROM osimis/orthanc:23.9.2-full RUN apt-get update && ACCEPT_EULA=Y apt-get dist-upgrade -y && apt-get install -y openssl @@ -9,6 +9,6 @@ RUN mkdir -p /ssl && cat /etc/ssl/private/server.key /etc/ssl/certs/server.pem COPY orthanc_ext /python/orthanc_ext WORKDIR /python COPY setup.py README.rst HISTORY.rst ./ -RUN pip3 install httpx .[nats-event-publisher] # does not get picked up in setup.py +RUN pip3 install httpx .[nats-event-publisher,pyorthanc] # does not get picked up in setup.py RUN python3 setup.py install COPY tests/entry_point.py /python/entry_point.py diff --git a/HISTORY.rst b/HISTORY.rst index eae3680..da78362 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -2,7 +2,15 @@ History ======= -3.3.0 (2022-01-30) +3.5.0 (2023-10-12) +------------------ +* Support pyOrthanc as Orthanc API client + +3.4.0 (2023-06-21) +------------------ +* Improved asyncio performance + +3.3.0 (2023-01-30) ------------------ * Publish Orthanc change events to Kafka, RabbitMQ and NATS * Run asyncio functions (coroutines) for concurrent processing of a change event diff --git a/orthanc_ext/__init__.py b/orthanc_ext/__init__.py index db9c04f..e550106 100644 --- a/orthanc_ext/__init__.py +++ b/orthanc_ext/__init__.py @@ -2,4 +2,4 @@ __author__ = """WalkIT""" __email__ = 'code@walkit.nl' -__version__ = '3.4.0' +__version__ = '3.5.0' diff --git a/orthanc_ext/event_dispatcher.py b/orthanc_ext/event_dispatcher.py index 85860e1..aa95217 100644 --- a/orthanc_ext/event_dispatcher.py +++ b/orthanc_ext/event_dispatcher.py @@ -4,8 +4,8 @@ from dataclasses import dataclass from orthanc_ext.executor_utilities import SequentialHybridExecutor -from orthanc_ext.http_utilities import create_internal_client, get_rest_api_base_url, \ - get_certificate, ClientType +from orthanc_ext.http_utilities import get_rest_api_base_url, \ + get_certificate, OrthancClientTypeFactory, HttpxClientType from orthanc_ext.logging_configurator import python_logging from orthanc_ext.python_utilities import ensure_iterable, create_reverse_type_dict @@ -67,8 +67,8 @@ def get_sync_handlers(handlers): return [handler for handler in handlers if not inspect.iscoroutinefunction(handler)] -def create_session(orthanc, client_type=ClientType.SYNC): +def create_session(orthanc, client_type: OrthancClientTypeFactory = HttpxClientType.SYNC): config = json.loads(orthanc.GetConfiguration()) - return create_internal_client( + return client_type.create_internal_client( get_rest_api_base_url(config), orthanc.GenerateRestApiAuthorizationToken(), - get_certificate(config), client_type) + get_certificate(config)) diff --git a/orthanc_ext/http_utilities.py b/orthanc_ext/http_utilities.py index 7bd2fe3..8fe69f0 100644 --- a/orthanc_ext/http_utilities.py +++ b/orthanc_ext/http_utilities.py @@ -1,3 +1,4 @@ +from dataclasses import dataclass from enum import Enum from typing import Union @@ -14,17 +15,29 @@ def get_certificate(config): return False if not config.get('SslEnabled', False) else config.get('SslCertificate', False) -class ClientType(Enum): +@dataclass +class OrthancClientTypeFactory: + http_client: type + + def create_internal_client(self, *args, **kwargs): + return create_internal_client(*args, **kwargs, client_type=self) + + +class HttpxClientType(OrthancClientTypeFactory, Enum): SYNC = httpx.Client ASYNC = httpx.AsyncClient +# deprecated, for backward compatibility +ClientType = HttpxClientType + + def create_internal_client( base_url, token='', cert: Union[str, bool] = False, - client_type: ClientType = ClientType.SYNC) -> httpx.Client: - return client_type.value( + client_type: ClientType = HttpxClientType.SYNC): + return client_type.http_client( base_url=base_url, timeout=httpx.Timeout(300, connect=30), verify=cert, diff --git a/orthanc_ext/pyorthanc_utilities.py b/orthanc_ext/pyorthanc_utilities.py new file mode 100644 index 0000000..1bc49d1 --- /dev/null +++ b/orthanc_ext/pyorthanc_utilities.py @@ -0,0 +1,30 @@ +from enum import Enum +from typing import Union + +import httpx +from pyorthanc import Orthanc, AsyncOrthanc + +from orthanc_ext.http_utilities import OrthancClientTypeFactory + + +class PyOrthancClientType(OrthancClientTypeFactory, Enum): + SYNC = Orthanc + ASYNC = AsyncOrthanc + + def create_internal_client(self, *args, **kwargs): + return create_internal_client(*args, **kwargs, client_type=self) + + +def create_internal_client( + base_url, + token='', + cert: Union[str, bool] = False, + client_type: PyOrthancClientType = PyOrthancClientType.SYNC): + + # note: only difference with the httpx.Client constructor is the `base_url` positional argument. + return client_type.http_client( + base_url, + base_url=base_url, + timeout=httpx.Timeout(300, connect=30), + verify=cert, + headers={'Authorization': token}) diff --git a/setup.cfg b/setup.cfg index 3df85ff..3bd2f85 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 3.4.0 +current_version = 3.5.0 commit = True tag = True diff --git a/setup.py b/setup.py index c192424..054da2d 100644 --- a/setup.py +++ b/setup.py @@ -46,11 +46,12 @@ extras_require={ 'nats-event-publisher': ['cloudevents', 'nats-py'], 'kafka-event-publisher': ['cloudevents', 'aiokafka'], - 'rabbitmq-event-publisher': ['cloudevents', 'aio-pika'] + 'rabbitmq-event-publisher': ['cloudevents', 'aio-pika'], + 'pyorthanc': ['pyorthanc>1.0'] }, test_suite='tests', tests_require=test_requirements, url='https://github.com/walkIT-nl/orthanc-server-extensions', - version='3.4.0', + version='3.5.0', zip_safe=False, ) diff --git a/tests/entry_point.py b/tests/entry_point.py index b67cdde..90bc5f9 100644 --- a/tests/entry_point.py +++ b/tests/entry_point.py @@ -1,13 +1,12 @@ """Test entry point script for Orthanc Python Plugin. """ - import logging from functools import partial import orthanc # NOQA provided by the plugin runtime. from orthanc_ext import event_dispatcher -from orthanc_ext.http_utilities import ClientType +from orthanc_ext.pyorthanc_utilities import PyOrthancClientType from orthanc_ext.python_utilities import pipeline from orthanc_ext.scripts.nats_event_publisher import create_stream, publish_to_nats, NatsConfig @@ -21,7 +20,7 @@ def start_maintenance_cycle(event, _): def retrieve_system_info(_, client): - return client.get('/system').json() + return client.get_system() def show_system_info(info, _client): @@ -30,6 +29,7 @@ def show_system_info(info, _client): nats_config = NatsConfig('nats://nats') + event_dispatcher.register_event_handlers({ orthanc.ChangeType.ORTHANC_STARTED: [ partial(log_event, 'started'), @@ -38,6 +38,6 @@ def show_system_info(info, _client): ], orthanc.ChangeType.STABLE_STUDY: [partial(publish_to_nats, nats_config)], orthanc.ChangeType.ORTHANC_STOPPED: partial(log_event, 'stopped') -}, orthanc, event_dispatcher.create_session(orthanc), +}, orthanc, event_dispatcher.create_session(orthanc, client_type=PyOrthancClientType.SYNC), event_dispatcher.create_session( - orthanc, client_type=ClientType.ASYNC)) + orthanc, client_type=PyOrthancClientType.ASYNC)) diff --git a/tests/test_http_utilities.py b/tests/test_http_utilities.py new file mode 100644 index 0000000..38fb059 --- /dev/null +++ b/tests/test_http_utilities.py @@ -0,0 +1,19 @@ +import httpx + +from orthanc_ext.event_dispatcher import create_session +from orthanc_ext.http_utilities import ClientType, HttpxClientType +from orthanc_ext.orthanc import OrthancApiHandler + + +def test_shall_create_sync_client(): + client = HttpxClientType.SYNC.create_internal_client(base_url='http://localhost:8042') + assert client is not None + assert type(client) == httpx.Client + + +def test_shall_support_create_session_for_backward_compatibility(): + assert type(create_session(OrthancApiHandler(), ClientType.SYNC)) == httpx.Client + + +def test_shall_support_create_async_client_for_backward_compatibility(): + assert type(create_session(OrthancApiHandler(), ClientType.ASYNC)) == httpx.AsyncClient diff --git a/tests/test_pyorthanc_utilities.py b/tests/test_pyorthanc_utilities.py new file mode 100644 index 0000000..e370068 --- /dev/null +++ b/tests/test_pyorthanc_utilities.py @@ -0,0 +1,21 @@ +from pyorthanc import Orthanc, AsyncOrthanc + +from orthanc_ext.event_dispatcher import create_session +from orthanc_ext.orthanc import OrthancApiHandler +from orthanc_ext.pyorthanc_utilities import PyOrthancClientType + + +def test_shall_create_sync_client(): + client = PyOrthancClientType.SYNC.create_internal_client(base_url='http://localhost:8042') + assert client is not None + assert type(client) == Orthanc + + +def test_shall_create_async_client(): + client = PyOrthancClientType.ASYNC.create_internal_client(base_url='http://localhost:8042') + assert client is not None + assert type(client) == AsyncOrthanc + + +def test_shall_support_create_session_for_backward_compatibility(): + assert create_session(OrthancApiHandler(), PyOrthancClientType.SYNC) is not None diff --git a/tox.ini b/tox.ini index 0e8e706..feed47f 100644 --- a/tox.ini +++ b/tox.ini @@ -15,7 +15,7 @@ python = setenv = PYTHONPATH = {toxinidir} deps = - .[nats-event-publisher, kafka-event-publisher, rabbitmq-event-publisher] + .[nats-event-publisher, kafka-event-publisher, rabbitmq-event-publisher, pyorthanc] -r{toxinidir}/requirements_dev.txt pytest-cov ; If you want to make tox run the tests with the same versions, create a