Skip to content

Commit

Permalink
Merge branch 'trunk' into 11635-http-headers-anystr
Browse files Browse the repository at this point in the history
  • Loading branch information
twm committed Oct 3, 2022
2 parents 2392a73 + f0be792 commit e251fd4
Show file tree
Hide file tree
Showing 23 changed files with 367 additions and 300 deletions.
10 changes: 4 additions & 6 deletions docs/core/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@ Twisted Core
examples/index
specifications/index

- :doc:`Developer guides <howto/index>`: documentation on using Twisted Core to develop your own applications
- :doc:`Examples <examples/index>`: short code examples using Twisted Core
- :doc:`Specifications <specifications/index>`: specification documents for elements of Twisted Core

- :doc:`Developer guides <howto/index>` : documentation on using Twisted Core to develop your own applications
- :doc:`Examples <examples/index>` : short code examples using Twisted Core
- :doc:`Specifications <specifications/index>` : specification documents for elements of Twisted Core


An `API reference <//twistedmatrix.com/documents/current/api/>`_ is available on the twistedmatrix web site.
An :py:mod:`API reference <twisted>` is available.
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ test =
; release scripts and process.
dev_release =
towncrier ~= 22.8
pydoctor ~= 22.7.0
pydoctor ~= 22.9.0
sphinx_rtd_theme ~= 1.0
readthedocs-sphinx-ext ~= 2.1
sphinx >= 5.0, <6
Expand Down
7 changes: 4 additions & 3 deletions src/twisted/internet/defer.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@

import attr
from incremental import Version
from typing_extensions import Literal, Protocol
from typing_extensions import Literal, ParamSpec, Protocol

from twisted.internet.interfaces import IDelayedCall, IReactorTime
from twisted.logger import Logger
Expand Down Expand Up @@ -78,6 +78,7 @@ def _copy_context() -> Type[_NoContext]:


_T = TypeVar("_T")
_P = ParamSpec("_P")


class AlreadyCalledError(Exception):
Expand Down Expand Up @@ -156,7 +157,7 @@ def fail(result: Optional[Union[Failure, BaseException]] = None) -> "Deferred[An


def execute(
callable: Callable[..., _T], *args: object, **kwargs: object
callable: Callable[_P, _T], *args: _P.args, **kwargs: _P.kwargs
) -> "Deferred[_T]":
"""
Create a L{Deferred} from a callable and arguments.
Expand All @@ -174,7 +175,7 @@ def execute(


def maybeDeferred(
f: Callable[..., _T], *args: object, **kwargs: object
f: Callable[_P, _T], *args: _P.args, **kwargs: _P.kwargs
) -> "Deferred[_T]":
"""
Invoke a function that may or may not return a L{Deferred} or coroutine.
Expand Down
37 changes: 35 additions & 2 deletions src/twisted/internet/test/reactormixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import os
import signal
import time
from typing import Dict, Optional, Sequence, Type, Union
from typing import TYPE_CHECKING, Dict, Optional, Sequence, Type, Union

from zope.interface import Interface

Expand All @@ -30,6 +30,11 @@
from twisted.trial.unittest import SkipTest, SynchronousTestCase
from twisted.trial.util import DEFAULT_TIMEOUT_DURATION, acquireAttribute

if TYPE_CHECKING:
# Only bring in this name to support the type annotation below. We don't
# really want to import a reactor module this early at runtime.
from twisted.internet import asyncioreactor

# Access private APIs.
try:
from twisted.internet import process as _process
Expand Down Expand Up @@ -153,7 +158,7 @@ class ReactorBuilder:
]
)

_reactors.append("twisted.internet.asyncioreactor.AsyncioSelectorReactor")
_reactors.append("twisted.internet.test.reactormixins.AsyncioSelectorReactor")

if platform.isMacOSX():
_reactors.append("twisted.internet.cfreactor.CFReactor")
Expand Down Expand Up @@ -366,3 +371,31 @@ class testcase(cls, SynchronousTestCase): # type: ignore[valid-type,misc]
testcase.__qualname__ = ".".join(cls.__qualname__.split()[0:-1] + [name])
classes[testcase.__name__] = testcase
return classes


def asyncioSelectorReactor(self: object) -> "asyncioreactor.AsyncioSelectorReactor":
"""
Make a new asyncio reactor associated with a new event loop.
The test suite prefers this constructor because having a new event loop
for each reactor provides better test isolation. The real constructor
prefers to re-use (or create) a global loop because of how this interacts
with other asyncio-based libraries and applications (though maybe it
shouldn't).
@param self: The L{ReactorBuilder} subclass this is being called on. We
don't use this parameter but we get called with it anyway.
"""
from asyncio import new_event_loop, set_event_loop

from twisted.internet import asyncioreactor

loop = new_event_loop()
set_event_loop(loop)

return asyncioreactor.AsyncioSelectorReactor(loop)


# Give it an alias that makes the names of the generated test classes fit the
# pattern.
AsyncioSelectorReactor = asyncioSelectorReactor
16 changes: 2 additions & 14 deletions src/twisted/internet/test/test_threads.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,20 +172,8 @@ def test_cleanUpThreadPoolEvenBeforeReactorIsRun(self):
reactor = self.buildReactor()
threadPoolRef = ref(reactor.getThreadPool())
reactor.fireSystemEvent("shutdown")

if reactor.__class__.__name__ == "AsyncioSelectorReactor":
self.assertIsNone(reactor.threadpool)
# ReactorBase.__init__ sets self.crash as a 'shutdown'
# event, which in turn calls stop on the underlying
# asyncio event loop, which in turn sets a _stopping
# attribute on it that's only unset after an iteration of
# the loop. Subsequent tests can only reuse the asyncio
# loop if it's allowed to run and unset that _stopping
# attribute.
self.runReactor(reactor)
else:
gc.collect()
self.assertIsNone(threadPoolRef())
gc.collect()
self.assertIsNone(threadPoolRef())

def test_isInIOThread(self):
"""
Expand Down
2 changes: 1 addition & 1 deletion src/twisted/internet/test/test_time.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def eventSource(reactor, event):

else:
raise SkipTest(
"Do not know how to synthesize non-time event to " "stop the test"
"Do not know how to synthesize non-time event to stop the test"
)

# Pick a pretty big delay.
Expand Down
Empty file.
1 change: 1 addition & 0 deletions src/twisted/newsfragments/11690.doc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The implementers of Zope interfaces are once more displayed in the documentations.
Empty file.
Empty file.
Empty file.
7 changes: 3 additions & 4 deletions src/twisted/python/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@

# let's try to keep path imports to a minimum...
from os.path import dirname, split as splitpath
from typing import cast

from zope.interface import Interface, implementer

Expand Down Expand Up @@ -262,7 +261,7 @@ def __init__(self, name, onObject, loaded, pythonValue):
@param loaded: always True, for now
@param pythonValue: the value of the attribute we're pointing to.
"""
self.name = name
self.name: str = name
self.onObject = onObject
self._loaded = loaded
self.pythonValue = pythonValue
Expand Down Expand Up @@ -318,7 +317,7 @@ def __init__(self, name, filePath, pathEntry):
"""
_name = nativeString(name)
assert not _name.endswith(".__init__")
self.name = _name
self.name: str = _name
self.filePath = filePath
self.parentPath = filePath.parent()
self.pathEntry = pathEntry
Expand Down Expand Up @@ -397,7 +396,7 @@ def __eq__(self, other: object) -> bool:
PythonModules with the same name are equal.
"""
if isinstance(other, PythonModule):
return cast(bool, other.name == self.name)
return other.name == self.name
return NotImplemented

def walkModules(self, importPackages=False):
Expand Down
2 changes: 1 addition & 1 deletion src/twisted/python/reflect.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ def filenameToModuleName(fn):
return modName


def qual(clazz):
def qual(clazz: Type[object]) -> str:
"""
Return full import path of a class.
"""
Expand Down
39 changes: 16 additions & 23 deletions src/twisted/scripts/trial.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,19 @@
import random
import sys
import time
import trace
import warnings
from typing import NoReturn, Optional, Type

from twisted import plugin
from twisted.application import app
from twisted.internet import defer
from twisted.python import failure, reflect, usage
from twisted.python.filepath import FilePath
from twisted.python.reflect import namedModule
from twisted.trial import itrial, reporter, runner
from twisted.trial import itrial, runner
from twisted.trial._dist.disttrial import DistTrialRunner
from twisted.trial.unittest import TestSuite

# Yea, this is stupid. Leave it for command-line compatibility for a
# while, though.
Expand Down Expand Up @@ -231,8 +235,7 @@ class _BasicOptions:
],
)

fallbackReporter = reporter.TreeReporter
tracer = None
tracer: Optional[trace.Trace] = None

def __init__(self):
self["tests"] = []
Expand Down Expand Up @@ -275,8 +278,6 @@ def opt_coverage(self):
Generate coverage information in the coverage file in the
directory specified by the temp-directory option.
"""
import trace

self.tracer = trace.Trace(count=1, trace=0)
sys.settrace(self.tracer.globaltrace)
self["coverage"] = True
Expand Down Expand Up @@ -474,10 +475,6 @@ class Options(_BasicOptions, usage.Options, app.ReactorSelectionMixin):
_workerFlags = ["disablegc", "force-gc", "coverage"]
_workerParameters = ["recursionlimit", "reactor", "without-module"]

fallbackReporter = reporter.TreeReporter
extra = None
tracer = None

def opt_jobs(self, number):
"""
Number of local workers to run, a strictly positive integer.
Expand Down Expand Up @@ -523,21 +520,21 @@ def postOptions(self):
failure.DO_POST_MORTEM = False


