diff --git a/docs/index.rst b/docs/index.rst index c597d4a681f..ee00e08a574 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -16,6 +16,7 @@ and integration packages. :caption: OpenTelemetry API: opentelemetry.context + opentelemetry.patcher opentelemetry.metrics opentelemetry.trace opentelemetry.util.loader diff --git a/docs/opentelemetry.patcher.rst b/docs/opentelemetry.patcher.rst new file mode 100644 index 00000000000..622b664310c --- /dev/null +++ b/docs/opentelemetry.patcher.rst @@ -0,0 +1,8 @@ +opentelemetry.patcher package +============================= + + +Module contents +--------------- + +.. automodule:: opentelemetry.patcher diff --git a/ext/opentelemetry-ext-flask/src/opentelemetry/ext/flask/__init__.py b/ext/opentelemetry-ext-flask/src/opentelemetry/ext/flask/__init__.py index 5bae2b9df9b..104060e1f39 100644 --- a/ext/opentelemetry-ext-flask/src/opentelemetry/ext/flask/__init__.py +++ b/ext/opentelemetry-ext-flask/src/opentelemetry/ext/flask/__init__.py @@ -8,7 +8,7 @@ import opentelemetry.ext.wsgi as otel_wsgi from opentelemetry import propagators, trace from opentelemetry.ext.flask.version import __version__ -from opentelemetry.patcher.base_patcher import BasePatcher +from opentelemetry.patcher import BasePatcher from opentelemetry.util import time_ns logger = getLogger(__name__) @@ -105,7 +105,10 @@ def _teardown_flask_request(exc): @BasePatcher.protect class FlaskPatcher(BasePatcher): - """A patcher for flask.Flask""" + """A patcher for flask.Flask + + See `BasePatcher` + """ def __init__(self): self._patched = False diff --git a/opentelemetry-api/src/opentelemetry/patcher/__init__.py b/opentelemetry-api/src/opentelemetry/patcher/__init__.py index 9d38e3b7e15..fad4a710e24 100644 --- a/opentelemetry-api/src/opentelemetry/patcher/__init__.py +++ b/opentelemetry-api/src/opentelemetry/patcher/__init__.py @@ -11,9 +11,98 @@ # 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. +# type: ignore """ OpenTelemetry patcher This includes the base patcher class and a no-op implementation. """ + +from abc import ABC, abstractmethod +from functools import wraps +from logging import getLogger + +_LOG = getLogger(__name__) + + +class BasePatcher(ABC): + """An ABC for patchers""" + + @staticmethod + def protect(class_) -> None: + """ + Provides a class decorator that protects patch and unpatch methods + + A protected patch method can't be called again until its corresponding + unpatch method has been called and vice versa. A protected patch method + must be called first before its corresponding unpatch method can be + called. + + To use this decorator simply decorate the patcher class: + + .. code-block:: python + + from opentelemetry.patcher.base_patcher import BasePatcher + + @BasePatcher.protect + class PatcherClass(BasePatcher): + ... + """ + + # pylint: disable=protected-access + + class_._unprotected_patch = class_.patch + class_._unprotected_patch._protected = False + + @wraps(class_.patch) + def protected_patch(self): + if not self._unprotected_patch.__func__._protected: + self._unprotected_patch.__func__._protected = True + self._unprotected_unpatch.__func__._protected = False + + return self._unprotected_patch() + + _LOG.warning("Attempting to patch while already patched") + + return None + + class_.patch = protected_patch + + class_._unprotected_unpatch = class_.unpatch + class_._unprotected_unpatch._protected = True + + @wraps(class_.unpatch) + def protected_unpatch(self): + if not self._unprotected_unpatch.__func__._protected: + self._unprotected_unpatch.__func__._protected = True + self._unprotected_patch.__func__._protected = False + + return self._unprotected_unpatch() + + _LOG.warning("Attempting to unpatch while already unpatched") + + return None + + class_.unpatch = protected_unpatch + + return class_ + + @abstractmethod + def patch(self) -> None: + """Patch""" + + @abstractmethod + def unpatch(self) -> None: + """Unpatch""" + + +class NoOpPatcher(BasePatcher): + def patch(self) -> None: + """Patch""" + + def unpatch(self) -> None: + """Unpatch""" + + +__all__ = ["BasePatcher", "NoOpPatcher"] diff --git a/opentelemetry-api/src/opentelemetry/patcher/base_patcher.py b/opentelemetry-api/src/opentelemetry/patcher/base_patcher.py deleted file mode 100644 index 04560dd3427..00000000000 --- a/opentelemetry-api/src/opentelemetry/patcher/base_patcher.py +++ /dev/null @@ -1,94 +0,0 @@ -# Copyright 2019, 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. -# type: ignore - -from abc import ABC, abstractmethod -from functools import wraps -from logging import getLogger - -_LOG = getLogger(__name__) - - -class BasePatcher(ABC): - """An ABC for patchers""" - - @staticmethod - def protect(class_) -> None: - """ - Provides a class decorator that protects patch and unpatch methods - - A protected patch method can't be called again until its corresponding - unpatch method has been called and vice versa. A protected patch method - must be called first before its corresponding unpatch method can be - called. - - To use this decorator simply decorate the patcher class: - - .. code-block:: python - - from opentelemetry.patcher.base_patcher import BasePatcher - - @BasePatcher.protect - class PatcherClass(BasePatcher): - ... - """ - - # pylint: disable=protected-access - - class_._unprotected_patch = class_.patch - class_._unprotected_patch._protected = False - - @wraps(class_.patch) - def protected_patch(self): - if not self._unprotected_patch.__func__._protected: - self._unprotected_patch.__func__._protected = True - self._unprotected_unpatch.__func__._protected = False - - return self._unprotected_patch() - - _LOG.warning("Attempting to patch while already patched") - - return None - - class_.patch = protected_patch - - class_._unprotected_unpatch = class_.unpatch - class_._unprotected_unpatch._protected = True - - @wraps(class_.unpatch) - def protected_unpatch(self): - if not self._unprotected_unpatch.__func__._protected: - self._unprotected_unpatch.__func__._protected = True - self._unprotected_patch.__func__._protected = False - - return self._unprotected_unpatch() - - _LOG.warning("Attempting to unpatch while already unpatched") - - return None - - class_.unpatch = protected_unpatch - - return class_ - - @abstractmethod - def patch(self) -> None: - """Patch""" - - @abstractmethod - def unpatch(self) -> None: - """Unpatch""" - - -__all__ = ["BasePatcher"] diff --git a/opentelemetry-api/src/opentelemetry/patcher/no_op_patcher.py b/opentelemetry-api/src/opentelemetry/patcher/no_op_patcher.py deleted file mode 100644 index 3791778778b..00000000000 --- a/opentelemetry-api/src/opentelemetry/patcher/no_op_patcher.py +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright 2019, 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 opentelemetry.patcher.base_patcher import BasePatcher # type: ignore - - -class NoOpPatcher(BasePatcher): # type: ignore - def patch(self) -> None: - """Patch""" - - def unpatch(self) -> None: - """Unpatch""" - - -__all__ = ["NoOpPatcher"] diff --git a/opentelemetry-api/tests/patcher/test_patcher.py b/opentelemetry-api/tests/patcher/test_patcher.py index 1c46a910e03..a1f1874c86e 100644 --- a/opentelemetry-api/tests/patcher/test_patcher.py +++ b/opentelemetry-api/tests/patcher/test_patcher.py @@ -15,7 +15,7 @@ from unittest import TestCase -from opentelemetry.patcher.base_patcher import BasePatcher +from opentelemetry.patcher import BasePatcher class TestSampler(TestCase):