Skip to content

Commit

Permalink
Merge branch 'main' into ios-platform-changes
Browse files Browse the repository at this point in the history
  • Loading branch information
freakboy3742 committed Mar 27, 2024
2 parents 096078a + 262fb91 commit 44bbf79
Show file tree
Hide file tree
Showing 18 changed files with 328 additions and 70 deletions.
10 changes: 5 additions & 5 deletions Doc/library/doctest.rst
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,10 @@ And so on, eventually ending with:
OverflowError: n too large
ok
2 items passed all tests:
1 tests in __main__
8 tests in __main__.factorial
9 tests in 2 items.
9 passed and 0 failed.
1 test in __main__
6 tests in __main__.factorial
7 tests in 2 items.
7 passed.
Test passed.
$
Expand Down Expand Up @@ -1933,7 +1933,7 @@ such a test runner::
optionflags=flags)
else:
fail, total = doctest.testmod(optionflags=flags)
print("{} failures out of {} tests".format(fail, total))
print(f"{fail} failures out of {total} tests")


.. rubric:: Footnotes
Expand Down
36 changes: 36 additions & 0 deletions Doc/library/platform.rst
Original file line number Diff line number Diff line change
Expand Up @@ -323,3 +323,39 @@ Linux Platforms
return ids

.. versionadded:: 3.10


Android Platform
----------------

.. function:: android_ver(release="", api_level=0, manufacturer="", \
model="", device="", is_emulator=False)

Get Android device information. Returns a :func:`~collections.namedtuple`
with the following attributes. Values which cannot be determined are set to
the defaults given as parameters.

* ``release`` - Android version, as a string (e.g. ``"14"``).

* ``api_level`` - API level of the running device, as an integer (e.g. ``34``
for Android 14). To get the API level which Python was built against, see
:func:`sys.getandroidapilevel`.

* ``manufacturer`` - `Manufacturer name
<https://developer.android.com/reference/android/os/Build#MANUFACTURER>`__.

* ``model`` - `Model name
<https://developer.android.com/reference/android/os/Build#MODEL>`__ –
typically the marketing name or model number.

* ``device`` - `Device name
<https://developer.android.com/reference/android/os/Build#DEVICE>`__ –
typically the model number or a codename.

* ``is_emulator`` - ``True`` if the device is an emulator; ``False`` if it's
a physical device.

Google maintains a `list of known model and device names
<https://storage.googleapis.com/play_public/supported_devices.html>`__.

.. versionadded:: 3.13
58 changes: 58 additions & 0 deletions Doc/library/statistics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1148,6 +1148,64 @@ The final prediction goes to the largest posterior. This is known as the
'female'


Sampling from kernel density estimation
***************************************

The :func:`kde()` function creates a continuous probability density
function from discrete samples. Some applications need a way to make
random selections from that distribution.

The technique is to pick a sample from a bandwidth scaled kernel
function and recenter the result around a randomly chosen point from
the input data. This can be done with any kernel that has a known or
accurately approximated inverse cumulative distribution function.

.. testcode::

from random import choice, random, seed
from math import sqrt, log, pi, tan, asin
from statistics import NormalDist

kernel_invcdfs = {
'normal': NormalDist().inv_cdf,
'logistic': lambda p: log(p / (1 - p)),
'sigmoid': lambda p: log(tan(p * pi/2)),
'rectangular': lambda p: 2*p - 1,
'triangular': lambda p: sqrt(2*p) - 1 if p < 0.5 else 1 - sqrt(2 - 2*p),
'cosine': lambda p: 2*asin(2*p - 1)/pi,
}

def kde_random(data, h, kernel='normal'):
'Return a function that samples from kde() smoothed data.'
kernel_invcdf = kernel_invcdfs[kernel]
def rand():
return h * kernel_invcdf(random()) + choice(data)
return rand

For example:

.. doctest::

>>> discrete_samples = [-2.1, -1.3, -0.4, 1.9, 5.1, 6.2]
>>> rand = kde_random(discrete_samples, h=1.5)
>>> seed(8675309)
>>> selections = [rand() for i in range(10)]
>>> [round(x, 1) for x in selections]
[4.7, 7.4, 1.2, 7.8, 6.9, -1.3, 5.8, 0.2, -1.4, 5.7]

.. testcode::
:hide:

from statistics import kde
from math import isclose

