Author: Philip Jenvey <pjen...@underboss.org> Branch: vendor/stdlib-3.6 Changeset: r89221:ace7255d9a26 Date: 2016-12-23 22:39 -0800 http://bitbucket.org/pypy/pypy/changeset/ace7255d9a26/
Log: update stdlib to v3.6.0 (41df79263a11) diff too long, truncating to 2000 out of 129052 lines diff --git a/lib-python/3/_collections_abc.py b/lib-python/3/_collections_abc.py --- a/lib-python/3/_collections_abc.py +++ b/lib-python/3/_collections_abc.py @@ -9,9 +9,10 @@ from abc import ABCMeta, abstractmethod import sys -__all__ = ["Awaitable", "Coroutine", "AsyncIterable", "AsyncIterator", - "Hashable", "Iterable", "Iterator", "Generator", - "Sized", "Container", "Callable", +__all__ = ["Awaitable", "Coroutine", + "AsyncIterable", "AsyncIterator", "AsyncGenerator", + "Hashable", "Iterable", "Iterator", "Generator", "Reversible", + "Sized", "Container", "Callable", "Collection", "Set", "MutableSet", "Mapping", "MutableMapping", "MappingView", "KeysView", "ItemsView", "ValuesView", @@ -29,8 +30,8 @@ # so that they will pass tests like: # it = iter(somebytearray) # assert isinstance(it, Iterable) -# Note: in other implementations, these types many not be distinct -# and they make have their own implementation specific types that +# Note: in other implementations, these types might not be distinct +# and they may have their own implementation specific types that # are not included on this list. bytes_iterator = type(iter(b'')) bytearray_iterator = type(iter(bytearray())) @@ -41,6 +42,7 @@ list_iterator = type(iter([])) list_reverseiterator = type(iter(reversed([]))) range_iterator = type(iter(range(0))) +longrange_iterator = type(iter(range(1 << 1000))) set_iterator = type(iter(set())) str_iterator = type(iter("")) tuple_iterator = type(iter(())) @@ -58,10 +60,27 @@ coroutine = type(_coro) _coro.close() # Prevent ResourceWarning del _coro +## asynchronous generator ## +async def _ag(): yield +_ag = _ag() +async_generator = type(_ag) +del _ag ### ONE-TRICK PONIES ### +def _check_methods(C, *methods): + mro = C.__mro__ + for method in methods: + for B in mro: + if method in B.__dict__: + if B.__dict__[method] is None: + return NotImplemented + break + else: + return NotImplemented + return True + class Hashable(metaclass=ABCMeta): __slots__ = () @@ -73,11 +92,7 @@ @classmethod def __subclasshook__(cls, C): if cls is Hashable: - for B in C.__mro__: - if "__hash__" in B.__dict__: - if B.__dict__["__hash__"]: - return True - break + return _check_methods(C, "__hash__") return NotImplemented @@ -92,11 +107,7 @@ @classmethod def __subclasshook__(cls, C): if cls is Awaitable: - for B in C.__mro__: - if "__await__" in B.__dict__: - if B.__dict__["__await__"]: - return True - break + return _check_methods(C, "__await__") return NotImplemented @@ -137,14 +148,7 @@ @classmethod def __subclasshook__(cls, C): if cls is Coroutine: - mro = C.__mro__ - for method in ('__await__', 'send', 'throw', 'close'): - for base in mro: - if method in base.__dict__: - break - else: - return NotImplemented - return True + return _check_methods(C, '__await__', 'send', 'throw', 'close') return NotImplemented @@ -162,8 +166,7 @@ @classmethod def __subclasshook__(cls, C): if cls is AsyncIterable: - if any("__aiter__" in B.__dict__ for B in C.__mro__): - return True + return _check_methods(C, "__aiter__") return NotImplemented @@ -182,12 +185,61 @@ @classmethod def __subclasshook__(cls, C): if cls is AsyncIterator: - if (any("__anext__" in B.__dict__ for B in C.__mro__) and - any("__aiter__" in B.__dict__ for B in C.__mro__)): - return True + return _check_methods(C, "__anext__", "__aiter__") return NotImplemented +class AsyncGenerator(AsyncIterator): + + __slots__ = () + + async def __anext__(self): + """Return the next item from the asynchronous generator. + When exhausted, raise StopAsyncIteration. + """ + return await self.asend(None) + + @abstractmethod + async def asend(self, value): + """Send a value into the asynchronous generator. + Return next yielded value or raise StopAsyncIteration. + """ + raise StopAsyncIteration + + @abstractmethod + async def athrow(self, typ, val=None, tb=None): + """Raise an exception in the asynchronous generator. + Return next yielded value or raise StopAsyncIteration. + """ + if val is None: + if tb is None: + raise typ + val = typ() + if tb is not None: + val = val.with_traceback(tb) + raise val + + async def aclose(self): + """Raise GeneratorExit inside coroutine. + """ + try: + await self.athrow(GeneratorExit) + except (GeneratorExit, StopAsyncIteration): + pass + else: + raise RuntimeError("asynchronous generator ignored GeneratorExit") + + @classmethod + def __subclasshook__(cls, C): + if cls is AsyncGenerator: + return _check_methods(C, '__aiter__', '__anext__', + 'asend', 'athrow', 'aclose') + return NotImplemented + + +AsyncGenerator.register(async_generator) + + class Iterable(metaclass=ABCMeta): __slots__ = () @@ -200,8 +252,7 @@ @classmethod def __subclasshook__(cls, C): if cls is Iterable: - if any("__iter__" in B.__dict__ for B in C.__mro__): - return True + return _check_methods(C, "__iter__") return NotImplemented @@ -220,9 +271,7 @@ @classmethod def __subclasshook__(cls, C): if cls is Iterator: - if (any("__next__" in B.__dict__ for B in C.__mro__) and - any("__iter__" in B.__dict__ for B in C.__mro__)): - return True + return _check_methods(C, '__iter__', '__next__') return NotImplemented Iterator.register(bytes_iterator) @@ -234,12 +283,29 @@ Iterator.register(list_iterator) Iterator.register(list_reverseiterator) Iterator.register(range_iterator) +Iterator.register(longrange_iterator) Iterator.register(set_iterator) Iterator.register(str_iterator) Iterator.register(tuple_iterator) Iterator.register(zip_iterator) +class Reversible(Iterable): + + __slots__ = () + + @abstractmethod + def __reversed__(self): + while False: + yield None + + @classmethod + def __subclasshook__(cls, C): + if cls is Reversible: + return _check_methods(C, "__reversed__", "__iter__") + return NotImplemented + + class Generator(Iterator): __slots__ = () @@ -283,17 +349,10 @@ @classmethod def __subclasshook__(cls, C): if cls is Generator: - mro = C.__mro__ - for method in ('__iter__', '__next__', 'send', 'throw', 'close'): - for base in mro: - if method in base.__dict__: - break - else: - return NotImplemented - return True + return _check_methods(C, '__iter__', '__next__', + 'send', 'throw', 'close') return NotImplemented - Generator.register(generator) @@ -308,8 +367,7 @@ @classmethod def __subclasshook__(cls, C): if cls is Sized: - if any("__len__" in B.__dict__ for B in C.__mro__): - return True + return _check_methods(C, "__len__") return NotImplemented @@ -324,10 +382,18 @@ @classmethod def __subclasshook__(cls, C): if cls is Container: - if any("__contains__" in B.__dict__ for B in C.__mro__): - return True + return _check_methods(C, "__contains__") return NotImplemented +class Collection(Sized, Iterable, Container): + + __slots__ = () + + @classmethod + def __subclasshook__(cls, C): + if cls is Collection: + return _check_methods(C, "__len__", "__iter__", "__contains__") + return NotImplemented class Callable(metaclass=ABCMeta): @@ -340,15 +406,14 @@ @classmethod def __subclasshook__(cls, C): if cls is Callable: - if any("__call__" in B.__dict__ for B in C.__mro__): - return True + return _check_methods(C, "__call__") return NotImplemented ### SETS ### -class Set(Sized, Iterable, Container): +class Set(Collection): """A set is a finite, iterable container. @@ -573,7 +638,7 @@ ### MAPPINGS ### -class Mapping(Sized, Iterable, Container): +class Mapping(Collection): __slots__ = () @@ -621,6 +686,8 @@ return NotImplemented return dict(self.items()) == dict(other.items()) + __reversed__ = None + Mapping.register(mappingproxy) @@ -670,7 +737,7 @@ except KeyError: return False else: - return v == value + return v is value or v == value def __iter__(self): for key in self._mapping: @@ -685,7 +752,8 @@ def __contains__(self, value): for key in self._mapping: - if value == self._mapping[key]: + v = self._mapping[key] + if v is value or v == value: return True return False @@ -794,7 +862,7 @@ ### SEQUENCES ### -class Sequence(Sized, Iterable, Container): +class Sequence(Reversible, Collection): """All the operations on a read-only sequence. @@ -820,7 +888,7 @@ def __contains__(self, value): for v in self: - if v == value: + if v is value or v == value: return True return False diff --git a/lib-python/3/_compat_pickle.py b/lib-python/3/_compat_pickle.py --- a/lib-python/3/_compat_pickle.py +++ b/lib-python/3/_compat_pickle.py @@ -242,3 +242,10 @@ for excname in PYTHON3_OSERROR_EXCEPTIONS: REVERSE_NAME_MAPPING[('builtins', excname)] = ('exceptions', 'OSError') + +PYTHON3_IMPORTERROR_EXCEPTIONS = ( + 'ModuleNotFoundError', +) + +for excname in PYTHON3_IMPORTERROR_EXCEPTIONS: + REVERSE_NAME_MAPPING[('builtins', excname)] = ('exceptions', 'ImportError') diff --git a/lib-python/3/_osx_support.py b/lib-python/3/_osx_support.py --- a/lib-python/3/_osx_support.py +++ b/lib-python/3/_osx_support.py @@ -210,7 +210,7 @@ # Do not alter a config var explicitly overridden by env var if cv in _config_vars and cv not in os.environ: flags = _config_vars[cv] - flags = re.sub('-arch\s+\w+\s', ' ', flags, re.ASCII) + flags = re.sub(r'-arch\s+\w+\s', ' ', flags, re.ASCII) flags = re.sub('-isysroot [^ \t]*', ' ', flags) _save_modified_value(_config_vars, cv, flags) @@ -232,7 +232,7 @@ if 'CC' in os.environ: return _config_vars - if re.search('-arch\s+ppc', _config_vars['CFLAGS']) is not None: + if re.search(r'-arch\s+ppc', _config_vars['CFLAGS']) is not None: # NOTE: Cannot use subprocess here because of bootstrap # issues when building Python itself status = os.system( @@ -251,7 +251,7 @@ for cv in _UNIVERSAL_CONFIG_VARS: if cv in _config_vars and cv not in os.environ: flags = _config_vars[cv] - flags = re.sub('-arch\s+ppc\w*\s', ' ', flags) + flags = re.sub(r'-arch\s+ppc\w*\s', ' ', flags) _save_modified_value(_config_vars, cv, flags) return _config_vars @@ -267,7 +267,7 @@ for cv in _UNIVERSAL_CONFIG_VARS: if cv in _config_vars and '-arch' in _config_vars[cv]: flags = _config_vars[cv] - flags = re.sub('-arch\s+\w+\s', ' ', flags) + flags = re.sub(r'-arch\s+\w+\s', ' ', flags) flags = flags + ' ' + arch _save_modified_value(_config_vars, cv, flags) @@ -465,7 +465,7 @@ machine = 'fat' - archs = re.findall('-arch\s+(\S+)', cflags) + archs = re.findall(r'-arch\s+(\S+)', cflags) archs = tuple(sorted(set(archs))) if len(archs) == 1: diff --git a/lib-python/3/_pydecimal.py b/lib-python/3/_pydecimal.py --- a/lib-python/3/_pydecimal.py +++ b/lib-python/3/_pydecimal.py @@ -148,7 +148,7 @@ __name__ = 'decimal' # For pickling __version__ = '1.70' # Highest version of the spec this complies with # See http://speleotrove.com/decimal/ -__libmpdec_version__ = "2.4.1" # compatible libmpdec version +__libmpdec_version__ = "2.4.2" # compatible libmpdec version import math as _math import numbers as _numbers @@ -589,7 +589,7 @@ # From a string # REs insist on real strings, so we can too. if isinstance(value, str): - m = _parser(value.strip()) + m = _parser(value.strip().replace("_", "")) if m is None: if context is None: context = getcontext() @@ -1010,6 +1010,56 @@ """ return DecimalTuple(self._sign, tuple(map(int, self._int)), self._exp) + def as_integer_ratio(self): + """Express a finite Decimal instance in the form n / d. + + Returns a pair (n, d) of integers. When called on an infinity + or NaN, raises OverflowError or ValueError respectively. + + >>> Decimal('3.14').as_integer_ratio() + (157, 50) + >>> Decimal('-123e5').as_integer_ratio() + (-12300000, 1) + >>> Decimal('0.00').as_integer_ratio() + (0, 1) + + """ + if self._is_special: + if self.is_nan(): + raise ValueError("cannot convert NaN to integer ratio") + else: + raise OverflowError("cannot convert Infinity to integer ratio") + + if not self: + return 0, 1 + + # Find n, d in lowest terms such that abs(self) == n / d; + # we'll deal with the sign later. + n = int(self._int) + if self._exp >= 0: + # self is an integer. + n, d = n * 10**self._exp, 1 + else: + # Find d2, d5 such that abs(self) = n / (2**d2 * 5**d5). + d5 = -self._exp + while d5 > 0 and n % 5 == 0: + n //= 5 + d5 -= 1 + + # (n & -n).bit_length() - 1 counts trailing zeros in binary + # representation of n (provided n is nonzero). + d2 = -self._exp + shift2 = min((n & -n).bit_length() - 1, d2) + if shift2: + n >>= shift2 + d2 -= shift2 + + d = 5**d5 << d2 + + if self._sign: + n = -n + return n, d + def __repr__(self): """Represents the number as an instance of Decimal.""" # Invariant: eval(repr(d)) == d @@ -1068,12 +1118,11 @@ return sign + intpart + fracpart + exp def to_eng_string(self, context=None): - """Convert to engineering-type string. - - Engineering notation has an exponent which is a multiple of 3, so there - are up to 3 digits left of the decimal place. - - Same rules for when in exponential and when as a value as in __str__. + """Convert to a string, using engineering notation if an exponent is needed. + + Engineering notation has an exponent which is a multiple of 3. This + can leave up to 3 digits to the left of the decimal place and may + require the addition of either one or two trailing zeros. """ return self.__str__(eng=True, context=context) @@ -4076,7 +4125,7 @@ This will make it round up for that operation. """ rounding = self.rounding - self.rounding= type + self.rounding = type return rounding def create_decimal(self, num='0'): @@ -4085,10 +4134,10 @@ This method implements the to-number operation of the IBM Decimal specification.""" - if isinstance(num, str) and num != num.strip(): + if isinstance(num, str) and (num != num.strip() or '_' in num): return self._raise_error(ConversionSyntax, - "no trailing or leading whitespace is " - "permitted.") + "trailing or leading whitespace and " + "underscores are not permitted.") d = Decimal(num, context=self) if d._isnan() and len(d._int) > self.prec - self.clamp: @@ -4107,7 +4156,7 @@ >>> context.create_decimal_from_float(3.1415926535897932) Traceback (most recent call last): ... - decimal.Inexact + decimal.Inexact: None """ d = Decimal.from_float(f) # An exact conversion @@ -5502,9 +5551,29 @@ return r def to_eng_string(self, a): - """Converts a number to a string, using scientific notation. + """Convert to a string, using engineering notation if an exponent is needed. + + Engineering notation has an exponent which is a multiple of 3. This + can leave up to 3 digits to the left of the decimal place and may + require the addition of either one or two trailing zeros. The operation is not affected by the context. + + >>> ExtendedContext.to_eng_string(Decimal('123E+1')) + '1.23E+3' + >>> ExtendedContext.to_eng_string(Decimal('123E+3')) + '123E+3' + >>> ExtendedContext.to_eng_string(Decimal('123E-10')) + '12.3E-9' + >>> ExtendedContext.to_eng_string(Decimal('-123E-12')) + '-123E-12' + >>> ExtendedContext.to_eng_string(Decimal('7E-7')) + '700E-9' + >>> ExtendedContext.to_eng_string(Decimal('7E+1')) + '70' + >>> ExtendedContext.to_eng_string(Decimal('0E+1')) + '0.00E+3' + """ a = _convert_other(a, raiseit=True) return a.to_eng_string(context=self) diff --git a/lib-python/3/_pyio.py b/lib-python/3/_pyio.py --- a/lib-python/3/_pyio.py +++ b/lib-python/3/_pyio.py @@ -6,7 +6,6 @@ import abc import codecs import errno -import array import stat import sys # Import _thread instead of threading to reduce startup cost @@ -161,6 +160,8 @@ opened in a text mode, and for bytes a BytesIO can be used like a file opened in a binary mode. """ + if not isinstance(file, int): + file = os.fspath(file) if not isinstance(file, (str, bytes, int)): raise TypeError("invalid file: %r" % file) if not isinstance(mode, str): @@ -182,8 +183,8 @@ text = "t" in modes binary = "b" in modes if "U" in modes: - if creating or writing or appending: - raise ValueError("can't use U and writing mode at once") + if creating or writing or appending or updating: + raise ValueError("mode U cannot be combined with 'x', 'w', 'a', or '+'") import warnings warnings.warn("'U' mode is deprecated", DeprecationWarning, 2) @@ -1516,7 +1517,7 @@ if self._fd >= 0 and self._closefd and not self.closed: import warnings warnings.warn('unclosed file %r' % (self,), ResourceWarning, - stacklevel=2) + stacklevel=2, source=self) self.close() def __getstate__(self): diff --git a/lib-python/3/_strptime.py b/lib-python/3/_strptime.py --- a/lib-python/3/_strptime.py +++ b/lib-python/3/_strptime.py @@ -199,12 +199,15 @@ 'f': r"(?P<f>[0-9]{1,6})", 'H': r"(?P<H>2[0-3]|[0-1]\d|\d)", 'I': r"(?P<I>1[0-2]|0[1-9]|[1-9])", + 'G': r"(?P<G>\d\d\d\d)", 'j': r"(?P<j>36[0-6]|3[0-5]\d|[1-2]\d\d|0[1-9]\d|00[1-9]|[1-9]\d|0[1-9]|[1-9])", 'm': r"(?P<m>1[0-2]|0[1-9]|[1-9])", 'M': r"(?P<M>[0-5]\d|\d)", 'S': r"(?P<S>6[0-1]|[0-5]\d|\d)", 'U': r"(?P<U>5[0-3]|[0-4]\d|\d)", 'w': r"(?P<w>[0-6])", + 'u': r"(?P<u>[1-7])", + 'V': r"(?P<V>5[0-3]|0[1-9]|[1-4]\d|\d)", # W is set below by using 'U' 'y': r"(?P<y>\d\d)", #XXX: Does 'Y' need to worry about having less or more than @@ -299,6 +302,22 @@ return 1 + days_to_week + day_of_week +def _calc_julian_from_V(iso_year, iso_week, iso_weekday): + """Calculate the Julian day based on the ISO 8601 year, week, and weekday. + ISO weeks start on Mondays, with week 01 being the week containing 4 Jan. + ISO week days range from 1 (Monday) to 7 (Sunday). + """ + correction = datetime_date(iso_year, 1, 4).isoweekday() + 3 + ordinal = (iso_week * 7) + iso_weekday - correction + # ordinal may be negative or 0 now, which means the date is in the previous + # calendar year + if ordinal < 1: + ordinal += datetime_date(iso_year, 1, 1).toordinal() + iso_year -= 1 + ordinal -= datetime_date(iso_year, 1, 1).toordinal() + return iso_year, ordinal + + def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"): """Return a 2-tuple consisting of a time struct and an int containing the number of microseconds based on the input string and the @@ -345,15 +364,15 @@ raise ValueError("unconverted data remains: %s" % data_string[found.end():]) - year = None + iso_year = year = None month = day = 1 hour = minute = second = fraction = 0 tz = -1 tzoffset = None # Default to -1 to signify that values not known; not critical to have, # though - week_of_year = -1 - week_of_year_start = -1 + iso_week = week_of_year = None + week_of_year_start = None # weekday and julian defaulted to None so as to signal need to calculate # values weekday = julian = None @@ -375,6 +394,8 @@ year += 1900 elif group_key == 'Y': year = int(found_dict['Y']) + elif group_key == 'G': + iso_year = int(found_dict['G']) elif group_key == 'm': month = int(found_dict['m']) elif group_key == 'B': @@ -420,6 +441,9 @@ weekday = 6 else: weekday -= 1 + elif group_key == 'u': + weekday = int(found_dict['u']) + weekday -= 1 elif group_key == 'j': julian = int(found_dict['j']) elif group_key in ('U', 'W'): @@ -430,6 +454,8 @@ else: # W starts week on Monday. week_of_year_start = 0 + elif group_key == 'V': + iso_week = int(found_dict['V']) elif group_key == 'z': z = found_dict['z'] tzoffset = int(z[1:3]) * 60 + int(z[3:5]) @@ -450,32 +476,61 @@ else: tz = value break + # Deal with the cases where ambiguities arize + # don't assume default values for ISO week/year + if year is None and iso_year is not None: + if iso_week is None or weekday is None: + raise ValueError("ISO year directive '%G' must be used with " + "the ISO week directive '%V' and a weekday " + "directive ('%A', '%a', '%w', or '%u').") + if julian is not None: + raise ValueError("Day of the year directive '%j' is not " + "compatible with ISO year directive '%G'. " + "Use '%Y' instead.") + elif week_of_year is None and iso_week is not None: + if weekday is None: + raise ValueError("ISO week directive '%V' must be used with " + "the ISO year directive '%G' and a weekday " + "directive ('%A', '%a', '%w', or '%u').") + else: + raise ValueError("ISO week directive '%V' is incompatible with " + "the year directive '%Y'. Use the ISO year '%G' " + "instead.") + leap_year_fix = False if year is None and month == 2 and day == 29: year = 1904 # 1904 is first leap year of 20th century leap_year_fix = True elif year is None: year = 1900 + + # If we know the week of the year and what day of that week, we can figure # out the Julian day of the year. - if julian is None and week_of_year != -1 and weekday is not None: - week_starts_Mon = True if week_of_year_start == 0 else False - julian = _calc_julian_from_U_or_W(year, week_of_year, weekday, - week_starts_Mon) - if julian <= 0: + if julian is None and weekday is not None: + if week_of_year is not None: + week_starts_Mon = True if week_of_year_start == 0 else False + julian = _calc_julian_from_U_or_W(year, week_of_year, weekday, + week_starts_Mon) + elif iso_year is not None and iso_week is not None: + year, julian = _calc_julian_from_V(iso_year, iso_week, weekday + 1) + if julian is not None and julian <= 0: year -= 1 yday = 366 if calendar.isleap(year) else 365 julian += yday - # Cannot pre-calculate datetime_date() since can change in Julian - # calculation and thus could have different value for the day of the week - # calculation. + if julian is None: + # Cannot pre-calculate datetime_date() since can change in Julian + # calculation and thus could have different value for the day of + # the week calculation. # Need to add 1 to result since first day of the year is 1, not 0. julian = datetime_date(year, month, day).toordinal() - \ datetime_date(year, 1, 1).toordinal() + 1 - else: # Assume that if they bothered to include Julian day it will - # be accurate. - datetime_result = datetime_date.fromordinal((julian - 1) + datetime_date(year, 1, 1).toordinal()) + else: # Assume that if they bothered to include Julian day (or if it was + # calculated above with year/week/weekday) it will be accurate. + datetime_result = datetime_date.fromordinal( + (julian - 1) + + datetime_date(year, 1, 1).toordinal()) year = datetime_result.year month = datetime_result.month day = datetime_result.day diff --git a/lib-python/3/aifc.py b/lib-python/3/aifc.py --- a/lib-python/3/aifc.py +++ b/lib-python/3/aifc.py @@ -257,6 +257,15 @@ _aifc_params = namedtuple('_aifc_params', 'nchannels sampwidth framerate nframes comptype compname') +_aifc_params.nchannels.__doc__ = 'Number of audio channels (1 for mono, 2 for stereo)' +_aifc_params.sampwidth.__doc__ = 'Sample width in bytes' +_aifc_params.framerate.__doc__ = 'Sampling frequency' +_aifc_params.nframes.__doc__ = 'Number of audio frames' +_aifc_params.comptype.__doc__ = 'Compression type ("NONE" for AIFF files)' +_aifc_params.compname.__doc__ = ("""\ +A human-readable version of the compression type +('not compressed' for AIFF files)""") + class Aifc_read: # Variables used in this class: diff --git a/lib-python/3/antigravity.py b/lib-python/3/antigravity.py --- a/lib-python/3/antigravity.py +++ b/lib-python/3/antigravity.py @@ -2,7 +2,7 @@ import webbrowser import hashlib -webbrowser.open("http://xkcd.com/353/") +webbrowser.open("https://xkcd.com/353/") def geohash(latitude, longitude, datedow): '''Compute geohash() using the Munroe algorithm. diff --git a/lib-python/3/argparse.py b/lib-python/3/argparse.py --- a/lib-python/3/argparse.py +++ b/lib-python/3/argparse.py @@ -118,10 +118,16 @@ def __repr__(self): type_name = type(self).__name__ arg_strings = [] + star_args = {} for arg in self._get_args(): arg_strings.append(repr(arg)) for name, value in self._get_kwargs(): - arg_strings.append('%s=%r' % (name, value)) + if name.isidentifier(): + arg_strings.append('%s=%r' % (name, value)) + else: + star_args[name] = value + if star_args: + arg_strings.append('**%s' % repr(star_args)) return '%s(%s)' % (type_name, ', '.join(arg_strings)) def _get_kwargs(self): @@ -204,8 +210,6 @@ if self.parent is not None: self.formatter._indent() join = self.formatter._join_parts - for func, args in self.items: - func(*args) item_help = join([func(*args) for func, args in self.items]) if self.parent is not None: self.formatter._dedent() diff --git a/lib-python/3/ast.py b/lib-python/3/ast.py --- a/lib-python/3/ast.py +++ b/lib-python/3/ast.py @@ -35,6 +35,8 @@ return compile(source, filename, mode, PyCF_ONLY_AST) +_NUM_TYPES = (int, float, complex) + def literal_eval(node_or_string): """ Safely evaluate an expression node or a string containing a Python @@ -47,7 +49,9 @@ if isinstance(node_or_string, Expression): node_or_string = node_or_string.body def _convert(node): - if isinstance(node, (Str, Bytes)): + if isinstance(node, Constant): + return node.value + elif isinstance(node, (Str, Bytes)): return node.s elif isinstance(node, Num): return node.n @@ -62,24 +66,21 @@ in zip(node.keys, node.values)) elif isinstance(node, NameConstant): return node.value - elif isinstance(node, UnaryOp) and \ - isinstance(node.op, (UAdd, USub)) and \ - isinstance(node.operand, (Num, UnaryOp, BinOp)): + elif isinstance(node, UnaryOp) and isinstance(node.op, (UAdd, USub)): operand = _convert(node.operand) - if isinstance(node.op, UAdd): - return + operand - else: - return - operand - elif isinstance(node, BinOp) and \ - isinstance(node.op, (Add, Sub)) and \ - isinstance(node.right, (Num, UnaryOp, BinOp)) and \ - isinstance(node.left, (Num, UnaryOp, BinOp)): + if isinstance(operand, _NUM_TYPES): + if isinstance(node.op, UAdd): + return + operand + else: + return - operand + elif isinstance(node, BinOp) and isinstance(node.op, (Add, Sub)): left = _convert(node.left) right = _convert(node.right) - if isinstance(node.op, Add): - return left + right - else: - return left - right + if isinstance(left, _NUM_TYPES) and isinstance(right, _NUM_TYPES): + if isinstance(node.op, Add): + return left + right + else: + return left - right raise ValueError('malformed node or string: ' + repr(node)) return _convert(node_or_string) @@ -196,12 +197,19 @@ """ if not isinstance(node, (AsyncFunctionDef, FunctionDef, ClassDef, Module)): raise TypeError("%r can't have docstrings" % node.__class__.__name__) - if node.body and isinstance(node.body[0], Expr) and \ - isinstance(node.body[0].value, Str): - if clean: - import inspect - return inspect.cleandoc(node.body[0].value.s) - return node.body[0].value.s + if not(node.body and isinstance(node.body[0], Expr)): + return + node = node.body[0].value + if isinstance(node, Str): + text = node.s + elif isinstance(node, Constant) and isinstance(node.value, str): + text = node.value + else: + return + if clean: + import inspect + text = inspect.cleandoc(text) + return text def walk(node): diff --git a/lib-python/3/asynchat.py b/lib-python/3/asynchat.py --- a/lib-python/3/asynchat.py +++ b/lib-python/3/asynchat.py @@ -285,35 +285,6 @@ return result -class fifo: - def __init__(self, list=None): - import warnings - warnings.warn('fifo class will be removed in Python 3.6', - DeprecationWarning, stacklevel=2) - if not list: - self.list = deque() - else: - self.list = deque(list) - - def __len__(self): - return len(self.list) - - def is_empty(self): - return not self.list - - def first(self): - return self.list[0] - - def push(self, data): - self.list.append(data) - - def pop(self): - if self.list: - return (1, self.list.popleft()) - else: - return (0, None) - - # Given 'haystack', see if any prefix of 'needle' is at its end. This # assumes an exact match has already been checked. Return the number of # characters matched. diff --git a/lib-python/3/asyncio/base_events.py b/lib-python/3/asyncio/base_events.py --- a/lib-python/3/asyncio/base_events.py +++ b/lib-python/3/asyncio/base_events.py @@ -13,7 +13,6 @@ to modify the meaning of the API call itself. """ - import collections import concurrent.futures import heapq @@ -28,6 +27,7 @@ import traceback import sys import warnings +import weakref from . import compat from . import coroutines @@ -41,9 +41,6 @@ __all__ = ['BaseEventLoop'] -# Argument for default thread pool executor creation. -_MAX_WORKERS = 5 - # Minimum number of _scheduled timer handles before cleanup of # cancelled handles is performed. _MIN_SCHEDULED_TIMER_HANDLES = 100 @@ -60,7 +57,7 @@ def _format_handle(handle): cb = handle._callback - if inspect.ismethod(cb) and isinstance(cb.__self__, tasks.Task): + if isinstance(getattr(cb, '__self__', None), tasks.Task): # format the task return repr(cb.__self__) else: @@ -76,12 +73,29 @@ return repr(fd) -# Linux's sock.type is a bitmask that can include extra info about socket. -_SOCKET_TYPE_MASK = 0 -if hasattr(socket, 'SOCK_NONBLOCK'): - _SOCKET_TYPE_MASK |= socket.SOCK_NONBLOCK -if hasattr(socket, 'SOCK_CLOEXEC'): - _SOCKET_TYPE_MASK |= socket.SOCK_CLOEXEC +def _set_reuseport(sock): + if not hasattr(socket, 'SO_REUSEPORT'): + raise ValueError('reuse_port not supported by socket module') + else: + try: + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + except OSError: + raise ValueError('reuse_port not supported by socket module, ' + 'SO_REUSEPORT defined but not implemented.') + + +def _is_stream_socket(sock): + # Linux's socket.type is a bitmask that can include extra info + # about socket, therefore we can't do simple + # `sock_type == socket.SOCK_STREAM`. + return (sock.type & socket.SOCK_STREAM) == socket.SOCK_STREAM + + +def _is_dgram_socket(sock): + # Linux's socket.type is a bitmask that can include extra info + # about socket, therefore we can't do simple + # `sock_type == socket.SOCK_DGRAM`. + return (sock.type & socket.SOCK_DGRAM) == socket.SOCK_DGRAM def _ipaddr_info(host, port, family, type, proto): @@ -94,8 +108,12 @@ host is None: return None - type &= ~_SOCKET_TYPE_MASK if type == socket.SOCK_STREAM: + # Linux only: + # getaddrinfo() can raise when socket.type is a bit mask. + # So if socket.type is a bit mask of SOCK_STREAM, and say + # SOCK_NONBLOCK, we simply return None, which will trigger + # a call to getaddrinfo() letting it process this request. proto = socket.IPPROTO_TCP elif type == socket.SOCK_DGRAM: proto = socket.IPPROTO_UDP @@ -104,27 +122,21 @@ if port is None: port = 0 - elif isinstance(port, bytes): - if port == b'': - port = 0 - else: - try: - port = int(port) - except ValueError: - # Might be a service name like b"http". - port = socket.getservbyname(port.decode('ascii')) - elif isinstance(port, str): - if port == '': - port = 0 - else: - try: - port = int(port) - except ValueError: - # Might be a service name like "http". - port = socket.getservbyname(port) + elif isinstance(port, bytes) and port == b'': + port = 0 + elif isinstance(port, str) and port == '': + port = 0 + else: + # If port's a service name like "http", don't skip getaddrinfo. + try: + port = int(port) + except (TypeError, ValueError): + return None if family == socket.AF_UNSPEC: - afs = [socket.AF_INET, socket.AF_INET6] + afs = [socket.AF_INET] + if hasattr(socket, 'AF_INET6'): + afs.append(socket.AF_INET6) else: afs = [family] @@ -242,6 +254,17 @@ self._task_factory = None self._coroutine_wrapper_set = False + if hasattr(sys, 'get_asyncgen_hooks'): + # Python >= 3.6 + # A weak set of all asynchronous generators that are + # being iterated by the loop. + self._asyncgens = weakref.WeakSet() + else: + self._asyncgens = None + + # Set to True when `loop.shutdown_asyncgens` is called. + self._asyncgens_shutdown_called = False + def __repr__(self): return ('<%s running=%s closed=%s debug=%s>' % (self.__class__.__name__, self.is_running(), @@ -333,14 +356,67 @@ if self._closed: raise RuntimeError('Event loop is closed') + def _asyncgen_finalizer_hook(self, agen): + self._asyncgens.discard(agen) + if not self.is_closed(): + self.create_task(agen.aclose()) + # Wake up the loop if the finalizer was called from + # a different thread. + self._write_to_self() + + def _asyncgen_firstiter_hook(self, agen): + if self._asyncgens_shutdown_called: + warnings.warn( + "asynchronous generator {!r} was scheduled after " + "loop.shutdown_asyncgens() call".format(agen), + ResourceWarning, source=self) + + self._asyncgens.add(agen) + + @coroutine + def shutdown_asyncgens(self): + """Shutdown all active asynchronous generators.""" + self._asyncgens_shutdown_called = True + + if self._asyncgens is None or not len(self._asyncgens): + # If Python version is <3.6 or we don't have any asynchronous + # generators alive. + return + + closing_agens = list(self._asyncgens) + self._asyncgens.clear() + + shutdown_coro = tasks.gather( + *[ag.aclose() for ag in closing_agens], + return_exceptions=True, + loop=self) + + results = yield from shutdown_coro + for result, agen in zip(results, closing_agens): + if isinstance(result, Exception): + self.call_exception_handler({ + 'message': 'an error occurred during closing of ' + 'asynchronous generator {!r}'.format(agen), + 'exception': result, + 'asyncgen': agen + }) + def run_forever(self): """Run until stop() is called.""" self._check_closed() if self.is_running(): - raise RuntimeError('Event loop is running.') + raise RuntimeError('This event loop is already running') + if events._get_running_loop() is not None: + raise RuntimeError( + 'Cannot run the event loop while another loop is running') self._set_coroutine_wrapper(self._debug) self._thread_id = threading.get_ident() + if self._asyncgens is not None: + old_agen_hooks = sys.get_asyncgen_hooks() + sys.set_asyncgen_hooks(firstiter=self._asyncgen_firstiter_hook, + finalizer=self._asyncgen_finalizer_hook) try: + events._set_running_loop(self) while True: self._run_once() if self._stopping: @@ -348,7 +424,10 @@ finally: self._stopping = False self._thread_id = None + events._set_running_loop(None) self._set_coroutine_wrapper(False) + if self._asyncgens is not None: + sys.set_asyncgen_hooks(*old_agen_hooks) def run_until_complete(self, future): """Run until the Future is done. @@ -363,7 +442,7 @@ """ self._check_closed() - new_task = not isinstance(future, futures.Future) + new_task = not futures.isfuture(future) future = tasks.ensure_future(future, loop=self) if new_task: # An exception is raised if the future didn't complete, so there @@ -426,7 +505,8 @@ if compat.PY34: def __del__(self): if not self.is_closed(): - warnings.warn("unclosed event loop %r" % self, ResourceWarning) + warnings.warn("unclosed event loop %r" % self, ResourceWarning, + source=self) if not self.is_running(): self.close() @@ -469,12 +549,10 @@ Absolute time corresponds to the event loop's time() method. """ - if (coroutines.iscoroutine(callback) - or coroutines.iscoroutinefunction(callback)): - raise TypeError("coroutines cannot be used with call_at()") self._check_closed() if self._debug: self._check_thread() + self._check_callback(callback, 'call_at') timer = events.TimerHandle(when, callback, args, self) if timer._source_traceback: del timer._source_traceback[-1] @@ -492,18 +570,27 @@ Any positional arguments after the callback will be passed to the callback when it is called. """ + self._check_closed() if self._debug: self._check_thread() + self._check_callback(callback, 'call_soon') handle = self._call_soon(callback, args) if handle._source_traceback: del handle._source_traceback[-1] return handle + def _check_callback(self, callback, method): + if (coroutines.iscoroutine(callback) or + coroutines.iscoroutinefunction(callback)): + raise TypeError( + "coroutines cannot be used with {}()".format(method)) + if not callable(callback): + raise TypeError( + 'a callable object was expected by {}(), got {!r}'.format( + method, callback)) + + def _call_soon(self, callback, args): - if (coroutines.iscoroutine(callback) - or coroutines.iscoroutinefunction(callback)): - raise TypeError("coroutines cannot be used with call_soon()") - self._check_closed() handle = events.Handle(callback, args, self) if handle._source_traceback: del handle._source_traceback[-1] @@ -529,6 +616,9 @@ def call_soon_threadsafe(self, callback, *args): """Like call_soon(), but thread-safe.""" + self._check_closed() + if self._debug: + self._check_callback(callback, 'call_soon_threadsafe') handle = self._call_soon(callback, args) if handle._source_traceback: del handle._source_traceback[-1] @@ -536,22 +626,13 @@ return handle def run_in_executor(self, executor, func, *args): - if (coroutines.iscoroutine(func) - or coroutines.iscoroutinefunction(func)): - raise TypeError("coroutines cannot be used with run_in_executor()") self._check_closed() - if isinstance(func, events.Handle): - assert not args - assert not isinstance(func, events.TimerHandle) - if func._cancelled: - f = self.create_future() - f.set_result(None) - return f - func, args = func._callback, func._args + if self._debug: + self._check_callback(func, 'run_in_executor') if executor is None: executor = self._default_executor if executor is None: - executor = concurrent.futures.ThreadPoolExecutor(_MAX_WORKERS) + executor = concurrent.futures.ThreadPoolExecutor() self._default_executor = executor return futures.wrap_future(executor.submit(func, *args), loop=self) @@ -703,11 +784,19 @@ raise OSError('Multiple exceptions: {}'.format( ', '.join(str(exc) for exc in exceptions))) - elif sock is None: - raise ValueError( - 'host and port was not specified and no sock specified') - - sock.setblocking(False) + else: + if sock is None: + raise ValueError( + 'host and port was not specified and no sock specified') + if not _is_stream_socket(sock): + # We allow AF_INET, AF_INET6, AF_UNIX as long as they + # are SOCK_STREAM. + # We support passing AF_UNIX sockets even though we have + # a dedicated API for that: create_unix_connection. + # Disallowing AF_UNIX in this method, breaks backwards + # compatibility. + raise ValueError( + 'A Stream Socket was expected, got {!r}'.format(sock)) transport, protocol = yield from self._create_connection_transport( sock, protocol_factory, ssl, server_hostname) @@ -721,14 +810,17 @@ @coroutine def _create_connection_transport(self, sock, protocol_factory, ssl, - server_hostname): + server_hostname, server_side=False): + + sock.setblocking(False) + protocol = protocol_factory() waiter = self.create_future() if ssl: sslcontext = None if isinstance(ssl, bool) else ssl transport = self._make_ssl_transport( sock, protocol, sslcontext, waiter, - server_side=False, server_hostname=server_hostname) + server_side=server_side, server_hostname=server_hostname) else: transport = self._make_socket_transport(sock, protocol, waiter) @@ -748,6 +840,9 @@ allow_broadcast=None, sock=None): """Create datagram connection.""" if sock is not None: + if not _is_dgram_socket(sock): + raise ValueError( + 'A UDP Socket was expected, got {!r}'.format(sock)) if (local_addr or remote_addr or family or proto or flags or reuse_address or reuse_port or allow_broadcast): @@ -813,12 +908,7 @@ sock.setsockopt( socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) if reuse_port: - if not hasattr(socket, 'SO_REUSEPORT'): - raise ValueError( - 'reuse_port not supported by socket module') - else: - sock.setsockopt( - socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + _set_reuseport(sock) if allow_broadcast: sock.setsockopt( socket.SOL_SOCKET, socket.SO_BROADCAST, 1) @@ -941,12 +1031,7 @@ sock.setsockopt( socket.SOL_SOCKET, socket.SO_REUSEADDR, True) if reuse_port: - if not hasattr(socket, 'SO_REUSEPORT'): - raise ValueError( - 'reuse_port not supported by socket module') - else: - sock.setsockopt( - socket.SOL_SOCKET, socket.SO_REUSEPORT, True) + _set_reuseport(sock) # Disable IPv4/IPv6 dual stack support (enabled by # default on Linux) which makes a single socket # listen on both address families. @@ -968,18 +1053,44 @@ else: if sock is None: raise ValueError('Neither host/port nor sock were specified') + if not _is_stream_socket(sock): + raise ValueError( + 'A Stream Socket was expected, got {!r}'.format(sock)) sockets = [sock] server = Server(self, sockets) for sock in sockets: sock.listen(backlog) sock.setblocking(False) - self._start_serving(protocol_factory, sock, ssl, server) + self._start_serving(protocol_factory, sock, ssl, server, backlog) if self._debug: logger.info("%r is serving", server) return server @coroutine + def connect_accepted_socket(self, protocol_factory, sock, *, ssl=None): + """Handle an accepted connection. + + This is used by servers that accept connections outside of + asyncio but that use asyncio to handle connections. + + This method is a coroutine. When completed, the coroutine + returns a (transport, protocol) pair. + """ + if not _is_stream_socket(sock): + raise ValueError( + 'A Stream Socket was expected, got {!r}'.format(sock)) + + transport, protocol = yield from self._create_connection_transport( + sock, protocol_factory, ssl, '', server_side=True) + if self._debug: + # Get the socket from the transport because SSL transport closes + # the old socket and creates a new SSL socket + sock = transport.get_extra_info('socket') + logger.debug("%r handled: (%r, %r)", sock, transport, protocol) + return transport, protocol + + @coroutine def connect_read_pipe(self, protocol_factory, pipe): protocol = protocol_factory() waiter = self.create_future() @@ -1048,7 +1159,7 @@ transport = yield from self._make_subprocess_transport( protocol, cmd, True, stdin, stdout, stderr, bufsize, **kwargs) if self._debug: - logger.info('%s: %r' % (debug_log, transport)) + logger.info('%s: %r', debug_log, transport) return transport, protocol @coroutine @@ -1078,7 +1189,7 @@ protocol, popen_args, False, stdin, stdout, stderr, bufsize, **kwargs) if self._debug: - logger.info('%s: %r' % (debug_log, transport)) + logger.info('%s: %r', debug_log, transport) return transport, protocol def get_exception_handler(self): @@ -1158,7 +1269,9 @@ - 'handle' (optional): Handle instance; - 'protocol' (optional): Protocol instance; - 'transport' (optional): Transport instance; - - 'socket' (optional): Socket instance. + - 'socket' (optional): Socket instance; + - 'asyncgen' (optional): Asynchronous generator that caused + the exception. New keys maybe introduced in the future. diff --git a/lib-python/3/asyncio/base_futures.py b/lib-python/3/asyncio/base_futures.py new file mode 100644 --- /dev/null +++ b/lib-python/3/asyncio/base_futures.py @@ -0,0 +1,71 @@ +__all__ = [] + +import concurrent.futures._base +import reprlib + +from . import events + +Error = concurrent.futures._base.Error +CancelledError = concurrent.futures.CancelledError +TimeoutError = concurrent.futures.TimeoutError + + +class InvalidStateError(Error): + """The operation is not allowed in this state.""" + + +# States for Future. +_PENDING = 'PENDING' +_CANCELLED = 'CANCELLED' +_FINISHED = 'FINISHED' + + +def isfuture(obj): + """Check for a Future. + + This returns True when obj is a Future instance or is advertising + itself as duck-type compatible by setting _asyncio_future_blocking. + See comment in Future for more details. + """ + return (hasattr(obj.__class__, '_asyncio_future_blocking') and + obj._asyncio_future_blocking is not None) + + +def _format_callbacks(cb): + """helper function for Future.__repr__""" + size = len(cb) + if not size: + cb = '' + + def format_cb(callback): + return events._format_callback_source(callback, ()) + + if size == 1: + cb = format_cb(cb[0]) + elif size == 2: + cb = '{}, {}'.format(format_cb(cb[0]), format_cb(cb[1])) + elif size > 2: + cb = '{}, <{} more>, {}'.format(format_cb(cb[0]), + size - 2, + format_cb(cb[-1])) + return 'cb=[%s]' % cb + + +def _future_repr_info(future): + # (Future) -> str + """helper function for Future.__repr__""" + info = [future._state.lower()] + if future._state == _FINISHED: + if future._exception is not None: + info.append('exception={!r}'.format(future._exception)) + else: + # use reprlib to limit the length of the output, especially + # for very long strings + result = reprlib.repr(future._result) + info.append('result={}'.format(result)) + if future._callbacks: + info.append(_format_callbacks(future._callbacks)) + if future._source_traceback: + frame = future._source_traceback[-1] + info.append('created at %s:%s' % (frame[0], frame[1])) + return info diff --git a/lib-python/3/asyncio/base_subprocess.py b/lib-python/3/asyncio/base_subprocess.py --- a/lib-python/3/asyncio/base_subprocess.py +++ b/lib-python/3/asyncio/base_subprocess.py @@ -3,7 +3,6 @@ import warnings from . import compat -from . import futures from . import protocols from . import transports from .coroutines import coroutine @@ -87,6 +86,12 @@ def _start(self, args, shell, stdin, stdout, stderr, bufsize, **kwargs): raise NotImplementedError + def set_protocol(self, protocol): + self._protocol = protocol + + def get_protocol(self): + return self._protocol + def is_closing(self): return self._closed @@ -122,7 +127,8 @@ if compat.PY34: def __del__(self): if not self._closed: - warnings.warn("unclosed transport %r" % self, ResourceWarning) + warnings.warn("unclosed transport %r" % self, ResourceWarning, + source=self) self.close() def get_pid(self): diff --git a/lib-python/3/asyncio/base_tasks.py b/lib-python/3/asyncio/base_tasks.py new file mode 100644 --- /dev/null +++ b/lib-python/3/asyncio/base_tasks.py @@ -0,0 +1,76 @@ +import linecache +import traceback + +from . import base_futures +from . import coroutines + + +def _task_repr_info(task): + info = base_futures._future_repr_info(task) + + if task._must_cancel: + # replace status + info[0] = 'cancelling' + + coro = coroutines._format_coroutine(task._coro) + info.insert(1, 'coro=<%s>' % coro) + + if task._fut_waiter is not None: + info.insert(2, 'wait_for=%r' % task._fut_waiter) + return info + + +def _task_get_stack(task, limit): + frames = [] + try: + # 'async def' coroutines + f = task._coro.cr_frame + except AttributeError: + f = task._coro.gi_frame + if f is not None: + while f is not None: + if limit is not None: + if limit <= 0: + break + limit -= 1 + frames.append(f) + f = f.f_back + frames.reverse() + elif task._exception is not None: + tb = task._exception.__traceback__ + while tb is not None: + if limit is not None: + if limit <= 0: + break + limit -= 1 + frames.append(tb.tb_frame) + tb = tb.tb_next + return frames + + +def _task_print_stack(task, limit, file): + extracted_list = [] + checked = set() + for f in task.get_stack(limit=limit): + lineno = f.f_lineno + co = f.f_code + filename = co.co_filename + name = co.co_name + if filename not in checked: + checked.add(filename) + linecache.checkcache(filename) + line = linecache.getline(filename, lineno, f.f_globals) + extracted_list.append((filename, lineno, name, line)) + exc = task._exception + if not extracted_list: + print('No stack for %r' % task, file=file) + elif exc is not None: + print('Traceback for %r (most recent call last):' % task, + file=file) + else: + print('Stack for %r (most recent call last):' % task, + file=file) + traceback.print_list(extracted_list, file=file) + if exc is not None: + for line in traceback.format_exception_only(exc.__class__, exc): + print(line, file=file, end='') diff --git a/lib-python/3/asyncio/coroutines.py b/lib-python/3/asyncio/coroutines.py --- a/lib-python/3/asyncio/coroutines.py +++ b/lib-python/3/asyncio/coroutines.py @@ -11,7 +11,7 @@ from . import compat from . import events -from . import futures +from . import base_futures from .log import logger @@ -33,12 +33,16 @@ try: _types_coroutine = types.coroutine + _types_CoroutineType = types.CoroutineType except AttributeError: + # Python 3.4 _types_coroutine = None + _types_CoroutineType = None try: _inspect_iscoroutinefunction = inspect.iscoroutinefunction except AttributeError: + # Python 3.4 _inspect_iscoroutinefunction = lambda func: False try: @@ -120,8 +124,8 @@ def send(self, value): return self.gen.send(value) - def throw(self, exc): - return self.gen.throw(exc) + def throw(self, type, value=None, traceback=None): + return self.gen.throw(type, value, traceback) def close(self): return self.gen.close() @@ -204,8 +208,8 @@ @functools.wraps(func) def coro(*args, **kw): res = func(*args, **kw) - if isinstance(res, futures.Future) or inspect.isgenerator(res) or \ - isinstance(res, CoroWrapper): + if (base_futures.isfuture(res) or inspect.isgenerator(res) or + isinstance(res, CoroWrapper)): res = yield from res elif _AwaitableABC is not None: # If 'func' returns an Awaitable (new in 3.5) we @@ -238,19 +242,27 @@ w.__qualname__ = getattr(func, '__qualname__', None) return w - wrapper._is_coroutine = True # For iscoroutinefunction(). + wrapper._is_coroutine = _is_coroutine # For iscoroutinefunction(). return wrapper +# A marker for iscoroutinefunction. +_is_coroutine = object() + + def iscoroutinefunction(func): """Return True if func is a decorated coroutine function.""" - return (getattr(func, '_is_coroutine', False) or + return (getattr(func, '_is_coroutine', None) is _is_coroutine or _inspect_iscoroutinefunction(func)) _COROUTINE_TYPES = (types.GeneratorType, CoroWrapper) if _CoroutineABC is not None: _COROUTINE_TYPES += (_CoroutineABC,) +if _types_CoroutineType is not None: + # Prioritize native coroutine check to speed-up + # asyncio.iscoroutine. + _COROUTINE_TYPES = (_types_CoroutineType,) + _COROUTINE_TYPES def iscoroutine(obj): @@ -261,6 +273,29 @@ def _format_coroutine(coro): assert iscoroutine(coro) + if not hasattr(coro, 'cr_code') and not hasattr(coro, 'gi_code'): + # Most likely a built-in type or a Cython coroutine. + + # Built-in types might not have __qualname__ or __name__. + coro_name = getattr( + coro, '__qualname__', + getattr(coro, '__name__', type(coro).__name__)) + coro_name = '{}()'.format(coro_name) + + running = False + try: + running = coro.cr_running + except AttributeError: + try: + running = coro.gi_running + except AttributeError: + pass + + if running: + return '{} running'.format(coro_name) + else: + return coro_name + coro_name = None if isinstance(coro, CoroWrapper): func = coro.func @@ -271,7 +306,7 @@ func = coro if coro_name is None: - coro_name = events._format_callback(func, ()) + coro_name = events._format_callback(func, (), {}) try: coro_code = coro.gi_code diff --git a/lib-python/3/asyncio/events.py b/lib-python/3/asyncio/events.py --- a/lib-python/3/asyncio/events.py +++ b/lib-python/3/asyncio/events.py @@ -6,6 +6,7 @@ 'get_event_loop_policy', 'set_event_loop_policy', 'get_event_loop', 'set_event_loop', 'new_event_loop', 'get_child_watcher', 'set_child_watcher', + '_set_running_loop', '_get_running_loop', ] import functools @@ -35,23 +36,25 @@ return None -def _format_args(args): - """Format function arguments. +def _format_args_and_kwargs(args, kwargs): + """Format function arguments and keyword arguments. Special case for a single parameter: ('hello',) is formatted as ('hello'). """ # use reprlib to limit the length of the output - args_repr = reprlib.repr(args) - if len(args) == 1 and args_repr.endswith(',)'): - args_repr = args_repr[:-2] + ')' - return args_repr + items = [] + if args: + items.extend(reprlib.repr(arg) for arg in args) + if kwargs: + items.extend('{}={}'.format(k, reprlib.repr(v)) + for k, v in kwargs.items()) + return '(' + ', '.join(items) + ')' -def _format_callback(func, args, suffix=''): +def _format_callback(func, args, kwargs, suffix=''): if isinstance(func, functools.partial): - if args is not None: - suffix = _format_args(args) + suffix - return _format_callback(func.func, func.args, suffix) + suffix = _format_args_and_kwargs(args, kwargs) + suffix + return _format_callback(func.func, func.args, func.keywords, suffix) if hasattr(func, '__qualname__'): func_repr = getattr(func, '__qualname__') @@ -60,14 +63,13 @@ else: func_repr = repr(func) - if args is not None: - func_repr += _format_args(args) + func_repr += _format_args_and_kwargs(args, kwargs) if suffix: func_repr += suffix return func_repr def _format_callback_source(func, args): - func_repr = _format_callback(func, args) + func_repr = _format_callback(func, args, None) source = _get_function_source(func) if source: func_repr += ' at %s:%s' % source @@ -81,7 +83,6 @@ '_source_traceback', '_repr', '__weakref__') def __init__(self, callback, args, loop): - assert not isinstance(callback, Handle), 'A Handle is not a callback' self._loop = loop self._callback = callback self._args = args @@ -248,6 +249,10 @@ """ raise NotImplementedError + def shutdown_asyncgens(self): + """Shutdown all active asynchronous generators.""" + raise NotImplementedError + # Methods scheduling callbacks. All these return Handles. def _timer_handle_cancelled(self, handle): @@ -603,6 +608,30 @@ _lock = threading.Lock() +# A TLS for the running event loop, used by _get_running_loop. +class _RunningLoop(threading.local): + _loop = None +_running_loop = _RunningLoop() + + +def _get_running_loop(): + """Return the running event loop or None. + + This is a low-level function intended to be used by event loops. + This function is thread-specific. + """ + return _running_loop._loop + + +def _set_running_loop(loop): + """Set the running event loop. + + This is a low-level function intended to be used by event loops. + This function is thread-specific. + """ + _running_loop._loop = loop + + def _init_event_loop_policy(): global _event_loop_policy with _lock: @@ -628,7 +657,17 @@ def get_event_loop(): - """Equivalent to calling get_event_loop_policy().get_event_loop().""" + """Return an asyncio event loop. + + When called from a coroutine or a callback (e.g. scheduled with call_soon + or similar API), this function will always return the running event loop. + + If there is no running event loop set, the function will return + the result of `get_event_loop_policy().get_event_loop()` call. + """ + current_loop = _get_running_loop() + if current_loop is not None: + return current_loop return get_event_loop_policy().get_event_loop() diff --git a/lib-python/3/asyncio/futures.py b/lib-python/3/asyncio/futures.py --- a/lib-python/3/asyncio/futures.py +++ b/lib-python/3/asyncio/futures.py @@ -1,35 +1,32 @@ """A Future class similar to the one in PEP 3148.""" -__all__ = ['CancelledError', 'TimeoutError', - 'InvalidStateError', - 'Future', 'wrap_future', - ] +__all__ = ['CancelledError', 'TimeoutError', 'InvalidStateError', + 'Future', 'wrap_future', 'isfuture'] -import concurrent.futures._base +import concurrent.futures import logging -import reprlib import sys import traceback +from . import base_futures from . import compat from . import events -# States for Future. -_PENDING = 'PENDING' -_CANCELLED = 'CANCELLED' -_FINISHED = 'FINISHED' -Error = concurrent.futures._base.Error -CancelledError = concurrent.futures.CancelledError -TimeoutError = concurrent.futures.TimeoutError +CancelledError = base_futures.CancelledError +InvalidStateError = base_futures.InvalidStateError +TimeoutError = base_futures.TimeoutError +isfuture = base_futures.isfuture + + +_PENDING = base_futures._PENDING +_CANCELLED = base_futures._CANCELLED +_FINISHED = base_futures._FINISHED + STACK_DEBUG = logging.DEBUG - 1 # heavy-duty debugging -class InvalidStateError(Error): - """The operation is not allowed in this state.""" - - class _TracebackLogger: """Helper to log a traceback upon destruction if not cleared. @@ -134,7 +131,15 @@ _loop = None _source_traceback = None - _blocking = False # proper use of future (yield vs yield from) + # This field is used for a dual purpose: + # - Its presence is a marker to declare that a class implements + # the Future protocol (i.e. is intended to be duck-type compatible). + # The value must also be not-None, to enable a subclass to declare + # that it is not compatible by setting this to None. + # - It is set by __iter__() below so that Task._step() can tell + # the difference between `yield from Future()` (correct) vs. + # `yield Future()` (incorrect). + _asyncio_future_blocking = False _log_traceback = False # Used for Python 3.4 and later _tb_logger = None # Used for Python 3.3 only @@ -154,45 +159,10 @@ if self._loop.get_debug(): self._source_traceback = traceback.extract_stack(sys._getframe(1)) - def __format_callbacks(self): - cb = self._callbacks - size = len(cb) - if not size: - cb = '' - - def format_cb(callback): - return events._format_callback_source(callback, ()) - - if size == 1: - cb = format_cb(cb[0]) - elif size == 2: - cb = '{}, {}'.format(format_cb(cb[0]), format_cb(cb[1])) - elif size > 2: - cb = '{}, <{} more>, {}'.format(format_cb(cb[0]), - size-2, - format_cb(cb[-1])) - return 'cb=[%s]' % cb - - def _repr_info(self): - info = [self._state.lower()] - if self._state == _FINISHED: - if self._exception is not None: - info.append('exception={!r}'.format(self._exception)) - else: - # use reprlib to limit the length of the output, especially - # for very long strings - result = reprlib.repr(self._result) - info.append('result={}'.format(result)) - if self._callbacks: - info.append(self.__format_callbacks()) _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit