Commit 15ebc207 authored by Jason Madden's avatar Jason Madden

Add the module gevent.time as a drop-in replacement for time.

Cleanups for timeout.py docs.
parent 2f3d720e
...@@ -39,6 +39,10 @@ ...@@ -39,6 +39,10 @@
``AttributeError``, now it once again raises the correct ``AttributeError``, now it once again raises the correct
``socket.error``. Reported in :issue:`1089` by André Cimander. ``socket.error``. Reported in :issue:`1089` by André Cimander.
- Add the module :mod:`gevent.time` that can be imported instead of
:mod:`time`, much like :mod:`gevent.socket` can be imported instead
of :mod:`socket`.
1.3a1 (2018-01-27) 1.3a1 (2018-01-27)
================== ==================
......
...@@ -40,6 +40,7 @@ extlinks = {'issue': ('https://github.com/gevent/gevent/issues/%s', ...@@ -40,6 +40,7 @@ extlinks = {'issue': ('https://github.com/gevent/gevent/issues/%s',
'pull request #')} 'pull request #')}
autodoc_default_flags = ['members', 'show-inheritance'] autodoc_default_flags = ['members', 'show-inheritance']
autodoc_member_order = 'bysource'
autoclass_content = 'both' autoclass_content = 'both'
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
...@@ -226,25 +227,3 @@ del gevent.Greenlet.throw ...@@ -226,25 +227,3 @@ del gevent.Greenlet.throw
for item in gevent.socket.__all__[:]: for item in gevent.socket.__all__[:]:
if getattr(gevent.socket, item) is getattr(socket, item, None): if getattr(gevent.socket, item) is getattr(socket, item, None):
gevent.socket.__all__.remove(item) gevent.socket.__all__.remove(item)
# order the methods in the class documentation the same way they are ordered in the source code
from sphinx.ext import autodoc
from sphinx.ext.autodoc import ClassDocumenter
class MyClassDocumenter(ClassDocumenter):
def get_object_members(self, want_all):
members_check_module, members = super(MyClassDocumenter, self).get_object_members(want_all)
def key((name, obj)):
try:
return obj.im_func.func_code.co_firstlineno
except AttributeError:
return 0
members.sort(key=key)
return members_check_module, members
autodoc.ClassDocumenter = MyClassDocumenter
...@@ -188,5 +188,6 @@ Timeouts ...@@ -188,5 +188,6 @@ Timeouts
.. autoclass:: Timeout .. autoclass:: Timeout
:members: :members:
:undoc-members: :undoc-members:
:special-members: __enter__, __exit__
.. autofunction:: with_timeout .. autofunction:: with_timeout
...@@ -21,5 +21,6 @@ API reference ...@@ -21,5 +21,6 @@ API reference
gevent.thread gevent.thread
gevent.threading gevent.threading
gevent.threadpool gevent.threadpool
gevent.time
gevent.util gevent.util
lowlevel lowlevel
...@@ -29,21 +29,22 @@ def copy_globals(source, ...@@ -29,21 +29,22 @@ def copy_globals(source,
dunder_names_to_keep=('__implements__', '__all__', '__imports__'), dunder_names_to_keep=('__implements__', '__all__', '__imports__'),
cleanup_globs=True): cleanup_globs=True):
""" """
Copy attributes defined in `source.__dict__` to the dictionary in globs Copy attributes defined in ``source.__dict__`` to the dictionary
(which should be the caller's globals()). in globs (which should be the caller's :func:`globals`).
Names that start with `__` are ignored (unless they are in Names that start with ``__`` are ignored (unless they are in
*dunder_names_to_keep*). Anything found in *names_to_ignore* is *dunder_names_to_keep*). Anything found in *names_to_ignore* is
also ignored. also ignored.
If *only_names* is given, only those attributes will be considered. If *only_names* is given, only those attributes will be
In this case, *ignore_missing_names* says whether or not to raise an AttributeError considered. In this case, *ignore_missing_names* says whether or
if one of those names can't be found. not to raise an :exc:`AttributeError` if one of those names can't
be found.
If cleanup_globs has a true value, then common things imported but not used If *cleanup_globs* has a true value, then common things imported but
at runtime are removed, including this function. not used at runtime are removed, including this function.
Returns a list of the names copied Returns a list of the names copied; this should be assigned to ``__imports__``.
""" """
if only_names: if only_names:
if ignore_missing_names: if ignore_missing_names:
......
...@@ -250,9 +250,7 @@ def patch_os(): ...@@ -250,9 +250,7 @@ def patch_os():
def patch_time(): def patch_time():
"""Replace :func:`time.sleep` with :func:`gevent.sleep`.""" """Replace :func:`time.sleep` with :func:`gevent.sleep`."""
from gevent.hub import sleep patch_module('time')
import time
patch_item(time, 'sleep', sleep)
def _patch_existing_locks(threading): def _patch_existing_locks(threading):
......
...@@ -33,7 +33,7 @@ _signal_getsignal = _signal.getsignal ...@@ -33,7 +33,7 @@ _signal_getsignal = _signal.getsignal
def getsignal(signalnum): def getsignal(signalnum):
""" """
Exactly the same as :func:`signal.signal` except where Exactly the same as :func:`signal.getsignal` except where
:const:`signal.SIGCHLD` is concerned. :const:`signal.SIGCHLD` is concerned.
For :const:`signal.SIGCHLD`, this cooperates with :func:`signal` For :const:`signal.SIGCHLD`, this cooperates with :func:`signal`
......
...@@ -359,12 +359,17 @@ if 'TimeoutExpired' not in globals(): ...@@ -359,12 +359,17 @@ if 'TimeoutExpired' not in globals():
.. versionadded:: 1.2a1 .. versionadded:: 1.2a1
""" """
def __init__(self, cmd, timeout, output=None): def __init__(self, cmd, timeout, output=None):
_Timeout.__init__(self, timeout, _use_timer=False) _Timeout.__init__(self, None)
self.cmd = cmd self.cmd = cmd
self.timeout = timeout self.seconds = timeout
self.output = output self.output = output
@property
def timeout(self):
return self.seconds
def __str__(self): def __str__(self):
return ("Command '%s' timed out after %s seconds" % return ("Command '%s' timed out after %s seconds" %
(self.cmd, self.timeout)) (self.cmd, self.timeout))
......
# Copyright (c) 2018 gevent. See LICENSE for details.
"""
The standard library :mod:`time` module, but :func:`sleep` is
gevent-aware.
.. versionadded:: 1.3a2
"""
from __future__ import absolute_import
__implements__ = [
'sleep',
]
__all__ = __implements__
import time as __time__
from gevent._util import copy_globals
__imports__ = copy_globals(__time__, globals(),
names_to_ignore=__implements__)
from gevent.hub import sleep
sleep = sleep # pylint
...@@ -17,8 +17,10 @@ from __future__ import absolute_import, print_function, division ...@@ -17,8 +17,10 @@ from __future__ import absolute_import, print_function, division
from gevent._compat import string_types from gevent._compat import string_types
from gevent.hub import getcurrent, _NONE, get_hub from gevent.hub import getcurrent, _NONE, get_hub
__all__ = ['Timeout', __all__ = [
'with_timeout'] 'Timeout',
'with_timeout',
]
class _FakeTimer(object): class _FakeTimer(object):
...@@ -26,8 +28,22 @@ class _FakeTimer(object): ...@@ -26,8 +28,22 @@ class _FakeTimer(object):
# without allocating any native resources. This is useful for timeouts # without allocating any native resources. This is useful for timeouts
# that will never expire. # that will never expire.
# Also partially mimics the API of Timeout itself for use in _start_new_or_dummy # Also partially mimics the API of Timeout itself for use in _start_new_or_dummy
pending = False
active = False # This object is used as a singleton, so it should be
# immutable.
__slots__ = ()
@property
def pending(self):
return False
active = pending
@property
def seconds(self):
return None
timer = exception = seconds
def start(self, *args, **kwargs): def start(self, *args, **kwargs):
# pylint:disable=unused-argument # pylint:disable=unused-argument
...@@ -51,23 +67,30 @@ _FakeTimer = _FakeTimer() ...@@ -51,23 +67,30 @@ _FakeTimer = _FakeTimer()
class Timeout(BaseException): class Timeout(BaseException):
""" """
Raise *exception* in the current greenlet after given time period:: Timeout(seconds=None, exception=None, ref=True, priority=-1)
Raise *exception* in the current greenlet after *seconds*
have elapsed::
timeout = Timeout(seconds, exception) timeout = Timeout(seconds, exception)
timeout.start() timeout.start()
try: try:
... # exception will be raised here, after *seconds* passed since start() call ... # exception will be raised here, after *seconds* passed since start() call
finally: finally:
timeout.cancel() timeout.close()
.. note:: If the code that the timeout was protecting finishes .. note::
executing before the timeout elapses, be sure to ``cancel`` the
timeout so it is not unexpectedly raised in the future. Even if
it is raised, it is a best practice to cancel it. This
``try/finally`` construct or a ``with`` statement is a
recommended pattern.
When *exception* is omitted or ``None``, the :class:`Timeout` instance itself is raised: If the code that the timeout was protecting finishes
executing before the timeout elapses, be sure to ``close`` the
timeout so it is not unexpectedly raised in the future. Even if it
is raised, it is a best practice to close it. This ``try/finally``
construct or a ``with`` statement is a recommended pattern. (If
the timeout object will be started again, use ``cancel`` instead
of ``close``; this is rare.)
When *exception* is omitted or ``None``, the ``Timeout`` instance
itself is raised::
>>> import gevent >>> import gevent
>>> gevent.Timeout(0.1).start() >>> gevent.Timeout(0.1).start()
...@@ -76,14 +99,41 @@ class Timeout(BaseException): ...@@ -76,14 +99,41 @@ class Timeout(BaseException):
... ...
Timeout: 0.1 seconds Timeout: 0.1 seconds
To simplify starting and canceling timeouts, the ``with`` statement can be used:: If the *seconds* argument is not given or is ``None`` (e.g.,
``Timeout()``), then the timeout will never expire and never raise
*exception*. This is convenient for creating functions which take
an optional timeout parameter of their own. (Note that this is **not**
the same thing as a *seconds* value of ``0``.)
::
def function(args, timeout=None):
"A function with an optional timeout."
timer = Timeout(timeout)
with timer:
...
.. caution::
A *seconds* value less than ``0.0`` (e.g., ``-1``) is poorly defined. In the future,
support for negative values is likely to do the same thing as a value
of ``None`` or ``0``
A *seconds* value of ``0`` requests that the event loop spin and poll for I/O;
it will immediately expire as soon as control returns to the event loop.
.. rubric:: Use As A Context Manager
To simplify starting and canceling timeouts, the ``with``
statement can be used::
with gevent.Timeout(seconds, exception) as timeout: with gevent.Timeout(seconds, exception) as timeout:
pass # ... code block ... pass # ... code block ...
This is equivalent to the try/finally block above with one additional feature: This is equivalent to the try/finally block above with one
if *exception* is the literal ``False``, the timeout is still raised, but the context manager additional feature: if *exception* is the literal ``False``, the
suppresses it, so the code outside the with-block won't see it. timeout is still raised, but the context manager suppresses it, so
the code outside the with-block won't see it.
This is handy for adding a timeout to the functions that don't This is handy for adding a timeout to the functions that don't
support a *timeout* parameter themselves:: support a *timeout* parameter themselves::
...@@ -96,9 +146,14 @@ class Timeout(BaseException): ...@@ -96,9 +146,14 @@ class Timeout(BaseException):
else: else:
... # a line was read within 5 seconds ... # a line was read within 5 seconds
.. caution:: If ``readline()`` above catches and doesn't re-raise :class:`BaseException` .. caution::
(for example, with a bare ``except:``), then your timeout will fail to function and control
won't be returned to you when you expect. If ``readline()`` above catches and doesn't re-raise
:exc:`BaseException` (for example, with a bare ``except:``), then
your timeout will fail to function and control won't be returned
to you when you expect.
.. rubric:: Catching Timeouts
When catching timeouts, keep in mind that the one you catch may When catching timeouts, keep in mind that the one you catch may
not be the one you have set (a calling function may have set its not be the one you have set (a calling function may have set its
...@@ -112,41 +167,49 @@ class Timeout(BaseException): ...@@ -112,41 +167,49 @@ class Timeout(BaseException):
except Timeout as t: except Timeout as t:
if t is not timeout: if t is not timeout:
raise # not my timeout raise # not my timeout
finally:
timeout.close()
If the *seconds* argument is not given or is ``None`` (e.g.,
``Timeout()``), then the timeout will never expire and never raise
*exception*. This is convenient for creating functions which take
an optional timeout parameter of their own. (Note that this is not the same thing
as a *seconds* value of 0.)
.. caution::
A *seconds* value less than 0.0 (e.g., -1) is poorly defined. In the future,
support for negative values is likely to do the same thing as a value
of ``None``.
.. versionchanged:: 1.1b2 .. versionchanged:: 1.1b2
If *seconds* is not given or is ``None``, no longer allocate a libev
timer that will never be started. If *seconds* is not given or is ``None``, no longer allocate a
native timer object that will never be started.
.. versionchanged:: 1.1 .. versionchanged:: 1.1
Add warning about negative *seconds* values.
Add warning about negative *seconds* values.
.. versionchanged:: 1.3a1
Timeout objects now have a :meth:`close`
method that must be called when the timeout will no longer be
used to properly clean up native resources.
The ``with`` statement does this automatically.
""" """
# We inherit a __dict__ from BaseException, so __slots__ actually
# makes us larger.
def __init__(self, seconds=None, exception=None, ref=True, priority=-1, def __init__(self, seconds=None, exception=None, ref=True, priority=-1,
_use_timer=True, _one_shot=False): _one_shot=False):
BaseException.__init__(self) BaseException.__init__(self)
self.seconds = seconds self.seconds = seconds
self.exception = exception self.exception = exception
self._one_shot = _one_shot self._one_shot = _one_shot
if seconds is None or not _use_timer: if seconds is None:
# Avoid going through the timer codepath if no timeout is # Avoid going through the timer codepath if no timeout is
# desired; this avoids some CFFI interactions on PyPy that can lead to a # desired; this avoids some CFFI interactions on PyPy that can lead to a
# RuntimeError if this implementation is used during an `import` statement. See # RuntimeError if this implementation is used during an `import` statement. See
# https://bitbucket.org/pypy/pypy/issues/2089/crash-in-pypy-260-linux64-with-gevent-11b1 # https://bitbucket.org/pypy/pypy/issues/2089/crash-in-pypy-260-linux64-with-gevent-11b1
# and https://github.com/gevent/gevent/issues/618. # and https://github.com/gevent/gevent/issues/618.
# Plus, in general, it should be more efficient # Plus, in general, it should be more efficient
self.timer = _FakeTimer self.timer = _FakeTimer
else: else:
# XXX: A zero second timer could cause libuv to block the loop. # XXX: A timer <= 0 could cause libuv to block the loop; we catch
# that case in libuv/loop.py
self.timer = get_hub().loop.timer(seconds or 0.0, ref=ref, priority=priority) self.timer = get_hub().loop.timer(seconds or 0.0, ref=ref, priority=priority)
def start(self): def start(self):
...@@ -207,18 +270,29 @@ class Timeout(BaseException): ...@@ -207,18 +270,29 @@ class Timeout(BaseException):
@property @property
def pending(self): def pending(self):
"""Return True if the timeout is scheduled to be raised.""" """True if the timeout is scheduled to be raised."""
return self.timer.pending or self.timer.active return self.timer.pending or self.timer.active
def cancel(self): def cancel(self):
"""If the timeout is pending, cancel it. Otherwise, do nothing.""" """
If the timeout is pending, cancel it. Otherwise, do nothing.
The timeout object can be :meth:`started <start>` again. If
you will not start the timeout again, you should use
:meth:`close` instead.
"""
self.timer.stop() self.timer.stop()
if self._one_shot: if self._one_shot:
self.close() self.close()
def close(self): def close(self):
"""
Close the timeout and free resources. The timer cannot be started again
after this method has been used.
"""
self.timer.stop() self.timer.stop()
self.timer.close() self.timer.close()
self.timer = _FakeTimer
def __repr__(self): def __repr__(self):
classname = type(self).__name__ classname = type(self).__name__
...@@ -251,6 +325,9 @@ class Timeout(BaseException): ...@@ -251,6 +325,9 @@ class Timeout(BaseException):
return '%s second%s: %s' % (self.seconds, suffix, self.exception) return '%s second%s: %s' % (self.seconds, suffix, self.exception)
def __enter__(self): def __enter__(self):
"""
Start and return the timer. If the timer is already started, just return it.
"""
if not self.pending: if not self.pending:
self.start() self.start()
return self return self
...@@ -258,6 +335,7 @@ class Timeout(BaseException): ...@@ -258,6 +335,7 @@ class Timeout(BaseException):
def __exit__(self, typ, value, tb): def __exit__(self, typ, value, tb):
""" """
Stop the timer. Stop the timer.
.. versionchanged:: 1.3a1 .. versionchanged:: 1.3a1
The underlying native timer is also stopped. This object cannot be The underlying native timer is also stopped. This object cannot be
used again. used again.
......
...@@ -19,6 +19,7 @@ MAPPING = { ...@@ -19,6 +19,7 @@ MAPPING = {
'gevent.threading': 'threading', 'gevent.threading': 'threading',
'gevent.builtins': 'builtins' if six.PY3 else '__builtin__', 'gevent.builtins': 'builtins' if six.PY3 else '__builtin__',
'gevent.signal': 'signal', 'gevent.signal': 'signal',
'gevent.time': 'time',
} }
...@@ -43,13 +44,16 @@ COULD_BE_MISSING = { ...@@ -43,13 +44,16 @@ COULD_BE_MISSING = {
'subprocess': ['_posixsubprocess'], 'subprocess': ['_posixsubprocess'],
} }
NO_ALL = ['gevent.threading', NO_ALL = [
'gevent._util', 'gevent.threading',
'gevent._compat', 'gevent._util',
'gevent._socketcommon', 'gevent._compat',
'gevent._fileobjectcommon', 'gevent._fileobjectposix', 'gevent._socketcommon',
'gevent._tblib', 'gevent._fileobjectcommon',
'gevent._corecffi'] 'gevent._fileobjectposix',
'gevent._tblib',
'gevent._corecffi',
]
# A list of modules that may contain things that aren't actually, technically, # A list of modules that may contain things that aren't actually, technically,
# extensions, but that need to be in __extensions__ anyway due to the way, # extensions, but that need to be in __extensions__ anyway due to the way,
...@@ -71,7 +75,7 @@ class Test(unittest.TestCase): ...@@ -71,7 +75,7 @@ class Test(unittest.TestCase):
def check_all(self): def check_all(self):
"Check that __all__ is present and does not contain invalid entries" "Check that __all__ is present and does not contain invalid entries"
if not hasattr(self.module, '__all__'): if not hasattr(self.module, '__all__'):
assert self.modname in NO_ALL self.assertIn(self.modname, NO_ALL)
return return
names = {} names = {}
six.exec_("from %s import *" % self.modname, names) six.exec_("from %s import *" % self.modname, names)
...@@ -89,7 +93,7 @@ class Test(unittest.TestCase): ...@@ -89,7 +93,7 @@ class Test(unittest.TestCase):
raise AssertionError('%r has __implements__ but no stdlib counterpart' % self.modname) raise AssertionError('%r has __implements__ but no stdlib counterpart' % self.modname)
def set_stdlib_all(self): def set_stdlib_all(self):
assert self.stdlib_module is not None self.assertIsNotNone(self.stdlib_module)
self.stdlib_has_all = True self.stdlib_has_all = True
self.stdlib_all = getattr(self.stdlib_module, '__all__', None) self.stdlib_all = getattr(self.stdlib_module, '__all__', None)
if self.stdlib_all is None: if self.stdlib_all is None:
...@@ -115,7 +119,7 @@ class Test(unittest.TestCase): ...@@ -115,7 +119,7 @@ class Test(unittest.TestCase):
item = getattr(self.module, name) item = getattr(self.module, name)
try: try:
stdlib_item = getattr(self.stdlib_module, name) stdlib_item = getattr(self.stdlib_module, name)
assert item is not stdlib_item, (name, item, stdlib_item) self.assertIsNot(item, stdlib_item)
except AttributeError: except AttributeError:
if name not in COULD_BE_MISSING.get(self.stdlib_name, []): if name not in COULD_BE_MISSING.get(self.stdlib_name, []):
raise raise
...@@ -125,7 +129,7 @@ class Test(unittest.TestCase): ...@@ -125,7 +129,7 @@ class Test(unittest.TestCase):
for name in self.__imports__: for name in self.__imports__:
item = getattr(self.module, name) item = getattr(self.module, name)
stdlib_item = getattr(self.stdlib_module, name) stdlib_item = getattr(self.stdlib_module, name)
assert item is stdlib_item, (name, item, stdlib_item) self.assertIs(item, stdlib_item)
def check_extensions_actually_extend(self): def check_extensions_actually_extend(self):
"""Check that the module actually defines new entries in __extensions__""" """Check that the module actually defines new entries in __extensions__"""
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment