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 @@
``AttributeError``, now it once again raises the correct
``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)
==================
......
......@@ -40,6 +40,7 @@ extlinks = {'issue': ('https://github.com/gevent/gevent/issues/%s',
'pull request #')}
autodoc_default_flags = ['members', 'show-inheritance']
autodoc_member_order = 'bysource'
autoclass_content = 'both'
# Add any paths that contain templates here, relative to this directory.
......@@ -226,25 +227,3 @@ del gevent.Greenlet.throw
for item in gevent.socket.__all__[:]:
if getattr(gevent.socket, item) is getattr(socket, item, None):
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
.. autoclass:: Timeout
:members:
:undoc-members:
:special-members: __enter__, __exit__
.. autofunction:: with_timeout
......@@ -21,5 +21,6 @@ API reference
gevent.thread
gevent.threading
gevent.threadpool
gevent.time
gevent.util
lowlevel
......@@ -29,21 +29,22 @@ def copy_globals(source,
dunder_names_to_keep=('__implements__', '__all__', '__imports__'),
cleanup_globs=True):
"""
Copy attributes defined in `source.__dict__` to the dictionary in globs
(which should be the caller's globals()).
Copy attributes defined in ``source.__dict__`` to the dictionary
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
also ignored.
If *only_names* is given, only those attributes will be considered.
In this case, *ignore_missing_names* says whether or not to raise an AttributeError
if one of those names can't be found.
If *only_names* is given, only those attributes will be
considered. In this case, *ignore_missing_names* says whether or
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
at runtime are removed, including this function.
If *cleanup_globs* has a true value, then common things imported but
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 ignore_missing_names:
......
......@@ -250,9 +250,7 @@ def patch_os():
def patch_time():
"""Replace :func:`time.sleep` with :func:`gevent.sleep`."""
from gevent.hub import sleep
import time
patch_item(time, 'sleep', sleep)
patch_module('time')
def _patch_existing_locks(threading):
......
......@@ -33,7 +33,7 @@ _signal_getsignal = _signal.getsignal
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.
For :const:`signal.SIGCHLD`, this cooperates with :func:`signal`
......
......@@ -359,12 +359,17 @@ if 'TimeoutExpired' not in globals():
.. versionadded:: 1.2a1
"""
def __init__(self, cmd, timeout, output=None):
_Timeout.__init__(self, timeout, _use_timer=False)
_Timeout.__init__(self, None)
self.cmd = cmd
self.timeout = timeout
self.seconds = timeout
self.output = output
@property
def timeout(self):
return self.seconds
def __str__(self):
return ("Command '%s' timed out after %s seconds" %
(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
from gevent._compat import string_types
from gevent.hub import getcurrent, _NONE, get_hub
__all__ = ['Timeout',
'with_timeout']
__all__ = [
'Timeout',
'with_timeout',
]
class _FakeTimer(object):
......@@ -26,8 +28,22 @@ class _FakeTimer(object):
# without allocating any native resources. This is useful for timeouts
# that will never expire.
# 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):
# pylint:disable=unused-argument
......@@ -51,23 +67,30 @@ _FakeTimer = _FakeTimer()
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.start()
try:
... # exception will be raised here, after *seconds* passed since start() call
finally:
timeout.cancel()
timeout.close()
.. note:: If the code that the timeout was protecting finishes
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.
.. note::
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
>>> gevent.Timeout(0.1).start()
......@@ -76,14 +99,41 @@ class Timeout(BaseException):
...
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:
pass # ... code block ...
This is equivalent to the try/finally block above with one additional feature:
if *exception* is the literal ``False``, the timeout is still raised, but the context manager
suppresses it, so the code outside the with-block won't see it.
This is equivalent to the try/finally block above with one
additional feature: if *exception* is the literal ``False``, the
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
support a *timeout* parameter themselves::
......@@ -96,9 +146,14 @@ class Timeout(BaseException):
else:
... # a line was read within 5 seconds
.. caution:: If ``readline()`` above catches and doesn't re-raise :class:`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.
.. caution::
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
not be the one you have set (a calling function may have set its
......@@ -112,41 +167,49 @@ class Timeout(BaseException):
except Timeout as t:
if t is not 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
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
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,
_use_timer=True, _one_shot=False):
_one_shot=False):
BaseException.__init__(self)
self.seconds = seconds
self.exception = exception
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
# desired; this avoids some CFFI interactions on PyPy that can lead to a
# 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
# and https://github.com/gevent/gevent/issues/618.
# Plus, in general, it should be more efficient
self.timer = _FakeTimer
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)
def start(self):
......@@ -207,18 +270,29 @@ class Timeout(BaseException):
@property
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
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()
if self._one_shot:
self.close()
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.close()
self.timer = _FakeTimer
def __repr__(self):
classname = type(self).__name__
......@@ -251,6 +325,9 @@ class Timeout(BaseException):
return '%s second%s: %s' % (self.seconds, suffix, self.exception)
def __enter__(self):
"""
Start and return the timer. If the timer is already started, just return it.
"""
if not self.pending:
self.start()
return self
......@@ -258,6 +335,7 @@ class Timeout(BaseException):
def __exit__(self, typ, value, tb):
"""
Stop the timer.
.. versionchanged:: 1.3a1
The underlying native timer is also stopped. This object cannot be
used again.
......
......@@ -19,6 +19,7 @@ MAPPING = {
'gevent.threading': 'threading',
'gevent.builtins': 'builtins' if six.PY3 else '__builtin__',
'gevent.signal': 'signal',
'gevent.time': 'time',
}
......@@ -43,13 +44,16 @@ COULD_BE_MISSING = {
'subprocess': ['_posixsubprocess'],
}
NO_ALL = ['gevent.threading',
'gevent._util',
'gevent._compat',
'gevent._socketcommon',
'gevent._fileobjectcommon', 'gevent._fileobjectposix',
'gevent._tblib',
'gevent._corecffi']
NO_ALL = [
'gevent.threading',
'gevent._util',
'gevent._compat',
'gevent._socketcommon',
'gevent._fileobjectcommon',
'gevent._fileobjectposix',
'gevent._tblib',
'gevent._corecffi',
]
# 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,
......@@ -71,7 +75,7 @@ class Test(unittest.TestCase):
def check_all(self):
"Check that __all__ is present and does not contain invalid entries"
if not hasattr(self.module, '__all__'):
assert self.modname in NO_ALL
self.assertIn(self.modname, NO_ALL)
return
names = {}
six.exec_("from %s import *" % self.modname, names)
......@@ -89,7 +93,7 @@ class Test(unittest.TestCase):
raise AssertionError('%r has __implements__ but no stdlib counterpart' % self.modname)
def set_stdlib_all(self):
assert self.stdlib_module is not None
self.assertIsNotNone(self.stdlib_module)
self.stdlib_has_all = True
self.stdlib_all = getattr(self.stdlib_module, '__all__', None)
if self.stdlib_all is None:
......@@ -115,7 +119,7 @@ class Test(unittest.TestCase):
item = getattr(self.module, name)
try:
stdlib_item = getattr(self.stdlib_module, name)
assert item is not stdlib_item, (name, item, stdlib_item)
self.assertIsNot(item, stdlib_item)
except AttributeError:
if name not in COULD_BE_MISSING.get(self.stdlib_name, []):
raise
......@@ -125,7 +129,7 @@ class Test(unittest.TestCase):
for name in self.__imports__:
item = getattr(self.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):
"""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