coverage 7.10.6__cp314-cp314t-musllinux_1_2_x86_64.whl → 7.11.0__cp314-cp314t-musllinux_1_2_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- coverage/cmdline.py +4 -2
- coverage/collector.py +2 -2
- coverage/config.py +11 -11
- coverage/control.py +6 -5
- coverage/debug.py +9 -8
- coverage/env.py +1 -64
- coverage/exceptions.py +6 -8
- coverage/html.py +12 -4
- coverage/jsonreport.py +14 -5
- coverage/lcovreport.py +5 -2
- coverage/misc.py +26 -25
- coverage/parser.py +63 -206
- coverage/patch.py +11 -10
- coverage/phystokens.py +3 -6
- coverage/python.py +1 -1
- coverage/results.py +99 -40
- coverage/sqldata.py +6 -2
- coverage/tracer.cpython-314t-x86_64-linux-musl.so +0 -0
- coverage/types.py +8 -12
- coverage/version.py +2 -16
- {coverage-7.10.6.dist-info → coverage-7.11.0.dist-info}/METADATA +13 -13
- {coverage-7.10.6.dist-info → coverage-7.11.0.dist-info}/RECORD +26 -26
- {coverage-7.10.6.dist-info → coverage-7.11.0.dist-info}/WHEEL +0 -0
- {coverage-7.10.6.dist-info → coverage-7.11.0.dist-info}/entry_points.txt +0 -0
- {coverage-7.10.6.dist-info → coverage-7.11.0.dist-info}/licenses/LICENSE.txt +0 -0
- {coverage-7.10.6.dist-info → coverage-7.11.0.dist-info}/top_level.txt +0 -0
coverage/cmdline.py
CHANGED
|
@@ -24,7 +24,7 @@ from coverage.control import DEFAULT_DATAFILE
|
|
|
24
24
|
from coverage.core import CTRACER_FILE
|
|
25
25
|
from coverage.data import combinable_files, debug_data_file
|
|
26
26
|
from coverage.debug import info_header, short_stack, write_formatted_info
|
|
27
|
-
from coverage.exceptions import NoSource,
|
|
27
|
+
from coverage.exceptions import NoSource, CoverageException, _ExceptionDuringRun
|
|
28
28
|
from coverage.execfile import PyRunner
|
|
29
29
|
from coverage.results import display_covered, should_fail_under
|
|
30
30
|
from coverage.version import __url__
|
|
@@ -1139,9 +1139,11 @@ def main(argv: list[str] | None = None) -> int | None:
|
|
|
1139
1139
|
# exception.
|
|
1140
1140
|
traceback.print_exception(*err.args) # pylint: disable=no-value-for-parameter
|
|
1141
1141
|
status = ERR
|
|
1142
|
-
except
|
|
1142
|
+
except CoverageException as err:
|
|
1143
1143
|
# A controlled error inside coverage.py: print the message to the user.
|
|
1144
1144
|
msg = err.args[0]
|
|
1145
|
+
if err.slug:
|
|
1146
|
+
msg = f"{msg.rstrip('.')}; see {__url__}/messages.html#error-{err.slug}"
|
|
1145
1147
|
print(msg)
|
|
1146
1148
|
status = ERR
|
|
1147
1149
|
except SystemExit as err:
|
coverage/collector.py
CHANGED
|
@@ -146,12 +146,12 @@ class Collector:
|
|
|
146
146
|
self.concur_id_func = greenlet.getcurrent
|
|
147
147
|
elif "eventlet" in concurrencies:
|
|
148
148
|
tried = "eventlet"
|
|
149
|
-
import eventlet.greenthread
|
|
149
|
+
import eventlet.greenthread
|
|
150
150
|
|
|
151
151
|
self.concur_id_func = eventlet.greenthread.getcurrent
|
|
152
152
|
elif "gevent" in concurrencies:
|
|
153
153
|
tried = "gevent"
|
|
154
|
-
import gevent
|
|
154
|
+
import gevent
|
|
155
155
|
|
|
156
156
|
self.concur_id_func = gevent.getcurrent
|
|
157
157
|
|
coverage/config.py
CHANGED
|
@@ -14,7 +14,7 @@ import os
|
|
|
14
14
|
import os.path
|
|
15
15
|
import re
|
|
16
16
|
from collections.abc import Iterable
|
|
17
|
-
from typing import Any, Callable, Final, Mapping
|
|
17
|
+
from typing import Any, Callable, Final, Mapping
|
|
18
18
|
|
|
19
19
|
from coverage.exceptions import ConfigError
|
|
20
20
|
from coverage.misc import human_sorted_items, isolate_module, substitute_variables
|
|
@@ -143,7 +143,7 @@ class HandyConfigParser(configparser.ConfigParser):
|
|
|
143
143
|
return process_regexlist(section, option, line_list.splitlines())
|
|
144
144
|
|
|
145
145
|
|
|
146
|
-
TConfigParser =
|
|
146
|
+
TConfigParser = HandyConfigParser | TomlConfigParser
|
|
147
147
|
|
|
148
148
|
|
|
149
149
|
# The default line exclusion regexes.
|
|
@@ -568,7 +568,7 @@ class CoverageConfig(TConfigurable, TPluginConfig):
|
|
|
568
568
|
return human_sorted_items((k, v) for k, v in self.__dict__.items() if not k.startswith("_"))
|
|
569
569
|
|
|
570
570
|
def serialize(self) -> str:
|
|
571
|
-
"""Convert to a string that can be ingested with `
|
|
571
|
+
"""Convert to a string that can be ingested with `deserialize`.
|
|
572
572
|
|
|
573
573
|
File paths used by `coverage run` are made absolute to ensure the
|
|
574
574
|
deserialized config will refer to the same files.
|
|
@@ -584,6 +584,14 @@ class CoverageConfig(TConfigurable, TPluginConfig):
|
|
|
584
584
|
data[k] = v
|
|
585
585
|
return base64.b64encode(json.dumps(data).encode()).decode()
|
|
586
586
|
|
|
587
|
+
@classmethod
|
|
588
|
+
def deserialize(cls, config_str: str) -> CoverageConfig:
|
|
589
|
+
"""Take a string from `serialize`, and make a CoverageConfig."""
|
|
590
|
+
data = json.loads(base64.b64decode(config_str.encode()).decode())
|
|
591
|
+
config = cls()
|
|
592
|
+
config.__dict__.update(data)
|
|
593
|
+
return config
|
|
594
|
+
|
|
587
595
|
|
|
588
596
|
def process_file_value(path: str) -> str:
|
|
589
597
|
"""Make adjustments to a file path to make it usable."""
|
|
@@ -707,11 +715,3 @@ def read_coverage_config(
|
|
|
707
715
|
config.post_process()
|
|
708
716
|
|
|
709
717
|
return config
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
def deserialize_config(config_str: str) -> CoverageConfig:
|
|
713
|
-
"""Take a string from CoverageConfig.serialize, and make a CoverageConfig."""
|
|
714
|
-
data = json.loads(base64.b64decode(config_str.encode()).decode())
|
|
715
|
-
config = CoverageConfig()
|
|
716
|
-
config.__dict__.update(data)
|
|
717
|
-
return config
|
coverage/control.py
CHANGED
|
@@ -19,12 +19,12 @@ import time
|
|
|
19
19
|
import warnings
|
|
20
20
|
from collections.abc import Iterable, Iterator
|
|
21
21
|
from types import FrameType
|
|
22
|
-
from typing import IO, Any, Callable,
|
|
22
|
+
from typing import IO, Any, Callable, cast
|
|
23
23
|
|
|
24
24
|
from coverage import env
|
|
25
25
|
from coverage.annotate import AnnotateReporter
|
|
26
26
|
from coverage.collector import Collector
|
|
27
|
-
from coverage.config import CoverageConfig,
|
|
27
|
+
from coverage.config import CoverageConfig, read_coverage_config
|
|
28
28
|
from coverage.context import combine_context_switchers, should_start_context_test_function
|
|
29
29
|
from coverage.core import CTRACER_FILE, Core
|
|
30
30
|
from coverage.data import CoverageData, combine_parallel_data
|
|
@@ -67,6 +67,7 @@ from coverage.types import (
|
|
|
67
67
|
TLineNo,
|
|
68
68
|
TMorf,
|
|
69
69
|
)
|
|
70
|
+
from coverage.version import __url__
|
|
70
71
|
from coverage.xmlreport import XmlReporter
|
|
71
72
|
|
|
72
73
|
os = isolate_module(os)
|
|
@@ -297,7 +298,7 @@ class Coverage(TConfigurable):
|
|
|
297
298
|
self._debug: DebugControl = NoDebugging()
|
|
298
299
|
self._inorout: InOrOut | None = None
|
|
299
300
|
self._plugins: Plugins = Plugins()
|
|
300
|
-
self._plugin_override = cast(
|
|
301
|
+
self._plugin_override = cast(Iterable[TCoverageInit] | None, plugins)
|
|
301
302
|
self._data: CoverageData | None = None
|
|
302
303
|
self._data_to_close: list[CoverageData] = []
|
|
303
304
|
self._core: Core | None = None
|
|
@@ -320,7 +321,7 @@ class Coverage(TConfigurable):
|
|
|
320
321
|
|
|
321
322
|
# Build our configuration from a number of sources.
|
|
322
323
|
if isinstance(config_file, str) and config_file.startswith(CONFIG_DATA_PREFIX):
|
|
323
|
-
self.config =
|
|
324
|
+
self.config = CoverageConfig.deserialize(config_file[len(CONFIG_DATA_PREFIX) :])
|
|
324
325
|
else:
|
|
325
326
|
if not isinstance(config_file, bool):
|
|
326
327
|
config_file = os.fspath(config_file)
|
|
@@ -479,7 +480,7 @@ class Coverage(TConfigurable):
|
|
|
479
480
|
|
|
480
481
|
self._warnings.append(msg)
|
|
481
482
|
if slug:
|
|
482
|
-
msg = f"{msg} ({slug})"
|
|
483
|
+
msg = f"{msg} ({slug}); see {__url__}/messages.html#warning-{slug}"
|
|
483
484
|
if self._debug.should("pid"):
|
|
484
485
|
msg = f"[{os.getpid()}] {msg}"
|
|
485
486
|
warnings.warn(msg, category=CoverageWarning, stacklevel=2)
|
coverage/debug.py
CHANGED
|
@@ -155,14 +155,15 @@ def info_formatter(info: Iterable[tuple[str, Any]]) -> Iterator[str]:
|
|
|
155
155
|
if data == []:
|
|
156
156
|
data = "-none-"
|
|
157
157
|
prefix = f"{label:>{LABEL_LEN}}: "
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
158
|
+
match data:
|
|
159
|
+
case tuple() if len(str(data)) < 30:
|
|
160
|
+
yield f"{prefix}{data}"
|
|
161
|
+
case tuple() | list() | set():
|
|
162
|
+
for e in data:
|
|
163
|
+
yield f"{prefix}{e}"
|
|
164
|
+
prefix = " " * (LABEL_LEN + 2)
|
|
165
|
+
case _:
|
|
166
|
+
yield f"{prefix}{data}"
|
|
166
167
|
|
|
167
168
|
|
|
168
169
|
def write_formatted_info(
|
coverage/env.py
CHANGED
|
@@ -43,7 +43,7 @@ else:
|
|
|
43
43
|
GIL = getattr(sys, "_is_gil_enabled", lambda: True)()
|
|
44
44
|
|
|
45
45
|
# Do we ship compiled coveragepy wheels for this version?
|
|
46
|
-
SHIPPING_WHEELS = CPYTHON and PYVERSION[:2] <= (3,
|
|
46
|
+
SHIPPING_WHEELS = CPYTHON and PYVERSION[:2] <= (3, 14)
|
|
47
47
|
|
|
48
48
|
# Should we default to sys.monitoring?
|
|
49
49
|
SYSMON_DEFAULT = CPYTHON and PYVERSION >= (3, 14)
|
|
@@ -53,63 +53,6 @@ SYSMON_DEFAULT = CPYTHON and PYVERSION >= (3, 14)
|
|
|
53
53
|
class PYBEHAVIOR:
|
|
54
54
|
"""Flags indicating this Python's behavior."""
|
|
55
55
|
|
|
56
|
-
# Does Python conform to PEP626, Precise line numbers for debugging and other tools.
|
|
57
|
-
# https://www.python.org/dev/peps/pep-0626
|
|
58
|
-
pep626 = (PYVERSION > (3, 10, 0, "alpha", 4)) # fmt: skip
|
|
59
|
-
|
|
60
|
-
# Is "if __debug__" optimized away?
|
|
61
|
-
optimize_if_debug = not pep626
|
|
62
|
-
|
|
63
|
-
# Is "if not __debug__" optimized away? The exact details have changed
|
|
64
|
-
# across versions.
|
|
65
|
-
optimize_if_not_debug = 1 if pep626 else 2
|
|
66
|
-
|
|
67
|
-
# 3.7 changed how functions with only docstrings are numbered.
|
|
68
|
-
docstring_only_function = (not PYPY) and (PYVERSION <= (3, 10))
|
|
69
|
-
|
|
70
|
-
# Lines after break/continue/return/raise are no longer compiled into the
|
|
71
|
-
# bytecode. They used to be marked as missing, now they aren't executable.
|
|
72
|
-
omit_after_jump = pep626 or PYPY
|
|
73
|
-
|
|
74
|
-
# PyPy has always omitted statements after return.
|
|
75
|
-
omit_after_return = omit_after_jump or PYPY
|
|
76
|
-
|
|
77
|
-
# Optimize away unreachable try-else clauses.
|
|
78
|
-
optimize_unreachable_try_else = pep626
|
|
79
|
-
|
|
80
|
-
# Modules used to have firstlineno equal to the line number of the first
|
|
81
|
-
# real line of code. Now they always start at 1.
|
|
82
|
-
module_firstline_1 = pep626
|
|
83
|
-
|
|
84
|
-
# Are "if 0:" lines (and similar) kept in the compiled code?
|
|
85
|
-
keep_constant_test = pep626
|
|
86
|
-
|
|
87
|
-
# When leaving a with-block, do we visit the with-line again for the exit?
|
|
88
|
-
# For example, wwith.py:
|
|
89
|
-
#
|
|
90
|
-
# with open("/tmp/test", "w") as f1:
|
|
91
|
-
# a = 2
|
|
92
|
-
# with open("/tmp/test2", "w") as f3:
|
|
93
|
-
# print(4)
|
|
94
|
-
#
|
|
95
|
-
# % python3.9 -m trace -t wwith.py | grep wwith
|
|
96
|
-
# --- modulename: wwith, funcname: <module>
|
|
97
|
-
# wwith.py(1): with open("/tmp/test", "w") as f1:
|
|
98
|
-
# wwith.py(2): a = 2
|
|
99
|
-
# wwith.py(3): with open("/tmp/test2", "w") as f3:
|
|
100
|
-
# wwith.py(4): print(4)
|
|
101
|
-
#
|
|
102
|
-
# % python3.10 -m trace -t wwith.py | grep wwith
|
|
103
|
-
# --- modulename: wwith, funcname: <module>
|
|
104
|
-
# wwith.py(1): with open("/tmp/test", "w") as f1:
|
|
105
|
-
# wwith.py(2): a = 2
|
|
106
|
-
# wwith.py(3): with open("/tmp/test2", "w") as f3:
|
|
107
|
-
# wwith.py(4): print(4)
|
|
108
|
-
# wwith.py(3): with open("/tmp/test2", "w") as f3:
|
|
109
|
-
# wwith.py(1): with open("/tmp/test", "w") as f1:
|
|
110
|
-
#
|
|
111
|
-
exit_through_with = (PYVERSION >= (3, 10, 0, "beta")) # fmt: skip
|
|
112
|
-
|
|
113
56
|
# When leaving a with-block, do we visit the with-line exactly,
|
|
114
57
|
# or the context managers in inner-out order?
|
|
115
58
|
#
|
|
@@ -147,12 +90,6 @@ class PYBEHAVIOR:
|
|
|
147
90
|
|
|
148
91
|
exit_with_through_ctxmgr = (PYVERSION >= (3, 12, 6)) # fmt: skip
|
|
149
92
|
|
|
150
|
-
# Match-case construct.
|
|
151
|
-
match_case = (PYVERSION >= (3, 10)) # fmt: skip
|
|
152
|
-
|
|
153
|
-
# Some words are keywords in some places, identifiers in other places.
|
|
154
|
-
soft_keywords = (PYVERSION >= (3, 10)) # fmt: skip
|
|
155
|
-
|
|
156
93
|
# f-strings are parsed as code, pep 701
|
|
157
94
|
fstring_syntax = (PYVERSION >= (3, 12)) # fmt: skip
|
|
158
95
|
|
coverage/exceptions.py
CHANGED
|
@@ -5,20 +5,18 @@
|
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
+
from typing import Any
|
|
8
9
|
|
|
9
|
-
class _BaseCoverageException(Exception):
|
|
10
|
-
"""The base-base of all Coverage exceptions."""
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class CoverageException(_BaseCoverageException):
|
|
11
|
+
class CoverageException(Exception):
|
|
16
12
|
"""The base class of all exceptions raised by Coverage.py."""
|
|
17
13
|
|
|
18
|
-
|
|
14
|
+
def __init__(self, *args: Any, slug: str | None = None) -> None:
|
|
15
|
+
super().__init__(*args)
|
|
16
|
+
self.slug = slug
|
|
19
17
|
|
|
20
18
|
|
|
21
|
-
class ConfigError(
|
|
19
|
+
class ConfigError(CoverageException):
|
|
22
20
|
"""A problem with a config file, or a value in one."""
|
|
23
21
|
|
|
24
22
|
pass
|
coverage/html.py
CHANGED
|
@@ -32,7 +32,7 @@ from coverage.misc import (
|
|
|
32
32
|
stdout_link,
|
|
33
33
|
)
|
|
34
34
|
from coverage.report_core import get_analysis_to_report
|
|
35
|
-
from coverage.results import Analysis, Numbers
|
|
35
|
+
from coverage.results import Analysis, AnalysisNarrower, Numbers
|
|
36
36
|
from coverage.templite import Templite
|
|
37
37
|
from coverage.types import TLineNo, TMorf
|
|
38
38
|
from coverage.version import __url__
|
|
@@ -582,13 +582,21 @@ class HtmlReporter:
|
|
|
582
582
|
|
|
583
583
|
for noun in region_nouns:
|
|
584
584
|
page_data = self.index_pages[noun]
|
|
585
|
-
outside_lines = set(range(1, num_lines + 1))
|
|
586
585
|
|
|
586
|
+
outside_lines = set(range(1, num_lines + 1))
|
|
587
587
|
for region in regions:
|
|
588
588
|
if region.kind != noun:
|
|
589
589
|
continue
|
|
590
590
|
outside_lines -= region.lines
|
|
591
|
-
|
|
591
|
+
|
|
592
|
+
narrower = AnalysisNarrower(ftr.analysis)
|
|
593
|
+
narrower.add_regions(r.lines for r in regions if r.kind == noun)
|
|
594
|
+
narrower.add_regions([outside_lines])
|
|
595
|
+
|
|
596
|
+
for region in regions:
|
|
597
|
+
if region.kind != noun:
|
|
598
|
+
continue
|
|
599
|
+
analysis = narrower.narrow(region.lines)
|
|
592
600
|
if not self.should_report(analysis, page_data):
|
|
593
601
|
continue
|
|
594
602
|
sorting_name = region.name.rpartition(".")[-1].lstrip("_")
|
|
@@ -605,7 +613,7 @@ class HtmlReporter:
|
|
|
605
613
|
)
|
|
606
614
|
)
|
|
607
615
|
|
|
608
|
-
analysis =
|
|
616
|
+
analysis = narrower.narrow(outside_lines)
|
|
609
617
|
if self.should_report(analysis, page_data):
|
|
610
618
|
page_data.summaries.append(
|
|
611
619
|
IndexItem(
|
coverage/jsonreport.py
CHANGED
|
@@ -13,7 +13,7 @@ from typing import IO, TYPE_CHECKING, Any
|
|
|
13
13
|
|
|
14
14
|
from coverage import __version__
|
|
15
15
|
from coverage.report_core import get_analysis_to_report
|
|
16
|
-
from coverage.results import Analysis, Numbers
|
|
16
|
+
from coverage.results import Analysis, AnalysisNarrower, Numbers
|
|
17
17
|
from coverage.types import TLineNo, TMorf
|
|
18
18
|
|
|
19
19
|
if TYPE_CHECKING:
|
|
@@ -128,21 +128,30 @@ class JsonReporter:
|
|
|
128
128
|
)
|
|
129
129
|
|
|
130
130
|
num_lines = len(file_reporter.source().splitlines())
|
|
131
|
+
regions = file_reporter.code_regions()
|
|
131
132
|
for noun, plural in file_reporter.code_region_kinds():
|
|
132
|
-
reported_file[plural] = region_data = {}
|
|
133
133
|
outside_lines = set(range(1, num_lines + 1))
|
|
134
|
-
for region in
|
|
134
|
+
for region in regions:
|
|
135
135
|
if region.kind != noun:
|
|
136
136
|
continue
|
|
137
137
|
outside_lines -= region.lines
|
|
138
|
+
|
|
139
|
+
narrower = AnalysisNarrower(analysis)
|
|
140
|
+
narrower.add_regions(r.lines for r in regions if r.kind == noun)
|
|
141
|
+
narrower.add_regions([outside_lines])
|
|
142
|
+
|
|
143
|
+
reported_file[plural] = region_data = {}
|
|
144
|
+
for region in regions:
|
|
145
|
+
if region.kind != noun:
|
|
146
|
+
continue
|
|
138
147
|
region_data[region.name] = self.make_region_data(
|
|
139
148
|
coverage_data,
|
|
140
|
-
|
|
149
|
+
narrower.narrow(region.lines),
|
|
141
150
|
)
|
|
142
151
|
|
|
143
152
|
region_data[""] = self.make_region_data(
|
|
144
153
|
coverage_data,
|
|
145
|
-
|
|
154
|
+
narrower.narrow(outside_lines),
|
|
146
155
|
)
|
|
147
156
|
return reported_file
|
|
148
157
|
|
coverage/lcovreport.py
CHANGED
|
@@ -13,7 +13,7 @@ from typing import IO, TYPE_CHECKING
|
|
|
13
13
|
|
|
14
14
|
from coverage.plugin import FileReporter
|
|
15
15
|
from coverage.report_core import get_analysis_to_report
|
|
16
|
-
from coverage.results import Analysis, Numbers
|
|
16
|
+
from coverage.results import Analysis, AnalysisNarrower, Numbers
|
|
17
17
|
from coverage.types import TMorf
|
|
18
18
|
|
|
19
19
|
if TYPE_CHECKING:
|
|
@@ -81,12 +81,15 @@ def lcov_functions(
|
|
|
81
81
|
if not functions:
|
|
82
82
|
return
|
|
83
83
|
|
|
84
|
+
narrower = AnalysisNarrower(file_analysis)
|
|
85
|
+
narrower.add_regions(r.lines for _, _, r in functions)
|
|
86
|
+
|
|
84
87
|
functions.sort()
|
|
85
88
|
functions_hit = 0
|
|
86
89
|
for first_line, last_line, region in functions:
|
|
87
90
|
# A function counts as having been executed if any of it has been
|
|
88
91
|
# executed.
|
|
89
|
-
analysis =
|
|
92
|
+
analysis = narrower.narrow(region.lines)
|
|
90
93
|
hit = int(analysis.numbers.n_executed > 0)
|
|
91
94
|
functions_hit += hit
|
|
92
95
|
|
coverage/misc.py
CHANGED
|
@@ -163,31 +163,32 @@ class Hasher:
|
|
|
163
163
|
def update(self, v: Any) -> None:
|
|
164
164
|
"""Add `v` to the hash, recursively if needed."""
|
|
165
165
|
self.hash.update(str(type(v)).encode("utf-8"))
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
166
|
+
match v:
|
|
167
|
+
case None:
|
|
168
|
+
pass
|
|
169
|
+
case str():
|
|
170
|
+
self.hash.update(v.encode("utf-8"))
|
|
171
|
+
case bytes():
|
|
172
|
+
self.hash.update(v)
|
|
173
|
+
case int() | float():
|
|
174
|
+
self.hash.update(str(v).encode("utf-8"))
|
|
175
|
+
case tuple() | list():
|
|
176
|
+
for e in v:
|
|
177
|
+
self.update(e)
|
|
178
|
+
case dict():
|
|
179
|
+
keys = v.keys()
|
|
180
|
+
for k in sorted(keys):
|
|
181
|
+
self.update(k)
|
|
182
|
+
self.update(v[k])
|
|
183
|
+
case _:
|
|
184
|
+
for k in dir(v):
|
|
185
|
+
if k.startswith("__"):
|
|
186
|
+
continue
|
|
187
|
+
a = getattr(v, k)
|
|
188
|
+
if inspect.isroutine(a):
|
|
189
|
+
continue
|
|
190
|
+
self.update(k)
|
|
191
|
+
self.update(a)
|
|
191
192
|
self.hash.update(b".")
|
|
192
193
|
|
|
193
194
|
def hexdigest(self) -> str:
|