def _initialDebugSetup(config):
def _initialDebugSetup(config: Options) -> None:
# do this part of debug setup first for easy debugging of import failures
if config["debug"]:
failure.startDebugMode()
if config["debug"] or config["debug-stacktraces"]:
defer.setDebugging(True)


def _getSuite(config):
def _getSuite(config: Options) -> TestSuite:
loader = _getLoader(config)
recurse = not config["no-recurse"]
return loader.loadByNames(config["tests"], recurse=recurse)


def _getLoader(config):
def _getLoader(config: Options) -> runner.TestLoader:
loader = runner.TestLoader()
if config["random"]:
randomer = random.Random()
Expand Down Expand Up @@ -584,16 +581,14 @@ class _DebuggerNotFound(Exception):
"""


def _makeRunner(config):
def _makeRunner(config: Options) -> runner._Runner:
"""
Return a trial runner class set up with the parameters extracted from
C{config}.
@return: A trial runner instance.
@rtype: L{runner.TrialRunner} or C{DistTrialRunner} depending on the
configuration.
"""
cls = runner.TrialRunner
cls: Type[runner._Runner] = runner.TrialRunner
args = {
"reporterFactory": config["reporter"],
"tracebackFormat": config["tbformat"],
Expand All @@ -606,8 +601,6 @@ def _makeRunner(config):
if config["dry-run"]:
args["mode"] = runner.TrialRunner.DRY_RUN
elif config["jobs"]:
from twisted.trial._dist.disttrial import DistTrialRunner

cls = DistTrialRunner
args["maxWorkers"] = config["jobs"]
args["workerArguments"] = config._getWorkerArguments()
Expand All @@ -632,7 +625,7 @@ def _makeRunner(config):
return cls(**args)


def run():
def run() -> NoReturn:
if len(sys.argv) == 1:
sys.argv.append("--help")
config = Options()
Expand All @@ -649,13 +642,13 @@ def run():

suite = _getSuite(config)
if config["until-failure"]:
test_result = trialRunner.runUntilFailure(suite)
testResult = trialRunner.runUntilFailure(suite)
else:
test_result = trialRunner.run(suite)
testResult = trialRunner.run(suite)
if config.tracer:
sys.settrace(None)
results = config.tracer.results()
results.write_results(
show_missing=1, summary=False, coverdir=config.coverdir().path
show_missing=True, summary=False, coverdir=config.coverdir().path
)
sys.exit(not test_result.wasSuccessful())
sys.exit(not testResult.wasSuccessful())
14 changes: 6 additions & 8 deletions src/twisted/test/test_defer.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
Generator,
List,
Mapping,
NoReturn,
Optional,
Tuple,
Type,
Expand Down Expand Up @@ -790,21 +791,18 @@ def test_maybeDeferredSyncException(self) -> None:
L{defer.maybeDeferred} should catch an exception raised by a synchronous
function and errback its resulting L{Deferred} with it.
"""
try:
"10" + 5 # type: ignore[operator]
except TypeError as e:
expected = str(e)
expected = ValueError("that value is unacceptable")

def plusFive(x: int) -> int:
return x + 5
def raisesException() -> NoReturn:
raise expected

results: List[int] = []
errors: List[Failure] = []
d = defer.maybeDeferred(plusFive, "10")
d = defer.maybeDeferred(raisesException)
d.addCallbacks(results.append, errors.append)
self.assertEqual(results, [])
self.assertEqual(len(errors), 1)
self.assertEqual(str(errors[0].value), expected)
self.assertEqual(str(errors[0].value), str(expected))

def test_maybeDeferredSyncFailure(self) -> None:
"""
Expand Down
9 changes: 6 additions & 3 deletions src/twisted/trial/_asyncrunner.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import doctest
import gc
import unittest as pyunit
from typing import Iterator, Union

from zope.interface import implementer

Expand Down Expand Up @@ -160,14 +161,16 @@ def run(self, result):
components.registerAdapter(_BrokenIDTestCaseAdapter, _docTestCase, itrial.ITestCase)


def _iterateTests(testSuiteOrCase):
def _iterateTests(
testSuiteOrCase: Union[pyunit.TestCase, pyunit.TestSuite]
) -> Iterator[itrial.ITestCase]:
"""
Iterate through all of the test cases in C{testSuiteOrCase}.
"""
try:
suite = iter(testSuiteOrCase)
suite = iter(testSuiteOrCase) # type: ignore[arg-type]
except TypeError:
yield testSuiteOrCase
yield testSuiteOrCase # type: ignore[misc]
else:
for test in suite:
yield from _iterateTests(test)

0 comments on commit e251fd4

Please sign in to comment.