Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 1 addition & 20 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,26 +25,7 @@ jobs:
fail-fast: false
matrix:
name: [
"windows-py35",
"windows-py36",
"windows-py37",
"windows-py37-pluggy",
"windows-py38",

"ubuntu-py35",
"ubuntu-py36",
"ubuntu-py37",
"ubuntu-py37-pluggy",
"ubuntu-py37-freeze",
"ubuntu-py38",
"ubuntu-pypy3",

"macos-py37",
"macos-py38",

"linting",
"docs",
"doctesting",
]

include:
Expand Down Expand Up @@ -116,7 +97,7 @@ jobs:
os: ubuntu-latest
tox_env: "linting"
- name: "docs"
python: "3.7"
python: "3.8"
os: ubuntu-latest
tox_env: "docs"
- name: "doctesting"
Expand Down
5 changes: 5 additions & 0 deletions doc/en/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
extensions = [
"pygments_pytest",
"sphinx.ext.autodoc",
"sphinx_autodoc_typehints",
"sphinx.ext.autosummary",
"sphinx.ext.intersphinx",
"sphinx.ext.todo",
Expand Down Expand Up @@ -346,6 +347,10 @@
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {"python": ("https://docs.python.org/3", None)}

# sphinx_autodoc_typehints
set_type_checking_flag = True
always_document_param_types = True


def configure_logging(app: "sphinx.application.Sphinx") -> None:
"""Configure Sphinx's WarningHandler to handle (expected) missing include."""
Expand Down
1 change: 1 addition & 0 deletions doc/en/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ pygments-pytest>=1.1.0
sphinx>=1.8.2,<2.1
sphinxcontrib-trio
sphinx-removed-in>=0.2.0
sphinx_autodoc_typehints
16 changes: 6 additions & 10 deletions src/_pytest/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,27 +326,23 @@ def _finalize(self) -> None:
logger.setLevel(level)

@property
def handler(self):
"""
:rtype: LogCaptureHandler
"""
return self._item.catch_log_handler
def handler(self) -> LogCaptureHandler:
return self._item.catch_log_handler # type: ignore[no-any-return] # noqa: F723

def get_records(self, when):
def get_records(self, when: str) -> List[logging.LogRecord]:
"""
Get the logging records for one of the possible test phases.

:param str when:
:param when:
Which test phase to obtain the records from. Valid values are: "setup", "call" and "teardown".

:rtype: List[logging.LogRecord]
:return: the list of captured records at the given stage

.. versionadded:: 3.4
"""
handler = self._item.catch_log_handlers.get(when)
if handler:
return handler.records
return handler.records # type: ignore[no-any-return] # noqa: F723
else:
return []

Expand Down Expand Up @@ -613,7 +609,7 @@ def _runtest_for(self, item, when):
yield

@contextmanager
def _runtest_for_main(self, item, when):
def _runtest_for_main(self, item, when: str) -> Generator[None, None, None]:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bluetech
item not typed because of catch_log_handler{,s} being set/unset there.
Ignoring any refactors I've wondered if a ItemWithLogging type would make sense in general, which would have that attribute allowed then? (I do not think it is worth it, was just wondering)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might work, but because the attribute is added and deleted, it can't be entirely correct, and would require casts as well, so probably doesn't make sense.

As for refactor, seems like a good case for an "item-level storage" mechanism #3740. Such storage could be typed in some appropriate manner, probably.

"""Implements the internals of pytest_runtest_xxx() hook."""
with catching_logs(
LogCaptureHandler(), formatter=self.formatter, level=self.log_level
Expand Down
13 changes: 4 additions & 9 deletions src/_pytest/mark/structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
import warnings
from collections import namedtuple
from collections.abc import MutableMapping
from typing import List
from typing import Set
from typing import Union

import attr

Expand Down Expand Up @@ -147,8 +149,6 @@ class Mark:
def combined_with(self, other):
"""
:param other: the mark to combine with
:type other: Mark
:rtype: Mark

combines by appending args and merging the mappings
"""
Expand Down Expand Up @@ -249,13 +249,8 @@ def get_unpacked_marks(obj):
return normalize_mark_list(mark_list)


def normalize_mark_list(mark_list):
"""
normalizes marker decorating helpers to mark objects

:type mark_list: List[Union[Mark, Markdecorator]]
:rtype: List[Mark]
"""
def normalize_mark_list(mark_list: List[Union[Mark, MarkDecorator]]) -> List[Mark]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mark_list can be Iterable (if you feel like it)

"""normalizes marker decorating helpers to mark objects"""
extracted = [
getattr(mark, "mark", mark) for mark in mark_list
] # unpack MarkDecorator
Expand Down
13 changes: 8 additions & 5 deletions src/_pytest/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,18 +333,21 @@ def repr_failure(
return self._repr_failure_py(excinfo, style)


def get_fslocation_from_item(item):
def get_fslocation_from_item(
item: "Item",
) -> Tuple[Union[str, py.path.local], Optional[int]]:
"""Tries to extract the actual location from an item, depending on available attributes:

* "fslocation": a pair (path, lineno)
* "obj": a Python object that the item wraps.
* "fspath": just a path

:rtype: a tuple of (str|LocalPath, int) with filename and line number.
:return: filename and line number
"""
result = getattr(item, "location", None)
if result is not None:
return result[:2]
try:
return item.location[:2]
except AttributeError:
pass
obj = getattr(item, "obj", None)
if obj is not None:
return getfslineno(obj)
Expand Down
21 changes: 12 additions & 9 deletions src/_pytest/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from collections.abc import Sequence
from functools import partial
from textwrap import dedent
from typing import Dict
from typing import List
from typing import Tuple
from typing import Union
Expand All @@ -36,8 +37,10 @@
from _pytest.config import hookimpl
from _pytest.deprecated import FUNCARGNAMES
from _pytest.mark import MARK_GEN
from _pytest.mark import ParameterSet
from _pytest.mark.structures import get_unpacked_marks
from _pytest.mark.structures import normalize_mark_list
from _pytest.nodes import Item
from _pytest.outcomes import fail
from _pytest.outcomes import skip
from _pytest.pathlib import parts
Expand Down Expand Up @@ -947,7 +950,6 @@ def parametrize(self, argnames, argvalues, indirect=False, ids=None, scope=None)
to set a dynamic scope using test context or configuration.
"""
from _pytest.fixtures import scope2index
from _pytest.mark import ParameterSet

argnames, parameters = ParameterSet._for_parametrize(
argnames,
Expand Down Expand Up @@ -996,15 +998,16 @@ def parametrize(self, argnames, argvalues, indirect=False, ids=None, scope=None)
newcalls.append(newcallspec)
self._calls = newcalls

def _resolve_arg_ids(self, argnames, ids, parameters, item):
def _resolve_arg_ids(
self, argnames: List[str], ids, parameters: List[ParameterSet], item: Item
):
"""Resolves the actual ids for the given argnames, based on the ``ids`` parameter given
to ``parametrize``.

:param List[str] argnames: list of argument names passed to ``parametrize()``.
:param argnames: list of argument names passed to ``parametrize()``.
:param ids: the ids parameter of the parametrized call (see docs).
:param List[ParameterSet] parameters: the list of parameter values, same size as ``argnames``.
:param Item item: the item that generated this parametrized call.
:rtype: List[str]
:param parameters: the list of parameter values, same size as ``argnames``.
:param item: the item that generated this parametrized call.
:return: the list of ids for each argname given
"""
from _pytest._io.saferepr import saferepr
Expand All @@ -1028,13 +1031,13 @@ def _resolve_arg_ids(self, argnames, ids, parameters, item):
ids = idmaker(argnames, parameters, idfn, ids, self.config, item=item)
return ids

def _resolve_arg_value_types(self, argnames, indirect):
def _resolve_arg_value_types(self, argnames: List[str], indirect) -> Dict[str, str]:
"""Resolves if each parametrized argument must be considered a parameter to a fixture or a "funcarg"
to the function, based on the ``indirect`` parameter of the parametrized() call.

:param List[str] argnames: list of argument names passed to ``parametrize()``.
:param argnames: list of argument names passed to ``parametrize()``.
:param indirect: same ``indirect`` parameter of ``parametrize()``.
:rtype: Dict[str, str]
:return:
A dict mapping each arg name to either:
* "params" if the argname should be the parameter of a fixture of the same name.
* "funcargs" if the argname should be a parameter to the parametrized test function.
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ commands =
diff-cover --fail-under=100 --compare-branch={env:DIFF_BRANCH:origin/{env:GITHUB_BASE_REF:master}} {envtmpdir}/cobertura.xml

[testenv:docs]
basepython = python3
basepython = python3.8
usedevelop = True
deps =
-r{toxinidir}/doc/en/requirements.txt
Expand Down