Skip to content

Commit

Permalink
Typing fixes around loader
Browse files Browse the repository at this point in the history
Had to also make some small changes to the actual implementation of
`Configuration()`
  • Loading branch information
aabmass committed Jun 2, 2020
1 parent 24a564d commit b451332
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 55 deletions.
2 changes: 1 addition & 1 deletion dev-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ pylint==2.4.4
flake8==3.7.9
isort~=4.3
black>=19.3b0,==19.*
mypy==0.740
mypy==0.770
sphinx~=2.1
sphinx-rtd-theme~=0.4
sphinx-autodoc-typehints~=1.10.2
Expand Down
Empty file.
69 changes: 33 additions & 36 deletions opentelemetry-api/src/opentelemetry/configuration/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.

# FIXME find a better way to avoid all those "Expression has type "Any"" errors
# type: ignore

"""
Simple configuration manager
Expand Down Expand Up @@ -95,17 +92,23 @@

from os import environ
from re import fullmatch
from typing import Union, Type, Optional, Dict, TypeVar

ConfigValue = Union[str, bool, int, float]

class Configuration:
_instance = None

__slots__ = []
class Configuration:
_instance: Optional["Configuration"] = None
_config_map: Dict[str, ConfigValue]

def __new__(cls) -> "Configuration":
if Configuration._instance is None:
if cls._instance is not None:
instance = cls._instance
else:

for key, value in environ.items():
instance = super().__new__(cls)
instance._config_map = {}
for key, value_str in environ.items():

match = fullmatch(
r"OPENTELEMETRY_PYTHON_([A-Za-z_][\w_]*)", key
Expand All @@ -114,56 +117,50 @@ def __new__(cls) -> "Configuration":
if match is not None:

key = match.group(1)
value: ConfigValue = value_str

if value == "True":
if value_str == "True":
value = True
elif value == "False":
elif value_str == "False":
value = False
else:
try:
value = int(value)
value = int(value_str)
except ValueError:
pass
try:
value = float(value)
value = float(value_str)
except ValueError:
pass

setattr(Configuration, "_{}".format(key), value)
setattr(
Configuration,
key,
property(
fget=lambda cls, key=key: getattr(
cls, "_{}".format(key)
)
),
)
instance._config_map[key] = value

Configuration.__slots__.append(key)
cls._instance = instance

Configuration.__slots__ = tuple(Configuration.__slots__)
return instance

Configuration._instance = object.__new__(cls)
def __getattr__(self, name: str) -> Optional[ConfigValue]:
return self._config_map.get(name)

return cls._instance
def __setattr__(self, key: str, val: ConfigValue) -> None:
if key == "_config_map":
super().__setattr__(key, val)
else:
raise AttributeError(key)

def __getattr__(self, name):
return None
def get(self, name: str, default: ConfigValue) -> ConfigValue:
val = self._config_map.get(name, default)
return val

@classmethod
def _reset(cls):
def _reset(cls) -> None:
"""
This method "resets" the global configuration attributes
It is not intended to be used by production code but by testing code
only.
"""

for slot in cls.__slots__:
if slot in cls.__dict__.keys():
delattr(cls, slot)
delattr(cls, "_{}".format(slot))

cls.__slots__ = []
cls._instance = None
if cls._instance:
cls._instance._config_map.clear() # pylint: disable=protected-access
cls._instance = None
4 changes: 2 additions & 2 deletions opentelemetry-api/src/opentelemetry/metrics/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
from logging import getLogger
from typing import Callable, Dict, Sequence, Tuple, Type, TypeVar

from opentelemetry.util import _load_provider
from opentelemetry.util import _load_meter_provider

logger = getLogger(__name__)
ValueT = TypeVar("ValueT", int, float)
Expand Down Expand Up @@ -410,6 +410,6 @@ def get_meter_provider() -> MeterProvider:
global _METER_PROVIDER # pylint: disable=global-statement

if _METER_PROVIDER is None:
_METER_PROVIDER = _load_provider("meter_provider")
_METER_PROVIDER = _load_meter_provider("meter_provider")

return _METER_PROVIDER
4 changes: 2 additions & 2 deletions opentelemetry-api/src/opentelemetry/trace/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
from logging import getLogger

from opentelemetry.trace.status import Status
from opentelemetry.util import _load_provider, types
from opentelemetry.util import _load_trace_provider, types

logger = getLogger(__name__)

Expand Down Expand Up @@ -706,6 +706,6 @@ def get_tracer_provider() -> TracerProvider:
global _TRACER_PROVIDER # pylint: disable=global-statement

if _TRACER_PROVIDER is None:
_TRACER_PROVIDER = _load_provider("tracer_provider")
_TRACER_PROVIDER = _load_trace_provider("tracer_provider")

return _TRACER_PROVIDER
36 changes: 25 additions & 11 deletions opentelemetry-api/src/opentelemetry/util/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,17 @@
import re
import time
from logging import getLogger
from typing import Sequence, Union
from typing import Sequence, Union, cast, TYPE_CHECKING

from pkg_resources import iter_entry_points

from opentelemetry.configuration import Configuration # type: ignore
from opentelemetry.configuration import Configuration

if TYPE_CHECKING:
from opentelemetry.trace import TracerProvider
from opentelemetry.metrics import MeterProvider

Provider = Union["TracerProvider", "MeterProvider"]

logger = getLogger(__name__)

Expand All @@ -34,25 +40,33 @@ def time_ns() -> int:
return int(time.time() * 1e9)


def _load_provider(
provider: str,
) -> Union["TracerProvider", "MeterProvider"]: # type: ignore
def _load_provider(provider: str) -> Provider:
try:
return next( # type: ignore
entry_point = next(
iter_entry_points(
"opentelemetry_{}".format(provider),
name=getattr(
Configuration(), # type: ignore
provider,
"default_{}".format(provider),
name=cast(
str,
Configuration().get(
provider, "default_{}".format(provider),
),
),
)
).load()()
)
return cast(Provider, entry_point.load()(),)
except Exception: # pylint: disable=broad-except
logger.error("Failed to load configured provider %s", provider)
raise


def _load_meter_provider(provider: str) -> "MeterProvider":
return cast("MeterProvider", _load_provider(provider))


def _load_trace_provider(provider: str) -> "TracerProvider":
return cast("TracerProvider", _load_provider(provider))


# Pattern for matching up until the first '/' after the 'https://' part.
_URL_PATTERN = r"(https?|ftp)://.*?/"

Expand Down
6 changes: 3 additions & 3 deletions opentelemetry-api/tests/configuration/test_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,16 @@
from unittest import TestCase
from unittest.mock import patch

from opentelemetry.configuration import Configuration # type: ignore
from opentelemetry.configuration import Configuration


class TestConfiguration(TestCase):
def tearDown(self):
def tearDown(self) -> None:
# This call resets the attributes of the Configuration class so that
# each test is executed in the same conditions.
Configuration._reset()

def test_singleton(self):
def test_singleton(self) -> None:
self.assertIsInstance(Configuration(), Configuration)
self.assertIs(Configuration(), Configuration())

Expand Down

0 comments on commit b451332

Please sign in to comment.