././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729876127.0 et_xmlfile-2.0.0/.hg_archival.txt0000644000000000000000000000016514706750237013672 0ustar00repo: 7e57ef3f132dceddfcdac8291aa43eb11a5abf29 node: 683e1aecb8273371f409857b20e971110d97c3ec branch: 2.0 tag: 2.0.0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729876127.0 et_xmlfile-2.0.0/.coveragerc0000644000000000000000000000010314706750237012715 0ustar00[report] exclude_lines = pragma: no cover omit = */tests/*././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729876127.0 et_xmlfile-2.0.0/.gitlab-ci.yml0000644000000000000000000000415614706750237013244 0ustar00stages: - test - deploy .unit-test: &unit stage: test image: python:3 variables: PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" before_script: - python -V - pip install tox - pip install -e . script: - tox --skip-missing-interpreters -- -qqrf test-python38: <<: *unit image: python:3.8 variables: TOXENV: py38 test-python39: <<: *unit image: python:3.9 variables: TOXENV: py39 test-python310: <<: *unit image: python:3.10 variables: TOXENV: py310 test-python311: <<: *unit image: python:3.11 variables: TOXENV: py311 test-python312: <<: *unit image: python:3.12 variables: TOXENV: py312 test-python313: <<: *unit image: python:3.13-rc variables: TOXENV: py313 test-pypy3: <<: *unit image: pypy:3 variables: TOXENV: pypy3 test-docs: <<: *unit script: - apt-get update && apt-get install -y --no-install-recommends zip - tox -e doc - zip -r docs.zip .tox/doc/tmp/html artifacts: paths: - docs.zip test-coverage: <<: *unit script: - export GIT_ID=$(hg tip --template '{node}\n') - export GIT_AUTHOR_NAME=$(hg tip --template '{author|person}\n') - export GIT_AUTHOR_EMAIL=$(hg tip --template '{author|email}\n') - export GIT_COMMITTER_NAME=$(hg tip --template '{author|person}\n') - export GIT_COMMITTER_EMAIL=$(hg tip --template '{author|email}\n') - export GIT_MESSAGE=$(hg tip --template '{desc}\n') - export GIT_BRANCH=$(hg branch) - export GIT_URL=https://foss.heptapod.net/openpyxl/et_xmlfile - tox -e cov coverage: '/^TOTAL.+?(\d+\%)$/' artifacts: reports: coverage_report: coverage_format: cobertura path: coverage.xml pages: stage: deploy image: python:3 script: - unzip docs.zip - mv .tox/doc/tmp/html public artifacts: paths: - public rules: # This ensures that only pushes to the default branch will trigger # a pages deploy - if: $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729876127.0 et_xmlfile-2.0.0/.hgeol0000644000000000000000000000024014706750237011675 0ustar00[repository] [patterns] **.ini = native **.yml = native **.py = native **.xml = native **.rst = native **.txt = native **.xlsx = BIN **.png = BIN **.zip = BIN ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729876127.0 et_xmlfile-2.0.0/.hgignore0000644000000000000000000000021314706750237012401 0ustar00syntax: glob et_xmlfile.egg-info doc/api bin/ lib/ .tox/ *.pyc .gitignore dist/ pyvenv.cfg .pytest_cache/ coverage.xml .coverage .DS_Store ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729876127.0 et_xmlfile-2.0.0/.hgtags0000644000000000000000000000013414706750237012056 0ustar00bd0a3056eb2bf989662cb118c5c1c8731c634167 1.0.1 de1a42079e168f8d3b2ff987785b353e0a892ee3 1.1 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729876127.0 et_xmlfile-2.0.0/AUTHORS.txt0000644000000000000000000000012214706750237012463 0ustar00The authors in alphabetical order * Charlie Clark * Daniel Hillier * Elias Rabel ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729876127.0 et_xmlfile-2.0.0/LICENCE.python0000644000000000000000000003454014706750237013115 0ustar00et_xml is licensed under the MIT license; see the file LICENCE for details. et_xml includes code from the Python standard library, which is licensed under the Python license, a permissive open source license. The copyright and license is included below for compliance with Python's terms. This module includes corrections and new features as follows: - Correct handling of attributes namespaces when a default namespace has been registered. - Records the namespaces for an Element during parsing and utilises them to allow inspection of namespaces at specific elements in the xml tree and during serialisation. Misc: - Includes the test_xml_etree with small modifications for testing the modifications in this package. ---------------------------------------------------------------------- Copyright (c) 2001-present Python Software Foundation; All Rights Reserved A. HISTORY OF THE SOFTWARE ========================== Python was created in the early 1990s by Guido van Rossum at Stichting Mathematisch Centrum (CWI, see https://www.cwi.nl) in the Netherlands as a successor of a language called ABC. Guido remains Python's principal author, although it includes many contributions from others. In 1995, Guido continued his work on Python at the Corporation for National Research Initiatives (CNRI, see https://www.cnri.reston.va.us) in Reston, Virginia where he released several versions of the software. In May 2000, Guido and the Python core development team moved to BeOpen.com to form the BeOpen PythonLabs team. In October of the same year, the PythonLabs team moved to Digital Creations, which became Zope Corporation. In 2001, the Python Software Foundation (PSF, see https://www.python.org/psf/) was formed, a non-profit organization created specifically to own Python-related Intellectual Property. Zope Corporation was a sponsoring member of the PSF. All Python releases are Open Source (see https://opensource.org for the Open Source Definition). Historically, most, but not all, Python releases have also been GPL-compatible; the table below summarizes the various releases. Release Derived Year Owner GPL- from compatible? (1) 0.9.0 thru 1.2 1991-1995 CWI yes 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes 1.6 1.5.2 2000 CNRI no 2.0 1.6 2000 BeOpen.com no 1.6.1 1.6 2001 CNRI yes (2) 2.1 2.0+1.6.1 2001 PSF no 2.0.1 2.0+1.6.1 2001 PSF yes 2.1.1 2.1+2.0.1 2001 PSF yes 2.1.2 2.1.1 2002 PSF yes 2.1.3 2.1.2 2002 PSF yes 2.2 and above 2.1.1 2001-now PSF yes Footnotes: (1) GPL-compatible doesn't mean that we're distributing Python under the GPL. All Python licenses, unlike the GPL, let you distribute a modified version without making your changes open source. The GPL-compatible licenses make it possible to combine Python with other software that is released under the GPL; the others don't. (2) According to Richard Stallman, 1.6.1 is not GPL-compatible, because its license has a choice of law clause. According to CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 is "not incompatible" with the GPL. Thanks to the many outside volunteers who have worked under Guido's direction to make these releases possible. B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON =============================================================== Python software and documentation are licensed under the Python Software Foundation License Version 2. Starting with Python 3.8.6, examples, recipes, and other code in the documentation are dual licensed under the PSF License Version 2 and the Zero-Clause BSD license. Some software incorporated into Python is under different licenses. The licenses are listed with code falling under that license. PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 -------------------------------------------- 1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and the Individual or Organization ("Licensee") accessing and otherwise using this software ("Python") in source or binary form and its associated documentation. 2. Subject to the terms and conditions of this License Agreement, PSF hereby grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use Python alone or in any derivative version, provided, however, that PSF's License Agreement and PSF's notice of copyright, i.e., "Copyright (c) 2001-2024 Python Software Foundation; All Rights Reserved" are retained in Python alone or in any derivative version prepared by Licensee. 3. In the event Licensee prepares a derivative work that is based on or incorporates Python or any part thereof, and wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in any such work a brief summary of the changes made to Python. 4. PSF is making Python available to Licensee on an "AS IS" basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 6. This License Agreement will automatically terminate upon a material breach of its terms and conditions. 7. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between PSF and Licensee. This License Agreement does not grant permission to use PSF trademarks or trade name in a trademark sense to endorse or promote products or services of Licensee, or any third party. 8. By copying, installing or otherwise using Python, Licensee agrees to be bound by the terms and conditions of this License Agreement. BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 ------------------------------------------- BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the Individual or Organization ("Licensee") accessing and otherwise using this software in source or binary form and its associated documentation ("the Software"). 2. Subject to the terms and conditions of this BeOpen Python License Agreement, BeOpen hereby grants Licensee a non-exclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use the Software alone or in any derivative version, provided, however, that the BeOpen Python License is retained in the Software, alone or in any derivative version prepared by Licensee. 3. BeOpen is making the Software available to Licensee on an "AS IS" basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. 4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 5. This License Agreement will automatically terminate upon a material breach of its terms and conditions. 6. This License Agreement shall be governed by and interpreted in all respects by the law of the State of California, excluding conflict of law provisions. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between BeOpen and Licensee. This License Agreement does not grant permission to use BeOpen trademarks or trade names in a trademark sense to endorse or promote products or services of Licensee, or any third party. As an exception, the "BeOpen Python" logos available at http://www.pythonlabs.com/logos.html may be used according to the permissions granted on that web page. 7. By copying, installing or otherwise using the software, Licensee agrees to be bound by the terms and conditions of this License Agreement. CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 --------------------------------------- 1. This LICENSE AGREEMENT is between the Corporation for National Research Initiatives, having an office at 1895 Preston White Drive, Reston, VA 20191 ("CNRI"), and the Individual or Organization ("Licensee") accessing and otherwise using Python 1.6.1 software in source or binary form and its associated documentation. 2. Subject to the terms and conditions of this License Agreement, CNRI hereby grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use Python 1.6.1 alone or in any derivative version, provided, however, that CNRI's License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) 1995-2001 Corporation for National Research Initiatives; All Rights Reserved" are retained in Python 1.6.1 alone or in any derivative version prepared by Licensee. Alternately, in lieu of CNRI's License Agreement, Licensee may substitute the following text (omitting the quotes): "Python 1.6.1 is made available subject to the terms and conditions in CNRI's License Agreement. This Agreement together with Python 1.6.1 may be located on the internet using the following unique, persistent identifier (known as a handle): 1895.22/1013. This Agreement may also be obtained from a proxy server on the internet using the following URL: http://hdl.handle.net/1895.22/1013". 3. In the event Licensee prepares a derivative work that is based on or incorporates Python 1.6.1 or any part thereof, and wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in any such work a brief summary of the changes made to Python 1.6.1. 4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. 5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 6. This License Agreement will automatically terminate upon a material breach of its terms and conditions. 7. This License Agreement shall be governed by the federal intellectual property law of the United States, including without limitation the federal copyright law, and, to the extent such U.S. federal law does not apply, by the law of the Commonwealth of Virginia, excluding Virginia's conflict of law provisions. Notwithstanding the foregoing, with regard to derivative works based on Python 1.6.1 that incorporate non-separable material that was previously distributed under the GNU General Public License (GPL), the law of the Commonwealth of Virginia shall govern this License Agreement only as to issues arising under or with respect to Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between CNRI and Licensee. This License Agreement does not grant permission to use CNRI trademarks or trade name in a trademark sense to endorse or promote products or services of Licensee, or any third party. 8. By clicking on the "ACCEPT" button where indicated, or by copying, installing or otherwise using Python 1.6.1, Licensee agrees to be bound by the terms and conditions of this License Agreement. ACCEPT CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 -------------------------------------------------- Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, The Netherlands. All rights reserved. Permission to use, copy, modify, and distribute this software and its documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of Stichting Mathematisch Centrum or CWI not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ZERO-CLAUSE BSD LICENSE FOR CODE IN THE PYTHON DOCUMENTATION ---------------------------------------------------------------------- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729876127.0 et_xmlfile-2.0.0/LICENCE.rst0000644000000000000000000000215314706750237012377 0ustar00This software is under the MIT Licence ====================================== Copyright (c) 2010 openpyxl Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729876127.0 et_xmlfile-2.0.0/MANIFEST.in0000644000000000000000000000003014706750237012331 0ustar00prune et_xmlfile/tests/ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729876127.0 et_xmlfile-2.0.0/README.rst0000644000000000000000000000277114706750237012300 0ustar00.. image:: https://foss.heptapod.net/openpyxl/et_xmlfile/badges/branch/default/coverage.svg :target: https://coveralls.io/bitbucket/openpyxl/et_xmlfile?branch=default :alt: coverage status et_xmfile ========= XML can use lots of memory, and et_xmlfile is a low memory library for creating large XML files And, although the standard library already includes an incremental parser, `iterparse` it has no equivalent when writing XML. Once an element has been added to the tree, it is written to the file or stream and the memory is then cleared. This module is based upon the `xmlfile module from lxml `_ with the aim of allowing code to be developed that will work with both libraries. It was developed initially for the openpyxl project, but is now a standalone module. The code was written by Elias Rabel as part of the `Python Düsseldorf `_ openpyxl sprint in September 2014. Proper support for incremental writing was provided by Daniel Hillier in 2024 Note on performance ------------------- The code was not developed with performance in mind, but turned out to be faster than the existing SAX-based implementation but is generally slower than lxml's xmlfile. There is one area where an optimisation for lxml may negatively affect the performance of et_xmfile and that is when using the `.element()` method on the xmlfile context manager. It is, therefore, recommended simply to create Elements write these directly, as in the sample code. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729876127.0 et_xmlfile-2.0.0/doc/changes.rst0000644000000000000000000000072714706750237013517 0ustar001.1.0 (2024-10-25) ================== Changes ------- * Implement proper incremental writing 1.1.0 (2021-04-26) ================== Changes ------- * Moved from BitBucket to Heptapod * Updated supported Python versions 1.0.1 (2015-10-27) ================== Changes ------- * Explicitly use UTF-8 in the ReadMe 1.0.0 (2015-03-30) ================== Initial Release --------------- * Provide basic support * Provide context manager for compatibility with lxml ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729876127.0 et_xmlfile-2.0.0/doc/conf.py0000644000000000000000000002003714706750237012650 0ustar00# -*- coding: utf-8 -*- # # openpyxl documentation build configuration file, created by # sphinx-quickstart on Fri Sep 10 09:50:03 2010. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) up = os.path.dirname sys.path.insert(0, os.path.abspath(os.path.join(up(os.getcwd()), '.'))) print(sys.path) import et_xmlfile import sphinx_rtd_theme # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode', 'sphinx.ext.doctest', 'sphinx.ext.coverage'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'et_xmlfile' copyright = u'2010 - 2015, %s' % et_xmlfile.__author__ # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = et_xmlfile.__version__ # The full version, including alpha/beta/rc tags. release = version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. import os html_theme = "sphinx_rtd_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = 'logo.png' # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'et_xmlfile' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'et_xmlfile.tex', u'et_xmlfile Documentation', u'Charlie Clark', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'et_xmlfile', u'et_xmfile Documentation', [u'Charlie Clark'], 1) ] # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'http://docs.python.org/': None} doctest_global_setup = """ import os, shutil if not os.path.exists("tmp"): os.mkdir("tmp") os.chdir("tmp") """ doctest_global_cleanup = """ import shutil import os os.chdir("..") shutil.rmtree("tmp") """ import shutil def run_apidoc(_): try: from sphinx.ext.apidoc import main except ImportError: from sphinx.apidoc import main cur_dir = os.path.abspath(os.path.dirname(__file__)) output_path = os.path.join(cur_dir, 'api') shutil.rmtree(output_path, ignore_errors=True) modules = os.path.dirname(et_xmlfile.__file__) exclusions = [ '../et_xmlfile/tests', ] main(['-f', '-T', '-e', '-M', '-o', output_path, modules] + exclusions) def setup(app): app.connect('builder-inited', run_apidoc)././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729876127.0 et_xmlfile-2.0.0/doc/example.py0000644000000000000000000000041414706750237013353 0ustar00from io import BytesIO from xml.etree.ElementTree import Element from et_xmlfile import xmlfile out = BytesIO() with xmlfile(out) as xf: el = Element("root") xf.write(el) # write the XML straight to the file-like object assert out.getvalue() == b"" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729876127.0 et_xmlfile-2.0.0/doc/index.rst0000644000000000000000000000116714706750237013215 0ustar00et_xmlfile - An incremental writer for Python's xml.etree ========================================================= :Author: Elias Rabel, Charlie Clark, Daniel Hillier and contributors :Source code: https://foss.heptapod.net/openpyxl/openpyxl :Issues: https://foss.heptapod.net/openpyxl/openpyxl/-/issues :Generated: |today| :License: MIT/Expat :Version: |release| .. include:: ../README.rst Sample code: ------------ .. literalinclude:: example.py .. toctree:: :maxdepth: 1 :caption: Release Notes :hidden: changes API Documentation ----------------- .. toctree:: :maxdepth: 1 api/et_xmlfile ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729876127.0 et_xmlfile-2.0.0/et_xmlfile/__init__.py0000644000000000000000000000034414706750237015044 0ustar00from .xmlfile import xmlfile # constants __version__ = '2.0.0' __author__ = 'See AUTHORS.txt' __license__ = 'MIT' __author_email__ = 'charlie.clark@clark-consulting.eu' __url__ = 'https://foss.heptapod.net/openpyxl/et_xmlfile' ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729876127.0 et_xmlfile-2.0.0/et_xmlfile/incremental_tree.py0000644000000000000000000010334614706750237016633 0ustar00# Code modified from cPython's Lib/xml/etree/ElementTree.py # The write() code is modified to allow specifying a particular namespace # uri -> prefix mapping. # # --------------------------------------------------------------------- # Licensed to PSF under a Contributor Agreement. # See https://www.python.org/psf/license for licensing details. # # ElementTree # Copyright (c) 1999-2008 by Fredrik Lundh. All rights reserved. # # fredrik@pythonware.com # http://www.pythonware.com # -------------------------------------------------------------------- # The ElementTree toolkit is # # Copyright (c) 1999-2008 by Fredrik Lundh # # By obtaining, using, and/or copying this software and/or its # associated documentation, you agree that you have read, understood, # and will comply with the following terms and conditions: # # Permission to use, copy, modify, and distribute this software and # its associated documentation for any purpose and without fee is # hereby granted, provided that the above copyright notice appears in # all copies, and that both that copyright notice and this permission # notice appear in supporting documentation, and that the name of # Secret Labs AB or the author not be used in advertising or publicity # pertaining to distribution of the software without specific, written # prior permission. # # SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD # TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- # ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR # BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY # DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE # OF THIS SOFTWARE. # -------------------------------------------------------------------- import contextlib import io import xml.etree.ElementTree as ET def current_global_nsmap(): return { prefix: uri for uri, prefix in ET._namespace_map.items() } class IncrementalTree(ET.ElementTree): def write( self, file_or_filename, encoding=None, xml_declaration=None, default_namespace=None, method=None, *, short_empty_elements=True, nsmap=None, root_ns_only=False, minimal_ns_only=False, ): """Write element tree to a file as XML. Arguments: *file_or_filename* -- file name or a file object opened for writing *encoding* -- the output encoding (default: US-ASCII) *xml_declaration* -- bool indicating if an XML declaration should be added to the output. If None, an XML declaration is added if encoding IS NOT either of: US-ASCII, UTF-8, or Unicode *default_namespace* -- sets the default XML namespace (for "xmlns"). Takes precedence over any default namespace provided in nsmap or xml.etree.ElementTree.register_namespace(). *method* -- either "xml" (default), "html, "text", or "c14n" *short_empty_elements* -- controls the formatting of elements that contain no content. If True (default) they are emitted as a single self-closed tag, otherwise they are emitted as a pair of start/end tags *nsmap* -- a mapping of namespace prefixes to URIs. These take precedence over any mappings registered using xml.etree.ElementTree.register_namespace(). The default_namespace argument, if supplied, takes precedence over any default namespace supplied in nsmap. All supplied namespaces will be declared on the root element, even if unused in the document. *root_ns_only* -- bool indicating namespace declrations should only be written on the root element. This requires two passes of the xml tree adding additional time to the writing process. This is primarily meant to mimic xml.etree.ElementTree's behaviour. *minimal_ns_only* -- bool indicating only namespaces that were used to qualify elements or attributes should be declared. All namespace declarations will be written on the root element regardless of the value of the root_ns_only arg. Requires two passes of the xml tree adding additional time to the writing process. """ if not method: method = "xml" elif method not in ("text", "xml", "html"): raise ValueError("unknown method %r" % method) if not encoding: encoding = "us-ascii" with _get_writer(file_or_filename, encoding) as (write, declared_encoding): if method == "xml" and ( xml_declaration or ( xml_declaration is None and encoding.lower() != "unicode" and declared_encoding.lower() not in ("utf-8", "us-ascii") ) ): write("\n" % (declared_encoding,)) if method == "text": ET._serialize_text(write, self._root) else: if method == "xml": is_html = False else: is_html = True if nsmap: if None in nsmap: raise ValueError( 'Found None as default nsmap prefix in nsmap. ' 'Use "" as the default namespace prefix.' ) new_nsmap = nsmap.copy() else: new_nsmap = {} if default_namespace: new_nsmap[""] = default_namespace if root_ns_only or minimal_ns_only: # _namespaces returns a mapping of only the namespaces that # were used. new_nsmap = _namespaces( self._root, default_namespace, new_nsmap, ) if not minimal_ns_only: if nsmap: # We want all namespaces defined in the provided # nsmap to be declared regardless of whether # they've been used. new_nsmap.update(nsmap) if default_namespace: new_nsmap[""] = default_namespace global_nsmap = { prefix: uri for uri, prefix in ET._namespace_map.items() } if None in global_nsmap: raise ValueError( 'Found None as default nsmap prefix in nsmap registered with ' 'register_namespace. Use "" for the default namespace prefix.' ) nsmap_scope = {} _serialize_ns_xml( write, self._root, nsmap_scope, global_nsmap, is_html=is_html, is_root=True, short_empty_elements=short_empty_elements, new_nsmap=new_nsmap, ) def _make_new_ns_prefix( nsmap_scope, global_prefixes, local_nsmap=None, default_namespace=None, ): i = len(nsmap_scope) if default_namespace is not None and "" not in nsmap_scope: # Keep the same numbering scheme as python which assumes the default # namespace is present if supplied. i += 1 while True: prefix = f"ns{i}" if ( prefix not in nsmap_scope and prefix not in global_prefixes and ( not local_nsmap or prefix not in local_nsmap ) ): return prefix i += 1 def _get_or_create_prefix( uri, nsmap_scope, global_nsmap, new_namespace_prefixes, uri_to_prefix, for_default_namespace_attr_prefix=False, ): """Find a prefix that doesn't conflict with the ns scope or create a new prefix This function mutates nsmap_scope, global_nsmap, new_namespace_prefixes and uri_to_prefix. It is intended to keep state in _serialize_ns_xml consistent while deduplicating the house keeping code or updating these dictionaries. """ # Check if we can reuse an existing (global) prefix within the current # namespace scope. There maybe many prefixes pointing to a single URI by # this point and we need to select a prefix that is not in use in the # current scope. for global_prefix, global_uri in global_nsmap.items(): if uri == global_uri and global_prefix not in nsmap_scope: prefix = global_prefix break else: # no break # We couldn't find a suitable existing prefix for this namespace scope, # let's create a new one. prefix = _make_new_ns_prefix(nsmap_scope, global_prefixes=global_nsmap) global_nsmap[prefix] = uri nsmap_scope[prefix] = uri if not for_default_namespace_attr_prefix: # Don't override the actual default namespace prefix uri_to_prefix[uri] = prefix if prefix != "xml": new_namespace_prefixes.add(prefix) return prefix def _find_default_namespace_attr_prefix( default_namespace, nsmap, local_nsmap, global_prefixes, provided_default_namespace=None, ): # Search the provided nsmap for any prefixes for this uri that aren't the # default namespace "" for prefix, uri in nsmap.items(): if uri == default_namespace and prefix != "": return prefix for prefix, uri in local_nsmap.items(): if uri == default_namespace and prefix != "": return prefix # _namespace_map is a 1:1 mapping of uri -> prefix prefix = ET._namespace_map.get(default_namespace) if prefix and prefix not in nsmap: return prefix return _make_new_ns_prefix( nsmap, global_prefixes, local_nsmap, provided_default_namespace, ) def process_attribs( elem, is_nsmap_scope_changed, default_ns_attr_prefix, nsmap_scope, global_nsmap, new_namespace_prefixes, uri_to_prefix, ): item_parts = [] for k, v in elem.items(): if isinstance(k, ET.QName): k = k.text try: if k[:1] == "{": uri_and_name = k[1:].rsplit("}", 1) try: prefix = uri_to_prefix[uri_and_name[0]] except KeyError: if not is_nsmap_scope_changed: # We're about to mutate the these dicts so # let's copy them first. We don't have to # recompute other mappings as we're looking up # or creating a new prefix nsmap_scope = nsmap_scope.copy() uri_to_prefix = uri_to_prefix.copy() is_nsmap_scope_changed = True prefix = _get_or_create_prefix( uri_and_name[0], nsmap_scope, global_nsmap, new_namespace_prefixes, uri_to_prefix, ) if not prefix: if default_ns_attr_prefix: prefix = default_ns_attr_prefix else: for prefix, known_uri in nsmap_scope.items(): if known_uri == uri_and_name[0] and prefix != "": default_ns_attr_prefix = prefix break else: # no break if not is_nsmap_scope_changed: # We're about to mutate the these dicts so # let's copy them first. We don't have to # recompute other mappings as we're looking up # or creating a new prefix nsmap_scope = nsmap_scope.copy() uri_to_prefix = uri_to_prefix.copy() is_nsmap_scope_changed = True prefix = _get_or_create_prefix( uri_and_name[0], nsmap_scope, global_nsmap, new_namespace_prefixes, uri_to_prefix, for_default_namespace_attr_prefix=True, ) default_ns_attr_prefix = prefix k = f"{prefix}:{uri_and_name[1]}" except TypeError: ET._raise_serialization_error(k) if isinstance(v, ET.QName): if v.text[:1] != "{": v = v.text else: uri_and_name = v.text[1:].rsplit("}", 1) try: prefix = uri_to_prefix[uri_and_name[0]] except KeyError: if not is_nsmap_scope_changed: # We're about to mutate the these dicts so # let's copy them first. We don't have to # recompute other mappings as we're looking up # or creating a new prefix nsmap_scope = nsmap_scope.copy() uri_to_prefix = uri_to_prefix.copy() is_nsmap_scope_changed = True prefix = _get_or_create_prefix( uri_and_name[0], nsmap_scope, global_nsmap, new_namespace_prefixes, uri_to_prefix, ) v = f"{prefix}:{uri_and_name[1]}" item_parts.append((k, v)) return item_parts, default_ns_attr_prefix, nsmap_scope def write_elem_start( write, elem, nsmap_scope, global_nsmap, short_empty_elements, is_html, is_root=False, uri_to_prefix=None, default_ns_attr_prefix=None, new_nsmap=None, **kwargs, ): """Write the opening tag (including self closing) and element text. Refer to _serialize_ns_xml for description of arguments. nsmap_scope should be an empty dictionary on first call. All nsmap prefixes must be strings with the default namespace prefix represented by "". eg. - (returns tag = 'foo') - text (returns tag = 'foo') - (returns tag = None) Returns: tag: The tag name to be closed or None if no closing required. nsmap_scope: The current nsmap after any prefix to uri additions from this element. This is the input dict if unmodified or an updated copy. default_ns_attr_prefix: The prefix for the default namespace to use with attrs. uri_to_prefix: The current uri to prefix map after any uri to prefix additions from this element. This is the input dict if unmodified or an updated copy. next_remains_root: A bool indicating if the child element(s) should be treated as their own roots. """ tag = elem.tag text = elem.text if tag is ET.Comment: write("" % text) tag = None next_remains_root = False elif tag is ET.ProcessingInstruction: write("" % text) tag = None next_remains_root = False else: if new_nsmap: is_nsmap_scope_changed = True nsmap_scope = nsmap_scope.copy() nsmap_scope.update(new_nsmap) new_namespace_prefixes = set(new_nsmap.keys()) new_namespace_prefixes.discard("xml") # We need to recompute the uri to prefixes uri_to_prefix = None default_ns_attr_prefix = None else: is_nsmap_scope_changed = False new_namespace_prefixes = set() if uri_to_prefix is None: if None in nsmap_scope: raise ValueError( 'Found None as a namespace prefix. Use "" as the default namespace prefix.' ) uri_to_prefix = {uri: prefix for prefix, uri in nsmap_scope.items()} if "" in nsmap_scope: # There may be multiple prefixes for the default namespace but # we want to make sure we preferentially use "" (for elements) uri_to_prefix[nsmap_scope[""]] = "" if tag is None: # tag supression where tag is set to None # Don't change is_root so namespaces can be passed down next_remains_root = is_root if text: write(ET._escape_cdata(text)) else: next_remains_root = False if isinstance(tag, ET.QName): tag = tag.text try: # These splits / fully qualified tag creationg are the # bottleneck in this implementation vs the python # implementation. # The following split takes ~42ns with no uri and ~85ns if a # prefix is present. If the uri was present, we then need to # look up a prefix (~14ns) and create the fully qualified # string (~41ns). This gives a total of ~140ns where a uri is # present. # Python's implementation needs to preprocess the tree to # create a dict of qname -> tag by traversing the tree which # takes a bit of extra time but it quickly makes that back by # only having to do a dictionary look up (~14ns) for each tag / # attrname vs our splitting (~140ns). # So here we have the flexibility of being able to redefine the # uri a prefix points to midway through serialisation at the # expense of performance (~10% slower for a 1mb file on my # machine). if tag[:1] == "{": uri_and_name = tag[1:].rsplit("}", 1) try: prefix = uri_to_prefix[uri_and_name[0]] except KeyError: if not is_nsmap_scope_changed: # We're about to mutate the these dicts so let's # copy them first. We don't have to recompute other # mappings as we're looking up or creating a new # prefix nsmap_scope = nsmap_scope.copy() uri_to_prefix = uri_to_prefix.copy() is_nsmap_scope_changed = True prefix = _get_or_create_prefix( uri_and_name[0], nsmap_scope, global_nsmap, new_namespace_prefixes, uri_to_prefix, ) if prefix: tag = f"{prefix}:{uri_and_name[1]}" else: tag = uri_and_name[1] elif "" in nsmap_scope: raise ValueError( "cannot use non-qualified names with default_namespace option" ) except TypeError: ET._raise_serialization_error(tag) write("<" + tag) if elem.attrib: item_parts, default_ns_attr_prefix, nsmap_scope = process_attribs( elem, is_nsmap_scope_changed, default_ns_attr_prefix, nsmap_scope, global_nsmap, new_namespace_prefixes, uri_to_prefix, ) else: item_parts = [] if new_namespace_prefixes: ns_attrs = [] for k in sorted(new_namespace_prefixes): v = nsmap_scope[k] if k: k = "xmlns:" + k else: k = "xmlns" ns_attrs.append((k, v)) if is_html: write("".join([f' {k}="{ET._escape_attrib_html(v)}"' for k, v in ns_attrs])) else: write("".join([f' {k}="{ET._escape_attrib(v)}"' for k, v in ns_attrs])) if item_parts: if is_html: write("".join([f' {k}="{ET._escape_attrib_html(v)}"' for k, v in item_parts])) else: write("".join([f' {k}="{ET._escape_attrib(v)}"' for k, v in item_parts])) if is_html: write(">") ltag = tag.lower() if text: if ltag == "script" or ltag == "style": write(text) else: write(ET._escape_cdata(text)) if ltag in ET.HTML_EMPTY: tag = None elif text or len(elem) or not short_empty_elements: write(">") if text: write(ET._escape_cdata(text)) else: tag = None write(" />") return ( tag, nsmap_scope, default_ns_attr_prefix, uri_to_prefix, next_remains_root, ) def _serialize_ns_xml( write, elem, nsmap_scope, global_nsmap, short_empty_elements, is_html, is_root=False, uri_to_prefix=None, default_ns_attr_prefix=None, new_nsmap=None, **kwargs, ): """Serialize an element or tree using 'write' for output. Args: write: A function to write the xml to its destination. elem: The element to serialize. nsmap_scope: The current prefix to uri mapping for this element. This should be an empty dictionary for the root element. Additional namespaces are progressively added using the new_nsmap arg. global_nsmap: A dict copy of the globally registered _namespace_map in uri to prefix form short_empty_elements: Controls the formatting of elements that contain no content. If True (default) they are emitted as a single self-closed tag, otherwise they are emitted as a pair of start/end tags. is_html: Set to True to serialize as HTML otherwise XML. is_root: Boolean indicating if this is a root element. uri_to_prefix: Current state of the mapping of uri to prefix. default_ns_attr_prefix: new_nsmap: New prefix -> uri mapping to be applied to this element. """ ( tag, nsmap_scope, default_ns_attr_prefix, uri_to_prefix, next_remains_root, ) = write_elem_start( write, elem, nsmap_scope, global_nsmap, short_empty_elements, is_html, is_root, uri_to_prefix, default_ns_attr_prefix, new_nsmap=new_nsmap, ) for e in elem: _serialize_ns_xml( write, e, nsmap_scope, global_nsmap, short_empty_elements, is_html, next_remains_root, uri_to_prefix, default_ns_attr_prefix, new_nsmap=None, ) if tag: write(f"") if elem.tail: write(ET._escape_cdata(elem.tail)) def _qnames_iter(elem): """Iterate through all the qualified names in elem""" seen_el_qnames = set() seen_other_qnames = set() for this_elem in elem.iter(): tag = this_elem.tag if isinstance(tag, str): if tag not in seen_el_qnames: seen_el_qnames.add(tag) yield tag, True elif isinstance(tag, ET.QName): tag = tag.text if tag not in seen_el_qnames: seen_el_qnames.add(tag) yield tag, True elif ( tag is not None and tag is not ET.ProcessingInstruction and tag is not ET.Comment ): ET._raise_serialization_error(tag) for key, value in this_elem.items(): if isinstance(key, ET.QName): key = key.text if key not in seen_other_qnames: seen_other_qnames.add(key) yield key, False if isinstance(value, ET.QName): if value.text not in seen_other_qnames: seen_other_qnames.add(value.text) yield value.text, False text = this_elem.text if isinstance(text, ET.QName): if text.text not in seen_other_qnames: seen_other_qnames.add(text.text) yield text.text, False def _namespaces( elem, default_namespace=None, nsmap=None, ): """Find all namespaces used in the document and return a prefix to uri map""" if nsmap is None: nsmap = {} out_nsmap = {} seen_uri_to_prefix = {} # Multiple prefixes may be present for a single uri. This will select the # last prefix found in nsmap for a given uri. local_prefix_map = {uri: prefix for prefix, uri in nsmap.items()} if default_namespace is not None: local_prefix_map[default_namespace] = "" elif "" in nsmap: # but we make sure the default prefix always take precedence local_prefix_map[nsmap[""]] = "" global_prefixes = set(ET._namespace_map.values()) has_unqual_el = False default_namespace_attr_prefix = None for qname, is_el in _qnames_iter(elem): try: if qname[:1] == "{": uri_and_name = qname[1:].rsplit("}", 1) prefix = seen_uri_to_prefix.get(uri_and_name[0]) if prefix is None: prefix = local_prefix_map.get(uri_and_name[0]) if prefix is None or prefix in out_nsmap: prefix = ET._namespace_map.get(uri_and_name[0]) if prefix is None or prefix in out_nsmap: prefix = _make_new_ns_prefix( out_nsmap, global_prefixes, nsmap, default_namespace, ) if prefix or is_el: out_nsmap[prefix] = uri_and_name[0] seen_uri_to_prefix[uri_and_name[0]] = prefix if not is_el and not prefix and not default_namespace_attr_prefix: # Find the alternative prefix to use with non-element # names default_namespace_attr_prefix = _find_default_namespace_attr_prefix( uri_and_name[0], out_nsmap, nsmap, global_prefixes, default_namespace, ) out_nsmap[default_namespace_attr_prefix] = uri_and_name[0] # Don't add this uri to prefix mapping as it might override # the uri -> "" default mapping. We'll fix this up at the # end of the fn. # local_prefix_map[uri_and_name[0]] = default_namespace_attr_prefix else: if is_el: has_unqual_el = True except TypeError: ET._raise_serialization_error(qname) if "" in out_nsmap and has_unqual_el: # FIXME: can this be handled in XML 1.0? raise ValueError( "cannot use non-qualified names with default_namespace option" ) # The xml prefix doesn't need to be declared but may have been used to # prefix names. Let's remove it if it has been used out_nsmap.pop("xml", None) return out_nsmap def tostring( element, encoding=None, method=None, *, xml_declaration=None, default_namespace=None, short_empty_elements=True, nsmap=None, root_ns_only=False, minimal_ns_only=False, tree_cls=IncrementalTree, ): """Generate string representation of XML element. All subelements are included. If encoding is "unicode", a string is returned. Otherwise a bytestring is returned. *element* is an Element instance, *encoding* is an optional output encoding defaulting to US-ASCII, *method* is an optional output which can be one of "xml" (default), "html", "text" or "c14n", *default_namespace* sets the default XML namespace (for "xmlns"). Returns an (optionally) encoded string containing the XML data. """ stream = io.StringIO() if encoding == "unicode" else io.BytesIO() tree_cls(element).write( stream, encoding, xml_declaration=xml_declaration, default_namespace=default_namespace, method=method, short_empty_elements=short_empty_elements, nsmap=nsmap, root_ns_only=root_ns_only, minimal_ns_only=minimal_ns_only, ) return stream.getvalue() def tostringlist( element, encoding=None, method=None, *, xml_declaration=None, default_namespace=None, short_empty_elements=True, nsmap=None, root_ns_only=False, minimal_ns_only=False, tree_cls=IncrementalTree, ): lst = [] stream = ET._ListDataStream(lst) tree_cls(element).write( stream, encoding, xml_declaration=xml_declaration, default_namespace=default_namespace, method=method, short_empty_elements=short_empty_elements, nsmap=nsmap, root_ns_only=root_ns_only, minimal_ns_only=minimal_ns_only, ) return lst def compat_tostring( element, encoding=None, method=None, *, xml_declaration=None, default_namespace=None, short_empty_elements=True, nsmap=None, root_ns_only=True, minimal_ns_only=False, tree_cls=IncrementalTree, ): """tostring with options that produce the same results as xml.etree.ElementTree.tostring root_ns_only=True is a bit slower than False as it needs to traverse the tree one more time to collect all the namespaces. """ return tostring( element, encoding=encoding, method=method, xml_declaration=xml_declaration, default_namespace=default_namespace, short_empty_elements=short_empty_elements, nsmap=nsmap, root_ns_only=root_ns_only, minimal_ns_only=minimal_ns_only, tree_cls=tree_cls, ) # -------------------------------------------------------------------- # serialization support @contextlib.contextmanager def _get_writer(file_or_filename, encoding): # Copied from Python 3.12 # returns text write method and release all resources after using try: write = file_or_filename.write except AttributeError: # file_or_filename is a file name if encoding.lower() == "unicode": encoding = "utf-8" with open(file_or_filename, "w", encoding=encoding, errors="xmlcharrefreplace") as file: yield file.write, encoding else: # file_or_filename is a file-like object # encoding determines if it is a text or binary writer if encoding.lower() == "unicode": # use a text writer as is yield write, getattr(file_or_filename, "encoding", None) or "utf-8" else: # wrap a binary writer with TextIOWrapper with contextlib.ExitStack() as stack: if isinstance(file_or_filename, io.BufferedIOBase): file = file_or_filename elif isinstance(file_or_filename, io.RawIOBase): file = io.BufferedWriter(file_or_filename) # Keep the original file open when the BufferedWriter is # destroyed stack.callback(file.detach) else: # This is to handle passed objects that aren't in the # IOBase hierarchy, but just have a write method file = io.BufferedIOBase() file.writable = lambda: True file.write = write try: # TextIOWrapper uses this methods to determine # if BOM (for UTF-16, etc) should be added file.seekable = file_or_filename.seekable file.tell = file_or_filename.tell except AttributeError: pass file = io.TextIOWrapper(file, encoding=encoding, errors="xmlcharrefreplace", newline="\n") # Keep the original file open when the TextIOWrapper is # destroyed stack.callback(file.detach) yield file.write, encoding ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729876127.0 et_xmlfile-2.0.0/et_xmlfile/tests/__init__.py0000644000000000000000000000000014706750237016173 0ustar00././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729876127.0 et_xmlfile-2.0.0/et_xmlfile/tests/_vendor/__init__.py0000644000000000000000000000000014706750237017627 0ustar00././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729876127.0 et_xmlfile-2.0.0/et_xmlfile/tests/_vendor/test/__init__.py0000644000000000000000000000000014706750237020606 0ustar00././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729876127.0 et_xmlfile-2.0.0/et_xmlfile/tests/_vendor/test/support/__init__.py0000644000000000000000000023737514706750237022355 0ustar00"""Supporting definitions for the Python regression tests.""" import contextlib import dataclasses import functools import opcode import os import re import stat import sys import sysconfig import textwrap import time import types import unittest import warnings __all__ = [ # globals "PIPE_MAX_SIZE", "verbose", "max_memuse", "use_resources", "failfast", # exceptions "Error", "TestFailed", "TestDidNotRun", "ResourceDenied", # io "record_original_stdout", "get_original_stdout", "captured_stdout", "captured_stdin", "captured_stderr", # unittest "is_resource_enabled", "requires", "requires_freebsd_version", "requires_linux_version", "requires_mac_ver", "check_syntax_error", "requires_gzip", "requires_bz2", "requires_lzma", "bigmemtest", "bigaddrspacetest", "cpython_only", "get_attribute", "requires_IEEE_754", "requires_zlib", "has_fork_support", "requires_fork", "has_subprocess_support", "requires_subprocess", "has_socket_support", "requires_working_socket", "anticipate_failure", "load_package_tests", "detect_api_mismatch", "check__all__", "skip_if_buggy_ucrt_strfptime", "check_disallow_instantiation", "check_sanitizer", "skip_if_sanitizer", "requires_limited_api", "requires_specialization", # sys "MS_WINDOWS", "is_jython", "is_android", "is_emscripten", "is_wasi", "check_impl_detail", "unix_shell", "setswitchinterval", # os "get_pagesize", # network "open_urlresource", # processes "reap_children", # miscellaneous "run_with_locale", "swap_item", "findfile", "infinite_recursion", "swap_attr", "Matcher", "set_memlimit", "SuppressCrashReport", "sortdict", "run_with_tz", "PGO", "missing_compiler_executable", "ALWAYS_EQ", "NEVER_EQ", "LARGEST", "SMALLEST", "LOOPBACK_TIMEOUT", "INTERNET_TIMEOUT", "SHORT_TIMEOUT", "LONG_TIMEOUT", "Py_DEBUG", "EXCEEDS_RECURSION_LIMIT", "C_RECURSION_LIMIT", "skip_on_s390x", ] # Timeout in seconds for tests using a network server listening on the network # local loopback interface like 127.0.0.1. # # The timeout is long enough to prevent test failure: it takes into account # that the client and the server can run in different threads or even different # processes. # # The timeout should be long enough for connect(), recv() and send() methods # of socket.socket. LOOPBACK_TIMEOUT = 10.0 # Timeout in seconds for network requests going to the internet. The timeout is # short enough to prevent a test to wait for too long if the internet request # is blocked for whatever reason. # # Usually, a timeout using INTERNET_TIMEOUT should not mark a test as failed, # but skip the test instead: see transient_internet(). INTERNET_TIMEOUT = 60.0 # Timeout in seconds to mark a test as failed if the test takes "too long". # # The timeout value depends on the regrtest --timeout command line option. # # If a test using SHORT_TIMEOUT starts to fail randomly on slow buildbots, use # LONG_TIMEOUT instead. SHORT_TIMEOUT = 30.0 # Timeout in seconds to detect when a test hangs. # # It is long enough to reduce the risk of test failure on the slowest Python # buildbots. It should not be used to mark a test as failed if the test takes # "too long". The timeout value depends on the regrtest --timeout command line # option. LONG_TIMEOUT = 5 * 60.0 # TEST_HOME_DIR refers to the top level directory of the "test" package # that contains Python's regression test suite TEST_SUPPORT_DIR = os.path.dirname(os.path.abspath(__file__)) TEST_HOME_DIR = os.path.dirname(TEST_SUPPORT_DIR) STDLIB_DIR = os.path.dirname(TEST_HOME_DIR) REPO_ROOT = os.path.dirname(STDLIB_DIR) class Error(Exception): """Base class for regression test exceptions.""" class TestFailed(Error): """Test failed.""" def __init__(self, msg, *args, stats=None): self.msg = msg self.stats = stats super().__init__(msg, *args) def __str__(self): return self.msg class TestFailedWithDetails(TestFailed): """Test failed.""" def __init__(self, msg, errors, failures, stats): self.errors = errors self.failures = failures super().__init__(msg, errors, failures, stats=stats) class TestDidNotRun(Error): """Test did not run any subtests.""" class ResourceDenied(unittest.SkipTest): """Test skipped because it requested a disallowed resource. This is raised when a test calls requires() for a resource that has not be enabled. It is used to distinguish between expected and unexpected skips. """ def anticipate_failure(condition): """Decorator to mark a test that is known to be broken in some cases Any use of this decorator should have a comment identifying the associated tracker issue. """ if condition: return unittest.expectedFailure return lambda f: f def load_package_tests(pkg_dir, loader, standard_tests, pattern): """Generic load_tests implementation for simple test packages. Most packages can implement load_tests using this function as follows: def load_tests(*args): return load_package_tests(os.path.dirname(__file__), *args) """ if pattern is None: pattern = "test*" top_dir = STDLIB_DIR package_tests = loader.discover(start_dir=pkg_dir, top_level_dir=top_dir, pattern=pattern) standard_tests.addTests(package_tests) return standard_tests def get_attribute(obj, name): """Get an attribute, raising SkipTest if AttributeError is raised.""" try: attribute = getattr(obj, name) except AttributeError: raise unittest.SkipTest("object %r has no attribute %r" % (obj, name)) else: return attribute verbose = 1 # Flag set to 0 by regrtest.py use_resources = None # Flag set to [] by regrtest.py max_memuse = 0 # Disable bigmem tests (they will still be run with # small sizes, to make sure they work.) real_max_memuse = 0 junit_xml_list = None # list of testsuite XML elements failfast = False # _original_stdout is meant to hold stdout at the time regrtest began. # This may be "the real" stdout, or IDLE's emulation of stdout, or whatever. # The point is to have some flavor of stdout the user can actually see. _original_stdout = None def record_original_stdout(stdout): global _original_stdout _original_stdout = stdout def get_original_stdout(): return _original_stdout or sys.stdout def _force_run(path, func, *args): try: return func(*args) except FileNotFoundError as err: # chmod() won't fix a missing file. if verbose >= 2: print('%s: %s' % (err.__class__.__name__, err)) raise except OSError as err: if verbose >= 2: print('%s: %s' % (err.__class__.__name__, err)) print('re-run %s%r' % (func.__name__, args)) os.chmod(path, stat.S_IRWXU) return func(*args) # Check whether a gui is actually available def _is_gui_available(): if hasattr(_is_gui_available, 'result'): return _is_gui_available.result import platform reason = None if sys.platform.startswith('win') and platform.win32_is_iot(): reason = "gui is not available on Windows IoT Core" elif sys.platform.startswith('win'): # if Python is running as a service (such as the buildbot service), # gui interaction may be disallowed import ctypes import ctypes.wintypes UOI_FLAGS = 1 WSF_VISIBLE = 0x0001 class USEROBJECTFLAGS(ctypes.Structure): _fields_ = [("fInherit", ctypes.wintypes.BOOL), ("fReserved", ctypes.wintypes.BOOL), ("dwFlags", ctypes.wintypes.DWORD)] dll = ctypes.windll.user32 h = dll.GetProcessWindowStation() if not h: raise ctypes.WinError() uof = USEROBJECTFLAGS() needed = ctypes.wintypes.DWORD() res = dll.GetUserObjectInformationW(h, UOI_FLAGS, ctypes.byref(uof), ctypes.sizeof(uof), ctypes.byref(needed)) if not res: raise ctypes.WinError() if not bool(uof.dwFlags & WSF_VISIBLE): reason = "gui not available (WSF_VISIBLE flag not set)" elif sys.platform == 'darwin': # The Aqua Tk implementations on OS X can abort the process if # being called in an environment where a window server connection # cannot be made, for instance when invoked by a buildbot or ssh # process not running under the same user id as the current console # user. To avoid that, raise an exception if the window manager # connection is not available. from ctypes import cdll, c_int, pointer, Structure from ctypes.util import find_library app_services = cdll.LoadLibrary(find_library("ApplicationServices")) if app_services.CGMainDisplayID() == 0: reason = "gui tests cannot run without OS X window manager" else: class ProcessSerialNumber(Structure): _fields_ = [("highLongOfPSN", c_int), ("lowLongOfPSN", c_int)] psn = ProcessSerialNumber() psn_p = pointer(psn) if ( (app_services.GetCurrentProcess(psn_p) < 0) or (app_services.SetFrontProcess(psn_p) < 0) ): reason = "cannot run without OS X gui process" # check on every platform whether tkinter can actually do anything if not reason: try: from tkinter import Tk root = Tk() root.withdraw() root.update() root.destroy() except Exception as e: err_string = str(e) if len(err_string) > 50: err_string = err_string[:50] + ' [...]' reason = 'Tk unavailable due to {}: {}'.format(type(e).__name__, err_string) _is_gui_available.reason = reason _is_gui_available.result = not reason return _is_gui_available.result def is_resource_enabled(resource): """Test whether a resource is enabled. Known resources are set by regrtest.py. If not running under regrtest.py, all resources are assumed enabled unless use_resources has been set. """ return use_resources is None or resource in use_resources def requires(resource, msg=None): """Raise ResourceDenied if the specified resource is not available.""" if not is_resource_enabled(resource): if msg is None: msg = "Use of the %r resource not enabled" % resource raise ResourceDenied(msg) if resource in {"network", "urlfetch"} and not has_socket_support: raise ResourceDenied("No socket support") if resource == 'gui' and not _is_gui_available(): raise ResourceDenied(_is_gui_available.reason) def _requires_unix_version(sysname, min_version): """Decorator raising SkipTest if the OS is `sysname` and the version is less than `min_version`. For example, @_requires_unix_version('FreeBSD', (7, 2)) raises SkipTest if the FreeBSD version is less than 7.2. """ import platform min_version_txt = '.'.join(map(str, min_version)) version_txt = platform.release().split('-', 1)[0] if platform.system() == sysname: try: version = tuple(map(int, version_txt.split('.'))) except ValueError: skip = False else: skip = version < min_version else: skip = False return unittest.skipIf( skip, f"{sysname} version {min_version_txt} or higher required, not " f"{version_txt}" ) def requires_freebsd_version(*min_version): """Decorator raising SkipTest if the OS is FreeBSD and the FreeBSD version is less than `min_version`. For example, @requires_freebsd_version(7, 2) raises SkipTest if the FreeBSD version is less than 7.2. """ return _requires_unix_version('FreeBSD', min_version) def requires_linux_version(*min_version): """Decorator raising SkipTest if the OS is Linux and the Linux version is less than `min_version`. For example, @requires_linux_version(2, 6, 32) raises SkipTest if the Linux version is less than 2.6.32. """ return _requires_unix_version('Linux', min_version) def requires_mac_ver(*min_version): """Decorator raising SkipTest if the OS is Mac OS X and the OS X version if less than min_version. For example, @requires_mac_ver(10, 5) raises SkipTest if the OS X version is lesser than 10.5. """ def decorator(func): @functools.wraps(func) def wrapper(*args, **kw): if sys.platform == 'darwin': import platform version_txt = platform.mac_ver()[0] try: version = tuple(map(int, version_txt.split('.'))) except ValueError: pass else: if version < min_version: min_version_txt = '.'.join(map(str, min_version)) raise unittest.SkipTest( "Mac OS X %s or higher required, not %s" % (min_version_txt, version_txt)) return func(*args, **kw) wrapper.min_version = min_version return wrapper return decorator def skip_if_buildbot(reason=None): """Decorator raising SkipTest if running on a buildbot.""" import getpass if not reason: reason = 'not suitable for buildbots' try: isbuildbot = getpass.getuser().lower() == 'buildbot' except (KeyError, OSError) as err: warnings.warn(f'getpass.getuser() failed {err}.', RuntimeWarning) isbuildbot = False return unittest.skipIf(isbuildbot, reason) def check_sanitizer(*, address=False, memory=False, ub=False, thread=False): """Returns True if Python is compiled with sanitizer support""" if not (address or memory or ub or thread): raise ValueError('At least one of address, memory, ub or thread must be True') cflags = sysconfig.get_config_var('CFLAGS') or '' config_args = sysconfig.get_config_var('CONFIG_ARGS') or '' memory_sanitizer = ( '-fsanitize=memory' in cflags or '--with-memory-sanitizer' in config_args ) address_sanitizer = ( '-fsanitize=address' in cflags or '--with-address-sanitizer' in config_args ) ub_sanitizer = ( '-fsanitize=undefined' in cflags or '--with-undefined-behavior-sanitizer' in config_args ) thread_sanitizer = ( '-fsanitize=thread' in cflags or '--with-thread-sanitizer' in config_args ) return ( (memory and memory_sanitizer) or (address and address_sanitizer) or (ub and ub_sanitizer) or (thread and thread_sanitizer) ) def skip_if_sanitizer(reason=None, *, address=False, memory=False, ub=False, thread=False): """Decorator raising SkipTest if running with a sanitizer active.""" if not reason: reason = 'not working with sanitizers active' skip = check_sanitizer(address=address, memory=memory, ub=ub, thread=thread) return unittest.skipIf(skip, reason) # gh-89363: True if fork() can hang if Python is built with Address Sanitizer # (ASAN): libasan race condition, dead lock in pthread_create(). HAVE_ASAN_FORK_BUG = check_sanitizer(address=True) def set_sanitizer_env_var(env, option): for name in ('ASAN_OPTIONS', 'MSAN_OPTIONS', 'UBSAN_OPTIONS', 'TSAN_OPTIONS'): if name in env: env[name] += f':{option}' else: env[name] = option def system_must_validate_cert(f): """Skip the test on TLS certificate validation failures.""" @functools.wraps(f) def dec(*args, **kwargs): try: f(*args, **kwargs) except OSError as e: if "CERTIFICATE_VERIFY_FAILED" in str(e): raise unittest.SkipTest("system does not contain " "necessary certificates") raise return dec # A constant likely larger than the underlying OS pipe buffer size, to # make writes blocking. # Windows limit seems to be around 512 B, and many Unix kernels have a # 64 KiB pipe buffer size or 16 * PAGE_SIZE: take a few megs to be sure. # (see issue #17835 for a discussion of this number). PIPE_MAX_SIZE = 4 * 1024 * 1024 + 1 # A constant likely larger than the underlying OS socket buffer size, to make # writes blocking. # The socket buffer sizes can usually be tuned system-wide (e.g. through sysctl # on Linux), or on a per-socket basis (SO_SNDBUF/SO_RCVBUF). See issue #18643 # for a discussion of this number. SOCK_MAX_SIZE = 16 * 1024 * 1024 + 1 # decorator for skipping tests on non-IEEE 754 platforms requires_IEEE_754 = unittest.skipUnless( float.__getformat__("double").startswith("IEEE"), "test requires IEEE 754 doubles") def requires_zlib(reason='requires zlib'): try: import zlib except ImportError: zlib = None return unittest.skipUnless(zlib, reason) def requires_gzip(reason='requires gzip'): try: import gzip except ImportError: gzip = None return unittest.skipUnless(gzip, reason) def requires_bz2(reason='requires bz2'): try: import bz2 except ImportError: bz2 = None return unittest.skipUnless(bz2, reason) def requires_lzma(reason='requires lzma'): try: import lzma except ImportError: lzma = None return unittest.skipUnless(lzma, reason) def has_no_debug_ranges(): try: import _testinternalcapi except ImportError: raise unittest.SkipTest("_testinternalcapi required") config = _testinternalcapi.get_config() return not bool(config['code_debug_ranges']) def requires_debug_ranges(reason='requires co_positions / debug_ranges'): return unittest.skipIf(has_no_debug_ranges(), reason) def requires_legacy_unicode_capi(): try: from _testcapi import unicode_legacy_string except ImportError: unicode_legacy_string = None return unittest.skipUnless(unicode_legacy_string, 'requires legacy Unicode C API') MS_WINDOWS = (sys.platform == 'win32') # Is not actually used in tests, but is kept for compatibility. is_jython = sys.platform.startswith('java') is_android = hasattr(sys, 'getandroidapilevel') if sys.platform not in ('win32', 'vxworks'): unix_shell = '/system/bin/sh' if is_android else '/bin/sh' else: unix_shell = None # wasm32-emscripten and -wasi are POSIX-like but do not # have subprocess or fork support. is_emscripten = sys.platform == "emscripten" is_wasi = sys.platform == "wasi" has_fork_support = hasattr(os, "fork") and not is_emscripten and not is_wasi def requires_fork(): return unittest.skipUnless(has_fork_support, "requires working os.fork()") has_subprocess_support = not is_emscripten and not is_wasi def requires_subprocess(): """Used for subprocess, os.spawn calls, fd inheritance""" return unittest.skipUnless(has_subprocess_support, "requires subprocess support") # Emscripten's socket emulation and WASI sockets have limitations. has_socket_support = not is_emscripten and not is_wasi def requires_working_socket(*, module=False): """Skip tests or modules that require working sockets Can be used as a function/class decorator or to skip an entire module. """ msg = "requires socket support" if module: if not has_socket_support: raise unittest.SkipTest(msg) else: return unittest.skipUnless(has_socket_support, msg) # Does strftime() support glibc extension like '%4Y'? has_strftime_extensions = False if sys.platform != "win32": # bpo-47037: Windows debug builds crash with "Debug Assertion Failed" try: has_strftime_extensions = time.strftime("%4Y") != "%4Y" except ValueError: pass # Define the URL of a dedicated HTTP server for the network tests. # The URL must use clear-text HTTP: no redirection to encrypted HTTPS. TEST_HTTP_URL = "http://www.pythontest.net" # Set by libregrtest/main.py so we can skip tests that are not # useful for PGO PGO = False # Set by libregrtest/main.py if we are running the extended (time consuming) # PGO task. If this is True, PGO is also True. PGO_EXTENDED = False # TEST_DATA_DIR is used as a target download location for remote resources TEST_DATA_DIR = os.path.join(TEST_HOME_DIR, "data") def darwin_malloc_err_warning(test_name): """Assure user that loud errors generated by macOS libc's malloc are expected.""" if sys.platform != 'darwin': return import shutil msg = ' NOTICE ' detail = (f'{test_name} may generate "malloc can\'t allocate region"\n' 'warnings on macOS systems. This behavior is known. Do not\n' 'report a bug unless tests are also failing.\n' 'See https://github.com/python/cpython/issues/85100') padding, _ = shutil.get_terminal_size() print(msg.center(padding, '-')) print(detail) print('-' * padding) def findfile(filename, subdir=None): """Try to find a file on sys.path or in the test directory. If it is not found the argument passed to the function is returned (this does not necessarily signal failure; could still be the legitimate path). Setting *subdir* indicates a relative path to use to find the file rather than looking directly in the path directories. """ if os.path.isabs(filename): return filename if subdir is not None: filename = os.path.join(subdir, filename) path = [TEST_HOME_DIR] + sys.path for dn in path: fn = os.path.join(dn, filename) if os.path.exists(fn): return fn return filename def sortdict(dict): "Like repr(dict), but in sorted order." items = sorted(dict.items()) reprpairs = ["%r: %r" % pair for pair in items] withcommas = ", ".join(reprpairs) return "{%s}" % withcommas def run_code(code): """Run a piece of code after dedenting it, and return its global namespace.""" ns = {} exec(textwrap.dedent(code), ns) return ns def check_syntax_error(testcase, statement, errtext='', *, lineno=None, offset=None): with testcase.assertRaisesRegex(SyntaxError, errtext) as cm: compile(statement, '', 'exec') err = cm.exception testcase.assertIsNotNone(err.lineno) if lineno is not None: testcase.assertEqual(err.lineno, lineno) testcase.assertIsNotNone(err.offset) if offset is not None: testcase.assertEqual(err.offset, offset) def open_urlresource(url, *args, **kw): import urllib.request, urllib.parse from .os_helper import unlink try: import gzip except ImportError: gzip = None check = kw.pop('check', None) filename = urllib.parse.urlparse(url)[2].split('/')[-1] # '/': it's URL! fn = os.path.join(TEST_DATA_DIR, filename) def check_valid_file(fn): f = open(fn, *args, **kw) if check is None: return f elif check(f): f.seek(0) return f f.close() if os.path.exists(fn): f = check_valid_file(fn) if f is not None: return f unlink(fn) # Verify the requirement before downloading the file requires('urlfetch') if verbose: print('\tfetching %s ...' % url, file=get_original_stdout()) opener = urllib.request.build_opener() if gzip: opener.addheaders.append(('Accept-Encoding', 'gzip')) f = opener.open(url, timeout=INTERNET_TIMEOUT) if gzip and f.headers.get('Content-Encoding') == 'gzip': f = gzip.GzipFile(fileobj=f) try: with open(fn, "wb") as out: s = f.read() while s: out.write(s) s = f.read() finally: f.close() f = check_valid_file(fn) if f is not None: return f raise TestFailed('invalid resource %r' % fn) @contextlib.contextmanager def captured_output(stream_name): """Return a context manager used by captured_stdout/stdin/stderr that temporarily replaces the sys stream *stream_name* with a StringIO.""" import io orig_stdout = getattr(sys, stream_name) setattr(sys, stream_name, io.StringIO()) try: yield getattr(sys, stream_name) finally: setattr(sys, stream_name, orig_stdout) def captured_stdout(): """Capture the output of sys.stdout: with captured_stdout() as stdout: print("hello") self.assertEqual(stdout.getvalue(), "hello\\n") """ return captured_output("stdout") def captured_stderr(): """Capture the output of sys.stderr: with captured_stderr() as stderr: print("hello", file=sys.stderr) self.assertEqual(stderr.getvalue(), "hello\\n") """ return captured_output("stderr") def captured_stdin(): """Capture the input to sys.stdin: with captured_stdin() as stdin: stdin.write('hello\\n') stdin.seek(0) # call test code that consumes from sys.stdin captured = input() self.assertEqual(captured, "hello") """ return captured_output("stdin") def gc_collect(): """Force as many objects as possible to be collected. In non-CPython implementations of Python, this is needed because timely deallocation is not guaranteed by the garbage collector. (Even in CPython this can be the case in case of reference cycles.) This means that __del__ methods may be called later than expected and weakrefs may remain alive for longer than expected. This function tries its best to force all garbage objects to disappear. """ import gc gc.collect() gc.collect() gc.collect() @contextlib.contextmanager def disable_gc(): import gc have_gc = gc.isenabled() gc.disable() try: yield finally: if have_gc: gc.enable() def python_is_optimized(): """Find if Python was built with optimizations.""" cflags = sysconfig.get_config_var('PY_CFLAGS') or '' final_opt = "" for opt in cflags.split(): if opt.startswith('-O'): final_opt = opt return final_opt not in ('', '-O0', '-Og') def check_cflags_pgo(): # Check if Python was built with ./configure --enable-optimizations: # with Profile Guided Optimization (PGO). cflags_nodist = sysconfig.get_config_var('PY_CFLAGS_NODIST') or '' pgo_options = [ # GCC '-fprofile-use', # clang: -fprofile-instr-use=code.profclangd '-fprofile-instr-use', # ICC "-prof-use", ] PGO_PROF_USE_FLAG = sysconfig.get_config_var('PGO_PROF_USE_FLAG') if PGO_PROF_USE_FLAG: pgo_options.append(PGO_PROF_USE_FLAG) return any(option in cflags_nodist for option in pgo_options) _header = 'nP' _align = '0n' if hasattr(sys, "getobjects"): _header = '2P' + _header _align = '0P' _vheader = _header + 'n' def calcobjsize(fmt): import struct return struct.calcsize(_header + fmt + _align) def calcvobjsize(fmt): import struct return struct.calcsize(_vheader + fmt + _align) _TPFLAGS_HAVE_GC = 1<<14 _TPFLAGS_HEAPTYPE = 1<<9 def check_sizeof(test, o, size): try: import _testinternalcapi except ImportError: raise unittest.SkipTest("_testinternalcapi required") result = sys.getsizeof(o) # add GC header size if ((type(o) == type) and (o.__flags__ & _TPFLAGS_HEAPTYPE) or\ ((type(o) != type) and (type(o).__flags__ & _TPFLAGS_HAVE_GC))): size += _testinternalcapi.SIZEOF_PYGC_HEAD msg = 'wrong size for %s: got %d, expected %d' \ % (type(o), result, size) test.assertEqual(result, size, msg) #======================================================================= # Decorator for running a function in a different locale, correctly resetting # it afterwards. @contextlib.contextmanager def run_with_locale(catstr, *locales): try: import locale category = getattr(locale, catstr) orig_locale = locale.setlocale(category) except AttributeError: # if the test author gives us an invalid category string raise except: # cannot retrieve original locale, so do nothing locale = orig_locale = None else: for loc in locales: try: locale.setlocale(category, loc) break except: pass try: yield finally: if locale and orig_locale: locale.setlocale(category, orig_locale) #======================================================================= # Decorator for running a function in a specific timezone, correctly # resetting it afterwards. def run_with_tz(tz): def decorator(func): def inner(*args, **kwds): try: tzset = time.tzset except AttributeError: raise unittest.SkipTest("tzset required") if 'TZ' in os.environ: orig_tz = os.environ['TZ'] else: orig_tz = None os.environ['TZ'] = tz tzset() # now run the function, resetting the tz on exceptions try: return func(*args, **kwds) finally: if orig_tz is None: del os.environ['TZ'] else: os.environ['TZ'] = orig_tz time.tzset() inner.__name__ = func.__name__ inner.__doc__ = func.__doc__ return inner return decorator #======================================================================= # Big-memory-test support. Separate from 'resources' because memory use # should be configurable. # Some handy shorthands. Note that these are used for byte-limits as well # as size-limits, in the various bigmem tests _1M = 1024*1024 _1G = 1024 * _1M _2G = 2 * _1G _4G = 4 * _1G MAX_Py_ssize_t = sys.maxsize def _parse_memlimit(limit): sizes = { 'k': 1024, 'm': _1M, 'g': _1G, 't': 1024*_1G, } m = re.match(r'(\d+(?:\.\d+)?) (K|M|G|T)b?$', limit, re.IGNORECASE | re.VERBOSE) if m is None: raise ValueError(f'Invalid memory limit: {limit!r}') return int(float(m.group(1)) * sizes[m.group(2).lower()]) def set_memlimit(limit): global max_memuse global real_max_memuse memlimit = _parse_memlimit(limit) if memlimit < _2G - 1: raise ValueError('Memory limit {limit!r} too low to be useful') real_max_memuse = memlimit memlimit = min(memlimit, MAX_Py_ssize_t) max_memuse = memlimit class _MemoryWatchdog: """An object which periodically watches the process' memory consumption and prints it out. """ def __init__(self): self.procfile = '/proc/{pid}/statm'.format(pid=os.getpid()) self.started = False def start(self): import warnings try: f = open(self.procfile, 'r') except OSError as e: warnings.warn('/proc not available for stats: {}'.format(e), RuntimeWarning) sys.stderr.flush() return import subprocess with f: watchdog_script = findfile("memory_watchdog.py") self.mem_watchdog = subprocess.Popen([sys.executable, watchdog_script], stdin=f, stderr=subprocess.DEVNULL) self.started = True def stop(self): if self.started: self.mem_watchdog.terminate() self.mem_watchdog.wait() def bigmemtest(size, memuse, dry_run=True): """Decorator for bigmem tests. 'size' is a requested size for the test (in arbitrary, test-interpreted units.) 'memuse' is the number of bytes per unit for the test, or a good estimate of it. For example, a test that needs two byte buffers, of 4 GiB each, could be decorated with @bigmemtest(size=_4G, memuse=2). The 'size' argument is normally passed to the decorated test method as an extra argument. If 'dry_run' is true, the value passed to the test method may be less than the requested value. If 'dry_run' is false, it means the test doesn't support dummy runs when -M is not specified. """ def decorator(f): def wrapper(self): size = wrapper.size memuse = wrapper.memuse if not real_max_memuse: maxsize = 5147 else: maxsize = size if ((real_max_memuse or not dry_run) and real_max_memuse < maxsize * memuse): raise unittest.SkipTest( "not enough memory: %.1fG minimum needed" % (size * memuse / (1024 ** 3))) if real_max_memuse and verbose: print() print(" ... expected peak memory use: {peak:.1f}G" .format(peak=size * memuse / (1024 ** 3))) watchdog = _MemoryWatchdog() watchdog.start() else: watchdog = None try: return f(self, maxsize) finally: if watchdog: watchdog.stop() wrapper.size = size wrapper.memuse = memuse return wrapper return decorator def bigaddrspacetest(f): """Decorator for tests that fill the address space.""" def wrapper(self): if max_memuse < MAX_Py_ssize_t: if MAX_Py_ssize_t >= 2**63 - 1 and max_memuse >= 2**31: raise unittest.SkipTest( "not enough memory: try a 32-bit build instead") else: raise unittest.SkipTest( "not enough memory: %.1fG minimum needed" % (MAX_Py_ssize_t / (1024 ** 3))) else: return f(self) return wrapper #======================================================================= # unittest integration. def _id(obj): return obj def requires_resource(resource): if resource == 'gui' and not _is_gui_available(): return unittest.skip(_is_gui_available.reason) if is_resource_enabled(resource): return _id else: return unittest.skip("resource {0!r} is not enabled".format(resource)) def cpython_only(test): """ Decorator for tests only applicable on CPython. """ return impl_detail(cpython=True)(test) def impl_detail(msg=None, **guards): if check_impl_detail(**guards): return _id if msg is None: guardnames, default = _parse_guards(guards) if default: msg = "implementation detail not available on {0}" else: msg = "implementation detail specific to {0}" guardnames = sorted(guardnames.keys()) msg = msg.format(' or '.join(guardnames)) return unittest.skip(msg) def _parse_guards(guards): # Returns a tuple ({platform_name: run_me}, default_value) if not guards: return ({'cpython': True}, False) is_true = list(guards.values())[0] assert list(guards.values()) == [is_true] * len(guards) # all True or all False return (guards, not is_true) # Use the following check to guard CPython's implementation-specific tests -- # or to run them only on the implementation(s) guarded by the arguments. def check_impl_detail(**guards): """This function returns True or False depending on the host platform. Examples: if check_impl_detail(): # only on CPython (default) if check_impl_detail(jython=True): # only on Jython if check_impl_detail(cpython=False): # everywhere except on CPython """ guards, default = _parse_guards(guards) return guards.get(sys.implementation.name, default) def no_tracing(func): """Decorator to temporarily turn off tracing for the duration of a test.""" if not hasattr(sys, 'gettrace'): return func else: @functools.wraps(func) def wrapper(*args, **kwargs): original_trace = sys.gettrace() try: sys.settrace(None) return func(*args, **kwargs) finally: sys.settrace(original_trace) return wrapper def refcount_test(test): """Decorator for tests which involve reference counting. To start, the decorator does not run the test if is not run by CPython. After that, any trace function is unset during the test to prevent unexpected refcounts caused by the trace function. """ return no_tracing(cpython_only(test)) def requires_limited_api(test): try: import _testcapi except ImportError: return unittest.skip('needs _testcapi module')(test) return unittest.skipUnless( _testcapi.LIMITED_API_AVAILABLE, 'needs Limited API support')(test) def requires_specialization(test): return unittest.skipUnless( opcode.ENABLE_SPECIALIZATION, "requires specialization")(test) #======================================================================= # Check for the presence of docstrings. # Rather than trying to enumerate all the cases where docstrings may be # disabled, we just check for that directly def _check_docstrings(): """Just used to check if docstrings are enabled""" MISSING_C_DOCSTRINGS = (check_impl_detail() and sys.platform != 'win32' and not sysconfig.get_config_var('WITH_DOC_STRINGS')) HAVE_DOCSTRINGS = (_check_docstrings.__doc__ is not None and not MISSING_C_DOCSTRINGS) requires_docstrings = unittest.skipUnless(HAVE_DOCSTRINGS, "test requires docstrings") #======================================================================= # Support for saving and restoring the imported modules. def flush_std_streams(): if sys.stdout is not None: sys.stdout.flush() if sys.stderr is not None: sys.stderr.flush() def print_warning(msg): # bpo-45410: Explicitly flush stdout to keep logs in order flush_std_streams() stream = print_warning.orig_stderr for line in msg.splitlines(): print(f"Warning -- {line}", file=stream) stream.flush() # bpo-39983: Store the original sys.stderr at Python startup to be able to # log warnings even if sys.stderr is captured temporarily by a test. print_warning.orig_stderr = sys.stderr # Flag used by saved_test_environment of test.libregrtest.save_env, # to check if a test modified the environment. The flag should be set to False # before running a new test. # # For example, threading_helper.threading_cleanup() sets the flag is the function fails # to cleanup threads. environment_altered = False def reap_children(): """Use this function at the end of test_main() whenever sub-processes are started. This will help ensure that no extra children (zombies) stick around to hog resources and create problems when looking for refleaks. """ global environment_altered # Need os.waitpid(-1, os.WNOHANG): Windows is not supported if not (hasattr(os, 'waitpid') and hasattr(os, 'WNOHANG')): return elif not has_subprocess_support: return # Reap all our dead child processes so we don't leave zombies around. # These hog resources and might be causing some of the buildbots to die. while True: try: # Read the exit status of any child process which already completed pid, status = os.waitpid(-1, os.WNOHANG) except OSError: break if pid == 0: break print_warning(f"reap_children() reaped child process {pid}") environment_altered = True @contextlib.contextmanager def swap_attr(obj, attr, new_val): """Temporary swap out an attribute with a new object. Usage: with swap_attr(obj, "attr", 5): ... This will set obj.attr to 5 for the duration of the with: block, restoring the old value at the end of the block. If `attr` doesn't exist on `obj`, it will be created and then deleted at the end of the block. The old value (or None if it doesn't exist) will be assigned to the target of the "as" clause, if there is one. """ if hasattr(obj, attr): real_val = getattr(obj, attr) setattr(obj, attr, new_val) try: yield real_val finally: setattr(obj, attr, real_val) else: setattr(obj, attr, new_val) try: yield finally: if hasattr(obj, attr): delattr(obj, attr) @contextlib.contextmanager def swap_item(obj, item, new_val): """Temporary swap out an item with a new object. Usage: with swap_item(obj, "item", 5): ... This will set obj["item"] to 5 for the duration of the with: block, restoring the old value at the end of the block. If `item` doesn't exist on `obj`, it will be created and then deleted at the end of the block. The old value (or None if it doesn't exist) will be assigned to the target of the "as" clause, if there is one. """ if item in obj: real_val = obj[item] obj[item] = new_val try: yield real_val finally: obj[item] = real_val else: obj[item] = new_val try: yield finally: if item in obj: del obj[item] def args_from_interpreter_flags(): """Return a list of command-line arguments reproducing the current settings in sys.flags and sys.warnoptions.""" import subprocess return subprocess._args_from_interpreter_flags() def optim_args_from_interpreter_flags(): """Return a list of command-line arguments reproducing the current optimization settings in sys.flags.""" import subprocess return subprocess._optim_args_from_interpreter_flags() class Matcher(object): _partial_matches = ('msg', 'message') def matches(self, d, **kwargs): """ Try to match a single dict with the supplied arguments. Keys whose values are strings and which are in self._partial_matches will be checked for partial (i.e. substring) matches. You can extend this scheme to (for example) do regular expression matching, etc. """ result = True for k in kwargs: v = kwargs[k] dv = d.get(k) if not self.match_value(k, dv, v): result = False break return result def match_value(self, k, dv, v): """ Try to match a single stored value (dv) with a supplied value (v). """ if type(v) != type(dv): result = False elif type(dv) is not str or k not in self._partial_matches: result = (v == dv) else: result = dv.find(v) >= 0 return result _buggy_ucrt = None def skip_if_buggy_ucrt_strfptime(test): """ Skip decorator for tests that use buggy strptime/strftime If the UCRT bugs are present time.localtime().tm_zone will be an empty string, otherwise we assume the UCRT bugs are fixed See bpo-37552 [Windows] strptime/strftime return invalid results with UCRT version 17763.615 """ import locale global _buggy_ucrt if _buggy_ucrt is None: if(sys.platform == 'win32' and locale.getencoding() == 'cp65001' and time.localtime().tm_zone == ''): _buggy_ucrt = True else: _buggy_ucrt = False return unittest.skip("buggy MSVC UCRT strptime/strftime")(test) if _buggy_ucrt else test class PythonSymlink: """Creates a symlink for the current Python executable""" def __init__(self, link=None): from .os_helper import TESTFN self.link = link or os.path.abspath(TESTFN) self._linked = [] self.real = os.path.realpath(sys.executable) self._also_link = [] self._env = None self._platform_specific() if sys.platform == "win32": def _platform_specific(self): import glob import _winapi if os.path.lexists(self.real) and not os.path.exists(self.real): # App symlink appears to not exist, but we want the # real executable here anyway self.real = _winapi.GetModuleFileName(0) dll = _winapi.GetModuleFileName(sys.dllhandle) src_dir = os.path.dirname(dll) dest_dir = os.path.dirname(self.link) self._also_link.append(( dll, os.path.join(dest_dir, os.path.basename(dll)) )) for runtime in glob.glob(os.path.join(glob.escape(src_dir), "vcruntime*.dll")): self._also_link.append(( runtime, os.path.join(dest_dir, os.path.basename(runtime)) )) self._env = {k.upper(): os.getenv(k) for k in os.environ} self._env["PYTHONHOME"] = os.path.dirname(self.real) if sysconfig.is_python_build(): self._env["PYTHONPATH"] = STDLIB_DIR else: def _platform_specific(self): pass def __enter__(self): os.symlink(self.real, self.link) self._linked.append(self.link) for real, link in self._also_link: os.symlink(real, link) self._linked.append(link) return self def __exit__(self, exc_type, exc_value, exc_tb): for link in self._linked: try: os.remove(link) except IOError as ex: if verbose: print("failed to clean up {}: {}".format(link, ex)) def _call(self, python, args, env, returncode): import subprocess cmd = [python, *args] p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) r = p.communicate() if p.returncode != returncode: if verbose: print(repr(r[0])) print(repr(r[1]), file=sys.stderr) raise RuntimeError( 'unexpected return code: {0} (0x{0:08X})'.format(p.returncode)) return r def call_real(self, *args, returncode=0): return self._call(self.real, args, None, returncode) def call_link(self, *args, returncode=0): return self._call(self.link, args, self._env, returncode) def skip_if_pgo_task(test): """Skip decorator for tests not run in (non-extended) PGO task""" ok = not PGO or PGO_EXTENDED msg = "Not run for (non-extended) PGO task" return test if ok else unittest.skip(msg)(test) def detect_api_mismatch(ref_api, other_api, *, ignore=()): """Returns the set of items in ref_api not in other_api, except for a defined list of items to be ignored in this check. By default this skips private attributes beginning with '_' but includes all magic methods, i.e. those starting and ending in '__'. """ missing_items = set(dir(ref_api)) - set(dir(other_api)) if ignore: missing_items -= set(ignore) missing_items = set(m for m in missing_items if not m.startswith('_') or m.endswith('__')) return missing_items def check__all__(test_case, module, name_of_module=None, extra=(), not_exported=()): """Assert that the __all__ variable of 'module' contains all public names. The module's public names (its API) are detected automatically based on whether they match the public name convention and were defined in 'module'. The 'name_of_module' argument can specify (as a string or tuple thereof) what module(s) an API could be defined in in order to be detected as a public API. One case for this is when 'module' imports part of its public API from other modules, possibly a C backend (like 'csv' and its '_csv'). The 'extra' argument can be a set of names that wouldn't otherwise be automatically detected as "public", like objects without a proper '__module__' attribute. If provided, it will be added to the automatically detected ones. The 'not_exported' argument can be a set of names that must not be treated as part of the public API even though their names indicate otherwise. Usage: import bar import foo import unittest from test import support class MiscTestCase(unittest.TestCase): def test__all__(self): support.check__all__(self, foo) class OtherTestCase(unittest.TestCase): def test__all__(self): extra = {'BAR_CONST', 'FOO_CONST'} not_exported = {'baz'} # Undocumented name. # bar imports part of its API from _bar. support.check__all__(self, bar, ('bar', '_bar'), extra=extra, not_exported=not_exported) """ if name_of_module is None: name_of_module = (module.__name__, ) elif isinstance(name_of_module, str): name_of_module = (name_of_module, ) expected = set(extra) for name in dir(module): if name.startswith('_') or name in not_exported: continue obj = getattr(module, name) if (getattr(obj, '__module__', None) in name_of_module or (not hasattr(obj, '__module__') and not isinstance(obj, types.ModuleType))): expected.add(name) test_case.assertCountEqual(module.__all__, expected) def suppress_msvcrt_asserts(verbose=False): try: import msvcrt except ImportError: return msvcrt.SetErrorMode(msvcrt.SEM_FAILCRITICALERRORS | msvcrt.SEM_NOALIGNMENTFAULTEXCEPT | msvcrt.SEM_NOGPFAULTERRORBOX | msvcrt.SEM_NOOPENFILEERRORBOX) # CrtSetReportMode() is only available in debug build if hasattr(msvcrt, 'CrtSetReportMode'): for m in [msvcrt.CRT_WARN, msvcrt.CRT_ERROR, msvcrt.CRT_ASSERT]: if verbose: msvcrt.CrtSetReportMode(m, msvcrt.CRTDBG_MODE_FILE) msvcrt.CrtSetReportFile(m, msvcrt.CRTDBG_FILE_STDERR) else: msvcrt.CrtSetReportMode(m, 0) class SuppressCrashReport: """Try to prevent a crash report from popping up. On Windows, don't display the Windows Error Reporting dialog. On UNIX, disable the creation of coredump file. """ old_value = None old_modes = None def __enter__(self): """On Windows, disable Windows Error Reporting dialogs using SetErrorMode() and CrtSetReportMode(). On UNIX, try to save the previous core file size limit, then set soft limit to 0. """ if sys.platform.startswith('win'): # see http://msdn.microsoft.com/en-us/library/windows/desktop/ms680621.aspx try: import msvcrt except ImportError: return self.old_value = msvcrt.GetErrorMode() msvcrt.SetErrorMode(self.old_value | msvcrt.SEM_NOGPFAULTERRORBOX) # bpo-23314: Suppress assert dialogs in debug builds. # CrtSetReportMode() is only available in debug build. if hasattr(msvcrt, 'CrtSetReportMode'): self.old_modes = {} for report_type in [msvcrt.CRT_WARN, msvcrt.CRT_ERROR, msvcrt.CRT_ASSERT]: old_mode = msvcrt.CrtSetReportMode(report_type, msvcrt.CRTDBG_MODE_FILE) old_file = msvcrt.CrtSetReportFile(report_type, msvcrt.CRTDBG_FILE_STDERR) self.old_modes[report_type] = old_mode, old_file else: try: import resource self.resource = resource except ImportError: self.resource = None if self.resource is not None: try: self.old_value = self.resource.getrlimit(self.resource.RLIMIT_CORE) self.resource.setrlimit(self.resource.RLIMIT_CORE, (0, self.old_value[1])) except (ValueError, OSError): pass if sys.platform == 'darwin': import subprocess # Check if the 'Crash Reporter' on OSX was configured # in 'Developer' mode and warn that it will get triggered # when it is. # # This assumes that this context manager is used in tests # that might trigger the next manager. cmd = ['/usr/bin/defaults', 'read', 'com.apple.CrashReporter', 'DialogType'] proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) with proc: stdout = proc.communicate()[0] if stdout.strip() == b'developer': print("this test triggers the Crash Reporter, " "that is intentional", end='', flush=True) return self def __exit__(self, *ignore_exc): """Restore Windows ErrorMode or core file behavior to initial value.""" if self.old_value is None: return if sys.platform.startswith('win'): import msvcrt msvcrt.SetErrorMode(self.old_value) if self.old_modes: for report_type, (old_mode, old_file) in self.old_modes.items(): msvcrt.CrtSetReportMode(report_type, old_mode) msvcrt.CrtSetReportFile(report_type, old_file) else: if self.resource is not None: try: self.resource.setrlimit(self.resource.RLIMIT_CORE, self.old_value) except (ValueError, OSError): pass def patch(test_instance, object_to_patch, attr_name, new_value): """Override 'object_to_patch'.'attr_name' with 'new_value'. Also, add a cleanup procedure to 'test_instance' to restore 'object_to_patch' value for 'attr_name'. The 'attr_name' should be a valid attribute for 'object_to_patch'. """ # check that 'attr_name' is a real attribute for 'object_to_patch' # will raise AttributeError if it does not exist getattr(object_to_patch, attr_name) # keep a copy of the old value attr_is_local = False try: old_value = object_to_patch.__dict__[attr_name] except (AttributeError, KeyError): old_value = getattr(object_to_patch, attr_name, None) else: attr_is_local = True # restore the value when the test is done def cleanup(): if attr_is_local: setattr(object_to_patch, attr_name, old_value) else: delattr(object_to_patch, attr_name) test_instance.addCleanup(cleanup) # actually override the attribute setattr(object_to_patch, attr_name, new_value) @contextlib.contextmanager def patch_list(orig): """Like unittest.mock.patch.dict, but for lists.""" try: saved = orig[:] yield finally: orig[:] = saved def run_in_subinterp(code): """ Run code in a subinterpreter. Raise unittest.SkipTest if the tracemalloc module is enabled. """ _check_tracemalloc() import _testcapi return _testcapi.run_in_subinterp(code) def run_in_subinterp_with_config(code, *, own_gil=None, **config): """ Run code in a subinterpreter. Raise unittest.SkipTest if the tracemalloc module is enabled. """ _check_tracemalloc() import _testcapi if own_gil is not None: assert 'gil' not in config, (own_gil, config) config['gil'] = 2 if own_gil else 1 return _testcapi.run_in_subinterp_with_config(code, **config) def _check_tracemalloc(): # Issue #10915, #15751: PyGILState_*() functions don't work with # sub-interpreters, the tracemalloc module uses these functions internally try: import tracemalloc except ImportError: pass else: if tracemalloc.is_tracing(): raise unittest.SkipTest("run_in_subinterp() cannot be used " "if tracemalloc module is tracing " "memory allocations") def check_free_after_iterating(test, iter, cls, args=()): class A(cls): def __del__(self): nonlocal done done = True try: next(it) except StopIteration: pass done = False it = iter(A(*args)) # Issue 26494: Shouldn't crash test.assertRaises(StopIteration, next, it) # The sequence should be deallocated just after the end of iterating gc_collect() test.assertTrue(done) def missing_compiler_executable(cmd_names=[]): """Check if the compiler components used to build the interpreter exist. Check for the existence of the compiler executables whose names are listed in 'cmd_names' or all the compiler executables when 'cmd_names' is empty and return the first missing executable or None when none is found missing. """ from setuptools._distutils import ccompiler, sysconfig, spawn from setuptools import errors compiler = ccompiler.new_compiler() sysconfig.customize_compiler(compiler) if compiler.compiler_type == "msvc": # MSVC has no executables, so check whether initialization succeeds try: compiler.initialize() except errors.PlatformError: return "msvc" for name in compiler.executables: if cmd_names and name not in cmd_names: continue cmd = getattr(compiler, name) if cmd_names: assert cmd is not None, \ "the '%s' executable is not configured" % name elif not cmd: continue if spawn.find_executable(cmd[0]) is None: return cmd[0] _is_android_emulator = None def setswitchinterval(interval): # Setting a very low gil interval on the Android emulator causes python # to hang (issue #26939). minimum_interval = 1e-5 if is_android and interval < minimum_interval: global _is_android_emulator if _is_android_emulator is None: import subprocess _is_android_emulator = (subprocess.check_output( ['getprop', 'ro.kernel.qemu']).strip() == b'1') if _is_android_emulator: interval = minimum_interval return sys.setswitchinterval(interval) def get_pagesize(): """Get size of a page in bytes.""" try: page_size = os.sysconf('SC_PAGESIZE') except (ValueError, AttributeError): try: page_size = os.sysconf('SC_PAGE_SIZE') except (ValueError, AttributeError): page_size = 4096 return page_size @contextlib.contextmanager def disable_faulthandler(): import faulthandler # use sys.__stderr__ instead of sys.stderr, since regrtest replaces # sys.stderr with a StringIO which has no file descriptor when a test # is run with -W/--verbose3. fd = sys.__stderr__.fileno() is_enabled = faulthandler.is_enabled() try: faulthandler.disable() yield finally: if is_enabled: faulthandler.enable(file=fd, all_threads=True) class SaveSignals: """ Save and restore signal handlers. This class is only able to save/restore signal handlers registered by the Python signal module: see bpo-13285 for "external" signal handlers. """ def __init__(self): import signal self.signal = signal self.signals = signal.valid_signals() # SIGKILL and SIGSTOP signals cannot be ignored nor caught for signame in ('SIGKILL', 'SIGSTOP'): try: signum = getattr(signal, signame) except AttributeError: continue self.signals.remove(signum) self.handlers = {} def save(self): for signum in self.signals: handler = self.signal.getsignal(signum) if handler is None: # getsignal() returns None if a signal handler was not # registered by the Python signal module, # and the handler is not SIG_DFL nor SIG_IGN. # # Ignore the signal: we cannot restore the handler. continue self.handlers[signum] = handler def restore(self): for signum, handler in self.handlers.items(): self.signal.signal(signum, handler) def with_pymalloc(): import _testcapi return _testcapi.WITH_PYMALLOC class _ALWAYS_EQ: """ Object that is equal to anything. """ def __eq__(self, other): return True def __ne__(self, other): return False ALWAYS_EQ = _ALWAYS_EQ() class _NEVER_EQ: """ Object that is not equal to anything. """ def __eq__(self, other): return False def __ne__(self, other): return True def __hash__(self): return 1 NEVER_EQ = _NEVER_EQ() @functools.total_ordering class _LARGEST: """ Object that is greater than anything (except itself). """ def __eq__(self, other): return isinstance(other, _LARGEST) def __lt__(self, other): return False LARGEST = _LARGEST() @functools.total_ordering class _SMALLEST: """ Object that is less than anything (except itself). """ def __eq__(self, other): return isinstance(other, _SMALLEST) def __gt__(self, other): return False SMALLEST = _SMALLEST() def maybe_get_event_loop_policy(): """Return the global event loop policy if one is set, else return None.""" import asyncio.events return asyncio.events._event_loop_policy # Helpers for testing hashing. NHASHBITS = sys.hash_info.width # number of bits in hash() result assert NHASHBITS in (32, 64) # Return mean and sdev of number of collisions when tossing nballs balls # uniformly at random into nbins bins. By definition, the number of # collisions is the number of balls minus the number of occupied bins at # the end. def collision_stats(nbins, nballs): n, k = nbins, nballs # prob a bin empty after k trials = (1 - 1/n)**k # mean # empty is then n * (1 - 1/n)**k # so mean # occupied is n - n * (1 - 1/n)**k # so collisions = k - (n - n*(1 - 1/n)**k) # # For the variance: # n*(n-1)*(1-2/n)**k + meanempty - meanempty**2 = # n*(n-1)*(1-2/n)**k + meanempty * (1 - meanempty) # # Massive cancellation occurs, and, e.g., for a 64-bit hash code # 1-1/2**64 rounds uselessly to 1.0. Rather than make heroic (and # error-prone) efforts to rework the naive formulas to avoid those, # we use the `decimal` module to get plenty of extra precision. # # Note: the exact values are straightforward to compute with # rationals, but in context that's unbearably slow, requiring # multi-million bit arithmetic. import decimal with decimal.localcontext() as ctx: bits = n.bit_length() * 2 # bits in n**2 # At least that many bits will likely cancel out. # Use that many decimal digits instead. ctx.prec = max(bits, 30) dn = decimal.Decimal(n) p1empty = ((dn - 1) / dn) ** k meanempty = n * p1empty occupied = n - meanempty collisions = k - occupied var = dn*(dn-1)*((dn-2)/dn)**k + meanempty * (1 - meanempty) return float(collisions), float(var.sqrt()) class catch_unraisable_exception: """ Context manager catching unraisable exception using sys.unraisablehook. Storing the exception value (cm.unraisable.exc_value) creates a reference cycle. The reference cycle is broken explicitly when the context manager exits. Storing the object (cm.unraisable.object) can resurrect it if it is set to an object which is being finalized. Exiting the context manager clears the stored object. Usage: with support.catch_unraisable_exception() as cm: # code creating an "unraisable exception" ... # check the unraisable exception: use cm.unraisable ... # cm.unraisable attribute no longer exists at this point # (to break a reference cycle) """ def __init__(self): self.unraisable = None self._old_hook = None def _hook(self, unraisable): # Storing unraisable.object can resurrect an object which is being # finalized. Storing unraisable.exc_value creates a reference cycle. self.unraisable = unraisable def __enter__(self): self._old_hook = sys.unraisablehook sys.unraisablehook = self._hook return self def __exit__(self, *exc_info): sys.unraisablehook = self._old_hook del self.unraisable def wait_process(pid, *, exitcode, timeout=None): """ Wait until process pid completes and check that the process exit code is exitcode. Raise an AssertionError if the process exit code is not equal to exitcode. If the process runs longer than timeout seconds (LONG_TIMEOUT by default), kill the process (if signal.SIGKILL is available) and raise an AssertionError. The timeout feature is not available on Windows. """ if os.name != "nt": import signal if timeout is None: timeout = LONG_TIMEOUT start_time = time.monotonic() for _ in sleeping_retry(timeout, error=False): pid2, status = os.waitpid(pid, os.WNOHANG) if pid2 != 0: break # rety: the process is still running else: try: os.kill(pid, signal.SIGKILL) os.waitpid(pid, 0) except OSError: # Ignore errors like ChildProcessError or PermissionError pass dt = time.monotonic() - start_time raise AssertionError(f"process {pid} is still running " f"after {dt:.1f} seconds") else: # Windows implementation: don't support timeout :-( pid2, status = os.waitpid(pid, 0) exitcode2 = os.waitstatus_to_exitcode(status) if exitcode2 != exitcode: raise AssertionError(f"process {pid} exited with code {exitcode2}, " f"but exit code {exitcode} is expected") # sanity check: it should not fail in practice if pid2 != pid: raise AssertionError(f"pid {pid2} != pid {pid}") def skip_if_broken_multiprocessing_synchronize(): """ Skip tests if the multiprocessing.synchronize module is missing, if there is no available semaphore implementation, or if creating a lock raises an OSError (on Linux only). """ from .import_helper import import_module # Skip tests if the _multiprocessing extension is missing. import_module('_multiprocessing') # Skip tests if there is no available semaphore implementation: # multiprocessing.synchronize requires _multiprocessing.SemLock. synchronize = import_module('multiprocessing.synchronize') if sys.platform == "linux": try: # bpo-38377: On Linux, creating a semaphore fails with OSError # if the current user does not have the permission to create # a file in /dev/shm/ directory. synchronize.Lock(ctx=None) except OSError as exc: raise unittest.SkipTest(f"broken multiprocessing SemLock: {exc!r}") def check_disallow_instantiation(testcase, tp, *args, **kwds): """ Check that given type cannot be instantiated using *args and **kwds. See bpo-43916: Add Py_TPFLAGS_DISALLOW_INSTANTIATION type flag. """ mod = tp.__module__ name = tp.__name__ if mod != 'builtins': qualname = f"{mod}.{name}" else: qualname = f"{name}" msg = f"cannot create '{re.escape(qualname)}' instances" testcase.assertRaisesRegex(TypeError, msg, tp, *args, **kwds) def get_recursion_depth(): """Get the recursion depth of the caller function. In the __main__ module, at the module level, it should be 1. """ try: import _testinternalcapi depth = _testinternalcapi.get_recursion_depth() except (ImportError, RecursionError, AttributeError) as exc: # sys._getframe() + frame.f_back implementation. try: depth = 0 frame = sys._getframe() while frame is not None: depth += 1 frame = frame.f_back finally: # Break any reference cycles. frame = None # Ignore get_recursion_depth() frame. return max(depth - 1, 1) def get_recursion_available(): """Get the number of available frames before RecursionError. It depends on the current recursion depth of the caller function and sys.getrecursionlimit(). """ limit = sys.getrecursionlimit() depth = get_recursion_depth() return limit - depth @contextlib.contextmanager def set_recursion_limit(limit): """Temporarily change the recursion limit.""" original_limit = sys.getrecursionlimit() try: sys.setrecursionlimit(limit) yield finally: sys.setrecursionlimit(original_limit) def infinite_recursion(max_depth=None): if max_depth is None: # Pick a number large enough to cause problems # but not take too long for code that can handle # very deep recursion. max_depth = 20_000 elif max_depth < 3: raise ValueError("max_depth must be at least 3, got {max_depth}") depth = get_recursion_depth() depth = max(depth - 1, 1) # Ignore infinite_recursion() frame. limit = depth + max_depth return set_recursion_limit(limit) def ignore_deprecations_from(module, *, like): token = object() warnings.filterwarnings( "ignore", category=DeprecationWarning, module=module, message=like + fr"(?#support{id(token)})", ) return token def clear_ignored_deprecations(*tokens): if not tokens: raise ValueError("Provide token or tokens returned by ignore_deprecations_from") new_filters = [] endswith = tuple(rf"(?#support{id(token)})" for token in tokens) for action, message, category, module, lineno in warnings.filters: if action == "ignore" and category is DeprecationWarning: if isinstance(message, re.Pattern): msg = message.pattern else: msg = message or "" if msg.endswith(endswith): continue new_filters.append((action, message, category, module, lineno)) if warnings.filters != new_filters: warnings.filters[:] = new_filters warnings._filters_mutated() # Skip a test if venv with pip is known to not work. def requires_venv_with_pip(): # ensurepip requires zlib to open ZIP archives (.whl binary wheel packages) try: import zlib except ImportError: return unittest.skipIf(True, "venv: ensurepip requires zlib") # bpo-26610: pip/pep425tags.py requires ctypes. # gh-92820: setuptools/windows_support.py uses ctypes (setuptools 58.1). try: import ctypes except ImportError: ctypes = None return unittest.skipUnless(ctypes, 'venv: pip requires ctypes') # @functools.cache def _findwheel(pkgname): """Try to find a wheel with the package specified as pkgname. If set, the wheels are searched for in WHEEL_PKG_DIR (see ensurepip). Otherwise, they are searched for in the test directory. """ wheel_dir = sysconfig.get_config_var('WHEEL_PKG_DIR') or os.path.join( TEST_HOME_DIR, 'wheeldata', ) filenames = os.listdir(wheel_dir) filenames = sorted(filenames, reverse=True) # approximate "newest" first for filename in filenames: # filename is like 'setuptools-67.6.1-py3-none-any.whl' if not filename.endswith(".whl"): continue prefix = pkgname + '-' if filename.startswith(prefix): return os.path.join(wheel_dir, filename) raise FileNotFoundError(f"No wheel for {pkgname} found in {wheel_dir}") # Context manager that creates a virtual environment, install setuptools and wheel in it # and returns the path to the venv directory and the path to the python executable @contextlib.contextmanager def setup_venv_with_pip_setuptools_wheel(venv_dir): import subprocess from .os_helper import temp_cwd with temp_cwd() as temp_dir: # Create virtual environment to get setuptools cmd = [sys.executable, '-X', 'dev', '-m', 'venv', venv_dir] if verbose: print() print('Run:', ' '.join(cmd)) subprocess.run(cmd, check=True) venv = os.path.join(temp_dir, venv_dir) # Get the Python executable of the venv python_exe = os.path.basename(sys.executable) if sys.platform == 'win32': python = os.path.join(venv, 'Scripts', python_exe) else: python = os.path.join(venv, 'bin', python_exe) cmd = [python, '-X', 'dev', '-m', 'pip', 'install', _findwheel('setuptools'), _findwheel('wheel')] if verbose: print() print('Run:', ' '.join(cmd)) subprocess.run(cmd, check=True) yield python # True if Python is built with the Py_DEBUG macro defined: if # Python is built in debug mode (./configure --with-pydebug). Py_DEBUG = hasattr(sys, 'gettotalrefcount') def late_deletion(obj): """ Keep a Python alive as long as possible. Create a reference cycle and store the cycle in an object deleted late in Python finalization. Try to keep the object alive until the very last garbage collection. The function keeps a strong reference by design. It should be called in a subprocess to not mark a test as "leaking a reference". """ # Late CPython finalization: # - finalize_interp_clear() # - _PyInterpreterState_Clear(): Clear PyInterpreterState members # (ex: codec_search_path, before_forkers) # - clear os.register_at_fork() callbacks # - clear codecs.register() callbacks ref_cycle = [obj] ref_cycle.append(ref_cycle) # Store a reference in PyInterpreterState.codec_search_path import codecs def search_func(encoding): return None search_func.reference = ref_cycle codecs.register(search_func) if hasattr(os, 'register_at_fork'): # Store a reference in PyInterpreterState.before_forkers def atfork_func(): pass atfork_func.reference = ref_cycle os.register_at_fork(before=atfork_func) def busy_retry(timeout, err_msg=None, /, *, error=True): """ Run the loop body until "break" stops the loop. After *timeout* seconds, raise an AssertionError if *error* is true, or just stop if *error is false. Example: for _ in support.busy_retry(support.SHORT_TIMEOUT): if check(): break Example of error=False usage: for _ in support.busy_retry(support.SHORT_TIMEOUT, error=False): if check(): break else: raise RuntimeError('my custom error') """ if timeout <= 0: raise ValueError("timeout must be greater than zero") start_time = time.monotonic() deadline = start_time + timeout while True: yield if time.monotonic() >= deadline: break if error: dt = time.monotonic() - start_time msg = f"timeout ({dt:.1f} seconds)" if err_msg: msg = f"{msg}: {err_msg}" raise AssertionError(msg) def sleeping_retry(timeout, err_msg=None, /, *, init_delay=0.010, max_delay=1.0, error=True): """ Wait strategy that applies exponential backoff. Run the loop body until "break" stops the loop. Sleep at each loop iteration, but not at the first iteration. The sleep delay is doubled at each iteration (up to *max_delay* seconds). See busy_retry() documentation for the parameters usage. Example raising an exception after SHORT_TIMEOUT seconds: for _ in support.sleeping_retry(support.SHORT_TIMEOUT): if check(): break Example of error=False usage: for _ in support.sleeping_retry(support.SHORT_TIMEOUT, error=False): if check(): break else: raise RuntimeError('my custom error') """ delay = init_delay for _ in busy_retry(timeout, err_msg, error=error): yield time.sleep(delay) delay = min(delay * 2, max_delay) @contextlib.contextmanager def adjust_int_max_str_digits(max_digits): """Temporarily change the integer string conversion length limit.""" current = sys.get_int_max_str_digits() try: sys.set_int_max_str_digits(max_digits) yield finally: sys.set_int_max_str_digits(current) #For recursion tests, easily exceeds default recursion limit EXCEEDS_RECURSION_LIMIT = 5000 # The default C recursion limit (from Include/cpython/pystate.h). if Py_DEBUG: if is_wasi: C_RECURSION_LIMIT = 150 else: C_RECURSION_LIMIT = 500 else: if is_wasi: C_RECURSION_LIMIT = 500 elif hasattr(os, 'uname') and os.uname().machine == 's390x': C_RECURSION_LIMIT = 800 elif sys.platform.startswith('win'): C_RECURSION_LIMIT = 3000 elif check_sanitizer(address=True): C_RECURSION_LIMIT = 4000 else: C_RECURSION_LIMIT = 10000 #Windows doesn't have os.uname() but it doesn't support s390x. skip_on_s390x = unittest.skipIf(hasattr(os, 'uname') and os.uname().machine == 's390x', 'skipped on s390x') _BASE_COPY_SRC_DIR_IGNORED_NAMES = frozenset({ # SRC_DIR/.git '.git', # ignore all __pycache__/ sub-directories '__pycache__', }) # Ignore function for shutil.copytree() to copy the Python source code. def copy_python_src_ignore(path, names): ignored = _BASE_COPY_SRC_DIR_IGNORED_NAMES if os.path.basename(path) == 'Doc': ignored |= { # SRC_DIR/Doc/build/ 'build', # SRC_DIR/Doc/venv/ 'venv', } # check if we are at the root of the source code elif 'Modules' in names: ignored |= { # SRC_DIR/build/ 'build', } return ignored def iter_builtin_types(): for obj in __builtins__.values(): if not isinstance(obj, type): continue cls = obj if cls.__module__ != 'builtins': continue yield cls def iter_slot_wrappers(cls): assert cls.__module__ == 'builtins', cls def is_slot_wrapper(name, value): if not isinstance(value, types.WrapperDescriptorType): assert not repr(value).startswith('= 2: # Different kinds of characters from various languages to minimize the # probability that the whole name is encodable to MBCS (issue #9819) TESTFN_UNENCODABLE = TESTFN_ASCII + "-\u5171\u0141\u2661\u0363\uDC80" try: TESTFN_UNENCODABLE.encode(sys.getfilesystemencoding()) except UnicodeEncodeError: pass else: print('WARNING: The filename %r CAN be encoded by the filesystem ' 'encoding (%s). Unicode filename tests may not be effective' % (TESTFN_UNENCODABLE, sys.getfilesystemencoding())) TESTFN_UNENCODABLE = None # macOS and Emscripten deny unencodable filenames (invalid utf-8) elif sys.platform not in {'darwin', 'emscripten', 'wasi'}: try: # ascii and utf-8 cannot encode the byte 0xff b'\xff'.decode(sys.getfilesystemencoding()) except UnicodeDecodeError: # 0xff will be encoded using the surrogate character u+DCFF TESTFN_UNENCODABLE = TESTFN_ASCII \ + b'-\xff'.decode(sys.getfilesystemencoding(), 'surrogateescape') else: # File system encoding (eg. ISO-8859-* encodings) can encode # the byte 0xff. Skip some unicode filename tests. pass # FS_NONASCII: non-ASCII character encodable by os.fsencode(), # or an empty string if there is no such character. FS_NONASCII = '' for character in ( # First try printable and common characters to have a readable filename. # For each character, the encoding list are just example of encodings able # to encode the character (the list is not exhaustive). # U+00E6 (Latin Small Letter Ae): cp1252, iso-8859-1 '\u00E6', # U+0130 (Latin Capital Letter I With Dot Above): cp1254, iso8859_3 '\u0130', # U+0141 (Latin Capital Letter L With Stroke): cp1250, cp1257 '\u0141', # U+03C6 (Greek Small Letter Phi): cp1253 '\u03C6', # U+041A (Cyrillic Capital Letter Ka): cp1251 '\u041A', # U+05D0 (Hebrew Letter Alef): Encodable to cp424 '\u05D0', # U+060C (Arabic Comma): cp864, cp1006, iso8859_6, mac_arabic '\u060C', # U+062A (Arabic Letter Teh): cp720 '\u062A', # U+0E01 (Thai Character Ko Kai): cp874 '\u0E01', # Then try more "special" characters. "special" because they may be # interpreted or displayed differently depending on the exact locale # encoding and the font. # U+00A0 (No-Break Space) '\u00A0', # U+20AC (Euro Sign) '\u20AC', ): try: # If Python is set up to use the legacy 'mbcs' in Windows, # 'replace' error mode is used, and encode() returns b'?' # for characters missing in the ANSI codepage if os.fsdecode(os.fsencode(character)) != character: raise UnicodeError except UnicodeError: pass else: FS_NONASCII = character break # Save the initial cwd SAVEDCWD = os.getcwd() # TESTFN_UNDECODABLE is a filename (bytes type) that should *not* be able to be # decoded from the filesystem encoding (in strict mode). It can be None if we # cannot generate such filename (ex: the latin1 encoding can decode any byte # sequence). On UNIX, TESTFN_UNDECODABLE can be decoded by os.fsdecode() thanks # to the surrogateescape error handler (PEP 383), but not from the filesystem # encoding in strict mode. TESTFN_UNDECODABLE = None for name in ( # b'\xff' is not decodable by os.fsdecode() with code page 932. Windows # accepts it to create a file or a directory, or don't accept to enter to # such directory (when the bytes name is used). So test b'\xe7' first: # it is not decodable from cp932. b'\xe7w\xf0', # undecodable from ASCII, UTF-8 b'\xff', # undecodable from iso8859-3, iso8859-6, iso8859-7, cp424, iso8859-8, cp856 # and cp857 b'\xae\xd5' # undecodable from UTF-8 (UNIX and Mac OS X) b'\xed\xb2\x80', b'\xed\xb4\x80', # undecodable from shift_jis, cp869, cp874, cp932, cp1250, cp1251, cp1252, # cp1253, cp1254, cp1255, cp1257, cp1258 b'\x81\x98', ): try: name.decode(sys.getfilesystemencoding()) except UnicodeDecodeError: try: name.decode(sys.getfilesystemencoding(), sys.getfilesystemencodeerrors()) except UnicodeDecodeError: continue TESTFN_UNDECODABLE = os.fsencode(TESTFN_ASCII) + name break if FS_NONASCII: TESTFN_NONASCII = TESTFN_ASCII + FS_NONASCII else: TESTFN_NONASCII = None TESTFN = TESTFN_NONASCII or TESTFN_ASCII def make_bad_fd(): """ Create an invalid file descriptor by opening and closing a file and return its fd. """ file = open(TESTFN, "wb") try: return file.fileno() finally: file.close() unlink(TESTFN) _can_symlink = None def can_symlink(): global _can_symlink if _can_symlink is not None: return _can_symlink # WASI / wasmtime prevents symlinks with absolute paths, see man # openat2(2) RESOLVE_BENEATH. Almost all symlink tests use absolute # paths. Skip symlink tests on WASI for now. src = os.path.abspath(TESTFN) symlink_path = src + "can_symlink" try: os.symlink(src, symlink_path) can = True except (OSError, NotImplementedError, AttributeError): can = False else: os.remove(symlink_path) _can_symlink = can return can def skip_unless_symlink(test): """Skip decorator for tests that require functional symlink""" ok = can_symlink() msg = "Requires functional symlink implementation" return test if ok else unittest.skip(msg)(test) _can_xattr = None def can_xattr(): import tempfile global _can_xattr if _can_xattr is not None: return _can_xattr if not hasattr(os, "setxattr"): can = False else: import platform tmp_dir = tempfile.mkdtemp() tmp_fp, tmp_name = tempfile.mkstemp(dir=tmp_dir) try: with open(TESTFN, "wb") as fp: try: # TESTFN & tempfile may use different file systems with # different capabilities os.setxattr(tmp_fp, b"user.test", b"") os.setxattr(tmp_name, b"trusted.foo", b"42") os.setxattr(fp.fileno(), b"user.test", b"") # Kernels < 2.6.39 don't respect setxattr flags. kernel_version = platform.release() m = re.match(r"2.6.(\d{1,2})", kernel_version) can = m is None or int(m.group(1)) >= 39 except OSError: can = False finally: unlink(TESTFN) unlink(tmp_name) rmdir(tmp_dir) _can_xattr = can return can def skip_unless_xattr(test): """Skip decorator for tests that require functional extended attributes""" ok = can_xattr() msg = "no non-broken extended attribute support" return test if ok else unittest.skip(msg)(test) _can_chmod = None def can_chmod(): global _can_chmod if _can_chmod is not None: return _can_chmod if not hasattr(os, "chmod"): _can_chmod = False return _can_chmod try: with open(TESTFN, "wb") as f: try: os.chmod(TESTFN, 0o555) mode1 = os.stat(TESTFN).st_mode os.chmod(TESTFN, 0o777) mode2 = os.stat(TESTFN).st_mode except OSError as e: can = False else: can = stat.S_IMODE(mode1) != stat.S_IMODE(mode2) finally: unlink(TESTFN) _can_chmod = can return can def skip_unless_working_chmod(test): """Skip tests that require working os.chmod() WASI SDK 15.0 cannot change file mode bits. """ ok = can_chmod() msg = "requires working os.chmod()" return test if ok else unittest.skip(msg)(test) # Check whether the current effective user has the capability to override # DAC (discretionary access control). Typically user root is able to # bypass file read, write, and execute permission checks. The capability # is independent of the effective user. See capabilities(7). _can_dac_override = None def can_dac_override(): global _can_dac_override if not can_chmod(): _can_dac_override = False if _can_dac_override is not None: return _can_dac_override try: with open(TESTFN, "wb") as f: os.chmod(TESTFN, 0o400) try: with open(TESTFN, "wb"): pass except OSError: _can_dac_override = False else: _can_dac_override = True finally: try: os.chmod(TESTFN, 0o700) except OSError: pass unlink(TESTFN) return _can_dac_override def skip_if_dac_override(test): ok = not can_dac_override() msg = "incompatible with CAP_DAC_OVERRIDE" return test if ok else unittest.skip(msg)(test) def skip_unless_dac_override(test): ok = can_dac_override() msg = "requires CAP_DAC_OVERRIDE" return test if ok else unittest.skip(msg)(test) def unlink(filename): try: _unlink(filename) except (FileNotFoundError, NotADirectoryError): pass if sys.platform.startswith("win"): def _waitfor(func, pathname, waitall=False): # Perform the operation func(pathname) # Now setup the wait loop if waitall: dirname = pathname else: dirname, name = os.path.split(pathname) dirname = dirname or '.' # Check for `pathname` to be removed from the filesystem. # The exponential backoff of the timeout amounts to a total # of ~1 second after which the deletion is probably an error # anyway. # Testing on an i7@4.3GHz shows that usually only 1 iteration is # required when contention occurs. timeout = 0.001 while timeout < 1.0: # Note we are only testing for the existence of the file(s) in # the contents of the directory regardless of any security or # access rights. If we have made it this far, we have sufficient # permissions to do that much using Python's equivalent of the # Windows API FindFirstFile. # Other Windows APIs can fail or give incorrect results when # dealing with files that are pending deletion. L = os.listdir(dirname) if not (L if waitall else name in L): return # Increase the timeout and try again time.sleep(timeout) timeout *= 2 warnings.warn('tests may fail, delete still pending for ' + pathname, RuntimeWarning, stacklevel=4) def _unlink(filename): _waitfor(os.unlink, filename) def _rmdir(dirname): _waitfor(os.rmdir, dirname) def _rmtree(path): from ..support import _force_run def _rmtree_inner(path): for name in _force_run(path, os.listdir, path): fullname = os.path.join(path, name) try: mode = os.lstat(fullname).st_mode except OSError as exc: print("support.rmtree(): os.lstat(%r) failed with %s" % (fullname, exc), file=sys.__stderr__) mode = 0 if stat.S_ISDIR(mode): _waitfor(_rmtree_inner, fullname, waitall=True) _force_run(fullname, os.rmdir, fullname) else: _force_run(fullname, os.unlink, fullname) _waitfor(_rmtree_inner, path, waitall=True) _waitfor(lambda p: _force_run(p, os.rmdir, p), path) def _longpath(path): try: import ctypes except ImportError: # No ctypes means we can't expands paths. pass else: buffer = ctypes.create_unicode_buffer(len(path) * 2) length = ctypes.windll.kernel32.GetLongPathNameW(path, buffer, len(buffer)) if length: return buffer[:length] return path else: _unlink = os.unlink _rmdir = os.rmdir def _rmtree(path): import shutil try: shutil.rmtree(path) return except OSError: pass def _rmtree_inner(path): from ..support import _force_run for name in _force_run(path, os.listdir, path): fullname = os.path.join(path, name) try: mode = os.lstat(fullname).st_mode except OSError: mode = 0 if stat.S_ISDIR(mode): _rmtree_inner(fullname) _force_run(path, os.rmdir, fullname) else: _force_run(path, os.unlink, fullname) _rmtree_inner(path) os.rmdir(path) def _longpath(path): return path def rmdir(dirname): try: _rmdir(dirname) except FileNotFoundError: pass def rmtree(path): try: _rmtree(path) except FileNotFoundError: pass @contextlib.contextmanager def temp_dir(path=None, quiet=False): """Return a context manager that creates a temporary directory. Arguments: path: the directory to create temporarily. If omitted or None, defaults to creating a temporary directory using tempfile.mkdtemp. quiet: if False (the default), the context manager raises an exception on error. Otherwise, if the path is specified and cannot be created, only a warning is issued. """ import tempfile dir_created = False if path is None: path = tempfile.mkdtemp() dir_created = True path = os.path.realpath(path) else: try: os.mkdir(path) dir_created = True except OSError as exc: if not quiet: raise warnings.warn(f'tests may fail, unable to create ' f'temporary directory {path!r}: {exc}', RuntimeWarning, stacklevel=3) if dir_created: pid = os.getpid() try: yield path finally: # In case the process forks, let only the parent remove the # directory. The child has a different process id. (bpo-30028) if dir_created and pid == os.getpid(): rmtree(path) @contextlib.contextmanager def change_cwd(path, quiet=False): """Return a context manager that changes the current working directory. Arguments: path: the directory to use as the temporary current working directory. quiet: if False (the default), the context manager raises an exception on error. Otherwise, it issues only a warning and keeps the current working directory the same. """ saved_dir = os.getcwd() try: os.chdir(os.path.realpath(path)) except OSError as exc: if not quiet: raise warnings.warn(f'tests may fail, unable to change the current working ' f'directory to {path!r}: {exc}', RuntimeWarning, stacklevel=3) try: yield os.getcwd() finally: os.chdir(saved_dir) @contextlib.contextmanager def temp_cwd(name='tempcwd', quiet=False): """ Context manager that temporarily creates and changes the CWD. The function temporarily changes the current working directory after creating a temporary directory in the current directory with name *name*. If *name* is None, the temporary directory is created using tempfile.mkdtemp. If *quiet* is False (default) and it is not possible to create or change the CWD, an error is raised. If *quiet* is True, only a warning is raised and the original CWD is used. """ with temp_dir(path=name, quiet=quiet) as temp_path: with change_cwd(temp_path, quiet=quiet) as cwd_dir: yield cwd_dir def create_empty_file(filename): """Create an empty file. If the file already exists, truncate it.""" fd = os.open(filename, os.O_WRONLY | os.O_CREAT | os.O_TRUNC) os.close(fd) @contextlib.contextmanager def open_dir_fd(path): """Open a file descriptor to a directory.""" assert os.path.isdir(path) flags = os.O_RDONLY if hasattr(os, "O_DIRECTORY"): flags |= os.O_DIRECTORY dir_fd = os.open(path, flags) try: yield dir_fd finally: os.close(dir_fd) def fs_is_case_insensitive(directory): """Detects if the file system for the specified directory is case-insensitive.""" import tempfile with tempfile.NamedTemporaryFile(dir=directory) as base: base_path = base.name case_path = base_path.upper() if case_path == base_path: case_path = base_path.lower() try: return os.path.samefile(base_path, case_path) except FileNotFoundError: return False class FakePath: """Simple implementation of the path protocol. """ def __init__(self, path): self.path = path def __repr__(self): return f'' def __fspath__(self): if (isinstance(self.path, BaseException) or isinstance(self.path, type) and issubclass(self.path, BaseException)): raise self.path else: return self.path def fd_count(): """Count the number of open file descriptors. """ if sys.platform.startswith(('linux', 'freebsd', 'emscripten')): fd_path = "/proc/self/fd" elif sys.platform == "darwin": fd_path = "/dev/fd" else: fd_path = None if fd_path is not None: try: names = os.listdir(fd_path) # Subtract one because listdir() internally opens a file # descriptor to list the content of the directory. return len(names) - 1 except FileNotFoundError: pass MAXFD = 256 if hasattr(os, 'sysconf'): try: MAXFD = os.sysconf("SC_OPEN_MAX") except OSError: pass old_modes = None if sys.platform == 'win32': # bpo-25306, bpo-31009: Call CrtSetReportMode() to not kill the process # on invalid file descriptor if Python is compiled in debug mode try: import msvcrt msvcrt.CrtSetReportMode except (AttributeError, ImportError): # no msvcrt or a release build pass else: old_modes = {} for report_type in (msvcrt.CRT_WARN, msvcrt.CRT_ERROR, msvcrt.CRT_ASSERT): old_modes[report_type] = msvcrt.CrtSetReportMode(report_type, 0) try: count = 0 for fd in range(MAXFD): try: # Prefer dup() over fstat(). fstat() can require input/output # whereas dup() doesn't. fd2 = os.dup(fd) except OSError as e: if e.errno != errno.EBADF: raise else: os.close(fd2) count += 1 finally: if old_modes is not None: for report_type in (msvcrt.CRT_WARN, msvcrt.CRT_ERROR, msvcrt.CRT_ASSERT): msvcrt.CrtSetReportMode(report_type, old_modes[report_type]) return count if hasattr(os, "umask"): @contextlib.contextmanager def temp_umask(umask): """Context manager that temporarily sets the process umask.""" oldmask = os.umask(umask) try: yield finally: os.umask(oldmask) else: @contextlib.contextmanager def temp_umask(umask): """no-op on platforms without umask()""" yield class EnvironmentVarGuard(collections.abc.MutableMapping): """Class to help protect the environment variable properly. Can be used as a context manager.""" def __init__(self): self._environ = os.environ self._changed = {} def __getitem__(self, envvar): return self._environ[envvar] def __setitem__(self, envvar, value): # Remember the initial value on the first access if envvar not in self._changed: self._changed[envvar] = self._environ.get(envvar) self._environ[envvar] = value def __delitem__(self, envvar): # Remember the initial value on the first access if envvar not in self._changed: self._changed[envvar] = self._environ.get(envvar) if envvar in self._environ: del self._environ[envvar] def keys(self): return self._environ.keys() def __iter__(self): return iter(self._environ) def __len__(self): return len(self._environ) def set(self, envvar, value): self[envvar] = value def unset(self, envvar): del self[envvar] def copy(self): # We do what os.environ.copy() does. return dict(self) def __enter__(self): return self def __exit__(self, *ignore_exc): for (k, v) in self._changed.items(): if v is None: if k in self._environ: del self._environ[k] else: self._environ[k] = v os.environ = self._environ try: if support.MS_WINDOWS: import ctypes kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) ERROR_FILE_NOT_FOUND = 2 DDD_REMOVE_DEFINITION = 2 DDD_EXACT_MATCH_ON_REMOVE = 4 DDD_NO_BROADCAST_SYSTEM = 8 else: raise AttributeError except (ImportError, AttributeError): def subst_drive(path): raise unittest.SkipTest('ctypes or kernel32 is not available') else: @contextlib.contextmanager def subst_drive(path): """Temporarily yield a substitute drive for a given path.""" for c in reversed(string.ascii_uppercase): drive = f'{c}:' if (not kernel32.QueryDosDeviceW(drive, None, 0) and ctypes.get_last_error() == ERROR_FILE_NOT_FOUND): break else: raise unittest.SkipTest('no available logical drive') if not kernel32.DefineDosDeviceW( DDD_NO_BROADCAST_SYSTEM, drive, path): raise ctypes.WinError(ctypes.get_last_error()) try: yield drive finally: if not kernel32.DefineDosDeviceW( DDD_REMOVE_DEFINITION | DDD_EXACT_MATCH_ON_REMOVE, drive, path): raise ctypes.WinError(ctypes.get_last_error()) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729876127.0 et_xmlfile-2.0.0/et_xmlfile/tests/_vendor/test/support/warnings_helper.py0000644000000000000000000001527714706750237024000 0ustar00import contextlib import functools import importlib import re import sys import warnings def import_deprecated(name): """Import *name* while suppressing DeprecationWarning.""" with warnings.catch_warnings(): warnings.simplefilter('ignore', category=DeprecationWarning) return importlib.import_module(name) def check_syntax_warning(testcase, statement, errtext='', *, lineno=1, offset=None): # Test also that a warning is emitted only once. from ..support import check_syntax_error with warnings.catch_warnings(record=True) as warns: warnings.simplefilter('always', SyntaxWarning) compile(statement, '', 'exec') testcase.assertEqual(len(warns), 1, warns) warn, = warns testcase.assertTrue(issubclass(warn.category, SyntaxWarning), warn.category) if errtext: testcase.assertRegex(str(warn.message), errtext) testcase.assertEqual(warn.filename, '') testcase.assertIsNotNone(warn.lineno) if lineno is not None: testcase.assertEqual(warn.lineno, lineno) # SyntaxWarning should be converted to SyntaxError when raised, # since the latter contains more information and provides better # error report. with warnings.catch_warnings(record=True) as warns: warnings.simplefilter('error', SyntaxWarning) check_syntax_error(testcase, statement, errtext, lineno=lineno, offset=offset) # No warnings are leaked when a SyntaxError is raised. testcase.assertEqual(warns, []) def ignore_warnings(*, category): """Decorator to suppress warnings. Use of context managers to hide warnings make diffs more noisy and tools like 'git blame' less useful. """ def decorator(test): @functools.wraps(test) def wrapper(self, *args, **kwargs): with warnings.catch_warnings(): warnings.simplefilter('ignore', category=category) return test(self, *args, **kwargs) return wrapper return decorator class WarningsRecorder(object): """Convenience wrapper for the warnings list returned on entry to the warnings.catch_warnings() context manager. """ def __init__(self, warnings_list): self._warnings = warnings_list self._last = 0 def __getattr__(self, attr): if len(self._warnings) > self._last: return getattr(self._warnings[-1], attr) elif attr in warnings.WarningMessage._WARNING_DETAILS: return None raise AttributeError("%r has no attribute %r" % (self, attr)) @property def warnings(self): return self._warnings[self._last:] def reset(self): self._last = len(self._warnings) @contextlib.contextmanager def check_warnings(*filters, **kwargs): """Context manager to silence warnings. Accept 2-tuples as positional arguments: ("message regexp", WarningCategory) Optional argument: - if 'quiet' is True, it does not fail if a filter catches nothing (default True without argument, default False if some filters are defined) Without argument, it defaults to: check_warnings(("", Warning), quiet=True) """ quiet = kwargs.get('quiet') if not filters: filters = (("", Warning),) # Preserve backward compatibility if quiet is None: quiet = True return _filterwarnings(filters, quiet) @contextlib.contextmanager def check_no_warnings(testcase, message='', category=Warning, force_gc=False): """Context manager to check that no warnings are emitted. This context manager enables a given warning within its scope and checks that no warnings are emitted even with that warning enabled. If force_gc is True, a garbage collection is attempted before checking for warnings. This may help to catch warnings emitted when objects are deleted, such as ResourceWarning. Other keyword arguments are passed to warnings.filterwarnings(). """ from ..support import gc_collect with warnings.catch_warnings(record=True) as warns: warnings.filterwarnings('always', message=message, category=category) yield if force_gc: gc_collect() testcase.assertEqual(warns, []) @contextlib.contextmanager def check_no_resource_warning(testcase): """Context manager to check that no ResourceWarning is emitted. Usage: with check_no_resource_warning(self): f = open(...) ... del f You must remove the object which may emit ResourceWarning before the end of the context manager. """ with check_no_warnings(testcase, category=ResourceWarning, force_gc=True): yield def _filterwarnings(filters, quiet=False): """Catch the warnings, then check if all the expected warnings have been raised and re-raise unexpected warnings. If 'quiet' is True, only re-raise the unexpected warnings. """ # Clear the warning registry of the calling module # in order to re-raise the warnings. frame = sys._getframe(2) registry = frame.f_globals.get('__warningregistry__') if registry: registry.clear() with warnings.catch_warnings(record=True) as w: # Set filter "always" to record all warnings. Because # test_warnings swap the module, we need to look up in # the sys.modules dictionary. sys.modules['warnings'].simplefilter("always") yield WarningsRecorder(w) # Filter the recorded warnings reraise = list(w) missing = [] for msg, cat in filters: seen = False for w in reraise[:]: warning = w.message # Filter out the matching messages if (re.match(msg, str(warning), re.I) and issubclass(warning.__class__, cat)): seen = True reraise.remove(w) if not seen and not quiet: # This filter caught nothing missing.append((msg, cat.__name__)) if reraise: raise AssertionError("unhandled warning %s" % reraise[0]) if missing: raise AssertionError("filter (%r, %s) did not catch any warning" % missing[0]) @contextlib.contextmanager def save_restore_warnings_filters(): old_filters = warnings.filters[:] try: yield finally: warnings.filters[:] = old_filters def _warn_about_deprecation(): warnings.warn( "This is used in test_support test to ensure" " support.ignore_deprecations_from() works as expected." " You should not be seeing this.", DeprecationWarning, stacklevel=0, ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729876127.0 et_xmlfile-2.0.0/et_xmlfile/tests/_vendor/test/test_xml_etree.py0000644000000000000000000051020214706750237022104 0ustar00# IMPORTANT: the same tests are run from "test_xml_etree_c" in order # to ensure consistency between the C implementation and the Python # implementation. # # For this purpose, the module-level "ET" symbol is temporarily # monkey-patched when running the "test_xml_etree_c" test suite. import copy import functools import html import io import itertools import operator import os import pickle import pyexpat import sys import textwrap import types import unittest import warnings import weakref from functools import partial from itertools import product, islice from . import support from .support import os_helper from .support import warnings_helper from .support import findfile, gc_collect, swap_attr, swap_item from .support.import_helper import import_fresh_module from .support.os_helper import TESTFN # pyET is the pure-Python implementation. # # ET is pyET in test_xml_etree and is the C accelerated version in # test_xml_etree_c. pyET = None ET = None SIMPLE_XMLFILE = findfile("simple.xml", subdir="xmltestdata") try: SIMPLE_XMLFILE.encode("utf-8") except UnicodeEncodeError: raise unittest.SkipTest("filename is not encodable to utf8") SIMPLE_NS_XMLFILE = findfile("simple-ns.xml", subdir="xmltestdata") UTF8_BUG_XMLFILE = findfile("expat224_utf8_bug.xml", subdir="xmltestdata") SAMPLE_XML = """\ text
subtext
""" SAMPLE_SECTION = """\
subtext
""" SAMPLE_XML_NS = """ text
subtext
""" SAMPLE_XML_NS_ELEMS = """ Apples Bananas African Coffee Table 80 120 """ ENTITY_XML = """\ %user-entities; ]> &entity; """ EXTERNAL_ENTITY_XML = """\ ]> &entity; """ ATTLIST_XML = """\ ]> &qux; """ def checkwarnings(*filters, quiet=False): def decorator(test): def newtest(*args, **kwargs): with warnings_helper.check_warnings(*filters, quiet=quiet): test(*args, **kwargs) functools.update_wrapper(newtest, test) return newtest return decorator def convlinesep(data): return data.replace(b'\n', os.linesep.encode()) class ModuleTest(unittest.TestCase): def test_sanity(self): # Import sanity. from xml.etree import ElementTree # noqa: F401 from xml.etree import ElementInclude # noqa: F401 from xml.etree import ElementPath # noqa: F401 def test_all(self): names = ("xml.etree.ElementTree", "_elementtree") support.check__all__(self, ET, names, not_exported=("HTML_EMPTY",)) def serialize(elem, to_string=True, encoding='unicode', **options): if encoding != 'unicode': file = io.BytesIO() else: file = io.StringIO() tree = ET.ElementTree(elem) tree.write(file, encoding=encoding, **options) if to_string: return file.getvalue() else: file.seek(0) return file def summarize_list(seq): return [elem.tag for elem in seq] class ElementTestCase: @classmethod def setUpClass(cls): cls.modules = {pyET, ET} def pickleRoundTrip(self, obj, name, dumper, loader, proto): try: with swap_item(sys.modules, name, dumper): temp = pickle.dumps(obj, proto) with swap_item(sys.modules, name, loader): result = pickle.loads(temp) except pickle.PicklingError as pe: # pyET must be second, because pyET may be (equal to) ET. human = dict([(ET, "cET"), (pyET, "pyET")]) raise support.TestFailed("Failed to round-trip %r from %r to %r" % (obj, human.get(dumper, dumper), human.get(loader, loader))) from pe return result def assertEqualElements(self, alice, bob): self.assertIsInstance(alice, (ET.Element, pyET.Element)) self.assertIsInstance(bob, (ET.Element, pyET.Element)) self.assertEqual(len(list(alice)), len(list(bob))) for x, y in zip(alice, bob): self.assertEqualElements(x, y) properties = operator.attrgetter('tag', 'tail', 'text', 'attrib') self.assertEqual(properties(alice), properties(bob)) # -------------------------------------------------------------------- # element tree tests class ElementTreeTest(unittest.TestCase): def serialize_check(self, elem, expected): self.assertEqual(serialize(elem), expected) def test_interface(self): # Test element tree interface. def check_element(element): self.assertTrue(ET.iselement(element), msg="not an element") direlem = dir(element) for attr in 'tag', 'attrib', 'text', 'tail': self.assertTrue(hasattr(element, attr), msg='no %s member' % attr) self.assertIn(attr, direlem, msg='no %s visible by dir' % attr) self.assertIsInstance(element.tag, str) self.assertIsInstance(element.attrib, dict) if element.text is not None: self.assertIsInstance(element.text, str) if element.tail is not None: self.assertIsInstance(element.tail, str) for elem in element: check_element(elem) element = ET.Element("tag") check_element(element) tree = ET.ElementTree(element) check_element(tree.getroot()) element = ET.Element("t\xe4g", key="value") tree = ET.ElementTree(element) self.assertRegex(repr(element), r"^$") element = ET.Element("tag", key="value") # Make sure all standard element methods exist. def check_method(method): self.assertTrue(hasattr(method, '__call__'), msg="%s not callable" % method) check_method(element.append) check_method(element.extend) check_method(element.insert) check_method(element.remove) check_method(element.find) check_method(element.iterfind) check_method(element.findall) check_method(element.findtext) check_method(element.clear) check_method(element.get) check_method(element.set) check_method(element.keys) check_method(element.items) check_method(element.iter) check_method(element.itertext) # These methods return an iterable. See bug 6472. def check_iter(it): check_method(it.__next__) check_iter(element.iterfind("tag")) check_iter(element.iterfind("*")) check_iter(tree.iterfind("tag")) check_iter(tree.iterfind("*")) # These aliases are provided: self.assertEqual(ET.XML, ET.fromstring) self.assertEqual(ET.PI, ET.ProcessingInstruction) def test_set_attribute(self): element = ET.Element('tag') self.assertEqual(element.tag, 'tag') element.tag = 'Tag' self.assertEqual(element.tag, 'Tag') element.tag = 'TAG' self.assertEqual(element.tag, 'TAG') self.assertIsNone(element.text) element.text = 'Text' self.assertEqual(element.text, 'Text') element.text = 'TEXT' self.assertEqual(element.text, 'TEXT') self.assertIsNone(element.tail) element.tail = 'Tail' self.assertEqual(element.tail, 'Tail') element.tail = 'TAIL' self.assertEqual(element.tail, 'TAIL') self.assertEqual(element.attrib, {}) element.attrib = {'a': 'b', 'c': 'd'} self.assertEqual(element.attrib, {'a': 'b', 'c': 'd'}) element.attrib = {'A': 'B', 'C': 'D'} self.assertEqual(element.attrib, {'A': 'B', 'C': 'D'}) def test_simpleops(self): # Basic method sanity checks. elem = ET.XML("") self.serialize_check(elem, '') e = ET.Element("tag2") elem.append(e) self.serialize_check(elem, '') elem.remove(e) self.serialize_check(elem, '') elem.insert(0, e) self.serialize_check(elem, '') elem.remove(e) elem.extend([e]) self.serialize_check(elem, '') elem.remove(e) elem.extend(iter([e])) self.serialize_check(elem, '') elem.remove(e) element = ET.Element("tag", key="value") self.serialize_check(element, '') # 1 subelement = ET.Element("subtag") element.append(subelement) self.serialize_check(element, '') # 2 element.insert(0, subelement) self.serialize_check(element, '') # 3 element.remove(subelement) self.serialize_check(element, '') # 4 element.remove(subelement) self.serialize_check(element, '') # 5 with self.assertRaises(ValueError) as cm: element.remove(subelement) self.assertEqual(str(cm.exception), 'list.remove(x): x not in list') self.serialize_check(element, '') # 6 element[0:0] = [subelement, subelement, subelement] self.serialize_check(element[1], '') self.assertEqual(element[1:9], [element[1], element[2]]) self.assertEqual(element[:9:2], [element[0], element[2]]) del element[1:2] self.serialize_check(element, '') def test_cdata(self): # Test CDATA handling (etc). self.serialize_check(ET.XML("hello"), 'hello') self.serialize_check(ET.XML("hello"), 'hello') self.serialize_check(ET.XML(""), 'hello') def test_file_init(self): stringfile = io.BytesIO(SAMPLE_XML.encode("utf-8")) tree = ET.ElementTree(file=stringfile) self.assertEqual(tree.find("tag").tag, 'tag') self.assertEqual(tree.find("section/tag").tag, 'tag') tree = ET.ElementTree(file=SIMPLE_XMLFILE) self.assertEqual(tree.find("element").tag, 'element') self.assertEqual(tree.find("element/../empty-element").tag, 'empty-element') def test_path_cache(self): # Check that the path cache behaves sanely. from xml.etree import ElementPath elem = ET.XML(SAMPLE_XML) ElementPath._cache.clear() for i in range(10): ET.ElementTree(elem).find('./'+str(i)) cache_len_10 = len(ElementPath._cache) for i in range(10): ET.ElementTree(elem).find('./'+str(i)) self.assertEqual(len(ElementPath._cache), cache_len_10) for i in range(20): ET.ElementTree(elem).find('./'+str(i)) self.assertGreater(len(ElementPath._cache), cache_len_10) for i in range(600): ET.ElementTree(elem).find('./'+str(i)) self.assertLess(len(ElementPath._cache), 500) def test_copy(self): # Test copy handling (etc). import copy e1 = ET.XML("hello") e2 = copy.copy(e1) e3 = copy.deepcopy(e1) e1.find("foo").tag = "bar" self.serialize_check(e1, 'hello') self.serialize_check(e2, 'hello') self.serialize_check(e3, 'hello') def test_attrib(self): # Test attribute handling. elem = ET.Element("tag") elem.get("key") # 1.1 self.assertEqual(elem.get("key", "default"), 'default') # 1.2 elem.set("key", "value") self.assertEqual(elem.get("key"), 'value') # 1.3 elem = ET.Element("tag", key="value") self.assertEqual(elem.get("key"), 'value') # 2.1 self.assertEqual(elem.attrib, {'key': 'value'}) # 2.2 attrib = {"key": "value"} elem = ET.Element("tag", attrib) attrib.clear() # check for aliasing issues self.assertEqual(elem.get("key"), 'value') # 3.1 self.assertEqual(elem.attrib, {'key': 'value'}) # 3.2 attrib = {"key": "value"} elem = ET.Element("tag", **attrib) attrib.clear() # check for aliasing issues self.assertEqual(elem.get("key"), 'value') # 4.1 self.assertEqual(elem.attrib, {'key': 'value'}) # 4.2 elem = ET.Element("tag", {"key": "other"}, key="value") self.assertEqual(elem.get("key"), 'value') # 5.1 self.assertEqual(elem.attrib, {'key': 'value'}) # 5.2 elem = ET.Element('test') elem.text = "aa" elem.set('testa', 'testval') elem.set('testb', 'test2') self.assertEqual(ET.tostring(elem), b'aa') self.assertEqual(sorted(elem.keys()), ['testa', 'testb']) self.assertEqual(sorted(elem.items()), [('testa', 'testval'), ('testb', 'test2')]) self.assertEqual(elem.attrib['testb'], 'test2') elem.attrib['testb'] = 'test1' elem.attrib['testc'] = 'test2' self.assertEqual(ET.tostring(elem), b'aa') # Test preserving white space chars in attributes elem = ET.Element('test') elem.set('a', '\r') elem.set('b', '\r\n') elem.set('c', '\t\n\r ') elem.set('d', '\n\n\r\r\t\t ') self.assertEqual(ET.tostring(elem), b'') def test_makeelement(self): # Test makeelement handling. elem = ET.Element("tag") attrib = {"key": "value"} subelem = elem.makeelement("subtag", attrib) self.assertIsNot(subelem.attrib, attrib, msg="attrib aliasing") elem.append(subelem) self.serialize_check(elem, '') elem.clear() self.serialize_check(elem, '') elem.append(subelem) self.serialize_check(elem, '') elem.extend([subelem, subelem]) self.serialize_check(elem, '') elem[:] = [subelem] self.serialize_check(elem, '') elem[:] = tuple([subelem]) self.serialize_check(elem, '') def test_parsefile(self): # Test parsing from file. tree = ET.parse(SIMPLE_XMLFILE) stream = io.StringIO() tree.write(stream, encoding='unicode') self.assertEqual(stream.getvalue(), '\n' ' text\n' ' texttail\n' ' \n' '') tree = ET.parse(SIMPLE_NS_XMLFILE) stream = io.StringIO() tree.write(stream, encoding='unicode') self.assertEqual(stream.getvalue(), '\n' ' text\n' ' texttail\n' ' \n' '') with open(SIMPLE_XMLFILE) as f: data = f.read() parser = ET.XMLParser() self.assertRegex(parser.version, r'^Expat ') parser.feed(data) self.serialize_check(parser.close(), '\n' ' text\n' ' texttail\n' ' \n' '') target = ET.TreeBuilder() parser = ET.XMLParser(target=target) parser.feed(data) self.serialize_check(parser.close(), '\n' ' text\n' ' texttail\n' ' \n' '') def test_parseliteral(self): element = ET.XML("text") self.assertEqual(ET.tostring(element, encoding='unicode'), 'text') element = ET.fromstring("text") self.assertEqual(ET.tostring(element, encoding='unicode'), 'text') sequence = ["", "text"] element = ET.fromstringlist(sequence) self.assertEqual(ET.tostring(element), b'text') self.assertEqual(b"".join(ET.tostringlist(element)), b'text') self.assertEqual(ET.tostring(element, "ascii"), b"\n" b"text") _, ids = ET.XMLID("text") self.assertEqual(len(ids), 0) _, ids = ET.XMLID("text") self.assertEqual(len(ids), 1) self.assertEqual(ids["body"].tag, 'body') def test_iterparse(self): # Test iterparse interface. iterparse = ET.iterparse context = iterparse(SIMPLE_XMLFILE) self.assertIsNone(context.root) action, elem = next(context) self.assertIsNone(context.root) self.assertEqual((action, elem.tag), ('end', 'element')) self.assertEqual([(action, elem.tag) for action, elem in context], [ ('end', 'element'), ('end', 'empty-element'), ('end', 'root'), ]) self.assertEqual(context.root.tag, 'root') context = iterparse(SIMPLE_NS_XMLFILE) self.assertEqual([(action, elem.tag) for action, elem in context], [ ('end', '{namespace}element'), ('end', '{namespace}element'), ('end', '{namespace}empty-element'), ('end', '{namespace}root'), ]) with open(SIMPLE_XMLFILE, 'rb') as source: context = iterparse(source) action, elem = next(context) self.assertEqual((action, elem.tag), ('end', 'element')) self.assertEqual([(action, elem.tag) for action, elem in context], [ ('end', 'element'), ('end', 'empty-element'), ('end', 'root'), ]) self.assertEqual(context.root.tag, 'root') events = () context = iterparse(SIMPLE_XMLFILE, events) self.assertEqual([(action, elem.tag) for action, elem in context], []) events = () context = iterparse(SIMPLE_XMLFILE, events=events) self.assertEqual([(action, elem.tag) for action, elem in context], []) events = ("start", "end") context = iterparse(SIMPLE_XMLFILE, events) self.assertEqual([(action, elem.tag) for action, elem in context], [ ('start', 'root'), ('start', 'element'), ('end', 'element'), ('start', 'element'), ('end', 'element'), ('start', 'empty-element'), ('end', 'empty-element'), ('end', 'root'), ]) events = ("start", "end", "start-ns", "end-ns") context = iterparse(SIMPLE_NS_XMLFILE, events) self.assertEqual([(action, elem.tag) if action in ("start", "end") else (action, elem) for action, elem in context], [ ('start-ns', ('', 'namespace')), ('start', '{namespace}root'), ('start', '{namespace}element'), ('end', '{namespace}element'), ('start', '{namespace}element'), ('end', '{namespace}element'), ('start', '{namespace}empty-element'), ('end', '{namespace}empty-element'), ('end', '{namespace}root'), ('end-ns', None), ]) events = ('start-ns', 'end-ns') context = iterparse(io.StringIO(r""), events) res = [action for action, elem in context] self.assertEqual(res, ['start-ns', 'end-ns']) events = ("start", "end", "bogus") with open(SIMPLE_XMLFILE, "rb") as f: with self.assertRaises(ValueError) as cm: iterparse(f, events) self.assertFalse(f.closed) self.assertEqual(str(cm.exception), "unknown event 'bogus'") with warnings_helper.check_no_resource_warning(self): with self.assertRaises(ValueError) as cm: iterparse(SIMPLE_XMLFILE, events) self.assertEqual(str(cm.exception), "unknown event 'bogus'") del cm source = io.BytesIO( b"\n" b"text\n") events = ("start-ns",) context = iterparse(source, events) self.assertEqual([(action, elem) for action, elem in context], [ ('start-ns', ('', 'http://\xe9ffbot.org/ns')), ('start-ns', ('cl\xe9', 'http://effbot.org/ns')), ]) source = io.StringIO("junk") it = iterparse(source) action, elem = next(it) self.assertEqual((action, elem.tag), ('end', 'document')) with self.assertRaises(ET.ParseError) as cm: next(it) self.assertEqual(str(cm.exception), 'junk after document element: line 1, column 12') self.addCleanup(os_helper.unlink, TESTFN) with open(TESTFN, "wb") as f: f.write(b"junk") it = iterparse(TESTFN) action, elem = next(it) self.assertEqual((action, elem.tag), ('end', 'document')) with warnings_helper.check_no_resource_warning(self): with self.assertRaises(ET.ParseError) as cm: next(it) self.assertEqual(str(cm.exception), 'junk after document element: line 1, column 12') del cm, it # Not exhausting the iterator still closes the resource (bpo-43292) with warnings_helper.check_no_resource_warning(self): it = iterparse(SIMPLE_XMLFILE) del it with warnings_helper.check_no_resource_warning(self): it = iterparse(SIMPLE_XMLFILE) it.close() del it with warnings_helper.check_no_resource_warning(self): it = iterparse(SIMPLE_XMLFILE) action, elem = next(it) self.assertEqual((action, elem.tag), ('end', 'element')) del it, elem with warnings_helper.check_no_resource_warning(self): it = iterparse(SIMPLE_XMLFILE) action, elem = next(it) it.close() self.assertEqual((action, elem.tag), ('end', 'element')) del it, elem with self.assertRaises(FileNotFoundError): iterparse("nonexistent") def test_iterparse_close(self): iterparse = ET.iterparse it = iterparse(SIMPLE_XMLFILE) it.close() with self.assertRaises(StopIteration): next(it) it.close() # idempotent with open(SIMPLE_XMLFILE, 'rb') as source: it = iterparse(source) it.close() self.assertFalse(source.closed) with self.assertRaises(StopIteration): next(it) it.close() # idempotent it = iterparse(SIMPLE_XMLFILE) action, elem = next(it) self.assertEqual((action, elem.tag), ('end', 'element')) it.close() with self.assertRaises(StopIteration): next(it) it.close() # idempotent with open(SIMPLE_XMLFILE, 'rb') as source: it = iterparse(source) action, elem = next(it) self.assertEqual((action, elem.tag), ('end', 'element')) it.close() self.assertFalse(source.closed) with self.assertRaises(StopIteration): next(it) it.close() # idempotent it = iterparse(SIMPLE_XMLFILE) list(it) it.close() with self.assertRaises(StopIteration): next(it) it.close() # idempotent with open(SIMPLE_XMLFILE, 'rb') as source: it = iterparse(source) list(it) it.close() self.assertFalse(source.closed) with self.assertRaises(StopIteration): next(it) it.close() # idempotent def test_writefile(self): elem = ET.Element("tag") elem.text = "text" self.serialize_check(elem, 'text') ET.SubElement(elem, "subtag").text = "subtext" self.serialize_check(elem, 'textsubtext') # Test tag suppression elem.tag = None self.serialize_check(elem, 'textsubtext') elem.insert(0, ET.Comment("comment")) self.serialize_check(elem, 'textsubtext') # assumes 1.3 elem[0] = ET.PI("key", "value") self.serialize_check(elem, 'textsubtext') def test_custom_builder(self): # Test parser w. custom builder. with open(SIMPLE_XMLFILE) as f: data = f.read() class Builder(list): def start(self, tag, attrib): self.append(("start", tag)) def end(self, tag): self.append(("end", tag)) def data(self, text): pass builder = Builder() parser = ET.XMLParser(target=builder) parser.feed(data) self.assertEqual(builder, [ ('start', 'root'), ('start', 'element'), ('end', 'element'), ('start', 'element'), ('end', 'element'), ('start', 'empty-element'), ('end', 'empty-element'), ('end', 'root'), ]) with open(SIMPLE_NS_XMLFILE) as f: data = f.read() class Builder(list): def start(self, tag, attrib): self.append(("start", tag)) def end(self, tag): self.append(("end", tag)) def data(self, text): pass def pi(self, target, data): self.append(("pi", target, data)) def comment(self, data): self.append(("comment", data)) def start_ns(self, prefix, uri): self.append(("start-ns", prefix, uri)) def end_ns(self, prefix): self.append(("end-ns", prefix)) builder = Builder() parser = ET.XMLParser(target=builder) parser.feed(data) self.assertEqual(builder, [ ('pi', 'pi', 'data'), ('comment', ' comment '), ('start-ns', '', 'namespace'), ('start', '{namespace}root'), ('start', '{namespace}element'), ('end', '{namespace}element'), ('start', '{namespace}element'), ('end', '{namespace}element'), ('start', '{namespace}empty-element'), ('end', '{namespace}empty-element'), ('end', '{namespace}root'), ('end-ns', ''), ]) def test_custom_builder_only_end_ns(self): class Builder(list): def end_ns(self, prefix): self.append(("end-ns", prefix)) builder = Builder() parser = ET.XMLParser(target=builder) parser.feed(textwrap.dedent("""\ text texttail """)) self.assertEqual(builder, [ ('end-ns', 'a'), ('end-ns', 'p'), ('end-ns', ''), ]) def test_initialize_parser_without_target(self): # Explicit None parser = ET.XMLParser(target=None) self.assertIsInstance(parser.target, ET.TreeBuilder) # Implicit None parser2 = ET.XMLParser() self.assertIsInstance(parser2.target, ET.TreeBuilder) def test_children(self): # Test Element children iteration with open(SIMPLE_XMLFILE, "rb") as f: tree = ET.parse(f) self.assertEqual([summarize_list(elem) for elem in tree.getroot().iter()], [ ['element', 'element', 'empty-element'], [], [], [], ]) self.assertEqual([summarize_list(elem) for elem in tree.iter()], [ ['element', 'element', 'empty-element'], [], [], [], ]) elem = ET.XML(SAMPLE_XML) self.assertEqual(len(list(elem)), 3) self.assertEqual(len(list(elem[2])), 1) self.assertEqual(elem[:], list(elem)) child1 = elem[0] child2 = elem[2] del elem[1:2] self.assertEqual(len(list(elem)), 2) self.assertEqual(child1, elem[0]) self.assertEqual(child2, elem[1]) elem[0:2] = [child2, child1] self.assertEqual(child2, elem[0]) self.assertEqual(child1, elem[1]) self.assertNotEqual(child1, elem[0]) elem.clear() self.assertEqual(list(elem), []) def test_writestring(self): elem = ET.XML("text") self.assertEqual(ET.tostring(elem), b'text') elem = ET.fromstring("text") self.assertEqual(ET.tostring(elem), b'text') def test_indent(self): elem = ET.XML("") ET.indent(elem) self.assertEqual(ET.tostring(elem), b'') elem = ET.XML("text") ET.indent(elem) self.assertEqual(ET.tostring(elem), b'\n text\n') elem = ET.XML(" text ") ET.indent(elem) self.assertEqual(ET.tostring(elem), b'\n text\n') elem = ET.XML("texttail") ET.indent(elem) self.assertEqual(ET.tostring(elem), b'\n texttail') elem = ET.XML("

par

\n

text

\t


") ET.indent(elem) self.assertEqual( ET.tostring(elem), b'\n' b' \n' b'

par

\n' b'

text

\n' b'

\n' b'
\n' b'

\n' b' \n' b'' ) elem = ET.XML("

pre
post

text

") ET.indent(elem) self.assertEqual( ET.tostring(elem), b'\n' b' \n' b'

pre
post

\n' b'

text

\n' b' \n' b'' ) def test_indent_space(self): elem = ET.XML("

pre
post

text

") ET.indent(elem, space='\t') self.assertEqual( ET.tostring(elem), b'\n' b'\t\n' b'\t\t

pre
post

\n' b'\t\t

text

\n' b'\t\n' b'' ) elem = ET.XML("

pre
post

text

") ET.indent(elem, space='') self.assertEqual( ET.tostring(elem), b'\n' b'\n' b'

pre
post

\n' b'

text

\n' b'\n' b'' ) def test_indent_space_caching(self): elem = ET.XML("

par

text


") ET.indent(elem) self.assertEqual( {el.tail for el in elem.iter()}, {None, "\n", "\n ", "\n "} ) self.assertEqual( {el.text for el in elem.iter()}, {None, "\n ", "\n ", "\n ", "par", "text"} ) self.assertEqual( len({el.tail for el in elem.iter()}), len({id(el.tail) for el in elem.iter()}), ) def test_indent_level(self): elem = ET.XML("

pre
post

text

") with self.assertRaises(ValueError): ET.indent(elem, level=-1) self.assertEqual( ET.tostring(elem), b"

pre
post

text

" ) ET.indent(elem, level=2) self.assertEqual( ET.tostring(elem), b'\n' b' \n' b'

pre
post

\n' b'

text

\n' b' \n' b' ' ) elem = ET.XML("

pre
post

text

") ET.indent(elem, level=1, space=' ') self.assertEqual( ET.tostring(elem), b'\n' b' \n' b'

pre
post

\n' b'

text

\n' b' \n' b' ' ) def test_tostring_default_namespace(self): elem = ET.XML('') self.assertEqual( ET.tostring(elem, encoding='unicode'), '' ) self.assertEqual( ET.tostring(elem, encoding='unicode', default_namespace='http://effbot.org/ns'), '' ) def test_tostring_default_namespace_different_namespace(self): elem = ET.XML('') self.assertEqual( ET.tostring(elem, encoding='unicode', default_namespace='foobar'), '' ) def test_tostring_default_namespace_original_no_namespace(self): elem = ET.XML('') EXPECTED_MSG = '^cannot use non-qualified names with default_namespace option$' with self.assertRaisesRegex(ValueError, EXPECTED_MSG): ET.tostring(elem, encoding='unicode', default_namespace='foobar') def test_tostring_no_xml_declaration(self): elem = ET.XML('') self.assertEqual( ET.tostring(elem, encoding='unicode'), '' ) def test_tostring_xml_declaration(self): elem = ET.XML('') self.assertEqual( ET.tostring(elem, encoding='utf8', xml_declaration=True), b"\n" ) def test_tostring_xml_declaration_unicode_encoding(self): elem = ET.XML('') self.assertEqual( ET.tostring(elem, encoding='unicode', xml_declaration=True), "\n" ) def test_tostring_xml_declaration_cases(self): elem = ET.XML('ø') TESTCASES = [ # (expected_retval, encoding, xml_declaration) # ... xml_declaration = None (b'ø', None, None), (b'\xc3\xb8', 'UTF-8', None), (b'ø', 'US-ASCII', None), (b"\n" b"\xf8", 'ISO-8859-1', None), ('ø', 'unicode', None), # ... xml_declaration = False (b"ø", None, False), (b"\xc3\xb8", 'UTF-8', False), (b"ø", 'US-ASCII', False), (b"\xf8", 'ISO-8859-1', False), ("ø", 'unicode', False), # ... xml_declaration = True (b"\n" b"ø", None, True), (b"\n" b"\xc3\xb8", 'UTF-8', True), (b"\n" b"ø", 'US-ASCII', True), (b"\n" b"\xf8", 'ISO-8859-1', True), ("\n" "ø", 'unicode', True), ] for expected_retval, encoding, xml_declaration in TESTCASES: with self.subTest(f'encoding={encoding} ' f'xml_declaration={xml_declaration}'): self.assertEqual( ET.tostring( elem, encoding=encoding, xml_declaration=xml_declaration ), expected_retval ) def test_tostringlist_default_namespace(self): elem = ET.XML('') self.assertEqual( ''.join(ET.tostringlist(elem, encoding='unicode')), '' ) self.assertEqual( ''.join(ET.tostringlist(elem, encoding='unicode', default_namespace='http://effbot.org/ns')), '' ) def test_tostringlist_xml_declaration(self): elem = ET.XML('') self.assertEqual( ''.join(ET.tostringlist(elem, encoding='unicode')), '' ) self.assertEqual( b''.join(ET.tostringlist(elem, xml_declaration=True)), b"\n" ) stringlist = ET.tostringlist(elem, encoding='unicode', xml_declaration=True) self.assertEqual( ''.join(stringlist), "\n" ) self.assertRegex(stringlist[0], r"^<\?xml version='1.0' encoding='.+'?>") self.assertEqual(['', '', ''], stringlist[1:]) def test_encoding(self): def check(encoding, body=''): xml = ("%s" % (encoding, body)) self.assertEqual(ET.XML(xml.encode(encoding)).text, body) self.assertEqual(ET.XML(xml).text, body) check("ascii", 'a') check("us-ascii", 'a') check("iso-8859-1", '\xbd') check("iso-8859-15", '\u20ac') check("cp437", '\u221a') check("mac-roman", '\u02da') def xml(encoding): return "" % encoding def bxml(encoding): return xml(encoding).encode(encoding) supported_encodings = [ 'ascii', 'utf-8', 'utf-8-sig', 'utf-16', 'utf-16be', 'utf-16le', 'iso8859-1', 'iso8859-2', 'iso8859-3', 'iso8859-4', 'iso8859-5', 'iso8859-6', 'iso8859-7', 'iso8859-8', 'iso8859-9', 'iso8859-10', 'iso8859-13', 'iso8859-14', 'iso8859-15', 'iso8859-16', 'cp437', 'cp720', 'cp737', 'cp775', 'cp850', 'cp852', 'cp855', 'cp856', 'cp857', 'cp858', 'cp860', 'cp861', 'cp862', 'cp863', 'cp865', 'cp866', 'cp869', 'cp874', 'cp1006', 'cp1125', 'cp1250', 'cp1251', 'cp1252', 'cp1253', 'cp1254', 'cp1255', 'cp1256', 'cp1257', 'cp1258', 'mac-cyrillic', 'mac-greek', 'mac-iceland', 'mac-latin2', 'mac-roman', 'mac-turkish', 'iso2022-jp', 'iso2022-jp-1', 'iso2022-jp-2', 'iso2022-jp-2004', 'iso2022-jp-3', 'iso2022-jp-ext', 'koi8-r', 'koi8-t', 'koi8-u', 'kz1048', 'hz', 'ptcp154', ] for encoding in supported_encodings: self.assertEqual(ET.tostring(ET.XML(bxml(encoding))), b'') unsupported_ascii_compatible_encodings = [ 'big5', 'big5hkscs', 'cp932', 'cp949', 'cp950', 'euc-jp', 'euc-jis-2004', 'euc-jisx0213', 'euc-kr', 'gb2312', 'gbk', 'gb18030', 'iso2022-kr', 'johab', 'shift-jis', 'shift-jis-2004', 'shift-jisx0213', 'utf-7', ] for encoding in unsupported_ascii_compatible_encodings: self.assertRaises(ValueError, ET.XML, bxml(encoding)) unsupported_ascii_incompatible_encodings = [ 'cp037', 'cp424', 'cp500', 'cp864', 'cp875', 'cp1026', 'cp1140', 'utf_32', 'utf_32_be', 'utf_32_le', ] for encoding in unsupported_ascii_incompatible_encodings: self.assertRaises(ET.ParseError, ET.XML, bxml(encoding)) self.assertRaises(ValueError, ET.XML, xml('undefined').encode('ascii')) self.assertRaises(LookupError, ET.XML, xml('xxx').encode('ascii')) def test_methods(self): # Test serialization methods. e = ET.XML("") e.tail = "\n" self.assertEqual(serialize(e), '\n') self.assertEqual(serialize(e, method=None), '\n') self.assertEqual(serialize(e, method="xml"), '\n') self.assertEqual(serialize(e, method="html"), '\n') self.assertEqual(serialize(e, method="text"), '1 < 2\n') def test_issue18347(self): e = ET.XML('text') self.assertEqual(serialize(e), 'text') self.assertEqual(serialize(e, method="html"), 'text') def test_entity(self): # Test entity handling. # 1) good entities e = ET.XML("test") self.assertEqual(serialize(e, encoding="us-ascii"), b'test') self.serialize_check(e, 'test') # 2) bad entities with self.assertRaises(ET.ParseError) as cm: ET.XML("&entity;") self.assertEqual(str(cm.exception), 'undefined entity: line 1, column 10') with self.assertRaises(ET.ParseError) as cm: ET.XML(ENTITY_XML) self.assertEqual(str(cm.exception), 'undefined entity &entity;: line 5, column 10') # 3) custom entity parser = ET.XMLParser() parser.entity["entity"] = "text" parser.feed(ENTITY_XML) root = parser.close() self.serialize_check(root, 'text') # 4) external (SYSTEM) entity with self.assertRaises(ET.ParseError) as cm: ET.XML(EXTERNAL_ENTITY_XML) self.assertEqual(str(cm.exception), 'undefined entity &entity;: line 4, column 10') def test_namespace(self): # Test namespace issues. # 1) xml namespace elem = ET.XML("") self.serialize_check(elem, '') # 1.1 # 2) other "well-known" namespaces elem = ET.XML("") self.serialize_check(elem, '') # 2.1 elem = ET.XML("") self.serialize_check(elem, '') # 2.2 elem = ET.XML("") self.serialize_check(elem, '') # 2.3 # 3) unknown namespaces elem = ET.XML(SAMPLE_XML_NS) self.serialize_check(elem, '\n' ' text\n' ' \n' ' \n' ' subtext\n' ' \n' '') def test_qname(self): # Test QName handling. # 1) decorated tags elem = ET.Element("{uri}tag") self.serialize_check(elem, '') # 1.1 elem = ET.Element(ET.QName("{uri}tag")) self.serialize_check(elem, '') # 1.2 elem = ET.Element(ET.QName("uri", "tag")) self.serialize_check(elem, '') # 1.3 elem = ET.Element(ET.QName("uri", "tag")) subelem = ET.SubElement(elem, ET.QName("uri", "tag1")) subelem = ET.SubElement(elem, ET.QName("uri", "tag2")) self.serialize_check(elem, '') # 1.4 # 2) decorated attributes elem.clear() elem.attrib["{uri}key"] = "value" self.serialize_check(elem, '') # 2.1 elem.clear() elem.attrib[ET.QName("{uri}key")] = "value" self.serialize_check(elem, '') # 2.2 # 3) decorated values are not converted by default, but the # QName wrapper can be used for values elem.clear() elem.attrib["{uri}key"] = "{uri}value" self.serialize_check(elem, '') # 3.1 elem.clear() elem.attrib["{uri}key"] = ET.QName("{uri}value") self.serialize_check(elem, '') # 3.2 elem.clear() subelem = ET.Element("tag") subelem.attrib["{uri1}key"] = ET.QName("{uri2}value") elem.append(subelem) elem.append(subelem) self.serialize_check(elem, '' '' '' '') # 3.3 # 4) Direct QName tests self.assertEqual(str(ET.QName('ns', 'tag')), '{ns}tag') self.assertEqual(str(ET.QName('{ns}tag')), '{ns}tag') q1 = ET.QName('ns', 'tag') q2 = ET.QName('ns', 'tag') self.assertEqual(q1, q2) q2 = ET.QName('ns', 'other-tag') self.assertNotEqual(q1, q2) self.assertNotEqual(q1, 'ns:tag') self.assertEqual(q1, '{ns}tag') def test_doctype_public(self): # Test PUBLIC doctype. elem = ET.XML('' 'text') def test_xpath_tokenizer(self): # Test the XPath tokenizer. from xml.etree import ElementPath def check(p, expected, namespaces=None): self.assertEqual([op or tag for op, tag in ElementPath.xpath_tokenizer(p, namespaces)], expected) # tests from the xml specification check("*", ['*']) check("text()", ['text', '()']) check("@name", ['@', 'name']) check("@*", ['@', '*']) check("para[1]", ['para', '[', '1', ']']) check("para[last()]", ['para', '[', 'last', '()', ']']) check("*/para", ['*', '/', 'para']) check("/doc/chapter[5]/section[2]", ['/', 'doc', '/', 'chapter', '[', '5', ']', '/', 'section', '[', '2', ']']) check("chapter//para", ['chapter', '//', 'para']) check("//para", ['//', 'para']) check("//olist/item", ['//', 'olist', '/', 'item']) check(".", ['.']) check(".//para", ['.', '//', 'para']) check("..", ['..']) check("../@lang", ['..', '/', '@', 'lang']) check("chapter[title]", ['chapter', '[', 'title', ']']) check("employee[@secretary and @assistant]", ['employee', '[', '@', 'secretary', '', 'and', '', '@', 'assistant', ']']) # additional tests check("@{ns}attr", ['@', '{ns}attr']) check("{http://spam}egg", ['{http://spam}egg']) check("./spam.egg", ['.', '/', 'spam.egg']) check(".//{http://spam}egg", ['.', '//', '{http://spam}egg']) # wildcard tags check("{ns}*", ['{ns}*']) check("{}*", ['{}*']) check("{*}tag", ['{*}tag']) check("{*}*", ['{*}*']) check(".//{*}tag", ['.', '//', '{*}tag']) # namespace prefix resolution check("./xsd:type", ['.', '/', '{http://www.w3.org/2001/XMLSchema}type'], {'xsd': 'http://www.w3.org/2001/XMLSchema'}) check("type", ['{http://www.w3.org/2001/XMLSchema}type'], {'': 'http://www.w3.org/2001/XMLSchema'}) check("@xsd:type", ['@', '{http://www.w3.org/2001/XMLSchema}type'], {'xsd': 'http://www.w3.org/2001/XMLSchema'}) check("@type", ['@', 'type'], {'': 'http://www.w3.org/2001/XMLSchema'}) check("@{*}type", ['@', '{*}type'], {'': 'http://www.w3.org/2001/XMLSchema'}) check("@{ns}attr", ['@', '{ns}attr'], {'': 'http://www.w3.org/2001/XMLSchema', 'ns': 'http://www.w3.org/2001/XMLSchema'}) def test_processinginstruction(self): # Test ProcessingInstruction directly self.assertEqual(ET.tostring(ET.ProcessingInstruction('test', 'instruction')), b'') self.assertEqual(ET.tostring(ET.PI('test', 'instruction')), b'') # Issue #2746 self.assertEqual(ET.tostring(ET.PI('test', '')), b'?>') self.assertEqual(ET.tostring(ET.PI('test', '\xe3'), 'latin-1'), b"\n" b"\xe3?>") def test_html_empty_elems_serialization(self): # issue 15970 # from http://www.w3.org/TR/html401/index/elements.html for element in ['AREA', 'BASE', 'BASEFONT', 'BR', 'COL', 'EMBED', 'FRAME', 'HR', 'IMG', 'INPUT', 'ISINDEX', 'LINK', 'META', 'PARAM', 'SOURCE', 'TRACK', 'WBR']: for elem in [element, element.lower()]: expected = '<%s>' % elem serialized = serialize(ET.XML('<%s />' % elem), method='html') self.assertEqual(serialized, expected) serialized = serialize(ET.XML('<%s>' % (elem,elem)), method='html') self.assertEqual(serialized, expected) def test_dump_attribute_order(self): # See BPO 34160 e = ET.Element('cirriculum', status='public', company='example') with support.captured_stdout() as stdout: ET.dump(e) self.assertEqual(stdout.getvalue(), '\n') def test_tree_write_attribute_order(self): # See BPO 34160 root = ET.Element('cirriculum', status='public', company='example') self.assertEqual(serialize(root), '') self.assertEqual(serialize(root, method='html'), '') def test_attlist_default(self): # Test default attribute values; See BPO 42151. root = ET.fromstring(ATTLIST_XML) self.assertEqual(root[0].attrib, {'{http://www.w3.org/XML/1998/namespace}lang': 'eng'}) class XMLPullParserTest(unittest.TestCase): def _feed(self, parser, data, chunk_size=None, flush=False): if chunk_size is None: parser.feed(data) else: for i in range(0, len(data), chunk_size): parser.feed(data[i:i+chunk_size]) if flush: parser.flush() def assert_events(self, parser, expected, max_events=None): self.assertEqual( [(event, (elem.tag, elem.text)) for event, elem in islice(parser.read_events(), max_events)], expected) def assert_event_tuples(self, parser, expected, max_events=None): self.assertEqual( list(islice(parser.read_events(), max_events)), expected) def assert_event_tags(self, parser, expected, max_events=None): events = islice(parser.read_events(), max_events) self.assertEqual([(action, elem.tag) for action, elem in events], expected) def test_simple_xml(self, chunk_size=None, flush=False): parser = ET.XMLPullParser() self.assert_event_tags(parser, []) self._feed(parser, "\n", chunk_size, flush) self.assert_event_tags(parser, []) self._feed(parser, "\n text\n", chunk_size, flush) self.assert_event_tags(parser, [('end', 'element')]) self._feed(parser, "texttail\n", chunk_size, flush) self._feed(parser, "\n", chunk_size, flush) self.assert_event_tags(parser, [ ('end', 'element'), ('end', 'empty-element'), ]) self._feed(parser, "\n", chunk_size, flush) self.assert_event_tags(parser, [('end', 'root')]) self.assertIsNone(parser.close()) def test_simple_xml_chunk_1(self): self.test_simple_xml(chunk_size=1, flush=True) def test_simple_xml_chunk_5(self): self.test_simple_xml(chunk_size=5, flush=True) def test_simple_xml_chunk_22(self): self.test_simple_xml(chunk_size=22) def test_feed_while_iterating(self): parser = ET.XMLPullParser() it = parser.read_events() self._feed(parser, "\n text\n") action, elem = next(it) self.assertEqual((action, elem.tag), ('end', 'element')) self._feed(parser, "\n") action, elem = next(it) self.assertEqual((action, elem.tag), ('end', 'root')) with self.assertRaises(StopIteration): next(it) def test_simple_xml_with_ns(self): parser = ET.XMLPullParser() self.assert_event_tags(parser, []) self._feed(parser, "\n") self.assert_event_tags(parser, []) self._feed(parser, "\n") self.assert_event_tags(parser, []) self._feed(parser, "text\n") self.assert_event_tags(parser, [('end', '{namespace}element')]) self._feed(parser, "texttail\n") self._feed(parser, "\n") self.assert_event_tags(parser, [ ('end', '{namespace}element'), ('end', '{namespace}empty-element'), ]) self._feed(parser, "\n") self.assert_event_tags(parser, [('end', '{namespace}root')]) self.assertIsNone(parser.close()) def test_ns_events(self): parser = ET.XMLPullParser(events=('start-ns', 'end-ns')) self._feed(parser, "\n") self._feed(parser, "\n") self.assertEqual( list(parser.read_events()), [('start-ns', ('', 'namespace'))]) self._feed(parser, "text\n") self._feed(parser, "texttail\n") self._feed(parser, "\n") self._feed(parser, "\n") self.assertEqual(list(parser.read_events()), [('end-ns', None)]) self.assertIsNone(parser.close()) def test_ns_events_start(self): parser = ET.XMLPullParser(events=('start-ns', 'start', 'end')) self._feed(parser, "\n") self.assert_event_tuples(parser, [ ('start-ns', ('', 'abc')), ('start-ns', ('p', 'xyz')), ], max_events=2) self.assert_event_tags(parser, [ ('start', '{abc}tag'), ], max_events=1) self._feed(parser, "\n") self.assert_event_tags(parser, [ ('start', '{abc}child'), ('end', '{abc}child'), ]) self._feed(parser, "\n") parser.close() self.assert_event_tags(parser, [ ('end', '{abc}tag'), ]) def test_ns_events_start_end(self): parser = ET.XMLPullParser(events=('start-ns', 'start', 'end', 'end-ns')) self._feed(parser, "\n") self.assert_event_tuples(parser, [ ('start-ns', ('', 'abc')), ('start-ns', ('p', 'xyz')), ], max_events=2) self.assert_event_tags(parser, [ ('start', '{abc}tag'), ], max_events=1) self._feed(parser, "\n") self.assert_event_tags(parser, [ ('start', '{abc}child'), ('end', '{abc}child'), ]) self._feed(parser, "\n") parser.close() self.assert_event_tags(parser, [ ('end', '{abc}tag'), ], max_events=1) self.assert_event_tuples(parser, [ ('end-ns', None), ('end-ns', None), ]) def test_events(self): parser = ET.XMLPullParser(events=()) self._feed(parser, "\n") self.assert_event_tags(parser, []) parser = ET.XMLPullParser(events=('start', 'end')) self._feed(parser, "\n") self.assert_events(parser, []) parser = ET.XMLPullParser(events=('start', 'end')) self._feed(parser, "\n") self.assert_event_tags(parser, [('start', 'root')]) self._feed(parser, "text\n") self.assert_event_tags(parser, [('end', 'element')]) self._feed(parser, "texttail\n") self.assert_event_tags(parser, [ ('start', '{foo}element'), ('start', '{foo}empty-element'), ('end', '{foo}empty-element'), ('end', '{foo}element'), ]) self._feed(parser, "") self.assertIsNone(parser.close()) self.assert_event_tags(parser, [('end', 'root')]) parser = ET.XMLPullParser(events=('start',)) self._feed(parser, "\n") self.assert_event_tags(parser, []) self._feed(parser, "\n") self.assert_event_tags(parser, [('start', 'root')]) self._feed(parser, "text\n") self.assert_event_tags(parser, []) self._feed(parser, "texttail\n") self.assert_event_tags(parser, [ ('start', '{foo}element'), ('start', '{foo}empty-element'), ]) self._feed(parser, "") self.assertIsNone(parser.close()) def test_events_comment(self): parser = ET.XMLPullParser(events=('start', 'comment', 'end')) self._feed(parser, "\n") self.assert_events(parser, [('comment', (ET.Comment, ' text here '))]) self._feed(parser, "\n") self.assert_events(parser, [('comment', (ET.Comment, ' more text here '))]) self._feed(parser, "text") self.assert_event_tags(parser, [('start', 'root-tag')]) self._feed(parser, "\n") self.assert_events(parser, [('comment', (ET.Comment, ' inner comment'))]) self._feed(parser, "\n") self.assert_event_tags(parser, [('end', 'root-tag')]) self._feed(parser, "\n") self.assert_events(parser, [('comment', (ET.Comment, ' outer comment '))]) parser = ET.XMLPullParser(events=('comment',)) self._feed(parser, "\n") self.assert_events(parser, [('comment', (ET.Comment, ' text here '))]) def test_events_pi(self): parser = ET.XMLPullParser(events=('start', 'pi', 'end')) self._feed(parser, "\n") self.assert_events(parser, [('pi', (ET.PI, 'pitarget'))]) parser = ET.XMLPullParser(events=('pi',)) self._feed(parser, "\n") self.assert_events(parser, [('pi', (ET.PI, 'pitarget some text '))]) def test_events_sequence(self): # Test that events can be some sequence that's not just a tuple or list eventset = {'end', 'start'} parser = ET.XMLPullParser(events=eventset) self._feed(parser, "bar") self.assert_event_tags(parser, [('start', 'foo'), ('end', 'foo')]) class DummyIter: def __init__(self): self.events = iter(['start', 'end', 'start-ns']) def __iter__(self): return self def __next__(self): return next(self.events) parser = ET.XMLPullParser(events=DummyIter()) self._feed(parser, "bar") self.assert_event_tags(parser, [('start', 'foo'), ('end', 'foo')]) def test_unknown_event(self): with self.assertRaises(ValueError): ET.XMLPullParser(events=('start', 'end', 'bogus')) @unittest.skipIf(pyexpat.version_info < (2, 6, 0), f'Expat {pyexpat.version_info} does not ' 'support reparse deferral') def test_flush_reparse_deferral_enabled(self): parser = ET.XMLPullParser(events=('start', 'end')) for chunk in (""): parser.feed(chunk) self.assert_event_tags(parser, []) # i.e. no elements started if ET is pyET: self.assertTrue(parser._parser._parser.GetReparseDeferralEnabled()) parser.flush() self.assert_event_tags(parser, [('start', 'doc')]) if ET is pyET: self.assertTrue(parser._parser._parser.GetReparseDeferralEnabled()) parser.feed("") parser.close() self.assert_event_tags(parser, [('end', 'doc')]) def test_flush_reparse_deferral_disabled(self): parser = ET.XMLPullParser(events=('start', 'end')) for chunk in (""): parser.feed(chunk) if pyexpat.version_info >= (2, 6, 0): if not ET is pyET: self.skipTest(f'XMLParser.(Get|Set)ReparseDeferralEnabled ' 'methods not available in C') parser._parser._parser.SetReparseDeferralEnabled(False) self.assert_event_tags(parser, []) # i.e. no elements started if ET is pyET: self.assertFalse(parser._parser._parser.GetReparseDeferralEnabled()) parser.flush() self.assert_event_tags(parser, [('start', 'doc')]) if ET is pyET: self.assertFalse(parser._parser._parser.GetReparseDeferralEnabled()) parser.feed("") parser.close() self.assert_event_tags(parser, [('end', 'doc')]) # # xinclude tests (samples from appendix C of the xinclude specification) XINCLUDE = {} XINCLUDE["C1.xml"] = """\

120 Mz is adequate for an average home user.

""" XINCLUDE["disclaimer.xml"] = """\

The opinions represented herein represent those of the individual and should not be interpreted as official policy endorsed by this organization.

""" XINCLUDE["C2.xml"] = """\

This document has been accessed times.

""" XINCLUDE["count.txt"] = "324387" XINCLUDE["C2b.xml"] = """\

This document has been accessed times.

""" XINCLUDE["C3.xml"] = """\

The following is the source of the "data.xml" resource:

""" XINCLUDE["data.xml"] = """\ """ XINCLUDE["C5.xml"] = """\ """ XINCLUDE["default.xml"] = """\

Example.

""".format(html.escape(SIMPLE_XMLFILE, True)) XINCLUDE["include_c1_repeated.xml"] = """\

The following is the source code of Recursive1.xml:

""" # # badly formatted xi:include tags XINCLUDE_BAD = {} XINCLUDE_BAD["B1.xml"] = """\

120 Mz is adequate for an average home user.

""" XINCLUDE_BAD["B2.xml"] = """\
""" XINCLUDE["Recursive1.xml"] = """\

The following is the source code of Recursive2.xml:

""" XINCLUDE["Recursive2.xml"] = """\

The following is the source code of Recursive3.xml:

""" XINCLUDE["Recursive3.xml"] = """\

