Skip to content

Commit 20cf4cb

Browse files
authored
refactor: Typing fixes #768, upgrade mypy to 0.770 (#769)
1 parent b108596 commit 20cf4cb

File tree

7 files changed

+83
-63
lines changed

7 files changed

+83
-63
lines changed

dev-requirements.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ pylint==2.4.4
22
flake8==3.7.9
33
isort~=4.3
44
black>=19.3b0,==19.*
5-
mypy==0.740
5+
mypy==0.770
66
sphinx~=2.1
77
sphinx-rtd-theme~=0.4
88
sphinx-autodoc-typehints~=1.10.2

opentelemetry-api/src/opentelemetry/__init__.pyi

Whitespace-only changes.

opentelemetry-api/src/opentelemetry/configuration/__init__.py

+37-36
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
# FIXME find a better way to avoid all those "Expression has type "Any"" errors
16-
# type: ignore
17-
1815
"""
1916
Simple configuration manager
2017
@@ -95,17 +92,23 @@
9592

9693
from os import environ
9794
from re import fullmatch
95+
from typing import ClassVar, Dict, Optional, TypeVar, Union
9896

97+
ConfigValue = Union[str, bool, int, float]
98+
_T = TypeVar("_T", ConfigValue, Optional[ConfigValue])
9999

100-
class Configuration:
101-
_instance = None
102100

103-
__slots__ = []
101+
class Configuration:
102+
_instance = None # type: ClassVar[Optional[Configuration]]
103+
_config_map = {} # type: ClassVar[Dict[str, ConfigValue]]
104104

105105
def __new__(cls) -> "Configuration":
106-
if Configuration._instance is None:
106+
if cls._instance is not None:
107+
instance = cls._instance
108+
else:
107109

108-
for key, value in environ.items():
110+
instance = super().__new__(cls)
111+
for key, value_str in environ.items():
109112

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

116119
key = match.group(1)
120+
value = value_str # type: ConfigValue
117121

118-
if value == "True":
122+
if value_str == "True":
119123
value = True
120-
elif value == "False":
124+
elif value_str == "False":
121125
value = False
122126
else:
123127
try:
124-
value = int(value)
128+
value = int(value_str)
125129
except ValueError:
126130
pass
127131
try:
128-
value = float(value)
132+
value = float(value_str)
129133
except ValueError:
130134
pass
131135

132-
setattr(Configuration, "_{}".format(key), value)
133-
setattr(
134-
Configuration,
135-
key,
136-
property(
137-
fget=lambda cls, key=key: getattr(
138-
cls, "_{}".format(key)
139-
)
140-
),
141-
)
136+
instance._config_map[key] = value
142137

143-
Configuration.__slots__.append(key)
138+
cls._instance = instance
144139

145-
Configuration.__slots__ = tuple(Configuration.__slots__)
140+
return instance
146141

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

149-
return cls._instance
145+
def __setattr__(self, key: str, val: ConfigValue) -> None:
146+
if key == "_config_map":
147+
super().__setattr__(key, val)
148+
else:
149+
raise AttributeError(key)
150150

151-
def __getattr__(self, name):
152-
return None
151+
def get(self, name: str, default: _T) -> _T:
152+
"""Use this typed method for dynamic access instead of `getattr`
153+
154+
:rtype: str or bool or int or float or None
155+
"""
156+
val = self._config_map.get(name, default)
157+
return val
153158

154159
@classmethod
155-
def _reset(cls):
160+
def _reset(cls) -> None:
156161
"""
157162
This method "resets" the global configuration attributes
158163
159164
It is not intended to be used by production code but by testing code
160165
only.
161166
"""
162167

163-
for slot in cls.__slots__:
164-
if slot in cls.__dict__.keys():
165-
delattr(cls, slot)
166-
delattr(cls, "_{}".format(slot))
167-
168-
cls.__slots__ = []
169-
cls._instance = None
168+
if cls._instance:
169+
cls._instance._config_map.clear() # pylint: disable=protected-access
170+
cls._instance = None

opentelemetry-api/src/opentelemetry/metrics/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
from logging import getLogger
3232
from typing import Callable, Dict, Optional, Sequence, Tuple, Type, TypeVar
3333

34-
from opentelemetry.util import _load_provider
34+
from opentelemetry.util import _load_meter_provider
3535

3636
logger = getLogger(__name__)
3737
ValueT = TypeVar("ValueT", int, float)
@@ -395,6 +395,6 @@ def get_meter_provider() -> MeterProvider:
395395
global _METER_PROVIDER # pylint: disable=global-statement
396396

397397
if _METER_PROVIDER is None:
398-
_METER_PROVIDER = _load_provider("meter_provider")
398+
_METER_PROVIDER = _load_meter_provider("meter_provider")
399399

400400
return _METER_PROVIDER

opentelemetry-api/src/opentelemetry/trace/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@
7878
from logging import getLogger
7979

8080
from opentelemetry.trace.status import Status
81-
from opentelemetry.util import _load_provider, types
81+
from opentelemetry.util import _load_trace_provider, types
8282

8383
logger = getLogger(__name__)
8484

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

708708
if _TRACER_PROVIDER is None:
709-
_TRACER_PROVIDER = _load_provider("tracer_provider")
709+
_TRACER_PROVIDER = _load_trace_provider("tracer_provider")
710710

711711
return _TRACER_PROVIDER

opentelemetry-api/src/opentelemetry/util/__init__.py

+25-11
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,17 @@
1414
import re
1515
import time
1616
from logging import getLogger
17-
from typing import Sequence, Union
17+
from typing import TYPE_CHECKING, Sequence, Union, cast
1818

1919
from pkg_resources import iter_entry_points
2020

21-
from opentelemetry.configuration import Configuration # type: ignore
21+
from opentelemetry.configuration import Configuration
22+
23+
if TYPE_CHECKING:
24+
from opentelemetry.trace import TracerProvider
25+
from opentelemetry.metrics import MeterProvider
26+
27+
Provider = Union["TracerProvider", "MeterProvider"]
2228

2329
logger = getLogger(__name__)
2430

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

3642

37-
def _load_provider(
38-
provider: str,
39-
) -> Union["TracerProvider", "MeterProvider"]: # type: ignore
43+
def _load_provider(provider: str) -> Provider:
4044
try:
41-
return next( # type: ignore
45+
entry_point = next(
4246
iter_entry_points(
4347
"opentelemetry_{}".format(provider),
44-
name=getattr(
45-
Configuration(), # type: ignore
46-
provider,
47-
"default_{}".format(provider),
48+
name=cast(
49+
str,
50+
Configuration().get(
51+
provider, "default_{}".format(provider),
52+
),
4853
),
4954
)
50-
).load()()
55+
)
56+
return cast(Provider, entry_point.load()(),)
5157
except Exception: # pylint: disable=broad-except
5258
logger.error("Failed to load configured provider %s", provider)
5359
raise
5460

5561

62+
def _load_meter_provider(provider: str) -> "MeterProvider":
63+
return cast("MeterProvider", _load_provider(provider))
64+
65+
66+
def _load_trace_provider(provider: str) -> "TracerProvider":
67+
return cast("TracerProvider", _load_provider(provider))
68+
69+
5670
# Pattern for matching up until the first '/' after the 'https://' part.
5771
_URL_PATTERN = r"(https?|ftp)://.*?/"
5872

opentelemetry-api/tests/configuration/test_configuration.py

+16-11
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,16 @@
1616
from unittest import TestCase
1717
from unittest.mock import patch
1818

19-
from opentelemetry.configuration import Configuration # type: ignore
19+
from opentelemetry.configuration import Configuration
2020

2121

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

28-
def test_singleton(self):
28+
def test_singleton(self) -> None:
2929
self.assertIsInstance(Configuration(), Configuration)
3030
self.assertIs(Configuration(), Configuration())
3131

@@ -39,7 +39,7 @@ def test_singleton(self):
3939
"OPENTELEMETRY_PTHON_TRACEX_PROVIDER": "tracex_provider",
4040
},
4141
)
42-
def test_environment_variables(self): # type: ignore
42+
def test_environment_variables(self):
4343
self.assertEqual(
4444
Configuration().METER_PROVIDER, "meter_provider"
4545
) # pylint: disable=no-member
@@ -62,16 +62,21 @@ def test_property(self):
6262
with self.assertRaises(AttributeError):
6363
Configuration().TRACER_PROVIDER = "new_tracer_provider"
6464

65-
def test_slots(self):
65+
def test_slots(self) -> None:
6666
with self.assertRaises(AttributeError):
6767
Configuration().XYZ = "xyz" # pylint: disable=assigning-non-slot
6868

69-
def test_getattr(self):
69+
def test_getattr(self) -> None:
70+
# literal access
7071
self.assertIsNone(Configuration().XYZ)
7172

72-
def test_reset(self):
73+
# dynamic access
74+
self.assertIsNone(getattr(Configuration(), "XYZ"))
75+
self.assertIsNone(Configuration().get("XYZ", None))
76+
77+
def test_reset(self) -> None:
7378
environ_patcher = patch.dict(
74-
"os.environ", # type: ignore
79+
"os.environ",
7580
{"OPENTELEMETRY_PYTHON_TRACER_PROVIDER": "tracer_provider"},
7681
)
7782

@@ -96,7 +101,7 @@ def test_reset(self):
96101
"OPENTELEMETRY_PYTHON_FALSE": "False",
97102
},
98103
)
99-
def test_boolean(self):
104+
def test_boolean(self) -> None:
100105
self.assertIsInstance(
101106
Configuration().TRUE, bool
102107
) # pylint: disable=no-member
@@ -114,7 +119,7 @@ def test_boolean(self):
114119
"OPENTELEMETRY_PYTHON_NON_INTEGER": "-12z3",
115120
},
116121
)
117-
def test_integer(self):
122+
def test_integer(self) -> None:
118123
self.assertEqual(
119124
Configuration().POSITIVE_INTEGER, 123
120125
) # pylint: disable=no-member
@@ -133,7 +138,7 @@ def test_integer(self):
133138
"OPENTELEMETRY_PYTHON_NON_FLOAT": "-12z3.123",
134139
},
135140
)
136-
def test_float(self):
141+
def test_float(self) -> None:
137142
self.assertEqual(
138143
Configuration().POSITIVE_FLOAT, 123.123
139144
) # pylint: disable=no-member

0 commit comments

Comments
 (0)