From 6841423669525cccb49177d902be6de473491726 Mon Sep 17 00:00:00 2001 From: Diego Hurtado Date: Thu, 28 Oct 2021 11:40:41 +0200 Subject: [PATCH] Add safety prototype --- safety_prototype/.gitignore | 3 + safety_prototype/README.rst | 64 +++++++++++++++++++ safety_prototype/application.py | 35 ++++++++++ safety_prototype/opentelemetry-api/setup.cfg | 45 +++++++++++++ safety_prototype/opentelemetry-api/setup.py | 27 ++++++++ .../src/opentelemetry/_safety.py | 50 +++++++++++++++ .../src/opentelemetry/configuration.py | 41 ++++++++++++ .../src/opentelemetry/trace/__init__.py | 42 ++++++++++++ .../src/opentelemetry/trace/api.py | 44 +++++++++++++ .../src/opentelemetry/version.py | 15 +++++ safety_prototype/opentelemetry-sdk/setup.cfg | 51 +++++++++++++++ safety_prototype/opentelemetry-sdk/setup.py | 29 +++++++++ .../src/opentelemetry/sdk/__init__.pyi | 0 .../src/opentelemetry/sdk/trace/__init__.py | 37 +++++++++++ .../src/opentelemetry/sdk/version.py | 15 +++++ 15 files changed, 498 insertions(+) create mode 100644 safety_prototype/.gitignore create mode 100644 safety_prototype/README.rst create mode 100644 safety_prototype/application.py create mode 100644 safety_prototype/opentelemetry-api/setup.cfg create mode 100644 safety_prototype/opentelemetry-api/setup.py create mode 100644 safety_prototype/opentelemetry-api/src/opentelemetry/_safety.py create mode 100644 safety_prototype/opentelemetry-api/src/opentelemetry/configuration.py create mode 100644 safety_prototype/opentelemetry-api/src/opentelemetry/trace/__init__.py create mode 100644 safety_prototype/opentelemetry-api/src/opentelemetry/trace/api.py create mode 100644 safety_prototype/opentelemetry-api/src/opentelemetry/version.py create mode 100644 safety_prototype/opentelemetry-sdk/setup.cfg create mode 100644 safety_prototype/opentelemetry-sdk/setup.py create mode 100644 safety_prototype/opentelemetry-sdk/src/opentelemetry/sdk/__init__.pyi create mode 100644 safety_prototype/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py create mode 100644 safety_prototype/opentelemetry-sdk/src/opentelemetry/sdk/version.py diff --git a/safety_prototype/.gitignore b/safety_prototype/.gitignore new file mode 100644 index 00000000000..8f9ac9d84f1 --- /dev/null +++ b/safety_prototype/.gitignore @@ -0,0 +1,3 @@ +__pycache__ +*.egg +*.egg-info diff --git a/safety_prototype/README.rst b/safety_prototype/README.rst new file mode 100644 index 00000000000..e53019ee352 --- /dev/null +++ b/safety_prototype/README.rst @@ -0,0 +1,64 @@ +README +====== + +Overview +-------- + +This is a prototype to implement `error handling`_ in Python. + +The user only calls API functions or methods, never SDK functions or methods. + +Every SDK must implement every public function or method defined in the API +except for the SDK setting function. + +A safety mechanism is applied to every API function or method (except for the +SDK setting function or method) and only to every API function or method. + +This safety mechanism has 3 main components: + +1. A predefined value to be returned in case of an exception being raised. This + value is predefined and independently set for every function or method. +2. A `try` / `except` block that catches any exception raised when executing + the function or method code. +3. A Python `warning`_ that is "raised" if an exception is raised in the code + protected by the safety mechanism. + +The API provides a function to set a specific SDK. This function is +intentionally not protected by the safety mechanism mentioned above because the +specification `mentions`_ this: + + The API or SDK may fail fast and cause the application to fail on + initialization... + +*Initialization* is understood as the process of setting the SDK. + +When an API function or method is called without an SDK being set, a warning +will be raised and the predefined value of `None` will be returned. + +After an SDK is set, calling an API function or method will call its +corresponding SDJ function or method. Any exception raised by the SDK function +or method will be caught by the safety mechanism and the predefined value +returned instead. + +The Python warning that is "raised" when an exception is raised in the SDK +function or method can be transformed into a full exception by running the +Python interpreter with the `-W error` option. This Python feature is used to +satisfy this `specification requirement`_. + +How to run +---------- + +0. Create a virtual environment and activate it +1. Run ``pip install -e opentelemetry-api`` +2. Run ``pip install -e opentelemetry-sdk`` +3. Run ``python application.py`` +4. Run ``python -W error application.py`` + +Noice how even failed operations (divisions by zero) don't crash the +application in step 3, but they do in step 4. + + +.. _error handling: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/error-handling.md +.. _warning: https://docs.python.org/3/library/warnings.html +.. _specification requirement: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/error-handling.md#configuring-error-handlers +.. _mentions: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/error-handling.md#basic-error-handling-principles diff --git a/safety_prototype/application.py b/safety_prototype/application.py new file mode 100644 index 00000000000..5e673a13d92 --- /dev/null +++ b/safety_prototype/application.py @@ -0,0 +1,35 @@ +# Only API objects are imported because the user only calls API functions or +# methods. +from opentelemetry.configuration import set_sdk +from opentelemetry.trace import function, Class0 + +# This is the function that sets the SDK. After this is set, any call to an API +# function or method will end up calling its corresponding SDK function or +# method. +set_sdk("sdk") + +# function returns the result of dividing its first argument by its second +# argument. + +# This does not raise an exception, the resulting value of 2.0 is returned. +print(function(4, 2)) + +# This is a division by zero, it raises an exception, the safety mechanism +# catches it and returns the predefined value of 0.0. +print(function(1, 0)) + + +# The class argument is stored in the SDK instance and method uses it to +# multiply the result of the division of it first argument by the second before +# returning the resulting value. +class0 = Class0(2) + +# Class0.method returns the result of dividing its first argument by its second +# argument. + +# This does not raise an exception, the resulting value of 4.0 is returned. +print(class0.method(4, 2)) + +# This is a division by zero, it raises an exception, the safety mechanism +# catches it and returns the predefined value of 0.0. +print(class0.method(1, 0)) diff --git a/safety_prototype/opentelemetry-api/setup.cfg b/safety_prototype/opentelemetry-api/setup.cfg new file mode 100644 index 00000000000..c029bf97be3 --- /dev/null +++ b/safety_prototype/opentelemetry-api/setup.cfg @@ -0,0 +1,45 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +[metadata] +name = opentelemetry-api +description = OpenTelemetry Python API +author = OpenTelemetry Authors +author_email = cncf-opentelemetry-contributors@lists.cncf.io +url = https://github.com/open-telemetry/opentelemetry-python/tree/main/opentelemetry-api +platforms = any +license = Apache-2.0 +classifiers = + Development Status :: 5 - Production/Stable + Intended Audience :: Developers + License :: OSI Approved :: Apache Software License + Programming Language :: Python + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 + Typing :: Typed + +[options] +python_requires = >=3.6 +package_dir= + =src +packages=find_namespace: +zip_safe = False +include_package_data = True + +[options.packages.find] +where = src diff --git a/safety_prototype/opentelemetry-api/setup.py b/safety_prototype/opentelemetry-api/setup.py new file mode 100644 index 00000000000..aac1579996d --- /dev/null +++ b/safety_prototype/opentelemetry-api/setup.py @@ -0,0 +1,27 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + +import setuptools + +BASE_DIR = os.path.dirname(__file__) +VERSION_FILENAME = os.path.join(BASE_DIR, "src", "opentelemetry", "version.py") +PACKAGE_INFO = {} +with open(VERSION_FILENAME, encoding="utf-8") as f: + exec(f.read(), PACKAGE_INFO) + +setuptools.setup( + version=PACKAGE_INFO["__version__"], +) diff --git a/safety_prototype/opentelemetry-api/src/opentelemetry/_safety.py b/safety_prototype/opentelemetry-api/src/opentelemetry/_safety.py new file mode 100644 index 00000000000..8ba87aa6c3e --- /dev/null +++ b/safety_prototype/opentelemetry-api/src/opentelemetry/_safety.py @@ -0,0 +1,50 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from functools import wraps +from warnings import warn, resetwarnings +from traceback import format_exception +from sys import exc_info + + +def _safe_function(predefined_return_value): + """ + This is the safety mechanism mentioned in the README file. + + This is used as a decorator on every function or method in the API. + """ + + + def internal(function): + @wraps(function) + def wrapper(*args, **kwargs): + + # This is the try / except block mentioned in the README file. + try: + exception = None + return function(*args, **kwargs) + except Exception: # pylint: disable=broad-except + exception = "".join(format_exception(*exc_info())) + + if exception is not None: + # This is the warning mentioned in the README file. + warn(f"OpenTelemetry handled an exception:\n\n{exception}") + exception = None + resetwarnings() + + return predefined_return_value + + return wrapper + + return internal diff --git a/safety_prototype/opentelemetry-api/src/opentelemetry/configuration.py b/safety_prototype/opentelemetry-api/src/opentelemetry/configuration.py new file mode 100644 index 00000000000..3aa849fc0ae --- /dev/null +++ b/safety_prototype/opentelemetry-api/src/opentelemetry/configuration.py @@ -0,0 +1,41 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from pkg_resources import iter_entry_points +from importlib import import_module + +_sdk = None + + +def set_sdk(sdk_name: str) -> None: + """ + This is the SDK setting function mentioned in the README file. + + It is intentionally not protected to make it possible for the application + to fail fast if it was not possible to set the SDK. In an actual + implementation of this prototype, the SDK setting mechanism may take + different forms to take into consideration that this SDK setting process + may happen also in auto instrumentation. This means that SDK setting may + also happen by using environment variables or other form of configuration. + """ + global _sdk + + if _sdk is None: + _sdk = next( + iter_entry_points("opentelemetry_sdk", name=sdk_name) + ).load().__package__ + + +def _get_sdk_module(path: str) -> object: + return import_module(".".join([_sdk, path])) diff --git a/safety_prototype/opentelemetry-api/src/opentelemetry/trace/__init__.py b/safety_prototype/opentelemetry-api/src/opentelemetry/trace/__init__.py new file mode 100644 index 00000000000..c4f2e99cd7a --- /dev/null +++ b/safety_prototype/opentelemetry-api/src/opentelemetry/trace/__init__.py @@ -0,0 +1,42 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +This an implementation of the API where every function or method is safe. + +Any call to a function or method defined here passes its arguments to a +corresponding underlying SDK function or method. In this way, objects defined +in this module act as proxies and (with the SDK setting function) are the only +objects the user has contact with. +""" + +from opentelemetry._safety import _safe_function +from opentelemetry.configuration import _get_sdk_module +from opentelemetry.trace.api import Class0 + + +@_safe_function(0.0) +def function(a: int, b: int) -> float: + return _get_sdk_module("trace").function(a, b) + + +class Class0(Class0): + + @_safe_function(None) + def __init__(self, a: int) -> None: + self._sdk_instance = _get_sdk_module("trace").Class0(a) + + @_safe_function(0.0) + def method(self, a: int, b: int) -> float: + return self._sdk_instance.method(a, b) diff --git a/safety_prototype/opentelemetry-api/src/opentelemetry/trace/api.py b/safety_prototype/opentelemetry-api/src/opentelemetry/trace/api.py new file mode 100644 index 00000000000..be4f67de475 --- /dev/null +++ b/safety_prototype/opentelemetry-api/src/opentelemetry/trace/api.py @@ -0,0 +1,44 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +This is the API. + +This module includes all functions and classes with their respective methods +that an SDK must implement. SDKs must implement the contents of this module +exactly, with the same names and signatures for functions and methods. This is +necessary because the API proxy objects will call the SDK functions and methods +in the exact same way as they are defined here. +""" + +from abc import ABC, abstractmethod + + +# There is no way to mandate the implementation of a function in a Python +# module, so this is added to inform SDK implementations that this function is +# to be implemented. A mechanism that checks SDK compliance can be implemented +# as well and it can use the contents of this module to check SDKs. +def function(a: int, b: int) -> float: + pass + + +class Class0(ABC): + + @abstractmethod + def __init__(self, a: int) -> None: + pass + + @abstractmethod + def method(self, a: int, b: int) -> float: + pass diff --git a/safety_prototype/opentelemetry-api/src/opentelemetry/version.py b/safety_prototype/opentelemetry-api/src/opentelemetry/version.py new file mode 100644 index 00000000000..ce2cf11c1f9 --- /dev/null +++ b/safety_prototype/opentelemetry-api/src/opentelemetry/version.py @@ -0,0 +1,15 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__version__ = "1.6.2" diff --git a/safety_prototype/opentelemetry-sdk/setup.cfg b/safety_prototype/opentelemetry-sdk/setup.cfg new file mode 100644 index 00000000000..96381da29d2 --- /dev/null +++ b/safety_prototype/opentelemetry-sdk/setup.cfg @@ -0,0 +1,51 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +[metadata] +name = opentelemetry-sdk +description = OpenTelemetry Python SDK +long_description = file: README.rst +long_description_content_type = text/x-rst +author = OpenTelemetry Authors +author_email = cncf-opentelemetry-contributors@lists.cncf.io +url = https://github.com/open-telemetry/opentelemetry-python/tree/main/opentelemetry-sdk +platforms = any +license = Apache-2.0 +classifiers = + Development Status :: 5 - Production/Stable + Intended Audience :: Developers + License :: OSI Approved :: Apache Software License + Programming Language :: Python + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 + Typing :: Typed + +[options] +python_requires = >=3.6 +package_dir= + =src +packages=find_namespace: +zip_safe = False +include_package_data = True + +[options.packages.find] +where = src + +[options.entry_points] +opentelemetry_sdk = + sdk = opentelemetry.sdk diff --git a/safety_prototype/opentelemetry-sdk/setup.py b/safety_prototype/opentelemetry-sdk/setup.py new file mode 100644 index 00000000000..61fc07cbbf4 --- /dev/null +++ b/safety_prototype/opentelemetry-sdk/setup.py @@ -0,0 +1,29 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + +import setuptools + +BASE_DIR = os.path.dirname(__file__) +VERSION_FILENAME = os.path.join( + BASE_DIR, "src", "opentelemetry", "sdk", "version.py" +) +PACKAGE_INFO = {} +with open(VERSION_FILENAME, encoding="utf-8") as f: + exec(f.read(), PACKAGE_INFO) + +setuptools.setup( + version=PACKAGE_INFO["__version__"], +) diff --git a/safety_prototype/opentelemetry-sdk/src/opentelemetry/sdk/__init__.pyi b/safety_prototype/opentelemetry-sdk/src/opentelemetry/sdk/__init__.pyi new file mode 100644 index 00000000000..e69de29bb2d diff --git a/safety_prototype/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/safety_prototype/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py new file mode 100644 index 00000000000..1bc54da21a3 --- /dev/null +++ b/safety_prototype/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py @@ -0,0 +1,37 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +This is the SDK. + +Functions and methods are implemented here, their execution may cause +exceptions to be raised. SDK implementations may also raise exceptions +intentionally. Any exception raised in one of these functions or methods will +be caught by the safety mechanism in the API. +""" + +from opentelemetry.trace.api import Class0 + + +def function(a: int, b: int) -> float: + return a / b + + +class Class0(Class0): + + def __init__(self, a: int) -> None: + self._a = a + + def method(self, a: int, b: int) -> float: + return self._a * (a / b) diff --git a/safety_prototype/opentelemetry-sdk/src/opentelemetry/sdk/version.py b/safety_prototype/opentelemetry-sdk/src/opentelemetry/sdk/version.py new file mode 100644 index 00000000000..ce2cf11c1f9 --- /dev/null +++ b/safety_prototype/opentelemetry-sdk/src/opentelemetry/sdk/version.py @@ -0,0 +1,15 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__version__ = "1.6.2"