# Verify that cdf / invcdf will round trip
xarr = [i/100 for i in range(-100, 101)]
for kernel, invcdf in kernel_invcdfs.items():
cdf = kde([0.0], h=1.0, kernel=kernel, cumulative=True)
for x in xarr:
assert isclose(invcdf(cdf(x)), x, abs_tol=1E-9)

..
# This modelines must appear within the last ten lines of the file.
kate: indent-width 3; remove-trailing-space on; replace-tabs on; encoding utf-8;
4 changes: 3 additions & 1 deletion Doc/library/sys.rst
Original file line number Diff line number Diff line change
Expand Up @@ -753,7 +753,9 @@ always available.

.. function:: getandroidapilevel()

Return the build time API version of Android as an integer.
Return the build-time API level of Android as an integer. This represents the
minimum version of Android this build of Python can run on. For runtime
version information, see :func:`platform.android_ver`.

.. availability:: Android.

Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_compile.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ int _PyCompile_InstructionSequence_UseLabel(_PyCompile_InstructionSequence *seq,
int _PyCompile_InstructionSequence_Addop(_PyCompile_InstructionSequence *seq,
int opcode, int oparg,
_PyCompilerSrcLocation loc);
int _PyCompile_InstructionSequence_ApplyLabelMap(_PyCompile_InstructionSequence *seq);

typedef struct {
PyObject *u_name;
Expand Down
59 changes: 39 additions & 20 deletions Lib/doctest.py
Original file line number Diff line number Diff line change
Expand Up @@ -1191,9 +1191,9 @@ class DocTestRunner:
2 tests in _TestClass
2 tests in _TestClass.__init__
2 tests in _TestClass.get
1 tests in _TestClass.square
1 test in _TestClass.square
7 tests in 4 items.
7 passed and 0 failed.
7 passed.
Test passed.
TestResults(failed=0, attempted=7)
Expand Down Expand Up @@ -1568,49 +1568,59 @@ def summarize(self, verbose=None):
"""
if verbose is None:
verbose = self._verbose
notests = []
passed = []
failed = []

notests, passed, failed = [], [], []
total_tries = total_failures = total_skips = 0
for item in self._stats.items():
name, (failures, tries, skips) = item

for name, (failures, tries, skips) in self._stats.items():
assert failures <= tries
total_tries += tries
total_failures += failures
total_skips += skips

if tries == 0:
notests.append(name)
elif failures == 0:
passed.append((name, tries))
else:
failed.append(item)
failed.append((name, (failures, tries, skips)))

if verbose:
if notests:
print(f"{len(notests)} items had no tests:")
print(f"{_n_items(notests)} had no tests:")
notests.sort()
for name in notests:
print(f" {name}")

if passed:
print(f"{len(passed)} items passed all tests:")
passed.sort()
for name, count in passed:
print(f" {count:3d} tests in {name}")
print(f"{_n_items(passed)} passed all tests:")
for name, count in sorted(passed):
s = "" if count == 1 else "s"
print(f" {count:3d} test{s} in {name}")

if failed:
print(self.DIVIDER)
print(f"{len(failed)} items had failures:")
failed.sort()
for name, (failures, tries, skips) in failed:
print(f"{_n_items(failed)} had failures:")
for name, (failures, tries, skips) in sorted(failed):
print(f" {failures:3d} of {tries:3d} in {name}")

if verbose:
print(f"{total_tries} tests in {len(self._stats)} items.")
print(f"{total_tries - total_failures} passed and {total_failures} failed.")
s = "" if total_tries == 1 else "s"
print(f"{total_tries} test{s} in {_n_items(self._stats)}.")

and_f = f" and {total_failures} failed" if total_failures else ""
print(f"{total_tries - total_failures} passed{and_f}.")

if total_failures:
msg = f"***Test Failed*** {total_failures} failures"
s = "" if total_failures == 1 else "s"
msg = f"***Test Failed*** {total_failures} failure{s}"
if total_skips:
msg = f"{msg} and {total_skips} skipped tests"
s = "" if total_skips == 1 else "s"
msg = f"{msg} and {total_skips} skipped test{s}"
print(f"{msg}.")
elif verbose:
print("Test passed.")

return TestResults(total_failures, total_tries, skipped=total_skips)

#/////////////////////////////////////////////////////////////////
Expand All @@ -1627,6 +1637,15 @@ def merge(self, other):
d[name] = (failures, tries, skips)


def _n_items(items: list) -> str:
"""
Helper to pluralise the number of items in a list.
"""
n = len(items)
s = "" if n == 1 else "s"
return f"{n} item{s}"


class OutputChecker:
"""
A class used to check the whether the actual output from a doctest
Expand Down
46 changes: 46 additions & 0 deletions Lib/platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,47 @@ def java_ver(release='', vendor='', vminfo=('', '', ''), osinfo=('', '', '')):

return release, vendor, vminfo, osinfo


AndroidVer = collections.namedtuple(
"AndroidVer", "release api_level manufacturer model device is_emulator")

def android_ver(release="", api_level=0, manufacturer="", model="", device="",
is_emulator=False):
if sys.platform == "android":
try:
from ctypes import CDLL, c_char_p, create_string_buffer
except ImportError:
pass
else:
# An NDK developer confirmed that this is an officially-supported
# API (https://stackoverflow.com/a/28416743). Use `getattr` to avoid
# private name mangling.
system_property_get = getattr(CDLL("libc.so"), "__system_property_get")
system_property_get.argtypes = (c_char_p, c_char_p)

def getprop(name, default):
# https://android.googlesource.com/platform/bionic/+/refs/tags/android-5.0.0_r1/libc/include/sys/system_properties.h#39
PROP_VALUE_MAX = 92
buffer = create_string_buffer(PROP_VALUE_MAX)
length = system_property_get(name.encode("UTF-8"), buffer)
if length == 0:
# This API doesn’t distinguish between an empty property and
# a missing one.
return default
else:
return buffer.value.decode("UTF-8", "backslashreplace")

release = getprop("ro.build.version.release", release)
api_level = int(getprop("ro.build.version.sdk", api_level))
manufacturer = getprop("ro.product.manufacturer", manufacturer)
model = getprop("ro.product.model", model)
device = getprop("ro.product.device", device)
is_emulator = getprop("ro.kernel.qemu", "0") == "1"

return AndroidVer(
release, api_level, manufacturer, model, device, is_emulator)


### System name aliasing

def system_alias(system, release, version):
Expand Down Expand Up @@ -1004,6 +1045,11 @@ def uname():
system = 'Windows'
release = 'Vista'

# On Android, return the name and version of the OS rather than the kernel.
if sys.platform == 'android':
system = 'Android'
release = android_ver().release

# Normalize responses on iOS
if sys.platform == 'ios':
system, release, _, _ = ios_ver()
Expand Down
3 changes: 3 additions & 0 deletions Lib/test/pythoninfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,9 @@ def collect_platform(info_add):
info_add(f'platform.freedesktop_os_release[{key}]',
os_release[key])

if sys.platform == 'android':
call_func(info_add, 'platform.android_ver', platform, 'android_ver')


def collect_locale(info_add):
import locale
Expand Down
16 changes: 8 additions & 8 deletions Lib/test/support/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1801,18 +1801,18 @@ def missing_compiler_executable(cmd_names=[]):
return cmd[0]


_is_android_emulator = None
_old_android_emulator = None
def setswitchinterval(interval):
# Setting a very low gil interval on the Android emulator causes python
# to hang (issue #26939).
minimum_interval = 1e-5
minimum_interval = 1e-4 # 100 us
if is_android and interval < minimum_interval:
global _is_android_emulator
if _is_android_emulator is None:
import subprocess
_is_android_emulator = (subprocess.check_output(
['getprop', 'ro.kernel.qemu']).strip() == b'1')
if _is_android_emulator:
global _old_android_emulator
if _old_android_emulator is None:
import platform
av = platform.android_ver()
_old_android_emulator = av.is_emulator and av.api_level < 24
if _old_android_emulator:
interval = minimum_interval
return sys.setswitchinterval(interval)

Expand Down
5 changes: 5 additions & 0 deletions Lib/test/test_asyncio/test_base_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import concurrent.futures
import errno
import math
import platform
import socket
import sys
import threading
Expand Down Expand Up @@ -1430,6 +1431,10 @@ def test_create_connection_no_inet_pton(self, m_socket):
self._test_create_connection_ip_addr(m_socket, False)

@patch_socket
@unittest.skipIf(
support.is_android and platform.android_ver().api_level < 23,
"Issue gh-71123: this fails on Android before API level 23"
)
def test_create_connection_service_name(self, m_socket):
m_socket.getaddrinfo = socket.getaddrinfo
sock = m_socket.socket.return_value
Expand Down
Loading

0 comments on commit 44bbf79

Please sign in to comment.