From 261cd69238baff0a804bb9cbcbad39eeed63a4f5 Mon Sep 17 00:00:00 2001 From: "Michael R. Crusoe" Date: Fri, 24 Nov 2023 15:58:42 +0100 Subject: [PATCH] lxml type fixes --- .../datatypes/dataproviders/hierarchy.py | 2 +- lib/galaxy/dependencies/__init__.py | 10 +- .../galaxy_install/tools/data_manager.py | 7 +- .../tools/tool_panel_manager.py | 3 +- .../tool_shed/tools/data_table_manager.py | 2 +- lib/galaxy/tool_util/data/__init__.py | 51 +- lib/galaxy/tool_util/data/bundles/models.py | 22 +- lib/galaxy/tool_util/toolbox/base.py | 3 +- .../tool_util/verify/asserts/__init__.py | 8 +- lib/galaxy/tool_util/verify/asserts/xml.py | 19 +- lib/galaxy/util/__init__.py | 8 +- lib/galaxy/util/tool_shed/xml_util.py | 4 +- lib/galaxy/util/xml_macros.py | 8 +- mypy-stubs/lxml/README.txt | 2 + mypy-stubs/lxml/__init__.pyi | 0 mypy-stubs/lxml/etree.pyi | 759 ++++++++++++++++++ pyproject.toml | 1 + tox.ini | 1 + 18 files changed, 852 insertions(+), 58 deletions(-) create mode 100644 mypy-stubs/lxml/README.txt create mode 100644 mypy-stubs/lxml/__init__.pyi create mode 100644 mypy-stubs/lxml/etree.pyi diff --git a/lib/galaxy/datatypes/dataproviders/hierarchy.py b/lib/galaxy/datatypes/dataproviders/hierarchy.py index e0773975548f..b685068759da 100644 --- a/lib/galaxy/datatypes/dataproviders/hierarchy.py +++ b/lib/galaxy/datatypes/dataproviders/hierarchy.py @@ -66,7 +66,7 @@ def matches_selector(self, element, selector=None): # TODO: fails with '#' - browser thinks it's an anchor - use urlencode # TODO: need removal/replacement of etree namespacing here - then move to string match Element = getattr(etree, "_Element", etree.Element) - return bool((selector is None) or (isinstance(element, Element) and selector in element.tag)) + return bool((selector is None) or (isinstance(element, etree._Element) and selector in element.tag)) def element_as_dict(self, element): """ diff --git a/lib/galaxy/dependencies/__init__.py b/lib/galaxy/dependencies/__init__.py index aca4c3fd8e84..1685e8b184ce 100644 --- a/lib/galaxy/dependencies/__init__.py +++ b/lib/galaxy/dependencies/__init__.py @@ -68,9 +68,13 @@ def load_job_config_dict(job_conf_dict): if ".xml" in job_conf_path: try: try: - for plugin in parse_xml(job_conf_path).find("plugins").findall("plugin"): - if "load" in plugin.attrib: - self.job_runners.append(plugin.attrib["load"]) + plugins = parse_xml(job_conf_path).find("plugins") + if plugins is not None: + for plugin in plugins.findall("plugin"): + if "load" in plugin.attrib: + self.job_runners.append(plugin.attrib["load"]) + else: + pass except OSError: pass try: diff --git a/lib/galaxy/tool_shed/galaxy_install/tools/data_manager.py b/lib/galaxy/tool_shed/galaxy_install/tools/data_manager.py index d3cce73744b5..1f2a61393156 100644 --- a/lib/galaxy/tool_shed/galaxy_install/tools/data_manager.py +++ b/lib/galaxy/tool_shed/galaxy_install/tools/data_manager.py @@ -14,7 +14,6 @@ InstallationTarget, ) from galaxy.util import ( - Element, etree, parse_xml_string, xml_to_string, @@ -34,7 +33,7 @@ class DataManagerHandler: app: InstallationTarget - root: Optional[Element] = None + root: Optional[etree._Element] = None def __init__(self, app: InstallationTarget): self.app = app @@ -47,7 +46,7 @@ def data_managers_path(self) -> Optional[str]: return root.get("tool_path", None) return None - def _data_manager_config_elems_to_xml_file(self, config_elems: List[Element], config_filename: StrPath) -> None: + def _data_manager_config_elems_to_xml_file(self, config_elems: List[etree._Element], config_filename: StrPath) -> None: """ Persist the current in-memory list of config_elems to a file named by the value of config_filename. @@ -171,7 +170,7 @@ def install_data_managers( ) if data_manager: rval.append(data_manager) - elif elem.tag is etree.Comment: + elif isinstance(elem, etree._Comment): pass else: log.warning(f"Encountered unexpected element '{elem.tag}':\n{xml_to_string(elem)}") diff --git a/lib/galaxy/tool_shed/galaxy_install/tools/tool_panel_manager.py b/lib/galaxy/tool_shed/galaxy_install/tools/tool_panel_manager.py index c68ba7fe4be3..003478482189 100644 --- a/lib/galaxy/tool_shed/galaxy_install/tools/tool_panel_manager.py +++ b/lib/galaxy/tool_shed/galaxy_install/tools/tool_panel_manager.py @@ -298,7 +298,7 @@ def generate_tool_panel_elem_list( owner="", ): """Generate a list of ElementTree Element objects for each section or tool.""" - elem_list: List[etree.Element] = [] + elem_list: List[etree._Element] = [] tool_elem = None cleaned_repository_clone_url = remove_protocol_and_user_from_clone_url(repository_clone_url) if not owner: @@ -335,6 +335,7 @@ def generate_tool_panel_elem_list( tool, tool_section if inside_section else None, ) + assert tool_section is not None if inside_section: if section_in_elem_list is not None: elem_list[section_in_elem_list] = tool_section diff --git a/lib/galaxy/tool_shed/tools/data_table_manager.py b/lib/galaxy/tool_shed/tools/data_table_manager.py index f2d02a4d96a6..98732be20463 100644 --- a/lib/galaxy/tool_shed/tools/data_table_manager.py +++ b/lib/galaxy/tool_shed/tools/data_table_manager.py @@ -29,7 +29,7 @@ def __init__(self, app: RequiredAppT): def generate_repository_info_elem( self, tool_shed: str, repository_name: str, changeset_revision: str, owner: str, parent_elem=None, **kwd - ) -> etree.Element: + ) -> etree._Element: """Create and return an ElementTree repository info Element.""" if parent_elem is None: elem = etree.Element("tool_shed_repository") diff --git a/lib/galaxy/tool_util/data/__init__.py b/lib/galaxy/tool_util/data/__init__.py index 8654b1dae4d4..bfdbd281821e 100644 --- a/lib/galaxy/tool_util/data/__init__.py +++ b/lib/galaxy/tool_util/data/__init__.py @@ -42,7 +42,7 @@ from galaxy import util from galaxy.exceptions import MessageException from galaxy.util import ( - Element, + etree, RW_R__R__, ) from galaxy.util.compression_utils import decompress_path_to_directory @@ -133,12 +133,12 @@ class FileNameInfoT(TypedDict): filename: str from_shed_config: bool tool_data_path: Optional[StrPath] - config_element: Optional[Element] + config_element: Optional[etree._Element] tool_shed_repository: Optional[Dict[str, Any]] errors: ErrorListT -LoadInfoT = Tuple[Tuple[Element, Optional[StrPath]], Dict[str, Any]] +LoadInfoT = Tuple[Tuple[etree._Element, Optional[StrPath]], Dict[str, Any]] class ToolDataTable(Dictifiable): @@ -162,7 +162,7 @@ def from_dict(cls, d): def __init__( self, - config_element: Element, + config_element: etree._Element, tool_data_path: Optional[StrPath], tool_data_path_files: ToolDataPathFiles, from_shed_config: bool = False, @@ -179,7 +179,7 @@ def __init__( self.tool_data_path = tool_data_path self.tool_data_path_files = tool_data_path_files self.other_config_dict = other_config_dict or {} - self.missing_index_file = None + self.missing_index_file: Optional[str] = None # increment this variable any time a new entry is added, or when the table is totally reloaded # This value has no external meaning, and does not represent an abstract version of the underlying data self._loaded_content_version = 1 @@ -192,7 +192,9 @@ def __init__( "filename": filename, }, ) - self._merged_load_info: List[Tuple[Type[ToolDataTable], Tuple[Tuple[Element, StrPath], Dict[str, Any]]]] = [] + self._merged_load_info: List[ + Tuple[Type[ToolDataTable], Tuple[Tuple[etree._Element, StrPath], Dict[str, Any]]] + ] = [] def _update_version(self, version: Optional[int] = None) -> int: if version is not None: @@ -303,6 +305,7 @@ class TabularToolDataTable(ToolDataTable): """ + comment_char: str dict_collection_visible_keys = ["name"] dict_element_visible_keys = ["name", "fields"] dict_export_visible_keys = ["name", "data", "largest_index", "columns", "missing_index_file"] @@ -311,7 +314,7 @@ class TabularToolDataTable(ToolDataTable): def __init__( self, - config_element: Element, + config_element: etree._Element, tool_data_path: Optional[StrPath], tool_data_path_files: ToolDataPathFiles, from_shed_config: bool = False, @@ -332,7 +335,7 @@ def __init__( def configure_and_load( self, - config_element: Element, + config_element: etree._Element, tool_data_path: Optional[StrPath], from_shed_config: bool = False, url_timeout: float = 10, @@ -349,10 +352,10 @@ def configure_and_load( repo_elem = config_element.find("tool_shed_repository") if repo_elem is not None: repo_info = dict( - tool_shed=repo_elem.find("tool_shed").text, - name=repo_elem.find("repository_name").text, - owner=repo_elem.find("repository_owner").text, - installed_changeset_revision=repo_elem.find("installed_changeset_revision").text, + tool_shed=cast(etree._Element, repo_elem.find("tool_shed")).text, + name=cast(etree._Element, repo_elem.find("repository_name")).text, + owner=cast(etree._Element, repo_elem.find("repository_owner")).text, + installed_changeset_revision=cast(etree._Element, repo_elem.find("installed_changeset_revision")).text, ) else: repo_info = None @@ -378,6 +381,7 @@ def configure_and_load( filename = file_element.get("from_config", None) or None if filename: filename = self.other_config_dict.get(filename, None) + assert filename is not None filename = file_path = _expand_here_template(filename, here=self.here) found = False if file_path is None: @@ -507,7 +511,7 @@ def get_named_fields_list(self) -> List[Dict[Union[str, int], str]]: def get_version_fields(self): return (self._loaded_content_version, self.get_fields()) - def parse_column_spec(self, config_element: Element) -> None: + def parse_column_spec(self, config_element: etree._Element) -> None: """ Parse column definitions, which can either be a set of 'column' elements with a name and index (as in dynamic options config), or a shorthand @@ -528,12 +532,12 @@ def parse_column_spec(self, config_element: Element) -> None: for column_elem in config_element.findall("column"): name = column_elem.get("name", None) assert name is not None, "Required 'name' attribute missing from column def" - index = column_elem.get("index", None) + index2 = column_elem.get("index", None) assert index is not None, "Required 'index' attribute missing from column def" - index = int(index) - self.columns[name] = index - if index > self.largest_index: - self.largest_index = index + index3 = int(cast(str, index2)) + self.columns[name] = index3 + if index3 > self.largest_index: + self.largest_index = index3 empty_field_value = column_elem.get("empty_field_value", None) if empty_field_value is not None: self.empty_field_values[name] = empty_field_value @@ -965,7 +969,7 @@ def to_json(self, path: StrPath) -> None: def load_from_config_file( self, config_filename: StrPath, tool_data_path: Optional[StrPath], from_shed_config: bool = False - ) -> List[Element]: + ) -> List[etree._Element]: """ This method is called under 3 conditions: @@ -989,6 +993,7 @@ def load_from_config_file( ) table_elems.append(table_elem) if table.name not in self.data_tables: + assert table.name is not None self.data_tables[table.name] = table log.debug("Loaded tool data table '%s' from file '%s'", table.name, config_filename) else: @@ -1005,7 +1010,7 @@ def load_from_config_file( def from_elem( self, - table_elem: Element, + table_elem: etree._Element, tool_data_path: Optional[StrPath], from_shed_config: bool, filename: StrPath, @@ -1029,7 +1034,7 @@ def add_new_entries_from_config_file( tool_data_path: Optional[StrPath], shed_tool_data_table_config: StrPath, persist: bool = False, - ) -> Tuple[List[Element], str]: + ) -> Tuple[List[etree._Element], str]: """ This method is called when a tool shed repository that includes a tool_data_table_conf.xml.sample file is being installed into a local galaxy instance. We have 2 cases to handle, files whose root tag is , for example:: @@ -1070,8 +1075,8 @@ def add_new_entries_from_config_file( def to_xml_file( self, shed_tool_data_table_config: StrPath, - new_elems: Optional[List[Element]] = None, - remove_elems: Optional[List[Element]] = None, + new_elems: Optional[List[etree._Element]] = None, + remove_elems: Optional[List[etree._Element]] = None, ) -> None: """ Write the current in-memory version of the shed_tool_data_table_conf.xml file to disk. diff --git a/lib/galaxy/tool_util/data/bundles/models.py b/lib/galaxy/tool_util/data/bundles/models.py index 6a3b8a1fc2ba..4c33d14e6b5d 100644 --- a/lib/galaxy/tool_util/data/bundles/models.py +++ b/lib/galaxy/tool_util/data/bundles/models.py @@ -17,7 +17,7 @@ from galaxy.util import ( asbool, - Element, + etree, ) DEFAULT_VALUE_TRANSLATION_TYPE = "template" @@ -170,7 +170,9 @@ class DataTableBundle(BaseModel): repo_info: Optional[RepoInfo] = None -def _xml_to_data_table_output_column_move(move_elem: Element) -> DataTableBundleProcessorDataTableOutputColumnMove: +def _xml_to_data_table_output_column_move( + move_elem: etree._Element, +) -> DataTableBundleProcessorDataTableOutputColumnMove: move_type = move_elem.get("type", "directory") relativize_symlinks = move_elem.get( "relativize_symlinks", False @@ -178,14 +180,14 @@ def _xml_to_data_table_output_column_move(move_elem: Element) -> DataTableBundle source_elem = move_elem.find("source") if source_elem is None: source_base = None - source_value = "" + source_value: Optional[str] = "" else: source_base = source_elem.get("base", None) source_value = source_elem.text target_elem = move_elem.find("target") if target_elem is None: target_base = None - target_value = "" + target_value: Optional[str] = "" else: target_base = target_elem.get("base", None) target_value = target_elem.text @@ -200,7 +202,7 @@ def _xml_to_data_table_output_column_move(move_elem: Element) -> DataTableBundle def _xml_to_data_table_output_column_translation( - value_translation_elem: Element, + value_translation_elem: etree._Element, ) -> Optional[DataTableBundleProcessorDataTableOutputColumnTranslation]: value_translation = value_translation_elem.text if value_translation is not None: @@ -212,7 +214,7 @@ def _xml_to_data_table_output_column_translation( return None -def _xml_to_data_table_output_column(column_elem: Element) -> DataTableBundleProcessorDataTableOutputColumn: +def _xml_to_data_table_output_column(column_elem: etree._Element) -> DataTableBundleProcessorDataTableOutputColumn: column_name = column_elem.get("name", None) assert column_name is not None, "Name is required for column entry" data_table_column_name = column_elem.get("data_table_name", column_name) @@ -239,7 +241,9 @@ def _xml_to_data_table_output_column(column_elem: Element) -> DataTableBundlePro ) -def _xml_to_data_table_output(output_elem: Optional[Element]) -> Optional[DataTableBundleProcessorDataTableOutput]: +def _xml_to_data_table_output( + output_elem: Optional[etree._Element], +) -> Optional[DataTableBundleProcessorDataTableOutput]: if output_elem is not None: columns = [] for column_elem in output_elem.findall("column"): @@ -249,7 +253,7 @@ def _xml_to_data_table_output(output_elem: Optional[Element]) -> Optional[DataTa return None -def _xml_to_data_table(data_table_elem: Element) -> DataTableBundleProcessorDataTable: +def _xml_to_data_table(data_table_elem: etree._Element) -> DataTableBundleProcessorDataTable: data_table_name = data_table_elem.get("name") assert data_table_name is not None, "A name is required for a data table entry" @@ -258,7 +262,7 @@ def _xml_to_data_table(data_table_elem: Element) -> DataTableBundleProcessorData return DataTableBundleProcessorDataTable(name=data_table_name, output=output) -def convert_data_tables_xml(elem: Element) -> DataTableBundleProcessorDescription: +def convert_data_tables_xml(elem: etree._Element) -> DataTableBundleProcessorDescription: undeclared_tables = asbool(elem.get("undeclared_tables", False)) data_tables = [] for data_table_elem in elem.findall("data_table"): diff --git a/lib/galaxy/tool_util/toolbox/base.py b/lib/galaxy/tool_util/toolbox/base.py index 264b96d26d60..6d1f2c082af2 100644 --- a/lib/galaxy/tool_util/toolbox/base.py +++ b/lib/galaxy/tool_util/toolbox/base.py @@ -7,6 +7,7 @@ from collections import namedtuple from errno import ENOENT from typing import ( + cast, Any, Dict, List, @@ -660,7 +661,7 @@ def _load_integrated_tool_panel_keys(self): elif elem.tag == "section": section = ToolSection(elem) for section_elem in elem: - section_id = section_elem.get("id") + section_id = cast(str, section_elem.get("id")) if section_elem.tag == "tool": section.elems.stub_tool(section_id) elif section_elem.tag == "workflow": diff --git a/lib/galaxy/tool_util/verify/asserts/__init__.py b/lib/galaxy/tool_util/verify/asserts/__init__.py index 1589b4b0c18d..5ab21c8b8349 100644 --- a/lib/galaxy/tool_util/verify/asserts/__init__.py +++ b/lib/galaxy/tool_util/verify/asserts/__init__.py @@ -5,6 +5,7 @@ getmembers, ) from tempfile import NamedTemporaryFile +from typing import List, Optional, Union from galaxy.util import unicodify from galaxy.util.compression_utils import get_fileobj @@ -32,11 +33,12 @@ assertion_functions[member] = value -def verify_assertions(data: bytes, assertion_description_list, decompress=None): +def verify_assertions(data: Union[bytes, str], assertion_description_list: List[str], decompress: Optional[bool] = None) -> None: """This function takes a list of assertions and a string to check these assertions against.""" if decompress: - with NamedTemporaryFile() as tmpfh: + mode = "rb" if isinstance(data, bytes) else "rt" + with NamedTemporaryFile(mode) as tmpfh: tmpfh.write(data) tmpfh.flush() with get_fileobj(tmpfh.name, mode="rb", compressed_formats=None) as fh: @@ -45,7 +47,7 @@ def verify_assertions(data: bytes, assertion_description_list, decompress=None): verify_assertion(data, assertion_description) -def verify_assertion(data: bytes, assertion_description): +def verify_assertion(data: Union[bytes, str], assertion_description) -> None: tag = assertion_description["tag"] assert_function_name = "assert_" + tag assert_function = assertion_functions.get(assert_function_name) diff --git a/lib/galaxy/tool_util/verify/asserts/xml.py b/lib/galaxy/tool_util/verify/asserts/xml.py index bdb53e3913e8..599d0ac2aafa 100644 --- a/lib/galaxy/tool_util/verify/asserts/xml.py +++ b/lib/galaxy/tool_util/verify/asserts/xml.py @@ -1,6 +1,10 @@ import re from typing import ( + Any, + cast, + List, Optional, + Protocol, Union, ) @@ -14,6 +18,11 @@ ) +class VerifyAssertionsFunctionType(Protocol): + def __call__(self, __data: Union[bytes, str], __assertion_description_list: List[str], **kwargs: Any) -> None: + ... + + def assert_is_valid_xml(output: str) -> None: """Simple assertion that just verifies the specified output is valid XML.""" @@ -74,7 +83,11 @@ def assert_attribute_is(output: str, path: str, attribute: str, text, negate: Un def assert_element_text( - output: str, path: str, verify_assertions_function, children, negate: Union[bool, str] = False + output: str, + path: str, + verify_assertions_function: VerifyAssertionsFunctionType, + children, + negate: Union[bool, str] = False, ) -> None: """Recursively checks the specified assertions against the text of the first element matching the specified path.""" @@ -84,7 +97,7 @@ def assert_element_text( def assert_xml_element( output: str, path: str, - verify_assertions_function=None, + verify_assertions_function: Optional[VerifyAssertionsFunctionType] = None, children=None, attribute: Optional[str] = None, all: Union[bool, str] = False, @@ -127,7 +140,7 @@ def assert_xml_element( return for occ in xml.findall(path): if attribute is None or attribute == "": - content = occ.text + content: Union[str, bytes] = cast(str, occ.text) else: content = occ.attrib[attribute] try: diff --git a/lib/galaxy/util/__init__.py b/lib/galaxy/util/__init__.py index 2db3e454bb22..31d112d435a4 100644 --- a/lib/galaxy/util/__init__.py +++ b/lib/galaxy/util/__init__.py @@ -43,6 +43,7 @@ Tuple, TypeVar, Union, + cast ) from urllib.parse import ( urlencode, @@ -75,8 +76,8 @@ # __init__() method, so we can add a __new__() constructor that mimicks # xml.etree.ElementTree.ElementTree initialization. class ElementTree(etree._ElementTree): - def __new__(cls, element=None, file=None) -> etree.ElementTree: - return etree.ElementTree(element, file=file) + def __new__(cls, element=None, file=None) -> "ElementTree": + return cast("ElementTree", etree.ElementTree(element, file=file)) except ImportError: LXML_AVAILABLE = False @@ -103,6 +104,7 @@ def __new__(cls, element=None, file=None) -> etree.ElementTree: def shlex_join(split_command): return " ".join(map(shlex.quote, split_command)) +__all__ = [ "etree" ] inflector = Inflector() @@ -294,7 +296,7 @@ def unique_id(KEY_SIZE=128): return md5(random_bits).hexdigest() -def parse_xml(fname: StrPath, strip_whitespace=True, remove_comments=True) -> ElementTree: +def parse_xml(fname: StrPath, strip_whitespace=True, remove_comments=True) -> etree._ElementTree: """Returns a parsed xml tree""" parser = None if remove_comments and LXML_AVAILABLE: diff --git a/lib/galaxy/util/tool_shed/xml_util.py b/lib/galaxy/util/tool_shed/xml_util.py index b7bc8fba1ac3..5e13c40856e6 100644 --- a/lib/galaxy/util/tool_shed/xml_util.py +++ b/lib/galaxy/util/tool_shed/xml_util.py @@ -17,7 +17,7 @@ log = logging.getLogger(__name__) -def create_and_write_tmp_file(elem: etree.Element) -> str: +def create_and_write_tmp_file(elem: etree._Element) -> str: tmp_str = xml_to_string(elem, pretty=True) with tempfile.NamedTemporaryFile(prefix="tmp-toolshed-cawrf", delete=False) as fh: tmp_filename = fh.name @@ -26,7 +26,7 @@ def create_and_write_tmp_file(elem: etree.Element) -> str: return tmp_filename -def parse_xml(file_name: StrPath, check_exists=True) -> Tuple[Optional[etree.ElementTree], str]: +def parse_xml(file_name: StrPath, check_exists=True) -> Tuple[Optional[etree._ElementTree], str]: """Returns a parsed xml tree with comments intact.""" error_message = "" if check_exists and not os.path.exists(file_name): diff --git a/lib/galaxy/util/xml_macros.py b/lib/galaxy/util/xml_macros.py index b52e2bc08f8e..1d022be702d0 100644 --- a/lib/galaxy/util/xml_macros.py +++ b/lib/galaxy/util/xml_macros.py @@ -9,7 +9,7 @@ from galaxy.util import ( Element, - ElementTree, + etree, parse_xml, ) from galaxy.util.path import StrPath @@ -17,7 +17,7 @@ REQUIRED_PARAMETER = object() -def load_with_references(path: StrPath) -> Tuple[ElementTree, Optional[List[str]]]: +def load_with_references(path: StrPath) -> Tuple[etree._ElementTree, Optional[List[str]]]: """Load XML documentation from file system and preprocesses XML macros. Return the XML representation of the expanded tree and paths to @@ -53,7 +53,7 @@ def load_with_references(path: StrPath) -> Tuple[ElementTree, Optional[List[str] return tree, macro_paths -def load(path: StrPath) -> ElementTree: +def load(path: StrPath) -> etree._ElementTree: tree, _ = load_with_references(path) return tree @@ -71,7 +71,7 @@ def template_macro_params(root): return param_dict -def raw_xml_tree(path: StrPath) -> ElementTree: +def raw_xml_tree(path: StrPath) -> etree._ElementTree: """Load raw (no macro expansion) tree representation of XML represented at the specified path. """ diff --git a/mypy-stubs/lxml/README.txt b/mypy-stubs/lxml/README.txt new file mode 100644 index 000000000000..0665f8152576 --- /dev/null +++ b/mypy-stubs/lxml/README.txt @@ -0,0 +1,2 @@ +Can be removed when there is a new release of lxml-stubs incorporating the following +- https://github.com/lxml/lxml-stubs/pull/94 diff --git a/mypy-stubs/lxml/__init__.pyi b/mypy-stubs/lxml/__init__.pyi new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/mypy-stubs/lxml/etree.pyi b/mypy-stubs/lxml/etree.pyi new file mode 100644 index 000000000000..d62cd785ae69 --- /dev/null +++ b/mypy-stubs/lxml/etree.pyi @@ -0,0 +1,759 @@ +# Hand-written stub for lxml.etree as used by mypy.report. +# This is *far* from complete, and the stubgen-generated ones crash mypy. +# Any use of `Any` below means I couldn't figure out the type. + +from os import PathLike +from typing import ( + IO, + Any, + Callable, + Collection, + Dict, + Iterable, + Iterator, + List, + Mapping, + Optional, + Sequence, + Sized, + SupportsBytes, + Tuple, + Type, + TypeVar, + Union, + overload, +) + +from typing_extensions import Literal, Protocol, TypeAlias, TypeGuard + +# dummy for missing stubs +def __getattr__(name: str) -> Any: ... + +# We do *not* want `typing.AnyStr` because it is a `TypeVar`, which is an +# unnecessary constraint. It seems reasonable to constrain each +# List/Dict argument to use one type consistently, though, and it is +# necessary in order to keep these brief. +_AnyStr = Union[str, bytes] +_AnySmartStr = Union[ + "_ElementUnicodeResult", "_PyElementUnicodeResult", "_ElementStringResult" +] +_TagName = Union[str, bytes, QName] +# _TagSelector also allows Element, Comment, ProcessingInstruction +_TagSelector = Union[_TagName, Collection[_TagSelector], Any] +# XPath object - http://lxml.de/xpathxslt.html#xpath-return-values +_XPathObject = Union[ + bool, + float, + _AnySmartStr, + _AnyStr, + List[ + Union[ + "_Element", + _AnySmartStr, + _AnyStr, + Tuple[Optional[_AnyStr], Optional[_AnyStr]], + ] + ], +] +_AnyParser = Union["XMLParser", "HTMLParser"] +_ListAnyStr = Union[List[str], List[bytes]] +_DictAnyStr = Union[Dict[str, str], Dict[bytes, bytes]] +_Dict_Tuple2AnyStr_Any = Union[Dict[Tuple[str, str], Any], Tuple[bytes, bytes], Any] +_xpath = Union["XPath", _AnyStr] + +# See https://github.com/python/typing/pull/273 +# Due to Mapping having invariant key types, Mapping[Union[A, B], ...] +# would fail to validate against either Mapping[A, ...] or Mapping[B, ...] +# Try to settle for simpler solution, encouraging use of empty string ('') +# as default namespace prefix. If too many people complain, it can be +# back-paddled as Mapping[Any, ...] +_NSMapArg = Mapping[str, str] +_NonDefaultNSMapArg = Mapping[str, str] # empty prefix disallowed + +_T = TypeVar("_T") +_KnownEncodings = Literal[ + "ASCII", + "ascii", + "UTF-8", + "utf-8", + "UTF8", + "utf8", + "US-ASCII", + "us-ascii", +] +_ElementOrTree = Union[_Element, _ElementTree] +_FileSource = Union[_AnyStr, IO[Any], PathLike[Any]] + +class ElementChildIterator(Iterator["_Element"]): + def __iter__(self) -> "ElementChildIterator": ... + def __next__(self) -> "_Element": ... + +class _ElementUnicodeResult(str): + is_attribute: bool + is_tail: bool + is_text: bool + attrname: Optional[_AnyStr] + def getparent(self) -> Optional["_Element"]: ... + +class _PyElementUnicodeResult(str): + is_attribute: bool + is_tail: bool + is_text: bool + attrname: Optional[_AnyStr] + def getparent(self) -> Optional["_Element"]: ... + +class _ElementStringResult(bytes): + is_attribute: bool + is_tail: bool + is_text: bool + attrname: Optional[_AnyStr] + def getparent(self) -> Optional["_Element"]: ... + +class DocInfo: + root_name = ... # type: str + public_id = ... # type: Optional[str] + system_id = ... # type: Optional[str] + xml_version = ... # type: Optional[str] + encoding = ... # type: Optional[str] + standalone = ... # type: Optional[bool] + URL = ... # type: str + internalDTD = ... # type: "DTD" + externalDTD = ... # type: "DTD" + def __init__(self, tree: Union["_ElementTree", "_Element"]) -> None: ... + def clear(self) -> None: ... + +class _Element(Iterable["_Element"], Sized): + def __delitem__(self, key: Union[int, slice]) -> None: ... + def __getitem__(self, item: int) -> _Element: ... + def __iter__(self) -> ElementChildIterator: ... + def __len__(self) -> int: ... + def addprevious(self, element: "_Element") -> None: ... + def addnext(self, element: "_Element") -> None: ... + def append(self, element: "_Element") -> None: ... + def cssselect(self, expression: str) -> List[_Element]: ... + def extend(self, elements: Iterable["_Element"]) -> None: ... + def find( + self, path: str, namespaces: Optional[_NSMapArg] = ... + ) -> Optional["_Element"]: ... + @overload + def findtext( + self, + path: str, + namespaces: Optional[_NSMapArg] = ..., + ) -> Optional[str]: ... + @overload + def findtext( + self, + path: str, + default: _T = ..., + namespaces: Optional[_NSMapArg] = ..., + ) -> Union[str, _T]: ... + def findall( + self, path: str, namespaces: Optional[_NSMapArg] = ... + ) -> List["_Element"]: ... + def clear(self) -> None: ... + @overload + def get(self, key: _TagName) -> Optional[str]: ... + @overload + def get(self, key: _TagName, default: _T) -> Union[str, _T]: ... + def getnext(self) -> Optional[_Element]: ... + def getparent(self) -> Optional[_Element]: ... + def getprevious(self) -> Optional[_Element]: ... + def getroottree(self) -> _ElementTree: ... + def index( + self, child: _Element, start: Optional[int] = ..., stop: Optional[int] = ... + ) -> int: ... + def insert(self, index: int, element: _Element) -> None: ... + def items(self) -> Sequence[Tuple[_AnyStr, _AnyStr]]: ... + def iter( + self, tag: Optional[_TagSelector] = ..., *tags: _TagSelector + ) -> Iterator[_Element]: ... + iterancestors = iter + def iterchildren( + self, + tag: Optional[_TagSelector] = ..., + *tags: _TagSelector, + reversed: bool = False, + ) -> Iterator[_Element]: ... + iterdescendants = iter + def iterfind( + self, path: str, namespaces: Optional[_NSMapArg] = ... + ) -> Iterator["_Element"]: ... + def itersiblings( + self, + tag: Optional[_TagSelector] = ..., + *tags: _TagSelector, + preceding: bool = False, + ) -> Iterator[_Element]: ... + def itertext( + self, + tag: Optional[_TagSelector] = ..., + *tags: _TagSelector, + with_tail: bool = False, + ) -> Iterator[_AnyStr]: ... + def keys(self) -> Sequence[_AnyStr]: ... + def makeelement( + self, + _tag: _TagName, + attrib: Optional[_DictAnyStr] = ..., + nsmap: Optional[_NSMapArg] = ..., + **_extra: Any, + ) -> _Element: ... + def remove(self, element: _Element) -> None: ... + def replace(self, old_element: _Element, new_element: _Element) -> None: ... + def set(self, key: _TagName, value: _AnyStr) -> None: ... + def values(self) -> Sequence[_AnyStr]: ... + def xpath( + self, + _path: _AnyStr, + namespaces: Optional[_NonDefaultNSMapArg] = ..., + extensions: Any = ..., + smart_strings: bool = ..., + **_variables: _XPathObject, + ) -> _XPathObject: ... + tag = ... # type: str + attrib = ... # type: _Attrib + text = ... # type: Optional[str] + tail = ... # type: Optional[str] + prefix = ... # type: str + sourceline = ... # Optional[int] + @property + def nsmap(self) -> Dict[Optional[str], str]: ... + base = ... # type: Optional[str] + +class ElementBase(_Element): ... + +class _ElementTree: + parser = ... # type: _AnyParser + docinfo = ... # type: DocInfo + def find( + self, path: str, namespaces: Optional[_NSMapArg] = ... + ) -> Optional["_Element"]: ... + def findtext( + self, + path: str, + default: Optional[str] = ..., + namespaces: Optional[_NSMapArg] = ..., + ) -> Optional[str]: ... + def findall( + self, path: str, namespaces: Optional[_NSMapArg] = ... + ) -> List["_Element"]: ... + def getpath(self, element: _Element) -> str: ... + def getelementpath(self, element: _Element) -> str: ... + def getroot(self) -> _Element: ... + def iter( + self, tag: Optional[_TagSelector] = ..., *tags: _TagSelector + ) -> Iterator[_Element]: ... + def iterfind( + self, path: str, namespaces: Optional[_NSMapArg] = ... + ) -> Iterator["_Element"]: ... + def parse( + self, + source: _FileSource, + parser: Optional[_AnyParser] = ..., + base_url: Optional[_AnyStr] = ..., + ) -> _Element: ... + def write( + self, + file: _FileSource, + encoding: _AnyStr = ..., + method: _AnyStr = ..., + pretty_print: bool = ..., + xml_declaration: Any = ..., + with_tail: Any = ..., + standalone: bool = ..., + compression: int = ..., + exclusive: bool = ..., + with_comments: bool = ..., + inclusive_ns_prefixes: _ListAnyStr = ..., + ) -> None: ... + def write_c14n( + self, + file: _FileSource, + with_comments: bool = ..., + compression: int = ..., + inclusive_ns_prefixes: Iterable[_AnyStr] = ..., + ) -> None: ... + def _setroot(self, root: _Element) -> None: ... + def xinclude(self) -> None: ... + def xpath( + self, + _path: _AnyStr, + namespaces: Optional[_NonDefaultNSMapArg] = ..., + extensions: Any = ..., + smart_strings: bool = ..., + **_variables: _XPathObject, + ) -> _XPathObject: ... + def xslt( + self, + _xslt: XSLT, + extensions: Optional[_Dict_Tuple2AnyStr_Any] = ..., + access_control: Optional[XSLTAccessControl] = ..., + **_variables: Any, + ) -> _ElementTree: ... + +class __ContentOnlyEleement(_Element): ... +class _Comment(__ContentOnlyEleement): ... + +class _ProcessingInstruction(__ContentOnlyEleement): + target: _AnyStr + +class _Attrib: + def __setitem__(self, key: _AnyStr, value: _AnyStr) -> None: ... + def __delitem__(self, key: _AnyStr) -> None: ... + def update( + self, + sequence_or_dict: Union[ + _Attrib, Mapping[_AnyStr, _AnyStr], Sequence[Tuple[_AnyStr, _AnyStr]] + ], + ) -> None: ... + @overload + def pop(self, key: _AnyStr) -> _AnyStr: ... + @overload + def pop(self, key: _AnyStr, default: _AnyStr) -> _AnyStr: ... + def clear(self) -> None: ... + def __repr__(self) -> str: ... + def __copy__(self) -> _DictAnyStr: ... + def __deepcopy__(self, memo: Dict[Any, Any]) -> _DictAnyStr: ... + def __getitem__(self, key: _AnyStr) -> _AnyStr: ... + def __bool__(self) -> bool: ... + def __len__(self) -> int: ... + def get(self, key: _AnyStr, default: Optional[_AnyStr] = ...) -> Optional[_AnyStr]: ... + def keys(self) -> _ListAnyStr: ... + def __iter__(self) -> Iterator[_AnyStr]: ... # actually _AttribIterator + def iterkeys(self) -> Iterator[_AnyStr]: ... + def values(self) -> _ListAnyStr: ... + def itervalues(self) -> Iterator[_AnyStr]: ... + def items(self) -> List[Tuple[_AnyStr, _AnyStr]]: ... + def iteritems(self) -> Iterator[Tuple[_AnyStr, _AnyStr]]: ... + def has_key(self, key: _AnyStr) -> bool: ... + def __contains__(self, key: _AnyStr) -> bool: ... + def __richcmp__(self, other: _Attrib, op: int) -> bool: ... + +class QName: + localname = ... # type: str + namespace = ... # type: str + text = ... # type: str + def __init__( + self, + text_or_uri_element: Union[None, _AnyStr, _Element], + tag: Optional[_AnyStr] = ..., + ) -> None: ... + +class _XSLTResultTree(_ElementTree, SupportsBytes): + def __bytes__(self) -> bytes: ... + +class _XSLTQuotedStringParam: ... + +# https://lxml.de/parsing.html#the-target-parser-interface +class ParserTarget(Protocol): + def comment(self, text: _AnyStr) -> None: ... + def close(self) -> Any: ... + def data(self, data: _AnyStr) -> None: ... + def end(self, tag: _AnyStr) -> None: ... + def start(self, tag: _AnyStr, attrib: Dict[_AnyStr, _AnyStr]) -> None: ... + +class ElementClassLookup: ... + +class FallbackElementClassLookup(ElementClassLookup): + fallback: Optional[ElementClassLookup] + def __init__(self, fallback: Optional[ElementClassLookup] = ...): ... + def set_fallback(self, lookup: ElementClassLookup) -> None: ... + +class CustomElementClassLookup(FallbackElementClassLookup): + def lookup( + self, type: str, doc: str, namespace: str, name: str + ) -> Optional[Type[ElementBase]]: ... + +class _BaseParser: + def __getattr__(self, name: str) -> Any: ... # Incomplete + def copy(self) -> _BaseParser: ... + def makeelement( + self, + _tag: _TagName, + attrib: Optional[Union[_DictAnyStr, _Attrib]] = ..., + nsmap: Optional[_NSMapArg] = ..., + **_extra: Any, + ) -> _Element: ... + def setElementClassLookup( + self, lookup: Optional[ElementClassLookup] = ... + ) -> None: ... + def set_element_class_lookup( + self, lookup: Optional[ElementClassLookup] = ... + ) -> None: ... + +class _FeedParser(_BaseParser): + def __getattr__(self, name: str) -> Any: ... # Incomplete + def close(self) -> _Element: ... + def feed(self, data: _AnyStr) -> None: ... + +class XMLParser(_FeedParser): + def __init__( + self, + encoding: Optional[_AnyStr] = ..., + attribute_defaults: bool = ..., + dtd_validation: bool = ..., + load_dtd: bool = ..., + no_network: bool = ..., + ns_clean: bool = ..., + recover: bool = ..., + schema: Optional[XMLSchema] = ..., + huge_tree: bool = ..., + remove_blank_text: bool = ..., + resolve_entities: bool = ..., + remove_comments: bool = ..., + remove_pis: bool = ..., + strip_cdata: bool = ..., + collect_ids: bool = ..., + target: Optional[ParserTarget] = ..., + compact: bool = ..., + ) -> None: ... + resolvers = ... # type: _ResolverRegistry + +class HTMLParser(_FeedParser): + def __init__( + self, + encoding: Optional[_AnyStr] = ..., + collect_ids: bool = ..., + compact: bool = ..., + huge_tree: bool = ..., + no_network: bool = ..., + recover: bool = ..., + remove_blank_text: bool = ..., + remove_comments: bool = ..., + remove_pis: bool = ..., + schema: Optional[XMLSchema] = ..., + strip_cdata: bool = ..., + target: Optional[ParserTarget] = ..., + ) -> None: ... + +class _ResolverRegistry: + def add(self, resolver: Resolver) -> None: ... + def remove(self, resolver: Resolver) -> None: ... + +class Resolver: + def resolve(self, system_url: str, public_id: str): ... + def resolve_file( + self, f: IO[Any], context: Any, *, base_url: Optional[_AnyStr], close: bool + ): ... + def resolve_string( + self, string: _AnyStr, context: Any, *, base_url: Optional[_AnyStr] + ): ... + +class XMLSchema(_Validator): + def __init__( + self, + etree: _ElementOrTree = ..., + file: _FileSource = ..., + ) -> None: ... + def __call__(self, etree: _ElementOrTree) -> bool: ... + +class XSLTAccessControl: ... + +class XSLT: + def __init__( + self, + xslt_input: _ElementOrTree, + extensions: _Dict_Tuple2AnyStr_Any = ..., + regexp: bool = ..., + access_control: XSLTAccessControl = ..., + ) -> None: ... + def __call__( + self, + _input: _ElementOrTree, + profile_run: bool = ..., + **kwargs: Union[_AnyStr, _XSLTQuotedStringParam], + ) -> _XSLTResultTree: ... + @staticmethod + def strparam(s: _AnyStr) -> _XSLTQuotedStringParam: ... + +def Comment(text: Optional[_AnyStr] = ...) -> _Comment: ... +def Element( + _tag: _TagName, + attrib: Optional[_DictAnyStr] = ..., + nsmap: Optional[_NSMapArg] = ..., + **extra: _AnyStr, +) -> _Element: ... +def SubElement( + _parent: _Element, + _tag: _TagName, + attrib: Optional[_DictAnyStr] = ..., + nsmap: Optional[_NSMapArg] = ..., + **extra: _AnyStr, +) -> _Element: ... +def ElementTree( + element: _Element = ..., + file: _FileSource = ..., + parser: Optional[_AnyParser] = ..., +) -> _ElementTree: ... +def ProcessingInstruction( + target: _AnyStr, text: _AnyStr = ... +) -> _ProcessingInstruction: ... + +PI = ProcessingInstruction + +def HTML( + text: _AnyStr, + parser: Optional[HTMLParser] = ..., + base_url: Optional[_AnyStr] = ..., +) -> _Element: ... +def XML( + text: _AnyStr, + parser: Optional[XMLParser] = ..., + base_url: Optional[_AnyStr] = ..., +) -> _Element: ... +def cleanup_namespaces( + tree_or_element: _ElementOrTree, + top_nsmap: Optional[_NSMapArg] = ..., + keep_ns_prefixes: Optional[Iterable[_AnyStr]] = ..., +) -> None: ... +def parse( + source: _FileSource, + parser: Optional[_AnyParser] = ..., + base_url: _AnyStr = ..., +) -> Union[_ElementTree, Any]: ... +@overload +def fromstring( + text: _AnyStr, + parser: None = ..., + *, + base_url: _AnyStr = ..., +) -> _Element: ... +@overload +def fromstring( + text: _AnyStr, + parser: _AnyParser = ..., + *, + base_url: _AnyStr = ..., +) -> Union[_Element, Any]: ... +@overload +def tostring( + element_or_tree: _ElementOrTree, + encoding: Union[Type[str], Literal["unicode"]], + method: str = ..., + xml_declaration: bool = ..., + pretty_print: bool = ..., + with_tail: bool = ..., + standalone: bool = ..., + doctype: str = ..., + exclusive: bool = ..., + with_comments: bool = ..., + inclusive_ns_prefixes: Any = ..., +) -> str: ... +@overload +def tostring( + element_or_tree: _ElementOrTree, + # Should be anything but "unicode", cannot be typed + encoding: Optional[_KnownEncodings] = None, + method: str = ..., + xml_declaration: bool = ..., + pretty_print: bool = ..., + with_tail: bool = ..., + standalone: bool = ..., + doctype: str = ..., + exclusive: bool = ..., + with_comments: bool = ..., + inclusive_ns_prefixes: Any = ..., +) -> bytes: ... +@overload +def tostring( + element_or_tree: _ElementOrTree, + encoding: Union[str, type] = ..., + method: str = ..., + xml_declaration: bool = ..., + pretty_print: bool = ..., + with_tail: bool = ..., + standalone: bool = ..., + doctype: str = ..., + exclusive: bool = ..., + with_comments: bool = ..., + inclusive_ns_prefixes: Any = ..., +) -> _AnyStr: ... + +class _ErrorLog: ... +class Error(Exception): ... + +class LxmlError(Error): + def __init__(self, message: Any, error_log: _ErrorLog = ...) -> None: ... + error_log = ... # type: _ErrorLog + +class DocumentInvalid(LxmlError): ... +class LxmlSyntaxError(LxmlError, SyntaxError): ... + +class ParseError(LxmlSyntaxError): + position: Tuple[int, int] + +class XMLSyntaxError(ParseError): ... + +class _Validator: + def assert_(self, etree: _ElementOrTree) -> None: ... + def assertValid(self, etree: _ElementOrTree) -> None: ... + def validate(self, etree: _ElementOrTree) -> bool: ... + error_log = ... # type: _ErrorLog + +class DTD(_Validator): + def __init__(self, file: _FileSource = ..., *, external_id: Any = ...) -> None: ... + def __call__(self, etree: _ElementOrTree) -> bool: ... + +class _XPathEvaluatorBase: ... + +class XPath(_XPathEvaluatorBase): + def __init__( + self, + path: _AnyStr, + *, + namespaces: Optional[_NonDefaultNSMapArg] = ..., + extensions: Any = ..., + regexp: bool = ..., + smart_strings: bool = ..., + ) -> None: ... + def __call__( + self, _etree_or_element: _ElementOrTree, **_variables: _XPathObject + ) -> _XPathObject: ... + path = ... # type: str + +class ETXPath(XPath): + def __init__( + self, + path: _AnyStr, + *, + extensions: Any = ..., + regexp: bool = ..., + smart_strings: bool = ..., + ) -> None: ... + +class XPathElementEvaluator(_XPathEvaluatorBase): + def __init__( + self, + element: _Element, + *, + namespaces: Optional[_NonDefaultNSMapArg] = ..., + extensions: Any = ..., + regexp: bool = ..., + smart_strings: bool = ..., + ) -> None: ... + def __call__(self, _path: _AnyStr, **_variables: _XPathObject) -> _XPathObject: ... + def register_namespace(self, prefix: _AnyStr, uri: _AnyStr) -> None: ... + def register_namespaces( + self, namespaces: Optional[_NonDefaultNSMapArg] + ) -> None: ... + +class XPathDocumentEvaluator(XPathElementEvaluator): + def __init__( + self, + etree: _ElementTree, + *, + namespaces: Optional[_NonDefaultNSMapArg] = ..., + extensions: Any = ..., + regexp: bool = ..., + smart_strings: bool = ..., + ) -> None: ... + +@overload +def XPathEvaluator( + etree_or_element: _Element, + namespaces: Optional[_NonDefaultNSMapArg] = ..., + extensions: Any = ..., + regexp: bool = ..., + smart_strings: bool = ..., +) -> XPathElementEvaluator: ... +@overload +def XPathEvaluator( + etree_or_element: _ElementTree, + namespaces: Optional[_NonDefaultNSMapArg] = ..., + extensions: Any = ..., + regexp: bool = ..., + smart_strings: bool = ..., +) -> XPathDocumentEvaluator: ... +@overload +def XPathEvaluator( + etree_or_element: _ElementOrTree, + namespaces: Optional[_NonDefaultNSMapArg] = ..., + extensions: Any = ..., + regexp: bool = ..., + smart_strings: bool = ..., +) -> Union[XPathElementEvaluator, XPathDocumentEvaluator]: ... + +_ElementFactory = Callable[[Any, Dict[_AnyStr, _AnyStr]], _Element] +_CommentFactory = Callable[[_AnyStr], _Comment] +_ProcessingInstructionFactory = Callable[[_AnyStr, _AnyStr], _ProcessingInstruction] + +class TreeBuilder: + def __init__( + self, + element_factory: Optional[_ElementFactory] = ..., + parser: Optional[_BaseParser] = ..., + comment_factory: Optional[_CommentFactory] = ..., + pi_factory: Optional[_ProcessingInstructionFactory] = ..., + insert_comments: bool = ..., + insert_pis: bool = ..., + ) -> None: ... + def close(self) -> _Element: ... + def comment(self, text: _AnyStr) -> None: ... + def data(self, data: _AnyStr) -> None: ... + def end(self, tag: _AnyStr) -> None: ... + def pi(self, target: _AnyStr, data: Optional[_AnyStr] = ...) -> Any: ... + def start(self, tag: _AnyStr, attrib: Dict[_AnyStr, _AnyStr]) -> None: ... + +def iselement(element: Any) -> TypeGuard[_Element]: ... + +_ParseEventType: TypeAlias = Literal[ + "start", "end", "start-ns", "end-ns", "comment", "pi" +] +_ParseEvent: TypeAlias = Union[ + tuple[Literal["start"], _Element], + tuple[Literal["end"], _Element], + tuple[Literal["start-ns"], Tuple[_AnyStr, _AnyStr]], + tuple[Literal["end-ns"], None], + tuple[Literal["comment"], _Comment], + tuple[Literal["pi"], _ProcessingInstruction], +] + +class XMLPullParser(XMLParser): + def __init__( + self, + events: Optional[Iterable[_ParseEventType]] = ..., + *, + tag: Optional[_TagSelector] = ..., + base_url: Optional[_AnyStr] = ..., + encoding: Optional[_AnyStr] = ..., + attribute_defaults: bool = ..., + dtd_validation: bool = ..., + load_dtd: bool = ..., + no_network: bool = ..., + ns_clean: bool = ..., + recover: bool = ..., + schema: Optional[XMLSchema] = ..., + huge_tree: bool = ..., + remove_blank_text: bool = ..., + resolve_entities: bool = ..., + remove_comments: bool = ..., + remove_pis: bool = ..., + strip_cdata: bool = ..., + collect_ids: bool = ..., + target: Optional[ParserTarget] = ..., + compact: bool = ..., + ) -> None: ... + def read_events(self) -> Iterator[_ParseEvent]: ... + +class HTMLPullParser(HTMLParser): + def __init__( + self, + events: Optional[Iterable[_ParseEventType]] = ..., + *, + tag: Optional[_TagSelector] = ..., + base_url: Optional[_AnyStr] = ..., + encoding: Optional[_AnyStr] = ..., + collect_ids: bool = ..., + compact: bool = ..., + huge_tree: bool = ..., + no_network: bool = ..., + recover: bool = ..., + remove_blank_text: bool = ..., + remove_comments: bool = ..., + remove_pis: bool = ..., + schema: Optional[XMLSchema] = ..., + strip_cdata: bool = ..., + target: Optional[ParserTarget] = ..., + ) -> None: ... + def read_events(self) -> Iterator[_ParseEvent]: ... diff --git a/pyproject.toml b/pyproject.toml index ec98c6f49ef2..b79e1d990d9f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -166,6 +166,7 @@ watchdog = "*" [tool.poetry.group.typecheck.dependencies] mypy = "*" +lxml-stubs = "*" pydantic = "<2" # for pydantic.mypy plugin types-bleach = "*" types-boto = "*" diff --git a/tox.ini b/tox.ini index 2bde302555f9..21ef8d0722b3 100644 --- a/tox.ini +++ b/tox.ini @@ -46,6 +46,7 @@ setenv = mulled,unit: GALAXY_VIRTUAL_ENV={envdir} unit: GALAXY_ENABLE_BETA_COMPRESSED_GENBANK_SNIFFING=1 unit: marker=not external_dependency_management + mypy: MYPYPATH=mypy-stubs deps = coverage: coverage lint,lint_docstring,lint_docstring_include_list: -rlib/galaxy/dependencies/pinned-lint-requirements.txt