etos-lib 4.3.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
etos_lib/__init__.py ADDED
@@ -0,0 +1,28 @@
1
+ # Copyright 2020 Axis Communications AB.
2
+ #
3
+ # For a full list of individual contributors, please see the commit history.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ """ETOS library module."""
17
+ from pkg_resources import get_distribution, DistributionNotFound
18
+ from etos_lib.etos import ETOS
19
+
20
+ # pylint:disable=invalid-name
21
+ try:
22
+ # Change here if project is renamed and does not equal the package name
23
+ dist_name = __name__
24
+ __version__ = get_distribution(dist_name).version
25
+ except DistributionNotFound:
26
+ __version__ = "unknown"
27
+ finally:
28
+ del get_distribution, DistributionNotFound
@@ -0,0 +1,16 @@
1
+ # Copyright Axis Communications AB.
2
+ #
3
+ # For a full list of individual contributors, please see the commit history.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ """ETOS library eiffel helpers."""
@@ -0,0 +1,117 @@
1
+ # Copyright Axis Communications AB.
2
+ #
3
+ # For a full list of individual contributors, please see the commit history.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ """Common functions for the eiffel helpers."""
17
+ from collections.abc import MutableMapping, Sequence
18
+ from typing import Iterable, Optional
19
+
20
+ from eiffellib.events.eiffel_base_event import EiffelBaseEvent
21
+ from opentelemetry.semconv.trace import MessagingOperationValues, SpanAttributes
22
+ from opentelemetry.trace.span import Span
23
+ from pika.channel import Channel
24
+ from pika.spec import BasicProperties
25
+
26
+ from ..lib.config import Config
27
+
28
+ PUBLISHER_TEMPLATE = "{SERVER_ADDRESS}:{SERVER_PORT},{RABBITMQ_VHOST},{RABBITMQ_EXCHANGE}"
29
+ CONSUMER_TEMPLATE = "{RABBITMQ_QUEUE_NAME}"
30
+ # pylint:disable=too-many-arguments
31
+
32
+
33
+ def add_span_attributes(
34
+ span: Span,
35
+ channel: Channel,
36
+ properties: BasicProperties,
37
+ routing_key: str,
38
+ operation: MessagingOperationValues,
39
+ destination_name: Optional[str] = None,
40
+ ) -> None:
41
+ """Add rabbitmq properties to a span.
42
+
43
+ Copied and modified from:
44
+ https://github.com/open-telemetry/opentelemetry-python-contrib/blob/main/instrumentation/opentelemetry-instrumentation-pika
45
+ """
46
+ ssl = bool(Config().get("rabbitmq").get("ssl"))
47
+
48
+ span.set_attribute(SpanAttributes.MESSAGING_SYSTEM, "rabbitmq")
49
+ span.set_attribute(SpanAttributes.MESSAGING_OPERATION, operation.value)
50
+ span.set_attribute(SpanAttributes.MESSAGING_RABBITMQ_DESTINATION_ROUTING_KEY, routing_key)
51
+
52
+ if destination_name is not None:
53
+ span.set_attribute(SpanAttributes.MESSAGING_DESTINATION_NAME, destination_name)
54
+ span.set_attribute("messaging.destination_publish.name", properties.type)
55
+ span.set_attribute(SpanAttributes.MESSAGING_DESTINATION_TEMPLATE, CONSUMER_TEMPLATE)
56
+ else:
57
+ span.set_attribute(SpanAttributes.MESSAGING_DESTINATION_NAME, properties.type)
58
+ span.set_attribute(SpanAttributes.MESSAGING_DESTINATION_TEMPLATE, PUBLISHER_TEMPLATE)
59
+
60
+ span.set_attribute(SpanAttributes.NETWORK_PROTOCOL_NAME, "amqps" if ssl else "amqp")
61
+ span.set_attribute(SpanAttributes.NETWORK_TYPE, "ipv4")
62
+ span.set_attribute(SpanAttributes.NETWORK_TRANSPORT, "tcp")
63
+
64
+ span.set_attribute(SpanAttributes.SERVER_ADDRESS, channel.connection.params.host)
65
+ span.set_attribute(SpanAttributes.SERVER_PORT, channel.connection.params.port)
66
+
67
+
68
+ def add_span_eiffel_attributes(span: Span, event: EiffelBaseEvent) -> None:
69
+ """Add Eiffel properties to a span."""
70
+ span.set_attribute(SpanAttributes.EVENT_NAME, event.meta.type)
71
+ span.set_attribute(SpanAttributes.MESSAGING_MESSAGE_ID, event.meta.event_id)
72
+
73
+
74
+ def _flatten(d: dict, parent_key: str = "", sep: str = ".") -> Iterable[tuple[str, str]]:
75
+ """Flatten a dictionary to be compatible with opentelemetry."""
76
+ for k, v in d.items():
77
+ new_key = parent_key + sep + k if parent_key else k
78
+ if isinstance(v, MutableMapping):
79
+ yield from _flatten_dict(v, new_key, sep=sep).items()
80
+ elif isinstance(v, list):
81
+ for i, lv in enumerate(v):
82
+ if isinstance(lv, str):
83
+ yield new_key, v
84
+ break
85
+ if isinstance(lv, MutableMapping):
86
+ new_key = new_key + sep + str(i)
87
+ yield from _flatten_dict(lv, new_key, sep=sep).items()
88
+ else:
89
+ yield new_key, v
90
+
91
+
92
+ def _flatten_dict(d: MutableMapping, parent_key: str = "", sep: str = ".") -> dict:
93
+ """Call flatten on a dictionary."""
94
+ return dict(_flatten(d, parent_key, sep))
95
+
96
+
97
+ def _links_to_dict(links: Sequence) -> MutableMapping:
98
+ """Convert an Eiffel links structure to a dictionary."""
99
+ dict_links = {}
100
+ for link in links:
101
+ key = link["type"].lower()
102
+ if key in dict_links:
103
+ if not isinstance(dict_links[key], list):
104
+ dict_links[key] = [dict_links[key]]
105
+ dict_links[key].append(link["target"])
106
+ else:
107
+ dict_links[key] = link["target"]
108
+ return dict_links
109
+
110
+
111
+ def add_event(event: EiffelBaseEvent) -> dict:
112
+ """Add event data to a dictionary."""
113
+ attributes = {}
114
+ event_json = event.json
115
+ event_json["links"] = _links_to_dict(event_json.pop("links"))
116
+ attributes.update(**_flatten_dict(event_json, parent_key="eiffel"))
117
+ return attributes
@@ -0,0 +1,129 @@
1
+ # Copyright Axis Communications AB.
2
+ #
3
+ # For a full list of individual contributors, please see the commit history.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ """Custom publishers for eiffellib."""
17
+ import logging
18
+ import time
19
+ from copy import deepcopy
20
+ from threading import current_thread
21
+
22
+ from eiffellib.events.eiffel_base_event import EiffelBaseEvent
23
+ from eiffellib.publishers.rabbitmq_publisher import RabbitMQPublisher
24
+ from opentelemetry import propagate, trace
25
+ from opentelemetry.semconv.trace import MessagingOperationValues
26
+ from opentelemetry.trace import SpanKind
27
+ from pika.spec import BasicProperties
28
+
29
+ from .common import add_event, add_span_attributes, add_span_eiffel_attributes
30
+
31
+ _LOG = logging.getLogger(__name__)
32
+
33
+
34
+ class TracingRabbitMQPublisher(RabbitMQPublisher):
35
+ """Custom RabbitMQ publisher that propagates otel trace information to headers."""
36
+
37
+ def __init__(self, *args, **kwargs):
38
+ """Get a tracer."""
39
+ # Must import this here, otherwise there would be a cyclic import problem.
40
+ # pylint:disable=cyclic-import,import-outside-toplevel
41
+ from etos_lib import __version__
42
+
43
+ super().__init__(*args, **kwargs)
44
+ self.tracer = trace.get_tracer(
45
+ __name__,
46
+ __version__,
47
+ schema_url="https://opentelemetry.io/schemas/1.11.0",
48
+ )
49
+ self.destination = f"{self.parameters.host},{self.parameters.virtual_host},{self.exchange}"
50
+
51
+ def send_event(self, event: EiffelBaseEvent, block: bool = True) -> None:
52
+ """Validate and send an eiffel event to the rabbitmq server.
53
+
54
+ This method will set the source on all events if there is a source
55
+ added to the :obj:`RabbitMQPublisher`.
56
+ If the routing key is set to None in the :obj:`RabbitMQPublisher` this
57
+ method will use the routing key from the event that is being sent.
58
+ The event domainId will also be added to `meta.source` if it is set to
59
+ anything other than the default value. If there is no domainId
60
+ set on the event, then the domainId from the source in the
61
+ :obj:`RabbitMQPublisher` will be used in the routing key, with a default
62
+ value taken from the :obj:`eiffellib.events.eiffel_base_event.EiffelBaseEvent`.
63
+
64
+ :param event: Event to send.
65
+ :type event: :obj:`eiffellib.events.eiffel_base_event.EiffelBaseEvent`
66
+ :param block: Set to True in order to block for channel to become ready.
67
+ Default: True
68
+ :type block: bool
69
+ """
70
+ if block:
71
+ self.wait_start()
72
+ while self._channel is None or not self._channel.is_open:
73
+ time.sleep(0.1)
74
+
75
+ properties = BasicProperties(
76
+ content_type="application/json", delivery_mode=2, headers={}, type=self.destination
77
+ )
78
+
79
+ source = deepcopy(self.source)
80
+ if self.routing_key is None and event.domain_id != EiffelBaseEvent.domain_id:
81
+ source = source or {}
82
+ source["domainId"] = event.domain_id
83
+ elif self.routing_key is None and source is not None:
84
+ # EiffelBaseEvent.domain_id will be the default value.
85
+ # By using that value instead of setting the default in this
86
+ # method there will only be one place to set the default (the events).
87
+ event.domain_id = source.get("domainId", EiffelBaseEvent.domain_id)
88
+ if source is not None:
89
+ event.meta.add("source", source)
90
+ event.validate()
91
+ routing_key = self.routing_key or event.routing_key
92
+
93
+ task_name = f"{self.exchange if self.exchange else routing_key} send"
94
+ span = self.tracer.start_span(
95
+ name=task_name,
96
+ kind=SpanKind.PRODUCER,
97
+ )
98
+ if span.is_recording():
99
+ add_span_attributes(
100
+ span,
101
+ self._channel,
102
+ properties,
103
+ routing_key,
104
+ MessagingOperationValues.PUBLISH,
105
+ )
106
+ add_span_eiffel_attributes(span, event)
107
+
108
+ _LOG.debug("[%s] Attempting to acquire 'send_event' lock", current_thread().name)
109
+ # Pylint is wrong.. pylint:disable=not-context-manager
110
+ with self._lock, trace.use_span(span, end_on_exit=True) as _span:
111
+ _LOG.debug("[%s] 'send_event' Lock acquired", current_thread().name)
112
+ propagate.inject(properties.headers)
113
+ if properties.headers == {}: # Tracing is not enabled?
114
+ properties.headers = None
115
+ try:
116
+ self._channel.basic_publish(
117
+ self.exchange,
118
+ routing_key,
119
+ event.serialized,
120
+ properties,
121
+ )
122
+ _span.add_event("Published event", attributes=add_event(event))
123
+ except Exception as exception: # pylint:disable=broad-except
124
+ self._nacked_deliveries.append(event)
125
+ _span.record_exception(exception, escaped=True)
126
+ return
127
+ self._delivered += 1
128
+ self._deliveries[self._delivered] = event
129
+ _LOG.debug("[%s] 'send_event' Lock released", current_thread().name)
@@ -0,0 +1,171 @@
1
+ # Copyright Axis Communications AB.
2
+ #
3
+ # For a full list of individual contributors, please see the commit history.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ """Custom subscribers for eiffellib."""
17
+ import functools
18
+ import json
19
+ import logging
20
+ import traceback
21
+ from typing import Optional
22
+
23
+ import eiffellib.events
24
+ from eiffellib.events.eiffel_base_event import EiffelBaseEvent
25
+ from eiffellib.subscribers.rabbitmq_subscriber import RabbitMQSubscriber
26
+ from opentelemetry import context, propagate, trace
27
+ from opentelemetry.propagators.textmap import CarrierT, Getter
28
+ from opentelemetry.semconv.trace import MessagingOperationValues
29
+ from opentelemetry.trace import SpanKind
30
+ from pika.spec import Basic, BasicProperties
31
+
32
+ from .common import add_span_attributes, add_span_eiffel_attributes
33
+
34
+ _LOG = logging.getLogger(__name__)
35
+
36
+
37
+ class _Getter(Getter[CarrierT]): # type: ignore
38
+ """Getter for receiving a key from amqp headers."""
39
+
40
+ def get(self, carrier: CarrierT, key: str) -> Optional[list[str]]:
41
+ """Get a key from headers."""
42
+ value = carrier.get(key, None)
43
+ if value is None:
44
+ return None
45
+ return [value]
46
+
47
+ def keys(self, carrier: CarrierT) -> list[str]:
48
+ """Return an empy list of keys."""
49
+ return []
50
+
51
+
52
+ _GETTER = _Getter()
53
+
54
+
55
+ class TracingRabbitMQSubscriber(RabbitMQSubscriber):
56
+ """Custom RabbitMQ subscriber that gets otel trace information to headers."""
57
+
58
+ def __init__(self, *args, **kwargs):
59
+ """Get a trace."""
60
+ # Must import this here, otherwise there would be a cyclic import problem.
61
+ # pylint:disable=cyclic-import,import-outside-toplevel
62
+ from etos_lib import __version__
63
+
64
+ super().__init__(*args, **kwargs)
65
+ self.tracer = trace.get_tracer(
66
+ __name__,
67
+ __version__,
68
+ schema_url="https://opentelemetry.io/schemas/1.11.0",
69
+ )
70
+
71
+ def _on_message(
72
+ self, _, method: Basic.Deliver, properties: BasicProperties, body: bytes
73
+ ) -> None:
74
+ """On message callback. Called on each message. Will block if no place in queue.
75
+
76
+ For each message attempt to acquire the `threading.Semaphore`. The semaphore
77
+ size is `max_threads` + `max_queue`. This is to limit the amount of threads
78
+ in the queue, waiting to be processed.
79
+ For each message apply them async to a `ThreadPool` with size=`max_threads`.
80
+
81
+ :param method: Pika basic deliver object.
82
+ :param properties: Pika basic properties object.
83
+ :param body: Message body.
84
+ """
85
+ self._RabbitMQSubscriber__workers.acquire() # pylint:disable=no-member
86
+ delivery_tag = method.delivery_tag
87
+ error_callback = functools.partial(self.callback_error, delivery_tag)
88
+ result_callback = functools.partial(self.callback_results, delivery_tag)
89
+ self._RabbitMQSubscriber__thread_pool.apply_async( # pylint:disable=no-member
90
+ self._tracer_call,
91
+ args=(body, method, properties),
92
+ callback=result_callback,
93
+ error_callback=error_callback,
94
+ )
95
+
96
+ def _tracer_call(
97
+ self, body: bytes, method: Basic.Deliver, properties: BasicProperties
98
+ ) -> tuple[bool, bool]:
99
+ """Tracing callback for the custom subscriber that extracts a trace from amq headers."""
100
+ if not properties:
101
+ properties = BasicProperties(headers={})
102
+ if properties.headers is None:
103
+ properties.headers = {}
104
+ ctx = propagate.extract(properties.headers, getter=_GETTER)
105
+ if not ctx:
106
+ ctx = context.get_current()
107
+ token = context.attach(ctx)
108
+
109
+ task_name = f"{method.exchange if method.exchange else self.routing_key} receive"
110
+ span = self.tracer.start_span(
111
+ name=task_name,
112
+ kind=SpanKind.CONSUMER,
113
+ )
114
+ if span.is_recording():
115
+ add_span_attributes(
116
+ span,
117
+ self._channel,
118
+ properties,
119
+ self.routing_key,
120
+ MessagingOperationValues.RECEIVE,
121
+ self.queue,
122
+ )
123
+ try:
124
+ event = self._event(body)
125
+ except: # pylint:disable=bare-except
126
+ # Pylint is wrong.. pylint:disable=not-context-manager
127
+ with trace.use_span(span, end_on_exit=True) as span:
128
+ raise
129
+ if span.is_recording():
130
+ add_span_eiffel_attributes(span, event)
131
+ try:
132
+ # Pylint is wrong.. pylint:disable=not-context-manager
133
+ with trace.use_span(span, end_on_exit=True):
134
+ response = self._event_call(event)
135
+ finally:
136
+ context.detach(token)
137
+ return response
138
+
139
+ def _event_call(self, event: EiffelBaseEvent) -> tuple[bool, bool]:
140
+ """Call followers and subscribers of an event."""
141
+ try:
142
+ ack = self._call_subscribers(event.meta.type, event)
143
+ self._call_followers(event)
144
+ except: # noqa, pylint:disable=bare-except
145
+ _LOG.error(
146
+ "Caught exception while processing subscriber "
147
+ "callbacks, some callbacks may not have been called: %s",
148
+ traceback.format_exc(),
149
+ )
150
+ ack = False
151
+ return ack, True # Requeue only if ack is False.
152
+
153
+ def _event(self, body: bytes) -> EiffelBaseEvent:
154
+ """Rebuild event."""
155
+ # pylint:disable=broad-exception-raised
156
+ try:
157
+ json_data = json.loads(body.decode("utf-8"))
158
+ except (json.decoder.JSONDecodeError, UnicodeDecodeError) as err:
159
+ raise Exception(
160
+ f"Unable to deserialize message body ({err}), rejecting: {body!r}"
161
+ ) from err
162
+ try:
163
+ meta_type = json_data.get("meta", {}).get("type")
164
+ event = getattr(eiffellib.events, meta_type)(json_data.get("meta", {}).get("version"))
165
+ except (AttributeError, TypeError) as err:
166
+ raise Exception(f"Malformed message. Rejecting: {json_data!r}") from err
167
+ try:
168
+ event.rebuild(json_data)
169
+ except Exception as err: # pylint:disable=broad-except
170
+ raise Exception(f"Unable to deserialize message ({err}): {json_data!r}") from err
171
+ return event
etos_lib/etos.py ADDED
@@ -0,0 +1,146 @@
1
+ # Copyright 2020-2021 Axis Communications AB.
2
+ #
3
+ # For a full list of individual contributors, please see the commit history.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ """ETOS Library module."""
17
+ from .eiffel.publisher import TracingRabbitMQPublisher as RabbitMQPublisher
18
+ from .eiffel.subscriber import TracingRabbitMQSubscriber as RabbitMQSubscriber
19
+ from .graphql.query_handler import GraphQLQueryHandler
20
+ from .lib.config import Config
21
+ from .lib.database import Database
22
+ from .lib.debug import Debug
23
+ from .lib.events import Events
24
+ from .lib.exceptions import (
25
+ PublisherConfigurationMissing,
26
+ PublisherNotStarted,
27
+ SubscriberConfigurationMissing,
28
+ )
29
+ from .lib.feature_flags import FeatureFlags
30
+ from .lib.http import Http
31
+ from .lib.monitor import Monitor
32
+ from .lib.utils import Utils
33
+
34
+
35
+ class ETOS: # pylint: disable=too-many-instance-attributes
36
+ """ETOS Library."""
37
+
38
+ publisher = None
39
+ subscriber = None
40
+ __config = None
41
+ __events = None
42
+ __monitor = None
43
+ __utils = None
44
+ __graphql = None
45
+ __http = None
46
+ __debug = None
47
+ __feature_flags = None
48
+ __database = None
49
+
50
+ def __init__(self, service_name, host, name, domain_id=None):
51
+ """Initialize source and service name."""
52
+ source = {"name": name, "host": host}
53
+ if domain_id is not None:
54
+ source["domainId"] = domain_id
55
+ self.config.set("source", source)
56
+ self.config.set("service_name", service_name)
57
+
58
+ def __del__(self):
59
+ """Delete references to eiffel publisher and subscriber."""
60
+ self.config.set("publisher", None)
61
+ self.config.set("subscriber", None)
62
+
63
+ def start_publisher(self):
64
+ """Start the RabbitMQ publisher using config data from ETOS library config service."""
65
+ rabbitmq = self.config.get("rabbitmq_publisher")
66
+ if not rabbitmq:
67
+ raise PublisherConfigurationMissing
68
+ self.publisher = RabbitMQPublisher(routing_key=None, **rabbitmq)
69
+ if not self.debug.disable_sending_events:
70
+ self.publisher.start()
71
+ self.config.set("publisher", self.publisher)
72
+
73
+ def start_subscriber(self):
74
+ """Start the RabbitMQ subscriber using config data from ETOS library config service."""
75
+ rabbitmq = self.config.get("rabbitmq_subscriber")
76
+ if not rabbitmq:
77
+ raise SubscriberConfigurationMissing
78
+ self.subscriber = RabbitMQSubscriber(**rabbitmq)
79
+ if not self.debug.disable_receiving_events:
80
+ self.subscriber.start()
81
+ self.config.set("subscriber", self.subscriber)
82
+
83
+ @property
84
+ def debug(self):
85
+ """Entry for debug parameters for ETOS."""
86
+ if self.__debug is None:
87
+ self.__debug = Debug()
88
+ return self.__debug
89
+
90
+ @property
91
+ def feature_flags(self):
92
+ """Entry for feature flags for ETOS."""
93
+ if self.__feature_flags is None:
94
+ self.__feature_flags = FeatureFlags()
95
+ return self.__feature_flags
96
+
97
+ @property
98
+ def monitor(self):
99
+ """Entry for ETOS Library monitor service."""
100
+ if self.__monitor is None:
101
+ self.__monitor = Monitor()
102
+ return self.__monitor
103
+
104
+ @property
105
+ def events(self):
106
+ """Entry for ETOS Library events service. Publisher must be started."""
107
+ if self.__events is None:
108
+ if self.publisher is None and not self.debug.disable_sending_events:
109
+ raise PublisherNotStarted
110
+ self.__events = Events(self.publisher)
111
+ return self.__events
112
+
113
+ @property
114
+ def config(self):
115
+ """Entry for ETOS Library config service."""
116
+ if self.__config is None:
117
+ self.__config = Config()
118
+ return self.__config
119
+
120
+ @property
121
+ def utils(self):
122
+ """Entry for ETOS Library utils service."""
123
+ if self.__utils is None:
124
+ self.__utils = Utils()
125
+ return self.__utils
126
+
127
+ @property
128
+ def http(self):
129
+ """Entry for ETOS Library http service."""
130
+ if self.__http is None:
131
+ self.__http = Http()
132
+ return self.__http
133
+
134
+ @property
135
+ def graphql(self):
136
+ """Entry for ETOS Library http service."""
137
+ if self.__graphql is None:
138
+ self.__graphql = GraphQLQueryHandler()
139
+ return self.__graphql
140
+
141
+ @property
142
+ def database(self):
143
+ """Entry to ETOS Library database service."""
144
+ if self.__database is None:
145
+ self.__database = Database()
146
+ return self.__database
@@ -0,0 +1,16 @@
1
+ # Copyright 2020 Axis Communications AB.
2
+ #
3
+ # For a full list of individual contributors, please see the commit history.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ """ETOS library GraphQL helpers."""