1010from collections import defaultdict
1111from html import escape
1212from pathlib import Path
13+ from typing import Any
14+ from typing import DefaultDict
15+ from typing import Dict
16+ from typing import List
1317
1418import pytest
19+ from _pytest .config import Config
20+ from _pytest .main import Session
21+ from _pytest .reports import CollectReport
22+ from _pytest .reports import TestReport
23+ from _pytest .terminal import TerminalReporter
24+ from jinja2 .environment import Template
1525
1626from pytest_html import __version__
1727from pytest_html import extras
28+ from pytest_html .report_data import ReportData
1829
1930
2031class BaseReport :
21- def __init__ (self , report_path , config , report_data , template , css ):
22- self ._report_path = (
32+ def __init__ (
33+ self ,
34+ report_path : Path ,
35+ config : Config ,
36+ report_data : ReportData ,
37+ template : Template ,
38+ css : str ,
39+ ) -> None :
40+ self ._report_path : Path = (
2341 Path .cwd () / Path (os .path .expandvars (report_path )).expanduser ()
2442 )
2543 self ._report_path .parent .mkdir (parents = True , exist_ok = True )
26- self ._config = config
27- self ._template = template
28- self ._css = css
29- self ._max_asset_filename_length = int (
44+ self ._config : Config = config
45+ self ._template : Template = template
46+ self ._css : str = css
47+ self ._max_asset_filename_length : int = int (
3048 config .getini ("max_asset_filename_length" )
3149 )
3250
33- self ._reports = defaultdict (dict )
34- self ._report = report_data
35- self ._report .title = self ._report_path .name
51+ self ._reports : DefaultDict = defaultdict (dict )
52+ self ._report : ReportData = report_data
53+ self ._report .title : str = self ._report_path .name
3654
3755 @property
3856 def css (self ):
3957 # implement in subclasses
4058 return
4159
42- def _asset_filename (self , test_id , extra_index , test_index , file_extension ):
60+ def _asset_filename (
61+ self , test_id : str , extra_index : int , test_index : int , file_extension : str
62+ ) -> str :
4363 return "{}_{}_{}.{}" .format (
4464 re .sub (r"[^\w.]" , "_" , test_id ),
4565 str (extra_index ),
4666 str (test_index ),
4767 file_extension ,
4868 )[- self ._max_asset_filename_length :]
4969
50- def _generate_report (self , self_contained = False ):
70+ def _generate_report (self , self_contained : bool = False ) -> None :
5171 generated = datetime .datetime .now ()
5272 test_data = self ._report .data
5373 test_data = json .dumps (test_data )
@@ -68,7 +88,7 @@ def _generate_report(self, self_contained=False):
6888
6989 self ._write_report (rendered_report )
7090
71- def _generate_environment (self ):
91+ def _generate_environment (self ) -> Dict [ str , Any ] :
7292 try :
7393 from pytest_metadata .plugin import metadata_key
7494
@@ -89,21 +109,21 @@ def _generate_environment(self):
89109
90110 return metadata
91111
92- def _is_redactable_environment_variable (self , environment_variable ) :
112+ def _is_redactable_environment_variable (self , environment_variable : str ) -> bool :
93113 redactable_regexes = self ._config .getini ("environment_table_redact_list" )
94114 for redactable_regex in redactable_regexes :
95115 if re .match (redactable_regex , environment_variable ):
96116 return True
97117
98118 return False
99119
100- def _data_content (self , * args , ** kwargs ):
120+ def _data_content (self , * args , ** kwargs ) -> None :
101121 pass
102122
103- def _media_content (self , * args , ** kwargs ):
123+ def _media_content (self , * args , ** kwargs ) -> None :
104124 pass
105125
106- def _process_extras (self , report , test_id ) :
126+ def _process_extras (self , report : CollectReport , test_id : str ) -> List [ Any ] :
107127 test_index = hasattr (report , "rerun" ) and report .rerun + 1 or 0
108128 report_extras = getattr (report , "extras" , [])
109129 for extra_index , extra in enumerate (report_extras ):
@@ -134,12 +154,12 @@ def _process_extras(self, report, test_id):
134154
135155 return report_extras
136156
137- def _write_report (self , rendered_report ) :
157+ def _write_report (self , rendered_report : str ) -> None :
138158 with self ._report_path .open ("w" , encoding = "utf-8" ) as f :
139159 f .write (rendered_report )
140160
141- def _run_count (self ):
142- relevant_outcomes = ["passed" , "failed" , "xpassed" , "xfailed" ]
161+ def _run_count (self ) -> str :
162+ relevant_outcomes : List [ str ] = ["passed" , "failed" , "xpassed" , "xfailed" ]
143163 counts = 0
144164 for outcome in self ._report .outcomes .keys ():
145165 if outcome in relevant_outcomes :
@@ -153,7 +173,7 @@ def _run_count(self):
153173
154174 return f"{ counts } /{ self ._report .collected_items } { 'tests' if plural else 'test' } done."
155175
156- def _hydrate_data (self , data , cells ) :
176+ def _hydrate_data (self , data : Dict [ str , List ], cells : List [ str ]) -> None :
157177 for index , cell in enumerate (cells ):
158178 # extract column name and data if column is sortable
159179 if "sortable" in self ._report .table_header [index ]:
@@ -163,7 +183,7 @@ def _hydrate_data(self, data, cells):
163183 data [name_match .group (1 )] = data_match .group (1 )
164184
165185 @pytest .hookimpl (trylast = True )
166- def pytest_sessionstart (self , session ) :
186+ def pytest_sessionstart (self , session : Session ) -> None :
167187 self ._report .set_data ("environment" , self ._generate_environment ())
168188
169189 session .config .hook .pytest_html_report_title (report = self ._report )
@@ -176,7 +196,7 @@ def pytest_sessionstart(self, session):
176196 self ._generate_report ()
177197
178198 @pytest .hookimpl (trylast = True )
179- def pytest_sessionfinish (self , session ) :
199+ def pytest_sessionfinish (self , session : Session ) -> None :
180200 session .config .hook .pytest_html_results_summary (
181201 prefix = self ._report .additional_summary ["prefix" ],
182202 summary = self ._report .additional_summary ["summary" ],
@@ -187,23 +207,23 @@ def pytest_sessionfinish(self, session):
187207 self ._generate_report ()
188208
189209 @pytest .hookimpl (trylast = True )
190- def pytest_terminal_summary (self , terminalreporter ) :
210+ def pytest_terminal_summary (self , terminalreporter : TerminalReporter ) -> None :
191211 terminalreporter .write_sep (
192212 "-" ,
193213 f"Generated html report: { self ._report_path .as_uri ()} " ,
194214 )
195215
196216 @pytest .hookimpl (trylast = True )
197- def pytest_collectreport (self , report ) :
217+ def pytest_collectreport (self , report : CollectReport ) -> None :
198218 if report .failed :
199219 self ._process_report (report , 0 )
200220
201221 @pytest .hookimpl (trylast = True )
202- def pytest_collection_finish (self , session ) :
222+ def pytest_collection_finish (self , session : Session ) -> None :
203223 self ._report .collected_items = len (session .items )
204224
205225 @pytest .hookimpl (trylast = True )
206- def pytest_runtest_logreport (self , report ) :
226+ def pytest_runtest_logreport (self , report : TestReport ) -> None :
207227 if hasattr (report , "duration_formatter" ):
208228 warnings .warn (
209229 "'duration_formatter' has been removed and no longer has any effect!"
@@ -247,7 +267,7 @@ def pytest_runtest_logreport(self, report):
247267 if self ._config .getini ("generate_report_on_test" ):
248268 self ._generate_report ()
249269
250- def _process_report (self , report , duration ) :
270+ def _process_report (self , report : TestReport , duration : int ) -> None :
251271 outcome = _process_outcome (report )
252272 try :
253273 # hook returns as list for some reason
@@ -291,9 +311,9 @@ def _process_report(self, report, duration):
291311 self ._report .add_test (data , report , outcome , processed_logs )
292312
293313
294- def _format_duration (duration ) :
314+ def _format_duration (duration : float ) -> str :
295315 if duration < 1 :
296- return "{} ms" . format ( round (duration * 1000 ))
316+ return f" { round (duration * 1000 )} ms"
297317
298318 hours = math .floor (duration / 3600 )
299319 remaining_seconds = duration % 3600
@@ -304,13 +324,13 @@ def _format_duration(duration):
304324 return f"{ hours :02d} :{ minutes :02d} :{ seconds :02d} "
305325
306326
307- def _is_error (report ) :
327+ def _is_error (report : BaseReport ) -> bool :
308328 return (
309329 report .when in ["setup" , "teardown" , "collect" ] and report .outcome == "failed"
310330 )
311331
312332
313- def _process_logs (report ):
333+ def _process_logs (report ) -> List [ str ] :
314334 log = []
315335 if report .longreprtext :
316336 log .append (escape (report .longreprtext ) + "\n " )
@@ -330,7 +350,7 @@ def _process_logs(report):
330350 return log
331351
332352
333- def _process_outcome (report ) :
353+ def _process_outcome (report : TestReport ) -> str :
334354 if _is_error (report ):
335355 return "Error"
336356 if hasattr (report , "wasxfail" ):
@@ -342,12 +362,12 @@ def _process_outcome(report):
342362 return report .outcome .capitalize ()
343363
344364
345- def _process_links (links ):
365+ def _process_links (links ) -> str :
346366 a_tag = '<a target="_blank" href="{content}" class="col-links__extra {format_type}">{name}</a>'
347367 return "" .join ([a_tag .format_map (link ) for link in links ])
348368
349369
350- def _fix_py (cells ) :
370+ def _fix_py (cells : List [ str ]) -> List [ str ] :
351371 # backwards-compat
352372 new_cells = []
353373 for html in cells :
0 commit comments