-
-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Request config #2949
Request config #2949
Changes from 4 commits
a40a5c3
e8fa21c
901b040
a73c0af
801419d
a31be0f
bea0846
f24176c
f961e25
3b0a79d
bdebac8
ad41c79
7c7193f
0fcff06
143bee9
87f1fe4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,7 @@ | |
import time | ||
import weakref | ||
from collections import namedtuple | ||
from collections.abc import Mapping | ||
from contextlib import suppress | ||
from math import ceil | ||
from pathlib import Path | ||
|
@@ -741,3 +742,63 @@ def set_result(fut, result): | |
def set_exception(fut, exc): | ||
if not fut.done(): | ||
fut.set_exception(exc) | ||
|
||
|
||
class ChainedProps(Mapping): | ||
__slots__ = ('_maps',) | ||
|
||
def __init__(self, maps): | ||
self._maps = tuple(maps) | ||
|
||
def __getitem__(self, key): | ||
for mapping in self._maps: | ||
try: | ||
return mapping[key] | ||
except KeyError: | ||
pass | ||
raise KeyError(key) | ||
|
||
def get(self, key, default=None): | ||
return self[key] if key in self else default | ||
|
||
def __len__(self): | ||
# reuses stored hash values if possible | ||
return len(set().union(*self._maps)) | ||
|
||
def __iter__(self): | ||
d = {} | ||
for mapping in reversed(self._maps): | ||
# reuses stored hash values if possible | ||
d.update(mapping) | ||
return iter(d) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It feels like you can have there the same There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did copy There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's keep it for sake of shared implementation with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Iteration is not the most common call, |
||
|
||
def __contains__(self, key): | ||
return any(key in m for m in self._maps) | ||
|
||
def __bool__(self): | ||
return any(self._maps) | ||
|
||
def __repr__(self): | ||
content = ", ".join(map(repr, self._maps)) | ||
return 'ChainedProps({})'.format(content) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here we should reuse class name in repr to avoid any confusions with subclasses. I know, there wouldn't any subclasses of those, but still this little bit could save few debugging hours for some hacker. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't want to allow subclassing. Hmm, should do it explicitly. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
|
||
|
||
class Namespace: | ||
__slots__ = ('_mapping') | ||
|
||
def __init__(self, mapping): | ||
self._mapping = mapping | ||
|
||
def __dir__(self): | ||
return dir(self.__class__) + list(self._mapping.keys()) | ||
|
||
def __getattr__(self, name): | ||
try: | ||
return self._mapping[name] | ||
except KeyError: | ||
raise AttributeError(name) | ||
|
||
def __repr__(self): | ||
content = ", ".join('{}={!r}'.format(k, v) | ||
for k, v in self._mapping.items()) | ||
return 'Namespace({})'.format(content) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,7 +19,8 @@ | |
from yarl import URL | ||
|
||
from . import hdrs, multipart | ||
from .helpers import DEBUG, HeadersMixin, reify, sentinel | ||
from .helpers import (DEBUG, ChainedProps, HeadersMixin, Namespace, reify, | ||
sentinel) | ||
from .streams import EmptyStreamReader | ||
from .web_exceptions import HTTPRequestEntityTooLarge | ||
|
||
|
@@ -664,6 +665,18 @@ def app(self): | |
"""Application instance.""" | ||
return self._match_info.current_app | ||
|
||
@property | ||
def config_dict(self): | ||
lst = self._match_info.apps | ||
app = self.app | ||
idx = lst.index(app) | ||
sublist = list(reversed(lst[:idx + 1])) | ||
return ChainedProps(sublist) | ||
|
||
@property | ||
def config(self): | ||
return Namespace(self.config_dict) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm in a very doubt if we need not a mapping class here. This will cause two problems:
I would prefer to have one way to work with the config that works and right. So far, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I heard complains many times that IDE doesn't autocomplete string keys but does it for custom attributes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, they don't do keys autocomplete unless you explicitly define those keys in the same scope. However, their ability to autocomplete custom attributes is limited and based on type knowledge and similar usages. However, for pylint you will have to put Namespace class into There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @socketpair I recall you wanted the feature. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
async def _prepare_hook(self, response): | ||
match_info = self._match_info | ||
if match_info is None: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the ChainedMaps would be better name here since it's a mapping. Props suffix leaves the feeling that it's something about attr-based access. While ChainedMaps causes collision with stdlib ChainMap, still, they have a bit different naming what should cause suspicions. However, if we make this class for internal-only usage, than own ChainMap is fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Naming is the hardest thing!
Maybe we can invite a name without chain word?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm...How about to just a Mapping? With the trick that all the maps will get merged right at the moment of it creation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean, if we do merge all the maps, than this class has no reason to exists. The merge result could be returned as MappingProxy - well known immutable mapping.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's name it
ChainMapProxy
and keep existing implementation -- I don't like early merging.