pax_global_header00006660000000000000000000000064144566224660014531gustar00rootroot0000000000000052 comment=741e4a12cb7df40c803a8956c705deae287778c4 flufl.i18n-5.0.2/000077500000000000000000000000001445662246600134235ustar00rootroot00000000000000flufl.i18n-5.0.2/.bandit000066400000000000000000000000431445662246600146620ustar00rootroot00000000000000[bandit] exclude: setup_helpers.py flufl.i18n-5.0.2/.gitignore000066400000000000000000000002211445662246600154060ustar00rootroot00000000000000build flufl.*.egg-info dist *.pyc .tox .coverage coverage.xml diffcov.html htmlcov /.pdm.toml /__pypackages__/ /.DS_Store /.pdm-python /pdm.toml flufl.i18n-5.0.2/.gitlab-ci.yml000066400000000000000000000002471445662246600160620ustar00rootroot00000000000000include: - remote: https://gitlab.com/warsaw/gitlab-ci/-/raw/main/common-gitlab-ci.yml variables: MODULE_NAME: "flufl.i18n" MODULE_PATH: "src/flufl/i18n" flufl.i18n-5.0.2/.readthedocs-req.txt000066400000000000000000000000361445662246600173130ustar00rootroot00000000000000sphinx_autodoc_typehints furo flufl.i18n-5.0.2/.readthedocs.yml000066400000000000000000000004241445662246600165110ustar00rootroot00000000000000# Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details version: 2 sphinx: configuration: docs/conf.py python: version: 3.8 install: - requirements: .readthedocs-req.txt - method: pip path: . flufl.i18n-5.0.2/LICENSE000066400000000000000000000010561445662246600144320ustar00rootroot00000000000000Copyright 2004-2023 Barry Warsaw Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. flufl.i18n-5.0.2/README.rst000066400000000000000000000016151445662246600151150ustar00rootroot00000000000000========== flufl.i18n ========== A high level API for internationalizing Python libraries and applications. The ``flufl.i18n`` library provides a convenient API for managing translation contexts in Python applications. It provides facilities not only for single-context applications like command line scripts, but also more sophisticated management of multiple-context applications such as Internet servers. Author ====== ``flufl.i18n`` is Copyright (C) 2004-2023 Barry Warsaw Licensed under the terms of the Apache License Version 2.0. See the LICENSE file for details. Project details =============== * Project home: https://gitlab.com/warsaw/flufl.i18n * Report bugs at: https://gitlab.com/warsaw/flufl.i18n/issues * Code hosting: https://gitlab.com/warsaw/flufl.i18n.git * Documentation: https://flufli18n.readthedocs.io/ * PyPI: https://pypi.python.org/pypi/flufl.i18n flufl.i18n-5.0.2/conftest.py000066400000000000000000000022761445662246600156310ustar00rootroot00000000000000import os import sys from sybil import Sybil from doctest import ELLIPSIS, REPORT_NDIFF, NORMALIZE_WHITESPACE from contextlib import ExitStack from sybil.parsers.doctest import DocTestParser from sybil.parsers.codeblock import PythonCodeBlockParser # For the message catalog used in the doctests. from test.data import messages DOCTEST_FLAGS = ELLIPSIS | NORMALIZE_WHITESPACE | REPORT_NDIFF class DoctestNamespace: def __init__(self): self._resources = ExitStack() def setup(self, namespace): sys.modules['messages'] = messages namespace['cleanups'] = self._resources # Ensure that environment variables affecting translation are # neutralized. for envar in ('LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG'): if envar in os.environ: del os.environ[envar] def teardown(self, namespace): del sys.modules['messages'] self._resources.close() namespace = DoctestNamespace() pytest_collect_file = Sybil( parsers=[ DocTestParser(optionflags=DOCTEST_FLAGS), PythonCodeBlockParser(), ], pattern='*.rst', setup=namespace.setup, teardown=namespace.teardown, ).pytest() flufl.i18n-5.0.2/docs/000077500000000000000000000000001445662246600143535ustar00rootroot00000000000000flufl.i18n-5.0.2/docs/NEWS.rst000066400000000000000000000126061445662246600156660ustar00rootroot00000000000000===================== flufl.i18n change log ===================== 5.0.2 (2023-07-21) ================== * Update dependencies. * Other minor improvements and cleanups. 5.0.1 (2023-06-13) ================== * Fix the build backend. 5.0 (unreleased) ================ * Drop Python 3.7 support. (GL#15) * Switch from ``flake8`` and ``isort`` to ``ruff`` for code quality. (GL#16) * Bump dependencies. 4.1.1 (2022-09-05) ================== * Improvements to the GitLab CI integration. * Several minor updates to work better with the latest pdm. 4.1 (2022-08-25) ================== * The standard substitution pattern now ignores the trailing dot on $-string placeholders. I.e. ``$foo.`` is now recognized as ``$foo``. (GL#12) * Update to pdm 1.3. * Update dependencies. * Make sure the doctest teardown gets run. * Add support for Python 3.11. 4.0 (2022-01-11) ================ * Use modern package management by adopting `pdm `_ and ``pyproject.toml``, and dropping ``setup.py`` and ``setup.cfg``. * Build the docs with Python 3.8. * Update to version 3.0 of `Sybil `_. * Adopt the `Furo `_ documentation theme. * Use `importlib.metadata.version() `_ as a better way to get the package version number for the documentation. * Drop Python 3.6 support. * Update Windows GitLab runner to include Python 3.10. * Update copyright years. 3.2 (2021-05-29) ================ * Add a ``py.typed`` file to satisfy type checkers. (GL#10) * Improve some QA by re-adding diff-cover, Gitlab SAST during CI, and testing on Python 3.10 beta (except for Windows) * The ``master`` branch is renamed to ``main``. (GL#11) 3.1.5 (2021-02-14) ================== * I `blue `_ it! 3.1.4 (2021-01-01) ================== * Update copyright years. * Include ``test/__init__.py`` and ``docs/__init__.py`` (GL#9) 3.1.3 (2020-10-22) ================== * Rename top-level tests/ directory to test/ (GL#8) 3.1.2 (2020-10-21) ================== * Small documentation fix. 3.1.1 (2020-10-21) ================== * Fix the site-packages pollution. (GL#7) 3.1 (2020-10-20) ================ * Improve the documentation. * Reorganized docs and tests out of the code directory. (GL#5) * Fix the Windows CI job. (GL#6) 3.0.1 (2020-07-28) ================== * Fix pytest 6.0.0 compatibility * Add CI for Python 3.9 on Windows 3.0 (2020-07-12) ================ * Drop support for Python 3.4 and 3.5. Add support for Python 3.9. * ``Translator.catalog`` property is now exposed. * New abstract classes for defining the types in this library: ``TranslationContextManager``, ``RuntimeTranslator``, ``TranslationStrategy`` * When ``expand()`` gets an exception, the original exception is re-raised. This used to inadvertently return None. * Add type annotations and API reference documentation. * Other internal improvements. 2.0.2 (2019-05-17) ================== * Add (testing) support for Python 3.7 and 3.8. * Add LICENSE and the top level README.rst file to release tarball. (Closes #4) 2.0.1 (2017-11-14) ================== * Restore Python 3.4 support. 2.0 (2017-07-24) ================ * Add ``_.defer_translation()`` context manager for marking, but not translating a string at the point of use. (Closes #2) * Drop Python 2, 3.3, and 3.4 compatibility; add Python 3.5 and 3.6. * Switch to the Apache License Version 2.0 * Use flufl.testing for nose2 and flake8 plugins. 1.1.3 (2014-04-25) ================== * Include MANIFEST.in in the sdist tarball, otherwise the Debian package won't built correctly. 1.1.2 (2014-03-31) ================== * Fix documentation bug. LP: #1026403 * Use modern setuptools rather than distutils. * Bump copyright years. 1.1.1 (2012-04-19) ================== * Add classifiers to setup.py and make the long description more compatible with the Cheeseshop. * Other changes to make the Cheeseshop page look nicer. (LP: #680136) * setup_helper.py version 2.1. 1.1 (2012-01-19) ================ * Support Python 3 without the need for 2to3. 1.0.4 (2010-12-06) ================== * Restore missing line from MANIFEST.in to fix distribution tarball. 1.0.3 (2010-12-01) ================== * Fix setup.py to not install myfixers artifact directory on install. * Remove pylint.rc; we'll use pyflakes instead. 1.0.2 (2010-06-23) ================== * Small documentation fix. 1.0.1 (2010-06-09) ================== * Ditch the use of zc.buildout. * Improved documentation. 1.0 (2010-04-24) ================ * Use Distribute instead of Setuptools. * Port to Python 3 when used with 2to3. * More documentation improvements. 0.6 (2010-04-21) ================ * Documentation and lint clean up. 0.5 (2010-04-20) ================ * Added a simplified initialization API for one-language-context applications. This works much better for non-server applications. * Added a SimpleStrategy which recognizes the $LOCPATH environment variable. * Show how PEP 292 strings are supported automatically. * When strategies are called with zero arguments, they supply the default translation context, which is usually a NullTranslation. This is better than hardcoding the NullTranslation in the Application. 0.4 (2010-03-04) ================ * Add the ability to get the current language code, via _.code 0.3 (2009-11-15) ================ * Initial release. flufl.i18n-5.0.2/docs/__init__.py000066400000000000000000000000001445662246600164520ustar00rootroot00000000000000flufl.i18n-5.0.2/docs/apiref.rst000066400000000000000000000012751445662246600163600ustar00rootroot00000000000000============= API Reference ============= .. currentmodule:: flufl.i18n Classes ======= .. autoclass:: Application :members: .. autoclass:: flufl.i18n._translator.Translator :members: .. autoclass:: PackageStrategy :members: .. autoclass:: SimpleStrategy :members: .. autoclass:: flufl.i18n._registry.Registry :members: Functions ========= .. autofunction:: initialize .. autofunction:: expand Globals ======= See the :class:`~_registry.Registry` class for details. .. autodata:: registry :annotation: Types ===== .. autoclass:: RuntimeTranslator :members: .. autoclass:: TranslationContextManager :members: .. autoclass:: TranslationStrategy :members: flufl.i18n-5.0.2/docs/conf.py000066400000000000000000000150321445662246600156530ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # flufl.i18n documentation build configuration file, created by # sphinx-quickstart on Fri Apr 23 11:41:37 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 from datetime import date import importlib.metadata # 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.append(os.path.abspath('../src')) # -- General configuration ----------------------------------------------------- # 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.intersphinx', ] intersphinx_mapping = { 'python': ('https://docs.python.org/3/', None), } autodoc_typehints = 'both' autoclass_content = 'both' # 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' # The master toctree document. master_doc = 'index' # General information about the project. project = 'flufl.i18n' author = 'Barry Warsaw' copyright = f'2009-{date.today().year}, {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 = importlib.metadata.version(project) # 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 documents that shouldn't be included in the build. #unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. exclude_trees = ['_build', 'build', 'flufl.i18n.egg-info', 'distribute-0.6.10'] # 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. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. html_theme = 'furo' # 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 = None # 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_use_modindex = 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, 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 = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'flufli18ndoc' # -- 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 = [ ('README.rst', 'flufli18n.tex', 'flufl.i18n Documentation', author, '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 # 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_use_modindex = True flufl.i18n-5.0.2/docs/expand.rst000066400000000000000000000021311445662246600163610ustar00rootroot00000000000000=================== Expanding templates =================== .. currentmodule:: flufl.i18n The Python standard library defines :class:`string.Template` strings, which are the implementation of `PEP 292`_ simple string templates. Substitution placeholders are identified by a leading ``$``-sign [#]_. A substitution dictionary names the keys and values that should be substituted into the template, replacing the placeholders. This module defines the :meth:`expand()` function which takes the translated string and a substitution dictionary, and returns the resulting string. Normally though, you shouldn't have to call this function yourself. :: >>> key_1 = 'key_1' >>> key_2 = 'key_2' >>> from flufl.i18n import expand >>> print(expand( ... '$key_2 is different than $key_1', { ... key_1: 'the first key', ... key_2: 'the second key', ... })) the second key is different than the first key .. _`PEP 292`: http://www.python.org/dev/peps/pep-0292/ .. [#] See the :class:`string.Template` documentation for the complete set of rules. flufl.i18n-5.0.2/docs/index.rst000066400000000000000000000043771445662246600162270ustar00rootroot00000000000000====================================================== flufl.i18n - A high level API for internationalization ====================================================== This package is called ``flufl.i18n``. It provides a high level, convenient API for managing internationalization translation contexts in Python application. There is a simple API for single-context applications, such as command line scripts which only need to translate into one language during the entire course of their execution. There is a more flexible, but still convenient API for multi-context applications, such as servers, which may need to switch language contexts for different tasks. Requirements ============ ``flufl.i18n`` requires Python 3.8 or newer. Documentation ============= A `simple guide`_ to using the library is available, along with a detailed `API reference`_. Project details =============== * Project home: https://gitlab.com/warsaw/flufl.i18n * Report bugs at: https://gitlab.com/warsaw/flufl.i18n/issues * Code hosting: https://gitlab.com/warsaw/flufl.i18n.git * Documentation: https://flufli18n.readthedocs.io/ You can install it with ``pip``:: % pip install flufl.i18n You can grab the latest development copy of the code using git. The master repository is hosted on GitLab. If you have git installed, you can grab your own branch of the code like this:: $ git clone https://gitlab.com/warsaw/flufl.i18n.git You may contact the author via barry@python.org. Copyright ========= Copyright (C) 2004-2023 Barry A. Warsaw Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Table of Contents and Index =========================== * :ref:`genindex` .. toctree:: :glob: using expand strategies apiref NEWS .. _`simple guide`: using.html .. _`API reference`: apiref.html flufl.i18n-5.0.2/docs/strategies.rst000066400000000000000000000065631445662246600172710ustar00rootroot00000000000000================== Catalog strategies ================== .. currentmodule:: flufl.i18n The way :doc:`flufl.i18n ` finds its catalog for an application is extensible. These are called *strategies*. ``flufl.i18n`` comes with a couple of fairly simple strategies. Usually one of the built-in strategies will be sufficient, but you can write your own strategies for finding your catalogs. Python package strategies ========================= The :class:`PackageStrategy` class locates catalog files from within an importable Python package's directory. Inside the package directory, you still need the `gettext`_ standard filesystem layout of ``/LC_MESSAGES/.mo``. .. note:: :doc:`As before ` these examples will use the faux ``xx`` language, and the faux ``messages`` module referred to in these examples contains the language code directories. First import the Python package containing the message catalogs for these examples [#]_. >>> import messages By setting the ``$LANG`` environment variable, we can specify that the application translates into that language automatically. >>> import os >>> os.environ['LANG'] = 'xx' Now we can instantiate a package strategy which finds its catalogs in this example's ``messages`` Python package. The first argument is the application name, which must be unique among all registered strategies. >>> from flufl.i18n import PackageStrategy >>> strategy = PackageStrategy('flufl', messages) Once you have the strategy you need, register it with the global registry. The registration process returns an application object which can be used to look up language codes. >>> from flufl.i18n import registry >>> application = registry.register(strategy) The application object keeps track of a current translation context, and exports a method which you can bind to the underscore function in your module globals for convenient gettext usage. >>> _ = application._ Now at run time, ``_()`` will always translate its string argument to the current catalog's language. >>> print(_('A test message')) N grfg zrffntr .. >>> # Hack to unregister the previous strategy. >>> registry._registry.clear() Simple strategy =============== There is also a simpler strategy that uses both the ``$LANG`` environment variable, and the ``$LOCPATH`` environment variable to set things up. :: >>> os.environ['LOCPATH'] = os.path.dirname(messages.__file__) >>> from flufl.i18n import SimpleStrategy >>> strategy = SimpleStrategy('flufl') >>> application = registry.register(strategy) >>> _ = application._ >>> print(_('A test message')) N grfg zrffntr Calling with zero arguments =========================== Strategies should be prepared to accept zero arguments when called, to produce a *default* translation (usually the :class:`gettext.NullTranslations`). :: >>> def get_gettext(strategy): ... catalog = strategy() ... return catalog.gettext >>> print(get_gettext( ... SimpleStrategy('example'))('A test message')) A test message >>> print(get_gettext( ... PackageStrategy('example', messages))('A test message')) A test message .. _gettext: https://www.gnu.org/software/gettext/manual/gettext.html .. [#] ``messages`` is not a standard Python package. It exists only for the examples in this documentation. flufl.i18n-5.0.2/docs/using.rst000066400000000000000000000252251445662246600162400ustar00rootroot00000000000000============================ Using the flufl.i18n library ============================ .. currentmodule:: flufl.i18n There are two ways that your application can set up translations to use :doc:`flufl.i18n `. The simple initialization will work for most applications, where there is only one *language context* for the entire run of the application, such as for a command line tool. The more complex initialization works well for applications like servers that may want to use multiple language contexts during their execution. Single language contexts ======================== If your applicationonly needs one language context for its entire execution (such as a command line tool), you can use the simple API to set things up. >>> from flufl.i18n import initialize The library by default uses the ``$LANG`` and ``$LOCPATH`` environment variables to set things up. As environment variables, ``$LANG`` sets the language code and ``$LOCPATH`` sets the directory containing containing the translation catalogs with the language codes as subdirectories. Python's :mod:`gettext` module provides more detail about the underlying organization and technology. .. note:: In these examples, we're using a faux language with a code of ``xx``. For demonstration purposes, the ``xx`` language is just the `rot13 `_ of the original source string. The faux ``messages`` module referred to in these examples contains these language code directories. :: >>> import os >>> # Import the Python package containing all the translations. >>> import messages >>> os.environ['LANG'] = 'xx' >>> os.environ['LOCPATH'] = os.path.dirname(messages.__file__) Now you just need to call the :meth:`initialize()` function, passing in the application's name, and you'll get an object back that you can bind to the local ``_()`` function for run-time translations. Note that using ``_()`` isn't required, but it's a widely-used convention, derived from the `GNU gettext `_ model. >>> _ = initialize('flufl') >>> print(_('A test message')) N grfg zrffntr It's probably best to just share this function through imports, but it does no harm to call :meth:`initialize()` again. >>> _ = initialize('flufl') >>> print(_('A test message')) N grfg zrffntr .. >>> # Unregister the application domain used earlier. Also, clear the >>> # environment settings from above. >>> from flufl.i18n import registry >>> registry._registry.clear() >>> del os.environ['LANG'] >>> del os.environ['LOCPATH'] Multiple language contexts ========================== Some applications, such as servers, are more complex; they need multiple language contexts during their execution. To support this, there is a global registry of catalog searching :doc:`strategies `. When a particular language code is specified, a strategy is used to find the catalog that provides that language's translations. :doc:`flufl.i18n ` comes with a couple of fairly simple strategies, and you can implement your own. A convenient built-in strategy looks up catalogs from within a directory using GNU gettext conventions, where the directory is an importable Python package (such as our ``messages`` example). >>> from flufl.i18n import PackageStrategy >>> strategy = PackageStrategy('flufl', messages) The first argument is the application name, which must be unique among all registered strategies. The second argument is the package under which the translations can be found. Once you have the desired strategy, register this with the global registry. The registration process returns an application object which can be used to look up language codes. >>> from flufl.i18n import registry >>> application = registry.register(strategy) The application object keeps track of the current *translation context*, essentially a stack of languages. This object also exports a method which you can bind to the ``_()`` function, usually in your module globals. This underscore function always returns translations in the language at the top of the stack. I.e., at run time, ``_()`` will always translate its string argument to the current context's language. >>> _ = application._ By default the application just returns the original source string; i.e. it is a null translator. >>> print(_('A test message')) A test message And it has no language code. >>> print(_.code) None You can temporarily *push* a new language context to the top of the stack, so that the same underscore function will now return translations in the new language context. >>> _.push('xx') >>> print(_.code) xx >>> print(_('A test message')) N grfg zrffntr Pop the current language to return to the default. Once you're at the bottom of the stack, more pops will just give you the *default translation*. Normally, that's the null translation, but as you'll see below, you can change that too. >>> _.pop() >>> print(_.code) None >>> print(_('A test message')) A test message >>> _.pop() >>> print(_.code) None >>> print(_('A test message')) A test message Context manager =============== To make all of this more convenient, the underscore method has a context manager called :meth:`~RuntimeTranslator.using()` which temporarily sets a new language inside a ``with`` statement. :: >>> print(_('A test message')) A test message >>> with _.using('xx'): ... print(_('A test message')) N grfg zrffntr >>> print(_('A test message')) A test message These ``with`` statements are nestable. .. note:: The ``yy`` language is another faux translation, where the source string is reversed. Here we can see that the outer context translates the source string differently than the inner context. :: >>> with _.using('xx'): ... print(_('A test message')) ... with _.using('yy'): ... print(_('A test message')) ... print(_('A test message')) N grfg zrffntr egassem tset A N grfg zrffntr >>> print(_('A test message')) A test message The default language context ============================ As mentioned above, the default language context, i.e. the `Yertle`_ at the bottom of the stack is, by default, the null translation. The null translation just returns the source string unchanged. You can set the default language context at the bottom of the stack. :: >>> _.default = 'xx' >>> print(_('A test message')) N grfg zrffntr >>> _.pop() >>> print(_.code) xx >>> print(_('A test message')) N grfg zrffntr >>> with _.using('yy'): ... print(_('A test message')) egassem tset A >>> print(_('A test message')) N grfg zrffntr You can reset the default back to the null context by ``del``-ing the :data:`~RuntimeTranslator.default` attribute. :: >>> del _.default >>> print(_.code) None Substitutions and placeholders ============================== Once you have an underscore function, using the library is very simple. You just call ``_()`` passing in the source string you want to translate. What if your source strings can't be static literals, because you need them to contain substitutions calculated at run time? You need to define some placeholders in your source string, so that the run time substitutions will be inserted there. Further complicating matters, some languages need to change the order of placeholders in their translations. In general, it's good practice for your source strings to be complete sentences, because that is easier for all your human translaters to ... translate! To support this, you use `PEP 292`_ style ``$``-substitution strings inside the underscore function. These strings contain ``$variables`` and when translated, run time data is inserted into these variables. The substitutions are taken from the locals and globals of the function where the translation is performed, so you don't need to repeat yourself. Here's a simple example where the variable names are ``$ordinal`` and ``$name``. See if you can figure out where the values are taken from at run time. >>> ordinal = 'first' >>> def print_it(name): ... print(_('The $ordinal test message $name')) In this example, when ``print_it()`` is called, the ``$ordinal`` placeholder is taken from globals, while the ``$name`` placeholder is taken from the function's locals (i.e. its parameters and other local variables). With no language context in place, the source string is printed unchanged, except that the substitutions are made. >>> print_it('Anne') The first test message Anne When a substitution is missing, rather than raise an exception, the ``$``-variable name itself is used. >>> del ordinal >>> print_it('Bart') The $ordinal test message Bart When there is a language context in effect, the substitutions happen after translation. >>> ordinal = 'second' >>> with _.using('xx'): ... print_it('Cris') second si n grfg zrffntr Cris Our hypothetical ``yy`` language changes the order of the substitution variables, but of course there is no problem with that. >>> ordinal = 'third' >>> with _.using('yy'): ... print_it('Dave') Dave egassem tset third eht Locals always take precedence over globals. :: >>> def print_it(name, ordinal): ... print(_('The $ordinal test message $name')) >>> with _.using('yy'): ... print_it('Elle', 'fourth') Elle egassem tset fourth eht Deferred translations ===================== Remember that the ``_()`` function has both a run time and a *static* purpose. Most source string extraction tools look for functions named ``_()`` taking a single string argument, and the pull those arguments out as the source strings. Sometimes however, you want to mark source strings for translation, but you need to defer the translation of some of them until later. The way to do this is with the :meth:`~RuntimeTranslator.defer_translation()` function. :: >>> with _.defer_translation(): ... print(_('This gets marked but not translated')) This gets marked but not translated Because the string is wrapped in the ``_()`` function, it will get extracted and added to the catalog by off-line tools, but it will not get translated until later. This is true even if there is a translation context in effect. >>> with _.using('xx'): ... with _.defer_translation(): ... print(_('A test message')) ... print(_('A test message')) A test message N grfg zrffntr .. _`PEP 292`: http://www.python.org/dev/peps/pep-0292/ .. _`Yertle`: https://en.wikipedia.org/wiki/Yertle_the_Turtle_and_Other_Stories flufl.i18n-5.0.2/pdm.lock000066400000000000000000002142521445662246600150630ustar00rootroot00000000000000# This file is @generated by PDM. # It is not intended for manual editing. [metadata] groups = ["default", "docs", "qa", "testing"] cross_platform = true static_urls = false lock_version = "4.3" content_hash = "sha256:c043e9d6baac8ec6dcc0199d8af9025320ba616c27a40dea7da53b142b38b116" [[package]] name = "alabaster" version = "0.7.13" requires_python = ">=3.6" summary = "A configurable sidebar-enabled Sphinx theme" files = [ {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, ] [[package]] name = "atpublic" version = "4.0" requires_python = ">=3.8" summary = "Keep all y'all's __all__'s in sync" files = [ {file = "atpublic-4.0-py3-none-any.whl", hash = "sha256:80057c55641253b86dcb68b524f82328172371b6547d4c7462a9127fbfbbabfc"}, {file = "atpublic-4.0.tar.gz", hash = "sha256:0f40433219e124edf115c6c363808ca6f0e1cfa7d160d86b2fb94793086d1294"}, ] [[package]] name = "babel" version = "2.12.1" requires_python = ">=3.7" summary = "Internationalization utilities" dependencies = [ "pytz>=2015.7; python_version < \"3.9\"", ] files = [ {file = "Babel-2.12.1-py3-none-any.whl", hash = "sha256:b4246fb7677d3b98f501a39d43396d3cafdc8eadb045f4a31be01863f655c610"}, {file = "Babel-2.12.1.tar.gz", hash = "sha256:cc2d99999cd01d44420ae725a21c9e3711b3aadc7976d6147f622d8581963455"}, ] [[package]] name = "beautifulsoup4" version = "4.12.2" requires_python = ">=3.6.0" summary = "Screen-scraping library" dependencies = [ "soupsieve>1.2", ] files = [ {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"}, {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"}, ] [[package]] name = "black" version = "22.1.0" requires_python = ">=3.6.2" summary = "The uncompromising code formatter." dependencies = [ "click>=8.0.0", "mypy-extensions>=0.4.3", "pathspec>=0.9.0", "platformdirs>=2", "tomli>=1.1.0", "typing-extensions>=3.10.0.0; python_version < \"3.10\"", ] files = [ {file = "black-22.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1297c63b9e1b96a3d0da2d85d11cd9bf8664251fd69ddac068b98dc4f34f73b6"}, {file = "black-22.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2ff96450d3ad9ea499fc4c60e425a1439c2120cbbc1ab959ff20f7c76ec7e866"}, {file = "black-22.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e21e1f1efa65a50e3960edd068b6ae6d64ad6235bd8bfea116a03b21836af71"}, {file = "black-22.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f69158a7d120fd641d1fa9a921d898e20d52e44a74a6fbbcc570a62a6bc8ab"}, {file = "black-22.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:228b5ae2c8e3d6227e4bde5920d2fc66cc3400fde7bcc74f480cb07ef0b570d5"}, {file = "black-22.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b1a5ed73ab4c482208d20434f700d514f66ffe2840f63a6252ecc43a9bc77e8a"}, {file = "black-22.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35944b7100af4a985abfcaa860b06af15590deb1f392f06c8683b4381e8eeaf0"}, {file = "black-22.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7835fee5238fc0a0baf6c9268fb816b5f5cd9b8793423a75e8cd663c48d073ba"}, {file = "black-22.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dae63f2dbf82882fa3b2a3c49c32bffe144970a573cd68d247af6560fc493ae1"}, {file = "black-22.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fa1db02410b1924b6749c245ab38d30621564e658297484952f3d8a39fce7e8"}, {file = "black-22.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c8226f50b8c34a14608b848dc23a46e5d08397d009446353dad45e04af0c8e28"}, {file = "black-22.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2d6f331c02f0f40aa51a22e479c8209d37fcd520c77721c034517d44eecf5912"}, {file = "black-22.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:742ce9af3086e5bd07e58c8feb09dbb2b047b7f566eb5f5bc63fd455814979f3"}, {file = "black-22.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fdb8754b453fb15fad3f72cd9cad3e16776f0964d67cf30ebcbf10327a3777a3"}, {file = "black-22.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5660feab44c2e3cb24b2419b998846cbb01c23c7fe645fee45087efa3da2d61"}, {file = "black-22.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:6f2f01381f91c1efb1451998bd65a129b3ed6f64f79663a55fe0e9b74a5f81fd"}, {file = "black-22.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:efbadd9b52c060a8fc3b9658744091cb33c31f830b3f074422ed27bad2b18e8f"}, {file = "black-22.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8871fcb4b447206904932b54b567923e5be802b9b19b744fdff092bd2f3118d0"}, {file = "black-22.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccad888050f5393f0d6029deea2a33e5ae371fd182a697313bdbd835d3edaf9c"}, {file = "black-22.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07e5c049442d7ca1a2fc273c79d1aecbbf1bc858f62e8184abe1ad175c4f7cc2"}, {file = "black-22.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:373922fc66676133ddc3e754e4509196a8c392fec3f5ca4486673e685a421321"}, {file = "black-22.1.0-py3-none-any.whl", hash = "sha256:3524739d76b6b3ed1132422bf9d82123cd1705086723bc3e235ca39fd21c667d"}, {file = "black-22.1.0.tar.gz", hash = "sha256:a7c0192d35635f6fc1174be575cb7915e92e5dd629ee79fdaf0dcfa41a80afb5"}, ] [[package]] name = "blue" version = "0.9.1" summary = "Blue -- Some folks like black but I prefer blue." dependencies = [ "black==22.1.0", "flake8<5.0.0,>=3.8", ] files = [ {file = "blue-0.9.1-py3-none-any.whl", hash = "sha256:037742c072c58a2ff024f59fb9164257b907f97f8f862008db3b013d1f27cc22"}, {file = "blue-0.9.1.tar.gz", hash = "sha256:76b4f26884a8425042356601d80773db6e0e14bebaa7a59d7c54bf8cef2e2af5"}, ] [[package]] name = "certifi" version = "2023.5.7" requires_python = ">=3.6" summary = "Python package for providing Mozilla's CA Bundle." files = [ {file = "certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"}, {file = "certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"}, ] [[package]] name = "chardet" version = "5.1.0" requires_python = ">=3.7" summary = "Universal encoding detector for Python 3" files = [ {file = "chardet-5.1.0-py3-none-any.whl", hash = "sha256:362777fb014af596ad31334fde1e8c327dfdb076e1960d1694662d46a6917ab9"}, {file = "chardet-5.1.0.tar.gz", hash = "sha256:0d62712b956bc154f85fb0a266e2a3c5913c2967e00348701b32411d6def31e5"}, ] [[package]] name = "charset-normalizer" version = "3.1.0" requires_python = ">=3.7.0" summary = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." files = [ {file = "charset-normalizer-3.1.0.tar.gz", hash = "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5"}, {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b"}, {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60"}, {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1"}, {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0"}, {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f"}, {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0"}, {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795"}, {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c"}, {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203"}, {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1"}, {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137"}, {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce"}, {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a"}, {file = "charset_normalizer-3.1.0-cp310-cp310-win32.whl", hash = "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448"}, {file = "charset_normalizer-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8"}, {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19"}, {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017"}, {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df"}, {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a"}, {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41"}, {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1"}, {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62"}, {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6"}, {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5"}, {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be"}, {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb"}, {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac"}, {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324"}, {file = "charset_normalizer-3.1.0-cp311-cp311-win32.whl", hash = "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909"}, {file = "charset_normalizer-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755"}, {file = "charset_normalizer-3.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373"}, {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"}, {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9"}, {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f"}, {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28"}, {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d"}, {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d"}, {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d"}, {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6"}, {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84"}, {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c"}, {file = "charset_normalizer-3.1.0-cp37-cp37m-win32.whl", hash = "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974"}, {file = "charset_normalizer-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23"}, {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531"}, {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c"}, {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14"}, {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb"}, {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1"}, {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b"}, {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0"}, {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649"}, {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326"}, {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11"}, {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b"}, {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd"}, {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8"}, {file = "charset_normalizer-3.1.0-cp38-cp38-win32.whl", hash = "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0"}, {file = "charset_normalizer-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59"}, {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e"}, {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31"}, {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f"}, {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e"}, {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f"}, {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854"}, {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706"}, {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e"}, {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0"}, {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230"}, {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7"}, {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e"}, {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f"}, {file = "charset_normalizer-3.1.0-cp39-cp39-win32.whl", hash = "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1"}, {file = "charset_normalizer-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b"}, {file = "charset_normalizer-3.1.0-py3-none-any.whl", hash = "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d"}, ] [[package]] name = "click" version = "8.1.4" requires_python = ">=3.7" summary = "Composable command line interface toolkit" dependencies = [ "colorama; platform_system == \"Windows\"", ] files = [ {file = "click-8.1.4-py3-none-any.whl", hash = "sha256:2739815aaa5d2c986a88f1e9230c55e17f0caad3d958a5e13ad0797c166db9e3"}, {file = "click-8.1.4.tar.gz", hash = "sha256:b97d0c74955da062a7d4ef92fadb583806a585b2ea81958a81bd72726cbb8e37"}, ] [[package]] name = "colorama" version = "0.4.6" requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" summary = "Cross-platform colored terminal text." files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] [[package]] name = "coverage" version = "7.2.7" requires_python = ">=3.7" summary = "Code coverage measurement for Python" files = [ {file = "coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8"}, {file = "coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb"}, {file = "coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6"}, {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2"}, {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063"}, {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1"}, {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353"}, {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495"}, {file = "coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818"}, {file = "coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850"}, {file = "coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f"}, {file = "coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe"}, {file = "coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3"}, {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f"}, {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb"}, {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833"}, {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97"}, {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a"}, {file = "coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a"}, {file = "coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562"}, {file = "coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4"}, {file = "coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4"}, {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01"}, {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6"}, {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d"}, {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de"}, {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d"}, {file = "coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511"}, {file = "coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3"}, {file = "coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f"}, {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb"}, {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9"}, {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd"}, {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a"}, {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959"}, {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02"}, {file = "coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f"}, {file = "coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0"}, {file = "coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5"}, {file = "coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5"}, {file = "coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9"}, {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6"}, {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e"}, {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050"}, {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5"}, {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f"}, {file = "coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e"}, {file = "coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c"}, {file = "coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9"}, {file = "coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2"}, {file = "coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7"}, {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e"}, {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1"}, {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9"}, {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250"}, {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2"}, {file = "coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb"}, {file = "coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27"}, {file = "coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d"}, {file = "coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59"}, ] [[package]] name = "coverage" version = "7.2.7" extras = ["toml"] requires_python = ">=3.7" summary = "Code coverage measurement for Python" dependencies = [ "coverage==7.2.7", "tomli; python_full_version <= \"3.11.0a6\"", ] files = [ {file = "coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8"}, {file = "coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb"}, {file = "coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6"}, {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2"}, {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063"}, {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1"}, {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353"}, {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495"}, {file = "coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818"}, {file = "coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850"}, {file = "coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f"}, {file = "coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe"}, {file = "coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3"}, {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f"}, {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb"}, {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833"}, {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97"}, {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a"}, {file = "coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a"}, {file = "coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562"}, {file = "coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4"}, {file = "coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4"}, {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01"}, {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6"}, {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d"}, {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de"}, {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d"}, {file = "coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511"}, {file = "coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3"}, {file = "coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f"}, {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb"}, {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9"}, {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd"}, {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a"}, {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959"}, {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02"}, {file = "coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f"}, {file = "coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0"}, {file = "coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5"}, {file = "coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5"}, {file = "coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9"}, {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6"}, {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e"}, {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050"}, {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5"}, {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f"}, {file = "coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e"}, {file = "coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c"}, {file = "coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9"}, {file = "coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2"}, {file = "coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7"}, {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e"}, {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1"}, {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9"}, {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250"}, {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2"}, {file = "coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb"}, {file = "coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27"}, {file = "coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d"}, {file = "coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59"}, ] [[package]] name = "diff-cover" version = "7.7.0" requires_python = ">=3.7.2,<4.0.0" summary = "Run coverage and linting reports on diffs" dependencies = [ "Jinja2>=2.7.1", "Pygments<3.0.0,>=2.9.0", "chardet>=3.0.0", "pluggy<2,>=0.13.1", ] files = [ {file = "diff_cover-7.7.0-py3-none-any.whl", hash = "sha256:bf86f32ec999f9a9e79bf24969f7127ea7b4e55c3ef3cd9300feb13188c89736"}, {file = "diff_cover-7.7.0.tar.gz", hash = "sha256:60614cf7e722cf7fb1bde497afac0b514294e1e26534449622dac4da296123fb"}, ] [[package]] name = "docutils" version = "0.20.1" requires_python = ">=3.7" summary = "Docutils -- Python Documentation Utilities" files = [ {file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"}, {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, ] [[package]] name = "exceptiongroup" version = "1.1.2" requires_python = ">=3.7" summary = "Backport of PEP 654 (exception groups)" files = [ {file = "exceptiongroup-1.1.2-py3-none-any.whl", hash = "sha256:e346e69d186172ca7cf029c8c1d16235aa0e04035e5750b4b95039e65204328f"}, {file = "exceptiongroup-1.1.2.tar.gz", hash = "sha256:12c3e887d6485d16943a309616de20ae5582633e0a2eda17f4e10fd61c1e8af5"}, ] [[package]] name = "flake8" version = "4.0.1" requires_python = ">=3.6" summary = "the modular source code checker: pep8 pyflakes and co" dependencies = [ "mccabe<0.7.0,>=0.6.0", "pycodestyle<2.9.0,>=2.8.0", "pyflakes<2.5.0,>=2.4.0", ] files = [ {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, ] [[package]] name = "furo" version = "2023.5.20" requires_python = ">=3.7" summary = "A clean customisable Sphinx documentation theme." dependencies = [ "beautifulsoup4", "pygments>=2.7", "sphinx-basic-ng", "sphinx<8.0,>=6.0", ] files = [ {file = "furo-2023.5.20-py3-none-any.whl", hash = "sha256:594a8436ddfe0c071f3a9e9a209c314a219d8341f3f1af33fdf7c69544fab9e6"}, {file = "furo-2023.5.20.tar.gz", hash = "sha256:40e09fa17c6f4b22419d122e933089226dcdb59747b5b6c79363089827dea16f"}, ] [[package]] name = "idna" version = "3.4" requires_python = ">=3.5" summary = "Internationalized Domain Names in Applications (IDNA)" files = [ {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] [[package]] name = "imagesize" version = "1.4.1" requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" summary = "Getting image size from png/jpeg/jpeg2000/gif file" files = [ {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, ] [[package]] name = "importlib-metadata" version = "6.8.0" requires_python = ">=3.8" summary = "Read metadata from Python packages" dependencies = [ "zipp>=0.5", ] files = [ {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, ] [[package]] name = "iniconfig" version = "2.0.0" requires_python = ">=3.7" summary = "brain-dead simple config-ini parsing" files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] [[package]] name = "jinja2" version = "3.1.2" requires_python = ">=3.7" summary = "A very fast and expressive template engine." dependencies = [ "MarkupSafe>=2.0", ] files = [ {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, ] [[package]] name = "markupsafe" version = "2.1.3" requires_python = ">=3.7" summary = "Safely add untrusted strings to HTML/XML markup." files = [ {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, ] [[package]] name = "mccabe" version = "0.6.1" summary = "McCabe checker, plugin for flake8" files = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, ] [[package]] name = "mypy" version = "1.4.1" requires_python = ">=3.7" summary = "Optional static typing for Python" dependencies = [ "mypy-extensions>=1.0.0", "tomli>=1.1.0; python_version < \"3.11\"", "typing-extensions>=4.1.0", ] files = [ {file = "mypy-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566e72b0cd6598503e48ea610e0052d1b8168e60a46e0bfd34b3acf2d57f96a8"}, {file = "mypy-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca637024ca67ab24a7fd6f65d280572c3794665eaf5edcc7e90a866544076878"}, {file = "mypy-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dde1d180cd84f0624c5dcaaa89c89775550a675aff96b5848de78fb11adabcd"}, {file = "mypy-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8c4d8e89aa7de683e2056a581ce63c46a0c41e31bd2b6d34144e2c80f5ea53dc"}, {file = "mypy-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:bfdca17c36ae01a21274a3c387a63aa1aafe72bff976522886869ef131b937f1"}, {file = "mypy-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7549fbf655e5825d787bbc9ecf6028731973f78088fbca3a1f4145c39ef09462"}, {file = "mypy-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98324ec3ecf12296e6422939e54763faedbfcc502ea4a4c38502082711867258"}, {file = "mypy-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141dedfdbfe8a04142881ff30ce6e6653c9685b354876b12e4fe6c78598b45e2"}, {file = "mypy-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8207b7105829eca6f3d774f64a904190bb2231de91b8b186d21ffd98005f14a7"}, {file = "mypy-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:16f0db5b641ba159eff72cff08edc3875f2b62b2fa2bc24f68c1e7a4e8232d01"}, {file = "mypy-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:470c969bb3f9a9efcedbadcd19a74ffb34a25f8e6b0e02dae7c0e71f8372f97b"}, {file = "mypy-1.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5952d2d18b79f7dc25e62e014fe5a23eb1a3d2bc66318df8988a01b1a037c5b"}, {file = "mypy-1.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:190b6bab0302cec4e9e6767d3eb66085aef2a1cc98fe04936d8a42ed2ba77bb7"}, {file = "mypy-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9d40652cc4fe33871ad3338581dca3297ff5f2213d0df345bcfbde5162abf0c9"}, {file = "mypy-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01fd2e9f85622d981fd9063bfaef1aed6e336eaacca00892cd2d82801ab7c042"}, {file = "mypy-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2460a58faeea905aeb1b9b36f5065f2dc9a9c6e4c992a6499a2360c6c74ceca3"}, {file = "mypy-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2746d69a8196698146a3dbe29104f9eb6a2a4d8a27878d92169a6c0b74435b6"}, {file = "mypy-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae704dcfaa180ff7c4cfbad23e74321a2b774f92ca77fd94ce1049175a21c97f"}, {file = "mypy-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:43d24f6437925ce50139a310a64b2ab048cb2d3694c84c71c3f2a1626d8101dc"}, {file = "mypy-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c482e1246726616088532b5e964e39765b6d1520791348e6c9dc3af25b233828"}, {file = "mypy-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43b592511672017f5b1a483527fd2684347fdffc041c9ef53428c8dc530f79a3"}, {file = "mypy-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34a9239d5b3502c17f07fd7c0b2ae6b7dd7d7f6af35fbb5072c6208e76295816"}, {file = "mypy-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5703097c4936bbb9e9bce41478c8d08edd2865e177dc4c52be759f81ee4dd26c"}, {file = "mypy-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e02d700ec8d9b1859790c0475df4e4092c7bf3272a4fd2c9f33d87fac4427b8f"}, {file = "mypy-1.4.1-py3-none-any.whl", hash = "sha256:45d32cec14e7b97af848bddd97d85ea4f0db4d5a149ed9676caa4eb2f7402bb4"}, {file = "mypy-1.4.1.tar.gz", hash = "sha256:9bbcd9ab8ea1f2e1c8031c21445b511442cc45c89951e49bbf852cbb70755b1b"}, ] [[package]] name = "mypy-extensions" version = "1.0.0" requires_python = ">=3.5" summary = "Type system extensions for programs checked with the mypy type checker." files = [ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] [[package]] name = "packaging" version = "23.1" requires_python = ">=3.7" summary = "Core utilities for Python packages" files = [ {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, ] [[package]] name = "pathspec" version = "0.11.1" requires_python = ">=3.7" summary = "Utility library for gitignore style pattern matching of file paths." files = [ {file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"}, {file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"}, ] [[package]] name = "platformdirs" version = "3.8.1" requires_python = ">=3.7" summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." files = [ {file = "platformdirs-3.8.1-py3-none-any.whl", hash = "sha256:cec7b889196b9144d088e4c57d9ceef7374f6c39694ad1577a0aab50d27ea28c"}, {file = "platformdirs-3.8.1.tar.gz", hash = "sha256:f87ca4fcff7d2b0f81c6a748a77973d7af0f4d526f98f308477c3c436c74d528"}, ] [[package]] name = "pluggy" version = "1.2.0" requires_python = ">=3.7" summary = "plugin and hook calling mechanisms for python" files = [ {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, ] [[package]] name = "pycodestyle" version = "2.8.0" requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" summary = "Python style guide checker" files = [ {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, ] [[package]] name = "pyflakes" version = "2.4.0" requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" summary = "passive checker of Python programs" files = [ {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, ] [[package]] name = "pygments" version = "2.15.1" requires_python = ">=3.7" summary = "Pygments is a syntax highlighting package written in Python." files = [ {file = "Pygments-2.15.1-py3-none-any.whl", hash = "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"}, {file = "Pygments-2.15.1.tar.gz", hash = "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c"}, ] [[package]] name = "pytest" version = "7.4.0" requires_python = ">=3.7" summary = "pytest: simple powerful testing with Python" dependencies = [ "colorama; sys_platform == \"win32\"", "exceptiongroup>=1.0.0rc8; python_version < \"3.11\"", "iniconfig", "packaging", "pluggy<2.0,>=0.12", "tomli>=1.0.0; python_version < \"3.11\"", ] files = [ {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, ] [[package]] name = "pytest-cov" version = "4.1.0" requires_python = ">=3.7" summary = "Pytest plugin for measuring coverage." dependencies = [ "coverage[toml]>=5.2.1", "pytest>=4.6", ] files = [ {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, ] [[package]] name = "pytz" version = "2023.3" summary = "World timezone definitions, modern and historical" files = [ {file = "pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"}, {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"}, ] [[package]] name = "requests" version = "2.31.0" requires_python = ">=3.7" summary = "Python HTTP for Humans." dependencies = [ "certifi>=2017.4.17", "charset-normalizer<4,>=2", "idna<4,>=2.5", "urllib3<3,>=1.21.1", ] files = [ {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, ] [[package]] name = "ruff" version = "0.0.279" requires_python = ">=3.7" summary = "An extremely fast Python linter, written in Rust." files = [ {file = "ruff-0.0.279-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:16655197ff7c4144cdf0b340c482c46831dcb76ddb01674114781ac0073338cc"}, {file = "ruff-0.0.279-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:26c60bd783bf4ccc38279744cd260843aa7b81baf2c7e4f2fb8256b9c13fc704"}, {file = "ruff-0.0.279-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4322639cfb87493801fb38fa669fba049df08294d6453ab340b54c333cde47d"}, {file = "ruff-0.0.279-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ec3edc3c1caad4d8aa7e54b26118b676eb596e68b3549ed2165964b8b288c8c"}, {file = "ruff-0.0.279-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d2d3d8dacf674298d71ebc6c6a9e636eb506e7b739c12133d2bffe40143092f7"}, {file = "ruff-0.0.279-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:271af3464cad078f0cc9f1342a90346ea99bf048f7fdacd04333e788d7bc754e"}, {file = "ruff-0.0.279-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66cd498eb5b7f03ff86fe62a5d5a76aea9b8772cb2f54282ffc330089506806d"}, {file = "ruff-0.0.279-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5563fa6975f18c771ae928e7c0c15056b9d5603dc1c061b5aec487aa8c8a198d"}, {file = "ruff-0.0.279-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3d307ba5d5061e75215ef0f27af1a22eca64a3576673ffb42fca3f8b6b60a26"}, {file = "ruff-0.0.279-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7fa27f3d2c006d5767744a0133b5a27e7db4965695c612dbfc528fd4f418f8aa"}, {file = "ruff-0.0.279-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:357b3ecf508bddcc0c9e8f271434b4ae0594cb0a022b0db444ad567079f8490d"}, {file = "ruff-0.0.279-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f1976fbfaaaa513e251b9aa8bb5435feeae1ebc7670b52d74a63eee2eec9a2b4"}, {file = "ruff-0.0.279-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:2235250e03760886a3900dd1de2d30cb8a0ce4d7a65d622ef0200b77a2161fdb"}, {file = "ruff-0.0.279-py3-none-win32.whl", hash = "sha256:f49fed164034833d709d4539f54c0d1f2c9ac90b5afea5d67ad6ad7fac917f07"}, {file = "ruff-0.0.279-py3-none-win_amd64.whl", hash = "sha256:38c5e411b8817fa13d7e650499e191d32fd01cd3f29de8eee4bd87a70d5acc1c"}, {file = "ruff-0.0.279-py3-none-win_arm64.whl", hash = "sha256:b6a0a30bd9ec73cd743252a06535ffa17b033d3146d2c32c95736a6d73ea2f49"}, {file = "ruff-0.0.279.tar.gz", hash = "sha256:ad100904a9c3ffe550274d6c891373d70f570c284193129cdcd4188797cadb97"}, ] [[package]] name = "snowballstemmer" version = "2.2.0" summary = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." files = [ {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, ] [[package]] name = "soupsieve" version = "2.4.1" requires_python = ">=3.7" summary = "A modern CSS selector implementation for Beautiful Soup." files = [ {file = "soupsieve-2.4.1-py3-none-any.whl", hash = "sha256:1c1bfee6819544a3447586c889157365a27e10d88cde3ad3da0cf0ddf646feb8"}, {file = "soupsieve-2.4.1.tar.gz", hash = "sha256:89d12b2d5dfcd2c9e8c22326da9d9aa9cb3dfab0a83a024f05704076ee8d35ea"}, ] [[package]] name = "sphinx" version = "7.0.1" requires_python = ">=3.8" summary = "Python documentation generator" dependencies = [ "Jinja2>=3.0", "Pygments>=2.13", "alabaster<0.8,>=0.7", "babel>=2.9", "colorama>=0.4.5; sys_platform == \"win32\"", "docutils<0.21,>=0.18.1", "imagesize>=1.3", "importlib-metadata>=4.8; python_version < \"3.10\"", "packaging>=21.0", "requests>=2.25.0", "snowballstemmer>=2.0", "sphinxcontrib-applehelp", "sphinxcontrib-devhelp", "sphinxcontrib-htmlhelp>=2.0.0", "sphinxcontrib-jsmath", "sphinxcontrib-qthelp", "sphinxcontrib-serializinghtml>=1.1.5", ] files = [ {file = "Sphinx-7.0.1.tar.gz", hash = "sha256:61e025f788c5977d9412587e733733a289e2b9fdc2fef8868ddfbfc4ccfe881d"}, {file = "sphinx-7.0.1-py3-none-any.whl", hash = "sha256:60c5e04756c1709a98845ed27a2eed7a556af3993afb66e77fec48189f742616"}, ] [[package]] name = "sphinx-basic-ng" version = "1.0.0b1" requires_python = ">=3.7" summary = "A modern skeleton for Sphinx themes." dependencies = [ "sphinx>=4.0", ] files = [ {file = "sphinx_basic_ng-1.0.0b1-py3-none-any.whl", hash = "sha256:ade597a3029c7865b24ad0eda88318766bcc2f9f4cef60df7e28126fde94db2a"}, {file = "sphinx_basic_ng-1.0.0b1.tar.gz", hash = "sha256:89374bd3ccd9452a301786781e28c8718e99960f2d4f411845ea75fc7bb5a9b0"}, ] [[package]] name = "sphinxcontrib-applehelp" version = "1.0.4" requires_python = ">=3.8" summary = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" files = [ {file = "sphinxcontrib-applehelp-1.0.4.tar.gz", hash = "sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e"}, {file = "sphinxcontrib_applehelp-1.0.4-py3-none-any.whl", hash = "sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228"}, ] [[package]] name = "sphinxcontrib-devhelp" version = "1.0.2" requires_python = ">=3.5" summary = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." files = [ {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, ] [[package]] name = "sphinxcontrib-htmlhelp" version = "2.0.1" requires_python = ">=3.8" summary = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" files = [ {file = "sphinxcontrib-htmlhelp-2.0.1.tar.gz", hash = "sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff"}, {file = "sphinxcontrib_htmlhelp-2.0.1-py3-none-any.whl", hash = "sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903"}, ] [[package]] name = "sphinxcontrib-jsmath" version = "1.0.1" requires_python = ">=3.5" summary = "A sphinx extension which renders display math in HTML via JavaScript" files = [ {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, ] [[package]] name = "sphinxcontrib-qthelp" version = "1.0.3" requires_python = ">=3.5" summary = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." files = [ {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, ] [[package]] name = "sphinxcontrib-serializinghtml" version = "1.1.5" requires_python = ">=3.5" summary = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." files = [ {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, ] [[package]] name = "sybil" version = "5.0.3" requires_python = ">=3.7" summary = "Automated testing for the examples in your code and documentation." files = [ {file = "sybil-5.0.3-py3-none-any.whl", hash = "sha256:6f3c30822169895c4fb34c8366bdb132cf62bb68fb1d03d2ebb05282eab08c95"}, {file = "sybil-5.0.3.tar.gz", hash = "sha256:20dfe3a35a8d1ffcb4311434d1abf38c030c91064d75ff6b56ddd1060e08e758"}, ] [[package]] name = "tomli" version = "2.0.1" requires_python = ">=3.7" summary = "A lil' TOML parser" files = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] [[package]] name = "typing-extensions" version = "4.7.1" requires_python = ">=3.7" summary = "Backported and Experimental Type Hints for Python 3.7+" files = [ {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, ] [[package]] name = "urllib3" version = "2.0.3" requires_python = ">=3.7" summary = "HTTP library with thread-safe connection pooling, file post, and more." files = [ {file = "urllib3-2.0.3-py3-none-any.whl", hash = "sha256:48e7fafa40319d358848e1bc6809b208340fafe2096f1725d05d67443d0483d1"}, {file = "urllib3-2.0.3.tar.gz", hash = "sha256:bee28b5e56addb8226c96f7f13ac28cb4c301dd5ea8a6ca179c0b9835e032825"}, ] [[package]] name = "zipp" version = "3.15.0" requires_python = ">=3.7" summary = "Backport of pathlib-compatible object wrapper for zip files" files = [ {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, ] flufl.i18n-5.0.2/pyproject.toml000066400000000000000000000115441445662246600163440ustar00rootroot00000000000000[project] name = 'flufl.i18n' authors = [ {name = 'Barry Warsaw', email = 'barry@python.org'}, ] description = 'A high level API for internationalizing Python libraries and applications' readme = 'README.rst' requires-python = '>=3.8' license = {text = 'Apache-2.0'} keywords = [ 'i18n', 'internationalization', ] classifiers = [ 'Development Status :: 5 - Production/Stable', 'Development Status :: 6 - Mature', 'Intended Audience :: Developers', 'License :: OSI Approved :: Apache Software License', 'Operating System :: POSIX', 'Operating System :: MacOS :: MacOS X', 'Operating System :: Microsoft :: Windows', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Topic :: Software Development :: Internationalization', 'Topic :: Software Development :: Libraries', 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Software Development :: Localization', ] dependencies = [ 'atpublic', ] dynamic = ['version'] [project.urls] 'Home Page' = 'https://flufli18n.readthedocs.io' 'Documentation' = 'https://flufli18n.readthedocs.io' 'Source Code' = 'https://gitlab.com/warsaw/flufl.i18n.git' 'Bug Tracker' = 'https://gitlab.com/warsaw/flufl.i18n/issues' [tool.pdm] version = {source = 'file', path = 'src/flufl/i18n/__init__.py'} [tool.pdm.build] includes = [ 'src/flufl', ] excludes = [ '**/.mypy_cache', ] source-includes = [ 'docs/', 'test/', 'tox.ini', 'conftest.py', ] [tool.pdm.dev-dependencies] testing = [ 'coverage[toml]', 'diff-cover', 'pytest', 'pytest-cov', 'sybil', ] qa = [ 'blue', 'mypy', 'ruff', ] docs = [ 'sphinx', 'furo', ] [tool.pytest.ini_options] addopts = '--cov=flufl --cov-report=term --cov-report=xml -p no:doctest' testpaths = 'test docs' [tool.coverage.report] fail_under = 100 show_missing = true [tool.coverage.run] branch = true parallel = true [tool.coverage.paths] source = ['flufl/i18n'] [tool.ruff] line-length = 79 src = ['src'] select = [ 'B', # flake8-bugbear 'D', # pydocstyle 'E', # pycodestyle 'F', # pyflakes 'I', # isort 'RUF100', # check for valid noqa directives 'UP', # pyupgrade 'W', # pycodestyle ] ignore = [ 'D100', # Missing docstring in public module 'D104', # Missing docstring in public package 'D105', # Missing docstring in magic method ] [tool.ruff.pydocstyle] convention = 'pep257' [tool.ruff.isort] # 2023-06-04(warsaw): Some isort options are not yet supported by ruff. Also, # while isort supports order-by-type=false and case-sensitive=true to get the # preferred ordering of from-imports, ruff does not support this combination # (there's no case-sensitive option). We can achieve the same results by # setting order-by-type=true, although I suspect this won't do the right thing # in some of my other repos. # #include_trailing_comma = true known-first-party = ['flufl.i18n'] #length_sort_straight = true lines-after-imports = 2 lines-between-types = 1 #multi_line_output = 3 order-by-type = true [tool.ruff.per-file-ignores] # Ruff does not yet support length-sort-straight so ignore the warnings until # this issue is resolved: https://github.com/astral-sh/ruff/issues/1567 'src/flufl/i18n/_strategy.py' = ['I001'] 'src/flufl/i18n/_substitute.py' = ['I001'] 'src/flufl/i18n/_translator.py' = ['I001'] # This warning is tricky because ruff wants us to remove the blank line after # the Attributes: semi-header in the Application class docstring, but Sphinx # wants us to add it back. Since I prefer the rendered Sphinx docs, let's # ignore this ruff warning. 'src/flufl/i18n/_application.py' = ['D412'] [tool.mypy] mypy_path = 'src' namespace_packages = true # Disallow dynamic typing disallow_any_generics = true disallow_subclassing_any = true # Untyped definitions and calls disallow_untyped_calls = false disallow_untyped_defs = true disallow_incomplete_defs = true check_untyped_defs = true disallow_untyped_decorators = false # None and Optional handling no_implicit_optional = true # Configuring warnings warn_redundant_casts = true warn_unused_ignores = true warn_no_return = true warn_return_any = true warn_unreachable = true # Miscellaneous strictness flags implicit_reexport = false strict_equality = true # Configuring error messages show_error_context = true show_column_numbers = true show_error_codes = true pretty = true show_absolute_path = true # Miscellaneous warn_unused_configs = true verbosity = 0 [[tool.mypy.overrides]] module = [ 'public', 'pytest', 'sybil.*', 'psutil', ] ignore_missing_imports = true [build-system] requires = ['pdm-backend'] build-backend = 'pdm.backend' flufl.i18n-5.0.2/src/000077500000000000000000000000001445662246600142125ustar00rootroot00000000000000flufl.i18n-5.0.2/src/flufl/000077500000000000000000000000001445662246600153225ustar00rootroot00000000000000flufl.i18n-5.0.2/src/flufl/i18n/000077500000000000000000000000001445662246600161015ustar00rootroot00000000000000flufl.i18n-5.0.2/src/flufl/i18n/__init__.py000066400000000000000000000017551445662246600202220ustar00rootroot00000000000000from public import public as _public from flufl.i18n._application import Application from flufl.i18n._expand import expand from flufl.i18n._registry import registry from flufl.i18n._strategy import PackageStrategy, SimpleStrategy from flufl.i18n.types import ( RuntimeTranslator, TranslationContextManager, TranslationStrategy, ) __version__ = '5.0.2' _public( Application=Application, PackageStrategy=PackageStrategy, RuntimeTranslator=RuntimeTranslator, SimpleStrategy=SimpleStrategy, TranslationContextManager=TranslationContextManager, TranslationStrategy=TranslationStrategy, expand=expand, registry=registry, ) @_public def initialize(domain: str) -> RuntimeTranslator: """Initialize a translation context. :param domain: The application's name. :return: The translation function, typically bound to _() """ strategy = SimpleStrategy(domain) application = registry.register(strategy) return application._ del _public flufl.i18n-5.0.2/src/flufl/i18n/_application.py000066400000000000000000000237721445662246600211300ustar00rootroot00000000000000from gettext import NullTranslations from types import TracebackType from typing import Dict, List, Literal, NamedTuple, Optional, Type from public import public from flufl.i18n._translator import Translator from flufl.i18n.types import ( RuntimeTranslator, TranslationContextManager, TranslationStrategy, ) class _Using(TranslationContextManager): """Context manager for _.using().""" def __init__(self, application: 'Application', language_code: str): self._application = application self._language_code = language_code def __enter__(self) -> TranslationContextManager: self._application.push(self._language_code) return self def __exit__( self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> Literal[False]: self._application.pop() # Do not suppress exceptions. return False class _Defer(TranslationContextManager): """Context manager for _.defer_translation().""" # 2020-07-06(warsaw): Once Python 3.7 is the minimum supported version, we # can use `from __future__ import annotations` instead of the string type. def __init__(self, application: 'Application'): self._application = application def __enter__(self) -> TranslationContextManager: self._application.defer() return self def __exit__( self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> Literal[False]: self._application.pop() # Do not suppress exceptions. return False class _Underscore(RuntimeTranslator): """The implementation of the _() function. This class is internal representation only and has an incestuous relationship with the Application class. """ # 2020-07-06(warsaw): Once Python 3.7 is the minimum supported version, we # can use `from __future__ import annotations` instead of the string type. def __init__(self, application: 'Application'): self._application = application def __call__( self, original: str, extras: Optional[Dict[str, str]] = None ) -> str: """Translate the string into the language of the current context. :param original: The original string to translate. :param extras: Extra substitution mapping, elements of which override the locals and globals. :return: The translated string. """ return self._application.current.translate(original, extras) def using(self, language_code: str) -> TranslationContextManager: """Create a context manager for temporary translation. While in this context manager, translations use the given language code. When the with statement exits, the original language is restored. These are nestable. :param language_code: The language code for the translation context. :return: The new translation context manager. """ return _Using(self._application, language_code) def defer_translation(self) -> TranslationContextManager: """Push a NullTranslations onto the stack. This is useful for when you want to mark strings statically for extraction but you want to defer translation of the string until later. :return: The NULLTranslations context. """ return _Defer(self._application) def push(self, language_code: str) -> None: """Push a new catalog onto the stack. The translation catalog associated with the language code now becomes the currently active translation context. :param language_code: The language code for the translation context. """ self._application.push(language_code) def pop(self) -> None: """Pop the current catalog off the translation stack. No exception is raised for under-runs. In that case, pop() just no-ops and the null translation becomes the current translation context. """ self._application.pop() @property def default(self) -> Optional[str]: """Return the default language code. :return: The default language code, or None if there is no default language. """ return self._application.default @default.setter def default(self, language_code: str) -> None: """Set the default language code. :param language_code: The language code for the default translation context. """ self._application.default = language_code @default.deleter def default(self) -> None: """Reset the default language to the null translator.""" del self._application.default @property def code(self) -> Optional[str]: """The language code currently in effect, if there is one.""" code = self._application.code return self.default if code is None else code class _StackItem(NamedTuple): code: str translator: Translator @public class Application: """Manage all the catalogs for a particular application. You can ask the application for a specific catalog based on the language code. The Application requires a strategy for finding catalog files. Attributes: * dedent (default True) - controls whether translated strings are dedented or not. This is passed through to the underlying `Translator` instance. * depth (default 2) - The number of stack frames to call sys._getframe() with in the underlying `Translator` instance. Passed through to that class's constructor. """ def __init__(self, strategy: TranslationStrategy): """Create an `Application`. Use the `dedent` attribute on this instance to control whether translated strings are dedented or not. This is passed straight through to the `Translator` instance created in the _() method. :param strategy: A callable that can find catalog files for the application based on the language code. """ self._strategy = strategy # A mapping from language codes to catalogs. self._catalogs: Dict[str, NullTranslations] = {} self._stack: List[_StackItem] = [] # Arguments to the Translator constructor. self.dedent = True self.depth = 2 # By default, the baseline translator is the null translator. Use our # public API so that we share code. self._default_language: Optional[str] self._default_translator: Translator # This sets _default_language and _default_translator. del self.default @property def name(self) -> str: """The application name. :return: The application name. """ return self._strategy.name @property def default(self) -> Optional[str]: """The default language code, if there is one. :return: The default language code or None. """ return self._default_language @default.setter def default(self, language_code: str) -> None: """Set the default language code. :param language_code: The language code for the default translator. """ self._default_language = language_code catalog = self.get(language_code) self._default_translator = Translator(catalog, self.dedent, self.depth) @default.deleter def default(self) -> None: """Reset the default language to the null translator.""" self._default_language = None self._default_translator = Translator( self._strategy(), self.dedent, self.depth ) def get(self, language_code: str) -> NullTranslations: """Get the catalog associated with the language code. :param language_code: The language code. :return: A `gettext` catalog. """ try: return self._catalogs[language_code] except KeyError: catalog = self._strategy(language_code) self._catalogs[language_code] = catalog return catalog @property def _(self) -> RuntimeTranslator: """Return a translator object, tied to the current catalog. :return: A translator context object for the current active catalog. """ return _Underscore(self) def defer(self) -> None: """Push a deferred (i.e. null) translation context onto the stack. This is primarily used to support the ``_.defer_translation()`` context manager. """ self._stack.append( _StackItem( '', Translator(NullTranslations(), self.dedent, self.depth) ) ) def push(self, language_code: str) -> None: """Push a new catalog onto the stack. The translation catalog associated with the language code now becomes the currently active translation context. :param language_code: The language code for the translation context. """ catalog = self.get(language_code) translator = Translator(catalog, self.dedent, self.depth) self._stack.append(_StackItem(language_code, translator)) def pop(self) -> None: """Pop the current catalog off the translation stack. No exception is raised for under-runs. In that case, pop() just no-ops and the null translation becomes the current translation context. """ if len(self._stack) > 0: self._stack.pop() @property def current(self) -> Translator: """Return the current translator. :return: The current translator. """ if len(self._stack) == 0: return self._default_translator return self._stack[-1].translator @property def code(self) -> Optional[str]: """Return the current language code. :return: The current language code, or None if there isn't one. """ if len(self._stack) == 0: return None return self._stack[-1].code flufl.i18n-5.0.2/src/flufl/i18n/_expand.py000066400000000000000000000014461445662246600200760ustar00rootroot00000000000000"""String interpolation.""" import logging from string import Template from typing import Dict from public import public log = logging.getLogger('flufl.i18n') @public def expand( template: str, substitutions: Dict[str, str], template_class: type = Template, ) -> str: """Expand string template with substitutions. :param template: A PEP 292 $-string template. :param substitutions: The substitutions dictionary. :param template_class: The template class to use. :return: The substituted string. """ try: expanded: str = template_class(template).safe_substitute(substitutions) return expanded except (TypeError, ValueError): # The template is really screwed up. log.exception('broken template: %s', template) raise flufl.i18n-5.0.2/src/flufl/i18n/_registry.py000066400000000000000000000020351445662246600204620ustar00rootroot00000000000000from typing import Dict from public import public from flufl.i18n._application import Application from flufl.i18n.types import TranslationStrategy @public class Registry: """A registry of application translation lookup strategies.""" def __init__(self) -> None: # Map application names to Application instances. self._registry: Dict[str, Application] = {} def register(self, strategy: TranslationStrategy) -> Application: """Add an association between an application and a lookup strategy. :param strategy: An application translation lookup strategy. :type application: A callable object with a .name attribute :return: An application instance which can be used to access the language catalogs for the application. :rtype: `Application` """ application = Application(strategy) self._registry[strategy.name] = application return application registry: Registry # For mypy. public(registry=Registry()) flufl.i18n-5.0.2/src/flufl/i18n/_strategy.py000066400000000000000000000045031445662246600204560ustar00rootroot00000000000000import os import gettext from types import ModuleType from typing import Optional from public import public from flufl.i18n.types import TranslationStrategy class _BaseStrategy(TranslationStrategy): """Common code for strategies.""" def __init__(self, name: str): """Create a catalog lookup strategy. :param name: The application's name. """ self._name = name self._messages_dir: str def __call__( self, language_code: Optional[str] = None ) -> gettext.NullTranslations: """Find the catalog for the language. :param language_code: The language code to find. If None, then the default gettext language code lookup scheme is used. :return: A `gettext` catalog. """ # gettext.translation() requires None or a sequence. languages = None if language_code is None else [language_code] try: return gettext.translation( self.name, self._messages_dir, languages ) except OSError: # Fall back to untranslated source language. return gettext.NullTranslations() @property def name(self) -> str: """The application's name.""" return self._name @public class PackageStrategy(_BaseStrategy): """A strategy that finds catalogs based on package paths.""" def __init__(self, name: str, package: ModuleType): """Create a catalog lookup strategy. :param name: The application's name. :param package: The package path to the message catalogs. This strategy uses the __file__ (which must exist and be a string) of the package path as the directory containing `gettext` messages. """ super().__init__(name) if hasattr(package, '__file__') and isinstance(package.__file__, str): self._messages_dir = os.path.dirname(package.__file__) else: raise ValueError('Argument `package` must have a string `__file__') @public class SimpleStrategy(_BaseStrategy): """A simpler strategy for getting translations.""" def __init__(self, name: str): """Create a catalog lookup strategy. :param name: The application's name. """ super().__init__(name) self._messages_dir = os.environ.get('LOCPATH', '') flufl.i18n-5.0.2/src/flufl/i18n/_substitute.py000066400000000000000000000014021445662246600210220ustar00rootroot00000000000000"""Substitutions.""" import string from typing import cast, Union from public import public _missing = object() @public class Template(string.Template): """Match any attribute path.""" idpattern = r'[_a-z][_a-z0-9.]*[_a-z0-9]' @public # Implicit generic "Any". Use "typing.Dict" and specify generic parameters # [type-arg] class attrdict(dict): # type: ignore """Follow attribute paths.""" def __getitem__(self, key: str) -> str: parts = key.split('.') value: Union[str, object] = super().__getitem__(parts.pop(0)) while parts: value = getattr(value, parts.pop(0), _missing) if value is _missing: raise KeyError(key) return cast(str, value) flufl.i18n-5.0.2/src/flufl/i18n/_translator.py000066400000000000000000000056371445662246600210160ustar00rootroot00000000000000import sys import textwrap from gettext import NullTranslations from typing import Dict, Optional from public import public from flufl.i18n._expand import expand from flufl.i18n._substitute import attrdict, Template @public class Translator: """A translation context.""" def __init__( self, catalog: NullTranslations, dedent: bool = True, depth: int = 2 ): """Create a translation context. :param catalog: The translation catalog. :param dedent: Whether the input string should be dedented. :param depth: Number of stack frames to call sys._getframe() with. """ self._catalog = catalog self.dedent = dedent self.depth = depth def translate( self, original: str, extras: Optional[Dict[str, str]] = None ) -> str: """Translate the string. :param original: The original string to translate. :param extras: Extra substitution mapping, elements of which override the locals and globals. :return: The translated string. """ if original == '': return '' assert original, f'Cannot translate: {original}' # Because the original string is what the text extractors put into the # catalog, we must first look up the original unadulterated string in # the catalog. Use the global translation context for this. # # Translations must be unicode safe internally. The translation # service is one boundary to the outside world, so to honor this # constraint, make sure that all strings to come out of this are # unicodes, even if the translated string or dictionary values are # 8-bit strings. tns = self._catalog.gettext(original) # Do PEP 292 style $-string interpolation into the resulting string. # # This lets you write something like: # # now = time.ctime(time.time()) # print _('The current time is: $now') # # and have it Just Work. Key precedence is: # # extras > locals > globals # # Get the frame of the caller. frame = sys._getframe(self.depth) # Create the raw dictionary of substitutions. raw_dict = frame.f_globals.copy() raw_dict.update(frame.f_locals) if extras is not None: raw_dict.update(extras) # Python 2 requires ** dictionaries to have str, not unicode keys. # For our purposes, keys should always be ascii. Values though should # be unicode. translated_string: str = expand(tns, attrdict(raw_dict), Template) # Dedent the string if so desired. if self.dedent: translated_string = textwrap.dedent(translated_string) return translated_string @property def catalog(self) -> NullTranslations: """The translation catalog.""" return self._catalog flufl.i18n-5.0.2/src/flufl/i18n/py.typed000066400000000000000000000000001445662246600175660ustar00rootroot00000000000000flufl.i18n-5.0.2/src/flufl/i18n/types.py000066400000000000000000000105321445662246600176200ustar00rootroot00000000000000from abc import ABC, abstractmethod from gettext import NullTranslations from types import TracebackType from typing import Dict, Literal, Optional, Type from public import public @public class TranslationContextManager(ABC): """Context manager for translations in a particular language.""" @abstractmethod def __enter__(self) -> 'TranslationContextManager': pass # pragma: nocover @abstractmethod def __exit__( self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> Literal[False]: pass # pragma nocover @public class RuntimeTranslator(ABC): """Abstract class representing the interface for the _() function.""" @abstractmethod def __call__( self, original: str, extras: Optional[Dict[str, str]] = None ) -> str: """Translate the string into the language of the current context. This is the primary method for the runtime behavior or translating a source string. :param original: The original string to translate. :param extras: Extra substitution mapping, elements of which override the locals and globals. :return: The translated string. """ @abstractmethod def using(self, language_code: str) -> TranslationContextManager: """Create a context manager for temporary translation. While in this context manager, translations use the given language code. When the with statement exits, the original language is restored. These are nestable. :param language_code: The language code for the translation context. :return: The new translation context. """ @abstractmethod def defer_translation(self) -> TranslationContextManager: """Push a NullTranslations onto the stack. This is useful for when you want to mark strings statically for extraction but you want to defer translation of the string until later. :return: The NULLTranslations context. """ @abstractmethod def push(self, language_code: str) -> None: """Push a new catalog onto the stack. The translation catalog associated with the language code now becomes the currently active translation context. :param language_code: The language code for the translation context. """ @abstractmethod def pop(self) -> None: """Pop the current catalog off the translation stack. No exception is raised for under-runs. In that case, pop() just no-ops and the null translation becomes the current translation context. """ # 2023-06-08(warsaw): mypy as of 1.3.0 gives us a bogus error: # Overloaded method has both abstract and non-abstract variants [misc] # # https://github.com/python/mypy/issues/13649 # https://github.com/python/mypy/issues/4165 @property # type: ignore @abstractmethod def default(self) -> Optional[str]: """Return the default language code. :return: The default language code, or None if there is no default language. """ @default.setter @abstractmethod def default(self, language_code: str) -> None: """Set the default language code. :param language_code: The language code for the default translation context. """ @default.deleter @abstractmethod def default(self) -> None: """Reset the default language to the null translator.""" @property @abstractmethod def code(self) -> Optional[str]: """The language code currently in effect, if there is one.""" @public class TranslationStrategy(ABC): """Abstract class representing the interface for translation strategies.""" @abstractmethod def __call__( self, language_code: Optional[str] = None ) -> NullTranslations: """Find the catalog for the language. :param language_code: The language code to find. If None, then the default gettext language code lookup scheme is used. :return: A `gettext` catalog. """ @property @abstractmethod def name(self) -> str: """The application's name.""" flufl.i18n-5.0.2/test/000077500000000000000000000000001445662246600144025ustar00rootroot00000000000000flufl.i18n-5.0.2/test/__init__.py000066400000000000000000000000001445662246600165010ustar00rootroot00000000000000flufl.i18n-5.0.2/test/data/000077500000000000000000000000001445662246600153135ustar00rootroot00000000000000flufl.i18n-5.0.2/test/data/__init__.py000066400000000000000000000000001445662246600174120ustar00rootroot00000000000000flufl.i18n-5.0.2/test/data/messages/000077500000000000000000000000001445662246600171225ustar00rootroot00000000000000flufl.i18n-5.0.2/test/data/messages/__init__.py000066400000000000000000000000001445662246600212210ustar00rootroot00000000000000flufl.i18n-5.0.2/test/data/messages/xx/000077500000000000000000000000001445662246600175615ustar00rootroot00000000000000flufl.i18n-5.0.2/test/data/messages/xx/LC_MESSAGES/000077500000000000000000000000001445662246600213465ustar00rootroot00000000000000flufl.i18n-5.0.2/test/data/messages/xx/LC_MESSAGES/flufl.mo000066400000000000000000000007721445662246600230210ustar00rootroot000000000000004L`ap9 A test messageThe $ordinal test message $nameProject-Id-Version: PACKAGE VERSION POT-Creation-Date: Sun Jan 8 15:53:47 2006 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language-Team: LANGUAGE MIME-Version: 1.0 Content-Type: text/plain; charset=iso-8859-1 Content-Transfer-Encoding: 8bit Generated-By: hand N grfg zrffntr$ordinal si n grfg zrffntr $nameflufl.i18n-5.0.2/test/data/messages/xx/LC_MESSAGES/flufl.po000066400000000000000000000012411445662246600230140ustar00rootroot00000000000000# A testing catalog # Copyright (C) 2009-2015 Barry Warsaw # Barry Warsaw , 2009, 2010. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "POT-Creation-Date: Sun Jan 8 15:53:47 2006\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=iso-8859-1\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: hand\n" #: flufl/i18n/docs/readme.txt:39 msgid "A test message" msgstr "N grfg zrffntr" #: flufl/i18n/docs/readme.txt:40 msgid "The $ordinal test message $name" msgstr "$ordinal si n grfg zrffntr $name" flufl.i18n-5.0.2/test/data/messages/yy/000077500000000000000000000000001445662246600175635ustar00rootroot00000000000000flufl.i18n-5.0.2/test/data/messages/yy/LC_MESSAGES/000077500000000000000000000000001445662246600213505ustar00rootroot00000000000000flufl.i18n-5.0.2/test/data/messages/yy/LC_MESSAGES/flufl.mo000066400000000000000000000007711445662246600230220ustar00rootroot000000000000004L`ap9A test messageThe $ordinal test message $nameProject-Id-Version: PACKAGE VERSION POT-Creation-Date: Sun Jan 8 15:53:47 2006 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language-Team: LANGUAGE MIME-Version: 1.0 Content-Type: text/plain; charset=iso-8859-1 Content-Transfer-Encoding: 8bit Generated-By: hand egassem tset A$name egassem tset $ordinal ehtflufl.i18n-5.0.2/test/data/messages/yy/LC_MESSAGES/flufl.po000066400000000000000000000012401445662246600230150ustar00rootroot00000000000000# A testing catalog # Copyright (C) 2009-2015 Barry Warsaw # Barry Warsaw , 2009, 2010. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "POT-Creation-Date: Sun Jan 8 15:53:47 2006\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=iso-8859-1\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: hand\n" #: flufl/i18n/docs/readme.txt:39 msgid "A test message" msgstr "egassem tset A" #: flufl/i18n/docs/readme.txt:40 msgid "The $ordinal test message $name" msgstr "$name egassem tset $ordinal eht" flufl.i18n-5.0.2/test/test_application.py000066400000000000000000000005621445662246600203210ustar00rootroot00000000000000import pytest from .data import messages from flufl.i18n import Application, PackageStrategy def test_application_name(): strategy = PackageStrategy('flufl', messages) application = Application(strategy) assert application.name == 'flufl' def test_bogus_strategy(): with pytest.raises(ValueError): PackageStrategy('flufl', 'not-a-package') flufl.i18n-5.0.2/test/test_expand.py000066400000000000000000000015151445662246600172740ustar00rootroot00000000000000from types import SimpleNamespace from unittest.mock import patch import pytest from flufl.i18n._expand import expand from flufl.i18n._substitute import Template class FailingTemplateClass: def __init__(self, template): self.template = template def safe_substitute(self, *args, **kws): raise TypeError def test_exception(): with patch('flufl.i18n._expand.log.exception') as log: with pytest.raises(TypeError): expand('my-template', {}, FailingTemplateClass) log.assert_called_once_with('broken template: %s', 'my-template') def test_trailing_dot_is_not_a_placeholder(): # GL#12 - $foo.bar.baz. interpreted the trailing dot as part of the # placeholder, but it shouldn't be. expanded = expand('Me and $you.', dict(you='You'), Template) assert expanded == 'Me and You.' flufl.i18n-5.0.2/test/test_substitute.py000066400000000000000000000010101445662246600202160ustar00rootroot00000000000000from types import SimpleNamespace import pytest from flufl.i18n._substitute import attrdict def test_attrdict_parts(): ant = dict(bee=SimpleNamespace(cat=SimpleNamespace(dog='elk'))) anteater = attrdict(ant) assert anteater['bee.cat.dog'] == 'elk' def test_attrdict_missing(): ant = dict(bee=SimpleNamespace(cat=SimpleNamespace(dog='elk'))) anteater = attrdict(ant) with pytest.raises(KeyError) as exc_info: anteater['bee.cat.doo'] assert str(exc_info.value) == "'bee.cat.doo'" flufl.i18n-5.0.2/test/test_translator.py000066400000000000000000000070411445662246600202060ustar00rootroot00000000000000# This cannot be a doctest because of the sys._getframe() manipulations. That # does not play well with the way doctest executes Python code. But see # translator.txt for a description of how this should work in real Python code. import pytest from flufl.i18n._translator import Translator # Some globals for following tests. purple = 'porpoises' magenta = 'monkeys' green = 'gerbil' class Catalog: """Test catalog.""" def __init__(self): self.translation = None def gettext(self, original): """Return the translation.""" return self.translation def charset(self): """Return the encoding.""" # The default is ASCII. return None @pytest.fixture def translator(): # We need depth=1 because we're calling the translation at the same level # as the locals we care about. return Translator(Catalog(), depth=1) def test_locals(translator): # Test that locals get properly substituted. aqua = 'aardvarks' # noqa: F841 blue = 'badgers' # noqa: F841 cyan = 'cats' # noqa: F841 translator.catalog.translation = '$blue and $cyan and $aqua' translated = translator.translate('source string') assert translated == 'badgers and cats and aardvarks' def test_globals(translator): # Test that globals get properly substituted. translator.catalog.translation = '$purple and $magenta and $green' translated = translator.translate('source string') assert translated == 'porpoises and monkeys and gerbil' def test_dict_overrides_locals(translator): # Test that explicit mappings override locals. aqua = 'aardvarks' # noqa: F841 blue = 'badgers' # noqa: F841 cyan = 'cats' # noqa: F841 overrides = dict(blue='bats') translator.catalog.translation = '$blue and $cyan and $aqua' translated = translator.translate('source string', overrides) assert translated == 'bats and cats and aardvarks' def test_globals_with_overrides(translator): # Test that globals with overrides get properly substituted. translator.catalog.translation = '$purple and $magenta and $green' overrides = dict(green='giraffe') translated = translator.translate('source string', overrides) assert translated == 'porpoises and monkeys and giraffe' def test_empty_string(translator): # The empty string is always translated as the empty string. assert translator.translate('') == '' def test_dedent(translator): # By default, the translated string is always dedented. aqua = 'aardvarks' # noqa: F841 blue = 'badgers' # noqa: F841 cyan = 'cats' # noqa: F841 translator.catalog.translation = """\ These are the $blue These are the $cyan These are the $aqua """ for line in translator.translate('source string').splitlines(): assert line[:5] == 'These' def test_no_dedent(translator): # You can optionally suppress the dedent. aqua = 'aardvarks' # noqa: F841 blue = 'badgers' # noqa: F841 cyan = 'cats' # noqa: F841 translator.catalog.translation = """\ These are the $blue These are the $cyan These are the $aqua\ """ translator.dedent = False for line in translator.translate('source string').splitlines(): assert line[:9] == ' These' flufl.i18n-5.0.2/tox.ini000066400000000000000000000014041445662246600147350ustar00rootroot00000000000000[tox] envlist = py{38,39,310,311},qa,docs skip_missing_interpreters = true isolated_build = true [testenv] allowlist_externals = pdm setenv = PDM_IGNORE_SAVED_PYTHON="1" PYTHONPATH="" MODULE_PATH="src/flufl/i18n" MODULE_NAME="flufl.i18n" passenv = commands = pdm install -G testing pytest {posargs} # The following is only useful in a git repository. -diff-cover coverage.xml usedevelop = true [testenv:qa] commands = pdm install -G qa ruff check {env:MODULE_PATH} blue --check {env:MODULE_PATH} mypy -p {env:MODULE_NAME} [testenv:fixit] commands = pdm install -G qa ruff check --fix {env:MODULE_PATH} blue {env:MODULE_PATH} [testenv:docs] commands = pdm install -G docs sphinx-build docs build/html