Skip to content
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

Cythonize reify #2995

Merged
merged 6 commits into from
May 14, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
.vimrc
aiohttp/_frozenlist.c
aiohttp/_frozenlist.html
aiohttp/_helpers.c
aiohttp/_helpers.html
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@asvetlov it looks like you forgot to update MANIFEST.in.
I wish we could just use a setuptools plugin, which would rely on git to do this automatically.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

*.c files should be included but *.html should not.
Not very big problem though: on travis html files are never generated.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed by ffa4532

aiohttp/_websocket.c
aiohttp/_websocket.html
aiohttp/_http_parser.c
Expand Down
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ script:
- make cov-ci-run

after_success:
- codecov
# - codecov
- bash <(curl -s https://codecov.io/bash)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do this? we install it in CI pinned to certain version:
https://github.com/aio-libs/aiohttp/blob/master/requirements/ci-wheel.txt#L25

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was my try to fix coverage reports.
Now upload on codecov is failed (with both codecov plugin and bash script).
Any ideas?


_helpers:
- &_mainstream_python_base
Expand Down
1 change: 1 addition & 0 deletions CHANGES/2995.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Cythonize ``@helpers.reify``, 5% boost on macro benchmark
35 changes: 35 additions & 0 deletions aiohttp/_helpers.pyx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
cdef class reify:
"""Use as a class method decorator. It operates almost exactly like
the Python `@property` decorator, but it puts the result of the
method it decorates into the instance dict after the first call,
effectively replacing the function it decorates with an instance
variable. It is, in Python parlance, a data descriptor.
"""

cdef object wrapped
cdef object name

def __init__(self, wrapped):
self.wrapped = wrapped
self.name = wrapped.__name__

@property
def __doc__(self):
return self.wrapped.__doc__

def __get__(self, inst, owner):
try:
try:
return inst._cache[self.name]
except KeyError:
val = self.wrapped(inst)
inst._cache[self.name] = val
return val
except AttributeError:
if inst is None:
return self
raise

def __set__(self, inst, value):
raise AttributeError("reified property is read-only")
16 changes: 11 additions & 5 deletions aiohttp/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -490,13 +490,10 @@ class reify:

def __init__(self, wrapped):
self.wrapped = wrapped
try:
self.__doc__ = wrapped.__doc__
except Exception: # pragma: no cover
self.__doc__ = ""
self.__doc__ = wrapped.__doc__
self.name = wrapped.__name__

def __get__(self, inst, owner, _sentinel=sentinel):
def __get__(self, inst, owner):
try:
try:
return inst._cache[self.name]
Expand All @@ -513,6 +510,15 @@ def __set__(self, inst, value):
raise AttributeError("reified property is read-only")


reify_py = reify

try:
from ._helpers import reify as reify_c
if not NO_EXTENSIONS:
reify = reify_c
except ImportError:
pass

_ipv4_pattern = (r'^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}'
r'(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$')
_ipv6_pattern = (
Expand Down
34 changes: 17 additions & 17 deletions aiohttp/web_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,18 +174,18 @@ def transport(self):
def writer(self):
return self._payload_writer

@property
@reify
def message(self):
warnings.warn("Request.message is deprecated",
DeprecationWarning,
stacklevel=3)
return self._message

@property
@reify
def rel_url(self):
return self._rel_url

@property
@reify
def loop(self):
return self._loop

Expand All @@ -208,7 +208,7 @@ def __iter__(self):

########

@property
@reify
def secure(self):
"""A bool indicating if the request is handled with SSL."""
return self.scheme == 'https'
Expand Down Expand Up @@ -289,15 +289,15 @@ def scheme(self):
else:
return 'http'

@property
@reify
def method(self):
"""Read only property for getting HTTP method.
The value is upper-cased str like 'GET', 'POST', 'PUT' etc.
"""
return self._method

@property
@reify
def version(self):
"""Read only property for getting HTTP version of request.
Expand Down Expand Up @@ -343,7 +343,7 @@ def url(self):
url = URL.build(scheme=self.scheme, host=self.host)
return url.join(self._rel_url)

@property
@reify
def path(self):
"""The URL including *PATH INFO* without the host or scheme.
Expand All @@ -359,7 +359,7 @@ def path_qs(self):
"""
return str(self._rel_url)

@property
@reify
def raw_path(self):
""" The URL including raw *PATH INFO* without the host or scheme.
Warning, the path is unquoted and may contains non valid URL characters
Expand All @@ -368,25 +368,25 @@ def raw_path(self):
"""
return self._message.path

@property
@reify
def query(self):
"""A multidict with all the variables in the query string."""
return self._rel_url.query

@property
@reify
def query_string(self):
"""The query string in the URL.
E.g., id=10
"""
return self._rel_url.query_string

@property
@reify
def headers(self):
"""A case-insensitive multidict proxy with all headers."""
return self._headers

@property
@reify
def raw_headers(self):
"""A sequence of pars for all headers."""
return self._message.raw_headers
Expand Down Expand Up @@ -427,7 +427,7 @@ def if_range(self, _IF_RANGE=hdrs.IF_RANGE):
"""
return self._http_date(self.headers.get(_IF_RANGE))

@property
@reify
def keep_alive(self):
"""Is keepalive enabled by client?"""
return not self._message.should_close
Expand All @@ -443,7 +443,7 @@ def cookies(self):
return MappingProxyType(
{key: val.value for key, val in parsed.items()})

@property
@reify
def http_range(self, *, _RANGE=hdrs.RANGE):
"""The content of Range HTTP header.
Expand Down Expand Up @@ -479,7 +479,7 @@ def http_range(self, *, _RANGE=hdrs.RANGE):

return slice(start, end, 1)

@property
@reify
def content(self):
"""Return raw payload stream."""
return self._payload
Expand All @@ -497,7 +497,7 @@ def can_read_body(self):
"""Return True if request's HTTP BODY can be read, False otherwise."""
return not self._payload.at_eof()

@property
@reify
def body_exists(self):
"""Return True if request has HTTP BODY, False otherwise."""
return type(self._payload) is not EmptyStreamReader
Expand Down Expand Up @@ -657,7 +657,7 @@ def clone(self, *, method=sentinel, rel_url=sentinel,
ret._match_info = self._match_info
return ret

@property
@reify
def match_info(self):
"""Result of route resolving."""
return self._match_info
Expand Down
1 change: 1 addition & 0 deletions docs/spelling_wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ ctor
Ctrl
Cython
cythonized
Cythonize
de
deduplicate
# de-facto:
Expand Down
2 changes: 2 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
),
Extension('aiohttp._frozenlist',
['aiohttp/_frozenlist' + ext]),
Extension('aiohttp._helpers',
['aiohttp/_helpers' + ext]),
Extension('aiohttp._http_writer',
['aiohttp/_http_writer' + ext])]

Expand Down
20 changes: 15 additions & 5 deletions tests/test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,14 +241,16 @@ def log(self, request, response, time):
mock_logger.info.assert_called_with('request response 1')


class TestReify:
class ReifyMixin:

reify = NotImplemented

def test_reify(self):
class A:
def __init__(self):
self._cache = {}

@helpers.reify
@self.reify
def prop(self):
return 1

Expand All @@ -260,20 +262,20 @@ class A:
def __init__(self):
self._cache = {}

@helpers.reify
@self.reify
def prop(self):
"""Docstring."""
return 1

assert isinstance(A.prop, helpers.reify)
assert isinstance(A.prop, self.reify)
assert 'Docstring.' == A.prop.__doc__

def test_reify_assignment(self):
class A:
def __init__(self):
self._cache = {}

@helpers.reify
@self.reify
def prop(self):
return 1

Expand All @@ -282,6 +284,14 @@ def prop(self):
with pytest.raises(AttributeError):
a.prop = 123


class TestPyReify(ReifyMixin):
reify = helpers.reify_py


class TestCReify(ReifyMixin):
reify = helpers.reify_c

# ----------------------------------- is_ip_address() ----------------------


Expand Down