The following is the source code of Recursive1.xml:

""" class XIncludeTest(unittest.TestCase): def xinclude_loader(self, href, parse="xml", encoding=None): try: data = XINCLUDE[href] except KeyError: raise OSError("resource not found") if parse == "xml": data = ET.XML(data) return data def none_loader(self, href, parser, encoding=None): return None def _my_loader(self, href, parse): # Used to avoid a test-dependency problem where the default loader # of ElementInclude uses the pyET parser for cET tests. if parse == 'xml': with open(href, 'rb') as f: return ET.parse(f).getroot() else: return None def test_xinclude_default(self): from xml.etree import ElementInclude doc = self.xinclude_loader('default.xml') ElementInclude.include(doc, self._my_loader) self.assertEqual(serialize(doc), '\n' '

Example.

\n' ' \n' ' text\n' ' texttail\n' ' \n' '\n' '
') def test_xinclude(self): from xml.etree import ElementInclude # Basic inclusion example (XInclude C.1) document = self.xinclude_loader("C1.xml") ElementInclude.include(document, self.xinclude_loader) self.assertEqual(serialize(document), '\n' '

120 Mz is adequate for an average home user.

\n' ' \n' '

The opinions represented herein represent those of the individual\n' ' and should not be interpreted as official policy endorsed by this\n' ' organization.

\n' '
\n' '
') # C1 # Textual inclusion example (XInclude C.2) document = self.xinclude_loader("C2.xml") ElementInclude.include(document, self.xinclude_loader) self.assertEqual(serialize(document), '\n' '

This document has been accessed\n' ' 324387 times.

\n' '
') # C2 # Textual inclusion after sibling element (based on modified XInclude C.2) document = self.xinclude_loader("C2b.xml") ElementInclude.include(document, self.xinclude_loader) self.assertEqual(serialize(document), '\n' '

This document has been accessed\n' ' 324387 times.

\n' '
') # C2b # Textual inclusion of XML example (XInclude C.3) document = self.xinclude_loader("C3.xml") ElementInclude.include(document, self.xinclude_loader) self.assertEqual(serialize(document), '\n' '

The following is the source of the "data.xml" resource:

\n' " <?xml version='1.0'?>\n" '<data>\n' ' <item><![CDATA[Brooks & Shields]]></item>\n' '</data>\n' '\n' '
') # C3 # Fallback example (XInclude C.5) # Note! Fallback support is not yet implemented document = self.xinclude_loader("C5.xml") with self.assertRaises(OSError) as cm: ElementInclude.include(document, self.xinclude_loader) self.assertEqual(str(cm.exception), 'resource not found') self.assertEqual(serialize(document), '
\n' ' \n' ' \n' ' \n' ' Report error\n' ' \n' ' \n' ' \n' '
') # C5 def test_xinclude_repeated(self): from xml.etree import ElementInclude document = self.xinclude_loader("include_c1_repeated.xml") ElementInclude.include(document, self.xinclude_loader) self.assertEqual(1+4*2, len(document.findall(".//p"))) def test_xinclude_failures(self): from xml.etree import ElementInclude # Test failure to locate included XML file. document = ET.XML(XINCLUDE["C1.xml"]) with self.assertRaises(ElementInclude.FatalIncludeError) as cm: ElementInclude.include(document, loader=self.none_loader) self.assertEqual(str(cm.exception), "cannot load 'disclaimer.xml' as 'xml'") # Test failure to locate included text file. document = ET.XML(XINCLUDE["C2.xml"]) with self.assertRaises(ElementInclude.FatalIncludeError) as cm: ElementInclude.include(document, loader=self.none_loader) self.assertEqual(str(cm.exception), "cannot load 'count.txt' as 'text'") # Test bad parse type. document = ET.XML(XINCLUDE_BAD["B1.xml"]) with self.assertRaises(ElementInclude.FatalIncludeError) as cm: ElementInclude.include(document, loader=self.none_loader) self.assertEqual(str(cm.exception), "unknown parse type in xi:include tag ('BAD_TYPE')") # Test xi:fallback outside xi:include. document = ET.XML(XINCLUDE_BAD["B2.xml"]) with self.assertRaises(ElementInclude.FatalIncludeError) as cm: ElementInclude.include(document, loader=self.none_loader) self.assertEqual(str(cm.exception), "xi:fallback tag must be child of xi:include " "('{http://www.w3.org/2001/XInclude}fallback')") # Test infinitely recursive includes. document = self.xinclude_loader("Recursive1.xml") with self.assertRaises(ElementInclude.FatalIncludeError) as cm: ElementInclude.include(document, self.xinclude_loader) self.assertEqual(str(cm.exception), "recursive include of Recursive2.xml") # Test 'max_depth' limitation. document = self.xinclude_loader("Recursive1.xml") with self.assertRaises(ElementInclude.FatalIncludeError) as cm: ElementInclude.include(document, self.xinclude_loader, max_depth=None) self.assertEqual(str(cm.exception), "recursive include of Recursive2.xml") document = self.xinclude_loader("Recursive1.xml") with self.assertRaises(ElementInclude.LimitedRecursiveIncludeError) as cm: ElementInclude.include(document, self.xinclude_loader, max_depth=0) self.assertEqual(str(cm.exception), "maximum xinclude depth reached when including file Recursive2.xml") document = self.xinclude_loader("Recursive1.xml") with self.assertRaises(ElementInclude.LimitedRecursiveIncludeError) as cm: ElementInclude.include(document, self.xinclude_loader, max_depth=1) self.assertEqual(str(cm.exception), "maximum xinclude depth reached when including file Recursive3.xml") document = self.xinclude_loader("Recursive1.xml") with self.assertRaises(ElementInclude.LimitedRecursiveIncludeError) as cm: ElementInclude.include(document, self.xinclude_loader, max_depth=2) self.assertEqual(str(cm.exception), "maximum xinclude depth reached when including file Recursive1.xml") document = self.xinclude_loader("Recursive1.xml") with self.assertRaises(ElementInclude.FatalIncludeError) as cm: ElementInclude.include(document, self.xinclude_loader, max_depth=3) self.assertEqual(str(cm.exception), "recursive include of Recursive2.xml") # -------------------------------------------------------------------- # reported bugs class BugsTest(unittest.TestCase): def test_bug_xmltoolkit21(self): # marshaller gives obscure errors for non-string values def check(elem): with self.assertRaises(TypeError) as cm: serialize(elem) self.assertEqual(str(cm.exception), 'cannot serialize 123 (type int)') elem = ET.Element(123) check(elem) # tag elem = ET.Element("elem") elem.text = 123 check(elem) # text elem = ET.Element("elem") elem.tail = 123 check(elem) # tail elem = ET.Element("elem") elem.set(123, "123") check(elem) # attribute key elem = ET.Element("elem") elem.set("123", 123) check(elem) # attribute value def test_bug_xmltoolkit25(self): # typo in ElementTree.findtext elem = ET.XML(SAMPLE_XML) tree = ET.ElementTree(elem) self.assertEqual(tree.findtext("tag"), 'text') self.assertEqual(tree.findtext("section/tag"), 'subtext') def test_bug_xmltoolkit28(self): # .//tag causes exceptions tree = ET.XML("
") self.assertEqual(summarize_list(tree.findall(".//thead")), []) self.assertEqual(summarize_list(tree.findall(".//tbody")), ['tbody']) def test_bug_xmltoolkitX1(self): # dump() doesn't flush the output buffer tree = ET.XML("
") with support.captured_stdout() as stdout: ET.dump(tree) self.assertEqual(stdout.getvalue(), '
\n') def test_bug_xmltoolkit39(self): # non-ascii element and attribute names doesn't work tree = ET.XML(b"") self.assertEqual(ET.tostring(tree, "utf-8"), b'') tree = ET.XML(b"" b"") self.assertEqual(tree.attrib, {'\xe4ttr': 'v\xe4lue'}) self.assertEqual(ET.tostring(tree, "utf-8"), b'') tree = ET.XML(b"" b'text') self.assertEqual(ET.tostring(tree, "utf-8"), b'text') tree = ET.Element("t\u00e4g") self.assertEqual(ET.tostring(tree, "utf-8"), b'') tree = ET.Element("tag") tree.set("\u00e4ttr", "v\u00e4lue") self.assertEqual(ET.tostring(tree, "utf-8"), b'') def test_bug_xmltoolkit54(self): # problems handling internally defined entities e = ET.XML("]>" '&ldots;') self.assertEqual(serialize(e, encoding="us-ascii"), b'') self.assertEqual(serialize(e), '\u8230') def test_bug_xmltoolkit55(self): # make sure we're reporting the first error, not the last with self.assertRaises(ET.ParseError) as cm: ET.XML(b"" b'&ldots;&ndots;&rdots;') self.assertEqual(str(cm.exception), 'undefined entity &ldots;: line 1, column 36') def test_bug_xmltoolkit60(self): # Handle crash in stream source. class ExceptionFile: def read(self, x): raise OSError self.assertRaises(OSError, ET.parse, ExceptionFile()) def test_bug_xmltoolkit62(self): # Don't crash when using custom entities. ENTITIES = {'rsquo': '\u2019', 'lsquo': '\u2018'} parser = ET.XMLParser() parser.entity.update(ENTITIES) parser.feed(""" A new cultivar of Begonia plant named ‘BCT9801BEG’. """) t = parser.close() self.assertEqual(t.find('.//paragraph').text, 'A new cultivar of Begonia plant named \u2018BCT9801BEG\u2019.') @unittest.skipIf(sys.gettrace(), "Skips under coverage.") def test_bug_xmltoolkit63(self): # Check reference leak. def xmltoolkit63(): tree = ET.TreeBuilder() tree.start("tag", {}) tree.data("text") tree.end("tag") xmltoolkit63() count = sys.getrefcount(None) for i in range(1000): xmltoolkit63() self.assertEqual(sys.getrefcount(None), count) def test_bug_200708_newline(self): # Preserve newlines in attributes. e = ET.Element('SomeTag', text="def _f():\n return 3\n") self.assertEqual(ET.tostring(e), b'') self.assertEqual(ET.XML(ET.tostring(e)).get("text"), 'def _f():\n return 3\n') self.assertEqual(ET.tostring(ET.XML(ET.tostring(e))), b'') def test_bug_200708_close(self): # Test default builder. parser = ET.XMLParser() # default parser.feed("some text") self.assertEqual(parser.close().tag, 'element') # Test custom builder. class EchoTarget: def close(self): return ET.Element("element") # simulate root parser = ET.XMLParser(target=EchoTarget()) parser.feed("some text") self.assertEqual(parser.close().tag, 'element') def test_bug_200709_default_namespace(self): e = ET.Element("{default}elem") s = ET.SubElement(e, "{default}elem") self.assertEqual(serialize(e, default_namespace="default"), # 1 '') e = ET.Element("{default}elem") s = ET.SubElement(e, "{default}elem") s = ET.SubElement(e, "{not-default}elem") self.assertEqual(serialize(e, default_namespace="default"), # 2 '' '' '' '') e = ET.Element("{default}elem") s = ET.SubElement(e, "{default}elem") s = ET.SubElement(e, "elem") # unprefixed name with self.assertRaises(ValueError) as cm: serialize(e, default_namespace="default") # 3 self.assertEqual(str(cm.exception), 'cannot use non-qualified names with default_namespace option') def test_bug_200709_register_namespace(self): e = ET.Element("{http://namespace.invalid/does/not/exist/}title") self.assertEqual(ET.tostring(e), b'') ET.register_namespace("foo", "http://namespace.invalid/does/not/exist/") e = ET.Element("{http://namespace.invalid/does/not/exist/}title") self.assertEqual(ET.tostring(e), b'') # And the Dublin Core namespace is in the default list: e = ET.Element("{http://purl.org/dc/elements/1.1/}title") self.assertEqual(ET.tostring(e), b'') def test_bug_200709_element_comment(self): # Not sure if this can be fixed, really (since the serializer needs # ET.Comment, not cET.comment). a = ET.Element('a') a.append(ET.Comment('foo')) self.assertEqual(a[0].tag, ET.Comment) a = ET.Element('a') a.append(ET.PI('foo')) self.assertEqual(a[0].tag, ET.PI) def test_bug_200709_element_insert(self): a = ET.Element('a') b = ET.SubElement(a, 'b') c = ET.SubElement(a, 'c') d = ET.Element('d') a.insert(0, d) self.assertEqual(summarize_list(a), ['d', 'b', 'c']) a.insert(-1, d) self.assertEqual(summarize_list(a), ['d', 'b', 'd', 'c']) def test_bug_200709_iter_comment(self): a = ET.Element('a') b = ET.SubElement(a, 'b') comment_b = ET.Comment("TEST-b") b.append(comment_b) self.assertEqual(summarize_list(a.iter(ET.Comment)), [ET.Comment]) # -------------------------------------------------------------------- # reported on bugs.python.org def test_bug_1534630(self): bob = ET.TreeBuilder() e = bob.data("data") e = bob.start("tag", {}) e = bob.end("tag") e = bob.close() self.assertEqual(serialize(e), '') def test_issue6233(self): e = ET.XML(b"" b't\xc3\xa3g') self.assertEqual(ET.tostring(e, 'ascii'), b"\n" b'tãg') e = ET.XML(b"" b't\xe3g') self.assertEqual(ET.tostring(e, 'ascii'), b"\n" b'tãg') def test_issue6565(self): elem = ET.XML("") self.assertEqual(summarize_list(elem), ['tag']) newelem = ET.XML(SAMPLE_XML) elem[:] = newelem[:] self.assertEqual(summarize_list(elem), ['tag', 'tag', 'section']) def test_issue10777(self): # Registering a namespace twice caused a "dictionary changed size during # iteration" bug. ET.register_namespace('test10777', 'http://myuri/') ET.register_namespace('test10777', 'http://myuri/') def test_lost_text(self): # Issue #25902: Borrowed text can disappear class Text: def __bool__(self): e.text = 'changed' return True e = ET.Element('tag') e.text = Text() i = e.itertext() t = next(i) self.assertIsInstance(t, Text) self.assertIsInstance(e.text, str) self.assertEqual(e.text, 'changed') def test_lost_tail(self): # Issue #25902: Borrowed tail can disappear class Text: def __bool__(self): e[0].tail = 'changed' return True e = ET.Element('root') e.append(ET.Element('tag')) e[0].tail = Text() i = e.itertext() t = next(i) self.assertIsInstance(t, Text) self.assertIsInstance(e[0].tail, str) self.assertEqual(e[0].tail, 'changed') def test_lost_elem(self): # Issue #25902: Borrowed element can disappear class Tag: def __eq__(self, other): e[0] = ET.Element('changed') next(i) return True e = ET.Element('root') e.append(ET.Element(Tag())) e.append(ET.Element('tag')) i = e.iter('tag') try: t = next(i) except ValueError: self.skipTest('generators are not reentrant') self.assertIsInstance(t.tag, Tag) self.assertIsInstance(e[0].tag, str) self.assertEqual(e[0].tag, 'changed') def check_expat224_utf8_bug(self, text): xml = b'' % text root = ET.XML(xml) self.assertEqual(root.get('b'), text.decode('utf-8')) def test_expat224_utf8_bug(self): # bpo-31170: Expat 2.2.3 had a bug in its UTF-8 decoder. # Check that Expat 2.2.4 fixed the bug. # # Test buffer bounds at odd and even positions. text = b'\xc3\xa0' * 1024 self.check_expat224_utf8_bug(text) text = b'x' + b'\xc3\xa0' * 1024 self.check_expat224_utf8_bug(text) def test_expat224_utf8_bug_file(self): with open(UTF8_BUG_XMLFILE, 'rb') as fp: raw = fp.read() root = ET.fromstring(raw) xmlattr = root.get('b') # "Parse" manually the XML file to extract the value of the 'b' # attribute of the XML element text = raw.decode('utf-8').strip() text = text.replace('\r\n', ' ') text = text[6:-4] self.assertEqual(root.get('b'), text) def test_39495_treebuilder_start(self): self.assertRaises(TypeError, ET.TreeBuilder().start, "tag") self.assertRaises(TypeError, ET.TreeBuilder().start, "tag", None) def test_issue123213_correct_extend_exception(self): # Does not hide the internal exception when extending the element self.assertRaises(ZeroDivisionError, ET.Element('tag').extend, (1/0 for i in range(2))) # Still raises the TypeError when extending with a non-iterable self.assertRaises(TypeError, ET.Element('tag').extend, None) # Preserves the TypeError message when extending with a generator def f(): raise TypeError("mymessage") self.assertRaisesRegex( TypeError, 'mymessage', ET.Element('tag').extend, (f() for i in range(2))) # -------------------------------------------------------------------- class BasicElementTest(ElementTestCase, unittest.TestCase): def test___init__(self): tag = "foo" attrib = { "zix": "wyp" } element_foo = ET.Element(tag, attrib) # traits of an element self.assertIsInstance(element_foo, ET.Element) self.assertIn("tag", dir(element_foo)) self.assertIn("attrib", dir(element_foo)) self.assertIn("text", dir(element_foo)) self.assertIn("tail", dir(element_foo)) # string attributes have expected values self.assertEqual(element_foo.tag, tag) self.assertIsNone(element_foo.text) self.assertIsNone(element_foo.tail) # attrib is a copy self.assertIsNot(element_foo.attrib, attrib) self.assertEqual(element_foo.attrib, attrib) # attrib isn't linked attrib["bar"] = "baz" self.assertIsNot(element_foo.attrib, attrib) self.assertNotEqual(element_foo.attrib, attrib) def test___copy__(self): element_foo = ET.Element("foo", { "zix": "wyp" }) element_foo.append(ET.Element("bar", { "baz": "qix" })) element_foo2 = copy.copy(element_foo) # elements are not the same self.assertIsNot(element_foo2, element_foo) # string attributes are equal self.assertEqual(element_foo2.tag, element_foo.tag) self.assertEqual(element_foo2.text, element_foo.text) self.assertEqual(element_foo2.tail, element_foo.tail) # number of children is the same self.assertEqual(len(element_foo2), len(element_foo)) # children are the same for (child1, child2) in itertools.zip_longest(element_foo, element_foo2): self.assertIs(child1, child2) # attrib is a copy self.assertEqual(element_foo2.attrib, element_foo.attrib) def test___deepcopy__(self): element_foo = ET.Element("foo", { "zix": "wyp" }) element_foo.append(ET.Element("bar", { "baz": "qix" })) element_foo2 = copy.deepcopy(element_foo) # elements are not the same self.assertIsNot(element_foo2, element_foo) # string attributes are equal self.assertEqual(element_foo2.tag, element_foo.tag) self.assertEqual(element_foo2.text, element_foo.text) self.assertEqual(element_foo2.tail, element_foo.tail) # number of children is the same self.assertEqual(len(element_foo2), len(element_foo)) # children are not the same for (child1, child2) in itertools.zip_longest(element_foo, element_foo2): self.assertIsNot(child1, child2) # attrib is a copy self.assertIsNot(element_foo2.attrib, element_foo.attrib) self.assertEqual(element_foo2.attrib, element_foo.attrib) # attrib isn't linked element_foo.attrib["bar"] = "baz" self.assertIsNot(element_foo2.attrib, element_foo.attrib) self.assertNotEqual(element_foo2.attrib, element_foo.attrib) def test_augmentation_type_errors(self): e = ET.Element('joe') self.assertRaises(TypeError, e.append, 'b') self.assertRaises(TypeError, e.extend, [ET.Element('bar'), 'foo']) self.assertRaises(TypeError, e.insert, 0, 'foo') e[:] = [ET.Element('bar')] with self.assertRaises(TypeError): e[0] = 'foo' with self.assertRaises(TypeError): e[:] = [ET.Element('bar'), 'foo'] if hasattr(e, '__setstate__'): state = { 'tag': 'tag', '_children': [None], # non-Element 'attrib': 'attr', 'tail': 'tail', 'text': 'text', } self.assertRaises(TypeError, e.__setstate__, state) if hasattr(e, '__deepcopy__'): class E(ET.Element): def __deepcopy__(self, memo): return None # non-Element e[:] = [E('bar')] self.assertRaises(TypeError, copy.deepcopy, e) def test_cyclic_gc(self): class Dummy: pass # Test the shortest cycle: d->element->d d = Dummy() d.dummyref = ET.Element('joe', attr=d) wref = weakref.ref(d) del d gc_collect() self.assertIsNone(wref()) # A longer cycle: d->e->e2->d e = ET.Element('joe') d = Dummy() d.dummyref = e wref = weakref.ref(d) e2 = ET.SubElement(e, 'foo', attr=d) del d, e, e2 gc_collect() self.assertIsNone(wref()) # A cycle between Element objects as children of one another # e1->e2->e3->e1 e1 = ET.Element('e1') e2 = ET.Element('e2') e3 = ET.Element('e3') e3.append(e1) e2.append(e3) e1.append(e2) wref = weakref.ref(e1) del e1, e2, e3 gc_collect() self.assertIsNone(wref()) def test_weakref(self): flag = False def wref_cb(w): nonlocal flag flag = True e = ET.Element('e') wref = weakref.ref(e, wref_cb) self.assertEqual(wref().tag, 'e') del e gc_collect() # For PyPy or other GCs. self.assertEqual(flag, True) self.assertEqual(wref(), None) def test_get_keyword_args(self): e1 = ET.Element('foo' , x=1, y=2, z=3) self.assertEqual(e1.get('x', default=7), 1) self.assertEqual(e1.get('w', default=7), 7) def test_pickle(self): # issue #16076: the C implementation wasn't pickleable. for proto in range(2, pickle.HIGHEST_PROTOCOL + 1): for dumper, loader in product(self.modules, repeat=2): e = dumper.Element('foo', bar=42) e.text = "text goes here" e.tail = "opposite of head" dumper.SubElement(e, 'child').append(dumper.Element('grandchild')) e.append(dumper.Element('child')) e.findall('.//grandchild')[0].set('attr', 'other value') e2 = self.pickleRoundTrip(e, 'xml.etree.ElementTree', dumper, loader, proto) self.assertEqual(e2.tag, 'foo') self.assertEqual(e2.attrib['bar'], 42) self.assertEqual(len(e2), 2) self.assertEqualElements(e, e2) def test_pickle_issue18997(self): for proto in range(2, pickle.HIGHEST_PROTOCOL + 1): for dumper, loader in product(self.modules, repeat=2): XMLTEXT = """ 4 """ e1 = dumper.fromstring(XMLTEXT) self.assertEqual(e1.__getstate__()['tag'], 'group') e2 = self.pickleRoundTrip(e1, 'xml.etree.ElementTree', dumper, loader, proto) self.assertEqual(e2.tag, 'group') self.assertEqual(e2[0].tag, 'dogs') class BadElementTest(ElementTestCase, unittest.TestCase): def test_extend_mutable_list(self): class X: @property def __class__(self): L[:] = [ET.Element('baz')] return ET.Element L = [X()] e = ET.Element('foo') try: e.extend(L) except TypeError: pass class Y(X, ET.Element): pass L = [Y('x')] e = ET.Element('foo') e.extend(L) def test_extend_mutable_list2(self): class X: @property def __class__(self): del L[:] return ET.Element L = [X(), ET.Element('baz')] e = ET.Element('foo') try: e.extend(L) except TypeError: pass class Y(X, ET.Element): pass L = [Y('bar'), ET.Element('baz')] e = ET.Element('foo') e.extend(L) def test_remove_with_mutating(self): class X(ET.Element): def __eq__(self, o): del e[:] return False e = ET.Element('foo') e.extend([X('bar')]) self.assertRaises(ValueError, e.remove, ET.Element('baz')) e = ET.Element('foo') e.extend([ET.Element('bar')]) self.assertRaises(ValueError, e.remove, X('baz')) @support.infinite_recursion(25) def test_recursive_repr(self): # Issue #25455 e = ET.Element('foo') with swap_attr(e, 'tag', e): with self.assertRaises(RuntimeError): repr(e) # Should not crash def test_element_get_text(self): # Issue #27863 class X(str): def __del__(self): try: elem.text except NameError: pass b = ET.TreeBuilder() b.start('tag', {}) b.data('ABCD') b.data(X('EFGH')) b.data('IJKL') b.end('tag') elem = b.close() self.assertEqual(elem.text, 'ABCDEFGHIJKL') def test_element_get_tail(self): # Issue #27863 class X(str): def __del__(self): try: elem[0].tail except NameError: pass b = ET.TreeBuilder() b.start('root', {}) b.start('tag', {}) b.end('tag') b.data('ABCD') b.data(X('EFGH')) b.data('IJKL') b.end('root') elem = b.close() self.assertEqual(elem[0].tail, 'ABCDEFGHIJKL') def test_subscr(self): # Issue #27863 class X: def __index__(self): del e[:] return 1 e = ET.Element('elem') e.append(ET.Element('child')) e[:X()] # shouldn't crash e.append(ET.Element('child')) e[0:10:X()] # shouldn't crash def test_ass_subscr(self): # Issue #27863 class X: def __index__(self): e[:] = [] return 1 e = ET.Element('elem') for _ in range(10): e.insert(0, ET.Element('child')) e[0:10:X()] = [] # shouldn't crash def test_treebuilder_start(self): # Issue #27863 def element_factory(x, y): return [] b = ET.TreeBuilder(element_factory=element_factory) b.start('tag', {}) b.data('ABCD') self.assertRaises(AttributeError, b.start, 'tag2', {}) del b gc_collect() def test_treebuilder_end(self): # Issue #27863 def element_factory(x, y): return [] b = ET.TreeBuilder(element_factory=element_factory) b.start('tag', {}) b.data('ABCD') self.assertRaises(AttributeError, b.end, 'tag') del b gc_collect() class MutatingElementPath(str): def __new__(cls, elem, *args): self = str.__new__(cls, *args) self.elem = elem return self def __eq__(self, o): del self.elem[:] return True MutatingElementPath.__hash__ = str.__hash__ class BadElementPath(str): def __eq__(self, o): raise 1/0 BadElementPath.__hash__ = str.__hash__ class BadElementPathTest(ElementTestCase, unittest.TestCase): def setUp(self): super().setUp() from xml.etree import ElementPath self.path_cache = ElementPath._cache ElementPath._cache = {} def tearDown(self): from xml.etree import ElementPath ElementPath._cache = self.path_cache super().tearDown() def test_find_with_mutating(self): e = ET.Element('foo') e.extend([ET.Element('bar')]) e.find(MutatingElementPath(e, 'x')) def test_find_with_error(self): e = ET.Element('foo') e.extend([ET.Element('bar')]) try: e.find(BadElementPath('x')) except ZeroDivisionError: pass def test_findtext_with_mutating(self): e = ET.Element('foo') e.extend([ET.Element('bar')]) e.findtext(MutatingElementPath(e, 'x')) def test_findtext_with_error(self): e = ET.Element('foo') e.extend([ET.Element('bar')]) try: e.findtext(BadElementPath('x')) except ZeroDivisionError: pass def test_findtext_with_falsey_text_attribute(self): root_elem = ET.Element('foo') sub_elem = ET.SubElement(root_elem, 'bar') falsey = ["", 0, False, [], (), {}] for val in falsey: sub_elem.text = val self.assertEqual(root_elem.findtext('./bar'), val) def test_findtext_with_none_text_attribute(self): root_elem = ET.Element('foo') sub_elem = ET.SubElement(root_elem, 'bar') sub_elem.text = None self.assertEqual(root_elem.findtext('./bar'), '') def test_findall_with_mutating(self): e = ET.Element('foo') e.extend([ET.Element('bar')]) e.findall(MutatingElementPath(e, 'x')) def test_findall_with_error(self): e = ET.Element('foo') e.extend([ET.Element('bar')]) try: e.findall(BadElementPath('x')) except ZeroDivisionError: pass class ElementTreeTypeTest(unittest.TestCase): def test_istype(self): self.assertIsInstance(ET.ParseError, type) self.assertIsInstance(ET.QName, type) self.assertIsInstance(ET.ElementTree, type) self.assertIsInstance(ET.Element, type) self.assertIsInstance(ET.TreeBuilder, type) self.assertIsInstance(ET.XMLParser, type) def test_Element_subclass_trivial(self): class MyElement(ET.Element): pass mye = MyElement('foo') self.assertIsInstance(mye, ET.Element) self.assertIsInstance(mye, MyElement) self.assertEqual(mye.tag, 'foo') # test that attribute assignment works (issue 14849) mye.text = "joe" self.assertEqual(mye.text, "joe") def test_Element_subclass_constructor(self): class MyElement(ET.Element): def __init__(self, tag, attrib={}, **extra): super(MyElement, self).__init__(tag + '__', attrib, **extra) mye = MyElement('foo', {'a': 1, 'b': 2}, c=3, d=4) self.assertEqual(mye.tag, 'foo__') self.assertEqual(sorted(mye.items()), [('a', 1), ('b', 2), ('c', 3), ('d', 4)]) def test_Element_subclass_new_method(self): class MyElement(ET.Element): def newmethod(self): return self.tag mye = MyElement('joe') self.assertEqual(mye.newmethod(), 'joe') def test_Element_subclass_find(self): class MyElement(ET.Element): pass e = ET.Element('foo') e.text = 'text' sub = MyElement('bar') sub.text = 'subtext' e.append(sub) self.assertEqual(e.findtext('bar'), 'subtext') self.assertEqual(e.find('bar').tag, 'bar') found = list(e.findall('bar')) self.assertEqual(len(found), 1, found) self.assertEqual(found[0].tag, 'bar') class ElementFindTest(unittest.TestCase): def test_find_simple(self): e = ET.XML(SAMPLE_XML) self.assertEqual(e.find('tag').tag, 'tag') self.assertEqual(e.find('section/tag').tag, 'tag') self.assertEqual(e.find('./tag').tag, 'tag') e[2] = ET.XML(SAMPLE_SECTION) self.assertEqual(e.find('section/nexttag').tag, 'nexttag') self.assertEqual(e.findtext('./tag'), 'text') self.assertEqual(e.findtext('section/tag'), 'subtext') # section/nexttag is found but has no text self.assertEqual(e.findtext('section/nexttag'), '') self.assertEqual(e.findtext('section/nexttag', 'default'), '') # tog doesn't exist and 'default' kicks in self.assertIsNone(e.findtext('tog')) self.assertEqual(e.findtext('tog', 'default'), 'default') # Issue #16922 self.assertEqual(ET.XML('').findtext('empty'), '') def test_find_xpath(self): LINEAR_XML = ''' ''' e = ET.XML(LINEAR_XML) # Test for numeric indexing and last() self.assertEqual(e.find('./tag[1]').attrib['class'], 'a') self.assertEqual(e.find('./tag[2]').attrib['class'], 'b') self.assertEqual(e.find('./tag[last()]').attrib['class'], 'd') self.assertEqual(e.find('./tag[last()-1]').attrib['class'], 'c') self.assertEqual(e.find('./tag[last()-2]').attrib['class'], 'b') self.assertRaisesRegex(SyntaxError, 'XPath', e.find, './tag[0]') self.assertRaisesRegex(SyntaxError, 'XPath', e.find, './tag[-1]') self.assertRaisesRegex(SyntaxError, 'XPath', e.find, './tag[last()-0]') self.assertRaisesRegex(SyntaxError, 'XPath', e.find, './tag[last()+1]') def test_findall(self): e = ET.XML(SAMPLE_XML) e[2] = ET.XML(SAMPLE_SECTION) self.assertEqual(summarize_list(e.findall('.')), ['body']) self.assertEqual(summarize_list(e.findall('tag')), ['tag', 'tag']) self.assertEqual(summarize_list(e.findall('tog')), []) self.assertEqual(summarize_list(e.findall('tog/foo')), []) self.assertEqual(summarize_list(e.findall('*')), ['tag', 'tag', 'section']) self.assertEqual(summarize_list(e.findall('.//tag')), ['tag'] * 4) self.assertEqual(summarize_list(e.findall('section/tag')), ['tag']) self.assertEqual(summarize_list(e.findall('section//tag')), ['tag'] * 2) self.assertEqual(summarize_list(e.findall('section/*')), ['tag', 'nexttag', 'nextsection']) self.assertEqual(summarize_list(e.findall('section//*')), ['tag', 'nexttag', 'nextsection', 'tag']) self.assertEqual(summarize_list(e.findall('section/.//*')), ['tag', 'nexttag', 'nextsection', 'tag']) self.assertEqual(summarize_list(e.findall('*/*')), ['tag', 'nexttag', 'nextsection']) self.assertEqual(summarize_list(e.findall('*//*')), ['tag', 'nexttag', 'nextsection', 'tag']) self.assertEqual(summarize_list(e.findall('*/tag')), ['tag']) self.assertEqual(summarize_list(e.findall('*/./tag')), ['tag']) self.assertEqual(summarize_list(e.findall('./tag')), ['tag'] * 2) self.assertEqual(summarize_list(e.findall('././tag')), ['tag'] * 2) self.assertEqual(summarize_list(e.findall('.//tag[@class]')), ['tag'] * 3) self.assertEqual(summarize_list(e.findall('.//tag[@class="a"]')), ['tag']) self.assertEqual(summarize_list(e.findall('.//tag[@class!="a"]')), ['tag'] * 2) self.assertEqual(summarize_list(e.findall('.//tag[@class="b"]')), ['tag'] * 2) self.assertEqual(summarize_list(e.findall('.//tag[@class!="b"]')), ['tag']) self.assertEqual(summarize_list(e.findall('.//tag[@id]')), ['tag']) self.assertEqual(summarize_list(e.findall('.//section[tag]')), ['section']) self.assertEqual(summarize_list(e.findall('.//section[element]')), []) self.assertEqual(summarize_list(e.findall('../tag')), []) self.assertEqual(summarize_list(e.findall('section/../tag')), ['tag'] * 2) self.assertEqual(e.findall('section//'), e.findall('section//*')) self.assertEqual(summarize_list(e.findall(".//section[tag='subtext']")), ['section']) self.assertEqual(summarize_list(e.findall(".//section[tag ='subtext']")), ['section']) self.assertEqual(summarize_list(e.findall(".//section[tag= 'subtext']")), ['section']) self.assertEqual(summarize_list(e.findall(".//section[tag = 'subtext']")), ['section']) self.assertEqual(summarize_list(e.findall(".//section[ tag = 'subtext' ]")), ['section']) # Negations of above tests. They match nothing because the sole section # tag has subtext. self.assertEqual(summarize_list(e.findall(".//section[tag!='subtext']")), []) self.assertEqual(summarize_list(e.findall(".//section[tag !='subtext']")), []) self.assertEqual(summarize_list(e.findall(".//section[tag!= 'subtext']")), []) self.assertEqual(summarize_list(e.findall(".//section[tag != 'subtext']")), []) self.assertEqual(summarize_list(e.findall(".//section[ tag != 'subtext' ]")), []) self.assertEqual(summarize_list(e.findall(".//tag[.='subtext']")), ['tag']) self.assertEqual(summarize_list(e.findall(".//tag[. ='subtext']")), ['tag']) self.assertEqual(summarize_list(e.findall('.//tag[.= "subtext"]')), ['tag']) self.assertEqual(summarize_list(e.findall('.//tag[ . = "subtext" ]')), ['tag']) self.assertEqual(summarize_list(e.findall(".//tag[. = 'subtext']")), ['tag']) self.assertEqual(summarize_list(e.findall(".//tag[. = 'subtext ']")), []) self.assertEqual(summarize_list(e.findall(".//tag[.= ' subtext']")), []) # Negations of above tests. # Matches everything but the tag containing subtext self.assertEqual(summarize_list(e.findall(".//tag[.!='subtext']")), ['tag'] * 3) self.assertEqual(summarize_list(e.findall(".//tag[. !='subtext']")), ['tag'] * 3) self.assertEqual(summarize_list(e.findall('.//tag[.!= "subtext"]')), ['tag'] * 3) self.assertEqual(summarize_list(e.findall('.//tag[ . != "subtext" ]')), ['tag'] * 3) self.assertEqual(summarize_list(e.findall(".//tag[. != 'subtext']")), ['tag'] * 3) # Matches all tags. self.assertEqual(summarize_list(e.findall(".//tag[. != 'subtext ']")), ['tag'] * 4) self.assertEqual(summarize_list(e.findall(".//tag[.!= ' subtext']")), ['tag'] * 4) # duplicate section => 2x tag matches e[1] = e[2] self.assertEqual(summarize_list(e.findall(".//section[tag = 'subtext']")), ['section', 'section']) self.assertEqual(summarize_list(e.findall(".//tag[. = 'subtext']")), ['tag', 'tag']) def test_test_find_with_ns(self): e = ET.XML(SAMPLE_XML_NS) self.assertEqual(summarize_list(e.findall('tag')), []) self.assertEqual( summarize_list(e.findall("{http://effbot.org/ns}tag")), ['{http://effbot.org/ns}tag'] * 2) self.assertEqual( summarize_list(e.findall(".//{http://effbot.org/ns}tag")), ['{http://effbot.org/ns}tag'] * 3) def test_findall_different_nsmaps(self): root = ET.XML(''' ''') nsmap = {'xx': 'X'} self.assertEqual(len(root.findall(".//xx:b", namespaces=nsmap)), 2) self.assertEqual(len(root.findall(".//b", namespaces=nsmap)), 2) nsmap = {'xx': 'Y'} self.assertEqual(len(root.findall(".//xx:b", namespaces=nsmap)), 1) self.assertEqual(len(root.findall(".//b", namespaces=nsmap)), 2) nsmap = {'xx': 'X', '': 'Y'} self.assertEqual(len(root.findall(".//xx:b", namespaces=nsmap)), 2) self.assertEqual(len(root.findall(".//b", namespaces=nsmap)), 1) def test_findall_wildcard(self): root = ET.XML(''' ''') root.append(ET.Comment('test')) self.assertEqual(summarize_list(root.findall("{*}b")), ['{X}b', 'b', '{Y}b']) self.assertEqual(summarize_list(root.findall("{*}c")), ['c']) self.assertEqual(summarize_list(root.findall("{X}*")), ['{X}b']) self.assertEqual(summarize_list(root.findall("{Y}*")), ['{Y}b']) self.assertEqual(summarize_list(root.findall("{}*")), ['b', 'c']) self.assertEqual(summarize_list(root.findall("{}b")), # only for consistency ['b']) self.assertEqual(summarize_list(root.findall("{}b")), summarize_list(root.findall("b"))) self.assertEqual(summarize_list(root.findall("{*}*")), ['{X}b', 'b', 'c', '{Y}b']) # This is an unfortunate difference, but that's how find('*') works. self.assertEqual(summarize_list(root.findall("{*}*") + [root[-1]]), summarize_list(root.findall("*"))) self.assertEqual(summarize_list(root.findall(".//{*}b")), ['{X}b', 'b', '{X}b', 'b', '{Y}b']) self.assertEqual(summarize_list(root.findall(".//{*}c")), ['c', 'c']) self.assertEqual(summarize_list(root.findall(".//{X}*")), ['{X}b', '{X}b']) self.assertEqual(summarize_list(root.findall(".//{Y}*")), ['{Y}b']) self.assertEqual(summarize_list(root.findall(".//{}*")), ['c', 'b', 'c', 'b']) self.assertEqual(summarize_list(root.findall(".//{}b")), # only for consistency ['b', 'b']) self.assertEqual(summarize_list(root.findall(".//{}b")), summarize_list(root.findall(".//b"))) def test_bad_find(self): e = ET.XML(SAMPLE_XML) with self.assertRaisesRegex(SyntaxError, 'cannot use absolute path'): e.findall('/tag') def test_find_through_ElementTree(self): e = ET.XML(SAMPLE_XML) self.assertEqual(ET.ElementTree(e).find('tag').tag, 'tag') self.assertEqual(ET.ElementTree(e).findtext('tag'), 'text') self.assertEqual(summarize_list(ET.ElementTree(e).findall('tag')), ['tag'] * 2) # this produces a warning msg = ("This search is broken in 1.3 and earlier, and will be fixed " "in a future version. If you rely on the current behaviour, " "change it to '.+'") with self.assertWarnsRegex(FutureWarning, msg): it = ET.ElementTree(e).findall('//tag') self.assertEqual(summarize_list(it), ['tag'] * 3) class ElementIterTest(unittest.TestCase): def _ilist(self, elem, tag=None): return summarize_list(elem.iter(tag)) def test_basic(self): doc = ET.XML("this is a paragraph...") self.assertEqual(self._ilist(doc), ['html', 'body', 'i']) self.assertEqual(self._ilist(doc.find('body')), ['body', 'i']) self.assertEqual(next(doc.iter()).tag, 'html') self.assertEqual(''.join(doc.itertext()), 'this is a paragraph...') self.assertEqual(''.join(doc.find('body').itertext()), 'this is a paragraph.') self.assertEqual(next(doc.itertext()), 'this is a ') # iterparse should return an iterator sourcefile = serialize(doc, to_string=False) self.assertEqual(next(ET.iterparse(sourcefile))[0], 'end') # With an explicit parser too (issue #9708) sourcefile = serialize(doc, to_string=False) parser = ET.XMLParser(target=ET.TreeBuilder()) self.assertEqual(next(ET.iterparse(sourcefile, parser=parser))[0], 'end') tree = ET.ElementTree(None) self.assertRaises(AttributeError, tree.iter) # Issue #16913 doc = ET.XML("a&b&c&") self.assertEqual(''.join(doc.itertext()), 'a&b&c&') def test_corners(self): # single root, no subelements a = ET.Element('a') self.assertEqual(self._ilist(a), ['a']) # one child b = ET.SubElement(a, 'b') self.assertEqual(self._ilist(a), ['a', 'b']) # one child and one grandchild c = ET.SubElement(b, 'c') self.assertEqual(self._ilist(a), ['a', 'b', 'c']) # two children, only first with grandchild d = ET.SubElement(a, 'd') self.assertEqual(self._ilist(a), ['a', 'b', 'c', 'd']) # replace first child by second a[0] = a[1] del a[1] self.assertEqual(self._ilist(a), ['a', 'd']) def test_iter_by_tag(self): doc = ET.XML(''' bedroom1 bedroom2 nothing here bedroom8 ''') self.assertEqual(self._ilist(doc, 'room'), ['room'] * 3) self.assertEqual(self._ilist(doc, 'house'), ['house'] * 2) # test that iter also accepts 'tag' as a keyword arg self.assertEqual( summarize_list(doc.iter(tag='room')), ['room'] * 3) # make sure both tag=None and tag='*' return all tags all_tags = ['document', 'house', 'room', 'room', 'shed', 'house', 'room'] self.assertEqual(summarize_list(doc.iter()), all_tags) self.assertEqual(self._ilist(doc), all_tags) self.assertEqual(self._ilist(doc, '*'), all_tags) def test_copy(self): a = ET.Element('a') it = a.iter() with self.assertRaises(TypeError): copy.copy(it) def test_pickle(self): a = ET.Element('a') it = a.iter() for proto in range(pickle.HIGHEST_PROTOCOL + 1): with self.assertRaises((TypeError, pickle.PicklingError)): pickle.dumps(it, proto) class TreeBuilderTest(unittest.TestCase): sample1 = ('' 'text
subtext
tail') sample2 = '''sometext''' def _check_sample1_element(self, e): self.assertEqual(e.tag, 'html') self.assertEqual(e.text, 'text') self.assertEqual(e.tail, None) self.assertEqual(e.attrib, {}) children = list(e) self.assertEqual(len(children), 1) child = children[0] self.assertEqual(child.tag, 'div') self.assertEqual(child.text, 'subtext') self.assertEqual(child.tail, 'tail') self.assertEqual(child.attrib, {}) def test_dummy_builder(self): class BaseDummyBuilder: def close(self): return 42 class DummyBuilder(BaseDummyBuilder): data = start = end = lambda *a: None parser = ET.XMLParser(target=DummyBuilder()) parser.feed(self.sample1) self.assertEqual(parser.close(), 42) parser = ET.XMLParser(target=BaseDummyBuilder()) parser.feed(self.sample1) self.assertEqual(parser.close(), 42) parser = ET.XMLParser(target=object()) parser.feed(self.sample1) self.assertIsNone(parser.close()) def test_treebuilder_comment(self): b = ET.TreeBuilder() self.assertEqual(b.comment('ctext').tag, ET.Comment) self.assertEqual(b.comment('ctext').text, 'ctext') b = ET.TreeBuilder(comment_factory=ET.Comment) self.assertEqual(b.comment('ctext').tag, ET.Comment) self.assertEqual(b.comment('ctext').text, 'ctext') b = ET.TreeBuilder(comment_factory=len) self.assertEqual(b.comment('ctext'), len('ctext')) def test_treebuilder_pi(self): b = ET.TreeBuilder() self.assertEqual(b.pi('target', None).tag, ET.PI) self.assertEqual(b.pi('target', None).text, 'target') b = ET.TreeBuilder(pi_factory=ET.PI) self.assertEqual(b.pi('target').tag, ET.PI) self.assertEqual(b.pi('target').text, "target") self.assertEqual(b.pi('pitarget', ' text ').tag, ET.PI) self.assertEqual(b.pi('pitarget', ' text ').text, "pitarget text ") b = ET.TreeBuilder(pi_factory=lambda target, text: (len(target), text)) self.assertEqual(b.pi('target'), (len('target'), None)) self.assertEqual(b.pi('pitarget', ' text '), (len('pitarget'), ' text ')) def test_late_tail(self): # Issue #37399: The tail of an ignored comment could overwrite the text before it. class TreeBuilderSubclass(ET.TreeBuilder): pass xml = "texttail" a = ET.fromstring(xml) self.assertEqual(a.text, "texttail") parser = ET.XMLParser(target=TreeBuilderSubclass()) parser.feed(xml) a = parser.close() self.assertEqual(a.text, "texttail") xml = "texttail" a = ET.fromstring(xml) self.assertEqual(a.text, "texttail") xml = "texttail" parser = ET.XMLParser(target=TreeBuilderSubclass()) parser.feed(xml) a = parser.close() self.assertEqual(a.text, "texttail") def test_late_tail_mix_pi_comments(self): # Issue #37399: The tail of an ignored comment could overwrite the text before it. # Test appending tails to comments/pis. class TreeBuilderSubclass(ET.TreeBuilder): pass xml = "text \ntail" parser = ET.XMLParser(target=ET.TreeBuilder(insert_comments=True)) parser.feed(xml) a = parser.close() self.assertEqual(a[0].text, ' comment ') self.assertEqual(a[0].tail, '\ntail') self.assertEqual(a.text, "text ") parser = ET.XMLParser(target=TreeBuilderSubclass(insert_comments=True)) parser.feed(xml) a = parser.close() self.assertEqual(a[0].text, ' comment ') self.assertEqual(a[0].tail, '\ntail') self.assertEqual(a.text, "text ") xml = "text\ntail" parser = ET.XMLParser(target=ET.TreeBuilder(insert_pis=True)) parser.feed(xml) a = parser.close() self.assertEqual(a[0].text, 'pi data') self.assertEqual(a[0].tail, 'tail') self.assertEqual(a.text, "text\n") parser = ET.XMLParser(target=TreeBuilderSubclass(insert_pis=True)) parser.feed(xml) a = parser.close() self.assertEqual(a[0].text, 'pi data') self.assertEqual(a[0].tail, 'tail') self.assertEqual(a.text, "text\n") def test_treebuilder_elementfactory_none(self): parser = ET.XMLParser(target=ET.TreeBuilder(element_factory=None)) parser.feed(self.sample1) e = parser.close() self._check_sample1_element(e) def test_subclass(self): class MyTreeBuilder(ET.TreeBuilder): def foobar(self, x): return x * 2 tb = MyTreeBuilder() self.assertEqual(tb.foobar(10), 20) parser = ET.XMLParser(target=tb) parser.feed(self.sample1) e = parser.close() self._check_sample1_element(e) def test_subclass_comment_pi(self): class MyTreeBuilder(ET.TreeBuilder): def foobar(self, x): return x * 2 tb = MyTreeBuilder(comment_factory=ET.Comment, pi_factory=ET.PI) self.assertEqual(tb.foobar(10), 20) parser = ET.XMLParser(target=tb) parser.feed(self.sample1) parser.feed('') e = parser.close() self._check_sample1_element(e) def test_element_factory(self): lst = [] def myfactory(tag, attrib): nonlocal lst lst.append(tag) return ET.Element(tag, attrib) tb = ET.TreeBuilder(element_factory=myfactory) parser = ET.XMLParser(target=tb) parser.feed(self.sample2) parser.close() self.assertEqual(lst, ['toplevel']) def _check_element_factory_class(self, cls): tb = ET.TreeBuilder(element_factory=cls) parser = ET.XMLParser(target=tb) parser.feed(self.sample1) e = parser.close() self.assertIsInstance(e, cls) self._check_sample1_element(e) def test_element_factory_subclass(self): class MyElement(ET.Element): pass self._check_element_factory_class(MyElement) def test_element_factory_pure_python_subclass(self): # Mimic SimpleTAL's behaviour (issue #16089): both versions of # TreeBuilder should be able to cope with a subclass of the # pure Python Element class. base = ET._Element_Py # Not from a C extension self.assertEqual(base.__module__, 'xml.etree.ElementTree') # Force some multiple inheritance with a C class to make things # more interesting. class MyElement(base, ValueError): pass self._check_element_factory_class(MyElement) def test_doctype(self): class DoctypeParser: _doctype = None def doctype(self, name, pubid, system): self._doctype = (name, pubid, system) def close(self): return self._doctype parser = ET.XMLParser(target=DoctypeParser()) parser.feed(self.sample1) self.assertEqual(parser.close(), ('html', '-//W3C//DTD XHTML 1.0 Transitional//EN', 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd')) def test_builder_lookup_errors(self): class RaisingBuilder: def __init__(self, raise_in=None, what=ValueError): self.raise_in = raise_in self.what = what def __getattr__(self, name): if name == self.raise_in: raise self.what(self.raise_in) def handle(*args): pass return handle ET.XMLParser(target=RaisingBuilder()) # cET also checks for 'close' and 'doctype', PyET does it only at need for event in ('start', 'data', 'end', 'comment', 'pi'): with self.assertRaisesRegex(ValueError, event): ET.XMLParser(target=RaisingBuilder(event)) ET.XMLParser(target=RaisingBuilder(what=AttributeError)) for event in ('start', 'data', 'end', 'comment', 'pi'): parser = ET.XMLParser(target=RaisingBuilder(event, what=AttributeError)) parser.feed(self.sample1) self.assertIsNone(parser.close()) class XMLParserTest(unittest.TestCase): sample1 = b'22' sample2 = (b'' b'text') sample3 = ('\n' '$\xa3\u20ac\U0001017b') def _check_sample_element(self, e): self.assertEqual(e.tag, 'file') self.assertEqual(e[0].tag, 'line') self.assertEqual(e[0].text, '22') def test_constructor_args(self): parser2 = ET.XMLParser(encoding='utf-8', target=ET.TreeBuilder()) parser2.feed(self.sample1) self._check_sample_element(parser2.close()) def test_subclass(self): class MyParser(ET.XMLParser): pass parser = MyParser() parser.feed(self.sample1) self._check_sample_element(parser.close()) def test_doctype_warning(self): with warnings.catch_warnings(): warnings.simplefilter('error', DeprecationWarning) parser = ET.XMLParser() parser.feed(self.sample2) parser.close() def test_subclass_doctype(self): _doctype = None class MyParserWithDoctype(ET.XMLParser): def doctype(self, *args, **kwargs): nonlocal _doctype _doctype = (args, kwargs) parser = MyParserWithDoctype() with self.assertWarnsRegex(RuntimeWarning, 'doctype'): parser.feed(self.sample2) parser.close() self.assertIsNone(_doctype) _doctype = _doctype2 = None with warnings.catch_warnings(): warnings.simplefilter('error', DeprecationWarning) warnings.simplefilter('error', RuntimeWarning) class DoctypeParser: def doctype(self, name, pubid, system): nonlocal _doctype2 _doctype2 = (name, pubid, system) parser = MyParserWithDoctype(target=DoctypeParser()) parser.feed(self.sample2) parser.close() self.assertIsNone(_doctype) self.assertEqual(_doctype2, ('html', '-//W3C//DTD XHTML 1.0 Transitional//EN', 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd')) def test_inherited_doctype(self): '''Ensure that ordinary usage is not deprecated (Issue 19176)''' with warnings.catch_warnings(): warnings.simplefilter('error', DeprecationWarning) warnings.simplefilter('error', RuntimeWarning) class MyParserWithoutDoctype(ET.XMLParser): pass parser = MyParserWithoutDoctype() parser.feed(self.sample2) parser.close() def test_parse_string(self): parser = ET.XMLParser(target=ET.TreeBuilder()) parser.feed(self.sample3) e = parser.close() self.assertEqual(e.tag, 'money') self.assertEqual(e.attrib['value'], '$\xa3\u20ac\U0001017b') self.assertEqual(e.text, '$\xa3\u20ac\U0001017b') class NamespaceParseTest(unittest.TestCase): def test_find_with_namespace(self): nsmap = {'h': 'hello', 'f': 'foo'} doc = ET.fromstring(SAMPLE_XML_NS_ELEMS) self.assertEqual(len(doc.findall('{hello}table', nsmap)), 1) self.assertEqual(len(doc.findall('.//{hello}td', nsmap)), 2) self.assertEqual(len(doc.findall('.//{foo}name', nsmap)), 1) class ElementSlicingTest(unittest.TestCase): def _elem_tags(self, elemlist): return [e.tag for e in elemlist] def _subelem_tags(self, elem): return self._elem_tags(list(elem)) def _make_elem_with_children(self, numchildren): """Create an Element with a tag 'a', with the given amount of children named 'a0', 'a1' ... and so on. """ e = ET.Element('a') for i in range(numchildren): ET.SubElement(e, 'a%s' % i) return e def test_getslice_single_index(self): e = self._make_elem_with_children(10) self.assertEqual(e[1].tag, 'a1') self.assertEqual(e[-2].tag, 'a8') self.assertRaises(IndexError, lambda: e[12]) self.assertRaises(IndexError, lambda: e[-12]) def test_getslice_range(self): e = self._make_elem_with_children(6) self.assertEqual(self._elem_tags(e[3:]), ['a3', 'a4', 'a5']) self.assertEqual(self._elem_tags(e[3:6]), ['a3', 'a4', 'a5']) self.assertEqual(self._elem_tags(e[3:16]), ['a3', 'a4', 'a5']) self.assertEqual(self._elem_tags(e[3:5]), ['a3', 'a4']) self.assertEqual(self._elem_tags(e[3:-1]), ['a3', 'a4']) self.assertEqual(self._elem_tags(e[:2]), ['a0', 'a1']) def test_getslice_steps(self): e = self._make_elem_with_children(10) self.assertEqual(self._elem_tags(e[8:10:1]), ['a8', 'a9']) self.assertEqual(self._elem_tags(e[::3]), ['a0', 'a3', 'a6', 'a9']) self.assertEqual(self._elem_tags(e[::8]), ['a0', 'a8']) self.assertEqual(self._elem_tags(e[1::8]), ['a1', 'a9']) self.assertEqual(self._elem_tags(e[3::sys.maxsize]), ['a3']) self.assertEqual(self._elem_tags(e[3::sys.maxsize<<64]), ['a3']) def test_getslice_negative_steps(self): e = self._make_elem_with_children(4) self.assertEqual(self._elem_tags(e[::-1]), ['a3', 'a2', 'a1', 'a0']) self.assertEqual(self._elem_tags(e[::-2]), ['a3', 'a1']) self.assertEqual(self._elem_tags(e[3::-sys.maxsize]), ['a3']) self.assertEqual(self._elem_tags(e[3::-sys.maxsize-1]), ['a3']) self.assertEqual(self._elem_tags(e[3::-sys.maxsize<<64]), ['a3']) def test_delslice(self): e = self._make_elem_with_children(4) del e[0:2] self.assertEqual(self._subelem_tags(e), ['a2', 'a3']) e = self._make_elem_with_children(4) del e[0:] self.assertEqual(self._subelem_tags(e), []) e = self._make_elem_with_children(4) del e[::-1] self.assertEqual(self._subelem_tags(e), []) e = self._make_elem_with_children(4) del e[::-2] self.assertEqual(self._subelem_tags(e), ['a0', 'a2']) e = self._make_elem_with_children(4) del e[1::2] self.assertEqual(self._subelem_tags(e), ['a0', 'a2']) e = self._make_elem_with_children(2) del e[::2] self.assertEqual(self._subelem_tags(e), ['a1']) def test_setslice_single_index(self): e = self._make_elem_with_children(4) e[1] = ET.Element('b') self.assertEqual(self._subelem_tags(e), ['a0', 'b', 'a2', 'a3']) e[-2] = ET.Element('c') self.assertEqual(self._subelem_tags(e), ['a0', 'b', 'c', 'a3']) with self.assertRaises(IndexError): e[5] = ET.Element('d') with self.assertRaises(IndexError): e[-5] = ET.Element('d') self.assertEqual(self._subelem_tags(e), ['a0', 'b', 'c', 'a3']) def test_setslice_range(self): e = self._make_elem_with_children(4) e[1:3] = [ET.Element('b%s' % i) for i in range(2)] self.assertEqual(self._subelem_tags(e), ['a0', 'b0', 'b1', 'a3']) e = self._make_elem_with_children(4) e[1:3] = [ET.Element('b')] self.assertEqual(self._subelem_tags(e), ['a0', 'b', 'a3']) e = self._make_elem_with_children(4) e[1:3] = [ET.Element('b%s' % i) for i in range(3)] self.assertEqual(self._subelem_tags(e), ['a0', 'b0', 'b1', 'b2', 'a3']) def test_setslice_steps(self): e = self._make_elem_with_children(6) e[1:5:2] = [ET.Element('b%s' % i) for i in range(2)] self.assertEqual(self._subelem_tags(e), ['a0', 'b0', 'a2', 'b1', 'a4', 'a5']) e = self._make_elem_with_children(6) with self.assertRaises(ValueError): e[1:5:2] = [ET.Element('b')] with self.assertRaises(ValueError): e[1:5:2] = [ET.Element('b%s' % i) for i in range(3)] with self.assertRaises(ValueError): e[1:5:2] = [] self.assertEqual(self._subelem_tags(e), ['a0', 'a1', 'a2', 'a3', 'a4', 'a5']) e = self._make_elem_with_children(4) e[1::sys.maxsize] = [ET.Element('b')] self.assertEqual(self._subelem_tags(e), ['a0', 'b', 'a2', 'a3']) e[1::sys.maxsize<<64] = [ET.Element('c')] self.assertEqual(self._subelem_tags(e), ['a0', 'c', 'a2', 'a3']) def test_setslice_negative_steps(self): e = self._make_elem_with_children(4) e[2:0:-1] = [ET.Element('b%s' % i) for i in range(2)] self.assertEqual(self._subelem_tags(e), ['a0', 'b1', 'b0', 'a3']) e = self._make_elem_with_children(4) with self.assertRaises(ValueError): e[2:0:-1] = [ET.Element('b')] with self.assertRaises(ValueError): e[2:0:-1] = [ET.Element('b%s' % i) for i in range(3)] with self.assertRaises(ValueError): e[2:0:-1] = [] self.assertEqual(self._subelem_tags(e), ['a0', 'a1', 'a2', 'a3']) e = self._make_elem_with_children(4) e[1::-sys.maxsize] = [ET.Element('b')] self.assertEqual(self._subelem_tags(e), ['a0', 'b', 'a2', 'a3']) e[1::-sys.maxsize-1] = [ET.Element('c')] self.assertEqual(self._subelem_tags(e), ['a0', 'c', 'a2', 'a3']) e[1::-sys.maxsize<<64] = [ET.Element('d')] self.assertEqual(self._subelem_tags(e), ['a0', 'd', 'a2', 'a3']) def test_issue123213_setslice_exception(self): e = ET.Element('tag') # Does not hide the internal exception when assigning to the element with self.assertRaises(ZeroDivisionError): e[:1] = (1/0 for i in range(2)) # Still raises the TypeError when assigning with a non-iterable with self.assertRaises(TypeError): e[:1] = None # Preserve the original TypeError message when assigning. def f(): raise TypeError("mymessage") with self.assertRaisesRegex(TypeError, 'mymessage'): e[:1] = (f() for i in range(2)) class IOTest(unittest.TestCase): def test_encoding(self): # Test encoding issues. elem = ET.Element("tag") elem.text = "abc" self.assertEqual(serialize(elem), 'abc') for enc in ("utf-8", "us-ascii"): with self.subTest(enc): self.assertEqual(serialize(elem, encoding=enc), b'abc') self.assertEqual(serialize(elem, encoding=enc.upper()), b'abc') for enc in ("iso-8859-1", "utf-16", "utf-32"): with self.subTest(enc): self.assertEqual(serialize(elem, encoding=enc), ("\n" "abc" % enc).encode(enc)) upper = enc.upper() self.assertEqual(serialize(elem, encoding=upper), ("\n" "abc" % upper).encode(enc)) elem = ET.Element("tag") elem.text = "<&\"\'>" self.assertEqual(serialize(elem), '<&"\'>') self.assertEqual(serialize(elem, encoding="utf-8"), b'<&"\'>') self.assertEqual(serialize(elem, encoding="us-ascii"), b'<&"\'>') for enc in ("iso-8859-1", "utf-16", "utf-32"): self.assertEqual(serialize(elem, encoding=enc), ("\n" "<&\"'>" % enc).encode(enc)) elem = ET.Element("tag") elem.attrib["key"] = "<&\"\'>" self.assertEqual(serialize(elem), '') self.assertEqual(serialize(elem, encoding="utf-8"), b'') self.assertEqual(serialize(elem, encoding="us-ascii"), b'') for enc in ("iso-8859-1", "utf-16", "utf-32"): self.assertEqual(serialize(elem, encoding=enc), ("\n" "" % enc).encode(enc)) elem = ET.Element("tag") elem.text = '\xe5\xf6\xf6<>' self.assertEqual(serialize(elem), '\xe5\xf6\xf6<>') self.assertEqual(serialize(elem, encoding="utf-8"), b'\xc3\xa5\xc3\xb6\xc3\xb6<>') self.assertEqual(serialize(elem, encoding="us-ascii"), b'åöö<>') for enc in ("iso-8859-1", "utf-16", "utf-32"): self.assertEqual(serialize(elem, encoding=enc), ("\n" "åöö<>" % enc).encode(enc)) elem = ET.Element("tag") elem.attrib["key"] = '\xe5\xf6\xf6<>' self.assertEqual(serialize(elem), '') self.assertEqual(serialize(elem, encoding="utf-8"), b'') self.assertEqual(serialize(elem, encoding="us-ascii"), b'') for enc in ("iso-8859-1", "utf-16", "utf-16le", "utf-16be", "utf-32"): self.assertEqual(serialize(elem, encoding=enc), ("\n" "" % enc).encode(enc)) def test_write_to_filename(self): self.addCleanup(os_helper.unlink, TESTFN) tree = ET.ElementTree(ET.XML('''\xf8''')) tree.write(TESTFN) with open(TESTFN, 'rb') as f: self.assertEqual(f.read(), b'''ø''') def test_write_to_filename_with_encoding(self): self.addCleanup(os_helper.unlink, TESTFN) tree = ET.ElementTree(ET.XML('''\xf8''')) tree.write(TESTFN, encoding='utf-8') with open(TESTFN, 'rb') as f: self.assertEqual(f.read(), b'''\xc3\xb8''') tree.write(TESTFN, encoding='ISO-8859-1') with open(TESTFN, 'rb') as f: self.assertEqual(f.read(), convlinesep( b'''\n''' b'''\xf8''')) def test_write_to_filename_as_unicode(self): self.addCleanup(os_helper.unlink, TESTFN) with open(TESTFN, 'w') as f: encoding = f.encoding os_helper.unlink(TESTFN) tree = ET.ElementTree(ET.XML('''\xf8''')) tree.write(TESTFN, encoding='unicode') with open(TESTFN, 'rb') as f: self.assertEqual(f.read(), b"\xc3\xb8") def test_write_to_text_file(self): self.addCleanup(os_helper.unlink, TESTFN) tree = ET.ElementTree(ET.XML('''\xf8''')) with open(TESTFN, 'w', encoding='utf-8') as f: tree.write(f, encoding='unicode') self.assertFalse(f.closed) with open(TESTFN, 'rb') as f: self.assertEqual(f.read(), b'''\xc3\xb8''') with open(TESTFN, 'w', encoding='ascii', errors='xmlcharrefreplace') as f: tree.write(f, encoding='unicode') self.assertFalse(f.closed) with open(TESTFN, 'rb') as f: self.assertEqual(f.read(), b'''ø''') with open(TESTFN, 'w', encoding='ISO-8859-1') as f: tree.write(f, encoding='unicode') self.assertFalse(f.closed) with open(TESTFN, 'rb') as f: self.assertEqual(f.read(), b'''\xf8''') def test_write_to_binary_file(self): self.addCleanup(os_helper.unlink, TESTFN) tree = ET.ElementTree(ET.XML('''\xf8''')) with open(TESTFN, 'wb') as f: tree.write(f) self.assertFalse(f.closed) with open(TESTFN, 'rb') as f: self.assertEqual(f.read(), b'''ø''') def test_write_to_binary_file_with_encoding(self): self.addCleanup(os_helper.unlink, TESTFN) tree = ET.ElementTree(ET.XML('''\xf8''')) with open(TESTFN, 'wb') as f: tree.write(f, encoding='utf-8') self.assertFalse(f.closed) with open(TESTFN, 'rb') as f: self.assertEqual(f.read(), b'''\xc3\xb8''') with open(TESTFN, 'wb') as f: tree.write(f, encoding='ISO-8859-1') self.assertFalse(f.closed) with open(TESTFN, 'rb') as f: self.assertEqual(f.read(), b'''\n''' b'''\xf8''') def test_write_to_binary_file_with_bom(self): self.addCleanup(os_helper.unlink, TESTFN) tree = ET.ElementTree(ET.XML('''\xf8''')) # test BOM writing to buffered file with open(TESTFN, 'wb') as f: tree.write(f, encoding='utf-16') self.assertFalse(f.closed) with open(TESTFN, 'rb') as f: self.assertEqual(f.read(), '''\n''' '''\xf8'''.encode("utf-16")) # test BOM writing to non-buffered file with open(TESTFN, 'wb', buffering=0) as f: tree.write(f, encoding='utf-16') self.assertFalse(f.closed) with open(TESTFN, 'rb') as f: self.assertEqual(f.read(), '''\n''' '''\xf8'''.encode("utf-16")) def test_read_from_stringio(self): tree = ET.ElementTree() stream = io.StringIO('''''') tree.parse(stream) self.assertEqual(tree.getroot().tag, 'site') def test_write_to_stringio(self): tree = ET.ElementTree(ET.XML('''\xf8''')) stream = io.StringIO() tree.write(stream, encoding='unicode') self.assertEqual(stream.getvalue(), '''\xf8''') def test_read_from_bytesio(self): tree = ET.ElementTree() raw = io.BytesIO(b'''''') tree.parse(raw) self.assertEqual(tree.getroot().tag, 'site') def test_write_to_bytesio(self): tree = ET.ElementTree(ET.XML('''\xf8''')) raw = io.BytesIO() tree.write(raw) self.assertEqual(raw.getvalue(), b'''ø''') class dummy: pass def test_read_from_user_text_reader(self): stream = io.StringIO('''''') reader = self.dummy() reader.read = stream.read tree = ET.ElementTree() tree.parse(reader) self.assertEqual(tree.getroot().tag, 'site') def test_write_to_user_text_writer(self): tree = ET.ElementTree(ET.XML('''\xf8''')) stream = io.StringIO() writer = self.dummy() writer.write = stream.write tree.write(writer, encoding='unicode') self.assertEqual(stream.getvalue(), '''\xf8''') def test_read_from_user_binary_reader(self): raw = io.BytesIO(b'''''') reader = self.dummy() reader.read = raw.read tree = ET.ElementTree() tree.parse(reader) self.assertEqual(tree.getroot().tag, 'site') tree = ET.ElementTree() def test_write_to_user_binary_writer(self): tree = ET.ElementTree(ET.XML('''\xf8''')) raw = io.BytesIO() writer = self.dummy() writer.write = raw.write tree.write(writer) self.assertEqual(raw.getvalue(), b'''ø''') def test_write_to_user_binary_writer_with_bom(self): tree = ET.ElementTree(ET.XML('''''')) raw = io.BytesIO() writer = self.dummy() writer.write = raw.write writer.seekable = lambda: True writer.tell = raw.tell tree.write(writer, encoding="utf-16") self.assertEqual(raw.getvalue(), '''\n''' ''''''.encode("utf-16")) def test_tostringlist_invariant(self): root = ET.fromstring('foo') self.assertEqual( ET.tostring(root, 'unicode'), ''.join(ET.tostringlist(root, 'unicode'))) self.assertEqual( ET.tostring(root, 'utf-16'), b''.join(ET.tostringlist(root, 'utf-16'))) def test_short_empty_elements(self): root = ET.fromstring('abc') self.assertEqual( ET.tostring(root, 'unicode'), 'abc') self.assertEqual( ET.tostring(root, 'unicode', short_empty_elements=True), 'abc') self.assertEqual( ET.tostring(root, 'unicode', short_empty_elements=False), 'abc') class ParseErrorTest(unittest.TestCase): def test_subclass(self): self.assertIsInstance(ET.ParseError(), SyntaxError) def _get_error(self, s): try: ET.fromstring(s) except ET.ParseError as e: return e def test_error_position(self): self.assertEqual(self._get_error('foo').position, (1, 0)) self.assertEqual(self._get_error('&foo;').position, (1, 5)) self.assertEqual(self._get_error('foobar<').position, (1, 6)) def test_error_code(self): import xml.parsers.expat.errors as ERRORS self.assertEqual(self._get_error('foo').code, ERRORS.codes[ERRORS.XML_ERROR_SYNTAX]) class KeywordArgsTest(unittest.TestCase): # Test various issues with keyword arguments passed to ET.Element # constructor and methods def test_issue14818(self): x = ET.XML("foo") self.assertEqual(x.find('a', None), x.find(path='a', namespaces=None)) self.assertEqual(x.findtext('a', None, None), x.findtext(path='a', default=None, namespaces=None)) self.assertEqual(x.findall('a', None), x.findall(path='a', namespaces=None)) self.assertEqual(list(x.iterfind('a', None)), list(x.iterfind(path='a', namespaces=None))) self.assertEqual(ET.Element('a').attrib, {}) elements = [ ET.Element('a', dict(href="#", id="foo")), ET.Element('a', attrib=dict(href="#", id="foo")), ET.Element('a', dict(href="#"), id="foo"), ET.Element('a', href="#", id="foo"), ET.Element('a', dict(href="#", id="foo"), href="#", id="foo"), ] for e in elements: self.assertEqual(e.tag, 'a') self.assertEqual(e.attrib, dict(href="#", id="foo")) e2 = ET.SubElement(elements[0], 'foobar', attrib={'key1': 'value1'}) self.assertEqual(e2.attrib['key1'], 'value1') with self.assertRaisesRegex(TypeError, 'must be dict, not str'): ET.Element('a', "I'm not a dict") with self.assertRaisesRegex(TypeError, 'must be dict, not str'): ET.Element('a', attrib="I'm not a dict") # -------------------------------------------------------------------- class NoAcceleratorTest(unittest.TestCase): @classmethod def setUpClass(cls): if ET is not pyET: raise unittest.SkipTest('only for the Python version') # Test that the C accelerator was not imported for pyET def test_correct_import_pyET(self): # The type of methods defined in Python code is types.FunctionType, # while the type of methods defined inside _elementtree is # self.assertIsInstance(pyET.Element.__init__, types.FunctionType) self.assertIsInstance(pyET.XMLParser.__init__, types.FunctionType) # -------------------------------------------------------------------- class BoolTest(unittest.TestCase): def test_warning(self): e = ET.fromstring('') msg = ( r"Testing an element's truth value will always return True in " r"future versions. " r"Use specific 'len\(elem\)' or 'elem is not None' test instead.") with self.assertWarnsRegex(DeprecationWarning, msg): result = bool(e) # Emulate prior behavior for now self.assertIs(result, False) # Element with children ET.SubElement(e, 'b') with self.assertWarnsRegex(DeprecationWarning, msg): new_result = bool(e) self.assertIs(new_result, True) # -------------------------------------------------------------------- def c14n_roundtrip(xml, **options): return pyET.canonicalize(xml, **options) class C14NTest(unittest.TestCase): maxDiff = None # # simple roundtrip tests (from c14n.py) def test_simple_roundtrip(self): # Basics self.assertEqual(c14n_roundtrip(""), '') self.assertEqual(c14n_roundtrip(""), # FIXME '') self.assertEqual(c14n_roundtrip(""), '') self.assertEqual(c14n_roundtrip(""), '') self.assertEqual(c14n_roundtrip(""), '') # C14N spec self.assertEqual(c14n_roundtrip("Hello, world!"), 'Hello, world!') self.assertEqual(c14n_roundtrip("2"), '2') self.assertEqual(c14n_roundtrip('"0" && value<"10" ?"valid":"error"]]>'), 'value>"0" && value<"10" ?"valid":"error"') self.assertEqual(c14n_roundtrip('''valid'''), 'valid') self.assertEqual(c14n_roundtrip(""), '') self.assertEqual(c14n_roundtrip(""), '') self.assertEqual(c14n_roundtrip(""), '') # fragments from PJ's tests #self.assertEqual(c14n_roundtrip(""), #'') # Namespace issues xml = '' self.assertEqual(c14n_roundtrip(xml), xml) xml = '' self.assertEqual(c14n_roundtrip(xml), xml) xml = '' self.assertEqual(c14n_roundtrip(xml), xml) def test_c14n_exclusion(self): xml = textwrap.dedent("""\ abtext btext dtext """) self.assertEqual( c14n_roundtrip(xml, strip_text=True), '' 'abtext' 'btext' 'dtext' '') self.assertEqual( c14n_roundtrip(xml, strip_text=True, exclude_attrs=['{http://example.com/x}attr']), '' 'abtext' 'btext' 'dtext' '') self.assertEqual( c14n_roundtrip(xml, strip_text=True, exclude_tags=['{http://example.com/x}d']), '' 'abtext' 'btext' '' '') self.assertEqual( c14n_roundtrip(xml, strip_text=True, exclude_attrs=['{http://example.com/x}attr'], exclude_tags=['{http://example.com/x}d']), '' 'abtext' 'btext' '' '') self.assertEqual( c14n_roundtrip(xml, strip_text=True, exclude_tags=['a', 'b']), '' 'dtext' '') self.assertEqual( c14n_roundtrip(xml, exclude_tags=['a', 'b']), '\n' ' \n' ' \n' ' \n' ' dtext\n' ' \n' '') self.assertEqual( c14n_roundtrip(xml, strip_text=True, exclude_tags=['{http://example.com/x}d', 'b']), '' '' '' '') self.assertEqual( c14n_roundtrip(xml, exclude_tags=['{http://example.com/x}d', 'b']), '\n' ' \n' ' \n' ' \n' ' \n' ' \n' ' \n' ' \n' '') # # basic method=c14n tests from the c14n 2.0 specification. uses # test files under xmltestdata/c14n-20. # note that this uses generated C14N versions of the standard ET.write # output, not roundtripped C14N (see above). def test_xml_c14n2(self): datadir = findfile("c14n-20", subdir="xmltestdata") full_path = partial(os.path.join, datadir) files = [filename[:-4] for filename in sorted(os.listdir(datadir)) if filename.endswith('.xml')] input_files = [ filename for filename in files if filename.startswith('in') ] configs = { filename: { # sequential option.tag.split('}')[-1]: ((option.text or '').strip(), option) for option in ET.parse(full_path(filename) + ".xml").getroot() } for filename in files if filename.startswith('c14n') } tests = { input_file: [ (filename, configs[filename.rsplit('_', 1)[-1]]) for filename in files if filename.startswith(f'out_{input_file}_') and filename.rsplit('_', 1)[-1] in configs ] for input_file in input_files } # Make sure we found all test cases. self.assertEqual(30, len([ output_file for output_files in tests.values() for output_file in output_files])) def get_option(config, option_name, default=None): return config.get(option_name, (default, ()))[0] for input_file, output_files in tests.items(): for output_file, config in output_files: keep_comments = get_option( config, 'IgnoreComments') == 'true' # no, it's right :) strip_text = get_option( config, 'TrimTextNodes') == 'true' rewrite_prefixes = get_option( config, 'PrefixRewrite') == 'sequential' if 'QNameAware' in config: qattrs = [ f"{{{el.get('NS')}}}{el.get('Name')}" for el in config['QNameAware'][1].findall( '{http://www.w3.org/2010/xml-c14n2}QualifiedAttr') ] qtags = [ f"{{{el.get('NS')}}}{el.get('Name')}" for el in config['QNameAware'][1].findall( '{http://www.w3.org/2010/xml-c14n2}Element') ] else: qtags = qattrs = None # Build subtest description from config. config_descr = ','.join( f"{name}={value or ','.join(c.tag.split('}')[-1] for c in children)}" for name, (value, children) in sorted(config.items()) ) with self.subTest(f"{output_file}({config_descr})"): if input_file == 'inNsRedecl' and not rewrite_prefixes: self.skipTest( f"Redeclared namespace handling is not supported in {output_file}") if input_file == 'inNsSuperfluous' and not rewrite_prefixes: self.skipTest( f"Redeclared namespace handling is not supported in {output_file}") if 'QNameAware' in config and config['QNameAware'][1].find( '{http://www.w3.org/2010/xml-c14n2}XPathElement') is not None: self.skipTest( f"QName rewriting in XPath text is not supported in {output_file}") f = full_path(input_file + ".xml") if input_file == 'inC14N5': # Hack: avoid setting up external entity resolution in the parser. with open(full_path('world.txt'), 'rb') as entity_file: with open(f, 'rb') as f: f = io.BytesIO(f.read().replace(b'&ent2;', entity_file.read())) text = ET.canonicalize( from_file=f, with_comments=keep_comments, strip_text=strip_text, rewrite_prefixes=rewrite_prefixes, qname_aware_tags=qtags, qname_aware_attrs=qattrs) with open(full_path(output_file + ".xml"), 'r', encoding='utf8') as f: expected = f.read() if input_file == 'inC14N3': # FIXME: cET resolves default attributes but ET does not! expected = expected.replace(' attr="default"', '') text = text.replace(' attr="default"', '') self.assertEqual(expected, text) # -------------------------------------------------------------------- def setUpModule(module=None): # When invoked without a module, runs the Python ET tests by loading pyET. # Otherwise, uses the given module as the ET. global pyET pyET = import_fresh_module(module.__name__, blocked=['_elementtree']) if module is None: module = pyET global ET ET = module # don't interfere with subsequent tests def cleanup(): global ET, pyET ET = pyET = None unittest.addModuleCleanup(cleanup) # Provide default namespace mapping and path cache. from xml.etree import ElementPath nsmap = ET.register_namespace._namespace_map # Copy the default namespace mapping nsmap_copy = nsmap.copy() unittest.addModuleCleanup(nsmap.update, nsmap_copy) unittest.addModuleCleanup(nsmap.clear) # Copy the path cache (should be empty) path_cache = ElementPath._cache unittest.addModuleCleanup(setattr, ElementPath, "_cache", path_cache) ElementPath._cache = path_cache.copy() # Align the Comment/PI factories. if hasattr(ET, '_set_factories'): old_factories = ET._set_factories(ET.Comment, ET.PI) unittest.addModuleCleanup(ET._set_factories, *old_factories) if __name__ == '__main__': unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729876127.0 et_xmlfile-2.0.0/et_xmlfile/tests/_vendor/test/xmltestdata/expat224_utf8_bug.xml0000644000000000000000000000201414706750237024734 0ustar00 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729876127.0 et_xmlfile-2.0.0/et_xmlfile/tests/_vendor/test/xmltestdata/simple-ns.xml0000644000000000000000000000023014706750237023465 0ustar00 text texttail ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729876127.0 et_xmlfile-2.0.0/et_xmlfile/tests/_vendor/test/xmltestdata/simple.xml0000644000000000000000000000017214706750237023054 0ustar00 text texttail ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729876127.0 et_xmlfile-2.0.0/et_xmlfile/tests/common_imports.py0000644000000000000000000002103414706750237017513 0ustar00import os import os.path import re import gc import sys import unittest try: import urlparse except ImportError: import urllib.parse as urlparse try: from urllib import pathname2url except: from urllib.request import pathname2url def make_version_tuple(version_string): l = [] for part in re.findall('([0-9]+|[^0-9.]+)', version_string): try: l.append(int(part)) except ValueError: l.append(part) return tuple(l) IS_PYPY = (getattr(sys, 'implementation', None) == 'pypy' or getattr(sys, 'pypy_version_info', None) is not None) IS_PYTHON3 = sys.version_info[0] >= 3 try: from xml.etree import ElementTree # Python 2.5+ except ImportError: try: from elementtree import ElementTree # standard ET except ImportError: ElementTree = None if hasattr(ElementTree, 'VERSION'): ET_VERSION = make_version_tuple(ElementTree.VERSION) else: ET_VERSION = (0,0,0) try: from xml.etree import cElementTree # Python 2.5+ except ImportError: try: import cElementTree # standard ET except ImportError: cElementTree = None if hasattr(cElementTree, 'VERSION'): CET_VERSION = make_version_tuple(cElementTree.VERSION) else: CET_VERSION = (0,0,0) def filter_by_version(test_class, version_dict, current_version): """Remove test methods that do not work with the current lib version. """ find_required_version = version_dict.get def dummy_test_method(self): pass for name in dir(test_class): expected_version = find_required_version(name, (0,0,0)) if expected_version > current_version: setattr(test_class, name, dummy_test_method) try: import doctest # check if the system version has everything we need doctest.DocFileSuite doctest.DocTestParser doctest.NORMALIZE_WHITESPACE doctest.ELLIPSIS except (ImportError, AttributeError): # we need our own version to make it work (Python 2.3?) import local_doctest as doctest try: sorted except NameError: def sorted(seq, **kwargs): seq = list(seq) seq.sort(**kwargs) return seq else: locals()['sorted'] = sorted try: next except NameError: def next(it): return it.next() else: locals()['next'] = next try: import pytest except ImportError: class skipif(object): "Using a class because a function would bind into a method when used in classes" def __init__(self, *args): pass def __call__(self, func, *args): return func else: skipif = pytest.mark.skipif def _get_caller_relative_path(filename, frame_depth=2): module = sys.modules[sys._getframe(frame_depth).f_globals['__name__']] return os.path.normpath(os.path.join( os.path.dirname(getattr(module, '__file__', '')), filename)) from io import StringIO if sys.version_info[0] >= 3: # Python 3 from builtins import str as unicode def _str(s, encoding="UTF-8"): return s def _bytes(s, encoding="UTF-8"): return s.encode(encoding) from io import BytesIO as _BytesIO def BytesIO(*args): if args and isinstance(args[0], str): args = (args[0].encode("UTF-8"),) return _BytesIO(*args) doctest_parser = doctest.DocTestParser() _fix_unicode = re.compile(r'(\s+)u(["\'])').sub _fix_exceptions = re.compile(r'(.*except [^(]*),\s*(.*:)').sub def make_doctest(filename): filename = _get_caller_relative_path(filename) doctests = read_file(filename) doctests = _fix_unicode(r'\1\2', doctests) doctests = _fix_exceptions(r'\1 as \2', doctests) return doctest.DocTestCase( doctest_parser.get_doctest( doctests, {}, os.path.basename(filename), filename, 0)) else: # Python 2 from __builtin__ import unicode def _str(s, encoding="UTF-8"): return unicode(s, encoding=encoding) def _bytes(s, encoding="UTF-8"): return s from io import BytesIO doctest_parser = doctest.DocTestParser() _fix_traceback = re.compile(r'^(\s*)(?:\w+\.)+(\w*(?:Error|Exception|Invalid):)', re.M).sub _fix_exceptions = re.compile(r'(.*except [^(]*)\s+as\s+(.*:)').sub _fix_bytes = re.compile(r'(\s+)b(["\'])').sub def make_doctest(filename): filename = _get_caller_relative_path(filename) doctests = read_file(filename) doctests = _fix_traceback(r'\1\2', doctests) doctests = _fix_exceptions(r'\1, \2', doctests) doctests = _fix_bytes(r'\1\2', doctests) return doctest.DocTestCase( doctest_parser.get_doctest( doctests, {}, os.path.basename(filename), filename, 0)) try: skipIf = unittest.skipIf except AttributeError: def skipIf(condition, why, _skip=lambda test_method: None, _keep=lambda test_method: test_method): if condition: return _skip return _keep class HelperTestCase(unittest.TestCase): def tearDown(self): gc.collect() def parse(self, text, parser=None): f = BytesIO(text) if isinstance(text, bytes) else StringIO(text) return etree.parse(f, parser=parser) def _rootstring(self, tree): return etree.tostring(tree.getroot()).replace( _bytes(' '), _bytes('')).replace(_bytes('\n'), _bytes('')) # assertFalse doesn't exist in Python 2.3 try: unittest.TestCase.assertFalse except AttributeError: assertFalse = unittest.TestCase.failIf class SillyFileLike: def __init__(self, xml_data=_bytes('')): self.xml_data = xml_data def read(self, amount=None): if self.xml_data: if amount: data = self.xml_data[:amount] self.xml_data = self.xml_data[amount:] else: data = self.xml_data self.xml_data = _bytes('') return data return _bytes('') class LargeFileLike: def __init__(self, charlen=100, depth=4, children=5): self.data = BytesIO() self.chars = _bytes('a') * charlen self.children = range(children) self.more = self.iterelements(depth) def iterelements(self, depth): yield _bytes('') depth -= 1 if depth > 0: for child in self.children: for element in self.iterelements(depth): yield element yield self.chars else: yield self.chars yield _bytes('') def read(self, amount=None): data = self.data append = data.write if amount: for element in self.more: append(element) if data.tell() >= amount: break else: for element in self.more: append(element) result = data.getvalue() data.seek(0) data.truncate() if amount: append(result[amount:]) result = result[:amount] return result class LargeFileLikeUnicode(LargeFileLike): def __init__(self, charlen=100, depth=4, children=5): LargeFileLike.__init__(self, charlen, depth, children) self.data = StringIO() self.chars = _str('a') * charlen self.more = self.iterelements(depth) def iterelements(self, depth): yield _str('') depth -= 1 if depth > 0: for child in self.children: for element in self.iterelements(depth): yield element yield self.chars else: yield self.chars yield _str('') def fileInTestDir(name): _testdir = os.path.dirname(__file__) return os.path.join(_testdir, name) def path2url(path): return urlparse.urljoin( 'file:', pathname2url(path)) def fileUrlInTestDir(name): return path2url(fileInTestDir(name)) def read_file(name, mode='r'): f = open(name, mode) try: data = f.read() finally: f.close() return data def write_to_file(name, data, mode='w'): f = open(name, mode) try: data = f.write(data) finally: f.close() def readFileInTestDir(name, mode='r'): return read_file(fileInTestDir(name), mode) def canonicalize(xml): tree = etree.parse(BytesIO(xml) if isinstance(xml, bytes) else StringIO(xml)) f = BytesIO() tree.write_c14n(f) return f.getvalue() def unentitify(xml): for entity_name, value in re.findall("(&#([0-9]+);)", xml): xml = xml.replace(entity_name, unichr(int(value))) return xml ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729876127.0 et_xmlfile-2.0.0/et_xmlfile/tests/helper.py0000644000000000000000000000113514706750237015725 0ustar00from __future__ import absolute_import # Copyright (c) 2010-2015 openpyxl # Python stdlib imports from lxml.doctestcompare import LXMLOutputChecker, PARSE_XML def compare_xml(generated, expected): """Use doctest checking from lxml for comparing XML trees. Returns diff if the two are not the same""" checker = LXMLOutputChecker() class DummyDocTest(): pass ob = DummyDocTest() ob.want = expected check = checker.check_output(expected, generated, PARSE_XML) if check is False: diff = checker.output_difference(ob, generated, PARSE_XML) return diff ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729876127.0 et_xmlfile-2.0.0/et_xmlfile/tests/stdlib_base_tests.py0000644000000000000000000003521014706750237020144 0ustar00import io import platform import sys import unittest import unittest.case from et_xmlfile.tests._vendor.test import test_xml_etree from et_xmlfile.tests._vendor.test.test_xml_etree import * # noqa: F401, F403 old_serialize = test_xml_etree.serialize def serialize(elem, **options): if "root_ns_only" not in options: options["root_ns_only"] = True return old_serialize(elem, **options) def install_tests(mod_globals, modified_tests=None, more_skip_classes=None): # Test classes should have __module__ referring to this module. # We want to skip the module test as that tests for equivalence between # python's xml.etree.ElementTree and _elementtree modules which doesn't # apply here. skip_test_classes = [ "ModuleTest", "C14NTest", ] if more_skip_classes: skip_test_classes.extend(more_skip_classes) registered_tests = [] if modified_tests: for test_cls in modified_tests: name = test_cls.__name__ assert name not in mod_globals registered_tests.append(name) mod_globals[name] = test_cls for name, base in globals().items(): if name in skip_test_classes or name in registered_tests: continue if isinstance(base, type) and issubclass(base, unittest.TestCase): class Temp(base): pass Temp.__name__ = Temp.__qualname__ = name Temp.__module__ = mod_globals["__name__"] assert name not in mod_globals mod_globals[name] = Temp class ElementTreeTest(test_xml_etree.ElementTreeTest): def _test_iterparse_pre_3_13(self): # Test iterparse interface. iterparse = test_xml_etree.ET.iterparse context = iterparse(test_xml_etree.SIMPLE_XMLFILE) self.assertIsNone(context.root) action, elem = next(context) self.assertIsNone(context.root) self.assertEqual((action, elem.tag), ('end', 'element')) self.assertEqual([(action, elem.tag) for action, elem in context], [ ('end', 'element'), ('end', 'empty-element'), ('end', 'root'), ]) self.assertEqual(context.root.tag, 'root') context = iterparse(test_xml_etree.SIMPLE_NS_XMLFILE) self.assertEqual([(action, elem.tag) for action, elem in context], [ ('end', '{namespace}element'), ('end', '{namespace}element'), ('end', '{namespace}empty-element'), ('end', '{namespace}root'), ]) events = () context = iterparse(test_xml_etree.SIMPLE_XMLFILE, events) self.assertEqual([(action, elem.tag) for action, elem in context], []) events = () context = iterparse(test_xml_etree.SIMPLE_XMLFILE, events=events) self.assertEqual([(action, elem.tag) for action, elem in context], []) events = ("start", "end") context = iterparse(test_xml_etree.SIMPLE_XMLFILE, events) self.assertEqual([(action, elem.tag) for action, elem in context], [ ('start', 'root'), ('start', 'element'), ('end', 'element'), ('start', 'element'), ('end', 'element'), ('start', 'empty-element'), ('end', 'empty-element'), ('end', 'root'), ]) events = ("start", "end", "start-ns", "end-ns") context = iterparse(test_xml_etree.SIMPLE_NS_XMLFILE, events) self.assertEqual( [ (action, elem.tag) if action in ("start", "end") else (action, elem) for action, elem in context ], [ ('start-ns', ('', 'namespace')), ('start', '{namespace}root'), ('start', '{namespace}element'), ('end', '{namespace}element'), ('start', '{namespace}element'), ('end', '{namespace}element'), ('start', '{namespace}empty-element'), ('end', '{namespace}empty-element'), ('end', '{namespace}root'), ('end-ns', None), ] ) events = ('start-ns', 'end-ns') context = iterparse(io.StringIO(r""), events) res = [action for action, elem in context] self.assertEqual(res, ['start-ns', 'end-ns']) events = ("start", "end", "bogus") with open(test_xml_etree.SIMPLE_XMLFILE, "rb") as f: with self.assertRaises(ValueError) as cm: iterparse(f, events) self.assertFalse(f.closed) self.assertEqual(str(cm.exception), "unknown event 'bogus'") with test_xml_etree.warnings_helper.check_no_resource_warning(self): with self.assertRaises(ValueError) as cm: iterparse(test_xml_etree.SIMPLE_XMLFILE, events) self.assertEqual(str(cm.exception), "unknown event 'bogus'") del cm source = io.BytesIO( b"\n" b"text\n") events = ("start-ns",) context = iterparse(source, events) self.assertEqual([(action, elem) for action, elem in context], [ ('start-ns', ('', 'http://\xe9ffbot.org/ns')), ('start-ns', ('cl\xe9', 'http://effbot.org/ns')), ]) source = io.StringIO("junk") it = iterparse(source) action, elem = next(it) self.assertEqual((action, elem.tag), ('end', 'document')) with self.assertRaises(test_xml_etree.ET.ParseError) as cm: next(it) self.assertEqual( str(cm.exception), 'junk after document element: line 1, column 12', ) self.addCleanup(test_xml_etree.os_helper.unlink, test_xml_etree.TESTFN) with open(test_xml_etree.TESTFN, "wb") as f: f.write(b"junk") it = iterparse(test_xml_etree.TESTFN) action, elem = next(it) self.assertEqual((action, elem.tag), ('end', 'document')) with test_xml_etree.warnings_helper.check_no_resource_warning(self): with self.assertRaises(test_xml_etree.ET.ParseError) as cm: next(it) self.assertEqual( str(cm.exception), 'junk after document element: line 1, column 12', ) del cm, it # Not exhausting the iterator still closes the resource (bpo-43292) with test_xml_etree.warnings_helper.check_no_resource_warning(self): it = iterparse(test_xml_etree.TESTFN) del it with self.assertRaises(FileNotFoundError): iterparse("nonexistent") def test_iterparse(self): if sys.version_info[:2] < (3, 9): pass elif sys.version_info[:2] < (3, 13): self._test_iterparse_pre_3_13() else: super().test_iterparse() @unittest.skipIf( sys.version_info[:2] < (3, 13), "iterparse close not implemented" ) def test_iterparse_close(self): super().test_iterparse_close() def _test_html_empty_elems_serialization_pre_3_11(self): # issue 15970 # from http://www.w3.org/TR/html401/index/elements.html for element in ['AREA', 'BASE', 'BASEFONT', 'BR', 'COL', 'FRAME', 'HR', 'IMG', 'INPUT', 'ISINDEX', 'LINK', 'META', 'PARAM']: for elem in [element, element.lower()]: expected = '<%s>' % elem serialized = serialize(test_xml_etree.ET.XML('<%s />' % elem), method='html') self.assertEqual(serialized, expected) serialized = serialize( test_xml_etree.ET.XML('<%s>' % (elem, elem)), method='html', ) self.assertEqual(serialized, expected) def test_html_empty_elems_serialization(self): if sys.version_info[:2] < (3, 11): self._test_html_empty_elems_serialization_pre_3_11() else: super().test_html_empty_elems_serialization() @unittest.skipIf(sys.version_info[:2] < (3, 9), "Fails in py3.8") def test_attrib(self): super().test_attrib() @unittest.skipIf(sys.version_info[:2] < (3, 9), "py3.8 doesn't have indent") def test_indent(self): super().test_indent() @unittest.skipIf(sys.version_info[:2] < (3, 9), "py3.8 doesn't have indent") def test_indent_level(self): super().test_indent_level() @unittest.skipIf(sys.version_info[:2] < (3, 9), "py3.8 doesn't have indent") def test_indent_space(self): super().test_indent_space() @unittest.skipIf(sys.version_info[:2] < (3, 9), "py3.8 doesn't have indent") def test_indent_space_caching(self): super().test_indent_space_caching() @unittest.skipIf(sys.version_info[:3] < (3, 9, 11), "Test / change introduced in 3.9.11") def test_initialize_parser_without_target(self): super().test_initialize_parser_without_target() @unittest.skipIf( ( platform.python_implementation() == "PyPy" and sys.version_info[:3] < (3, 10, 15) ), "Functionality reverted but not picked up by PyPy yet", ) def test_simpleops(self): super().test_simpleops() class BasicElementTest(test_xml_etree.BasicElementTest): @unittest.skipIf( platform.python_implementation() == "PyPy", "Fails on pypy", ) def test_pickle_issue18997(self): super().test_pickle_issue18997() class BugsTest(test_xml_etree.BugsTest): @unittest.skipIf(sys.version_info[:2] < (3, 9), "Fails in py3.8") def test_39495_treebuilder_start(self): super().test_39495_treebuilder_start() @unittest.skipIf( platform.python_implementation() == "PyPy", "sys.getrefcount doesn't exist", ) def test_bug_xmltoolkit63(self): super().test_bug_xmltoolkit63() @unittest.skipIf( sys.version_info[:3] < (3, 12, 6), "Changed in 3.12.6", ) def test_issue123213_correct_extend_exception(self): super().test_issue123213_correct_extend_exception() class XIncludeTest(test_xml_etree.XIncludeTest): @unittest.skipIf(sys.version_info[:2] < (3, 9), "Fails in py3.8") def test_xinclude_failures(self): super().test_xinclude_failures() @unittest.skipIf(sys.version_info[:2] < (3, 9), "Fails in py3.8") def test_xinclude_repeated(self): super().test_xinclude_repeated() class BadElementPathTest(test_xml_etree.BadElementPathTest): @unittest.skipIf( sys.version_info[:2] < (3, 11), "Test fails / not available in Python 3.10 and lower.", ) def test_findtext_with_falsey_text_attribute(self): super().test_findtext_with_falsey_text_attribute() class NoAcceleratorTest(test_xml_etree.NoAcceleratorTest): @unittest.skipIf( sys.version_info[:2] < (3, 11), "Workaround for changes in import_fresh_module since 3.11 breaks some pyET tests", ) def test_correct_import_pyET(self): super().test_correct_import_pyET() class ElementFindTest(test_xml_etree.ElementFindTest): @unittest.skipIf( sys.version_info[:2] < (3, 10), "Support and tests were added for xpath != operator", ) def test_findall(self): super().test_findall() @unittest.skip("incremental_tree doesn't change the XMLPullParser so skip those tests") class XMLPullParserTest(test_xml_etree.XMLPullParserTest): pass class BoolTest(test_xml_etree.BoolTest): def _test_warning_pre_3_12_5(self): e = test_xml_etree.ET.fromstring('') msg = ( r"Testing an element's truth value will raise an exception in " r"future versions. " r"Use specific 'len\(elem\)' or 'elem is not None' test instead.") with self.assertWarnsRegex(DeprecationWarning, msg): result = bool(e) # Emulate prior behavior for now self.assertIs(result, False) # Element with children test_xml_etree.ET.SubElement(e, 'b') with self.assertWarnsRegex(DeprecationWarning, msg): new_result = bool(e) self.assertIs(new_result, True) def test_warning(self): if sys.version_info[:2] < (3, 12): pass elif sys.version_info[:3] < (3, 12, 5): self._test_warning_pre_3_12_5() else: super().test_warning() class ElementSlicingTest(test_xml_etree.ElementSlicingTest): @unittest.skipIf( sys.version_info[:3] < (3, 12, 6), "Changed in 3.12.6", ) def test_issue123213_setslice_exception(self): super().test_issue123213_setslice_exception() def setUpModule_old(module=None): # Adapted from test_xml_etree to avoid the `import_fresh_module` whose # implementation changed in older versions. # When invoked without a module, runs the Python ET tests by loading pyET. # Otherwise, uses the given module as the ET. # global pyET # pyET = import_fresh_module('xml.etree.ElementTree', # blocked=['_elementtree']) # if module is None: # module = pyET # global ET # ET = module ET = test_xml_etree.ET = test_xml_etree.pyET = module # don't interfere with subsequent tests def cleanup(): global ET ET = test_xml_etree.ET = test_xml_etree.pyET = None unittest.addModuleCleanup(cleanup) # Provide default namespace mapping and path cache. from xml.etree import ElementPath nsmap = ET.register_namespace._namespace_map # Copy the default namespace mapping nsmap_copy = nsmap.copy() unittest.addModuleCleanup(nsmap.update, nsmap_copy) unittest.addModuleCleanup(nsmap.clear) # Copy the path cache (should be empty) path_cache = ElementPath._cache unittest.addModuleCleanup(setattr, ElementPath, "_cache", path_cache) ElementPath._cache = path_cache.copy() # Align the Comment/PI factories. # if hasattr(ET, '_set_factories'): # old_factories = ET._set_factories(ET.Comment, ET.PI) # unittest.addModuleCleanup(ET._set_factories, *old_factories) def setUpModule(module): test_xml_etree.serialize = serialize def revert_serialize(): test_xml_etree.serialize = old_serialize unittest.addModuleCleanup(revert_serialize) if sys.version_info[:3] < (3, 11): setUpModule_old(module=module) else: test_xml_etree.setUpModule(module=module) def tearDownModule(): # pytest doesn't call doModuleCleanups implicitly like unittest unittest.case.doModuleCleanups() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729876127.0 et_xmlfile-2.0.0/et_xmlfile/tests/stdlibshim.py0000644000000000000000000000054214706750237016611 0ustar00# A shim file to replace certain parts of xml.etree.ElementTree with this # package's modified versions so that we can test against the stdlib tests import xml.etree.ElementTree as pyET from xml.etree.ElementTree import * from et_xmlfile.incremental_tree import * ElementTree = IncrementalTree tostring = compat_tostring _Element_Py = pyET._Element_Py ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729876127.0 et_xmlfile-2.0.0/et_xmlfile/tests/test_incremental_tree.py0000644000000000000000000003730414706750237021034 0ustar00import io import operator import unittest import xml.etree.ElementTree as ET from et_xmlfile import incremental_tree as inc_tree def serialize(elem, to_string=True, encoding="unicode", **options): if encoding != "unicode": file = io.BytesIO() else: file = io.StringIO() tree = inc_tree.IncrementalTree(elem) tree.write(file, encoding=encoding, **options) if to_string: return file.getvalue() else: file.seek(0) return file class BaseETTestCase(unittest.TestCase): @classmethod def setUpClass(cls): cls._orig_namespace_map = ET.register_namespace._namespace_map.copy() def tearDown(self): # Clear any namespaces registered during tests ET.register_namespace._namespace_map.clear() ET.register_namespace._namespace_map.update(self._orig_namespace_map) def assertEqualElements(self, alice, bob): self.assertEqual(len(list(alice)), len(list(bob))) for x, y in zip(alice, bob): self.assertEqualElements(x, y) class ETModTests(BaseETTestCase): def test_namespaces_returns_only_used(self): "_namespaces only returns uris that were used in the docuemnt" elem = ET.Element("{namespace0}foo") out_nsmap = inc_tree._namespaces( elem, ) self.assertEqual( out_nsmap, {"ns0": "namespace0"}, ) def test_namespaces_default_namespace_not_used(self): "_namespaces doesn't return default_namespace if not used" elem = ET.Element("{namespace1}foo") out_nsmap = inc_tree._namespaces( elem, default_namespace="other", ) self.assertEqual( out_nsmap, {"ns1": "namespace1"}, ) def test_default_namespace_not_used(self): "Serializing will declare the default_namespace even if unused" elem = ET.Element("{namespace1}foo") self.assertEqual( serialize(elem, default_namespace="other"), '' ) def test_default_namespace_not_used_minimal(self): "Serializing will declare the default_namespace even if unused unless minimal ns declared" elem = ET.Element("{namespace1}foo") self.assertEqual( serialize(elem, default_namespace="other", minimal_ns_only=True), '' ) def test_nsmap_not_used_minimal(self): "Serializing will declare the default_namespace even if unused unless minimal ns declared" elem = ET.Element("{namespace0}foo") self.assertEqual( serialize(elem, nsmap={"oth": "other"}, minimal_ns_only=True), '' ) def test_minimal_ns_only_implies_root_ns_only(self): elem = ET.Element("{namespace1}foo") ET.SubElement(elem, "{namespace2}bar") self.assertEqual( serialize( elem, default_namespace="default", nsmap={"oth": "other"}, minimal_ns_only=True, ), '' ) class CommonFixesTests(BaseETTestCase): """A collection of tests added to PRs for cpython for bug fixes / enhancements. The bug fixes are: - conflicts of registered namespaces and default_namespace uri - incorrect handling of attributes with a URI that is the same as the default_uri (attributes in that namespace must have a prefix - unprefixed attrs are not in the default namespace) Feature: - Add local namespace map arg, `nsmap` to write()` """ compat_serialize = False def serialize(self, elem, to_string=True, encoding="unicode", **options): if self.compat_serialize: options["root_ns_only"] = True return serialize(elem, to_string=to_string, encoding=encoding, **options) def test_tostring_nsmap(self): elem = ET.XML( '' ) if self.compat_serialize: expected = '' else: expected = '' self.assertEqual(self.serialize(elem, encoding="unicode"), expected) self.assertEqual( self.serialize( elem, encoding="unicode", nsmap={ "": "http://effbot.org/ns", "foo": "bar", "unused": "nothing", }, ), '' "", ) def test_tostring_nsmap_default_namespace(self): elem = ET.XML('') self.assertEqual( self.serialize(elem, encoding="unicode"), '', ) self.assertEqual( self.serialize( elem, encoding="unicode", nsmap={"": "http://effbot.org/ns"}, ), '', ) def test_tostring_nsmap_default_namespace_none(self): elem = ET.XML('') self.assertEqual( self.serialize(elem, encoding="unicode"), '', ) msg = '^Found None as default nsmap prefix in nsmap. Use "" as the default namespace prefix.$' with self.assertRaisesRegex(ValueError, msg): self.serialize( elem, encoding="unicode", nsmap={None: "http://effbot.org/ns"}, ) def test_tostring_nsmap_default_namespace_overrides(self): elem = ET.XML('') self.assertEqual( self.serialize( elem, encoding="unicode", default_namespace="other", nsmap={"": "http://effbot.org/ns"}, ), '' "" "", ) self.assertEqual( self.serialize( elem, encoding="unicode", default_namespace="http://effbot.org/ns", nsmap={"": "other"}, ), '', ) def test_tostring_nsmap_default_namespace_attr(self): reg_name = "gh57587" namespace = "ns_gh57587" elem = ET.XML( f'' "" ) self.assertEqual( self.serialize(elem, encoding="unicode"), f'', ) ET.register_namespace(reg_name, namespace) self.assertEqual( self.serialize( elem, encoding="unicode", nsmap={ "": namespace, "foo": namespace, }, ), f'' "", ) # default attr gets name from global registered namespaces self.assertEqual( self.serialize( elem, encoding="unicode", nsmap={"": namespace}, ), f'' "", ) def test_tostring_nsmap_default_namespace_original_no_namespace(self): elem = ET.XML("") EXPECTED_MSG = "^cannot use non-qualified names with default_namespace option$" with self.assertRaisesRegex(ValueError, EXPECTED_MSG): self.serialize(elem, encoding="unicode", nsmap={"": "foobar"}) def test_tostringlist_nsmap_default_namespace(self): elem = ET.XML('') self.assertEqual( "".join(inc_tree.tostringlist(elem, encoding="unicode")), '', ) self.assertEqual( "".join( inc_tree.tostringlist( elem, encoding="unicode", nsmap={"": "http://effbot.org/ns"}, ) ), '', ) def test_namespace_attribs(self): # Unprefixed attributes are unqualified even if a default # namespace is in effect. (This is a little unclear in some # versions of the XML TR but is clarified in errata and other # versions.) See bugs.python.org issue 17088. # # The reasoning behind this, alluded to in the spec, is that # attribute meanings already depend on the element they're # attached to; attributes have always lived in per-element # namespaces even before explicit XML namespaces were # introduced. For that reason qualified attribute names are # only really needed when one XML module defines attributes # that can be placed on elements defined in a different module # (such as happens with XLINK or, for that matter, the XML # namespace spec itself). e = ET.XML( '' '' '' '' "" ) self.assertEqual(e.tag, "{space1}elt") self.assertEqual(e.get("foo"), "value") self.assertIsNone(e.get("{space1}foo")) self.assertIsNone(e.get("{space2}foo")) self.assertEqual(e[0].tag, "{space1}foo") self.assertEqual(e[0].attrib, {"foo": "value2", "{space2}foo": "value3"}) self.assertEqual(e[1].tag, "{space2}foo") self.assertEqual( e[1].attrib, {"foo": "value4", "{space1}foo": "value5", "{space2}foo": "value6"}, ) self.assertEqual(e[2].tag, "foo") self.assertEqual(e[2].attrib, {"foo": "value7", "{space1}foo": "value8"}) if self.compat_serialize: serialized1 = ( '' '' '' '' "" ) else: serialized1 = ( '' '' '' '' "" ) self.assertEqual(self.serialize(e), serialized1) self.assertEqualElements(e, ET.XML(serialized1)) # Test writing with a default namespace. with self.assertRaisesRegex( ValueError, "cannot use non-qualified name.* with default_namespace option" ): self.serialize(e, default_namespace="space1") # Remove the unqualified element from the tree so we can test # further. del e[2] # Serialization can require a namespace prefix to be declared for # space1 even if no elements use that prefix, in order to # write an attribute name in that namespace. if self.compat_serialize: serialized2 = ( '' '' '' "" ) else: serialized2 = ( '' '' '' "" ) self.assertEqual(self.serialize(e, default_namespace="space2"), serialized2) self.assertEqualElements(e, ET.XML(serialized2)) if self.compat_serialize: serialized3 = ( '' '' '' "" ) else: serialized3 = ( '' '' '' "" ) self.assertEqual(self.serialize(e, default_namespace="space1"), serialized3) self.assertEqualElements(e, ET.XML(serialized3)) def test_pre_gh118416_register_default_namespace_behaviour(self): # All these examples worked prior to fixing bug gh118416 ET.register_namespace("", "gh118416") # If the registered default prefix's URI is not used, don't raise an # error e = ET.Element("{other}elem") self.assertEqual( self.serialize(e), '', ) # no error even with unqualified tags e = ET.Element("elem") self.assertEqual( self.serialize(e), "", ) # Uses registered default prefix, if used in tree e = ET.Element("{gh118416}elem") self.assertEqual( self.serialize(e), '', ) # Use explicitly provided default_namespace if registered default # prefix is not used. e = ET.Element("{default}elem") ET.SubElement(e, "{other}otherEl") if self.compat_serialize: expected_xml = '' else: expected_xml = '' self.assertEqual( self.serialize(e, default_namespace="default"), expected_xml, ) def test_gh118416_register_default_namespace(self): ET.register_namespace("", "gh118416") e = ET.Element("{gh118416}elem") ET.SubElement(e, "noPrefixElem") with self.assertRaises(ValueError) as cm: self.serialize(e) self.assertEqual( str(cm.exception), "cannot use non-qualified names with default_namespace option", ) e = ET.Element("{gh118416}elem") # explicitly set default_namespace takes precedence self.assertEqual( self.serialize(e, default_namespace="otherdefault"), '', ) class CommonFixesTestsCompat(CommonFixesTests): compat_serialize = True ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729876127.0 et_xmlfile-2.0.0/et_xmlfile/tests/test_incremental_tree_with_stdlib_tests.py0000644000000000000000000000350014706750237024641 0ustar00# Test et_xmlfile.incremental_tree using Python's Lib/test/test_xml_etree.py tests # # This imports the test from the python installation that is running the test # and might be quite flakey as python changes. It is good as a sanity check but # should be disabled if it starts causing too many headaches. # # Hint: If you need to debug any of the stdlib tests in detail, create a new # test file in this repository and copy the failing test over to play with. import sys import unittest from . import stdlib_base_tests def make_modified_tests(): class ElementTreeTest(stdlib_base_tests.ElementTreeTest): @unittest.skip("3.8 has incompatible xml declaration case") def test_tostring_xml_declaration_cases(self): super().test_tostring_xml_declaration_cases() @unittest.skip("3.8 has incompatible xml declaration case") def test_tostring_xml_declaration_unicode_encoding(self): super().test_tostring_xml_declaration_unicode_encoding() @unittest.skip("3.8 has incompatible xml declaration case") def test_tostringlist_xml_declaration(self): super().test_tostringlist_xml_declaration() if sys.version_info[:2] == (3, 10): class IOTest(stdlib_base_tests.IOTest): @unittest.skip( "Fixeb by: gh-91810: Fix regression with writing an XML declaration with encoding='unicode'" ) def test_write_to_text_file(self): pass else: IOTest = stdlib_base_tests.IOTest return ( ElementTreeTest, IOTest, ) stdlib_base_tests.install_tests(globals(), make_modified_tests()) def setUpModule(): import et_xmlfile.tests.stdlibshim stdlib_base_tests.setUpModule(module=et_xmlfile.tests.stdlibshim) def tearDownModule(): stdlib_base_tests.tearDownModule() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729876127.0 et_xmlfile-2.0.0/et_xmlfile/tests/test_incremental_xmlfile.py0000644000000000000000000002657114706750237021541 0ustar00from __future__ import absolute_import """ Tests for the incremental XML serialisation API. Adapted from the tests from lxml.etree.xmlfile """ try: import lxml except ImportError: raise ImportError("lxml is required to run the tests.") from io import BytesIO import unittest import tempfile, os, sys from .common_imports import HelperTestCase, skipIf from et_xmlfile import xmlfile from et_xmlfile.xmlfile import LxmlSyntaxError import pytest from .helper import compare_xml import xml.etree.ElementTree from xml.etree.ElementTree import Element, parse class _XmlFileTestCaseBase(HelperTestCase): _file = None # to be set by specific subtypes below def setUp(self): self._file = BytesIO() def test_element(self): with xmlfile(self._file) as xf: with xf.element('test'): pass self.assertXml('') def test_element_write_text(self): with xmlfile(self._file) as xf: with xf.element('test'): xf.write('toast') self.assertXml('toast') def test_element_nested(self): with xmlfile(self._file) as xf: with xf.element('test'): with xf.element('toast'): with xf.element('taste'): xf.write('conTent') self.assertXml('conTent') def test_element_nested_with_text(self): with xmlfile(self._file) as xf: with xf.element('test'): xf.write('con') with xf.element('toast'): xf.write('tent') with xf.element('taste'): xf.write('inside') xf.write('tnet') xf.write('noc') self.assertXml('contentinside' 'tnetnoc') def test_write_Element(self): with xmlfile(self._file) as xf: xf.write(Element('test')) self.assertXml('') def test_write_Element_repeatedly(self): element = Element('test') with xmlfile(self._file) as xf: with xf.element('test'): for i in range(100): xf.write(element) tree = self._parse_file() self.assertTrue(tree is not None) self.assertEqual(100, len(tree.getroot())) self.assertEqual(set(['test']), set(el.tag for el in tree.getroot())) def test_namespace_nsmap(self): with xmlfile(self._file) as xf: with xf.element('{nsURI}test', nsmap={'x': 'nsURI'}): pass self.assertXml('') def test_namespace_nested_nsmap(self): with xmlfile(self._file) as xf: with xf.element('test', nsmap={'x': 'nsURI'}): with xf.element('{nsURI}toast'): pass self.assertXml('') def test_anonymous_namespace(self): with xmlfile(self._file) as xf: with xf.element('{nsURI}test'): pass self.assertSerialised('') self.assertXml('') def test_namespace_nested_anonymous(self): with xmlfile(self._file) as xf: with xf.element('test'): with xf.element('{nsURI}toast'): pass self.assertXml('') def test_default_namespace(self): with xmlfile(self._file) as xf: with xf.element('{nsURI}test', nsmap={None: 'nsURI'}): pass self.assertXml('') self.assertSerialised('') def test_nested_default_namespace(self): with xmlfile(self._file) as xf: with xf.element('{nsURI}test', nsmap={None: 'nsURI'}): with xf.element('{nsURI}toast'): pass self.assertXml('') @pytest.mark.xfail def test_pi(self): from et_xmlfile.xmlfile import ProcessingInstruction with xmlfile(self._file) as xf: xf.write(ProcessingInstruction('pypi')) with xf.element('test'): pass self.assertXml('') @pytest.mark.xfail def test_comment(self): with xmlfile(self._file) as xf: xf.write(etree.Comment('a comment')) with xf.element('test'): pass self.assertXml('') def test_attribute(self): with xmlfile(self._file) as xf: with xf.element('test', attrib={'k': 'v'}): pass self.assertXml('') def test_escaping(self): with xmlfile(self._file) as xf: with xf.element('test'): xf.write('Comments: \n') xf.write('Entities: &') self.assertXml( 'Comments: <!-- text -->\nEntities: &amp;') @pytest.mark.xfail def test_encoding(self): with xmlfile(self._file, encoding='utf16') as xf: with xf.element('test'): xf.write('toast') self.assertXml('toast', encoding='utf16') @pytest.mark.xfail def test_buffering(self): with xmlfile(self._file, buffered=False) as xf: with xf.element('test'): self.assertXml("") xf.write('toast') self.assertXml("toast") with xf.element('taste'): self.assertXml("toast") xf.write('some', etree.Element("more"), "toast") self.assertXml("toastsometoast") self.assertXml("toastsometoast") xf.write('end') self.assertXml("toastsometoastend") self.assertXml("toastsometoastend") self.assertXml("toastsometoastend") @pytest.mark.xfail def test_flush(self): with xmlfile(self._file, buffered=True) as xf: with xf.element('test'): self.assertXml("") xf.write('toast') self.assertXml("") with xf.element('taste'): self.assertXml("") xf.flush() self.assertXml("toast") self.assertXml("toast") self.assertXml("toast") self.assertXml("toast") def test_failure_preceding_text(self): try: with xmlfile(self._file) as xf: xf.write('toast') except LxmlSyntaxError: self.assertTrue(True) else: self.assertTrue(False) def test_failure_trailing_text(self): with xmlfile(self._file) as xf: with xf.element('test'): pass try: xf.write('toast') except LxmlSyntaxError: self.assertTrue(True) else: self.assertTrue(False) def test_failure_trailing_Element(self): with xmlfile(self._file) as xf: with xf.element('test'): pass try: xf.write(Element('test')) except LxmlSyntaxError: self.assertTrue(True) else: self.assertTrue(False) @pytest.mark.xfail def test_closing_out_of_order_in_error_case(self): cm_exit = None try: with xmlfile(self._file) as xf: x = xf.element('test') cm_exit = x.__exit__ x.__enter__() raise ValueError('123') except ValueError: self.assertTrue(cm_exit) try: cm_exit(ValueError, ValueError("huhu"), None) except LxmlSyntaxError: self.assertTrue(True) else: self.assertTrue(False) else: self.assertTrue(False) def _read_file(self): pos = self._file.tell() self._file.seek(0) try: return self._file.read() finally: self._file.seek(pos) def _parse_file(self): pos = self._file.tell() self._file.seek(0) try: return parse(self._file) finally: self._file.seek(pos) def tearDown(self): if self._file is not None: self._file.close() def assertXml(self, expected, encoding='utf8'): diff = compare_xml(self._read_file().decode(encoding), expected) assert diff is None, diff def assertSerialised(self, expected, encoding='utf8'): self.assertEqual(self._read_file().decode(encoding), expected) class BytesIOXmlFileTestCase(_XmlFileTestCaseBase): def setUp(self): self._file = BytesIO() def test_filelike_close(self): with xmlfile(self._file, close=True) as xf: with xf.element('test'): pass self.assertRaises(ValueError, self._file.getvalue) class TempXmlFileTestCase(_XmlFileTestCaseBase): def setUp(self): self._file = tempfile.TemporaryFile() class TempPathXmlFileTestCase(_XmlFileTestCaseBase): def setUp(self): self._tmpfile = tempfile.NamedTemporaryFile(delete=False) self._file = self._tmpfile.name def tearDown(self): try: self._tmpfile.close() finally: if os.path.exists(self._tmpfile.name): os.unlink(self._tmpfile.name) def _read_file(self): self._tmpfile.seek(0) return self._tmpfile.read() def _parse_file(self): self._tmpfile.seek(0) return parse(self._tmpfile) @skipIf(True, "temp file behaviour is too platform specific here") def test_buffering(self): pass @skipIf(True, "temp file behaviour is too platform specific here") def test_flush(self): pass class SimpleFileLikeXmlFileTestCase(_XmlFileTestCaseBase): class SimpleFileLike(object): def __init__(self, target): self._target = target self.write = target.write self.tell = target.tell self.seek = target.seek self.closed = False def close(self): assert not self.closed self.closed = True self._target.close() def setUp(self): self._target = BytesIO() self._file = self.SimpleFileLike(self._target) def _read_file(self): return self._target.getvalue() def _parse_file(self): pos = self._file.tell() self._target.seek(0) try: return parse(self._target) finally: self._target.seek(pos) def test_filelike_not_closing(self): with xmlfile(self._file) as xf: with xf.element('test'): pass self.assertFalse(self._file.closed) def test_filelike_close(self): with xmlfile(self._file, close=True) as xf: with xf.element('test'): pass self.assertTrue(self._file.closed) self._file = None # prevent closing in tearDown() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729876127.0 et_xmlfile-2.0.0/et_xmlfile/xmlfile.py0000644000000000000000000001142614706750237014750 0ustar00from __future__ import absolute_import # Copyright (c) 2010-2015 openpyxl """Implements the lxml.etree.xmlfile API using the standard library xml.etree""" from contextlib import contextmanager from xml.etree.ElementTree import ( Element, _escape_cdata, ) from . import incremental_tree class LxmlSyntaxError(Exception): pass class _IncrementalFileWriter(object): """Replacement for _IncrementalFileWriter of lxml""" def __init__(self, output_file): self._element_stack = [] self._file = output_file self._have_root = False self.global_nsmap = incremental_tree.current_global_nsmap() self.is_html = False @contextmanager def element(self, tag, attrib=None, nsmap=None, **_extra): """Create a new xml element using a context manager.""" if nsmap and None in nsmap: # Normalise None prefix (lxml's default namespace prefix) -> "", as # required for incremental_tree if "" in nsmap and nsmap[""] != nsmap[None]: raise ValueError( 'Found None and "" as default nsmap prefixes with different URIs' ) nsmap = nsmap.copy() nsmap[""] = nsmap.pop(None) # __enter__ part self._have_root = True if attrib is None: attrib = {} elem = Element(tag, attrib=attrib, **_extra) elem.text = '' elem.tail = '' if self._element_stack: is_root = False ( nsmap_scope, default_ns_attr_prefix, uri_to_prefix, ) = self._element_stack[-1] else: is_root = True nsmap_scope = {} default_ns_attr_prefix = None uri_to_prefix = {} ( tag, nsmap_scope, default_ns_attr_prefix, uri_to_prefix, next_remains_root, ) = incremental_tree.write_elem_start( self._file, elem, nsmap_scope=nsmap_scope, global_nsmap=self.global_nsmap, short_empty_elements=False, is_html=self.is_html, is_root=is_root, uri_to_prefix=uri_to_prefix, default_ns_attr_prefix=default_ns_attr_prefix, new_nsmap=nsmap, ) self._element_stack.append( ( nsmap_scope, default_ns_attr_prefix, uri_to_prefix, ) ) yield # __exit__ part self._element_stack.pop() self._file(f"") if elem.tail: self._file(_escape_cdata(elem.tail)) def write(self, arg): """Write a string or subelement.""" if isinstance(arg, str): # it is not allowed to write a string outside of an element if not self._element_stack: raise LxmlSyntaxError() self._file(_escape_cdata(arg)) else: if not self._element_stack and self._have_root: raise LxmlSyntaxError() if self._element_stack: is_root = False ( nsmap_scope, default_ns_attr_prefix, uri_to_prefix, ) = self._element_stack[-1] else: is_root = True nsmap_scope = {} default_ns_attr_prefix = None uri_to_prefix = {} incremental_tree._serialize_ns_xml( self._file, arg, nsmap_scope=nsmap_scope, global_nsmap=self.global_nsmap, short_empty_elements=True, is_html=self.is_html, is_root=is_root, uri_to_prefix=uri_to_prefix, default_ns_attr_prefix=default_ns_attr_prefix, ) def __enter__(self): pass def __exit__(self, type, value, traceback): # without root the xml document is incomplete if not self._have_root: raise LxmlSyntaxError() class xmlfile(object): """Context manager that can replace lxml.etree.xmlfile.""" def __init__(self, output_file, buffered=False, encoding="utf-8", close=False): self._file = output_file self._close = close self.encoding = encoding self.writer_cm = None def __enter__(self): self.writer_cm = incremental_tree._get_writer(self._file, encoding=self.encoding) writer, declared_encoding = self.writer_cm.__enter__() return _IncrementalFileWriter(writer) def __exit__(self, type, value, traceback): if self.writer_cm: self.writer_cm.__exit__(type, value, traceback) if self._close: self._file.close() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729876127.0 et_xmlfile-2.0.0/pytest.ini0000644000000000000000000000014114706750237012627 0ustar00[pytest] addopts = -l --strict-markers norecursedirs = lib include .tox et_xmlfile/tests/_vendor ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729876127.0 et_xmlfile-2.0.0/requirements.txt0000644000000000000000000000002514706750237014063 0ustar00lxml>=3.4 pytest tox ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729876127.0 et_xmlfile-2.0.0/setup.py0000755000000000000000000000336614706750237012327 0ustar00#!/usr/bin/env python """Setup script for packaging et_xmfile. """ import sys import os from setuptools import setup, find_packages here = os.path.abspath(os.path.dirname(__file__)) try: with open(os.path.join(here, 'README.rst')) as f: README = f.read() except IOError: README = '' sys.path.append('') from et_xmlfile import ( __author__, __license__, __author_email__, __url__, __version__ ) setup(name='et_xmlfile', packages=find_packages( exclude=["*.tests*",] ), # metadata version=__version__, description="An implementation of lxml.xmlfile for the standard library", long_description=README, author=__author__, author_email=__author_email__, url=__url__, license=__license__, python_requires=">=3.8", project_urls={ 'Documentation': 'https://openpyxl.pages.heptapod.net/et_xmlfile/', 'Source': 'https://foss.heptapod.net/openpyxl/et_xmlfile', 'Tracker': 'https://foss.heptapod.net/openpyxl/et_xmfile/-/issues', }, classifiers=[ 'Development Status :: 5 - Production/Stable', 'Operating System :: MacOS :: MacOS X', 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX', 'License :: OSI Approved :: MIT License', 'Programming Language :: Python', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: 3.13', ], ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729876127.0 et_xmlfile-2.0.0/tox.ini0000644000000000000000000000145114706750237012116 0ustar00# Tox (http://tox.testrun.org/) is a tool for running tests # in multiple virtualenvs. This configuration file will run the # test suite on all supported python versions. To use it, "pip install tox" # and then run "tox" from this directory. [tox] envlist = py38, py39, py310, py311, py312, py313, doc, [testenv] commands = {envbindir}/pytest {posargs} deps = pytest lxml [testenv:doc] changedir = doc setenv = APIDOC=True deps = sphinx pytest sphinx_rtd_theme commands = sphinx-build -q -d {envtmpdir}/doctrees . {envtmpdir}/html [testenv:cov] passenv = COVERALLS_REPO_TOKEN GIT_* deps = {[testenv]deps} pytest-cov coveralls commands = pytest -qq --cov=et_xmlfile --cov-report=term-missing --cov-report=xml coveralls