summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--oslo_context/context.py184
-rw-r--r--oslo_context/fixture.py4
-rw-r--r--oslo_context/version.py1
-rw-r--r--setup.cfg11
-rw-r--r--test-requirements.txt1
-rw-r--r--tox.ini14
6 files changed, 121 insertions, 94 deletions
diff --git a/oslo_context/context.py b/oslo_context/context.py
index 34e1e0c..148817e 100644
--- a/oslo_context/context.py
+++ b/oslo_context/context.py
@@ -30,6 +30,7 @@ import collections.abc
import functools
import itertools
import threading
+import typing as ty
import uuid
import warnings
@@ -41,27 +42,21 @@ _request_store = threading.local()
# These arguments will be passed to a new context from the first available
# header to support backwards compatibility.
-_ENVIRON_HEADERS = {
- 'auth_token': ['HTTP_X_AUTH_TOKEN',
- 'HTTP_X_STORAGE_TOKEN'],
- 'user_id': ['HTTP_X_USER_ID',
- 'HTTP_X_USER'],
- 'project_id': ['HTTP_X_PROJECT_ID',
- 'HTTP_X_TENANT_ID',
- 'HTTP_X_TENANT'],
+_ENVIRON_HEADERS: ty.Dict[str, ty.List[str]] = {
+ 'auth_token': ['HTTP_X_AUTH_TOKEN', 'HTTP_X_STORAGE_TOKEN'],
+ 'user_id': ['HTTP_X_USER_ID', 'HTTP_X_USER'],
+ 'project_id': ['HTTP_X_PROJECT_ID', 'HTTP_X_TENANT_ID', 'HTTP_X_TENANT'],
'domain_id': ['HTTP_X_DOMAIN_ID'],
'system_scope': ['HTTP_OPENSTACK_SYSTEM_SCOPE'],
'user_domain_id': ['HTTP_X_USER_DOMAIN_ID'],
'project_domain_id': ['HTTP_X_PROJECT_DOMAIN_ID'],
'user_name': ['HTTP_X_USER_NAME'],
- 'project_name': ['HTTP_X_PROJECT_NAME',
- 'HTTP_X_TENANT_NAME'],
+ 'project_name': ['HTTP_X_PROJECT_NAME', 'HTTP_X_TENANT_NAME'],
'user_domain_name': ['HTTP_X_USER_DOMAIN_NAME'],
'project_domain_name': ['HTTP_X_PROJECT_DOMAIN_NAME'],
'request_id': ['openstack.request_id'],
'global_request_id': ['openstack.global_request_id'],
-
'service_token': ['HTTP_X_SERVICE_TOKEN'],
'service_user_id': ['HTTP_X_SERVICE_USER_ID'],
'service_user_name': ['HTTP_X_SERVICE_USER_NAME'],
@@ -74,7 +69,7 @@ _ENVIRON_HEADERS = {
}
-def generate_request_id():
+def generate_request_id() -> str:
"""Generate a unique request id."""
return 'req-%s' % uuid.uuid4()
@@ -87,11 +82,11 @@ class _DeprecatedPolicyValues(collections.abc.MutableMapping):
these values as oslo.policy will do will trigger a DeprecationWarning.
"""
- def __init__(self, data):
+ def __init__(self, data: ty.Dict[str, ty.Any]):
self._data = data
- self._deprecated = {}
+ self._deprecated: ty.Dict[str, ty.Any] = {}
- def __getitem__(self, k):
+ def __getitem__(self, k: str) -> ty.Any:
try:
return self._data[k]
except KeyError:
@@ -110,32 +105,33 @@ class _DeprecatedPolicyValues(collections.abc.MutableMapping):
raise KeyError(k)
- def __setitem__(self, k, v):
+ def __setitem__(self, k: str, v: ty.Any) -> None:
self._deprecated[k] = v
- def __delitem__(self, k):
+ def __delitem__(self, k: str) -> None:
del self._deprecated[k]
- def __iter__(self):
+ def __iter__(self) -> ty.Iterator[ty.Any]:
return iter(self._dict)
- def __len__(self):
+ def __len__(self) -> int:
return len(self._dict)
- def __str__(self):
+ def __str__(self) -> str:
return self._dict.__str__()
- def __repr__(self):
+ def __repr__(self) -> str:
return self._dict.__repr__()
@property
- def _dict(self):
+ def _dict(self) -> ty.Dict[str, ty.Any]:
d = self._deprecated.copy()
d.update(self._data)
return d
-def _moved_msg(new_name, old_name):
+# TODO(stephenfin): Remove this in the 4.0 release
+def _moved_msg(new_name: str, old_name: ty.Optional[str]) -> None:
if old_name:
deprecated_msg = "Property '%(old_name)s' has moved to '%(new_name)s'"
deprecated_msg = deprecated_msg % {'old_name': old_name,
@@ -147,22 +143,23 @@ def _moved_msg(new_name, old_name):
stacklevel=5)
-def _moved_property(new_name, old_name=None, target=None):
-
- if not target:
- target = new_name
+def _moved_property(
+ new_name: str,
+ old_name: ty.Optional[str] = None,
+ target: ty.Optional[str] = None,
+) -> ty.Any:
- def getter(self):
+ def getter(self: ty.Any) -> ty.Any:
_moved_msg(new_name, old_name)
- return getattr(self, target)
+ return getattr(self, target or new_name)
- def setter(self, value):
+ def setter(self: ty.Any, value: str) -> None:
_moved_msg(new_name, old_name)
- setattr(self, target, value)
+ setattr(self, target or new_name, value)
- def deleter(self):
+ def deleter(self: ty.Any) -> None:
_moved_msg(new_name, old_name)
- delattr(self, target)
+ delattr(self, target or new_name)
return property(getter, setter, deleter)
@@ -181,47 +178,49 @@ class RequestContext(object):
accesses the system, as well as additional request information.
"""
- user_idt_format = u'{user} {project_id} {domain} {user_domain} {p_domain}'
+ user_idt_format = '{user} {project_id} {domain} {user_domain} {p_domain}'
# Can be overridden in subclasses to specify extra keys that should be
# read when constructing a context using from_dict.
- FROM_DICT_EXTRA_KEYS = []
+ FROM_DICT_EXTRA_KEYS: ty.List[str] = []
@_renamed_kwarg('user', 'user_id')
@_renamed_kwarg('domain', 'domain_id')
@_renamed_kwarg('user_domain', 'user_domain_id')
@_renamed_kwarg('project_domain', 'project_domain_id')
- def __init__(self,
- auth_token=None,
- user_id=None,
- project_id=None,
- domain_id=None,
- user_domain_id=None,
- project_domain_id=None,
- is_admin=False,
- read_only=False,
- show_deleted=False,
- request_id=None,
- resource_uuid=None,
- overwrite=True,
- roles=None,
- user_name=None,
- project_name=None,
- domain_name=None,
- user_domain_name=None,
- project_domain_name=None,
- is_admin_project=True,
- service_token=None,
- service_user_id=None,
- service_user_name=None,
- service_user_domain_id=None,
- service_user_domain_name=None,
- service_project_id=None,
- service_project_name=None,
- service_project_domain_id=None,
- service_project_domain_name=None,
- service_roles=None,
- global_request_id=None,
- system_scope=None):
+ def __init__(
+ self,
+ auth_token: ty.Optional[str] = None,
+ user_id: ty.Optional[str] = None,
+ project_id: ty.Optional[str] = None,
+ domain_id: ty.Optional[str] = None,
+ user_domain_id: ty.Optional[str] = None,
+ project_domain_id: ty.Optional[str] = None,
+ is_admin: bool = False,
+ read_only: bool = False,
+ show_deleted: bool = False,
+ request_id: ty.Optional[str] = None,
+ resource_uuid: ty.Optional[str] = None,
+ overwrite: bool = True,
+ roles: ty.Optional[ty.List[str]] = None,
+ user_name: ty.Optional[str] = None,
+ project_name: ty.Optional[str] = None,
+ domain_name: ty.Optional[str] = None,
+ user_domain_name: ty.Optional[str] = None,
+ project_domain_name: ty.Optional[str] = None,
+ is_admin_project: bool = True,
+ service_token: ty.Optional[str] = None,
+ service_user_id: ty.Optional[str] = None,
+ service_user_name: ty.Optional[str] = None,
+ service_user_domain_id: ty.Optional[str] = None,
+ service_user_domain_name: ty.Optional[str] = None,
+ service_project_id: ty.Optional[str] = None,
+ service_project_name: ty.Optional[str] = None,
+ service_project_domain_id: ty.Optional[str] = None,
+ service_project_domain_name: ty.Optional[str] = None,
+ service_roles: ty.Optional[ty.List[str]] = None,
+ global_request_id: ty.Optional[str] = None,
+ system_scope: ty.Optional[str] = None,
+ ):
"""Initialize the RequestContext
:param overwrite: Set to False to ensure that the greenthread local
@@ -280,12 +279,10 @@ class RequestContext(object):
# to the same private variable rather than each other.
user = _moved_property('user_id', 'user', target='_user_id')
domain = _moved_property('domain_id', 'domain', target='_domain_id')
- user_domain = _moved_property('user_domain_id',
- 'user_domain',
- target='_user_domain_id')
- project_domain = _moved_property('project_domain_id',
- 'project_domain',
- target='_project_domain_id')
+ user_domain = _moved_property(
+ 'user_domain_id', 'user_domain', target='_user_domain_id')
+ project_domain = _moved_property(
+ 'project_domain_id', 'project_domain', target='_project_domain_id')
user_id = _moved_property('_user_id')
project_id = _moved_property('_project_id')
@@ -293,11 +290,11 @@ class RequestContext(object):
user_domain_id = _moved_property('_user_domain_id')
project_domain_id = _moved_property('_project_domain_id')
- def update_store(self):
+ def update_store(self) -> None:
"""Store the context in the current thread."""
_request_store.context = self
- def to_policy_values(self):
+ def to_policy_values(self) -> _DeprecatedPolicyValues:
"""A dictionary of context attributes to enforce policy with.
oslo.policy enforcement requires a dictionary of attributes
@@ -326,16 +323,18 @@ class RequestContext(object):
'service_user_domain_id': self.service_user_domain_id,
'service_project_id': self.service_project_id,
'service_project_domain_id': self.service_project_domain_id,
- 'service_roles': self.service_roles})
+ 'service_roles': self.service_roles,
+ })
- def to_dict(self):
+ def to_dict(self) -> ty.Dict[str, ty.Any]:
"""Return a dictionary of context attributes."""
user_idt = self.user_idt_format.format(
user=self.user_id or '-',
project_id=self.project_id or '-',
domain=self.domain_id or '-',
user_domain=self.user_domain_id or '-',
- p_domain=self.project_domain_id or '-')
+ p_domain=self.project_domain_id or '-',
+ )
return {'user': self.user_id,
'project_id': self.project_id,
@@ -355,7 +354,7 @@ class RequestContext(object):
'user_identity': user_idt,
'is_admin_project': self.is_admin_project}
- def get_logging_values(self):
+ def get_logging_values(self) -> ty.Dict[str, ty.Any]:
"""Return a dictionary of logging specific context attributes."""
values = {'user_name': self.user_name,
'project_name': self.project_name,
@@ -379,7 +378,7 @@ class RequestContext(object):
return values
@property
- def global_id(self):
+ def global_id(self) -> str:
"""Return a sensible value for global_id to pass on.
When we want to make a call with to another service, it's
@@ -393,7 +392,9 @@ class RequestContext(object):
@_renamed_kwarg('domain', 'domain_id')
@_renamed_kwarg('user_domain', 'user_domain_id')
@_renamed_kwarg('project_domain', 'project_domain_id')
- def from_dict(cls, values, **kwargs):
+ def from_dict(
+ cls, values: ty.Dict[str, ty.Any], **kwargs: ty.Any,
+ ) -> 'RequestContext':
"""Construct a context object from a provided dictionary."""
kwargs.setdefault('auth_token', values.get('auth_token'))
kwargs.setdefault('user_id', values.get('user'))
@@ -426,7 +427,9 @@ class RequestContext(object):
@_renamed_kwarg('domain', 'domain_id')
@_renamed_kwarg('user_domain', 'user_domain_id')
@_renamed_kwarg('project_domain', 'project_domain_id')
- def from_environ(cls, environ, **kwargs):
+ def from_environ(
+ cls, environ: ty.Dict[str, ty.Any], **kwargs: ty.Any,
+ ) -> 'RequestContext':
"""Load a context object from a request environment.
If keyword arguments are provided then they override the values in the
@@ -469,7 +472,7 @@ class RequestContext(object):
return cls(**kwargs)
-def get_admin_context(show_deleted=False):
+def get_admin_context(show_deleted: bool = False) -> RequestContext:
"""Create an administrator context."""
context = RequestContext(None,
project_id=None,
@@ -479,13 +482,16 @@ def get_admin_context(show_deleted=False):
return context
-def get_context_from_function_and_args(function, args, kwargs):
+def get_context_from_function_and_args(
+ function: ty.Callable,
+ args: ty.List[ty.Any],
+ kwargs: ty.Dict[str, ty.Any],
+) -> ty.Optional[RequestContext]:
"""Find an arg of type RequestContext and return it.
- This is useful in a couple of decorators where we don't
- know much about the function we're wrapping.
+ This is useful in a couple of decorators where we don't know much about the
+ function we're wrapping.
"""
-
for arg in itertools.chain(kwargs.values(), args):
if isinstance(arg, RequestContext):
return arg
@@ -493,7 +499,7 @@ def get_context_from_function_and_args(function, args, kwargs):
return None
-def is_user_context(context):
+def is_user_context(context: RequestContext) -> bool:
"""Indicates if the request context is a normal user."""
if not context or not isinstance(context, RequestContext):
return False
@@ -502,7 +508,7 @@ def is_user_context(context):
return True
-def get_current():
+def get_current() -> ty.Optional[RequestContext]:
"""Return this thread's current context
If no context is set, returns None
diff --git a/oslo_context/fixture.py b/oslo_context/fixture.py
index a7bb0ff..715f43a 100644
--- a/oslo_context/fixture.py
+++ b/oslo_context/fixture.py
@@ -24,7 +24,7 @@ class ClearRequestContext(fixtures.Fixture):
run later in the same process.
"""
- def setUp(self):
+ def setUp(self) -> None:
super(ClearRequestContext, self).setUp()
# we need to clear both when we start, and when we finish,
# because there might be other tests running that don't handle
@@ -32,7 +32,7 @@ class ClearRequestContext(fixtures.Fixture):
self._remove_cached_context()
self.addCleanup(self._remove_cached_context)
- def _remove_cached_context(self):
+ def _remove_cached_context(self) -> None:
"""Remove the thread-local context stored in the module."""
try:
del context._request_store.context
diff --git a/oslo_context/version.py b/oslo_context/version.py
index 19e4fb1..e9810b7 100644
--- a/oslo_context/version.py
+++ b/oslo_context/version.py
@@ -12,7 +12,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-
import pbr.version
version_info = pbr.version.VersionInfo('oslo.context')
diff --git a/setup.cfg b/setup.cfg
index f823667..2cfdd53 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -24,3 +24,14 @@ classifier =
[files]
packages =
oslo_context
+
+[mypy]
+show_column_numbers = true
+show_error_context = true
+ignore_missing_imports = true
+check_untyped_defs = true
+warn_unused_ignores = true
+disallow_untyped_defs = true
+
+[mypy-oslo_context.tests.*]
+ignore_errors = true
diff --git a/test-requirements.txt b/test-requirements.txt
index ff28c2e..4aa0d27 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -3,6 +3,7 @@
# process, which may cause wedges in the gate later.
hacking>=3.0.1,<3.1.0 # Apache-2.0
+mypy>=0.761 # MIT
fixtures>=3.0.0 # Apache-2.0/BSD
oslotest>=3.2.0 # Apache-2.0
coverage!=4.4,>=4.0 # Apache-2.0
diff --git a/tox.ini b/tox.ini
index 9abbd96..d377fde 100644
--- a/tox.ini
+++ b/tox.ini
@@ -14,21 +14,29 @@ deps =
-r{toxinidir}/test-requirements.txt
commands = stestr run --slowest {posargs}
+[testenv:mypy]
+description =
+ Run type checks.
+envdir = {toxworkdir}/shared
+commands =
+ python -m mypy {posargs:.}
+
[testenv:pep8]
-deps = {[testenv]deps}
+envdir = {toxworkdir}/shared
commands =
pre-commit run -a
# Run security linter
bandit -r oslo_context -x tests -n5
[testenv:venv]
+envdir = {toxworkdir}/shared
commands = {posargs}
[testenv:docs]
-whitelist_externals = rm
deps =
-c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
-r{toxinidir}/doc/requirements.txt
+whitelist_externals = rm
commands =
rm -fr doc/build
sphinx-build -W --keep-going -b html doc/source doc/build/html
@@ -36,6 +44,7 @@ commands =
[testenv:cover]
# TODO(damani): Drop distutils override once logging improves in setuptools
# https://github.com/pypa/setuptools/issues/3038
+envdir = {toxworkdir}/shared
setenv =
SETUPTOOLS_USE_DISTUTILS=stdlib
PYTHON=coverage run --source oslo_context --parallel-mode
@@ -55,6 +64,7 @@ exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build
import_exceptions =
[testenv:releasenotes]
+envdir = {toxworkdir}/docs
whitelist_externals = rm
deps = {[testenv:docs]deps}
commands =