-
-
\ No newline at end of file
diff --git a/venv/Lib/site-packages/Django-3.2.5.dist-info/INSTALLER b/venv/Lib/site-packages/Django-3.2.5.dist-info/INSTALLER
deleted file mode 100644
index a1b589e..0000000
--- a/venv/Lib/site-packages/Django-3.2.5.dist-info/INSTALLER
+++ /dev/null
@@ -1 +0,0 @@
-pip
diff --git a/venv/Lib/site-packages/Django-3.2.5.dist-info/REQUESTED b/venv/Lib/site-packages/Django-3.2.5.dist-info/REQUESTED
deleted file mode 100644
index e69de29..0000000
diff --git a/venv/Lib/site-packages/Django-3.2.5.dist-info/top_level.txt b/venv/Lib/site-packages/Django-3.2.5.dist-info/top_level.txt
deleted file mode 100644
index d3e4ba5..0000000
--- a/venv/Lib/site-packages/Django-3.2.5.dist-info/top_level.txt
+++ /dev/null
@@ -1 +0,0 @@
-django
diff --git a/venv/Lib/site-packages/Markdown-3.3.6.dist-info/INSTALLER b/venv/Lib/site-packages/Markdown-3.3.6.dist-info/INSTALLER
deleted file mode 100644
index a1b589e..0000000
--- a/venv/Lib/site-packages/Markdown-3.3.6.dist-info/INSTALLER
+++ /dev/null
@@ -1 +0,0 @@
-pip
diff --git a/venv/Lib/site-packages/Markdown-3.3.6.dist-info/LICENSE.md b/venv/Lib/site-packages/Markdown-3.3.6.dist-info/LICENSE.md
deleted file mode 100644
index 2652d97..0000000
--- a/venv/Lib/site-packages/Markdown-3.3.6.dist-info/LICENSE.md
+++ /dev/null
@@ -1,29 +0,0 @@
-Copyright 2007, 2008 The Python Markdown Project (v. 1.7 and later)
-Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b)
-Copyright 2004 Manfred Stienstra (the original version)
-
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-* Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
-* Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in the
- documentation and/or other materials provided with the distribution.
-* Neither the name of the Python Markdown Project nor the
- names of its contributors may be used to endorse or promote products
- derived from this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE PYTHON MARKDOWN PROJECT ''AS IS'' AND ANY
-EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL ANY CONTRIBUTORS TO THE PYTHON MARKDOWN PROJECT
-BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGE.
diff --git a/venv/Lib/site-packages/Markdown-3.3.6.dist-info/METADATA b/venv/Lib/site-packages/Markdown-3.3.6.dist-info/METADATA
deleted file mode 100644
index 0dd5837..0000000
--- a/venv/Lib/site-packages/Markdown-3.3.6.dist-info/METADATA
+++ /dev/null
@@ -1,110 +0,0 @@
-Metadata-Version: 2.1
-Name: Markdown
-Version: 3.3.6
-Summary: Python implementation of Markdown.
-Home-page: https://Python-Markdown.github.io/
-Author: Manfred Stienstra, Yuri takhteyev and Waylan limberg
-Author-email: python.markdown@gmail.com
-Maintainer: Waylan Limberg
-Maintainer-email: python.markdown@gmail.com
-License: BSD License
-Project-URL: Documentation, https://Python-Markdown.github.io/
-Project-URL: GitHub Project, https://github.com/Python-Markdown/markdown
-Project-URL: Issue Tracker, https://github.com/Python-Markdown/markdown/issues
-Platform: UNKNOWN
-Classifier: Development Status :: 5 - Production/Stable
-Classifier: License :: OSI Approved :: BSD License
-Classifier: Operating System :: OS Independent
-Classifier: Programming Language :: Python
-Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.6
-Classifier: Programming Language :: Python :: 3.7
-Classifier: Programming Language :: Python :: 3.8
-Classifier: Programming Language :: Python :: 3.9
-Classifier: Programming Language :: Python :: 3.10
-Classifier: Programming Language :: Python :: 3 :: Only
-Classifier: Programming Language :: Python :: Implementation :: CPython
-Classifier: Programming Language :: Python :: Implementation :: PyPy
-Classifier: Topic :: Communications :: Email :: Filters
-Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries
-Classifier: Topic :: Internet :: WWW/HTTP :: Site Management
-Classifier: Topic :: Software Development :: Documentation
-Classifier: Topic :: Software Development :: Libraries :: Python Modules
-Classifier: Topic :: Text Processing :: Filters
-Classifier: Topic :: Text Processing :: Markup :: HTML
-Classifier: Topic :: Text Processing :: Markup :: Markdown
-Requires-Python: >=3.6
-Description-Content-Type: text/markdown
-License-File: LICENSE.md
-Requires-Dist: importlib-metadata (>=4.4) ; python_version < "3.10"
-Provides-Extra: testing
-Requires-Dist: coverage ; extra == 'testing'
-Requires-Dist: pyyaml ; extra == 'testing'
-
-[Python-Markdown][]
-===================
-
-[![Build Status][build-button]][build]
-[![Coverage Status][codecov-button]][codecov]
-[![Latest Version][mdversion-button]][md-pypi]
-[![Python Versions][pyversion-button]][md-pypi]
-[![BSD License][bsdlicense-button]][bsdlicense]
-[![Code of Conduct][codeofconduct-button]][Code of Conduct]
-
-[build-button]: https://github.com/Python-Markdown/markdown/workflows/CI/badge.svg?event=push
-[build]: https://github.com/Python-Markdown/markdown/actions?query=workflow%3ACI+event%3Apush
-[codecov-button]: https://codecov.io/gh/Python-Markdown/markdown/branch/master/graph/badge.svg
-[codecov]: https://codecov.io/gh/Python-Markdown/markdown
-[mdversion-button]: https://img.shields.io/pypi/v/Markdown.svg
-[md-pypi]: https://pypi.org/project/Markdown/
-[pyversion-button]: https://img.shields.io/pypi/pyversions/Markdown.svg
-[bsdlicense-button]: https://img.shields.io/badge/license-BSD-yellow.svg
-[bsdlicense]: https://opensource.org/licenses/BSD-3-Clause
-[codeofconduct-button]: https://img.shields.io/badge/code%20of%20conduct-contributor%20covenant-green.svg?style=flat-square
-[Code of Conduct]: https://github.com/Python-Markdown/markdown/blob/master/CODE_OF_CONDUCT.md
-
-This is a Python implementation of John Gruber's [Markdown][].
-It is almost completely compliant with the reference implementation,
-though there are a few known issues. See [Features][] for information
-on what exactly is supported and what is not. Additional features are
-supported by the [Available Extensions][].
-
-[Python-Markdown]: https://Python-Markdown.github.io/
-[Markdown]: https://daringfireball.net/projects/markdown/
-[Features]: https://Python-Markdown.github.io#Features
-[Available Extensions]: https://Python-Markdown.github.io/extensions
-
-Documentation
--------------
-
-```bash
-pip install markdown
-```
-```python
-import markdown
-html = markdown.markdown(your_text_string)
-```
-
-For more advanced [installation] and [usage] documentation, see the `docs/` directory
-of the distribution or the project website at .
-
-[installation]: https://python-markdown.github.io/install/
-[usage]: https://python-markdown.github.io/reference/
-
-See the change log at .
-
-Support
--------
-
-You may report bugs, ask for help, and discuss various other issues on the [bug tracker][].
-
-[bug tracker]: https://github.com/Python-Markdown/markdown/issues
-
-Code of Conduct
----------------
-
-Everyone interacting in the Python-Markdown project's codebases, issue trackers,
-and mailing lists is expected to follow the [Code of Conduct].
-
-
-
diff --git a/venv/Lib/site-packages/Markdown-3.3.6.dist-info/RECORD b/venv/Lib/site-packages/Markdown-3.3.6.dist-info/RECORD
deleted file mode 100644
index dd376e7..0000000
--- a/venv/Lib/site-packages/Markdown-3.3.6.dist-info/RECORD
+++ /dev/null
@@ -1,77 +0,0 @@
-../../Scripts/markdown_py.exe,sha256=vCw3UluhGhDZ5OHeFwbu_i2uuAow6Ag76pfC21_f61A,106375
-Markdown-3.3.6.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
-Markdown-3.3.6.dist-info/LICENSE.md,sha256=bxGTy2NHGOZcOlN9biXr1hSCDsDvaTz8EiSBEmONZNo,1645
-Markdown-3.3.6.dist-info/METADATA,sha256=5gK5efFze8GvYs5GX7G5M597OXkRKG5DtqP8CvscZVQ,4630
-Markdown-3.3.6.dist-info/RECORD,,
-Markdown-3.3.6.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
-Markdown-3.3.6.dist-info/WHEEL,sha256=ewwEueio1C2XeHTvT17n8dZUJgOvyCWCt0WVNLClP9o,92
-Markdown-3.3.6.dist-info/entry_points.txt,sha256=j4jiKg-iwZGImvi8OzotZePWoFbJJ4GrfzDqH03u3SQ,1103
-Markdown-3.3.6.dist-info/top_level.txt,sha256=IAxs8x618RXoH1uCqeLLxXsDefJvE_mIibr_M4sOlyk,9
-markdown/__init__.py,sha256=002-LuHviYzROW2rg_gBGai81nMouUNO9UFj5nSsTSk,2065
-markdown/__main__.py,sha256=JX1057VoovH3NA5uH5nQdQE8b0kXoeT79ZxCzFoL_kg,5803
-markdown/__meta__.py,sha256=IDpR5cdETCEXvY2YxKlRPnIQvdyf5vHJolJNDD4Um9w,1630
-markdown/__pycache__/__init__.cpython-39.pyc,,
-markdown/__pycache__/__main__.cpython-39.pyc,,
-markdown/__pycache__/__meta__.cpython-39.pyc,,
-markdown/__pycache__/blockparser.cpython-39.pyc,,
-markdown/__pycache__/blockprocessors.cpython-39.pyc,,
-markdown/__pycache__/core.cpython-39.pyc,,
-markdown/__pycache__/htmlparser.cpython-39.pyc,,
-markdown/__pycache__/inlinepatterns.cpython-39.pyc,,
-markdown/__pycache__/pep562.cpython-39.pyc,,
-markdown/__pycache__/postprocessors.cpython-39.pyc,,
-markdown/__pycache__/preprocessors.cpython-39.pyc,,
-markdown/__pycache__/serializers.cpython-39.pyc,,
-markdown/__pycache__/test_tools.cpython-39.pyc,,
-markdown/__pycache__/treeprocessors.cpython-39.pyc,,
-markdown/__pycache__/util.cpython-39.pyc,,
-markdown/blockparser.py,sha256=JpBhOokOoBUGCXolftOc5m1hPcR2y9s9hVd9WSuhHzo,4285
-markdown/blockprocessors.py,sha256=LK4mfcgjH8rk3zsyxBzxisxdQjpFj0xkg1LxBtlpLUs,24890
-markdown/core.py,sha256=ZHtqvLdVHOKWIuX_UzdL3rIcxMwji5TC5ZCkV19iM4U,15401
-markdown/extensions/__init__.py,sha256=nw2VtafIf5zHjAcUuykQbaNY6taOmNn7ARn11-Pe080,3661
-markdown/extensions/__pycache__/__init__.cpython-39.pyc,,
-markdown/extensions/__pycache__/abbr.cpython-39.pyc,,
-markdown/extensions/__pycache__/admonition.cpython-39.pyc,,
-markdown/extensions/__pycache__/attr_list.cpython-39.pyc,,
-markdown/extensions/__pycache__/codehilite.cpython-39.pyc,,
-markdown/extensions/__pycache__/def_list.cpython-39.pyc,,
-markdown/extensions/__pycache__/extra.cpython-39.pyc,,
-markdown/extensions/__pycache__/fenced_code.cpython-39.pyc,,
-markdown/extensions/__pycache__/footnotes.cpython-39.pyc,,
-markdown/extensions/__pycache__/legacy_attrs.cpython-39.pyc,,
-markdown/extensions/__pycache__/legacy_em.cpython-39.pyc,,
-markdown/extensions/__pycache__/md_in_html.cpython-39.pyc,,
-markdown/extensions/__pycache__/meta.cpython-39.pyc,,
-markdown/extensions/__pycache__/nl2br.cpython-39.pyc,,
-markdown/extensions/__pycache__/sane_lists.cpython-39.pyc,,
-markdown/extensions/__pycache__/smarty.cpython-39.pyc,,
-markdown/extensions/__pycache__/tables.cpython-39.pyc,,
-markdown/extensions/__pycache__/toc.cpython-39.pyc,,
-markdown/extensions/__pycache__/wikilinks.cpython-39.pyc,,
-markdown/extensions/abbr.py,sha256=5TNU5ml6-H1n-fztEkgUphSTvp5yKCXaiPZMrVuRFvo,3186
-markdown/extensions/admonition.py,sha256=INIecvdzQ7RLmgP8M-N6AZJ5uMd6dBfh9Uj6YibgNLk,5847
-markdown/extensions/attr_list.py,sha256=nhKFY_u6BVyKW2oMUeC4wEjqFNGpDSnNXqaohuF6M7I,5988
-markdown/extensions/codehilite.py,sha256=aEorLnWkEA_zwC2gAoqlR5nb8ZwjiUUFe0bA8bVP0Co,11654
-markdown/extensions/def_list.py,sha256=p-JT64hKqMkfxlmhETMVRPxjrdnBIPDW8k3S05S-qNM,3634
-markdown/extensions/extra.py,sha256=udRN8OvSWcq3UwkPygvsFl1RlCVtCJ-ARVg2IwVH6VY,1831
-markdown/extensions/fenced_code.py,sha256=pRZjVaEh8JZdhyLb2Vy7alfqIQNuPtN2Mzt_Imj2Vm0,7346
-markdown/extensions/footnotes.py,sha256=xvT6etWuTWTHLNHXYQWQGV-35RHTCvH9kBp2xJA6Jdg,15481
-markdown/extensions/legacy_attrs.py,sha256=2EaVQkxQoNnP8_lMPvGRBdNda8L4weUQroiyEuVdS-w,2547
-markdown/extensions/legacy_em.py,sha256=18j4L6zdScy9k18y-U2zaIhYsKVTxCaPurjqLFZmWkI,1582
-markdown/extensions/md_in_html.py,sha256=17w2s-YvjzKPWmng9La6J9-1-h1TWNEFBhVd0pF-j9U,15830
-markdown/extensions/meta.py,sha256=EUfkzM7l7UpH__Or9K3pl8ldVddwndlCZWA3d712RAE,2331
-markdown/extensions/nl2br.py,sha256=wAqTNOuf2L1NzlEvEqoID70n9y-aiYaGLkuyQk3CD0w,783
-markdown/extensions/sane_lists.py,sha256=ZQmCf-247KBexVG0fc62nDvokGkV6W1uavYbieNKSG4,1505
-markdown/extensions/smarty.py,sha256=0padzkVCNACainKw-Xj1S5UfT0125VCTfNejmrCZItA,10238
-markdown/extensions/tables.py,sha256=bicFx_wqhnEx6Y_8MJqA56rh71pt5fOe94oiWbvcobY,7685
-markdown/extensions/toc.py,sha256=Q8YP0DIuyl_B0GfUASYbE4q_B7zFsHS93t5fjiZyJWE,14136
-markdown/extensions/wikilinks.py,sha256=GkgT9BY7b1-qW--dIwFAhC9V20RoeF13b7CFdw_V21Q,2812
-markdown/htmlparser.py,sha256=K3OMq-OU2CTWCMNGPbMGqMmiwZAIrG2lQxuDOJxDjYo,13032
-markdown/inlinepatterns.py,sha256=csrxrPIET_nltn-phz80ObXu5i5oibK68h9ZCWT5eAo,29775
-markdown/pep562.py,sha256=5UkqT7sb-cQufgbOl_jF-RYUVVHS7VThzlMzR9vrd3I,8917
-markdown/postprocessors.py,sha256=NeJyWBqPeDuBBJLTGs5Bfm5oTkUBXk9HWBeQy2_OldI,4262
-markdown/preprocessors.py,sha256=-s8QGHGlX7JAIJTfCivuc-CVwTLWs0IyEU94YUT2IvQ,2742
-markdown/serializers.py,sha256=_wQl-iJrPSUEQ4Q1owWYqN9qceVh6TOlAOH_i44BKAQ,6540
-markdown/test_tools.py,sha256=svokrqFAHJ1H_BiYhEY9kilmk4dZIO3jYJTJcOsphCg,8361
-markdown/treeprocessors.py,sha256=S91w6byWeyBF96q9w8SJ_8UQV8p0UuFJ7Brj6Rw0-y4,15433
-markdown/util.py,sha256=1BKofVbYfqmgAK982UJATA22pj-ee9BcwxaHxB_bOZg,16063
diff --git a/venv/Lib/site-packages/Markdown-3.3.6.dist-info/REQUESTED b/venv/Lib/site-packages/Markdown-3.3.6.dist-info/REQUESTED
deleted file mode 100644
index e69de29..0000000
diff --git a/venv/Lib/site-packages/Markdown-3.3.6.dist-info/WHEEL b/venv/Lib/site-packages/Markdown-3.3.6.dist-info/WHEEL
deleted file mode 100644
index 5bad85f..0000000
--- a/venv/Lib/site-packages/Markdown-3.3.6.dist-info/WHEEL
+++ /dev/null
@@ -1,5 +0,0 @@
-Wheel-Version: 1.0
-Generator: bdist_wheel (0.37.0)
-Root-Is-Purelib: true
-Tag: py3-none-any
-
diff --git a/venv/Lib/site-packages/Markdown-3.3.6.dist-info/entry_points.txt b/venv/Lib/site-packages/Markdown-3.3.6.dist-info/entry_points.txt
deleted file mode 100644
index f49693d..0000000
--- a/venv/Lib/site-packages/Markdown-3.3.6.dist-info/entry_points.txt
+++ /dev/null
@@ -1,23 +0,0 @@
-[console_scripts]
-markdown_py = markdown.__main__:run
-
-[markdown.extensions]
-abbr = markdown.extensions.abbr:AbbrExtension
-admonition = markdown.extensions.admonition:AdmonitionExtension
-attr_list = markdown.extensions.attr_list:AttrListExtension
-codehilite = markdown.extensions.codehilite:CodeHiliteExtension
-def_list = markdown.extensions.def_list:DefListExtension
-extra = markdown.extensions.extra:ExtraExtension
-fenced_code = markdown.extensions.fenced_code:FencedCodeExtension
-footnotes = markdown.extensions.footnotes:FootnoteExtension
-legacy_attrs = markdown.extensions.legacy_attrs:LegacyAttrExtension
-legacy_em = markdown.extensions.legacy_em:LegacyEmExtension
-md_in_html = markdown.extensions.md_in_html:MarkdownInHtmlExtension
-meta = markdown.extensions.meta:MetaExtension
-nl2br = markdown.extensions.nl2br:Nl2BrExtension
-sane_lists = markdown.extensions.sane_lists:SaneListExtension
-smarty = markdown.extensions.smarty:SmartyExtension
-tables = markdown.extensions.tables:TableExtension
-toc = markdown.extensions.toc:TocExtension
-wikilinks = markdown.extensions.wikilinks:WikiLinkExtension
-
diff --git a/venv/Lib/site-packages/Markdown-3.3.6.dist-info/top_level.txt b/venv/Lib/site-packages/Markdown-3.3.6.dist-info/top_level.txt
deleted file mode 100644
index 0918c97..0000000
--- a/venv/Lib/site-packages/Markdown-3.3.6.dist-info/top_level.txt
+++ /dev/null
@@ -1 +0,0 @@
-markdown
diff --git a/venv/Lib/site-packages/MySQLdb/__init__.py b/venv/Lib/site-packages/MySQLdb/__init__.py
deleted file mode 100644
index b567363..0000000
--- a/venv/Lib/site-packages/MySQLdb/__init__.py
+++ /dev/null
@@ -1,170 +0,0 @@
-"""
-MySQLdb - A DB API v2.0 compatible interface to MySQL.
-
-This package is a wrapper around _mysql, which mostly implements the
-MySQL C API.
-
-connect() -- connects to server
-
-See the C API specification and the MySQL documentation for more info
-on other items.
-
-For information on how MySQLdb handles type conversion, see the
-MySQLdb.converters module.
-"""
-
-try:
- from MySQLdb.release import version_info
- from . import _mysql
-
- assert version_info == _mysql.version_info
-except Exception:
- raise ImportError(
- "this is MySQLdb version {}, but _mysql is version {!r}\n_mysql: {!r}".format(
- version_info, _mysql.version_info, _mysql.__file__
- )
- )
-
-
-from ._mysql import (
- NotSupportedError,
- OperationalError,
- get_client_info,
- ProgrammingError,
- Error,
- InterfaceError,
- debug,
- IntegrityError,
- string_literal,
- MySQLError,
- DataError,
- DatabaseError,
- InternalError,
- Warning,
-)
-from MySQLdb.constants import FIELD_TYPE
-from MySQLdb.times import (
- Date,
- Time,
- Timestamp,
- DateFromTicks,
- TimeFromTicks,
- TimestampFromTicks,
-)
-
-threadsafety = 1
-apilevel = "2.0"
-paramstyle = "format"
-
-
-class DBAPISet(frozenset):
- """A special type of set for which A == x is true if A is a
- DBAPISet and x is a member of that set."""
-
- def __eq__(self, other):
- if isinstance(other, DBAPISet):
- return not self.difference(other)
- return other in self
-
-
-STRING = DBAPISet([FIELD_TYPE.ENUM, FIELD_TYPE.STRING, FIELD_TYPE.VAR_STRING])
-BINARY = DBAPISet(
- [
- FIELD_TYPE.BLOB,
- FIELD_TYPE.LONG_BLOB,
- FIELD_TYPE.MEDIUM_BLOB,
- FIELD_TYPE.TINY_BLOB,
- ]
-)
-NUMBER = DBAPISet(
- [
- FIELD_TYPE.DECIMAL,
- FIELD_TYPE.DOUBLE,
- FIELD_TYPE.FLOAT,
- FIELD_TYPE.INT24,
- FIELD_TYPE.LONG,
- FIELD_TYPE.LONGLONG,
- FIELD_TYPE.TINY,
- FIELD_TYPE.YEAR,
- FIELD_TYPE.NEWDECIMAL,
- ]
-)
-DATE = DBAPISet([FIELD_TYPE.DATE])
-TIME = DBAPISet([FIELD_TYPE.TIME])
-TIMESTAMP = DBAPISet([FIELD_TYPE.TIMESTAMP, FIELD_TYPE.DATETIME])
-DATETIME = TIMESTAMP
-ROWID = DBAPISet()
-
-
-def test_DBAPISet_set_equality():
- assert STRING == STRING
-
-
-def test_DBAPISet_set_inequality():
- assert STRING != NUMBER
-
-
-def test_DBAPISet_set_equality_membership():
- assert FIELD_TYPE.VAR_STRING == STRING
-
-
-def test_DBAPISet_set_inequality_membership():
- assert FIELD_TYPE.DATE != STRING
-
-
-def Binary(x):
- return bytes(x)
-
-
-def Connect(*args, **kwargs):
- """Factory function for connections.Connection."""
- from MySQLdb.connections import Connection
-
- return Connection(*args, **kwargs)
-
-
-connect = Connection = Connect
-
-__all__ = [
- "BINARY",
- "Binary",
- "Connect",
- "Connection",
- "DATE",
- "Date",
- "Time",
- "Timestamp",
- "DateFromTicks",
- "TimeFromTicks",
- "TimestampFromTicks",
- "DataError",
- "DatabaseError",
- "Error",
- "FIELD_TYPE",
- "IntegrityError",
- "InterfaceError",
- "InternalError",
- "MySQLError",
- "NUMBER",
- "NotSupportedError",
- "DBAPISet",
- "OperationalError",
- "ProgrammingError",
- "ROWID",
- "STRING",
- "TIME",
- "TIMESTAMP",
- "Warning",
- "apilevel",
- "connect",
- "connections",
- "constants",
- "converters",
- "cursors",
- "debug",
- "get_client_info",
- "paramstyle",
- "string_literal",
- "threadsafety",
- "version_info",
-]
diff --git a/venv/Lib/site-packages/MySQLdb/_exceptions.py b/venv/Lib/site-packages/MySQLdb/_exceptions.py
deleted file mode 100644
index ba35dea..0000000
--- a/venv/Lib/site-packages/MySQLdb/_exceptions.py
+++ /dev/null
@@ -1,69 +0,0 @@
-"""Exception classes for _mysql and MySQLdb.
-
-These classes are dictated by the DB API v2.0:
-
- https://www.python.org/dev/peps/pep-0249/
-"""
-
-
-class MySQLError(Exception):
- """Exception related to operation with MySQL."""
-
-
-class Warning(Warning, MySQLError):
- """Exception raised for important warnings like data truncations
- while inserting, etc."""
-
-
-class Error(MySQLError):
- """Exception that is the base class of all other error exceptions
- (not Warning)."""
-
-
-class InterfaceError(Error):
- """Exception raised for errors that are related to the database
- interface rather than the database itself."""
-
-
-class DatabaseError(Error):
- """Exception raised for errors that are related to the
- database."""
-
-
-class DataError(DatabaseError):
- """Exception raised for errors that are due to problems with the
- processed data like division by zero, numeric value out of range,
- etc."""
-
-
-class OperationalError(DatabaseError):
- """Exception raised for errors that are related to the database's
- operation and not necessarily under the control of the programmer,
- e.g. an unexpected disconnect occurs, the data source name is not
- found, a transaction could not be processed, a memory allocation
- error occurred during processing, etc."""
-
-
-class IntegrityError(DatabaseError):
- """Exception raised when the relational integrity of the database
- is affected, e.g. a foreign key check fails, duplicate key,
- etc."""
-
-
-class InternalError(DatabaseError):
- """Exception raised when the database encounters an internal
- error, e.g. the cursor is not valid anymore, the transaction is
- out of sync, etc."""
-
-
-class ProgrammingError(DatabaseError):
- """Exception raised for programming errors, e.g. table not found
- or already exists, syntax error in the SQL statement, wrong number
- of parameters specified, etc."""
-
-
-class NotSupportedError(DatabaseError):
- """Exception raised in case a method or database API was used
- which is not supported by the database, e.g. requesting a
- .rollback() on a connection that does not support transaction or
- has transactions turned off."""
diff --git a/venv/Lib/site-packages/MySQLdb/_mysql.cp39-win_amd64.pyd b/venv/Lib/site-packages/MySQLdb/_mysql.cp39-win_amd64.pyd
deleted file mode 100644
index c2edd42..0000000
Binary files a/venv/Lib/site-packages/MySQLdb/_mysql.cp39-win_amd64.pyd and /dev/null differ
diff --git a/venv/Lib/site-packages/MySQLdb/connections.py b/venv/Lib/site-packages/MySQLdb/connections.py
deleted file mode 100644
index 3832466..0000000
--- a/venv/Lib/site-packages/MySQLdb/connections.py
+++ /dev/null
@@ -1,333 +0,0 @@
-"""
-This module implements connections for MySQLdb. Presently there is
-only one class: Connection. Others are unlikely. However, you might
-want to make your own subclasses. In most cases, you will probably
-override Connection.default_cursor with a non-standard Cursor class.
-"""
-import re
-
-from . import cursors, _mysql
-from ._exceptions import (
- Warning,
- Error,
- InterfaceError,
- DataError,
- DatabaseError,
- OperationalError,
- IntegrityError,
- InternalError,
- NotSupportedError,
- ProgrammingError,
-)
-
-# Mapping from MySQL charset name to Python codec name
-_charset_to_encoding = {
- "utf8mb4": "utf8",
- "utf8mb3": "utf8",
- "latin1": "cp1252",
- "koi8r": "koi8_r",
- "koi8u": "koi8_u",
-}
-
-re_numeric_part = re.compile(r"^(\d+)")
-
-
-def numeric_part(s):
- """Returns the leading numeric part of a string.
-
- >>> numeric_part("20-alpha")
- 20
- >>> numeric_part("foo")
- >>> numeric_part("16b")
- 16
- """
-
- m = re_numeric_part.match(s)
- if m:
- return int(m.group(1))
- return None
-
-
-class Connection(_mysql.connection):
- """MySQL Database Connection Object"""
-
- default_cursor = cursors.Cursor
-
- def __init__(self, *args, **kwargs):
- """
- Create a connection to the database. It is strongly recommended
- that you only use keyword parameters. Consult the MySQL C API
- documentation for more information.
-
- :param str host: host to connect
- :param str user: user to connect as
- :param str password: password to use
- :param str passwd: alias of password (deprecated)
- :param str database: database to use
- :param str db: alias of database (deprecated)
- :param int port: TCP/IP port to connect to
- :param str unix_socket: location of unix_socket to use
- :param dict conv: conversion dictionary, see MySQLdb.converters
- :param int connect_timeout:
- number of seconds to wait before the connection attempt fails.
-
- :param bool compress: if set, compression is enabled
- :param str named_pipe: if set, a named pipe is used to connect (Windows only)
- :param str init_command:
- command which is run once the connection is created
-
- :param str read_default_file:
- file from which default client values are read
-
- :param str read_default_group:
- configuration group to use from the default file
-
- :param type cursorclass:
- class object, used to create cursors (keyword only)
-
- :param bool use_unicode:
- If True, text-like columns are returned as unicode objects
- using the connection's character set. Otherwise, text-like
- columns are returned as bytes. Unicode objects will always
- be encoded to the connection's character set regardless of
- this setting.
- Default to True.
-
- :param str charset:
- If supplied, the connection character set will be changed
- to this character set.
-
- :param str auth_plugin:
- If supplied, the connection default authentication plugin will be
- changed to this value. Example values:
- `mysql_native_password` or `caching_sha2_password`
-
- :param str sql_mode:
- If supplied, the session SQL mode will be changed to this
- setting.
- For more details and legal values, see the MySQL documentation.
-
- :param int client_flag:
- flags to use or 0 (see MySQL docs or constants/CLIENTS.py)
-
- :param bool multi_statements:
- If True, enable multi statements for clients >= 4.1.
- Defaults to True.
-
- :param str ssl_mode:
- specify the security settings for connection to the server;
- see the MySQL documentation for more details
- (mysql_option(), MYSQL_OPT_SSL_MODE).
- Only one of 'DISABLED', 'PREFERRED', 'REQUIRED',
- 'VERIFY_CA', 'VERIFY_IDENTITY' can be specified.
-
- :param dict ssl:
- dictionary or mapping contains SSL connection parameters;
- see the MySQL documentation for more details
- (mysql_ssl_set()). If this is set, and the client does not
- support SSL, NotSupportedError will be raised.
-
- :param bool local_infile:
- enables LOAD LOCAL INFILE; zero disables
-
- :param bool autocommit:
- If False (default), autocommit is disabled.
- If True, autocommit is enabled.
- If None, autocommit isn't set and server default is used.
-
- :param bool binary_prefix:
- If set, the '_binary' prefix will be used for raw byte query
- arguments (e.g. Binary). This is disabled by default.
-
- There are a number of undocumented, non-standard methods. See the
- documentation for the MySQL C API for some hints on what they do.
- """
- from MySQLdb.constants import CLIENT, FIELD_TYPE
- from MySQLdb.converters import conversions, _bytes_or_str
- from weakref import proxy
-
- kwargs2 = kwargs.copy()
-
- if "db" in kwargs2:
- kwargs2["database"] = kwargs2.pop("db")
- if "passwd" in kwargs2:
- kwargs2["password"] = kwargs2.pop("passwd")
-
- if "conv" in kwargs:
- conv = kwargs["conv"]
- else:
- conv = conversions
-
- conv2 = {}
- for k, v in conv.items():
- if isinstance(k, int) and isinstance(v, list):
- conv2[k] = v[:]
- else:
- conv2[k] = v
- kwargs2["conv"] = conv2
-
- cursorclass = kwargs2.pop("cursorclass", self.default_cursor)
- charset = kwargs2.get("charset", "")
- use_unicode = kwargs2.pop("use_unicode", True)
- sql_mode = kwargs2.pop("sql_mode", "")
- self._binary_prefix = kwargs2.pop("binary_prefix", False)
-
- client_flag = kwargs.get("client_flag", 0)
- client_flag |= CLIENT.MULTI_RESULTS
- multi_statements = kwargs2.pop("multi_statements", True)
- if multi_statements:
- client_flag |= CLIENT.MULTI_STATEMENTS
- kwargs2["client_flag"] = client_flag
-
- # PEP-249 requires autocommit to be initially off
- autocommit = kwargs2.pop("autocommit", False)
-
- super().__init__(*args, **kwargs2)
- self.cursorclass = cursorclass
- self.encoders = {k: v for k, v in conv.items() if type(k) is not int}
-
- self._server_version = tuple(
- [numeric_part(n) for n in self.get_server_info().split(".")[:2]]
- )
-
- self.encoding = "ascii" # overridden in set_character_set()
-
- if not charset:
- charset = self.character_set_name()
- self.set_character_set(charset)
-
- if sql_mode:
- self.set_sql_mode(sql_mode)
-
- if use_unicode:
- for t in (
- FIELD_TYPE.STRING,
- FIELD_TYPE.VAR_STRING,
- FIELD_TYPE.VARCHAR,
- FIELD_TYPE.TINY_BLOB,
- FIELD_TYPE.MEDIUM_BLOB,
- FIELD_TYPE.LONG_BLOB,
- FIELD_TYPE.BLOB,
- ):
- self.converter[t] = _bytes_or_str
- # Unlike other string/blob types, JSON is always text.
- # MySQL may return JSON with charset==binary.
- self.converter[FIELD_TYPE.JSON] = str
-
- db = proxy(self)
-
- def unicode_literal(u, dummy=None):
- return db.string_literal(u.encode(db.encoding))
-
- self.encoders[str] = unicode_literal
-
- self._transactional = self.server_capabilities & CLIENT.TRANSACTIONS
- if self._transactional:
- if autocommit is not None:
- self.autocommit(autocommit)
- self.messages = []
-
- def __enter__(self):
- return self
-
- def __exit__(self, exc_type, exc_value, traceback):
- self.close()
-
- def autocommit(self, on):
- on = bool(on)
- if self.get_autocommit() != on:
- _mysql.connection.autocommit(self, on)
-
- def cursor(self, cursorclass=None):
- """
- Create a cursor on which queries may be performed. The
- optional cursorclass parameter is used to create the
- Cursor. By default, self.cursorclass=cursors.Cursor is
- used.
- """
- return (cursorclass or self.cursorclass)(self)
-
- def query(self, query):
- # Since _mysql releases GIL while querying, we need immutable buffer.
- if isinstance(query, bytearray):
- query = bytes(query)
- _mysql.connection.query(self, query)
-
- def _bytes_literal(self, bs):
- assert isinstance(bs, (bytes, bytearray))
- x = self.string_literal(bs) # x is escaped and quoted bytes
- if self._binary_prefix:
- return b"_binary" + x
- return x
-
- def _tuple_literal(self, t):
- return b"(%s)" % (b",".join(map(self.literal, t)))
-
- def literal(self, o):
- """If o is a single object, returns an SQL literal as a string.
- If o is a non-string sequence, the items of the sequence are
- converted and returned as a sequence.
-
- Non-standard. For internal use; do not use this in your
- applications.
- """
- if isinstance(o, str):
- s = self.string_literal(o.encode(self.encoding))
- elif isinstance(o, bytearray):
- s = self._bytes_literal(o)
- elif isinstance(o, bytes):
- s = self._bytes_literal(o)
- elif isinstance(o, (tuple, list)):
- s = self._tuple_literal(o)
- else:
- s = self.escape(o, self.encoders)
- if isinstance(s, str):
- s = s.encode(self.encoding)
- assert isinstance(s, bytes)
- return s
-
- def begin(self):
- """Explicitly begin a connection.
-
- This method is not used when autocommit=False (default).
- """
- self.query(b"BEGIN")
-
- def set_character_set(self, charset):
- """Set the connection character set to charset."""
- super().set_character_set(charset)
- self.encoding = _charset_to_encoding.get(charset, charset)
-
- def set_sql_mode(self, sql_mode):
- """Set the connection sql_mode. See MySQL documentation for
- legal values."""
- if self._server_version < (4, 1):
- raise NotSupportedError("server is too old to set sql_mode")
- self.query("SET SESSION sql_mode='%s'" % sql_mode)
- self.store_result()
-
- def show_warnings(self):
- """Return detailed information about warnings as a
- sequence of tuples of (Level, Code, Message). This
- is only supported in MySQL-4.1 and up. If your server
- is an earlier version, an empty sequence is returned."""
- if self._server_version < (4, 1):
- return ()
- self.query("SHOW WARNINGS")
- r = self.store_result()
- warnings = r.fetch_row(0)
- return warnings
-
- Warning = Warning
- Error = Error
- InterfaceError = InterfaceError
- DatabaseError = DatabaseError
- DataError = DataError
- OperationalError = OperationalError
- IntegrityError = IntegrityError
- InternalError = InternalError
- ProgrammingError = ProgrammingError
- NotSupportedError = NotSupportedError
-
-
-# vim: colorcolumn=100
diff --git a/venv/Lib/site-packages/MySQLdb/constants/CLIENT.py b/venv/Lib/site-packages/MySQLdb/constants/CLIENT.py
deleted file mode 100644
index 35f578c..0000000
--- a/venv/Lib/site-packages/MySQLdb/constants/CLIENT.py
+++ /dev/null
@@ -1,27 +0,0 @@
-"""MySQL CLIENT constants
-
-These constants are used when creating the connection. Use bitwise-OR
-(|) to combine options together, and pass them as the client_flags
-parameter to MySQLdb.Connection. For more information on these flags,
-see the MySQL C API documentation for mysql_real_connect().
-
-"""
-
-LONG_PASSWORD = 1
-FOUND_ROWS = 2
-LONG_FLAG = 4
-CONNECT_WITH_DB = 8
-NO_SCHEMA = 16
-COMPRESS = 32
-ODBC = 64
-LOCAL_FILES = 128
-IGNORE_SPACE = 256
-CHANGE_USER = 512
-INTERACTIVE = 1024
-SSL = 2048
-IGNORE_SIGPIPE = 4096
-TRANSACTIONS = 8192 # mysql_com.h was WRONG prior to 3.23.35
-RESERVED = 16384
-SECURE_CONNECTION = 32768
-MULTI_STATEMENTS = 65536
-MULTI_RESULTS = 131072
diff --git a/venv/Lib/site-packages/MySQLdb/constants/CR.py b/venv/Lib/site-packages/MySQLdb/constants/CR.py
deleted file mode 100644
index 9d33cf6..0000000
--- a/venv/Lib/site-packages/MySQLdb/constants/CR.py
+++ /dev/null
@@ -1,105 +0,0 @@
-"""MySQL Connection Errors
-
-Nearly all of these raise OperationalError. COMMANDS_OUT_OF_SYNC
-raises ProgrammingError.
-
-"""
-
-if __name__ == "__main__":
- """
- Usage: python CR.py [/path/to/mysql/errmsg.h ...] >> CR.py
- """
- import fileinput
- import re
-
- data = {}
- error_last = None
- for line in fileinput.input():
- line = re.sub(r"/\*.*?\*/", "", line)
- m = re.match(r"^\s*#define\s+CR_([A-Z0-9_]+)\s+(\d+)(\s.*|$)", line)
- if m:
- name = m.group(1)
- value = int(m.group(2))
- if name == "ERROR_LAST":
- if error_last is None or error_last < value:
- error_last = value
- continue
- if value not in data:
- data[value] = set()
- data[value].add(name)
- for value, names in sorted(data.items()):
- for name in sorted(names):
- print("{} = {}".format(name, value))
- if error_last is not None:
- print("ERROR_LAST = %s" % error_last)
-
-
-ERROR_FIRST = 2000
-MIN_ERROR = 2000
-UNKNOWN_ERROR = 2000
-SOCKET_CREATE_ERROR = 2001
-CONNECTION_ERROR = 2002
-CONN_HOST_ERROR = 2003
-IPSOCK_ERROR = 2004
-UNKNOWN_HOST = 2005
-SERVER_GONE_ERROR = 2006
-VERSION_ERROR = 2007
-OUT_OF_MEMORY = 2008
-WRONG_HOST_INFO = 2009
-LOCALHOST_CONNECTION = 2010
-TCP_CONNECTION = 2011
-SERVER_HANDSHAKE_ERR = 2012
-SERVER_LOST = 2013
-COMMANDS_OUT_OF_SYNC = 2014
-NAMEDPIPE_CONNECTION = 2015
-NAMEDPIPEWAIT_ERROR = 2016
-NAMEDPIPEOPEN_ERROR = 2017
-NAMEDPIPESETSTATE_ERROR = 2018
-CANT_READ_CHARSET = 2019
-NET_PACKET_TOO_LARGE = 2020
-EMBEDDED_CONNECTION = 2021
-PROBE_SLAVE_STATUS = 2022
-PROBE_SLAVE_HOSTS = 2023
-PROBE_SLAVE_CONNECT = 2024
-PROBE_MASTER_CONNECT = 2025
-SSL_CONNECTION_ERROR = 2026
-MALFORMED_PACKET = 2027
-WRONG_LICENSE = 2028
-NULL_POINTER = 2029
-NO_PREPARE_STMT = 2030
-PARAMS_NOT_BOUND = 2031
-DATA_TRUNCATED = 2032
-NO_PARAMETERS_EXISTS = 2033
-INVALID_PARAMETER_NO = 2034
-INVALID_BUFFER_USE = 2035
-UNSUPPORTED_PARAM_TYPE = 2036
-SHARED_MEMORY_CONNECTION = 2037
-SHARED_MEMORY_CONNECT_REQUEST_ERROR = 2038
-SHARED_MEMORY_CONNECT_ANSWER_ERROR = 2039
-SHARED_MEMORY_CONNECT_FILE_MAP_ERROR = 2040
-SHARED_MEMORY_CONNECT_MAP_ERROR = 2041
-SHARED_MEMORY_FILE_MAP_ERROR = 2042
-SHARED_MEMORY_MAP_ERROR = 2043
-SHARED_MEMORY_EVENT_ERROR = 2044
-SHARED_MEMORY_CONNECT_ABANDONED_ERROR = 2045
-SHARED_MEMORY_CONNECT_SET_ERROR = 2046
-CONN_UNKNOW_PROTOCOL = 2047
-INVALID_CONN_HANDLE = 2048
-UNUSED_1 = 2049
-FETCH_CANCELED = 2050
-NO_DATA = 2051
-NO_STMT_METADATA = 2052
-NO_RESULT_SET = 2053
-NOT_IMPLEMENTED = 2054
-SERVER_LOST_EXTENDED = 2055
-STMT_CLOSED = 2056
-NEW_STMT_METADATA = 2057
-ALREADY_CONNECTED = 2058
-AUTH_PLUGIN_CANNOT_LOAD = 2059
-DUPLICATE_CONNECTION_ATTR = 2060
-AUTH_PLUGIN_ERR = 2061
-INSECURE_API_ERR = 2062
-FILE_NAME_TOO_LONG = 2063
-SSL_FIPS_MODE_ERR = 2064
-MAX_ERROR = 2999
-ERROR_LAST = 2064
diff --git a/venv/Lib/site-packages/MySQLdb/constants/ER.py b/venv/Lib/site-packages/MySQLdb/constants/ER.py
deleted file mode 100644
index fcd5bf2..0000000
--- a/venv/Lib/site-packages/MySQLdb/constants/ER.py
+++ /dev/null
@@ -1,827 +0,0 @@
-"""MySQL ER Constants
-
-These constants are error codes for the bulk of the error conditions
-that may occur.
-"""
-
-if __name__ == "__main__":
- """
- Usage: python ER.py [/path/to/mysql/mysqld_error.h ...] >> ER.py
- """
- import fileinput
- import re
-
- data = {}
- error_last = None
- for line in fileinput.input():
- line = re.sub(r"/\*.*?\*/", "", line)
- m = re.match(r"^\s*#define\s+((ER|WARN)_[A-Z0-9_]+)\s+(\d+)\s*", line)
- if m:
- name = m.group(1)
- if name.startswith("ER_"):
- name = name[3:]
- value = int(m.group(3))
- if name == "ERROR_LAST":
- if error_last is None or error_last < value:
- error_last = value
- continue
- if value not in data:
- data[value] = set()
- data[value].add(name)
- for value, names in sorted(data.items()):
- for name in sorted(names):
- print("{} = {}".format(name, value))
- if error_last is not None:
- print("ERROR_LAST = %s" % error_last)
-
-
-ERROR_FIRST = 1000
-NO = 1002
-YES = 1003
-CANT_CREATE_FILE = 1004
-CANT_CREATE_TABLE = 1005
-CANT_CREATE_DB = 1006
-DB_CREATE_EXISTS = 1007
-DB_DROP_EXISTS = 1008
-DB_DROP_RMDIR = 1010
-CANT_FIND_SYSTEM_REC = 1012
-CANT_GET_STAT = 1013
-CANT_LOCK = 1015
-CANT_OPEN_FILE = 1016
-FILE_NOT_FOUND = 1017
-CANT_READ_DIR = 1018
-CHECKREAD = 1020
-DUP_KEY = 1022
-ERROR_ON_READ = 1024
-ERROR_ON_RENAME = 1025
-ERROR_ON_WRITE = 1026
-FILE_USED = 1027
-FILSORT_ABORT = 1028
-GET_ERRNO = 1030
-ILLEGAL_HA = 1031
-KEY_NOT_FOUND = 1032
-NOT_FORM_FILE = 1033
-NOT_KEYFILE = 1034
-OLD_KEYFILE = 1035
-OPEN_AS_READONLY = 1036
-OUTOFMEMORY = 1037
-OUT_OF_SORTMEMORY = 1038
-CON_COUNT_ERROR = 1040
-OUT_OF_RESOURCES = 1041
-BAD_HOST_ERROR = 1042
-HANDSHAKE_ERROR = 1043
-DBACCESS_DENIED_ERROR = 1044
-ACCESS_DENIED_ERROR = 1045
-NO_DB_ERROR = 1046
-UNKNOWN_COM_ERROR = 1047
-BAD_NULL_ERROR = 1048
-BAD_DB_ERROR = 1049
-TABLE_EXISTS_ERROR = 1050
-BAD_TABLE_ERROR = 1051
-NON_UNIQ_ERROR = 1052
-SERVER_SHUTDOWN = 1053
-BAD_FIELD_ERROR = 1054
-WRONG_FIELD_WITH_GROUP = 1055
-WRONG_GROUP_FIELD = 1056
-WRONG_SUM_SELECT = 1057
-WRONG_VALUE_COUNT = 1058
-TOO_LONG_IDENT = 1059
-DUP_FIELDNAME = 1060
-DUP_KEYNAME = 1061
-DUP_ENTRY = 1062
-WRONG_FIELD_SPEC = 1063
-PARSE_ERROR = 1064
-EMPTY_QUERY = 1065
-NONUNIQ_TABLE = 1066
-INVALID_DEFAULT = 1067
-MULTIPLE_PRI_KEY = 1068
-TOO_MANY_KEYS = 1069
-TOO_MANY_KEY_PARTS = 1070
-TOO_LONG_KEY = 1071
-KEY_COLUMN_DOES_NOT_EXITS = 1072
-BLOB_USED_AS_KEY = 1073
-TOO_BIG_FIELDLENGTH = 1074
-WRONG_AUTO_KEY = 1075
-READY = 1076
-SHUTDOWN_COMPLETE = 1079
-FORCING_CLOSE = 1080
-IPSOCK_ERROR = 1081
-NO_SUCH_INDEX = 1082
-WRONG_FIELD_TERMINATORS = 1083
-BLOBS_AND_NO_TERMINATED = 1084
-TEXTFILE_NOT_READABLE = 1085
-FILE_EXISTS_ERROR = 1086
-LOAD_INFO = 1087
-ALTER_INFO = 1088
-WRONG_SUB_KEY = 1089
-CANT_REMOVE_ALL_FIELDS = 1090
-CANT_DROP_FIELD_OR_KEY = 1091
-INSERT_INFO = 1092
-UPDATE_TABLE_USED = 1093
-NO_SUCH_THREAD = 1094
-KILL_DENIED_ERROR = 1095
-NO_TABLES_USED = 1096
-TOO_BIG_SET = 1097
-NO_UNIQUE_LOGFILE = 1098
-TABLE_NOT_LOCKED_FOR_WRITE = 1099
-TABLE_NOT_LOCKED = 1100
-BLOB_CANT_HAVE_DEFAULT = 1101
-WRONG_DB_NAME = 1102
-WRONG_TABLE_NAME = 1103
-TOO_BIG_SELECT = 1104
-UNKNOWN_ERROR = 1105
-UNKNOWN_PROCEDURE = 1106
-WRONG_PARAMCOUNT_TO_PROCEDURE = 1107
-WRONG_PARAMETERS_TO_PROCEDURE = 1108
-UNKNOWN_TABLE = 1109
-FIELD_SPECIFIED_TWICE = 1110
-INVALID_GROUP_FUNC_USE = 1111
-UNSUPPORTED_EXTENSION = 1112
-TABLE_MUST_HAVE_COLUMNS = 1113
-RECORD_FILE_FULL = 1114
-UNKNOWN_CHARACTER_SET = 1115
-TOO_MANY_TABLES = 1116
-TOO_MANY_FIELDS = 1117
-TOO_BIG_ROWSIZE = 1118
-STACK_OVERRUN = 1119
-WRONG_OUTER_JOIN_UNUSED = 1120
-NULL_COLUMN_IN_INDEX = 1121
-CANT_FIND_UDF = 1122
-CANT_INITIALIZE_UDF = 1123
-UDF_NO_PATHS = 1124
-UDF_EXISTS = 1125
-CANT_OPEN_LIBRARY = 1126
-CANT_FIND_DL_ENTRY = 1127
-FUNCTION_NOT_DEFINED = 1128
-HOST_IS_BLOCKED = 1129
-HOST_NOT_PRIVILEGED = 1130
-PASSWORD_ANONYMOUS_USER = 1131
-PASSWORD_NOT_ALLOWED = 1132
-PASSWORD_NO_MATCH = 1133
-UPDATE_INFO = 1134
-CANT_CREATE_THREAD = 1135
-WRONG_VALUE_COUNT_ON_ROW = 1136
-CANT_REOPEN_TABLE = 1137
-INVALID_USE_OF_NULL = 1138
-REGEXP_ERROR = 1139
-MIX_OF_GROUP_FUNC_AND_FIELDS = 1140
-NONEXISTING_GRANT = 1141
-TABLEACCESS_DENIED_ERROR = 1142
-COLUMNACCESS_DENIED_ERROR = 1143
-ILLEGAL_GRANT_FOR_TABLE = 1144
-GRANT_WRONG_HOST_OR_USER = 1145
-NO_SUCH_TABLE = 1146
-NONEXISTING_TABLE_GRANT = 1147
-NOT_ALLOWED_COMMAND = 1148
-SYNTAX_ERROR = 1149
-ABORTING_CONNECTION = 1152
-NET_PACKET_TOO_LARGE = 1153
-NET_READ_ERROR_FROM_PIPE = 1154
-NET_FCNTL_ERROR = 1155
-NET_PACKETS_OUT_OF_ORDER = 1156
-NET_UNCOMPRESS_ERROR = 1157
-NET_READ_ERROR = 1158
-NET_READ_INTERRUPTED = 1159
-NET_ERROR_ON_WRITE = 1160
-NET_WRITE_INTERRUPTED = 1161
-TOO_LONG_STRING = 1162
-TABLE_CANT_HANDLE_BLOB = 1163
-TABLE_CANT_HANDLE_AUTO_INCREMENT = 1164
-WRONG_COLUMN_NAME = 1166
-WRONG_KEY_COLUMN = 1167
-WRONG_MRG_TABLE = 1168
-DUP_UNIQUE = 1169
-BLOB_KEY_WITHOUT_LENGTH = 1170
-PRIMARY_CANT_HAVE_NULL = 1171
-TOO_MANY_ROWS = 1172
-REQUIRES_PRIMARY_KEY = 1173
-UPDATE_WITHOUT_KEY_IN_SAFE_MODE = 1175
-KEY_DOES_NOT_EXITS = 1176
-CHECK_NO_SUCH_TABLE = 1177
-CHECK_NOT_IMPLEMENTED = 1178
-CANT_DO_THIS_DURING_AN_TRANSACTION = 1179
-ERROR_DURING_COMMIT = 1180
-ERROR_DURING_ROLLBACK = 1181
-ERROR_DURING_FLUSH_LOGS = 1182
-NEW_ABORTING_CONNECTION = 1184
-MASTER = 1188
-MASTER_NET_READ = 1189
-MASTER_NET_WRITE = 1190
-FT_MATCHING_KEY_NOT_FOUND = 1191
-LOCK_OR_ACTIVE_TRANSACTION = 1192
-UNKNOWN_SYSTEM_VARIABLE = 1193
-CRASHED_ON_USAGE = 1194
-CRASHED_ON_REPAIR = 1195
-WARNING_NOT_COMPLETE_ROLLBACK = 1196
-TRANS_CACHE_FULL = 1197
-SLAVE_NOT_RUNNING = 1199
-BAD_SLAVE = 1200
-MASTER_INFO = 1201
-SLAVE_THREAD = 1202
-TOO_MANY_USER_CONNECTIONS = 1203
-SET_CONSTANTS_ONLY = 1204
-LOCK_WAIT_TIMEOUT = 1205
-LOCK_TABLE_FULL = 1206
-READ_ONLY_TRANSACTION = 1207
-WRONG_ARGUMENTS = 1210
-NO_PERMISSION_TO_CREATE_USER = 1211
-LOCK_DEADLOCK = 1213
-TABLE_CANT_HANDLE_FT = 1214
-CANNOT_ADD_FOREIGN = 1215
-NO_REFERENCED_ROW = 1216
-ROW_IS_REFERENCED = 1217
-CONNECT_TO_MASTER = 1218
-ERROR_WHEN_EXECUTING_COMMAND = 1220
-WRONG_USAGE = 1221
-WRONG_NUMBER_OF_COLUMNS_IN_SELECT = 1222
-CANT_UPDATE_WITH_READLOCK = 1223
-MIXING_NOT_ALLOWED = 1224
-DUP_ARGUMENT = 1225
-USER_LIMIT_REACHED = 1226
-SPECIFIC_ACCESS_DENIED_ERROR = 1227
-LOCAL_VARIABLE = 1228
-GLOBAL_VARIABLE = 1229
-NO_DEFAULT = 1230
-WRONG_VALUE_FOR_VAR = 1231
-WRONG_TYPE_FOR_VAR = 1232
-VAR_CANT_BE_READ = 1233
-CANT_USE_OPTION_HERE = 1234
-NOT_SUPPORTED_YET = 1235
-MASTER_FATAL_ERROR_READING_BINLOG = 1236
-SLAVE_IGNORED_TABLE = 1237
-INCORRECT_GLOBAL_LOCAL_VAR = 1238
-WRONG_FK_DEF = 1239
-KEY_REF_DO_NOT_MATCH_TABLE_REF = 1240
-OPERAND_COLUMNS = 1241
-SUBQUERY_NO_1_ROW = 1242
-UNKNOWN_STMT_HANDLER = 1243
-CORRUPT_HELP_DB = 1244
-AUTO_CONVERT = 1246
-ILLEGAL_REFERENCE = 1247
-DERIVED_MUST_HAVE_ALIAS = 1248
-SELECT_REDUCED = 1249
-TABLENAME_NOT_ALLOWED_HERE = 1250
-NOT_SUPPORTED_AUTH_MODE = 1251
-SPATIAL_CANT_HAVE_NULL = 1252
-COLLATION_CHARSET_MISMATCH = 1253
-TOO_BIG_FOR_UNCOMPRESS = 1256
-ZLIB_Z_MEM_ERROR = 1257
-ZLIB_Z_BUF_ERROR = 1258
-ZLIB_Z_DATA_ERROR = 1259
-CUT_VALUE_GROUP_CONCAT = 1260
-WARN_TOO_FEW_RECORDS = 1261
-WARN_TOO_MANY_RECORDS = 1262
-WARN_NULL_TO_NOTNULL = 1263
-WARN_DATA_OUT_OF_RANGE = 1264
-WARN_DATA_TRUNCATED = 1265
-WARN_USING_OTHER_HANDLER = 1266
-CANT_AGGREGATE_2COLLATIONS = 1267
-REVOKE_GRANTS = 1269
-CANT_AGGREGATE_3COLLATIONS = 1270
-CANT_AGGREGATE_NCOLLATIONS = 1271
-VARIABLE_IS_NOT_STRUCT = 1272
-UNKNOWN_COLLATION = 1273
-SLAVE_IGNORED_SSL_PARAMS = 1274
-SERVER_IS_IN_SECURE_AUTH_MODE = 1275
-WARN_FIELD_RESOLVED = 1276
-BAD_SLAVE_UNTIL_COND = 1277
-MISSING_SKIP_SLAVE = 1278
-UNTIL_COND_IGNORED = 1279
-WRONG_NAME_FOR_INDEX = 1280
-WRONG_NAME_FOR_CATALOG = 1281
-BAD_FT_COLUMN = 1283
-UNKNOWN_KEY_CACHE = 1284
-WARN_HOSTNAME_WONT_WORK = 1285
-UNKNOWN_STORAGE_ENGINE = 1286
-WARN_DEPRECATED_SYNTAX = 1287
-NON_UPDATABLE_TABLE = 1288
-FEATURE_DISABLED = 1289
-OPTION_PREVENTS_STATEMENT = 1290
-DUPLICATED_VALUE_IN_TYPE = 1291
-TRUNCATED_WRONG_VALUE = 1292
-INVALID_ON_UPDATE = 1294
-UNSUPPORTED_PS = 1295
-GET_ERRMSG = 1296
-GET_TEMPORARY_ERRMSG = 1297
-UNKNOWN_TIME_ZONE = 1298
-WARN_INVALID_TIMESTAMP = 1299
-INVALID_CHARACTER_STRING = 1300
-WARN_ALLOWED_PACKET_OVERFLOWED = 1301
-CONFLICTING_DECLARATIONS = 1302
-SP_NO_RECURSIVE_CREATE = 1303
-SP_ALREADY_EXISTS = 1304
-SP_DOES_NOT_EXIST = 1305
-SP_DROP_FAILED = 1306
-SP_STORE_FAILED = 1307
-SP_LILABEL_MISMATCH = 1308
-SP_LABEL_REDEFINE = 1309
-SP_LABEL_MISMATCH = 1310
-SP_UNINIT_VAR = 1311
-SP_BADSELECT = 1312
-SP_BADRETURN = 1313
-SP_BADSTATEMENT = 1314
-UPDATE_LOG_DEPRECATED_IGNORED = 1315
-UPDATE_LOG_DEPRECATED_TRANSLATED = 1316
-QUERY_INTERRUPTED = 1317
-SP_WRONG_NO_OF_ARGS = 1318
-SP_COND_MISMATCH = 1319
-SP_NORETURN = 1320
-SP_NORETURNEND = 1321
-SP_BAD_CURSOR_QUERY = 1322
-SP_BAD_CURSOR_SELECT = 1323
-SP_CURSOR_MISMATCH = 1324
-SP_CURSOR_ALREADY_OPEN = 1325
-SP_CURSOR_NOT_OPEN = 1326
-SP_UNDECLARED_VAR = 1327
-SP_WRONG_NO_OF_FETCH_ARGS = 1328
-SP_FETCH_NO_DATA = 1329
-SP_DUP_PARAM = 1330
-SP_DUP_VAR = 1331
-SP_DUP_COND = 1332
-SP_DUP_CURS = 1333
-SP_CANT_ALTER = 1334
-SP_SUBSELECT_NYI = 1335
-STMT_NOT_ALLOWED_IN_SF_OR_TRG = 1336
-SP_VARCOND_AFTER_CURSHNDLR = 1337
-SP_CURSOR_AFTER_HANDLER = 1338
-SP_CASE_NOT_FOUND = 1339
-FPARSER_TOO_BIG_FILE = 1340
-FPARSER_BAD_HEADER = 1341
-FPARSER_EOF_IN_COMMENT = 1342
-FPARSER_ERROR_IN_PARAMETER = 1343
-FPARSER_EOF_IN_UNKNOWN_PARAMETER = 1344
-VIEW_NO_EXPLAIN = 1345
-WRONG_OBJECT = 1347
-NONUPDATEABLE_COLUMN = 1348
-VIEW_SELECT_CLAUSE = 1350
-VIEW_SELECT_VARIABLE = 1351
-VIEW_SELECT_TMPTABLE = 1352
-VIEW_WRONG_LIST = 1353
-WARN_VIEW_MERGE = 1354
-WARN_VIEW_WITHOUT_KEY = 1355
-VIEW_INVALID = 1356
-SP_NO_DROP_SP = 1357
-TRG_ALREADY_EXISTS = 1359
-TRG_DOES_NOT_EXIST = 1360
-TRG_ON_VIEW_OR_TEMP_TABLE = 1361
-TRG_CANT_CHANGE_ROW = 1362
-TRG_NO_SUCH_ROW_IN_TRG = 1363
-NO_DEFAULT_FOR_FIELD = 1364
-DIVISION_BY_ZERO = 1365
-TRUNCATED_WRONG_VALUE_FOR_FIELD = 1366
-ILLEGAL_VALUE_FOR_TYPE = 1367
-VIEW_NONUPD_CHECK = 1368
-VIEW_CHECK_FAILED = 1369
-PROCACCESS_DENIED_ERROR = 1370
-RELAY_LOG_FAIL = 1371
-UNKNOWN_TARGET_BINLOG = 1373
-IO_ERR_LOG_INDEX_READ = 1374
-BINLOG_PURGE_PROHIBITED = 1375
-FSEEK_FAIL = 1376
-BINLOG_PURGE_FATAL_ERR = 1377
-LOG_IN_USE = 1378
-LOG_PURGE_UNKNOWN_ERR = 1379
-RELAY_LOG_INIT = 1380
-NO_BINARY_LOGGING = 1381
-RESERVED_SYNTAX = 1382
-PS_MANY_PARAM = 1390
-KEY_PART_0 = 1391
-VIEW_CHECKSUM = 1392
-VIEW_MULTIUPDATE = 1393
-VIEW_NO_INSERT_FIELD_LIST = 1394
-VIEW_DELETE_MERGE_VIEW = 1395
-CANNOT_USER = 1396
-XAER_NOTA = 1397
-XAER_INVAL = 1398
-XAER_RMFAIL = 1399
-XAER_OUTSIDE = 1400
-XAER_RMERR = 1401
-XA_RBROLLBACK = 1402
-NONEXISTING_PROC_GRANT = 1403
-PROC_AUTO_GRANT_FAIL = 1404
-PROC_AUTO_REVOKE_FAIL = 1405
-DATA_TOO_LONG = 1406
-SP_BAD_SQLSTATE = 1407
-STARTUP = 1408
-LOAD_FROM_FIXED_SIZE_ROWS_TO_VAR = 1409
-CANT_CREATE_USER_WITH_GRANT = 1410
-WRONG_VALUE_FOR_TYPE = 1411
-TABLE_DEF_CHANGED = 1412
-SP_DUP_HANDLER = 1413
-SP_NOT_VAR_ARG = 1414
-SP_NO_RETSET = 1415
-CANT_CREATE_GEOMETRY_OBJECT = 1416
-BINLOG_UNSAFE_ROUTINE = 1418
-BINLOG_CREATE_ROUTINE_NEED_SUPER = 1419
-STMT_HAS_NO_OPEN_CURSOR = 1421
-COMMIT_NOT_ALLOWED_IN_SF_OR_TRG = 1422
-NO_DEFAULT_FOR_VIEW_FIELD = 1423
-SP_NO_RECURSION = 1424
-TOO_BIG_SCALE = 1425
-TOO_BIG_PRECISION = 1426
-M_BIGGER_THAN_D = 1427
-WRONG_LOCK_OF_SYSTEM_TABLE = 1428
-CONNECT_TO_FOREIGN_DATA_SOURCE = 1429
-QUERY_ON_FOREIGN_DATA_SOURCE = 1430
-FOREIGN_DATA_SOURCE_DOESNT_EXIST = 1431
-FOREIGN_DATA_STRING_INVALID_CANT_CREATE = 1432
-FOREIGN_DATA_STRING_INVALID = 1433
-TRG_IN_WRONG_SCHEMA = 1435
-STACK_OVERRUN_NEED_MORE = 1436
-TOO_LONG_BODY = 1437
-WARN_CANT_DROP_DEFAULT_KEYCACHE = 1438
-TOO_BIG_DISPLAYWIDTH = 1439
-XAER_DUPID = 1440
-DATETIME_FUNCTION_OVERFLOW = 1441
-CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG = 1442
-VIEW_PREVENT_UPDATE = 1443
-PS_NO_RECURSION = 1444
-SP_CANT_SET_AUTOCOMMIT = 1445
-VIEW_FRM_NO_USER = 1447
-VIEW_OTHER_USER = 1448
-NO_SUCH_USER = 1449
-FORBID_SCHEMA_CHANGE = 1450
-ROW_IS_REFERENCED_2 = 1451
-NO_REFERENCED_ROW_2 = 1452
-SP_BAD_VAR_SHADOW = 1453
-TRG_NO_DEFINER = 1454
-OLD_FILE_FORMAT = 1455
-SP_RECURSION_LIMIT = 1456
-SP_WRONG_NAME = 1458
-TABLE_NEEDS_UPGRADE = 1459
-SP_NO_AGGREGATE = 1460
-MAX_PREPARED_STMT_COUNT_REACHED = 1461
-VIEW_RECURSIVE = 1462
-NON_GROUPING_FIELD_USED = 1463
-TABLE_CANT_HANDLE_SPKEYS = 1464
-NO_TRIGGERS_ON_SYSTEM_SCHEMA = 1465
-REMOVED_SPACES = 1466
-AUTOINC_READ_FAILED = 1467
-USERNAME = 1468
-HOSTNAME = 1469
-WRONG_STRING_LENGTH = 1470
-NON_INSERTABLE_TABLE = 1471
-ADMIN_WRONG_MRG_TABLE = 1472
-TOO_HIGH_LEVEL_OF_NESTING_FOR_SELECT = 1473
-NAME_BECOMES_EMPTY = 1474
-AMBIGUOUS_FIELD_TERM = 1475
-FOREIGN_SERVER_EXISTS = 1476
-FOREIGN_SERVER_DOESNT_EXIST = 1477
-ILLEGAL_HA_CREATE_OPTION = 1478
-PARTITION_REQUIRES_VALUES_ERROR = 1479
-PARTITION_WRONG_VALUES_ERROR = 1480
-PARTITION_MAXVALUE_ERROR = 1481
-PARTITION_WRONG_NO_PART_ERROR = 1484
-PARTITION_WRONG_NO_SUBPART_ERROR = 1485
-WRONG_EXPR_IN_PARTITION_FUNC_ERROR = 1486
-FIELD_NOT_FOUND_PART_ERROR = 1488
-INCONSISTENT_PARTITION_INFO_ERROR = 1490
-PARTITION_FUNC_NOT_ALLOWED_ERROR = 1491
-PARTITIONS_MUST_BE_DEFINED_ERROR = 1492
-RANGE_NOT_INCREASING_ERROR = 1493
-INCONSISTENT_TYPE_OF_FUNCTIONS_ERROR = 1494
-MULTIPLE_DEF_CONST_IN_LIST_PART_ERROR = 1495
-PARTITION_ENTRY_ERROR = 1496
-MIX_HANDLER_ERROR = 1497
-PARTITION_NOT_DEFINED_ERROR = 1498
-TOO_MANY_PARTITIONS_ERROR = 1499
-SUBPARTITION_ERROR = 1500
-CANT_CREATE_HANDLER_FILE = 1501
-BLOB_FIELD_IN_PART_FUNC_ERROR = 1502
-UNIQUE_KEY_NEED_ALL_FIELDS_IN_PF = 1503
-NO_PARTS_ERROR = 1504
-PARTITION_MGMT_ON_NONPARTITIONED = 1505
-FOREIGN_KEY_ON_PARTITIONED = 1506
-DROP_PARTITION_NON_EXISTENT = 1507
-DROP_LAST_PARTITION = 1508
-COALESCE_ONLY_ON_HASH_PARTITION = 1509
-REORG_HASH_ONLY_ON_SAME_NO = 1510
-REORG_NO_PARAM_ERROR = 1511
-ONLY_ON_RANGE_LIST_PARTITION = 1512
-ADD_PARTITION_SUBPART_ERROR = 1513
-ADD_PARTITION_NO_NEW_PARTITION = 1514
-COALESCE_PARTITION_NO_PARTITION = 1515
-REORG_PARTITION_NOT_EXIST = 1516
-SAME_NAME_PARTITION = 1517
-NO_BINLOG_ERROR = 1518
-CONSECUTIVE_REORG_PARTITIONS = 1519
-REORG_OUTSIDE_RANGE = 1520
-PARTITION_FUNCTION_FAILURE = 1521
-LIMITED_PART_RANGE = 1523
-PLUGIN_IS_NOT_LOADED = 1524
-WRONG_VALUE = 1525
-NO_PARTITION_FOR_GIVEN_VALUE = 1526
-FILEGROUP_OPTION_ONLY_ONCE = 1527
-CREATE_FILEGROUP_FAILED = 1528
-DROP_FILEGROUP_FAILED = 1529
-TABLESPACE_AUTO_EXTEND_ERROR = 1530
-WRONG_SIZE_NUMBER = 1531
-SIZE_OVERFLOW_ERROR = 1532
-ALTER_FILEGROUP_FAILED = 1533
-BINLOG_ROW_LOGGING_FAILED = 1534
-EVENT_ALREADY_EXISTS = 1537
-EVENT_DOES_NOT_EXIST = 1539
-EVENT_INTERVAL_NOT_POSITIVE_OR_TOO_BIG = 1542
-EVENT_ENDS_BEFORE_STARTS = 1543
-EVENT_EXEC_TIME_IN_THE_PAST = 1544
-EVENT_SAME_NAME = 1551
-DROP_INDEX_FK = 1553
-WARN_DEPRECATED_SYNTAX_WITH_VER = 1554
-CANT_LOCK_LOG_TABLE = 1556
-FOREIGN_DUPLICATE_KEY_OLD_UNUSED = 1557
-COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE = 1558
-TEMP_TABLE_PREVENTS_SWITCH_OUT_OF_RBR = 1559
-STORED_FUNCTION_PREVENTS_SWITCH_BINLOG_FORMAT = 1560
-PARTITION_NO_TEMPORARY = 1562
-PARTITION_CONST_DOMAIN_ERROR = 1563
-PARTITION_FUNCTION_IS_NOT_ALLOWED = 1564
-NULL_IN_VALUES_LESS_THAN = 1566
-WRONG_PARTITION_NAME = 1567
-CANT_CHANGE_TX_CHARACTERISTICS = 1568
-DUP_ENTRY_AUTOINCREMENT_CASE = 1569
-EVENT_SET_VAR_ERROR = 1571
-PARTITION_MERGE_ERROR = 1572
-BASE64_DECODE_ERROR = 1575
-EVENT_RECURSION_FORBIDDEN = 1576
-ONLY_INTEGERS_ALLOWED = 1578
-UNSUPORTED_LOG_ENGINE = 1579
-BAD_LOG_STATEMENT = 1580
-CANT_RENAME_LOG_TABLE = 1581
-WRONG_PARAMCOUNT_TO_NATIVE_FCT = 1582
-WRONG_PARAMETERS_TO_NATIVE_FCT = 1583
-WRONG_PARAMETERS_TO_STORED_FCT = 1584
-NATIVE_FCT_NAME_COLLISION = 1585
-DUP_ENTRY_WITH_KEY_NAME = 1586
-BINLOG_PURGE_EMFILE = 1587
-EVENT_CANNOT_CREATE_IN_THE_PAST = 1588
-EVENT_CANNOT_ALTER_IN_THE_PAST = 1589
-NO_PARTITION_FOR_GIVEN_VALUE_SILENT = 1591
-BINLOG_UNSAFE_STATEMENT = 1592
-BINLOG_FATAL_ERROR = 1593
-BINLOG_LOGGING_IMPOSSIBLE = 1598
-VIEW_NO_CREATION_CTX = 1599
-VIEW_INVALID_CREATION_CTX = 1600
-TRG_CORRUPTED_FILE = 1602
-TRG_NO_CREATION_CTX = 1603
-TRG_INVALID_CREATION_CTX = 1604
-EVENT_INVALID_CREATION_CTX = 1605
-TRG_CANT_OPEN_TABLE = 1606
-NO_FORMAT_DESCRIPTION_EVENT_BEFORE_BINLOG_STATEMENT = 1609
-SLAVE_CORRUPT_EVENT = 1610
-LOG_PURGE_NO_FILE = 1612
-XA_RBTIMEOUT = 1613
-XA_RBDEADLOCK = 1614
-NEED_REPREPARE = 1615
-WARN_NO_MASTER_INFO = 1617
-WARN_OPTION_IGNORED = 1618
-PLUGIN_DELETE_BUILTIN = 1619
-WARN_PLUGIN_BUSY = 1620
-VARIABLE_IS_READONLY = 1621
-WARN_ENGINE_TRANSACTION_ROLLBACK = 1622
-SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE = 1624
-NDB_REPLICATION_SCHEMA_ERROR = 1625
-CONFLICT_FN_PARSE_ERROR = 1626
-EXCEPTIONS_WRITE_ERROR = 1627
-TOO_LONG_TABLE_COMMENT = 1628
-TOO_LONG_FIELD_COMMENT = 1629
-FUNC_INEXISTENT_NAME_COLLISION = 1630
-DATABASE_NAME = 1631
-TABLE_NAME = 1632
-PARTITION_NAME = 1633
-SUBPARTITION_NAME = 1634
-TEMPORARY_NAME = 1635
-RENAMED_NAME = 1636
-TOO_MANY_CONCURRENT_TRXS = 1637
-WARN_NON_ASCII_SEPARATOR_NOT_IMPLEMENTED = 1638
-DEBUG_SYNC_TIMEOUT = 1639
-DEBUG_SYNC_HIT_LIMIT = 1640
-DUP_SIGNAL_SET = 1641
-SIGNAL_WARN = 1642
-SIGNAL_NOT_FOUND = 1643
-SIGNAL_EXCEPTION = 1644
-RESIGNAL_WITHOUT_ACTIVE_HANDLER = 1645
-SIGNAL_BAD_CONDITION_TYPE = 1646
-WARN_COND_ITEM_TRUNCATED = 1647
-COND_ITEM_TOO_LONG = 1648
-UNKNOWN_LOCALE = 1649
-SLAVE_IGNORE_SERVER_IDS = 1650
-SAME_NAME_PARTITION_FIELD = 1652
-PARTITION_COLUMN_LIST_ERROR = 1653
-WRONG_TYPE_COLUMN_VALUE_ERROR = 1654
-TOO_MANY_PARTITION_FUNC_FIELDS_ERROR = 1655
-MAXVALUE_IN_VALUES_IN = 1656
-TOO_MANY_VALUES_ERROR = 1657
-ROW_SINGLE_PARTITION_FIELD_ERROR = 1658
-FIELD_TYPE_NOT_ALLOWED_AS_PARTITION_FIELD = 1659
-PARTITION_FIELDS_TOO_LONG = 1660
-BINLOG_ROW_ENGINE_AND_STMT_ENGINE = 1661
-BINLOG_ROW_MODE_AND_STMT_ENGINE = 1662
-BINLOG_UNSAFE_AND_STMT_ENGINE = 1663
-BINLOG_ROW_INJECTION_AND_STMT_ENGINE = 1664
-BINLOG_STMT_MODE_AND_ROW_ENGINE = 1665
-BINLOG_ROW_INJECTION_AND_STMT_MODE = 1666
-BINLOG_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE = 1667
-BINLOG_UNSAFE_LIMIT = 1668
-BINLOG_UNSAFE_SYSTEM_TABLE = 1670
-BINLOG_UNSAFE_AUTOINC_COLUMNS = 1671
-BINLOG_UNSAFE_UDF = 1672
-BINLOG_UNSAFE_SYSTEM_VARIABLE = 1673
-BINLOG_UNSAFE_SYSTEM_FUNCTION = 1674
-BINLOG_UNSAFE_NONTRANS_AFTER_TRANS = 1675
-MESSAGE_AND_STATEMENT = 1676
-SLAVE_CANT_CREATE_CONVERSION = 1678
-INSIDE_TRANSACTION_PREVENTS_SWITCH_BINLOG_FORMAT = 1679
-PATH_LENGTH = 1680
-WARN_DEPRECATED_SYNTAX_NO_REPLACEMENT = 1681
-WRONG_NATIVE_TABLE_STRUCTURE = 1682
-WRONG_PERFSCHEMA_USAGE = 1683
-WARN_I_S_SKIPPED_TABLE = 1684
-INSIDE_TRANSACTION_PREVENTS_SWITCH_BINLOG_DIRECT = 1685
-STORED_FUNCTION_PREVENTS_SWITCH_BINLOG_DIRECT = 1686
-SPATIAL_MUST_HAVE_GEOM_COL = 1687
-TOO_LONG_INDEX_COMMENT = 1688
-LOCK_ABORTED = 1689
-DATA_OUT_OF_RANGE = 1690
-WRONG_SPVAR_TYPE_IN_LIMIT = 1691
-BINLOG_UNSAFE_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE = 1692
-BINLOG_UNSAFE_MIXED_STATEMENT = 1693
-INSIDE_TRANSACTION_PREVENTS_SWITCH_SQL_LOG_BIN = 1694
-STORED_FUNCTION_PREVENTS_SWITCH_SQL_LOG_BIN = 1695
-FAILED_READ_FROM_PAR_FILE = 1696
-VALUES_IS_NOT_INT_TYPE_ERROR = 1697
-ACCESS_DENIED_NO_PASSWORD_ERROR = 1698
-SET_PASSWORD_AUTH_PLUGIN = 1699
-TRUNCATE_ILLEGAL_FK = 1701
-PLUGIN_IS_PERMANENT = 1702
-SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MIN = 1703
-SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MAX = 1704
-STMT_CACHE_FULL = 1705
-MULTI_UPDATE_KEY_CONFLICT = 1706
-TABLE_NEEDS_REBUILD = 1707
-WARN_OPTION_BELOW_LIMIT = 1708
-INDEX_COLUMN_TOO_LONG = 1709
-ERROR_IN_TRIGGER_BODY = 1710
-ERROR_IN_UNKNOWN_TRIGGER_BODY = 1711
-INDEX_CORRUPT = 1712
-UNDO_RECORD_TOO_BIG = 1713
-BINLOG_UNSAFE_INSERT_IGNORE_SELECT = 1714
-BINLOG_UNSAFE_INSERT_SELECT_UPDATE = 1715
-BINLOG_UNSAFE_REPLACE_SELECT = 1716
-BINLOG_UNSAFE_CREATE_IGNORE_SELECT = 1717
-BINLOG_UNSAFE_CREATE_REPLACE_SELECT = 1718
-BINLOG_UNSAFE_UPDATE_IGNORE = 1719
-PLUGIN_NO_UNINSTALL = 1720
-PLUGIN_NO_INSTALL = 1721
-BINLOG_UNSAFE_WRITE_AUTOINC_SELECT = 1722
-BINLOG_UNSAFE_CREATE_SELECT_AUTOINC = 1723
-BINLOG_UNSAFE_INSERT_TWO_KEYS = 1724
-TABLE_IN_FK_CHECK = 1725
-UNSUPPORTED_ENGINE = 1726
-BINLOG_UNSAFE_AUTOINC_NOT_FIRST = 1727
-CANNOT_LOAD_FROM_TABLE_V2 = 1728
-MASTER_DELAY_VALUE_OUT_OF_RANGE = 1729
-ONLY_FD_AND_RBR_EVENTS_ALLOWED_IN_BINLOG_STATEMENT = 1730
-PARTITION_EXCHANGE_DIFFERENT_OPTION = 1731
-PARTITION_EXCHANGE_PART_TABLE = 1732
-PARTITION_EXCHANGE_TEMP_TABLE = 1733
-PARTITION_INSTEAD_OF_SUBPARTITION = 1734
-UNKNOWN_PARTITION = 1735
-TABLES_DIFFERENT_METADATA = 1736
-ROW_DOES_NOT_MATCH_PARTITION = 1737
-BINLOG_CACHE_SIZE_GREATER_THAN_MAX = 1738
-WARN_INDEX_NOT_APPLICABLE = 1739
-PARTITION_EXCHANGE_FOREIGN_KEY = 1740
-RPL_INFO_DATA_TOO_LONG = 1742
-BINLOG_STMT_CACHE_SIZE_GREATER_THAN_MAX = 1745
-CANT_UPDATE_TABLE_IN_CREATE_TABLE_SELECT = 1746
-PARTITION_CLAUSE_ON_NONPARTITIONED = 1747
-ROW_DOES_NOT_MATCH_GIVEN_PARTITION_SET = 1748
-CHANGE_RPL_INFO_REPOSITORY_FAILURE = 1750
-WARNING_NOT_COMPLETE_ROLLBACK_WITH_CREATED_TEMP_TABLE = 1751
-WARNING_NOT_COMPLETE_ROLLBACK_WITH_DROPPED_TEMP_TABLE = 1752
-MTS_FEATURE_IS_NOT_SUPPORTED = 1753
-MTS_UPDATED_DBS_GREATER_MAX = 1754
-MTS_CANT_PARALLEL = 1755
-MTS_INCONSISTENT_DATA = 1756
-FULLTEXT_NOT_SUPPORTED_WITH_PARTITIONING = 1757
-DA_INVALID_CONDITION_NUMBER = 1758
-INSECURE_PLAIN_TEXT = 1759
-INSECURE_CHANGE_MASTER = 1760
-FOREIGN_DUPLICATE_KEY_WITH_CHILD_INFO = 1761
-FOREIGN_DUPLICATE_KEY_WITHOUT_CHILD_INFO = 1762
-SQLTHREAD_WITH_SECURE_SLAVE = 1763
-TABLE_HAS_NO_FT = 1764
-VARIABLE_NOT_SETTABLE_IN_SF_OR_TRIGGER = 1765
-VARIABLE_NOT_SETTABLE_IN_TRANSACTION = 1766
-SET_STATEMENT_CANNOT_INVOKE_FUNCTION = 1769
-GTID_NEXT_CANT_BE_AUTOMATIC_IF_GTID_NEXT_LIST_IS_NON_NULL = 1770
-MALFORMED_GTID_SET_SPECIFICATION = 1772
-MALFORMED_GTID_SET_ENCODING = 1773
-MALFORMED_GTID_SPECIFICATION = 1774
-GNO_EXHAUSTED = 1775
-BAD_SLAVE_AUTO_POSITION = 1776
-AUTO_POSITION_REQUIRES_GTID_MODE_NOT_OFF = 1777
-CANT_DO_IMPLICIT_COMMIT_IN_TRX_WHEN_GTID_NEXT_IS_SET = 1778
-GTID_MODE_ON_REQUIRES_ENFORCE_GTID_CONSISTENCY_ON = 1779
-CANT_SET_GTID_NEXT_TO_GTID_WHEN_GTID_MODE_IS_OFF = 1781
-CANT_SET_GTID_NEXT_TO_ANONYMOUS_WHEN_GTID_MODE_IS_ON = 1782
-CANT_SET_GTID_NEXT_LIST_TO_NON_NULL_WHEN_GTID_MODE_IS_OFF = 1783
-GTID_UNSAFE_NON_TRANSACTIONAL_TABLE = 1785
-GTID_UNSAFE_CREATE_SELECT = 1786
-GTID_UNSAFE_CREATE_DROP_TEMPORARY_TABLE_IN_TRANSACTION = 1787
-GTID_MODE_CAN_ONLY_CHANGE_ONE_STEP_AT_A_TIME = 1788
-MASTER_HAS_PURGED_REQUIRED_GTIDS = 1789
-CANT_SET_GTID_NEXT_WHEN_OWNING_GTID = 1790
-UNKNOWN_EXPLAIN_FORMAT = 1791
-CANT_EXECUTE_IN_READ_ONLY_TRANSACTION = 1792
-TOO_LONG_TABLE_PARTITION_COMMENT = 1793
-SLAVE_CONFIGURATION = 1794
-INNODB_FT_LIMIT = 1795
-INNODB_NO_FT_TEMP_TABLE = 1796
-INNODB_FT_WRONG_DOCID_COLUMN = 1797
-INNODB_FT_WRONG_DOCID_INDEX = 1798
-INNODB_ONLINE_LOG_TOO_BIG = 1799
-UNKNOWN_ALTER_ALGORITHM = 1800
-UNKNOWN_ALTER_LOCK = 1801
-MTS_CHANGE_MASTER_CANT_RUN_WITH_GAPS = 1802
-MTS_RECOVERY_FAILURE = 1803
-MTS_RESET_WORKERS = 1804
-COL_COUNT_DOESNT_MATCH_CORRUPTED_V2 = 1805
-SLAVE_SILENT_RETRY_TRANSACTION = 1806
-DISCARD_FK_CHECKS_RUNNING = 1807
-TABLE_SCHEMA_MISMATCH = 1808
-TABLE_IN_SYSTEM_TABLESPACE = 1809
-IO_READ_ERROR = 1810
-IO_WRITE_ERROR = 1811
-TABLESPACE_MISSING = 1812
-TABLESPACE_EXISTS = 1813
-TABLESPACE_DISCARDED = 1814
-INTERNAL_ERROR = 1815
-INNODB_IMPORT_ERROR = 1816
-INNODB_INDEX_CORRUPT = 1817
-INVALID_YEAR_COLUMN_LENGTH = 1818
-NOT_VALID_PASSWORD = 1819
-MUST_CHANGE_PASSWORD = 1820
-FK_NO_INDEX_CHILD = 1821
-FK_NO_INDEX_PARENT = 1822
-FK_FAIL_ADD_SYSTEM = 1823
-FK_CANNOT_OPEN_PARENT = 1824
-FK_INCORRECT_OPTION = 1825
-FK_DUP_NAME = 1826
-PASSWORD_FORMAT = 1827
-FK_COLUMN_CANNOT_DROP = 1828
-FK_COLUMN_CANNOT_DROP_CHILD = 1829
-FK_COLUMN_NOT_NULL = 1830
-DUP_INDEX = 1831
-FK_COLUMN_CANNOT_CHANGE = 1832
-FK_COLUMN_CANNOT_CHANGE_CHILD = 1833
-MALFORMED_PACKET = 1835
-READ_ONLY_MODE = 1836
-GTID_NEXT_TYPE_UNDEFINED_GTID = 1837
-VARIABLE_NOT_SETTABLE_IN_SP = 1838
-CANT_SET_GTID_PURGED_WHEN_GTID_EXECUTED_IS_NOT_EMPTY = 1840
-CANT_SET_GTID_PURGED_WHEN_OWNED_GTIDS_IS_NOT_EMPTY = 1841
-GTID_PURGED_WAS_CHANGED = 1842
-GTID_EXECUTED_WAS_CHANGED = 1843
-BINLOG_STMT_MODE_AND_NO_REPL_TABLES = 1844
-ALTER_OPERATION_NOT_SUPPORTED = 1845
-ALTER_OPERATION_NOT_SUPPORTED_REASON = 1846
-ALTER_OPERATION_NOT_SUPPORTED_REASON_COPY = 1847
-ALTER_OPERATION_NOT_SUPPORTED_REASON_PARTITION = 1848
-ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_RENAME = 1849
-ALTER_OPERATION_NOT_SUPPORTED_REASON_COLUMN_TYPE = 1850
-ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_CHECK = 1851
-ALTER_OPERATION_NOT_SUPPORTED_REASON_NOPK = 1853
-ALTER_OPERATION_NOT_SUPPORTED_REASON_AUTOINC = 1854
-ALTER_OPERATION_NOT_SUPPORTED_REASON_HIDDEN_FTS = 1855
-ALTER_OPERATION_NOT_SUPPORTED_REASON_CHANGE_FTS = 1856
-ALTER_OPERATION_NOT_SUPPORTED_REASON_FTS = 1857
-SQL_SLAVE_SKIP_COUNTER_NOT_SETTABLE_IN_GTID_MODE = 1858
-DUP_UNKNOWN_IN_INDEX = 1859
-IDENT_CAUSES_TOO_LONG_PATH = 1860
-ALTER_OPERATION_NOT_SUPPORTED_REASON_NOT_NULL = 1861
-MUST_CHANGE_PASSWORD_LOGIN = 1862
-ROW_IN_WRONG_PARTITION = 1863
-MTS_EVENT_BIGGER_PENDING_JOBS_SIZE_MAX = 1864
-BINLOG_LOGICAL_CORRUPTION = 1866
-WARN_PURGE_LOG_IN_USE = 1867
-WARN_PURGE_LOG_IS_ACTIVE = 1868
-AUTO_INCREMENT_CONFLICT = 1869
-WARN_ON_BLOCKHOLE_IN_RBR = 1870
-SLAVE_MI_INIT_REPOSITORY = 1871
-SLAVE_RLI_INIT_REPOSITORY = 1872
-ACCESS_DENIED_CHANGE_USER_ERROR = 1873
-INNODB_READ_ONLY = 1874
-STOP_SLAVE_SQL_THREAD_TIMEOUT = 1875
-STOP_SLAVE_IO_THREAD_TIMEOUT = 1876
-TABLE_CORRUPT = 1877
-TEMP_FILE_WRITE_FAILURE = 1878
-INNODB_FT_AUX_NOT_HEX_ID = 1879
-OLD_TEMPORALS_UPGRADED = 1880
-INNODB_FORCED_RECOVERY = 1881
-AES_INVALID_IV = 1882
-PLUGIN_CANNOT_BE_UNINSTALLED = 1883
-GTID_UNSAFE_BINLOG_SPLITTABLE_STATEMENT_AND_ASSIGNED_GTID = 1884
-SLAVE_HAS_MORE_GTIDS_THAN_MASTER = 1885
-MISSING_KEY = 1886
-ERROR_LAST = 1973
diff --git a/venv/Lib/site-packages/MySQLdb/constants/FIELD_TYPE.py b/venv/Lib/site-packages/MySQLdb/constants/FIELD_TYPE.py
deleted file mode 100644
index 3c4eca9..0000000
--- a/venv/Lib/site-packages/MySQLdb/constants/FIELD_TYPE.py
+++ /dev/null
@@ -1,40 +0,0 @@
-"""MySQL FIELD_TYPE Constants
-
-These constants represent the various column (field) types that are
-supported by MySQL.
-"""
-
-DECIMAL = 0
-TINY = 1
-SHORT = 2
-LONG = 3
-FLOAT = 4
-DOUBLE = 5
-NULL = 6
-TIMESTAMP = 7
-LONGLONG = 8
-INT24 = 9
-DATE = 10
-TIME = 11
-DATETIME = 12
-YEAR = 13
-# NEWDATE = 14 # Internal to MySQL.
-VARCHAR = 15
-BIT = 16
-# TIMESTAMP2 = 17
-# DATETIME2 = 18
-# TIME2 = 19
-JSON = 245
-NEWDECIMAL = 246
-ENUM = 247
-SET = 248
-TINY_BLOB = 249
-MEDIUM_BLOB = 250
-LONG_BLOB = 251
-BLOB = 252
-VAR_STRING = 253
-STRING = 254
-GEOMETRY = 255
-
-CHAR = TINY
-INTERVAL = ENUM
diff --git a/venv/Lib/site-packages/MySQLdb/constants/FLAG.py b/venv/Lib/site-packages/MySQLdb/constants/FLAG.py
deleted file mode 100644
index 00e6c7c..0000000
--- a/venv/Lib/site-packages/MySQLdb/constants/FLAG.py
+++ /dev/null
@@ -1,23 +0,0 @@
-"""MySQL FLAG Constants
-
-These flags are used along with the FIELD_TYPE to indicate various
-properties of columns in a result set.
-
-"""
-
-NOT_NULL = 1
-PRI_KEY = 2
-UNIQUE_KEY = 4
-MULTIPLE_KEY = 8
-BLOB = 16
-UNSIGNED = 32
-ZEROFILL = 64
-BINARY = 128
-ENUM = 256
-AUTO_INCREMENT = 512
-TIMESTAMP = 1024
-SET = 2048
-NUM = 32768
-PART_KEY = 16384
-GROUP = 32768
-UNIQUE = 65536
diff --git a/venv/Lib/site-packages/MySQLdb/constants/__init__.py b/venv/Lib/site-packages/MySQLdb/constants/__init__.py
deleted file mode 100644
index 0372265..0000000
--- a/venv/Lib/site-packages/MySQLdb/constants/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-__all__ = ["CR", "FIELD_TYPE", "CLIENT", "ER", "FLAG"]
diff --git a/venv/Lib/site-packages/MySQLdb/converters.py b/venv/Lib/site-packages/MySQLdb/converters.py
deleted file mode 100644
index 33f22f7..0000000
--- a/venv/Lib/site-packages/MySQLdb/converters.py
+++ /dev/null
@@ -1,139 +0,0 @@
-"""MySQLdb type conversion module
-
-This module handles all the type conversions for MySQL. If the default
-type conversions aren't what you need, you can make your own. The
-dictionary conversions maps some kind of type to a conversion function
-which returns the corresponding value:
-
-Key: FIELD_TYPE.* (from MySQLdb.constants)
-
-Conversion function:
-
- Arguments: string
-
- Returns: Python object
-
-Key: Python type object (from types) or class
-
-Conversion function:
-
- Arguments: Python object of indicated type or class AND
- conversion dictionary
-
- Returns: SQL literal value
-
- Notes: Most conversion functions can ignore the dictionary, but
- it is a required parameter. It is necessary for converting
- things like sequences and instances.
-
-Don't modify conversions if you can avoid it. Instead, make copies
-(with the copy() method), modify the copies, and then pass them to
-MySQL.connect().
-"""
-from decimal import Decimal
-
-from MySQLdb._mysql import string_literal
-from MySQLdb.constants import FIELD_TYPE, FLAG
-from MySQLdb.times import (
- Date,
- DateTimeType,
- DateTime2literal,
- DateTimeDeltaType,
- DateTimeDelta2literal,
- DateTime_or_None,
- TimeDelta_or_None,
- Date_or_None,
-)
-from MySQLdb._exceptions import ProgrammingError
-
-import array
-
-NoneType = type(None)
-
-try:
- ArrayType = array.ArrayType
-except AttributeError:
- ArrayType = array.array
-
-
-def Bool2Str(s, d):
- return b"1" if s else b"0"
-
-
-def Set2Str(s, d):
- # Only support ascii string. Not tested.
- return string_literal(",".join(s))
-
-
-def Thing2Str(s, d):
- """Convert something into a string via str()."""
- return str(s)
-
-
-def Float2Str(o, d):
- s = repr(o)
- if s in ("inf", "nan"):
- raise ProgrammingError("%s can not be used with MySQL" % s)
- if "e" not in s:
- s += "e0"
- return s
-
-
-def None2NULL(o, d):
- """Convert None to NULL."""
- return b"NULL"
-
-
-def Thing2Literal(o, d):
- """Convert something into a SQL string literal. If using
- MySQL-3.23 or newer, string_literal() is a method of the
- _mysql.MYSQL object, and this function will be overridden with
- that method when the connection is created."""
- return string_literal(o)
-
-
-def Decimal2Literal(o, d):
- return format(o, "f")
-
-
-def array2Str(o, d):
- return Thing2Literal(o.tostring(), d)
-
-
-# bytes or str regarding to BINARY_FLAG.
-_bytes_or_str = ((FLAG.BINARY, bytes), (None, str))
-
-conversions = {
- int: Thing2Str,
- float: Float2Str,
- NoneType: None2NULL,
- ArrayType: array2Str,
- bool: Bool2Str,
- Date: Thing2Literal,
- DateTimeType: DateTime2literal,
- DateTimeDeltaType: DateTimeDelta2literal,
- set: Set2Str,
- Decimal: Decimal2Literal,
- FIELD_TYPE.TINY: int,
- FIELD_TYPE.SHORT: int,
- FIELD_TYPE.LONG: int,
- FIELD_TYPE.FLOAT: float,
- FIELD_TYPE.DOUBLE: float,
- FIELD_TYPE.DECIMAL: Decimal,
- FIELD_TYPE.NEWDECIMAL: Decimal,
- FIELD_TYPE.LONGLONG: int,
- FIELD_TYPE.INT24: int,
- FIELD_TYPE.YEAR: int,
- FIELD_TYPE.TIMESTAMP: DateTime_or_None,
- FIELD_TYPE.DATETIME: DateTime_or_None,
- FIELD_TYPE.TIME: TimeDelta_or_None,
- FIELD_TYPE.DATE: Date_or_None,
- FIELD_TYPE.TINY_BLOB: bytes,
- FIELD_TYPE.MEDIUM_BLOB: bytes,
- FIELD_TYPE.LONG_BLOB: bytes,
- FIELD_TYPE.BLOB: bytes,
- FIELD_TYPE.STRING: bytes,
- FIELD_TYPE.VAR_STRING: bytes,
- FIELD_TYPE.VARCHAR: bytes,
- FIELD_TYPE.JSON: bytes,
-}
diff --git a/venv/Lib/site-packages/MySQLdb/cursors.py b/venv/Lib/site-packages/MySQLdb/cursors.py
deleted file mode 100644
index f8a4864..0000000
--- a/venv/Lib/site-packages/MySQLdb/cursors.py
+++ /dev/null
@@ -1,489 +0,0 @@
-"""MySQLdb Cursors
-
-This module implements Cursors of various types for MySQLdb. By
-default, MySQLdb uses the Cursor class.
-"""
-import re
-
-from ._exceptions import ProgrammingError
-
-
-#: Regular expression for :meth:`Cursor.executemany`.
-#: executemany only supports simple bulk insert.
-#: You can use it to load large dataset.
-RE_INSERT_VALUES = re.compile(
- "".join(
- [
- r"\s*((?:INSERT|REPLACE)\b.+\bVALUES?\s*)",
- r"(\(\s*(?:%s|%\(.+\)s)\s*(?:,\s*(?:%s|%\(.+\)s)\s*)*\))",
- r"(\s*(?:ON DUPLICATE.*)?);?\s*\Z",
- ]
- ),
- re.IGNORECASE | re.DOTALL,
-)
-
-
-class BaseCursor:
- """A base for Cursor classes. Useful attributes:
-
- description
- A tuple of DB API 7-tuples describing the columns in
- the last executed query; see PEP-249 for details.
-
- description_flags
- Tuple of column flags for last query, one entry per column
- in the result set. Values correspond to those in
- MySQLdb.constants.FLAG. See MySQL documentation (C API)
- for more information. Non-standard extension.
-
- arraysize
- default number of rows fetchmany() will fetch
- """
-
- #: Max statement size which :meth:`executemany` generates.
- #:
- #: Max size of allowed statement is max_allowed_packet - packet_header_size.
- #: Default value of max_allowed_packet is 1048576.
- max_stmt_length = 64 * 1024
-
- from ._exceptions import (
- MySQLError,
- Warning,
- Error,
- InterfaceError,
- DatabaseError,
- DataError,
- OperationalError,
- IntegrityError,
- InternalError,
- ProgrammingError,
- NotSupportedError,
- )
-
- connection = None
-
- def __init__(self, connection):
- self.connection = connection
- self.description = None
- self.description_flags = None
- self.rowcount = -1
- self.arraysize = 1
- self._executed = None
-
- self.lastrowid = None
- self._result = None
- self.rownumber = None
- self._rows = None
-
- def close(self):
- """Close the cursor. No further queries will be possible."""
- try:
- if self.connection is None:
- return
- while self.nextset():
- pass
- finally:
- self.connection = None
- self._result = None
-
- def __enter__(self):
- return self
-
- def __exit__(self, *exc_info):
- del exc_info
- self.close()
-
- def _escape_args(self, args, conn):
- encoding = conn.encoding
- literal = conn.literal
-
- def ensure_bytes(x):
- if isinstance(x, str):
- return x.encode(encoding)
- elif isinstance(x, tuple):
- return tuple(map(ensure_bytes, x))
- elif isinstance(x, list):
- return list(map(ensure_bytes, x))
- return x
-
- if isinstance(args, (tuple, list)):
- ret = tuple(literal(ensure_bytes(arg)) for arg in args)
- elif isinstance(args, dict):
- ret = {
- ensure_bytes(key): literal(ensure_bytes(val))
- for (key, val) in args.items()
- }
- else:
- # If it's not a dictionary let's try escaping it anyways.
- # Worst case it will throw a Value error
- ret = literal(ensure_bytes(args))
-
- ensure_bytes = None # break circular reference
- return ret
-
- def _check_executed(self):
- if not self._executed:
- raise ProgrammingError("execute() first")
-
- def nextset(self):
- """Advance to the next result set.
-
- Returns None if there are no more result sets.
- """
- if self._executed:
- self.fetchall()
-
- db = self._get_db()
- nr = db.next_result()
- if nr == -1:
- return None
- self._do_get_result(db)
- self._post_get_result()
- return 1
-
- def _do_get_result(self, db):
- self._result = result = self._get_result()
- if result is None:
- self.description = self.description_flags = None
- else:
- self.description = result.describe()
- self.description_flags = result.field_flags()
-
- self.rowcount = db.affected_rows()
- self.rownumber = 0
- self.lastrowid = db.insert_id()
-
- def _post_get_result(self):
- pass
-
- def setinputsizes(self, *args):
- """Does nothing, required by DB API."""
-
- def setoutputsizes(self, *args):
- """Does nothing, required by DB API."""
-
- def _get_db(self):
- con = self.connection
- if con is None:
- raise ProgrammingError("cursor closed")
- return con
-
- def execute(self, query, args=None):
- """Execute a query.
-
- query -- string, query to execute on server
- args -- optional sequence or mapping, parameters to use with query.
-
- Note: If args is a sequence, then %s must be used as the
- parameter placeholder in the query. If a mapping is used,
- %(key)s must be used as the placeholder.
-
- Returns integer represents rows affected, if any
- """
- while self.nextset():
- pass
- db = self._get_db()
-
- if isinstance(query, str):
- query = query.encode(db.encoding)
-
- if args is not None:
- if isinstance(args, dict):
- nargs = {}
- for key, item in args.items():
- if isinstance(key, str):
- key = key.encode(db.encoding)
- nargs[key] = db.literal(item)
- args = nargs
- else:
- args = tuple(map(db.literal, args))
- try:
- query = query % args
- except TypeError as m:
- raise ProgrammingError(str(m))
-
- assert isinstance(query, (bytes, bytearray))
- res = self._query(query)
- return res
-
- def executemany(self, query, args):
- # type: (str, list) -> int
- """Execute a multi-row query.
-
- :param query: query to execute on server
- :param args: Sequence of sequences or mappings. It is used as parameter.
- :return: Number of rows affected, if any.
-
- This method improves performance on multiple-row INSERT and
- REPLACE. Otherwise it is equivalent to looping over args with
- execute().
- """
- if not args:
- return
-
- m = RE_INSERT_VALUES.match(query)
- if m:
- q_prefix = m.group(1) % ()
- q_values = m.group(2).rstrip()
- q_postfix = m.group(3) or ""
- assert q_values[0] == "(" and q_values[-1] == ")"
- return self._do_execute_many(
- q_prefix,
- q_values,
- q_postfix,
- args,
- self.max_stmt_length,
- self._get_db().encoding,
- )
-
- self.rowcount = sum(self.execute(query, arg) for arg in args)
- return self.rowcount
-
- def _do_execute_many(
- self, prefix, values, postfix, args, max_stmt_length, encoding
- ):
- conn = self._get_db()
- escape = self._escape_args
- if isinstance(prefix, str):
- prefix = prefix.encode(encoding)
- if isinstance(values, str):
- values = values.encode(encoding)
- if isinstance(postfix, str):
- postfix = postfix.encode(encoding)
- sql = bytearray(prefix)
- args = iter(args)
- v = values % escape(next(args), conn)
- sql += v
- rows = 0
- for arg in args:
- v = values % escape(arg, conn)
- if len(sql) + len(v) + len(postfix) + 1 > max_stmt_length:
- rows += self.execute(sql + postfix)
- sql = bytearray(prefix)
- else:
- sql += b","
- sql += v
- rows += self.execute(sql + postfix)
- self.rowcount = rows
- return rows
-
- def callproc(self, procname, args=()):
- """Execute stored procedure procname with args
-
- procname -- string, name of procedure to execute on server
-
- args -- Sequence of parameters to use with procedure
-
- Returns the original args.
-
- Compatibility warning: PEP-249 specifies that any modified
- parameters must be returned. This is currently impossible
- as they are only available by storing them in a server
- variable and then retrieved by a query. Since stored
- procedures return zero or more result sets, there is no
- reliable way to get at OUT or INOUT parameters via callproc.
- The server variables are named @_procname_n, where procname
- is the parameter above and n is the position of the parameter
- (from zero). Once all result sets generated by the procedure
- have been fetched, you can issue a SELECT @_procname_0, ...
- query using .execute() to get any OUT or INOUT values.
-
- Compatibility warning: The act of calling a stored procedure
- itself creates an empty result set. This appears after any
- result sets generated by the procedure. This is non-standard
- behavior with respect to the DB-API. Be sure to use nextset()
- to advance through all result sets; otherwise you may get
- disconnected.
- """
- db = self._get_db()
- if isinstance(procname, str):
- procname = procname.encode(db.encoding)
- if args:
- fmt = b"@_" + procname + b"_%d=%s"
- q = b"SET %s" % b",".join(
- fmt % (index, db.literal(arg)) for index, arg in enumerate(args)
- )
- self._query(q)
- self.nextset()
-
- q = b"CALL %s(%s)" % (
- procname,
- b",".join([b"@_%s_%d" % (procname, i) for i in range(len(args))]),
- )
- self._query(q)
- return args
-
- def _query(self, q):
- db = self._get_db()
- self._result = None
- db.query(q)
- self._do_get_result(db)
- self._post_get_result()
- self._executed = q
- return self.rowcount
-
- def _fetch_row(self, size=1):
- if not self._result:
- return ()
- return self._result.fetch_row(size, self._fetch_type)
-
- def __iter__(self):
- return iter(self.fetchone, None)
-
- Warning = Warning
- Error = Error
- InterfaceError = InterfaceError
- DatabaseError = DatabaseError
- DataError = DataError
- OperationalError = OperationalError
- IntegrityError = IntegrityError
- InternalError = InternalError
- ProgrammingError = ProgrammingError
- NotSupportedError = NotSupportedError
-
-
-class CursorStoreResultMixIn:
- """This is a MixIn class which causes the entire result set to be
- stored on the client side, i.e. it uses mysql_store_result(). If the
- result set can be very large, consider adding a LIMIT clause to your
- query, or using CursorUseResultMixIn instead."""
-
- def _get_result(self):
- return self._get_db().store_result()
-
- def _post_get_result(self):
- self._rows = self._fetch_row(0)
- self._result = None
-
- def fetchone(self):
- """Fetches a single row from the cursor. None indicates that
- no more rows are available."""
- self._check_executed()
- if self.rownumber >= len(self._rows):
- return None
- result = self._rows[self.rownumber]
- self.rownumber = self.rownumber + 1
- return result
-
- def fetchmany(self, size=None):
- """Fetch up to size rows from the cursor. Result set may be smaller
- than size. If size is not defined, cursor.arraysize is used."""
- self._check_executed()
- end = self.rownumber + (size or self.arraysize)
- result = self._rows[self.rownumber : end]
- self.rownumber = min(end, len(self._rows))
- return result
-
- def fetchall(self):
- """Fetches all available rows from the cursor."""
- self._check_executed()
- if self.rownumber:
- result = self._rows[self.rownumber :]
- else:
- result = self._rows
- self.rownumber = len(self._rows)
- return result
-
- def scroll(self, value, mode="relative"):
- """Scroll the cursor in the result set to a new position according
- to mode.
-
- If mode is 'relative' (default), value is taken as offset to
- the current position in the result set, if set to 'absolute',
- value states an absolute target position."""
- self._check_executed()
- if mode == "relative":
- r = self.rownumber + value
- elif mode == "absolute":
- r = value
- else:
- raise ProgrammingError("unknown scroll mode %s" % repr(mode))
- if r < 0 or r >= len(self._rows):
- raise IndexError("out of range")
- self.rownumber = r
-
- def __iter__(self):
- self._check_executed()
- result = self.rownumber and self._rows[self.rownumber :] or self._rows
- return iter(result)
-
-
-class CursorUseResultMixIn:
-
- """This is a MixIn class which causes the result set to be stored
- in the server and sent row-by-row to client side, i.e. it uses
- mysql_use_result(). You MUST retrieve the entire result set and
- close() the cursor before additional queries can be performed on
- the connection."""
-
- def _get_result(self):
- return self._get_db().use_result()
-
- def fetchone(self):
- """Fetches a single row from the cursor."""
- self._check_executed()
- r = self._fetch_row(1)
- if not r:
- return None
- self.rownumber = self.rownumber + 1
- return r[0]
-
- def fetchmany(self, size=None):
- """Fetch up to size rows from the cursor. Result set may be smaller
- than size. If size is not defined, cursor.arraysize is used."""
- self._check_executed()
- r = self._fetch_row(size or self.arraysize)
- self.rownumber = self.rownumber + len(r)
- return r
-
- def fetchall(self):
- """Fetches all available rows from the cursor."""
- self._check_executed()
- r = self._fetch_row(0)
- self.rownumber = self.rownumber + len(r)
- return r
-
- def __iter__(self):
- return self
-
- def next(self):
- row = self.fetchone()
- if row is None:
- raise StopIteration
- return row
-
- __next__ = next
-
-
-class CursorTupleRowsMixIn:
- """This is a MixIn class that causes all rows to be returned as tuples,
- which is the standard form required by DB API."""
-
- _fetch_type = 0
-
-
-class CursorDictRowsMixIn:
- """This is a MixIn class that causes all rows to be returned as
- dictionaries. This is a non-standard feature."""
-
- _fetch_type = 1
-
-
-class Cursor(CursorStoreResultMixIn, CursorTupleRowsMixIn, BaseCursor):
- """This is the standard Cursor class that returns rows as tuples
- and stores the result set in the client."""
-
-
-class DictCursor(CursorStoreResultMixIn, CursorDictRowsMixIn, BaseCursor):
- """This is a Cursor class that returns rows as dictionaries and
- stores the result set in the client."""
-
-
-class SSCursor(CursorUseResultMixIn, CursorTupleRowsMixIn, BaseCursor):
- """This is a Cursor class that returns rows as tuples and stores
- the result set in the server."""
-
-
-class SSDictCursor(CursorUseResultMixIn, CursorDictRowsMixIn, BaseCursor):
- """This is a Cursor class that returns rows as dictionaries and
- stores the result set in the server."""
diff --git a/venv/Lib/site-packages/MySQLdb/release.py b/venv/Lib/site-packages/MySQLdb/release.py
deleted file mode 100644
index 38b522c..0000000
--- a/venv/Lib/site-packages/MySQLdb/release.py
+++ /dev/null
@@ -1,4 +0,0 @@
-
-__author__ = "Inada Naoki "
-version_info = (2,1,0,'final',0)
-__version__ = "2.1.0"
diff --git a/venv/Lib/site-packages/MySQLdb/times.py b/venv/Lib/site-packages/MySQLdb/times.py
deleted file mode 100644
index 915d827..0000000
--- a/venv/Lib/site-packages/MySQLdb/times.py
+++ /dev/null
@@ -1,150 +0,0 @@
-"""times module
-
-This module provides some Date and Time classes for dealing with MySQL data.
-
-Use Python datetime module to handle date and time columns.
-"""
-from time import localtime
-from datetime import date, datetime, time, timedelta
-from MySQLdb._mysql import string_literal
-
-Date = date
-Time = time
-TimeDelta = timedelta
-Timestamp = datetime
-
-DateTimeDeltaType = timedelta
-DateTimeType = datetime
-
-
-def DateFromTicks(ticks):
- """Convert UNIX ticks into a date instance."""
- return date(*localtime(ticks)[:3])
-
-
-def TimeFromTicks(ticks):
- """Convert UNIX ticks into a time instance."""
- return time(*localtime(ticks)[3:6])
-
-
-def TimestampFromTicks(ticks):
- """Convert UNIX ticks into a datetime instance."""
- return datetime(*localtime(ticks)[:6])
-
-
-format_TIME = format_DATE = str
-
-
-def format_TIMEDELTA(v):
- seconds = int(v.seconds) % 60
- minutes = int(v.seconds // 60) % 60
- hours = int(v.seconds // 3600) % 24
- return "%d %d:%d:%d" % (v.days, hours, minutes, seconds)
-
-
-def format_TIMESTAMP(d):
- """
- :type d: datetime.datetime
- """
- if d.microsecond:
- fmt = " ".join(
- [
- "{0.year:04}-{0.month:02}-{0.day:02}",
- "{0.hour:02}:{0.minute:02}:{0.second:02}.{0.microsecond:06}",
- ]
- )
- else:
- fmt = " ".join(
- [
- "{0.year:04}-{0.month:02}-{0.day:02}",
- "{0.hour:02}:{0.minute:02}:{0.second:02}",
- ]
- )
- return fmt.format(d)
-
-
-def DateTime_or_None(s):
- try:
- if len(s) < 11:
- return Date_or_None(s)
-
- micros = s[20:]
-
- if len(micros) == 0:
- # 12:00:00
- micros = 0
- elif len(micros) < 7:
- # 12:00:00.123456
- micros = int(micros) * 10 ** (6 - len(micros))
- else:
- return None
-
- return datetime(
- int(s[:4]), # year
- int(s[5:7]), # month
- int(s[8:10]), # day
- int(s[11:13] or 0), # hour
- int(s[14:16] or 0), # minute
- int(s[17:19] or 0), # second
- micros, # microsecond
- )
- except ValueError:
- return None
-
-
-def TimeDelta_or_None(s):
- try:
- h, m, s = s.split(":")
- if "." in s:
- s, ms = s.split(".")
- ms = ms.ljust(6, "0")
- else:
- ms = 0
- if h[0] == "-":
- negative = True
- else:
- negative = False
- h, m, s, ms = abs(int(h)), int(m), int(s), int(ms)
- td = timedelta(hours=h, minutes=m, seconds=s, microseconds=ms)
- if negative:
- return -td
- else:
- return td
- except ValueError:
- # unpacking or int/float conversion failed
- return None
-
-
-def Time_or_None(s):
- try:
- h, m, s = s.split(":")
- if "." in s:
- s, ms = s.split(".")
- ms = ms.ljust(6, "0")
- else:
- ms = 0
- h, m, s, ms = int(h), int(m), int(s), int(ms)
- return time(hour=h, minute=m, second=s, microsecond=ms)
- except ValueError:
- return None
-
-
-def Date_or_None(s):
- try:
- return date(
- int(s[:4]),
- int(s[5:7]),
- int(s[8:10]),
- ) # year # month # day
- except ValueError:
- return None
-
-
-def DateTime2literal(d, c):
- """Format a DateTime object as an ISO timestamp."""
- return string_literal(format_TIMESTAMP(d))
-
-
-def DateTimeDelta2literal(d, c):
- """Format a DateTimeDelta object as a time."""
- return string_literal(format_TIMEDELTA(d))
diff --git a/venv/Lib/site-packages/PIL/BdfFontFile.py b/venv/Lib/site-packages/PIL/BdfFontFile.py
deleted file mode 100644
index 102b72e..0000000
--- a/venv/Lib/site-packages/PIL/BdfFontFile.py
+++ /dev/null
@@ -1,110 +0,0 @@
-#
-# The Python Imaging Library
-# $Id$
-#
-# bitmap distribution font (bdf) file parser
-#
-# history:
-# 1996-05-16 fl created (as bdf2pil)
-# 1997-08-25 fl converted to FontFile driver
-# 2001-05-25 fl removed bogus __init__ call
-# 2002-11-20 fl robustification (from Kevin Cazabon, Dmitry Vasiliev)
-# 2003-04-22 fl more robustification (from Graham Dumpleton)
-#
-# Copyright (c) 1997-2003 by Secret Labs AB.
-# Copyright (c) 1997-2003 by Fredrik Lundh.
-#
-# See the README file for information on usage and redistribution.
-#
-
-"""
-Parse X Bitmap Distribution Format (BDF)
-"""
-
-
-from . import FontFile, Image
-
-bdf_slant = {
- "R": "Roman",
- "I": "Italic",
- "O": "Oblique",
- "RI": "Reverse Italic",
- "RO": "Reverse Oblique",
- "OT": "Other",
-}
-
-bdf_spacing = {"P": "Proportional", "M": "Monospaced", "C": "Cell"}
-
-
-def bdf_char(f):
- # skip to STARTCHAR
- while True:
- s = f.readline()
- if not s:
- return None
- if s[:9] == b"STARTCHAR":
- break
- id = s[9:].strip().decode("ascii")
-
- # load symbol properties
- props = {}
- while True:
- s = f.readline()
- if not s or s[:6] == b"BITMAP":
- break
- i = s.find(b" ")
- props[s[:i].decode("ascii")] = s[i + 1 : -1].decode("ascii")
-
- # load bitmap
- bitmap = []
- while True:
- s = f.readline()
- if not s or s[:7] == b"ENDCHAR":
- break
- bitmap.append(s[:-1])
- bitmap = b"".join(bitmap)
-
- [x, y, l, d] = [int(p) for p in props["BBX"].split()]
- [dx, dy] = [int(p) for p in props["DWIDTH"].split()]
-
- bbox = (dx, dy), (l, -d - y, x + l, -d), (0, 0, x, y)
-
- try:
- im = Image.frombytes("1", (x, y), bitmap, "hex", "1")
- except ValueError:
- # deal with zero-width characters
- im = Image.new("1", (x, y))
-
- return id, int(props["ENCODING"]), bbox, im
-
-
-class BdfFontFile(FontFile.FontFile):
- """Font file plugin for the X11 BDF format."""
-
- def __init__(self, fp):
- super().__init__()
-
- s = fp.readline()
- if s[:13] != b"STARTFONT 2.1":
- raise SyntaxError("not a valid BDF file")
-
- props = {}
- comments = []
-
- while True:
- s = fp.readline()
- if not s or s[:13] == b"ENDPROPERTIES":
- break
- i = s.find(b" ")
- props[s[:i].decode("ascii")] = s[i + 1 : -1].decode("ascii")
- if s[:i] in [b"COMMENT", b"COPYRIGHT"]:
- if s.find(b"LogicalFontDescription") < 0:
- comments.append(s[i + 1 : -1].decode("ascii"))
-
- while True:
- c = bdf_char(fp)
- if not c:
- break
- id, ch, (xy, dst, src), im = c
- if 0 <= ch < len(self.glyph):
- self.glyph[ch] = xy, dst, src, im
diff --git a/venv/Lib/site-packages/PIL/BlpImagePlugin.py b/venv/Lib/site-packages/PIL/BlpImagePlugin.py
deleted file mode 100644
index ecd3da5..0000000
--- a/venv/Lib/site-packages/PIL/BlpImagePlugin.py
+++ /dev/null
@@ -1,497 +0,0 @@
-"""
-Blizzard Mipmap Format (.blp)
-Jerome Leclanche
-
-The contents of this file are hereby released in the public domain (CC0)
-Full text of the CC0 license:
- https://creativecommons.org/publicdomain/zero/1.0/
-
-BLP1 files, used mostly in Warcraft III, are not fully supported.
-All types of BLP2 files used in World of Warcraft are supported.
-
-The BLP file structure consists of a header, up to 16 mipmaps of the
-texture
-
-Texture sizes must be powers of two, though the two dimensions do
-not have to be equal; 512x256 is valid, but 512x200 is not.
-The first mipmap (mipmap #0) is the full size image; each subsequent
-mipmap halves both dimensions. The final mipmap should be 1x1.
-
-BLP files come in many different flavours:
-* JPEG-compressed (type == 0) - only supported for BLP1.
-* RAW images (type == 1, encoding == 1). Each mipmap is stored as an
- array of 8-bit values, one per pixel, left to right, top to bottom.
- Each value is an index to the palette.
-* DXT-compressed (type == 1, encoding == 2):
-- DXT1 compression is used if alpha_encoding == 0.
- - An additional alpha bit is used if alpha_depth == 1.
- - DXT3 compression is used if alpha_encoding == 1.
- - DXT5 compression is used if alpha_encoding == 7.
-"""
-
-import os
-import struct
-import warnings
-from enum import IntEnum
-from io import BytesIO
-
-from . import Image, ImageFile
-
-
-class Format(IntEnum):
- JPEG = 0
-
-
-class Encoding(IntEnum):
- UNCOMPRESSED = 1
- DXT = 2
- UNCOMPRESSED_RAW_BGRA = 3
-
-
-class AlphaEncoding(IntEnum):
- DXT1 = 0
- DXT3 = 1
- DXT5 = 7
-
-
-def __getattr__(name):
- deprecated = "deprecated and will be removed in Pillow 10 (2023-07-01). "
- for enum, prefix in {
- Format: "BLP_FORMAT_",
- Encoding: "BLP_ENCODING_",
- AlphaEncoding: "BLP_ALPHA_ENCODING_",
- }.items():
- if name.startswith(prefix):
- name = name[len(prefix) :]
- if name in enum.__members__:
- warnings.warn(
- prefix
- + name
- + " is "
- + deprecated
- + "Use "
- + enum.__name__
- + "."
- + name
- + " instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- return enum[name]
- raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
-
-
-def unpack_565(i):
- return (((i >> 11) & 0x1F) << 3, ((i >> 5) & 0x3F) << 2, (i & 0x1F) << 3)
-
-
-def decode_dxt1(data, alpha=False):
- """
- input: one "row" of data (i.e. will produce 4*width pixels)
- """
-
- blocks = len(data) // 8 # number of blocks in row
- ret = (bytearray(), bytearray(), bytearray(), bytearray())
-
- for block in range(blocks):
- # Decode next 8-byte block.
- idx = block * 8
- color0, color1, bits = struct.unpack_from("> 2
-
- a = 0xFF
- if control == 0:
- r, g, b = r0, g0, b0
- elif control == 1:
- r, g, b = r1, g1, b1
- elif control == 2:
- if color0 > color1:
- r = (2 * r0 + r1) // 3
- g = (2 * g0 + g1) // 3
- b = (2 * b0 + b1) // 3
- else:
- r = (r0 + r1) // 2
- g = (g0 + g1) // 2
- b = (b0 + b1) // 2
- elif control == 3:
- if color0 > color1:
- r = (2 * r1 + r0) // 3
- g = (2 * g1 + g0) // 3
- b = (2 * b1 + b0) // 3
- else:
- r, g, b, a = 0, 0, 0, 0
-
- if alpha:
- ret[j].extend([r, g, b, a])
- else:
- ret[j].extend([r, g, b])
-
- return ret
-
-
-def decode_dxt3(data):
- """
- input: one "row" of data (i.e. will produce 4*width pixels)
- """
-
- blocks = len(data) // 16 # number of blocks in row
- ret = (bytearray(), bytearray(), bytearray(), bytearray())
-
- for block in range(blocks):
- idx = block * 16
- block = data[idx : idx + 16]
- # Decode next 16-byte block.
- bits = struct.unpack_from("<8B", block)
- color0, color1 = struct.unpack_from(">= 4
- else:
- high = True
- a &= 0xF
- a *= 17 # We get a value between 0 and 15
-
- color_code = (code >> 2 * (4 * j + i)) & 0x03
-
- if color_code == 0:
- r, g, b = r0, g0, b0
- elif color_code == 1:
- r, g, b = r1, g1, b1
- elif color_code == 2:
- r = (2 * r0 + r1) // 3
- g = (2 * g0 + g1) // 3
- b = (2 * b0 + b1) // 3
- elif color_code == 3:
- r = (2 * r1 + r0) // 3
- g = (2 * g1 + g0) // 3
- b = (2 * b1 + b0) // 3
-
- ret[j].extend([r, g, b, a])
-
- return ret
-
-
-def decode_dxt5(data):
- """
- input: one "row" of data (i.e. will produce 4 * width pixels)
- """
-
- blocks = len(data) // 16 # number of blocks in row
- ret = (bytearray(), bytearray(), bytearray(), bytearray())
-
- for block in range(blocks):
- idx = block * 16
- block = data[idx : idx + 16]
- # Decode next 16-byte block.
- a0, a1 = struct.unpack_from("> alphacode_index) & 0x07
- elif alphacode_index == 15:
- alphacode = (alphacode2 >> 15) | ((alphacode1 << 1) & 0x06)
- else: # alphacode_index >= 18 and alphacode_index <= 45
- alphacode = (alphacode1 >> (alphacode_index - 16)) & 0x07
-
- if alphacode == 0:
- a = a0
- elif alphacode == 1:
- a = a1
- elif a0 > a1:
- a = ((8 - alphacode) * a0 + (alphacode - 1) * a1) // 7
- elif alphacode == 6:
- a = 0
- elif alphacode == 7:
- a = 255
- else:
- a = ((6 - alphacode) * a0 + (alphacode - 1) * a1) // 5
-
- color_code = (code >> 2 * (4 * j + i)) & 0x03
-
- if color_code == 0:
- r, g, b = r0, g0, b0
- elif color_code == 1:
- r, g, b = r1, g1, b1
- elif color_code == 2:
- r = (2 * r0 + r1) // 3
- g = (2 * g0 + g1) // 3
- b = (2 * b0 + b1) // 3
- elif color_code == 3:
- r = (2 * r1 + r0) // 3
- g = (2 * g1 + g0) // 3
- b = (2 * b1 + b0) // 3
-
- ret[j].extend([r, g, b, a])
-
- return ret
-
-
-class BLPFormatError(NotImplementedError):
- pass
-
-
-def _accept(prefix):
- return prefix[:4] in (b"BLP1", b"BLP2")
-
-
-class BlpImageFile(ImageFile.ImageFile):
- """
- Blizzard Mipmap Format
- """
-
- format = "BLP"
- format_description = "Blizzard Mipmap Format"
-
- def _open(self):
- self.magic = self.fp.read(4)
-
- self.fp.seek(5, os.SEEK_CUR)
- (self._blp_alpha_depth,) = struct.unpack(" mode, rawmode
- 1: ("P", "P;1"),
- 4: ("P", "P;4"),
- 8: ("P", "P"),
- 16: ("RGB", "BGR;15"),
- 24: ("RGB", "BGR"),
- 32: ("RGB", "BGRX"),
-}
-
-
-def _accept(prefix):
- return prefix[:2] == b"BM"
-
-
-def _dib_accept(prefix):
- return i32(prefix) in [12, 40, 64, 108, 124]
-
-
-# =============================================================================
-# Image plugin for the Windows BMP format.
-# =============================================================================
-class BmpImageFile(ImageFile.ImageFile):
- """Image plugin for the Windows Bitmap format (BMP)"""
-
- # ------------------------------------------------------------- Description
- format_description = "Windows Bitmap"
- format = "BMP"
-
- # -------------------------------------------------- BMP Compression values
- COMPRESSIONS = {"RAW": 0, "RLE8": 1, "RLE4": 2, "BITFIELDS": 3, "JPEG": 4, "PNG": 5}
- for k, v in COMPRESSIONS.items():
- vars()[k] = v
-
- def _bitmap(self, header=0, offset=0):
- """Read relevant info about the BMP"""
- read, seek = self.fp.read, self.fp.seek
- if header:
- seek(header)
- file_info = {}
- # read bmp header size @offset 14 (this is part of the header size)
- file_info["header_size"] = i32(read(4))
- file_info["direction"] = -1
-
- # -------------------- If requested, read header at a specific position
- # read the rest of the bmp header, without its size
- header_data = ImageFile._safe_read(self.fp, file_info["header_size"] - 4)
-
- # -------------------------------------------------- IBM OS/2 Bitmap v1
- # ----- This format has different offsets because of width/height types
- if file_info["header_size"] == 12:
- file_info["width"] = i16(header_data, 0)
- file_info["height"] = i16(header_data, 2)
- file_info["planes"] = i16(header_data, 4)
- file_info["bits"] = i16(header_data, 6)
- file_info["compression"] = self.RAW
- file_info["palette_padding"] = 3
-
- # --------------------------------------------- Windows Bitmap v2 to v5
- # v3, OS/2 v2, v4, v5
- elif file_info["header_size"] in (40, 64, 108, 124):
- file_info["y_flip"] = header_data[7] == 0xFF
- file_info["direction"] = 1 if file_info["y_flip"] else -1
- file_info["width"] = i32(header_data, 0)
- file_info["height"] = (
- i32(header_data, 4)
- if not file_info["y_flip"]
- else 2**32 - i32(header_data, 4)
- )
- file_info["planes"] = i16(header_data, 8)
- file_info["bits"] = i16(header_data, 10)
- file_info["compression"] = i32(header_data, 12)
- # byte size of pixel data
- file_info["data_size"] = i32(header_data, 16)
- file_info["pixels_per_meter"] = (
- i32(header_data, 20),
- i32(header_data, 24),
- )
- file_info["colors"] = i32(header_data, 28)
- file_info["palette_padding"] = 4
- self.info["dpi"] = tuple(x / 39.3701 for x in file_info["pixels_per_meter"])
- if file_info["compression"] == self.BITFIELDS:
- if len(header_data) >= 52:
- for idx, mask in enumerate(
- ["r_mask", "g_mask", "b_mask", "a_mask"]
- ):
- file_info[mask] = i32(header_data, 36 + idx * 4)
- else:
- # 40 byte headers only have the three components in the
- # bitfields masks, ref:
- # https://msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx
- # See also
- # https://github.com/python-pillow/Pillow/issues/1293
- # There is a 4th component in the RGBQuad, in the alpha
- # location, but it is listed as a reserved component,
- # and it is not generally an alpha channel
- file_info["a_mask"] = 0x0
- for mask in ["r_mask", "g_mask", "b_mask"]:
- file_info[mask] = i32(read(4))
- file_info["rgb_mask"] = (
- file_info["r_mask"],
- file_info["g_mask"],
- file_info["b_mask"],
- )
- file_info["rgba_mask"] = (
- file_info["r_mask"],
- file_info["g_mask"],
- file_info["b_mask"],
- file_info["a_mask"],
- )
- else:
- raise OSError(f"Unsupported BMP header type ({file_info['header_size']})")
-
- # ------------------ Special case : header is reported 40, which
- # ---------------------- is shorter than real size for bpp >= 16
- self._size = file_info["width"], file_info["height"]
-
- # ------- If color count was not found in the header, compute from bits
- file_info["colors"] = (
- file_info["colors"]
- if file_info.get("colors", 0)
- else (1 << file_info["bits"])
- )
- if offset == 14 + file_info["header_size"] and file_info["bits"] <= 8:
- offset += 4 * file_info["colors"]
-
- # ---------------------- Check bit depth for unusual unsupported values
- self.mode, raw_mode = BIT2MODE.get(file_info["bits"], (None, None))
- if self.mode is None:
- raise OSError(f"Unsupported BMP pixel depth ({file_info['bits']})")
-
- # ---------------- Process BMP with Bitfields compression (not palette)
- decoder_name = "raw"
- if file_info["compression"] == self.BITFIELDS:
- SUPPORTED = {
- 32: [
- (0xFF0000, 0xFF00, 0xFF, 0x0),
- (0xFF0000, 0xFF00, 0xFF, 0xFF000000),
- (0xFF, 0xFF00, 0xFF0000, 0xFF000000),
- (0x0, 0x0, 0x0, 0x0),
- (0xFF000000, 0xFF0000, 0xFF00, 0x0),
- ],
- 24: [(0xFF0000, 0xFF00, 0xFF)],
- 16: [(0xF800, 0x7E0, 0x1F), (0x7C00, 0x3E0, 0x1F)],
- }
- MASK_MODES = {
- (32, (0xFF0000, 0xFF00, 0xFF, 0x0)): "BGRX",
- (32, (0xFF000000, 0xFF0000, 0xFF00, 0x0)): "XBGR",
- (32, (0xFF, 0xFF00, 0xFF0000, 0xFF000000)): "RGBA",
- (32, (0xFF0000, 0xFF00, 0xFF, 0xFF000000)): "BGRA",
- (32, (0x0, 0x0, 0x0, 0x0)): "BGRA",
- (24, (0xFF0000, 0xFF00, 0xFF)): "BGR",
- (16, (0xF800, 0x7E0, 0x1F)): "BGR;16",
- (16, (0x7C00, 0x3E0, 0x1F)): "BGR;15",
- }
- if file_info["bits"] in SUPPORTED:
- if (
- file_info["bits"] == 32
- and file_info["rgba_mask"] in SUPPORTED[file_info["bits"]]
- ):
- raw_mode = MASK_MODES[(file_info["bits"], file_info["rgba_mask"])]
- self.mode = "RGBA" if "A" in raw_mode else self.mode
- elif (
- file_info["bits"] in (24, 16)
- and file_info["rgb_mask"] in SUPPORTED[file_info["bits"]]
- ):
- raw_mode = MASK_MODES[(file_info["bits"], file_info["rgb_mask"])]
- else:
- raise OSError("Unsupported BMP bitfields layout")
- else:
- raise OSError("Unsupported BMP bitfields layout")
- elif file_info["compression"] == self.RAW:
- if file_info["bits"] == 32 and header == 22: # 32-bit .cur offset
- raw_mode, self.mode = "BGRA", "RGBA"
- elif file_info["compression"] == self.RLE8:
- decoder_name = "bmp_rle"
- else:
- raise OSError(f"Unsupported BMP compression ({file_info['compression']})")
-
- # --------------- Once the header is processed, process the palette/LUT
- if self.mode == "P": # Paletted for 1, 4 and 8 bit images
-
- # ---------------------------------------------------- 1-bit images
- if not (0 < file_info["colors"] <= 65536):
- raise OSError(f"Unsupported BMP Palette size ({file_info['colors']})")
- else:
- padding = file_info["palette_padding"]
- palette = read(padding * file_info["colors"])
- greyscale = True
- indices = (
- (0, 255)
- if file_info["colors"] == 2
- else list(range(file_info["colors"]))
- )
-
- # ----------------- Check if greyscale and ignore palette if so
- for ind, val in enumerate(indices):
- rgb = palette[ind * padding : ind * padding + 3]
- if rgb != o8(val) * 3:
- greyscale = False
-
- # ------- If all colors are grey, white or black, ditch palette
- if greyscale:
- self.mode = "1" if file_info["colors"] == 2 else "L"
- raw_mode = self.mode
- else:
- self.mode = "P"
- self.palette = ImagePalette.raw(
- "BGRX" if padding == 4 else "BGR", palette
- )
-
- # ---------------------------- Finally set the tile data for the plugin
- self.info["compression"] = file_info["compression"]
- self.tile = [
- (
- decoder_name,
- (0, 0, file_info["width"], file_info["height"]),
- offset or self.fp.tell(),
- (
- raw_mode,
- ((file_info["width"] * file_info["bits"] + 31) >> 3) & (~3),
- file_info["direction"],
- ),
- )
- ]
-
- def _open(self):
- """Open file, check magic number and read header"""
- # read 14 bytes: magic number, filesize, reserved, header final offset
- head_data = self.fp.read(14)
- # choke if the file does not have the required magic bytes
- if not _accept(head_data):
- raise SyntaxError("Not a BMP file")
- # read the start position of the BMP image data (u32)
- offset = i32(head_data, 10)
- # load bitmap information (offset=raster info)
- self._bitmap(offset=offset)
-
-
-class BmpRleDecoder(ImageFile.PyDecoder):
- _pulls_fd = True
-
- def decode(self, buffer):
- data = bytearray()
- x = 0
- while len(data) < self.state.xsize * self.state.ysize:
- pixels = self.fd.read(1)
- byte = self.fd.read(1)
- if not pixels or not byte:
- break
- num_pixels = pixels[0]
- if num_pixels:
- # encoded mode
- if x + num_pixels > self.state.xsize:
- # Too much data for row
- num_pixels = max(0, self.state.xsize - x)
- data += byte * num_pixels
- x += num_pixels
- else:
- if byte[0] == 0:
- # end of line
- while len(data) % self.state.xsize != 0:
- data += b"\x00"
- x = 0
- elif byte[0] == 1:
- # end of bitmap
- break
- elif byte[0] == 2:
- # delta
- bytes_read = self.fd.read(2)
- if len(bytes_read) < 2:
- break
- right, up = self.fd.read(2)
- data += b"\x00" * (right + up * self.state.xsize)
- x = len(data) % self.state.xsize
- else:
- # absolute mode
- bytes_read = self.fd.read(byte[0])
- data += bytes_read
- if len(bytes_read) < byte[0]:
- break
- x += byte[0]
-
- # align to 16-bit word boundary
- if self.fd.tell() % 2 != 0:
- self.fd.seek(1, os.SEEK_CUR)
- self.set_as_raw(bytes(data), ("P", 0, self.args[-1]))
- return -1, 0
-
-
-# =============================================================================
-# Image plugin for the DIB format (BMP alias)
-# =============================================================================
-class DibImageFile(BmpImageFile):
-
- format = "DIB"
- format_description = "Windows Bitmap"
-
- def _open(self):
- self._bitmap()
-
-
-#
-# --------------------------------------------------------------------
-# Write BMP file
-
-
-SAVE = {
- "1": ("1", 1, 2),
- "L": ("L", 8, 256),
- "P": ("P", 8, 256),
- "RGB": ("BGR", 24, 0),
- "RGBA": ("BGRA", 32, 0),
-}
-
-
-def _dib_save(im, fp, filename):
- _save(im, fp, filename, False)
-
-
-def _save(im, fp, filename, bitmap_header=True):
- try:
- rawmode, bits, colors = SAVE[im.mode]
- except KeyError as e:
- raise OSError(f"cannot write mode {im.mode} as BMP") from e
-
- info = im.encoderinfo
-
- dpi = info.get("dpi", (96, 96))
-
- # 1 meter == 39.3701 inches
- ppm = tuple(map(lambda x: int(x * 39.3701 + 0.5), dpi))
-
- stride = ((im.size[0] * bits + 7) // 8 + 3) & (~3)
- header = 40 # or 64 for OS/2 version 2
- image = stride * im.size[1]
-
- # bitmap header
- if bitmap_header:
- offset = 14 + header + colors * 4
- file_size = offset + image
- if file_size > 2**32 - 1:
- raise ValueError("File size is too large for the BMP format")
- fp.write(
- b"BM" # file type (magic)
- + o32(file_size) # file size
- + o32(0) # reserved
- + o32(offset) # image data offset
- )
-
- # bitmap info header
- fp.write(
- o32(header) # info header size
- + o32(im.size[0]) # width
- + o32(im.size[1]) # height
- + o16(1) # planes
- + o16(bits) # depth
- + o32(0) # compression (0=uncompressed)
- + o32(image) # size of bitmap
- + o32(ppm[0]) # resolution
- + o32(ppm[1]) # resolution
- + o32(colors) # colors used
- + o32(colors) # colors important
- )
-
- fp.write(b"\0" * (header - 40)) # padding (for OS/2 format)
-
- if im.mode == "1":
- for i in (0, 255):
- fp.write(o8(i) * 4)
- elif im.mode == "L":
- for i in range(256):
- fp.write(o8(i) * 4)
- elif im.mode == "P":
- fp.write(im.im.getpalette("RGB", "BGRX"))
-
- ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, stride, -1))])
-
-
-#
-# --------------------------------------------------------------------
-# Registry
-
-
-Image.register_open(BmpImageFile.format, BmpImageFile, _accept)
-Image.register_save(BmpImageFile.format, _save)
-
-Image.register_extension(BmpImageFile.format, ".bmp")
-
-Image.register_mime(BmpImageFile.format, "image/bmp")
-
-Image.register_decoder("bmp_rle", BmpRleDecoder)
-
-Image.register_open(DibImageFile.format, DibImageFile, _dib_accept)
-Image.register_save(DibImageFile.format, _dib_save)
-
-Image.register_extension(DibImageFile.format, ".dib")
-
-Image.register_mime(DibImageFile.format, "image/bmp")
diff --git a/venv/Lib/site-packages/PIL/BufrStubImagePlugin.py b/venv/Lib/site-packages/PIL/BufrStubImagePlugin.py
deleted file mode 100644
index 9510f73..0000000
--- a/venv/Lib/site-packages/PIL/BufrStubImagePlugin.py
+++ /dev/null
@@ -1,73 +0,0 @@
-#
-# The Python Imaging Library
-# $Id$
-#
-# BUFR stub adapter
-#
-# Copyright (c) 1996-2003 by Fredrik Lundh
-#
-# See the README file for information on usage and redistribution.
-#
-
-from . import Image, ImageFile
-
-_handler = None
-
-
-def register_handler(handler):
- """
- Install application-specific BUFR image handler.
-
- :param handler: Handler object.
- """
- global _handler
- _handler = handler
-
-
-# --------------------------------------------------------------------
-# Image adapter
-
-
-def _accept(prefix):
- return prefix[:4] == b"BUFR" or prefix[:4] == b"ZCZC"
-
-
-class BufrStubImageFile(ImageFile.StubImageFile):
-
- format = "BUFR"
- format_description = "BUFR"
-
- def _open(self):
-
- offset = self.fp.tell()
-
- if not _accept(self.fp.read(4)):
- raise SyntaxError("Not a BUFR file")
-
- self.fp.seek(offset)
-
- # make something up
- self.mode = "F"
- self._size = 1, 1
-
- loader = self._load()
- if loader:
- loader.open(self)
-
- def _load(self):
- return _handler
-
-
-def _save(im, fp, filename):
- if _handler is None or not hasattr(_handler, "save"):
- raise OSError("BUFR save handler not installed")
- _handler.save(im, fp, filename)
-
-
-# --------------------------------------------------------------------
-# Registry
-
-Image.register_open(BufrStubImageFile.format, BufrStubImageFile, _accept)
-Image.register_save(BufrStubImageFile.format, _save)
-
-Image.register_extension(BufrStubImageFile.format, ".bufr")
diff --git a/venv/Lib/site-packages/PIL/ContainerIO.py b/venv/Lib/site-packages/PIL/ContainerIO.py
deleted file mode 100644
index 45e80b3..0000000
--- a/venv/Lib/site-packages/PIL/ContainerIO.py
+++ /dev/null
@@ -1,120 +0,0 @@
-#
-# The Python Imaging Library.
-# $Id$
-#
-# a class to read from a container file
-#
-# History:
-# 1995-06-18 fl Created
-# 1995-09-07 fl Added readline(), readlines()
-#
-# Copyright (c) 1997-2001 by Secret Labs AB
-# Copyright (c) 1995 by Fredrik Lundh
-#
-# See the README file for information on usage and redistribution.
-#
-
-
-import io
-
-
-class ContainerIO:
- """
- A file object that provides read access to a part of an existing
- file (for example a TAR file).
- """
-
- def __init__(self, file, offset, length):
- """
- Create file object.
-
- :param file: Existing file.
- :param offset: Start of region, in bytes.
- :param length: Size of region, in bytes.
- """
- self.fh = file
- self.pos = 0
- self.offset = offset
- self.length = length
- self.fh.seek(offset)
-
- ##
- # Always false.
-
- def isatty(self):
- return False
-
- def seek(self, offset, mode=io.SEEK_SET):
- """
- Move file pointer.
-
- :param offset: Offset in bytes.
- :param mode: Starting position. Use 0 for beginning of region, 1
- for current offset, and 2 for end of region. You cannot move
- the pointer outside the defined region.
- """
- if mode == 1:
- self.pos = self.pos + offset
- elif mode == 2:
- self.pos = self.length + offset
- else:
- self.pos = offset
- # clamp
- self.pos = max(0, min(self.pos, self.length))
- self.fh.seek(self.offset + self.pos)
-
- def tell(self):
- """
- Get current file pointer.
-
- :returns: Offset from start of region, in bytes.
- """
- return self.pos
-
- def read(self, n=0):
- """
- Read data.
-
- :param n: Number of bytes to read. If omitted or zero,
- read until end of region.
- :returns: An 8-bit string.
- """
- if n:
- n = min(n, self.length - self.pos)
- else:
- n = self.length - self.pos
- if not n: # EOF
- return b"" if "b" in self.fh.mode else ""
- self.pos = self.pos + n
- return self.fh.read(n)
-
- def readline(self):
- """
- Read a line of text.
-
- :returns: An 8-bit string.
- """
- s = b"" if "b" in self.fh.mode else ""
- newline_character = b"\n" if "b" in self.fh.mode else "\n"
- while True:
- c = self.read(1)
- if not c:
- break
- s = s + c
- if c == newline_character:
- break
- return s
-
- def readlines(self):
- """
- Read multiple lines of text.
-
- :returns: A list of 8-bit strings.
- """
- lines = []
- while True:
- s = self.readline()
- if not s:
- break
- lines.append(s)
- return lines
diff --git a/venv/Lib/site-packages/PIL/CurImagePlugin.py b/venv/Lib/site-packages/PIL/CurImagePlugin.py
deleted file mode 100644
index 42af5ca..0000000
--- a/venv/Lib/site-packages/PIL/CurImagePlugin.py
+++ /dev/null
@@ -1,75 +0,0 @@
-#
-# The Python Imaging Library.
-# $Id$
-#
-# Windows Cursor support for PIL
-#
-# notes:
-# uses BmpImagePlugin.py to read the bitmap data.
-#
-# history:
-# 96-05-27 fl Created
-#
-# Copyright (c) Secret Labs AB 1997.
-# Copyright (c) Fredrik Lundh 1996.
-#
-# See the README file for information on usage and redistribution.
-#
-from . import BmpImagePlugin, Image
-from ._binary import i16le as i16
-from ._binary import i32le as i32
-
-#
-# --------------------------------------------------------------------
-
-
-def _accept(prefix):
- return prefix[:4] == b"\0\0\2\0"
-
-
-##
-# Image plugin for Windows Cursor files.
-
-
-class CurImageFile(BmpImagePlugin.BmpImageFile):
-
- format = "CUR"
- format_description = "Windows Cursor"
-
- def _open(self):
-
- offset = self.fp.tell()
-
- # check magic
- s = self.fp.read(6)
- if not _accept(s):
- raise SyntaxError("not a CUR file")
-
- # pick the largest cursor in the file
- m = b""
- for i in range(i16(s, 4)):
- s = self.fp.read(16)
- if not m:
- m = s
- elif s[0] > m[0] and s[1] > m[1]:
- m = s
- if not m:
- raise TypeError("No cursors were found")
-
- # load as bitmap
- self._bitmap(i32(m, 12) + offset)
-
- # patch up the bitmap height
- self._size = self.size[0], self.size[1] // 2
- d, e, o, a = self.tile[0]
- self.tile[0] = d, (0, 0) + self.size, o, a
-
- return
-
-
-#
-# --------------------------------------------------------------------
-
-Image.register_open(CurImageFile.format, CurImageFile, _accept)
-
-Image.register_extension(CurImageFile.format, ".cur")
diff --git a/venv/Lib/site-packages/PIL/DcxImagePlugin.py b/venv/Lib/site-packages/PIL/DcxImagePlugin.py
deleted file mode 100644
index de21db8..0000000
--- a/venv/Lib/site-packages/PIL/DcxImagePlugin.py
+++ /dev/null
@@ -1,89 +0,0 @@
-#
-# The Python Imaging Library.
-# $Id$
-#
-# DCX file handling
-#
-# DCX is a container file format defined by Intel, commonly used
-# for fax applications. Each DCX file consists of a directory
-# (a list of file offsets) followed by a set of (usually 1-bit)
-# PCX files.
-#
-# History:
-# 1995-09-09 fl Created
-# 1996-03-20 fl Properly derived from PcxImageFile.
-# 1998-07-15 fl Renamed offset attribute to avoid name clash
-# 2002-07-30 fl Fixed file handling
-#
-# Copyright (c) 1997-98 by Secret Labs AB.
-# Copyright (c) 1995-96 by Fredrik Lundh.
-#
-# See the README file for information on usage and redistribution.
-#
-
-from . import Image
-from ._binary import i32le as i32
-from .PcxImagePlugin import PcxImageFile
-
-MAGIC = 0x3ADE68B1 # QUIZ: what's this value, then?
-
-
-def _accept(prefix):
- return len(prefix) >= 4 and i32(prefix) == MAGIC
-
-
-##
-# Image plugin for the Intel DCX format.
-
-
-class DcxImageFile(PcxImageFile):
-
- format = "DCX"
- format_description = "Intel DCX"
- _close_exclusive_fp_after_loading = False
-
- def _open(self):
-
- # Header
- s = self.fp.read(4)
- if not _accept(s):
- raise SyntaxError("not a DCX file")
-
- # Component directory
- self._offset = []
- for i in range(1024):
- offset = i32(self.fp.read(4))
- if not offset:
- break
- self._offset.append(offset)
-
- self.__fp = self.fp
- self.frame = None
- self.n_frames = len(self._offset)
- self.is_animated = self.n_frames > 1
- self.seek(0)
-
- def seek(self, frame):
- if not self._seek_check(frame):
- return
- self.frame = frame
- self.fp = self.__fp
- self.fp.seek(self._offset[frame])
- PcxImageFile._open(self)
-
- def tell(self):
- return self.frame
-
- def _close__fp(self):
- try:
- if self.__fp != self.fp:
- self.__fp.close()
- except AttributeError:
- pass
- finally:
- self.__fp = None
-
-
-Image.register_open(DcxImageFile.format, DcxImageFile, _accept)
-
-Image.register_extension(DcxImageFile.format, ".dcx")
diff --git a/venv/Lib/site-packages/PIL/DdsImagePlugin.py b/venv/Lib/site-packages/PIL/DdsImagePlugin.py
deleted file mode 100644
index 3a04bdb..0000000
--- a/venv/Lib/site-packages/PIL/DdsImagePlugin.py
+++ /dev/null
@@ -1,249 +0,0 @@
-"""
-A Pillow loader for .dds files (S3TC-compressed aka DXTC)
-Jerome Leclanche
-
-Documentation:
- https://web.archive.org/web/20170802060935/http://oss.sgi.com/projects/ogl-sample/registry/EXT/texture_compression_s3tc.txt
-
-The contents of this file are hereby released in the public domain (CC0)
-Full text of the CC0 license:
- https://creativecommons.org/publicdomain/zero/1.0/
-"""
-
-import struct
-from io import BytesIO
-
-from . import Image, ImageFile
-from ._binary import o32le as o32
-
-# Magic ("DDS ")
-DDS_MAGIC = 0x20534444
-
-# DDS flags
-DDSD_CAPS = 0x1
-DDSD_HEIGHT = 0x2
-DDSD_WIDTH = 0x4
-DDSD_PITCH = 0x8
-DDSD_PIXELFORMAT = 0x1000
-DDSD_MIPMAPCOUNT = 0x20000
-DDSD_LINEARSIZE = 0x80000
-DDSD_DEPTH = 0x800000
-
-# DDS caps
-DDSCAPS_COMPLEX = 0x8
-DDSCAPS_TEXTURE = 0x1000
-DDSCAPS_MIPMAP = 0x400000
-
-DDSCAPS2_CUBEMAP = 0x200
-DDSCAPS2_CUBEMAP_POSITIVEX = 0x400
-DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800
-DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000
-DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000
-DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000
-DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000
-DDSCAPS2_VOLUME = 0x200000
-
-# Pixel Format
-DDPF_ALPHAPIXELS = 0x1
-DDPF_ALPHA = 0x2
-DDPF_FOURCC = 0x4
-DDPF_PALETTEINDEXED8 = 0x20
-DDPF_RGB = 0x40
-DDPF_LUMINANCE = 0x20000
-
-
-# dds.h
-
-DDS_FOURCC = DDPF_FOURCC
-DDS_RGB = DDPF_RGB
-DDS_RGBA = DDPF_RGB | DDPF_ALPHAPIXELS
-DDS_LUMINANCE = DDPF_LUMINANCE
-DDS_LUMINANCEA = DDPF_LUMINANCE | DDPF_ALPHAPIXELS
-DDS_ALPHA = DDPF_ALPHA
-DDS_PAL8 = DDPF_PALETTEINDEXED8
-
-DDS_HEADER_FLAGS_TEXTURE = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT
-DDS_HEADER_FLAGS_MIPMAP = DDSD_MIPMAPCOUNT
-DDS_HEADER_FLAGS_VOLUME = DDSD_DEPTH
-DDS_HEADER_FLAGS_PITCH = DDSD_PITCH
-DDS_HEADER_FLAGS_LINEARSIZE = DDSD_LINEARSIZE
-
-DDS_HEIGHT = DDSD_HEIGHT
-DDS_WIDTH = DDSD_WIDTH
-
-DDS_SURFACE_FLAGS_TEXTURE = DDSCAPS_TEXTURE
-DDS_SURFACE_FLAGS_MIPMAP = DDSCAPS_COMPLEX | DDSCAPS_MIPMAP
-DDS_SURFACE_FLAGS_CUBEMAP = DDSCAPS_COMPLEX
-
-DDS_CUBEMAP_POSITIVEX = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEX
-DDS_CUBEMAP_NEGATIVEX = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEX
-DDS_CUBEMAP_POSITIVEY = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEY
-DDS_CUBEMAP_NEGATIVEY = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEY
-DDS_CUBEMAP_POSITIVEZ = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEZ
-DDS_CUBEMAP_NEGATIVEZ = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEZ
-
-
-# DXT1
-DXT1_FOURCC = 0x31545844
-
-# DXT3
-DXT3_FOURCC = 0x33545844
-
-# DXT5
-DXT5_FOURCC = 0x35545844
-
-
-# dxgiformat.h
-
-DXGI_FORMAT_R8G8B8A8_TYPELESS = 27
-DXGI_FORMAT_R8G8B8A8_UNORM = 28
-DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = 29
-DXGI_FORMAT_BC5_TYPELESS = 82
-DXGI_FORMAT_BC5_UNORM = 83
-DXGI_FORMAT_BC5_SNORM = 84
-DXGI_FORMAT_BC7_TYPELESS = 97
-DXGI_FORMAT_BC7_UNORM = 98
-DXGI_FORMAT_BC7_UNORM_SRGB = 99
-
-
-class DdsImageFile(ImageFile.ImageFile):
- format = "DDS"
- format_description = "DirectDraw Surface"
-
- def _open(self):
- if not _accept(self.fp.read(4)):
- raise SyntaxError("not a DDS file")
- (header_size,) = struct.unpack(" 0:
- s = fp.read(min(lengthfile, 100 * 1024))
- if not s:
- break
- lengthfile -= len(s)
- f.write(s)
-
- device = "pngalpha" if transparency else "ppmraw"
-
- # Build Ghostscript command
- command = [
- "gs",
- "-q", # quiet mode
- "-g%dx%d" % size, # set output geometry (pixels)
- "-r%fx%f" % res, # set input DPI (dots per inch)
- "-dBATCH", # exit after processing
- "-dNOPAUSE", # don't pause between pages
- "-dSAFER", # safe mode
- f"-sDEVICE={device}",
- f"-sOutputFile={outfile}", # output file
- # adjust for image origin
- "-c",
- f"{-bbox[0]} {-bbox[1]} translate",
- "-f",
- infile, # input file
- # showpage (see https://bugs.ghostscript.com/show_bug.cgi?id=698272)
- "-c",
- "showpage",
- ]
-
- if gs_windows_binary is not None:
- if not gs_windows_binary:
- raise OSError("Unable to locate Ghostscript on paths")
- command[0] = gs_windows_binary
-
- # push data through Ghostscript
- try:
- startupinfo = None
- if sys.platform.startswith("win"):
- startupinfo = subprocess.STARTUPINFO()
- startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
- subprocess.check_call(command, startupinfo=startupinfo)
- out_im = Image.open(outfile)
- out_im.load()
- finally:
- try:
- os.unlink(outfile)
- if infile_temp:
- os.unlink(infile_temp)
- except OSError:
- pass
-
- im = out_im.im.copy()
- out_im.close()
- return im
-
-
-class PSFile:
- """
- Wrapper for bytesio object that treats either CR or LF as end of line.
- """
-
- def __init__(self, fp):
- self.fp = fp
- self.char = None
-
- def seek(self, offset, whence=io.SEEK_SET):
- self.char = None
- self.fp.seek(offset, whence)
-
- def readline(self):
- s = [self.char or b""]
- self.char = None
-
- c = self.fp.read(1)
- while (c not in b"\r\n") and len(c):
- s.append(c)
- c = self.fp.read(1)
-
- self.char = self.fp.read(1)
- # line endings can be 1 or 2 of \r \n, in either order
- if self.char in b"\r\n":
- self.char = None
-
- return b"".join(s).decode("latin-1")
-
-
-def _accept(prefix):
- return prefix[:4] == b"%!PS" or (len(prefix) >= 4 and i32(prefix) == 0xC6D3D0C5)
-
-
-##
-# Image plugin for Encapsulated PostScript. This plugin supports only
-# a few variants of this format.
-
-
-class EpsImageFile(ImageFile.ImageFile):
- """EPS File Parser for the Python Imaging Library"""
-
- format = "EPS"
- format_description = "Encapsulated Postscript"
-
- mode_map = {1: "L", 2: "LAB", 3: "RGB", 4: "CMYK"}
-
- def _open(self):
- (length, offset) = self._find_offset(self.fp)
-
- # Rewrap the open file pointer in something that will
- # convert line endings and decode to latin-1.
- fp = PSFile(self.fp)
-
- # go to offset - start of "%!PS"
- fp.seek(offset)
-
- box = None
-
- self.mode = "RGB"
- self._size = 1, 1 # FIXME: huh?
-
- #
- # Load EPS header
-
- s_raw = fp.readline()
- s = s_raw.strip("\r\n")
-
- while s_raw:
- if s:
- if len(s) > 255:
- raise SyntaxError("not an EPS file")
-
- try:
- m = split.match(s)
- except re.error as e:
- raise SyntaxError("not an EPS file") from e
-
- if m:
- k, v = m.group(1, 2)
- self.info[k] = v
- if k == "BoundingBox":
- try:
- # Note: The DSC spec says that BoundingBox
- # fields should be integers, but some drivers
- # put floating point values there anyway.
- box = [int(float(i)) for i in v.split()]
- self._size = box[2] - box[0], box[3] - box[1]
- self.tile = [
- ("eps", (0, 0) + self.size, offset, (length, box))
- ]
- except Exception:
- pass
-
- else:
- m = field.match(s)
- if m:
- k = m.group(1)
-
- if k == "EndComments":
- break
- if k[:8] == "PS-Adobe":
- self.info[k[:8]] = k[9:]
- else:
- self.info[k] = ""
- elif s[0] == "%":
- # handle non-DSC PostScript comments that some
- # tools mistakenly put in the Comments section
- pass
- else:
- raise OSError("bad EPS header")
-
- s_raw = fp.readline()
- s = s_raw.strip("\r\n")
-
- if s and s[:1] != "%":
- break
-
- #
- # Scan for an "ImageData" descriptor
-
- while s[:1] == "%":
-
- if len(s) > 255:
- raise SyntaxError("not an EPS file")
-
- if s[:11] == "%ImageData:":
- # Encoded bitmapped image.
- x, y, bi, mo = s[11:].split(None, 7)[:4]
-
- if int(bi) != 8:
- break
- try:
- self.mode = self.mode_map[int(mo)]
- except ValueError:
- break
-
- self._size = int(x), int(y)
- return
-
- s = fp.readline().strip("\r\n")
- if not s:
- break
-
- if not box:
- raise OSError("cannot determine EPS bounding box")
-
- def _find_offset(self, fp):
-
- s = fp.read(160)
-
- if s[:4] == b"%!PS":
- # for HEAD without binary preview
- fp.seek(0, io.SEEK_END)
- length = fp.tell()
- offset = 0
- elif i32(s, 0) == 0xC6D3D0C5:
- # FIX for: Some EPS file not handled correctly / issue #302
- # EPS can contain binary data
- # or start directly with latin coding
- # more info see:
- # https://web.archive.org/web/20160528181353/http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf
- offset = i32(s, 4)
- length = i32(s, 8)
- else:
- raise SyntaxError("not an EPS file")
-
- return (length, offset)
-
- def load(self, scale=1, transparency=False):
- # Load EPS via Ghostscript
- if self.tile:
- self.im = Ghostscript(self.tile, self.size, self.fp, scale, transparency)
- self.mode = self.im.mode
- self._size = self.im.size
- self.tile = []
- return Image.Image.load(self)
-
- def load_seek(self, *args, **kwargs):
- # we can't incrementally load, so force ImageFile.parser to
- # use our custom load method by defining this method.
- pass
-
-
-#
-# --------------------------------------------------------------------
-
-
-def _save(im, fp, filename, eps=1):
- """EPS Writer for the Python Imaging Library."""
-
- #
- # make sure image data is available
- im.load()
-
- #
- # determine PostScript image mode
- if im.mode == "L":
- operator = (8, 1, b"image")
- elif im.mode == "RGB":
- operator = (8, 3, b"false 3 colorimage")
- elif im.mode == "CMYK":
- operator = (8, 4, b"false 4 colorimage")
- else:
- raise ValueError("image mode is not supported")
-
- if eps:
- #
- # write EPS header
- fp.write(b"%!PS-Adobe-3.0 EPSF-3.0\n")
- fp.write(b"%%Creator: PIL 0.1 EpsEncode\n")
- # fp.write("%%CreationDate: %s"...)
- fp.write(b"%%%%BoundingBox: 0 0 %d %d\n" % im.size)
- fp.write(b"%%Pages: 1\n")
- fp.write(b"%%EndComments\n")
- fp.write(b"%%Page: 1 1\n")
- fp.write(b"%%ImageData: %d %d " % im.size)
- fp.write(b'%d %d 0 1 1 "%s"\n' % operator)
-
- #
- # image header
- fp.write(b"gsave\n")
- fp.write(b"10 dict begin\n")
- fp.write(b"/buf %d string def\n" % (im.size[0] * operator[1]))
- fp.write(b"%d %d scale\n" % im.size)
- fp.write(b"%d %d 8\n" % im.size) # <= bits
- fp.write(b"[%d 0 0 -%d 0 %d]\n" % (im.size[0], im.size[1], im.size[1]))
- fp.write(b"{ currentfile buf readhexstring pop } bind\n")
- fp.write(operator[2] + b"\n")
- if hasattr(fp, "flush"):
- fp.flush()
-
- ImageFile._save(im, fp, [("eps", (0, 0) + im.size, 0, None)])
-
- fp.write(b"\n%%%%EndBinary\n")
- fp.write(b"grestore end\n")
- if hasattr(fp, "flush"):
- fp.flush()
-
-
-#
-# --------------------------------------------------------------------
-
-
-Image.register_open(EpsImageFile.format, EpsImageFile, _accept)
-
-Image.register_save(EpsImageFile.format, _save)
-
-Image.register_extensions(EpsImageFile.format, [".ps", ".eps"])
-
-Image.register_mime(EpsImageFile.format, "application/postscript")
diff --git a/venv/Lib/site-packages/PIL/ExifTags.py b/venv/Lib/site-packages/PIL/ExifTags.py
deleted file mode 100644
index 7da2dda..0000000
--- a/venv/Lib/site-packages/PIL/ExifTags.py
+++ /dev/null
@@ -1,331 +0,0 @@
-#
-# The Python Imaging Library.
-# $Id$
-#
-# EXIF tags
-#
-# Copyright (c) 2003 by Secret Labs AB
-#
-# See the README file for information on usage and redistribution.
-#
-
-"""
-This module provides constants and clear-text names for various
-well-known EXIF tags.
-"""
-
-
-TAGS = {
- # possibly incomplete
- 0x0001: "InteropIndex",
- 0x000B: "ProcessingSoftware",
- 0x00FE: "NewSubfileType",
- 0x00FF: "SubfileType",
- 0x0100: "ImageWidth",
- 0x0101: "ImageLength",
- 0x0102: "BitsPerSample",
- 0x0103: "Compression",
- 0x0106: "PhotometricInterpretation",
- 0x0107: "Thresholding",
- 0x0108: "CellWidth",
- 0x0109: "CellLength",
- 0x010A: "FillOrder",
- 0x010D: "DocumentName",
- 0x010E: "ImageDescription",
- 0x010F: "Make",
- 0x0110: "Model",
- 0x0111: "StripOffsets",
- 0x0112: "Orientation",
- 0x0115: "SamplesPerPixel",
- 0x0116: "RowsPerStrip",
- 0x0117: "StripByteCounts",
- 0x0118: "MinSampleValue",
- 0x0119: "MaxSampleValue",
- 0x011A: "XResolution",
- 0x011B: "YResolution",
- 0x011C: "PlanarConfiguration",
- 0x011D: "PageName",
- 0x0120: "FreeOffsets",
- 0x0121: "FreeByteCounts",
- 0x0122: "GrayResponseUnit",
- 0x0123: "GrayResponseCurve",
- 0x0124: "T4Options",
- 0x0125: "T6Options",
- 0x0128: "ResolutionUnit",
- 0x0129: "PageNumber",
- 0x012D: "TransferFunction",
- 0x0131: "Software",
- 0x0132: "DateTime",
- 0x013B: "Artist",
- 0x013C: "HostComputer",
- 0x013D: "Predictor",
- 0x013E: "WhitePoint",
- 0x013F: "PrimaryChromaticities",
- 0x0140: "ColorMap",
- 0x0141: "HalftoneHints",
- 0x0142: "TileWidth",
- 0x0143: "TileLength",
- 0x0144: "TileOffsets",
- 0x0145: "TileByteCounts",
- 0x014A: "SubIFDs",
- 0x014C: "InkSet",
- 0x014D: "InkNames",
- 0x014E: "NumberOfInks",
- 0x0150: "DotRange",
- 0x0151: "TargetPrinter",
- 0x0152: "ExtraSamples",
- 0x0153: "SampleFormat",
- 0x0154: "SMinSampleValue",
- 0x0155: "SMaxSampleValue",
- 0x0156: "TransferRange",
- 0x0157: "ClipPath",
- 0x0158: "XClipPathUnits",
- 0x0159: "YClipPathUnits",
- 0x015A: "Indexed",
- 0x015B: "JPEGTables",
- 0x015F: "OPIProxy",
- 0x0200: "JPEGProc",
- 0x0201: "JpegIFOffset",
- 0x0202: "JpegIFByteCount",
- 0x0203: "JpegRestartInterval",
- 0x0205: "JpegLosslessPredictors",
- 0x0206: "JpegPointTransforms",
- 0x0207: "JpegQTables",
- 0x0208: "JpegDCTables",
- 0x0209: "JpegACTables",
- 0x0211: "YCbCrCoefficients",
- 0x0212: "YCbCrSubSampling",
- 0x0213: "YCbCrPositioning",
- 0x0214: "ReferenceBlackWhite",
- 0x02BC: "XMLPacket",
- 0x1000: "RelatedImageFileFormat",
- 0x1001: "RelatedImageWidth",
- 0x1002: "RelatedImageLength",
- 0x4746: "Rating",
- 0x4749: "RatingPercent",
- 0x800D: "ImageID",
- 0x828D: "CFARepeatPatternDim",
- 0x828E: "CFAPattern",
- 0x828F: "BatteryLevel",
- 0x8298: "Copyright",
- 0x829A: "ExposureTime",
- 0x829D: "FNumber",
- 0x83BB: "IPTCNAA",
- 0x8649: "ImageResources",
- 0x8769: "ExifOffset",
- 0x8773: "InterColorProfile",
- 0x8822: "ExposureProgram",
- 0x8824: "SpectralSensitivity",
- 0x8825: "GPSInfo",
- 0x8827: "ISOSpeedRatings",
- 0x8828: "OECF",
- 0x8829: "Interlace",
- 0x882A: "TimeZoneOffset",
- 0x882B: "SelfTimerMode",
- 0x8830: "SensitivityType",
- 0x8831: "StandardOutputSensitivity",
- 0x8832: "RecommendedExposureIndex",
- 0x8833: "ISOSpeed",
- 0x8834: "ISOSpeedLatitudeyyy",
- 0x8835: "ISOSpeedLatitudezzz",
- 0x9000: "ExifVersion",
- 0x9003: "DateTimeOriginal",
- 0x9004: "DateTimeDigitized",
- 0x9010: "OffsetTime",
- 0x9011: "OffsetTimeOriginal",
- 0x9012: "OffsetTimeDigitized",
- 0x9101: "ComponentsConfiguration",
- 0x9102: "CompressedBitsPerPixel",
- 0x9201: "ShutterSpeedValue",
- 0x9202: "ApertureValue",
- 0x9203: "BrightnessValue",
- 0x9204: "ExposureBiasValue",
- 0x9205: "MaxApertureValue",
- 0x9206: "SubjectDistance",
- 0x9207: "MeteringMode",
- 0x9208: "LightSource",
- 0x9209: "Flash",
- 0x920A: "FocalLength",
- 0x920B: "FlashEnergy",
- 0x920C: "SpatialFrequencyResponse",
- 0x920D: "Noise",
- 0x9211: "ImageNumber",
- 0x9212: "SecurityClassification",
- 0x9213: "ImageHistory",
- 0x9214: "SubjectLocation",
- 0x9215: "ExposureIndex",
- 0x9216: "TIFF/EPStandardID",
- 0x927C: "MakerNote",
- 0x9286: "UserComment",
- 0x9290: "SubsecTime",
- 0x9291: "SubsecTimeOriginal",
- 0x9292: "SubsecTimeDigitized",
- 0x9400: "AmbientTemperature",
- 0x9401: "Humidity",
- 0x9402: "Pressure",
- 0x9403: "WaterDepth",
- 0x9404: "Acceleration",
- 0x9405: "CameraElevationAngle",
- 0x9C9B: "XPTitle",
- 0x9C9C: "XPComment",
- 0x9C9D: "XPAuthor",
- 0x9C9E: "XPKeywords",
- 0x9C9F: "XPSubject",
- 0xA000: "FlashPixVersion",
- 0xA001: "ColorSpace",
- 0xA002: "ExifImageWidth",
- 0xA003: "ExifImageHeight",
- 0xA004: "RelatedSoundFile",
- 0xA005: "ExifInteroperabilityOffset",
- 0xA20B: "FlashEnergy",
- 0xA20C: "SpatialFrequencyResponse",
- 0xA20E: "FocalPlaneXResolution",
- 0xA20F: "FocalPlaneYResolution",
- 0xA210: "FocalPlaneResolutionUnit",
- 0xA214: "SubjectLocation",
- 0xA215: "ExposureIndex",
- 0xA217: "SensingMethod",
- 0xA300: "FileSource",
- 0xA301: "SceneType",
- 0xA302: "CFAPattern",
- 0xA401: "CustomRendered",
- 0xA402: "ExposureMode",
- 0xA403: "WhiteBalance",
- 0xA404: "DigitalZoomRatio",
- 0xA405: "FocalLengthIn35mmFilm",
- 0xA406: "SceneCaptureType",
- 0xA407: "GainControl",
- 0xA408: "Contrast",
- 0xA409: "Saturation",
- 0xA40A: "Sharpness",
- 0xA40B: "DeviceSettingDescription",
- 0xA40C: "SubjectDistanceRange",
- 0xA420: "ImageUniqueID",
- 0xA430: "CameraOwnerName",
- 0xA431: "BodySerialNumber",
- 0xA432: "LensSpecification",
- 0xA433: "LensMake",
- 0xA434: "LensModel",
- 0xA435: "LensSerialNumber",
- 0xA460: "CompositeImage",
- 0xA461: "CompositeImageCount",
- 0xA462: "CompositeImageExposureTimes",
- 0xA500: "Gamma",
- 0xC4A5: "PrintImageMatching",
- 0xC612: "DNGVersion",
- 0xC613: "DNGBackwardVersion",
- 0xC614: "UniqueCameraModel",
- 0xC615: "LocalizedCameraModel",
- 0xC616: "CFAPlaneColor",
- 0xC617: "CFALayout",
- 0xC618: "LinearizationTable",
- 0xC619: "BlackLevelRepeatDim",
- 0xC61A: "BlackLevel",
- 0xC61B: "BlackLevelDeltaH",
- 0xC61C: "BlackLevelDeltaV",
- 0xC61D: "WhiteLevel",
- 0xC61E: "DefaultScale",
- 0xC61F: "DefaultCropOrigin",
- 0xC620: "DefaultCropSize",
- 0xC621: "ColorMatrix1",
- 0xC622: "ColorMatrix2",
- 0xC623: "CameraCalibration1",
- 0xC624: "CameraCalibration2",
- 0xC625: "ReductionMatrix1",
- 0xC626: "ReductionMatrix2",
- 0xC627: "AnalogBalance",
- 0xC628: "AsShotNeutral",
- 0xC629: "AsShotWhiteXY",
- 0xC62A: "BaselineExposure",
- 0xC62B: "BaselineNoise",
- 0xC62C: "BaselineSharpness",
- 0xC62D: "BayerGreenSplit",
- 0xC62E: "LinearResponseLimit",
- 0xC62F: "CameraSerialNumber",
- 0xC630: "LensInfo",
- 0xC631: "ChromaBlurRadius",
- 0xC632: "AntiAliasStrength",
- 0xC633: "ShadowScale",
- 0xC634: "DNGPrivateData",
- 0xC635: "MakerNoteSafety",
- 0xC65A: "CalibrationIlluminant1",
- 0xC65B: "CalibrationIlluminant2",
- 0xC65C: "BestQualityScale",
- 0xC65D: "RawDataUniqueID",
- 0xC68B: "OriginalRawFileName",
- 0xC68C: "OriginalRawFileData",
- 0xC68D: "ActiveArea",
- 0xC68E: "MaskedAreas",
- 0xC68F: "AsShotICCProfile",
- 0xC690: "AsShotPreProfileMatrix",
- 0xC691: "CurrentICCProfile",
- 0xC692: "CurrentPreProfileMatrix",
- 0xC6BF: "ColorimetricReference",
- 0xC6F3: "CameraCalibrationSignature",
- 0xC6F4: "ProfileCalibrationSignature",
- 0xC6F6: "AsShotProfileName",
- 0xC6F7: "NoiseReductionApplied",
- 0xC6F8: "ProfileName",
- 0xC6F9: "ProfileHueSatMapDims",
- 0xC6FA: "ProfileHueSatMapData1",
- 0xC6FB: "ProfileHueSatMapData2",
- 0xC6FC: "ProfileToneCurve",
- 0xC6FD: "ProfileEmbedPolicy",
- 0xC6FE: "ProfileCopyright",
- 0xC714: "ForwardMatrix1",
- 0xC715: "ForwardMatrix2",
- 0xC716: "PreviewApplicationName",
- 0xC717: "PreviewApplicationVersion",
- 0xC718: "PreviewSettingsName",
- 0xC719: "PreviewSettingsDigest",
- 0xC71A: "PreviewColorSpace",
- 0xC71B: "PreviewDateTime",
- 0xC71C: "RawImageDigest",
- 0xC71D: "OriginalRawFileDigest",
- 0xC71E: "SubTileBlockSize",
- 0xC71F: "RowInterleaveFactor",
- 0xC725: "ProfileLookTableDims",
- 0xC726: "ProfileLookTableData",
- 0xC740: "OpcodeList1",
- 0xC741: "OpcodeList2",
- 0xC74E: "OpcodeList3",
- 0xC761: "NoiseProfile",
-}
-"""Maps EXIF tags to tag names."""
-
-
-GPSTAGS = {
- 0: "GPSVersionID",
- 1: "GPSLatitudeRef",
- 2: "GPSLatitude",
- 3: "GPSLongitudeRef",
- 4: "GPSLongitude",
- 5: "GPSAltitudeRef",
- 6: "GPSAltitude",
- 7: "GPSTimeStamp",
- 8: "GPSSatellites",
- 9: "GPSStatus",
- 10: "GPSMeasureMode",
- 11: "GPSDOP",
- 12: "GPSSpeedRef",
- 13: "GPSSpeed",
- 14: "GPSTrackRef",
- 15: "GPSTrack",
- 16: "GPSImgDirectionRef",
- 17: "GPSImgDirection",
- 18: "GPSMapDatum",
- 19: "GPSDestLatitudeRef",
- 20: "GPSDestLatitude",
- 21: "GPSDestLongitudeRef",
- 22: "GPSDestLongitude",
- 23: "GPSDestBearingRef",
- 24: "GPSDestBearing",
- 25: "GPSDestDistanceRef",
- 26: "GPSDestDistance",
- 27: "GPSProcessingMethod",
- 28: "GPSAreaInformation",
- 29: "GPSDateStamp",
- 30: "GPSDifferential",
- 31: "GPSHPositioningError",
-}
-"""Maps EXIF GPS tags to tag names."""
diff --git a/venv/Lib/site-packages/PIL/FitsImagePlugin.py b/venv/Lib/site-packages/PIL/FitsImagePlugin.py
deleted file mode 100644
index c16300e..0000000
--- a/venv/Lib/site-packages/PIL/FitsImagePlugin.py
+++ /dev/null
@@ -1,71 +0,0 @@
-#
-# The Python Imaging Library
-# $Id$
-#
-# FITS file handling
-#
-# Copyright (c) 1998-2003 by Fredrik Lundh
-#
-# See the README file for information on usage and redistribution.
-#
-
-import math
-
-from . import Image, ImageFile
-
-
-def _accept(prefix):
- return prefix[:6] == b"SIMPLE"
-
-
-class FitsImageFile(ImageFile.ImageFile):
-
- format = "FITS"
- format_description = "FITS"
-
- def _open(self):
- headers = {}
- while True:
- header = self.fp.read(80)
- if not header:
- raise OSError("Truncated FITS file")
- keyword = header[:8].strip()
- if keyword == b"END":
- break
- value = header[8:].strip()
- if value.startswith(b"="):
- value = value[1:].strip()
- if not headers and (not _accept(keyword) or value != b"T"):
- raise SyntaxError("Not a FITS file")
- headers[keyword] = value
-
- naxis = int(headers[b"NAXIS"])
- if naxis == 0:
- raise ValueError("No image data")
- elif naxis == 1:
- self._size = 1, int(headers[b"NAXIS1"])
- else:
- self._size = int(headers[b"NAXIS1"]), int(headers[b"NAXIS2"])
-
- number_of_bits = int(headers[b"BITPIX"])
- if number_of_bits == 8:
- self.mode = "L"
- elif number_of_bits == 16:
- self.mode = "I"
- # rawmode = "I;16S"
- elif number_of_bits == 32:
- self.mode = "I"
- elif number_of_bits in (-32, -64):
- self.mode = "F"
- # rawmode = "F" if number_of_bits == -32 else "F;64F"
-
- offset = math.ceil(self.fp.tell() / 2880) * 2880
- self.tile = [("raw", (0, 0) + self.size, offset, (self.mode, 0, -1))]
-
-
-# --------------------------------------------------------------------
-# Registry
-
-Image.register_open(FitsImageFile.format, FitsImageFile, _accept)
-
-Image.register_extensions(FitsImageFile.format, [".fit", ".fits"])
diff --git a/venv/Lib/site-packages/PIL/FitsStubImagePlugin.py b/venv/Lib/site-packages/PIL/FitsStubImagePlugin.py
deleted file mode 100644
index 9eed029..0000000
--- a/venv/Lib/site-packages/PIL/FitsStubImagePlugin.py
+++ /dev/null
@@ -1,77 +0,0 @@
-#
-# The Python Imaging Library
-# $Id$
-#
-# FITS stub adapter
-#
-# Copyright (c) 1998-2003 by Fredrik Lundh
-#
-# See the README file for information on usage and redistribution.
-#
-
-import warnings
-
-from . import FitsImagePlugin, Image, ImageFile
-
-_handler = None
-
-
-def register_handler(handler):
- """
- Install application-specific FITS image handler.
-
- :param handler: Handler object.
- """
- global _handler
- _handler = handler
-
- warnings.warn(
- "FitsStubImagePlugin is deprecated and will be removed in Pillow "
- "10 (2023-07-01). FITS images can now be read without a handler through "
- "FitsImagePlugin instead.",
- DeprecationWarning,
- )
-
- # Override FitsImagePlugin with this handler
- # for backwards compatibility
- try:
- Image.ID.remove(FITSStubImageFile.format)
- except ValueError:
- pass
-
- Image.register_open(
- FITSStubImageFile.format, FITSStubImageFile, FitsImagePlugin._accept
- )
-
-
-class FITSStubImageFile(ImageFile.StubImageFile):
-
- format = FitsImagePlugin.FitsImageFile.format
- format_description = FitsImagePlugin.FitsImageFile.format_description
-
- def _open(self):
- offset = self.fp.tell()
-
- im = FitsImagePlugin.FitsImageFile(self.fp)
- self._size = im.size
- self.mode = im.mode
- self.tile = []
-
- self.fp.seek(offset)
-
- loader = self._load()
- if loader:
- loader.open(self)
-
- def _load(self):
- return _handler
-
-
-def _save(im, fp, filename):
- raise OSError("FITS save handler not installed")
-
-
-# --------------------------------------------------------------------
-# Registry
-
-Image.register_save(FITSStubImageFile.format, _save)
diff --git a/venv/Lib/site-packages/PIL/FliImagePlugin.py b/venv/Lib/site-packages/PIL/FliImagePlugin.py
deleted file mode 100644
index ea95033..0000000
--- a/venv/Lib/site-packages/PIL/FliImagePlugin.py
+++ /dev/null
@@ -1,171 +0,0 @@
-#
-# The Python Imaging Library.
-# $Id$
-#
-# FLI/FLC file handling.
-#
-# History:
-# 95-09-01 fl Created
-# 97-01-03 fl Fixed parser, setup decoder tile
-# 98-07-15 fl Renamed offset attribute to avoid name clash
-#
-# Copyright (c) Secret Labs AB 1997-98.
-# Copyright (c) Fredrik Lundh 1995-97.
-#
-# See the README file for information on usage and redistribution.
-#
-
-
-from . import Image, ImageFile, ImagePalette
-from ._binary import i16le as i16
-from ._binary import i32le as i32
-from ._binary import o8
-
-#
-# decoder
-
-
-def _accept(prefix):
- return (
- len(prefix) >= 6
- and i16(prefix, 4) in [0xAF11, 0xAF12]
- and i16(prefix, 14) in [0, 3] # flags
- )
-
-
-##
-# Image plugin for the FLI/FLC animation format. Use the seek
-# method to load individual frames.
-
-
-class FliImageFile(ImageFile.ImageFile):
-
- format = "FLI"
- format_description = "Autodesk FLI/FLC Animation"
- _close_exclusive_fp_after_loading = False
-
- def _open(self):
-
- # HEAD
- s = self.fp.read(128)
- if not (_accept(s) and s[20:22] == b"\x00\x00"):
- raise SyntaxError("not an FLI/FLC file")
-
- # frames
- self.n_frames = i16(s, 6)
- self.is_animated = self.n_frames > 1
-
- # image characteristics
- self.mode = "P"
- self._size = i16(s, 8), i16(s, 10)
-
- # animation speed
- duration = i32(s, 16)
- magic = i16(s, 4)
- if magic == 0xAF11:
- duration = (duration * 1000) // 70
- self.info["duration"] = duration
-
- # look for palette
- palette = [(a, a, a) for a in range(256)]
-
- s = self.fp.read(16)
-
- self.__offset = 128
-
- if i16(s, 4) == 0xF100:
- # prefix chunk; ignore it
- self.__offset = self.__offset + i32(s)
- s = self.fp.read(16)
-
- if i16(s, 4) == 0xF1FA:
- # look for palette chunk
- s = self.fp.read(6)
- if i16(s, 4) == 11:
- self._palette(palette, 2)
- elif i16(s, 4) == 4:
- self._palette(palette, 0)
-
- palette = [o8(r) + o8(g) + o8(b) for (r, g, b) in palette]
- self.palette = ImagePalette.raw("RGB", b"".join(palette))
-
- # set things up to decode first frame
- self.__frame = -1
- self.__fp = self.fp
- self.__rewind = self.fp.tell()
- self.seek(0)
-
- def _palette(self, palette, shift):
- # load palette
-
- i = 0
- for e in range(i16(self.fp.read(2))):
- s = self.fp.read(2)
- i = i + s[0]
- n = s[1]
- if n == 0:
- n = 256
- s = self.fp.read(n * 3)
- for n in range(0, len(s), 3):
- r = s[n] << shift
- g = s[n + 1] << shift
- b = s[n + 2] << shift
- palette[i] = (r, g, b)
- i += 1
-
- def seek(self, frame):
- if not self._seek_check(frame):
- return
- if frame < self.__frame:
- self._seek(0)
-
- for f in range(self.__frame + 1, frame + 1):
- self._seek(f)
-
- def _seek(self, frame):
- if frame == 0:
- self.__frame = -1
- self.__fp.seek(self.__rewind)
- self.__offset = 128
- else:
- # ensure that the previous frame was loaded
- self.load()
-
- if frame != self.__frame + 1:
- raise ValueError(f"cannot seek to frame {frame}")
- self.__frame = frame
-
- # move to next frame
- self.fp = self.__fp
- self.fp.seek(self.__offset)
-
- s = self.fp.read(4)
- if not s:
- raise EOFError
-
- framesize = i32(s)
-
- self.decodermaxblock = framesize
- self.tile = [("fli", (0, 0) + self.size, self.__offset, None)]
-
- self.__offset += framesize
-
- def tell(self):
- return self.__frame
-
- def _close__fp(self):
- try:
- if self.__fp != self.fp:
- self.__fp.close()
- except AttributeError:
- pass
- finally:
- self.__fp = None
-
-
-#
-# registry
-
-Image.register_open(FliImageFile.format, FliImageFile, _accept)
-
-Image.register_extensions(FliImageFile.format, [".fli", ".flc"])
diff --git a/venv/Lib/site-packages/PIL/FontFile.py b/venv/Lib/site-packages/PIL/FontFile.py
deleted file mode 100644
index c5fc80b..0000000
--- a/venv/Lib/site-packages/PIL/FontFile.py
+++ /dev/null
@@ -1,111 +0,0 @@
-#
-# The Python Imaging Library
-# $Id$
-#
-# base class for raster font file parsers
-#
-# history:
-# 1997-06-05 fl created
-# 1997-08-19 fl restrict image width
-#
-# Copyright (c) 1997-1998 by Secret Labs AB
-# Copyright (c) 1997-1998 by Fredrik Lundh
-#
-# See the README file for information on usage and redistribution.
-#
-
-
-import os
-
-from . import Image, _binary
-
-WIDTH = 800
-
-
-def puti16(fp, values):
- """Write network order (big-endian) 16-bit sequence"""
- for v in values:
- if v < 0:
- v += 65536
- fp.write(_binary.o16be(v))
-
-
-class FontFile:
- """Base class for raster font file handlers."""
-
- bitmap = None
-
- def __init__(self):
-
- self.info = {}
- self.glyph = [None] * 256
-
- def __getitem__(self, ix):
- return self.glyph[ix]
-
- def compile(self):
- """Create metrics and bitmap"""
-
- if self.bitmap:
- return
-
- # create bitmap large enough to hold all data
- h = w = maxwidth = 0
- lines = 1
- for glyph in self:
- if glyph:
- d, dst, src, im = glyph
- h = max(h, src[3] - src[1])
- w = w + (src[2] - src[0])
- if w > WIDTH:
- lines += 1
- w = src[2] - src[0]
- maxwidth = max(maxwidth, w)
-
- xsize = maxwidth
- ysize = lines * h
-
- if xsize == 0 and ysize == 0:
- return ""
-
- self.ysize = h
-
- # paste glyphs into bitmap
- self.bitmap = Image.new("1", (xsize, ysize))
- self.metrics = [None] * 256
- x = y = 0
- for i in range(256):
- glyph = self[i]
- if glyph:
- d, dst, src, im = glyph
- xx = src[2] - src[0]
- # yy = src[3] - src[1]
- x0, y0 = x, y
- x = x + xx
- if x > WIDTH:
- x, y = 0, y + h
- x0, y0 = x, y
- x = xx
- s = src[0] + x0, src[1] + y0, src[2] + x0, src[3] + y0
- self.bitmap.paste(im.crop(src), s)
- self.metrics[i] = d, dst, s
-
- def save(self, filename):
- """Save font"""
-
- self.compile()
-
- # font data
- self.bitmap.save(os.path.splitext(filename)[0] + ".pbm", "PNG")
-
- # font metrics
- with open(os.path.splitext(filename)[0] + ".pil", "wb") as fp:
- fp.write(b"PILfont\n")
- fp.write(f";;;;;;{self.ysize};\n".encode("ascii")) # HACK!!!
- fp.write(b"DATA\n")
- for id in range(256):
- m = self.metrics[id]
- if not m:
- puti16(fp, [0] * 10)
- else:
- puti16(fp, m[0] + m[1] + m[2])
diff --git a/venv/Lib/site-packages/PIL/FpxImagePlugin.py b/venv/Lib/site-packages/PIL/FpxImagePlugin.py
deleted file mode 100644
index 5e38546..0000000
--- a/venv/Lib/site-packages/PIL/FpxImagePlugin.py
+++ /dev/null
@@ -1,242 +0,0 @@
-#
-# THIS IS WORK IN PROGRESS
-#
-# The Python Imaging Library.
-# $Id$
-#
-# FlashPix support for PIL
-#
-# History:
-# 97-01-25 fl Created (reads uncompressed RGB images only)
-#
-# Copyright (c) Secret Labs AB 1997.
-# Copyright (c) Fredrik Lundh 1997.
-#
-# See the README file for information on usage and redistribution.
-#
-import olefile
-
-from . import Image, ImageFile
-from ._binary import i32le as i32
-
-# we map from colour field tuples to (mode, rawmode) descriptors
-MODES = {
- # opacity
- (0x00007FFE): ("A", "L"),
- # monochrome
- (0x00010000,): ("L", "L"),
- (0x00018000, 0x00017FFE): ("RGBA", "LA"),
- # photo YCC
- (0x00020000, 0x00020001, 0x00020002): ("RGB", "YCC;P"),
- (0x00028000, 0x00028001, 0x00028002, 0x00027FFE): ("RGBA", "YCCA;P"),
- # standard RGB (NIFRGB)
- (0x00030000, 0x00030001, 0x00030002): ("RGB", "RGB"),
- (0x00038000, 0x00038001, 0x00038002, 0x00037FFE): ("RGBA", "RGBA"),
-}
-
-
-#
-# --------------------------------------------------------------------
-
-
-def _accept(prefix):
- return prefix[:8] == olefile.MAGIC
-
-
-##
-# Image plugin for the FlashPix images.
-
-
-class FpxImageFile(ImageFile.ImageFile):
-
- format = "FPX"
- format_description = "FlashPix"
-
- def _open(self):
- #
- # read the OLE directory and see if this is a likely
- # to be a FlashPix file
-
- try:
- self.ole = olefile.OleFileIO(self.fp)
- except OSError as e:
- raise SyntaxError("not an FPX file; invalid OLE file") from e
-
- if self.ole.root.clsid != "56616700-C154-11CE-8553-00AA00A1F95B":
- raise SyntaxError("not an FPX file; bad root CLSID")
-
- self._open_index(1)
-
- def _open_index(self, index=1):
- #
- # get the Image Contents Property Set
-
- prop = self.ole.getproperties(
- [f"Data Object Store {index:06d}", "\005Image Contents"]
- )
-
- # size (highest resolution)
-
- self._size = prop[0x1000002], prop[0x1000003]
-
- size = max(self.size)
- i = 1
- while size > 64:
- size = size / 2
- i += 1
- self.maxid = i - 1
-
- # mode. instead of using a single field for this, flashpix
- # requires you to specify the mode for each channel in each
- # resolution subimage, and leaves it to the decoder to make
- # sure that they all match. for now, we'll cheat and assume
- # that this is always the case.
-
- id = self.maxid << 16
-
- s = prop[0x2000002 | id]
-
- colors = []
- bands = i32(s, 4)
- if bands > 4:
- raise OSError("Invalid number of bands")
- for i in range(bands):
- # note: for now, we ignore the "uncalibrated" flag
- colors.append(i32(s, 8 + i * 4) & 0x7FFFFFFF)
-
- self.mode, self.rawmode = MODES[tuple(colors)]
-
- # load JPEG tables, if any
- self.jpeg = {}
- for i in range(256):
- id = 0x3000001 | (i << 16)
- if id in prop:
- self.jpeg[i] = prop[id]
-
- self._open_subimage(1, self.maxid)
-
- def _open_subimage(self, index=1, subimage=0):
- #
- # setup tile descriptors for a given subimage
-
- stream = [
- f"Data Object Store {index:06d}",
- f"Resolution {subimage:04d}",
- "Subimage 0000 Header",
- ]
-
- fp = self.ole.openstream(stream)
-
- # skip prefix
- fp.read(28)
-
- # header stream
- s = fp.read(36)
-
- size = i32(s, 4), i32(s, 8)
- # tilecount = i32(s, 12)
- tilesize = i32(s, 16), i32(s, 20)
- # channels = i32(s, 24)
- offset = i32(s, 28)
- length = i32(s, 32)
-
- if size != self.size:
- raise OSError("subimage mismatch")
-
- # get tile descriptors
- fp.seek(28 + offset)
- s = fp.read(i32(s, 12) * length)
-
- x = y = 0
- xsize, ysize = size
- xtile, ytile = tilesize
- self.tile = []
-
- for i in range(0, len(s), length):
-
- compression = i32(s, i + 8)
-
- if compression == 0:
- self.tile.append(
- (
- "raw",
- (x, y, x + xtile, y + ytile),
- i32(s, i) + 28,
- (self.rawmode),
- )
- )
-
- elif compression == 1:
-
- # FIXME: the fill decoder is not implemented
- self.tile.append(
- (
- "fill",
- (x, y, x + xtile, y + ytile),
- i32(s, i) + 28,
- (self.rawmode, s[12:16]),
- )
- )
-
- elif compression == 2:
-
- internal_color_conversion = s[14]
- jpeg_tables = s[15]
- rawmode = self.rawmode
-
- if internal_color_conversion:
- # The image is stored as usual (usually YCbCr).
- if rawmode == "RGBA":
- # For "RGBA", data is stored as YCbCrA based on
- # negative RGB. The following trick works around
- # this problem :
- jpegmode, rawmode = "YCbCrK", "CMYK"
- else:
- jpegmode = None # let the decoder decide
-
- else:
- # The image is stored as defined by rawmode
- jpegmode = rawmode
-
- self.tile.append(
- (
- "jpeg",
- (x, y, x + xtile, y + ytile),
- i32(s, i) + 28,
- (rawmode, jpegmode),
- )
- )
-
- # FIXME: jpeg tables are tile dependent; the prefix
- # data must be placed in the tile descriptor itself!
-
- if jpeg_tables:
- self.tile_prefix = self.jpeg[jpeg_tables]
-
- else:
- raise OSError("unknown/invalid compression")
-
- x = x + xtile
- if x >= xsize:
- x, y = 0, y + ytile
- if y >= ysize:
- break # isn't really required
-
- self.stream = stream
- self.fp = None
-
- def load(self):
-
- if not self.fp:
- self.fp = self.ole.openstream(self.stream[:2] + ["Subimage 0000 Data"])
-
- return ImageFile.ImageFile.load(self)
-
-
-#
-# --------------------------------------------------------------------
-
-
-Image.register_open(FpxImageFile.format, FpxImageFile, _accept)
-
-Image.register_extension(FpxImageFile.format, ".fpx")
diff --git a/venv/Lib/site-packages/PIL/FtexImagePlugin.py b/venv/Lib/site-packages/PIL/FtexImagePlugin.py
deleted file mode 100644
index 55d28e1..0000000
--- a/venv/Lib/site-packages/PIL/FtexImagePlugin.py
+++ /dev/null
@@ -1,135 +0,0 @@
-"""
-A Pillow loader for .ftc and .ftu files (FTEX)
-Jerome Leclanche
-
-The contents of this file are hereby released in the public domain (CC0)
-Full text of the CC0 license:
- https://creativecommons.org/publicdomain/zero/1.0/
-
-Independence War 2: Edge Of Chaos - Texture File Format - 16 October 2001
-
-The textures used for 3D objects in Independence War 2: Edge Of Chaos are in a
-packed custom format called FTEX. This file format uses file extensions FTC
-and FTU.
-* FTC files are compressed textures (using standard texture compression).
-* FTU files are not compressed.
-Texture File Format
-The FTC and FTU texture files both use the same format. This
-has the following structure:
-{header}
-{format_directory}
-{data}
-Where:
-{header} = {
- u32:magic,
- u32:version,
- u32:width,
- u32:height,
- u32:mipmap_count,
- u32:format_count
-}
-
-* The "magic" number is "FTEX".
-* "width" and "height" are the dimensions of the texture.
-* "mipmap_count" is the number of mipmaps in the texture.
-* "format_count" is the number of texture formats (different versions of the
-same texture) in this file.
-
-{format_directory} = format_count * { u32:format, u32:where }
-
-The format value is 0 for DXT1 compressed textures and 1 for 24-bit RGB
-uncompressed textures.
-The texture data for a format starts at the position "where" in the file.
-
-Each set of texture data in the file has the following structure:
-{data} = format_count * { u32:mipmap_size, mipmap_size * { u8 } }
-* "mipmap_size" is the number of bytes in that mip level. For compressed
-textures this is the size of the texture data compressed with DXT1. For 24 bit
-uncompressed textures, this is 3 * width * height. Following this are the image
-bytes for that mipmap level.
-
-Note: All data is stored in little-Endian (Intel) byte order.
-"""
-
-import struct
-import warnings
-from enum import IntEnum
-from io import BytesIO
-
-from . import Image, ImageFile
-
-MAGIC = b"FTEX"
-
-
-class Format(IntEnum):
- DXT1 = 0
- UNCOMPRESSED = 1
-
-
-def __getattr__(name):
- deprecated = "deprecated and will be removed in Pillow 10 (2023-07-01). "
- for enum, prefix in {Format: "FORMAT_"}.items():
- if name.startswith(prefix):
- name = name[len(prefix) :]
- if name in enum.__members__:
- warnings.warn(
- prefix
- + name
- + " is "
- + deprecated
- + "Use "
- + enum.__name__
- + "."
- + name
- + " instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- return enum[name]
- raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
-
-
-class FtexImageFile(ImageFile.ImageFile):
- format = "FTEX"
- format_description = "Texture File Format (IW2:EOC)"
-
- def _open(self):
- if not _accept(self.fp.read(4)):
- raise SyntaxError("not an FTEX file")
- struct.unpack("= 8 and i32(prefix, 0) >= 20 and i32(prefix, 4) in (1, 2)
-
-
-##
-# Image plugin for the GIMP brush format.
-
-
-class GbrImageFile(ImageFile.ImageFile):
-
- format = "GBR"
- format_description = "GIMP brush file"
-
- def _open(self):
- header_size = i32(self.fp.read(4))
- if header_size < 20:
- raise SyntaxError("not a GIMP brush")
- version = i32(self.fp.read(4))
- if version not in (1, 2):
- raise SyntaxError(f"Unsupported GIMP brush version: {version}")
-
- width = i32(self.fp.read(4))
- height = i32(self.fp.read(4))
- color_depth = i32(self.fp.read(4))
- if width <= 0 or height <= 0:
- raise SyntaxError("not a GIMP brush")
- if color_depth not in (1, 4):
- raise SyntaxError(f"Unsupported GIMP brush color depth: {color_depth}")
-
- if version == 1:
- comment_length = header_size - 20
- else:
- comment_length = header_size - 28
- magic_number = self.fp.read(4)
- if magic_number != b"GIMP":
- raise SyntaxError("not a GIMP brush, bad magic number")
- self.info["spacing"] = i32(self.fp.read(4))
-
- comment = self.fp.read(comment_length)[:-1]
-
- if color_depth == 1:
- self.mode = "L"
- else:
- self.mode = "RGBA"
-
- self._size = width, height
-
- self.info["comment"] = comment
-
- # Image might not be small
- Image._decompression_bomb_check(self.size)
-
- # Data is an uncompressed block of w * h * bytes/pixel
- self._data_size = width * height * color_depth
-
- def load(self):
- if not self.im:
- self.im = Image.core.new(self.mode, self.size)
- self.frombytes(self.fp.read(self._data_size))
- return Image.Image.load(self)
-
-
-#
-# registry
-
-
-Image.register_open(GbrImageFile.format, GbrImageFile, _accept)
-Image.register_extension(GbrImageFile.format, ".gbr")
diff --git a/venv/Lib/site-packages/PIL/GdImageFile.py b/venv/Lib/site-packages/PIL/GdImageFile.py
deleted file mode 100644
index 9c34ada..0000000
--- a/venv/Lib/site-packages/PIL/GdImageFile.py
+++ /dev/null
@@ -1,90 +0,0 @@
-#
-# The Python Imaging Library.
-# $Id$
-#
-# GD file handling
-#
-# History:
-# 1996-04-12 fl Created
-#
-# Copyright (c) 1997 by Secret Labs AB.
-# Copyright (c) 1996 by Fredrik Lundh.
-#
-# See the README file for information on usage and redistribution.
-#
-
-
-"""
-.. note::
- This format cannot be automatically recognized, so the
- class is not registered for use with :py:func:`PIL.Image.open()`. To open a
- gd file, use the :py:func:`PIL.GdImageFile.open()` function instead.
-
-.. warning::
- THE GD FORMAT IS NOT DESIGNED FOR DATA INTERCHANGE. This
- implementation is provided for convenience and demonstrational
- purposes only.
-"""
-
-
-from . import ImageFile, ImagePalette, UnidentifiedImageError
-from ._binary import i16be as i16
-from ._binary import i32be as i32
-
-
-class GdImageFile(ImageFile.ImageFile):
- """
- Image plugin for the GD uncompressed format. Note that this format
- is not supported by the standard :py:func:`PIL.Image.open()` function. To use
- this plugin, you have to import the :py:mod:`PIL.GdImageFile` module and
- use the :py:func:`PIL.GdImageFile.open()` function.
- """
-
- format = "GD"
- format_description = "GD uncompressed images"
-
- def _open(self):
-
- # Header
- s = self.fp.read(1037)
-
- if not i16(s) in [65534, 65535]:
- raise SyntaxError("Not a valid GD 2.x .gd file")
-
- self.mode = "L" # FIXME: "P"
- self._size = i16(s, 2), i16(s, 4)
-
- trueColor = s[6]
- trueColorOffset = 2 if trueColor else 0
-
- # transparency index
- tindex = i32(s, 7 + trueColorOffset)
- if tindex < 256:
- self.info["transparency"] = tindex
-
- self.palette = ImagePalette.raw(
- "XBGR", s[7 + trueColorOffset + 4 : 7 + trueColorOffset + 4 + 256 * 4]
- )
-
- self.tile = [
- ("raw", (0, 0) + self.size, 7 + trueColorOffset + 4 + 256 * 4, ("L", 0, 1))
- ]
-
-
-def open(fp, mode="r"):
- """
- Load texture from a GD image file.
-
- :param filename: GD file name, or an opened file handle.
- :param mode: Optional mode. In this version, if the mode argument
- is given, it must be "r".
- :returns: An image instance.
- :raises OSError: If the image could not be read.
- """
- if mode != "r":
- raise ValueError("bad mode")
-
- try:
- return GdImageFile(fp)
- except SyntaxError as e:
- raise UnidentifiedImageError("cannot identify this image file") from e
diff --git a/venv/Lib/site-packages/PIL/GifImagePlugin.py b/venv/Lib/site-packages/PIL/GifImagePlugin.py
deleted file mode 100644
index b798bb9..0000000
--- a/venv/Lib/site-packages/PIL/GifImagePlugin.py
+++ /dev/null
@@ -1,1038 +0,0 @@
-#
-# The Python Imaging Library.
-# $Id$
-#
-# GIF file handling
-#
-# History:
-# 1995-09-01 fl Created
-# 1996-12-14 fl Added interlace support
-# 1996-12-30 fl Added animation support
-# 1997-01-05 fl Added write support, fixed local colour map bug
-# 1997-02-23 fl Make sure to load raster data in getdata()
-# 1997-07-05 fl Support external decoder (0.4)
-# 1998-07-09 fl Handle all modes when saving (0.5)
-# 1998-07-15 fl Renamed offset attribute to avoid name clash
-# 2001-04-16 fl Added rewind support (seek to frame 0) (0.6)
-# 2001-04-17 fl Added palette optimization (0.7)
-# 2002-06-06 fl Added transparency support for save (0.8)
-# 2004-02-24 fl Disable interlacing for small images
-#
-# Copyright (c) 1997-2004 by Secret Labs AB
-# Copyright (c) 1995-2004 by Fredrik Lundh
-#
-# See the README file for information on usage and redistribution.
-#
-
-import itertools
-import math
-import os
-import subprocess
-from enum import IntEnum
-
-from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence
-from ._binary import i16le as i16
-from ._binary import o8
-from ._binary import o16le as o16
-
-
-class LoadingStrategy(IntEnum):
- """.. versionadded:: 9.1.0"""
-
- RGB_AFTER_FIRST = 0
- RGB_AFTER_DIFFERENT_PALETTE_ONLY = 1
- RGB_ALWAYS = 2
-
-
-#: .. versionadded:: 9.1.0
-LOADING_STRATEGY = LoadingStrategy.RGB_AFTER_FIRST
-
-# --------------------------------------------------------------------
-# Identify/read GIF files
-
-
-def _accept(prefix):
- return prefix[:6] in [b"GIF87a", b"GIF89a"]
-
-
-##
-# Image plugin for GIF images. This plugin supports both GIF87 and
-# GIF89 images.
-
-
-class GifImageFile(ImageFile.ImageFile):
-
- format = "GIF"
- format_description = "Compuserve GIF"
- _close_exclusive_fp_after_loading = False
-
- global_palette = None
-
- def data(self):
- s = self.fp.read(1)
- if s and s[0]:
- return self.fp.read(s[0])
- return None
-
- def _is_palette_needed(self, p):
- for i in range(0, len(p), 3):
- if not (i // 3 == p[i] == p[i + 1] == p[i + 2]):
- return True
- return False
-
- def _open(self):
-
- # Screen
- s = self.fp.read(13)
- if not _accept(s):
- raise SyntaxError("not a GIF file")
-
- self.info["version"] = s[:6]
- self._size = i16(s, 6), i16(s, 8)
- self.tile = []
- flags = s[10]
- bits = (flags & 7) + 1
-
- if flags & 128:
- # get global palette
- self.info["background"] = s[11]
- # check if palette contains colour indices
- p = self.fp.read(3 << bits)
- if self._is_palette_needed(p):
- p = ImagePalette.raw("RGB", p)
- self.global_palette = self.palette = p
-
- self.__fp = self.fp # FIXME: hack
- self.__rewind = self.fp.tell()
- self._n_frames = None
- self._is_animated = None
- self._seek(0) # get ready to read first frame
-
- @property
- def n_frames(self):
- if self._n_frames is None:
- current = self.tell()
- try:
- while True:
- self._seek(self.tell() + 1, False)
- except EOFError:
- self._n_frames = self.tell() + 1
- self.seek(current)
- return self._n_frames
-
- @property
- def is_animated(self):
- if self._is_animated is None:
- if self._n_frames is not None:
- self._is_animated = self._n_frames != 1
- else:
- current = self.tell()
- if current:
- self._is_animated = True
- else:
- try:
- self._seek(1, False)
- self._is_animated = True
- except EOFError:
- self._is_animated = False
-
- self.seek(current)
- return self._is_animated
-
- def seek(self, frame):
- if not self._seek_check(frame):
- return
- if frame < self.__frame:
- self.im = None
- self._seek(0)
-
- last_frame = self.__frame
- for f in range(self.__frame + 1, frame + 1):
- try:
- self._seek(f)
- except EOFError as e:
- self.seek(last_frame)
- raise EOFError("no more images in GIF file") from e
-
- def _seek(self, frame, update_image=True):
-
- if frame == 0:
- # rewind
- self.__offset = 0
- self.dispose = None
- self.__frame = -1
- self.__fp.seek(self.__rewind)
- self.disposal_method = 0
- else:
- # ensure that the previous frame was loaded
- if self.tile and update_image:
- self.load()
-
- if frame != self.__frame + 1:
- raise ValueError(f"cannot seek to frame {frame}")
-
- self.fp = self.__fp
- if self.__offset:
- # backup to last frame
- self.fp.seek(self.__offset)
- while self.data():
- pass
- self.__offset = 0
-
- s = self.fp.read(1)
- if not s or s == b";":
- raise EOFError
-
- self.__frame = frame
-
- self.tile = []
-
- palette = None
-
- info = {}
- frame_transparency = None
- interlace = None
- frame_dispose_extent = None
- while True:
-
- if not s:
- s = self.fp.read(1)
- if not s or s == b";":
- break
-
- elif s == b"!":
- #
- # extensions
- #
- s = self.fp.read(1)
- block = self.data()
- if s[0] == 249:
- #
- # graphic control extension
- #
- flags = block[0]
- if flags & 1:
- frame_transparency = block[3]
- info["duration"] = i16(block, 1) * 10
-
- # disposal method - find the value of bits 4 - 6
- dispose_bits = 0b00011100 & flags
- dispose_bits = dispose_bits >> 2
- if dispose_bits:
- # only set the dispose if it is not
- # unspecified. I'm not sure if this is
- # correct, but it seems to prevent the last
- # frame from looking odd for some animations
- self.disposal_method = dispose_bits
- elif s[0] == 254:
- #
- # comment extension
- #
- while block:
- if "comment" in info:
- info["comment"] += block
- else:
- info["comment"] = block
- block = self.data()
- s = None
- continue
- elif s[0] == 255:
- #
- # application extension
- #
- info["extension"] = block, self.fp.tell()
- if block[:11] == b"NETSCAPE2.0":
- block = self.data()
- if len(block) >= 3 and block[0] == 1:
- info["loop"] = i16(block, 1)
- while self.data():
- pass
-
- elif s == b",":
- #
- # local image
- #
- s = self.fp.read(9)
-
- # extent
- x0, y0 = i16(s, 0), i16(s, 2)
- x1, y1 = x0 + i16(s, 4), y0 + i16(s, 6)
- if (x1 > self.size[0] or y1 > self.size[1]) and update_image:
- self._size = max(x1, self.size[0]), max(y1, self.size[1])
- frame_dispose_extent = x0, y0, x1, y1
- flags = s[8]
-
- interlace = (flags & 64) != 0
-
- if flags & 128:
- bits = (flags & 7) + 1
- p = self.fp.read(3 << bits)
- if self._is_palette_needed(p):
- palette = ImagePalette.raw("RGB", p)
-
- # image data
- bits = self.fp.read(1)[0]
- self.__offset = self.fp.tell()
- break
-
- else:
- pass
- # raise OSError, "illegal GIF tag `%x`" % s[0]
- s = None
-
- if interlace is None:
- # self.__fp = None
- raise EOFError
- if not update_image:
- return
-
- if self.dispose:
- self.im.paste(self.dispose, self.dispose_extent)
-
- self._frame_palette = palette or self.global_palette
- if frame == 0:
- if self._frame_palette:
- self.mode = (
- "RGB" if LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS else "P"
- )
- else:
- self.mode = "L"
-
- if not palette and self.global_palette:
- from copy import copy
-
- palette = copy(self.global_palette)
- self.palette = palette
- else:
- self._frame_transparency = frame_transparency
- if self.mode == "P":
- if (
- LOADING_STRATEGY != LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY
- or palette
- ):
- self.pyaccess = None
- if "transparency" in self.info:
- self.im.putpalettealpha(self.info["transparency"], 0)
- self.im = self.im.convert("RGBA", Image.Dither.FLOYDSTEINBERG)
- self.mode = "RGBA"
- del self.info["transparency"]
- else:
- self.mode = "RGB"
- self.im = self.im.convert("RGB", Image.Dither.FLOYDSTEINBERG)
-
- def _rgb(color):
- if self._frame_palette:
- color = tuple(self._frame_palette.palette[color * 3 : color * 3 + 3])
- else:
- color = (color, color, color)
- return color
-
- self.dispose_extent = frame_dispose_extent
- try:
- if self.disposal_method < 2:
- # do not dispose or none specified
- self.dispose = None
- elif self.disposal_method == 2:
- # replace with background colour
-
- # only dispose the extent in this frame
- x0, y0, x1, y1 = self.dispose_extent
- dispose_size = (x1 - x0, y1 - y0)
-
- Image._decompression_bomb_check(dispose_size)
-
- # by convention, attempt to use transparency first
- dispose_mode = "P"
- color = self.info.get("transparency", frame_transparency)
- if color is not None:
- if self.mode in ("RGB", "RGBA"):
- dispose_mode = "RGBA"
- color = _rgb(color) + (0,)
- else:
- color = self.info.get("background", 0)
- if self.mode in ("RGB", "RGBA"):
- dispose_mode = "RGB"
- color = _rgb(color)
- self.dispose = Image.core.fill(dispose_mode, dispose_size, color)
- else:
- # replace with previous contents
- if self.im is not None:
- # only dispose the extent in this frame
- self.dispose = self._crop(self.im, self.dispose_extent)
- elif frame_transparency is not None:
- x0, y0, x1, y1 = self.dispose_extent
- dispose_size = (x1 - x0, y1 - y0)
-
- Image._decompression_bomb_check(dispose_size)
- dispose_mode = "P"
- color = frame_transparency
- if self.mode in ("RGB", "RGBA"):
- dispose_mode = "RGBA"
- color = _rgb(frame_transparency) + (0,)
- self.dispose = Image.core.fill(dispose_mode, dispose_size, color)
- except AttributeError:
- pass
-
- if interlace is not None:
- transparency = -1
- if frame_transparency is not None:
- if frame == 0:
- self.info["transparency"] = frame_transparency
- elif self.mode not in ("RGB", "RGBA"):
- transparency = frame_transparency
- self.tile = [
- (
- "gif",
- (x0, y0, x1, y1),
- self.__offset,
- (bits, interlace, transparency),
- )
- ]
-
- for k in ["duration", "comment", "extension", "loop"]:
- if k in info:
- self.info[k] = info[k]
- elif k in self.info:
- del self.info[k]
-
- def load_prepare(self):
- temp_mode = "P" if self._frame_palette else "L"
- self._prev_im = None
- if self.__frame == 0:
- if "transparency" in self.info:
- self.im = Image.core.fill(
- temp_mode, self.size, self.info["transparency"]
- )
- elif self.mode in ("RGB", "RGBA"):
- self._prev_im = self.im
- if self._frame_palette:
- self.im = Image.core.fill("P", self.size, self._frame_transparency or 0)
- self.im.putpalette(*self._frame_palette.getdata())
- else:
- self.im = None
- self.mode = temp_mode
- self._frame_palette = None
-
- super().load_prepare()
-
- def load_end(self):
- if self.__frame == 0:
- if self.mode == "P" and LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS:
- self.mode = "RGB"
- self.im = self.im.convert("RGB", Image.Dither.FLOYDSTEINBERG)
- return
- if self.mode == "P" and self._prev_im:
- if self._frame_transparency is not None:
- self.im.putpalettealpha(self._frame_transparency, 0)
- frame_im = self.im.convert("RGBA")
- else:
- frame_im = self.im.convert("RGB")
- else:
- if not self._prev_im:
- return
- frame_im = self.im
- frame_im = self._crop(frame_im, self.dispose_extent)
-
- self.im = self._prev_im
- self.mode = self.im.mode
- if frame_im.mode == "RGBA":
- self.im.paste(frame_im, self.dispose_extent, frame_im)
- else:
- self.im.paste(frame_im, self.dispose_extent)
-
- def tell(self):
- return self.__frame
-
- def _close__fp(self):
- try:
- if self.__fp != self.fp:
- self.__fp.close()
- except AttributeError:
- pass
- finally:
- self.__fp = None
-
-
-# --------------------------------------------------------------------
-# Write GIF files
-
-
-RAWMODE = {"1": "L", "L": "L", "P": "P"}
-
-
-def _normalize_mode(im):
- """
- Takes an image (or frame), returns an image in a mode that is appropriate
- for saving in a Gif.
-
- It may return the original image, or it may return an image converted to
- palette or 'L' mode.
-
- :param im: Image object
- :returns: Image object
- """
- if im.mode in RAWMODE:
- im.load()
- return im
- if Image.getmodebase(im.mode) == "RGB":
- im = im.convert("P", palette=Image.Palette.ADAPTIVE)
- if im.palette.mode == "RGBA":
- for rgba in im.palette.colors.keys():
- if rgba[3] == 0:
- im.info["transparency"] = im.palette.colors[rgba]
- break
- return im
- return im.convert("L")
-
-
-def _normalize_palette(im, palette, info):
- """
- Normalizes the palette for image.
- - Sets the palette to the incoming palette, if provided.
- - Ensures that there's a palette for L mode images
- - Optimizes the palette if necessary/desired.
-
- :param im: Image object
- :param palette: bytes object containing the source palette, or ....
- :param info: encoderinfo
- :returns: Image object
- """
- source_palette = None
- if palette:
- # a bytes palette
- if isinstance(palette, (bytes, bytearray, list)):
- source_palette = bytearray(palette[:768])
- if isinstance(palette, ImagePalette.ImagePalette):
- source_palette = bytearray(palette.palette)
-
- if im.mode == "P":
- if not source_palette:
- source_palette = im.im.getpalette("RGB")[:768]
- else: # L-mode
- if not source_palette:
- source_palette = bytearray(i // 3 for i in range(768))
- im.palette = ImagePalette.ImagePalette("RGB", palette=source_palette)
-
- if palette:
- used_palette_colors = []
- for i in range(0, len(source_palette), 3):
- source_color = tuple(source_palette[i : i + 3])
- try:
- index = im.palette.colors[source_color]
- except KeyError:
- index = None
- used_palette_colors.append(index)
- for i, index in enumerate(used_palette_colors):
- if index is None:
- for j in range(len(used_palette_colors)):
- if j not in used_palette_colors:
- used_palette_colors[i] = j
- break
- im = im.remap_palette(used_palette_colors)
- else:
- used_palette_colors = _get_optimize(im, info)
- if used_palette_colors is not None:
- return im.remap_palette(used_palette_colors, source_palette)
-
- im.palette.palette = source_palette
- return im
-
-
-def _write_single_frame(im, fp, palette):
- im_out = _normalize_mode(im)
- for k, v in im_out.info.items():
- im.encoderinfo.setdefault(k, v)
- im_out = _normalize_palette(im_out, palette, im.encoderinfo)
-
- for s in _get_global_header(im_out, im.encoderinfo):
- fp.write(s)
-
- # local image header
- flags = 0
- if get_interlace(im):
- flags = flags | 64
- _write_local_header(fp, im, (0, 0), flags)
-
- im_out.encoderconfig = (8, get_interlace(im))
- ImageFile._save(im_out, fp, [("gif", (0, 0) + im.size, 0, RAWMODE[im_out.mode])])
-
- fp.write(b"\0") # end of image data
-
-
-def _write_multiple_frames(im, fp, palette):
-
- duration = im.encoderinfo.get("duration", im.info.get("duration"))
- disposal = im.encoderinfo.get("disposal", im.info.get("disposal"))
-
- im_frames = []
- frame_count = 0
- background_im = None
- for imSequence in itertools.chain([im], im.encoderinfo.get("append_images", [])):
- for im_frame in ImageSequence.Iterator(imSequence):
- # a copy is required here since seek can still mutate the image
- im_frame = _normalize_mode(im_frame.copy())
- if frame_count == 0:
- for k, v in im_frame.info.items():
- im.encoderinfo.setdefault(k, v)
- im_frame = _normalize_palette(im_frame, palette, im.encoderinfo)
-
- encoderinfo = im.encoderinfo.copy()
- if isinstance(duration, (list, tuple)):
- encoderinfo["duration"] = duration[frame_count]
- if isinstance(disposal, (list, tuple)):
- encoderinfo["disposal"] = disposal[frame_count]
- frame_count += 1
-
- if im_frames:
- # delta frame
- previous = im_frames[-1]
- if encoderinfo.get("disposal") == 2:
- if background_im is None:
- color = im.encoderinfo.get(
- "transparency", im.info.get("transparency", (0, 0, 0))
- )
- background = _get_background(im_frame, color)
- background_im = Image.new("P", im_frame.size, background)
- background_im.putpalette(im_frames[0]["im"].palette)
- base_im = background_im
- else:
- base_im = previous["im"]
- if _get_palette_bytes(im_frame) == _get_palette_bytes(base_im):
- delta = ImageChops.subtract_modulo(im_frame, base_im)
- else:
- delta = ImageChops.subtract_modulo(
- im_frame.convert("RGB"), base_im.convert("RGB")
- )
- bbox = delta.getbbox()
- if not bbox:
- # This frame is identical to the previous frame
- if duration:
- previous["encoderinfo"]["duration"] += encoderinfo["duration"]
- continue
- else:
- bbox = None
- im_frames.append({"im": im_frame, "bbox": bbox, "encoderinfo": encoderinfo})
-
- if len(im_frames) > 1:
- for frame_data in im_frames:
- im_frame = frame_data["im"]
- if not frame_data["bbox"]:
- # global header
- for s in _get_global_header(im_frame, frame_data["encoderinfo"]):
- fp.write(s)
- offset = (0, 0)
- else:
- # compress difference
- if not palette:
- frame_data["encoderinfo"]["include_color_table"] = True
-
- im_frame = im_frame.crop(frame_data["bbox"])
- offset = frame_data["bbox"][:2]
- _write_frame_data(fp, im_frame, offset, frame_data["encoderinfo"])
- return True
- elif "duration" in im.encoderinfo and isinstance(
- im.encoderinfo["duration"], (list, tuple)
- ):
- # Since multiple frames will not be written, add together the frame durations
- im.encoderinfo["duration"] = sum(im.encoderinfo["duration"])
-
-
-def _save_all(im, fp, filename):
- _save(im, fp, filename, save_all=True)
-
-
-def _save(im, fp, filename, save_all=False):
- # header
- if "palette" in im.encoderinfo or "palette" in im.info:
- palette = im.encoderinfo.get("palette", im.info.get("palette"))
- else:
- palette = None
- im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True)
-
- if not save_all or not _write_multiple_frames(im, fp, palette):
- _write_single_frame(im, fp, palette)
-
- fp.write(b";") # end of file
-
- if hasattr(fp, "flush"):
- fp.flush()
-
-
-def get_interlace(im):
- interlace = im.encoderinfo.get("interlace", 1)
-
- # workaround for @PIL153
- if min(im.size) < 16:
- interlace = 0
-
- return interlace
-
-
-def _write_local_header(fp, im, offset, flags):
- transparent_color_exists = False
- try:
- if "transparency" in im.encoderinfo:
- transparency = im.encoderinfo["transparency"]
- else:
- transparency = im.info["transparency"]
- transparency = int(transparency)
- except (KeyError, ValueError):
- pass
- else:
- # optimize the block away if transparent color is not used
- transparent_color_exists = True
-
- used_palette_colors = _get_optimize(im, im.encoderinfo)
- if used_palette_colors is not None:
- # adjust the transparency index after optimize
- try:
- transparency = used_palette_colors.index(transparency)
- except ValueError:
- transparent_color_exists = False
-
- if "duration" in im.encoderinfo:
- duration = int(im.encoderinfo["duration"] / 10)
- else:
- duration = 0
-
- disposal = int(im.encoderinfo.get("disposal", 0))
-
- if transparent_color_exists or duration != 0 or disposal:
- packed_flag = 1 if transparent_color_exists else 0
- packed_flag |= disposal << 2
- if not transparent_color_exists:
- transparency = 0
-
- fp.write(
- b"!"
- + o8(249) # extension intro
- + o8(4) # length
- + o8(packed_flag) # packed fields
- + o16(duration) # duration
- + o8(transparency) # transparency index
- + o8(0)
- )
-
- if "comment" in im.encoderinfo and 1 <= len(im.encoderinfo["comment"]):
- fp.write(b"!" + o8(254)) # extension intro
- comment = im.encoderinfo["comment"]
- if isinstance(comment, str):
- comment = comment.encode()
- for i in range(0, len(comment), 255):
- subblock = comment[i : i + 255]
- fp.write(o8(len(subblock)) + subblock)
- fp.write(o8(0))
- if "loop" in im.encoderinfo:
- number_of_loops = im.encoderinfo["loop"]
- fp.write(
- b"!"
- + o8(255) # extension intro
- + o8(11)
- + b"NETSCAPE2.0"
- + o8(3)
- + o8(1)
- + o16(number_of_loops) # number of loops
- + o8(0)
- )
- include_color_table = im.encoderinfo.get("include_color_table")
- if include_color_table:
- palette_bytes = _get_palette_bytes(im)
- color_table_size = _get_color_table_size(palette_bytes)
- if color_table_size:
- flags = flags | 128 # local color table flag
- flags = flags | color_table_size
-
- fp.write(
- b","
- + o16(offset[0]) # offset
- + o16(offset[1])
- + o16(im.size[0]) # size
- + o16(im.size[1])
- + o8(flags) # flags
- )
- if include_color_table and color_table_size:
- fp.write(_get_header_palette(palette_bytes))
- fp.write(o8(8)) # bits
-
-
-def _save_netpbm(im, fp, filename):
-
- # Unused by default.
- # To use, uncomment the register_save call at the end of the file.
- #
- # If you need real GIF compression and/or RGB quantization, you
- # can use the external NETPBM/PBMPLUS utilities. See comments
- # below for information on how to enable this.
- tempfile = im._dump()
-
- try:
- with open(filename, "wb") as f:
- if im.mode != "RGB":
- subprocess.check_call(
- ["ppmtogif", tempfile], stdout=f, stderr=subprocess.DEVNULL
- )
- else:
- # Pipe ppmquant output into ppmtogif
- # "ppmquant 256 %s | ppmtogif > %s" % (tempfile, filename)
- quant_cmd = ["ppmquant", "256", tempfile]
- togif_cmd = ["ppmtogif"]
- quant_proc = subprocess.Popen(
- quant_cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL
- )
- togif_proc = subprocess.Popen(
- togif_cmd,
- stdin=quant_proc.stdout,
- stdout=f,
- stderr=subprocess.DEVNULL,
- )
-
- # Allow ppmquant to receive SIGPIPE if ppmtogif exits
- quant_proc.stdout.close()
-
- retcode = quant_proc.wait()
- if retcode:
- raise subprocess.CalledProcessError(retcode, quant_cmd)
-
- retcode = togif_proc.wait()
- if retcode:
- raise subprocess.CalledProcessError(retcode, togif_cmd)
- finally:
- try:
- os.unlink(tempfile)
- except OSError:
- pass
-
-
-# Force optimization so that we can test performance against
-# cases where it took lots of memory and time previously.
-_FORCE_OPTIMIZE = False
-
-
-def _get_optimize(im, info):
- """
- Palette optimization is a potentially expensive operation.
-
- This function determines if the palette should be optimized using
- some heuristics, then returns the list of palette entries in use.
-
- :param im: Image object
- :param info: encoderinfo
- :returns: list of indexes of palette entries in use, or None
- """
- if im.mode in ("P", "L") and info and info.get("optimize", 0):
- # Potentially expensive operation.
-
- # The palette saves 3 bytes per color not used, but palette
- # lengths are restricted to 3*(2**N) bytes. Max saving would
- # be 768 -> 6 bytes if we went all the way down to 2 colors.
- # * If we're over 128 colors, we can't save any space.
- # * If there aren't any holes, it's not worth collapsing.
- # * If we have a 'large' image, the palette is in the noise.
-
- # create the new palette if not every color is used
- optimise = _FORCE_OPTIMIZE or im.mode == "L"
- if optimise or im.width * im.height < 512 * 512:
- # check which colors are used
- used_palette_colors = []
- for i, count in enumerate(im.histogram()):
- if count:
- used_palette_colors.append(i)
-
- if optimise or (
- len(used_palette_colors) <= 128
- and max(used_palette_colors) > len(used_palette_colors)
- ):
- return used_palette_colors
-
-
-def _get_color_table_size(palette_bytes):
- # calculate the palette size for the header
- if not palette_bytes:
- return 0
- elif len(palette_bytes) < 9:
- return 1
- else:
- return math.ceil(math.log(len(palette_bytes) // 3, 2)) - 1
-
-
-def _get_header_palette(palette_bytes):
- """
- Returns the palette, null padded to the next power of 2 (*3) bytes
- suitable for direct inclusion in the GIF header
-
- :param palette_bytes: Unpadded palette bytes, in RGBRGB form
- :returns: Null padded palette
- """
- color_table_size = _get_color_table_size(palette_bytes)
-
- # add the missing amount of bytes
- # the palette has to be 2< 0:
- palette_bytes += o8(0) * 3 * actual_target_size_diff
- return palette_bytes
-
-
-def _get_palette_bytes(im):
- """
- Gets the palette for inclusion in the gif header
-
- :param im: Image object
- :returns: Bytes, len<=768 suitable for inclusion in gif header
- """
- return im.palette.palette
-
-
-def _get_background(im, infoBackground):
- background = 0
- if infoBackground:
- background = infoBackground
- if isinstance(background, tuple):
- # WebPImagePlugin stores an RGBA value in info["background"]
- # So it must be converted to the same format as GifImagePlugin's
- # info["background"] - a global color table index
- try:
- background = im.palette.getcolor(background, im)
- except ValueError as e:
- if str(e) == "cannot allocate more than 256 colors":
- # If all 256 colors are in use,
- # then there is no need for the background color
- return 0
- else:
- raise
- return background
-
-
-def _get_global_header(im, info):
- """Return a list of strings representing a GIF header"""
-
- # Header Block
- # https://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp
-
- version = b"87a"
- for extensionKey in ["transparency", "duration", "loop", "comment"]:
- if info and extensionKey in info:
- if (extensionKey == "duration" and info[extensionKey] == 0) or (
- extensionKey == "comment" and not (1 <= len(info[extensionKey]) <= 255)
- ):
- continue
- version = b"89a"
- break
- else:
- if im.info.get("version") == b"89a":
- version = b"89a"
-
- background = _get_background(im, info.get("background"))
-
- palette_bytes = _get_palette_bytes(im)
- color_table_size = _get_color_table_size(palette_bytes)
-
- return [
- b"GIF" # signature
- + version # version
- + o16(im.size[0]) # canvas width
- + o16(im.size[1]), # canvas height
- # Logical Screen Descriptor
- # size of global color table + global color table flag
- o8(color_table_size + 128), # packed fields
- # background + reserved/aspect
- o8(background) + o8(0),
- # Global Color Table
- _get_header_palette(palette_bytes),
- ]
-
-
-def _write_frame_data(fp, im_frame, offset, params):
- try:
- im_frame.encoderinfo = params
-
- # local image header
- _write_local_header(fp, im_frame, offset, 0)
-
- ImageFile._save(
- im_frame, fp, [("gif", (0, 0) + im_frame.size, 0, RAWMODE[im_frame.mode])]
- )
-
- fp.write(b"\0") # end of image data
- finally:
- del im_frame.encoderinfo
-
-
-# --------------------------------------------------------------------
-# Legacy GIF utilities
-
-
-def getheader(im, palette=None, info=None):
- """
- Legacy Method to get Gif data from image.
-
- Warning:: May modify image data.
-
- :param im: Image object
- :param palette: bytes object containing the source palette, or ....
- :param info: encoderinfo
- :returns: tuple of(list of header items, optimized palette)
-
- """
- used_palette_colors = _get_optimize(im, info)
-
- if info is None:
- info = {}
-
- if "background" not in info and "background" in im.info:
- info["background"] = im.info["background"]
-
- im_mod = _normalize_palette(im, palette, info)
- im.palette = im_mod.palette
- im.im = im_mod.im
- header = _get_global_header(im, info)
-
- return header, used_palette_colors
-
-
-# To specify duration, add the time in milliseconds to getdata(),
-# e.g. getdata(im_frame, duration=1000)
-def getdata(im, offset=(0, 0), **params):
- """
- Legacy Method
-
- Return a list of strings representing this image.
- The first string is a local image header, the rest contains
- encoded image data.
-
- :param im: Image object
- :param offset: Tuple of (x, y) pixels. Defaults to (0,0)
- :param \\**params: E.g. duration or other encoder info parameters
- :returns: List of Bytes containing gif encoded frame data
-
- """
-
- class Collector:
- data = []
-
- def write(self, data):
- self.data.append(data)
-
- im.load() # make sure raster data is available
-
- fp = Collector()
-
- _write_frame_data(fp, im, offset, params)
-
- return fp.data
-
-
-# --------------------------------------------------------------------
-# Registry
-
-Image.register_open(GifImageFile.format, GifImageFile, _accept)
-Image.register_save(GifImageFile.format, _save)
-Image.register_save_all(GifImageFile.format, _save_all)
-Image.register_extension(GifImageFile.format, ".gif")
-Image.register_mime(GifImageFile.format, "image/gif")
-
-#
-# Uncomment the following line if you wish to use NETPBM/PBMPLUS
-# instead of the built-in "uncompressed" GIF encoder
-
-# Image.register_save(GifImageFile.format, _save_netpbm)
diff --git a/venv/Lib/site-packages/PIL/GimpGradientFile.py b/venv/Lib/site-packages/PIL/GimpGradientFile.py
deleted file mode 100644
index 7ab7f99..0000000
--- a/venv/Lib/site-packages/PIL/GimpGradientFile.py
+++ /dev/null
@@ -1,140 +0,0 @@
-#
-# Python Imaging Library
-# $Id$
-#
-# stuff to read (and render) GIMP gradient files
-#
-# History:
-# 97-08-23 fl Created
-#
-# Copyright (c) Secret Labs AB 1997.
-# Copyright (c) Fredrik Lundh 1997.
-#
-# See the README file for information on usage and redistribution.
-#
-
-"""
-Stuff to translate curve segments to palette values (derived from
-the corresponding code in GIMP, written by Federico Mena Quintero.
-See the GIMP distribution for more information.)
-"""
-
-
-from math import log, pi, sin, sqrt
-
-from ._binary import o8
-
-EPSILON = 1e-10
-"""""" # Enable auto-doc for data member
-
-
-def linear(middle, pos):
- if pos <= middle:
- if middle < EPSILON:
- return 0.0
- else:
- return 0.5 * pos / middle
- else:
- pos = pos - middle
- middle = 1.0 - middle
- if middle < EPSILON:
- return 1.0
- else:
- return 0.5 + 0.5 * pos / middle
-
-
-def curved(middle, pos):
- return pos ** (log(0.5) / log(max(middle, EPSILON)))
-
-
-def sine(middle, pos):
- return (sin((-pi / 2.0) + pi * linear(middle, pos)) + 1.0) / 2.0
-
-
-def sphere_increasing(middle, pos):
- return sqrt(1.0 - (linear(middle, pos) - 1.0) ** 2)
-
-
-def sphere_decreasing(middle, pos):
- return 1.0 - sqrt(1.0 - linear(middle, pos) ** 2)
-
-
-SEGMENTS = [linear, curved, sine, sphere_increasing, sphere_decreasing]
-"""""" # Enable auto-doc for data member
-
-
-class GradientFile:
-
- gradient = None
-
- def getpalette(self, entries=256):
-
- palette = []
-
- ix = 0
- x0, x1, xm, rgb0, rgb1, segment = self.gradient[ix]
-
- for i in range(entries):
-
- x = i / (entries - 1)
-
- while x1 < x:
- ix += 1
- x0, x1, xm, rgb0, rgb1, segment = self.gradient[ix]
-
- w = x1 - x0
-
- if w < EPSILON:
- scale = segment(0.5, 0.5)
- else:
- scale = segment((xm - x0) / w, (x - x0) / w)
-
- # expand to RGBA
- r = o8(int(255 * ((rgb1[0] - rgb0[0]) * scale + rgb0[0]) + 0.5))
- g = o8(int(255 * ((rgb1[1] - rgb0[1]) * scale + rgb0[1]) + 0.5))
- b = o8(int(255 * ((rgb1[2] - rgb0[2]) * scale + rgb0[2]) + 0.5))
- a = o8(int(255 * ((rgb1[3] - rgb0[3]) * scale + rgb0[3]) + 0.5))
-
- # add to palette
- palette.append(r + g + b + a)
-
- return b"".join(palette), "RGBA"
-
-
-class GimpGradientFile(GradientFile):
- """File handler for GIMP's gradient format."""
-
- def __init__(self, fp):
-
- if fp.readline()[:13] != b"GIMP Gradient":
- raise SyntaxError("not a GIMP gradient file")
-
- line = fp.readline()
-
- # GIMP 1.2 gradient files don't contain a name, but GIMP 1.3 files do
- if line.startswith(b"Name: "):
- line = fp.readline().strip()
-
- count = int(line)
-
- gradient = []
-
- for i in range(count):
-
- s = fp.readline().split()
- w = [float(x) for x in s[:11]]
-
- x0, x1 = w[0], w[2]
- xm = w[1]
- rgb0 = w[3:7]
- rgb1 = w[7:11]
-
- segment = SEGMENTS[int(s[11])]
- cspace = int(s[12])
-
- if cspace != 0:
- raise OSError("cannot handle HSV colour space")
-
- gradient.append((x0, x1, xm, rgb0, rgb1, segment))
-
- self.gradient = gradient
diff --git a/venv/Lib/site-packages/PIL/GimpPaletteFile.py b/venv/Lib/site-packages/PIL/GimpPaletteFile.py
deleted file mode 100644
index 4d7cfba..0000000
--- a/venv/Lib/site-packages/PIL/GimpPaletteFile.py
+++ /dev/null
@@ -1,56 +0,0 @@
-#
-# Python Imaging Library
-# $Id$
-#
-# stuff to read GIMP palette files
-#
-# History:
-# 1997-08-23 fl Created
-# 2004-09-07 fl Support GIMP 2.0 palette files.
-#
-# Copyright (c) Secret Labs AB 1997-2004. All rights reserved.
-# Copyright (c) Fredrik Lundh 1997-2004.
-#
-# See the README file for information on usage and redistribution.
-#
-
-import re
-
-from ._binary import o8
-
-
-class GimpPaletteFile:
- """File handler for GIMP's palette format."""
-
- rawmode = "RGB"
-
- def __init__(self, fp):
-
- self.palette = [o8(i) * 3 for i in range(256)]
-
- if fp.readline()[:12] != b"GIMP Palette":
- raise SyntaxError("not a GIMP palette file")
-
- for i in range(256):
-
- s = fp.readline()
- if not s:
- break
-
- # skip fields and comment lines
- if re.match(rb"\w+:|#", s):
- continue
- if len(s) > 100:
- raise SyntaxError("bad palette file")
-
- v = tuple(map(int, s.split()[:3]))
- if len(v) != 3:
- raise ValueError("bad palette entry")
-
- self.palette[i] = o8(v[0]) + o8(v[1]) + o8(v[2])
-
- self.palette = b"".join(self.palette)
-
- def getpalette(self):
-
- return self.palette, self.rawmode
diff --git a/venv/Lib/site-packages/PIL/GribStubImagePlugin.py b/venv/Lib/site-packages/PIL/GribStubImagePlugin.py
deleted file mode 100644
index cc9bc26..0000000
--- a/venv/Lib/site-packages/PIL/GribStubImagePlugin.py
+++ /dev/null
@@ -1,73 +0,0 @@
-#
-# The Python Imaging Library
-# $Id$
-#
-# GRIB stub adapter
-#
-# Copyright (c) 1996-2003 by Fredrik Lundh
-#
-# See the README file for information on usage and redistribution.
-#
-
-from . import Image, ImageFile
-
-_handler = None
-
-
-def register_handler(handler):
- """
- Install application-specific GRIB image handler.
-
- :param handler: Handler object.
- """
- global _handler
- _handler = handler
-
-
-# --------------------------------------------------------------------
-# Image adapter
-
-
-def _accept(prefix):
- return prefix[0:4] == b"GRIB" and prefix[7] == 1
-
-
-class GribStubImageFile(ImageFile.StubImageFile):
-
- format = "GRIB"
- format_description = "GRIB"
-
- def _open(self):
-
- offset = self.fp.tell()
-
- if not _accept(self.fp.read(8)):
- raise SyntaxError("Not a GRIB file")
-
- self.fp.seek(offset)
-
- # make something up
- self.mode = "F"
- self._size = 1, 1
-
- loader = self._load()
- if loader:
- loader.open(self)
-
- def _load(self):
- return _handler
-
-
-def _save(im, fp, filename):
- if _handler is None or not hasattr(_handler, "save"):
- raise OSError("GRIB save handler not installed")
- _handler.save(im, fp, filename)
-
-
-# --------------------------------------------------------------------
-# Registry
-
-Image.register_open(GribStubImageFile.format, GribStubImageFile, _accept)
-Image.register_save(GribStubImageFile.format, _save)
-
-Image.register_extension(GribStubImageFile.format, ".grib")
diff --git a/venv/Lib/site-packages/PIL/Hdf5StubImagePlugin.py b/venv/Lib/site-packages/PIL/Hdf5StubImagePlugin.py
deleted file mode 100644
index df11cf2..0000000
--- a/venv/Lib/site-packages/PIL/Hdf5StubImagePlugin.py
+++ /dev/null
@@ -1,73 +0,0 @@
-#
-# The Python Imaging Library
-# $Id$
-#
-# HDF5 stub adapter
-#
-# Copyright (c) 2000-2003 by Fredrik Lundh
-#
-# See the README file for information on usage and redistribution.
-#
-
-from . import Image, ImageFile
-
-_handler = None
-
-
-def register_handler(handler):
- """
- Install application-specific HDF5 image handler.
-
- :param handler: Handler object.
- """
- global _handler
- _handler = handler
-
-
-# --------------------------------------------------------------------
-# Image adapter
-
-
-def _accept(prefix):
- return prefix[:8] == b"\x89HDF\r\n\x1a\n"
-
-
-class HDF5StubImageFile(ImageFile.StubImageFile):
-
- format = "HDF5"
- format_description = "HDF5"
-
- def _open(self):
-
- offset = self.fp.tell()
-
- if not _accept(self.fp.read(8)):
- raise SyntaxError("Not an HDF file")
-
- self.fp.seek(offset)
-
- # make something up
- self.mode = "F"
- self._size = 1, 1
-
- loader = self._load()
- if loader:
- loader.open(self)
-
- def _load(self):
- return _handler
-
-
-def _save(im, fp, filename):
- if _handler is None or not hasattr(_handler, "save"):
- raise OSError("HDF5 save handler not installed")
- _handler.save(im, fp, filename)
-
-
-# --------------------------------------------------------------------
-# Registry
-
-Image.register_open(HDF5StubImageFile.format, HDF5StubImageFile, _accept)
-Image.register_save(HDF5StubImageFile.format, _save)
-
-Image.register_extensions(HDF5StubImageFile.format, [".h5", ".hdf"])
diff --git a/venv/Lib/site-packages/PIL/IcnsImagePlugin.py b/venv/Lib/site-packages/PIL/IcnsImagePlugin.py
deleted file mode 100644
index fa192f0..0000000
--- a/venv/Lib/site-packages/PIL/IcnsImagePlugin.py
+++ /dev/null
@@ -1,392 +0,0 @@
-#
-# The Python Imaging Library.
-# $Id$
-#
-# macOS icns file decoder, based on icns.py by Bob Ippolito.
-#
-# history:
-# 2004-10-09 fl Turned into a PIL plugin; removed 2.3 dependencies.
-# 2020-04-04 Allow saving on all operating systems.
-#
-# Copyright (c) 2004 by Bob Ippolito.
-# Copyright (c) 2004 by Secret Labs.
-# Copyright (c) 2004 by Fredrik Lundh.
-# Copyright (c) 2014 by Alastair Houghton.
-# Copyright (c) 2020 by Pan Jing.
-#
-# See the README file for information on usage and redistribution.
-#
-
-import io
-import os
-import struct
-import sys
-
-from PIL import Image, ImageFile, PngImagePlugin, features
-
-enable_jpeg2k = features.check_codec("jpg_2000")
-if enable_jpeg2k:
- from PIL import Jpeg2KImagePlugin
-
-MAGIC = b"icns"
-HEADERSIZE = 8
-
-
-def nextheader(fobj):
- return struct.unpack(">4sI", fobj.read(HEADERSIZE))
-
-
-def read_32t(fobj, start_length, size):
- # The 128x128 icon seems to have an extra header for some reason.
- (start, length) = start_length
- fobj.seek(start)
- sig = fobj.read(4)
- if sig != b"\x00\x00\x00\x00":
- raise SyntaxError("Unknown signature, expecting 0x00000000")
- return read_32(fobj, (start + 4, length - 4), size)
-
-
-def read_32(fobj, start_length, size):
- """
- Read a 32bit RGB icon resource. Seems to be either uncompressed or
- an RLE packbits-like scheme.
- """
- (start, length) = start_length
- fobj.seek(start)
- pixel_size = (size[0] * size[2], size[1] * size[2])
- sizesq = pixel_size[0] * pixel_size[1]
- if length == sizesq * 3:
- # uncompressed ("RGBRGBGB")
- indata = fobj.read(length)
- im = Image.frombuffer("RGB", pixel_size, indata, "raw", "RGB", 0, 1)
- else:
- # decode image
- im = Image.new("RGB", pixel_size, None)
- for band_ix in range(3):
- data = []
- bytesleft = sizesq
- while bytesleft > 0:
- byte = fobj.read(1)
- if not byte:
- break
- byte = byte[0]
- if byte & 0x80:
- blocksize = byte - 125
- byte = fobj.read(1)
- for i in range(blocksize):
- data.append(byte)
- else:
- blocksize = byte + 1
- data.append(fobj.read(blocksize))
- bytesleft -= blocksize
- if bytesleft <= 0:
- break
- if bytesleft != 0:
- raise SyntaxError(f"Error reading channel [{repr(bytesleft)} left]")
- band = Image.frombuffer("L", pixel_size, b"".join(data), "raw", "L", 0, 1)
- im.im.putband(band.im, band_ix)
- return {"RGB": im}
-
-
-def read_mk(fobj, start_length, size):
- # Alpha masks seem to be uncompressed
- start = start_length[0]
- fobj.seek(start)
- pixel_size = (size[0] * size[2], size[1] * size[2])
- sizesq = pixel_size[0] * pixel_size[1]
- band = Image.frombuffer("L", pixel_size, fobj.read(sizesq), "raw", "L", 0, 1)
- return {"A": band}
-
-
-def read_png_or_jpeg2000(fobj, start_length, size):
- (start, length) = start_length
- fobj.seek(start)
- sig = fobj.read(12)
- if sig[:8] == b"\x89PNG\x0d\x0a\x1a\x0a":
- fobj.seek(start)
- im = PngImagePlugin.PngImageFile(fobj)
- Image._decompression_bomb_check(im.size)
- return {"RGBA": im}
- elif (
- sig[:4] == b"\xff\x4f\xff\x51"
- or sig[:4] == b"\x0d\x0a\x87\x0a"
- or sig == b"\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a"
- ):
- if not enable_jpeg2k:
- raise ValueError(
- "Unsupported icon subimage format (rebuild PIL "
- "with JPEG 2000 support to fix this)"
- )
- # j2k, jpc or j2c
- fobj.seek(start)
- jp2kstream = fobj.read(length)
- f = io.BytesIO(jp2kstream)
- im = Jpeg2KImagePlugin.Jpeg2KImageFile(f)
- Image._decompression_bomb_check(im.size)
- if im.mode != "RGBA":
- im = im.convert("RGBA")
- return {"RGBA": im}
- else:
- raise ValueError("Unsupported icon subimage format")
-
-
-class IcnsFile:
-
- SIZES = {
- (512, 512, 2): [(b"ic10", read_png_or_jpeg2000)],
- (512, 512, 1): [(b"ic09", read_png_or_jpeg2000)],
- (256, 256, 2): [(b"ic14", read_png_or_jpeg2000)],
- (256, 256, 1): [(b"ic08", read_png_or_jpeg2000)],
- (128, 128, 2): [(b"ic13", read_png_or_jpeg2000)],
- (128, 128, 1): [
- (b"ic07", read_png_or_jpeg2000),
- (b"it32", read_32t),
- (b"t8mk", read_mk),
- ],
- (64, 64, 1): [(b"icp6", read_png_or_jpeg2000)],
- (32, 32, 2): [(b"ic12", read_png_or_jpeg2000)],
- (48, 48, 1): [(b"ih32", read_32), (b"h8mk", read_mk)],
- (32, 32, 1): [
- (b"icp5", read_png_or_jpeg2000),
- (b"il32", read_32),
- (b"l8mk", read_mk),
- ],
- (16, 16, 2): [(b"ic11", read_png_or_jpeg2000)],
- (16, 16, 1): [
- (b"icp4", read_png_or_jpeg2000),
- (b"is32", read_32),
- (b"s8mk", read_mk),
- ],
- }
-
- def __init__(self, fobj):
- """
- fobj is a file-like object as an icns resource
- """
- # signature : (start, length)
- self.dct = dct = {}
- self.fobj = fobj
- sig, filesize = nextheader(fobj)
- if not _accept(sig):
- raise SyntaxError("not an icns file")
- i = HEADERSIZE
- while i < filesize:
- sig, blocksize = nextheader(fobj)
- if blocksize <= 0:
- raise SyntaxError("invalid block header")
- i += HEADERSIZE
- blocksize -= HEADERSIZE
- dct[sig] = (i, blocksize)
- fobj.seek(blocksize, io.SEEK_CUR)
- i += blocksize
-
- def itersizes(self):
- sizes = []
- for size, fmts in self.SIZES.items():
- for (fmt, reader) in fmts:
- if fmt in self.dct:
- sizes.append(size)
- break
- return sizes
-
- def bestsize(self):
- sizes = self.itersizes()
- if not sizes:
- raise SyntaxError("No 32bit icon resources found")
- return max(sizes)
-
- def dataforsize(self, size):
- """
- Get an icon resource as {channel: array}. Note that
- the arrays are bottom-up like windows bitmaps and will likely
- need to be flipped or transposed in some way.
- """
- dct = {}
- for code, reader in self.SIZES[size]:
- desc = self.dct.get(code)
- if desc is not None:
- dct.update(reader(self.fobj, desc, size))
- return dct
-
- def getimage(self, size=None):
- if size is None:
- size = self.bestsize()
- if len(size) == 2:
- size = (size[0], size[1], 1)
- channels = self.dataforsize(size)
-
- im = channels.get("RGBA", None)
- if im:
- return im
-
- im = channels.get("RGB").copy()
- try:
- im.putalpha(channels["A"])
- except KeyError:
- pass
- return im
-
-
-##
-# Image plugin for Mac OS icons.
-
-
-class IcnsImageFile(ImageFile.ImageFile):
- """
- PIL image support for Mac OS .icns files.
- Chooses the best resolution, but will possibly load
- a different size image if you mutate the size attribute
- before calling 'load'.
-
- The info dictionary has a key 'sizes' that is a list
- of sizes that the icns file has.
- """
-
- format = "ICNS"
- format_description = "Mac OS icns resource"
-
- def _open(self):
- self.icns = IcnsFile(self.fp)
- self.mode = "RGBA"
- self.info["sizes"] = self.icns.itersizes()
- self.best_size = self.icns.bestsize()
- self.size = (
- self.best_size[0] * self.best_size[2],
- self.best_size[1] * self.best_size[2],
- )
-
- @property
- def size(self):
- return self._size
-
- @size.setter
- def size(self, value):
- info_size = value
- if info_size not in self.info["sizes"] and len(info_size) == 2:
- info_size = (info_size[0], info_size[1], 1)
- if (
- info_size not in self.info["sizes"]
- and len(info_size) == 3
- and info_size[2] == 1
- ):
- simple_sizes = [
- (size[0] * size[2], size[1] * size[2]) for size in self.info["sizes"]
- ]
- if value in simple_sizes:
- info_size = self.info["sizes"][simple_sizes.index(value)]
- if info_size not in self.info["sizes"]:
- raise ValueError("This is not one of the allowed sizes of this image")
- self._size = value
-
- def load(self):
- if len(self.size) == 3:
- self.best_size = self.size
- self.size = (
- self.best_size[0] * self.best_size[2],
- self.best_size[1] * self.best_size[2],
- )
-
- px = Image.Image.load(self)
- if self.im is not None and self.im.size == self.size:
- # Already loaded
- return px
- self.load_prepare()
- # This is likely NOT the best way to do it, but whatever.
- im = self.icns.getimage(self.best_size)
-
- # If this is a PNG or JPEG 2000, it won't be loaded yet
- px = im.load()
-
- self.im = im.im
- self.mode = im.mode
- self.size = im.size
-
- return px
-
-
-def _save(im, fp, filename):
- """
- Saves the image as a series of PNG files,
- that are then combined into a .icns file.
- """
- if hasattr(fp, "flush"):
- fp.flush()
-
- sizes = {
- b"ic07": 128,
- b"ic08": 256,
- b"ic09": 512,
- b"ic10": 1024,
- b"ic11": 32,
- b"ic12": 64,
- b"ic13": 256,
- b"ic14": 512,
- }
- provided_images = {im.width: im for im in im.encoderinfo.get("append_images", [])}
- size_streams = {}
- for size in set(sizes.values()):
- image = (
- provided_images[size]
- if size in provided_images
- else im.resize((size, size))
- )
-
- temp = io.BytesIO()
- image.save(temp, "png")
- size_streams[size] = temp.getvalue()
-
- entries = []
- for type, size in sizes.items():
- stream = size_streams[size]
- entries.append(
- {"type": type, "size": HEADERSIZE + len(stream), "stream": stream}
- )
-
- # Header
- fp.write(MAGIC)
- file_length = HEADERSIZE # Header
- file_length += HEADERSIZE + 8 * len(entries) # TOC
- file_length += sum(entry["size"] for entry in entries)
- fp.write(struct.pack(">i", file_length))
-
- # TOC
- fp.write(b"TOC ")
- fp.write(struct.pack(">i", HEADERSIZE + len(entries) * HEADERSIZE))
- for entry in entries:
- fp.write(entry["type"])
- fp.write(struct.pack(">i", entry["size"]))
-
- # Data
- for entry in entries:
- fp.write(entry["type"])
- fp.write(struct.pack(">i", entry["size"]))
- fp.write(entry["stream"])
-
- if hasattr(fp, "flush"):
- fp.flush()
-
-
-def _accept(prefix):
- return prefix[:4] == MAGIC
-
-
-Image.register_open(IcnsImageFile.format, IcnsImageFile, _accept)
-Image.register_extension(IcnsImageFile.format, ".icns")
-
-Image.register_save(IcnsImageFile.format, _save)
-Image.register_mime(IcnsImageFile.format, "image/icns")
-
-if __name__ == "__main__":
- if len(sys.argv) < 2:
- print("Syntax: python3 IcnsImagePlugin.py [file]")
- sys.exit()
-
- with open(sys.argv[1], "rb") as fp:
- imf = IcnsImageFile(fp)
- for size in imf.info["sizes"]:
- imf.size = size
- imf.save("out-%s-%s-%s.png" % size)
- with Image.open(sys.argv[1]) as im:
- im.save("out.png")
- if sys.platform == "windows":
- os.startfile("out.png")
diff --git a/venv/Lib/site-packages/PIL/IcoImagePlugin.py b/venv/Lib/site-packages/PIL/IcoImagePlugin.py
deleted file mode 100644
index 17b9855..0000000
--- a/venv/Lib/site-packages/PIL/IcoImagePlugin.py
+++ /dev/null
@@ -1,355 +0,0 @@
-#
-# The Python Imaging Library.
-# $Id$
-#
-# Windows Icon support for PIL
-#
-# History:
-# 96-05-27 fl Created
-#
-# Copyright (c) Secret Labs AB 1997.
-# Copyright (c) Fredrik Lundh 1996.
-#
-# See the README file for information on usage and redistribution.
-#
-
-# This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis
-# .
-# https://code.google.com/archive/p/casadebender/wikis/Win32IconImagePlugin.wiki
-#
-# Icon format references:
-# * https://en.wikipedia.org/wiki/ICO_(file_format)
-# * https://msdn.microsoft.com/en-us/library/ms997538.aspx
-
-
-import warnings
-from io import BytesIO
-from math import ceil, log
-
-from . import BmpImagePlugin, Image, ImageFile, PngImagePlugin
-from ._binary import i16le as i16
-from ._binary import i32le as i32
-from ._binary import o8
-from ._binary import o16le as o16
-from ._binary import o32le as o32
-
-#
-# --------------------------------------------------------------------
-
-_MAGIC = b"\0\0\1\0"
-
-
-def _save(im, fp, filename):
- fp.write(_MAGIC) # (2+2)
- bmp = im.encoderinfo.get("bitmap_format") == "bmp"
- sizes = im.encoderinfo.get(
- "sizes",
- [(16, 16), (24, 24), (32, 32), (48, 48), (64, 64), (128, 128), (256, 256)],
- )
- frames = []
- provided_ims = [im] + im.encoderinfo.get("append_images", [])
- width, height = im.size
- for size in sorted(set(sizes)):
- if size[0] > width or size[1] > height or size[0] > 256 or size[1] > 256:
- continue
-
- for provided_im in provided_ims:
- if provided_im.size != size:
- continue
- frames.append(provided_im)
- if bmp:
- bits = BmpImagePlugin.SAVE[provided_im.mode][1]
- bits_used = [bits]
- for other_im in provided_ims:
- if other_im.size != size:
- continue
- bits = BmpImagePlugin.SAVE[other_im.mode][1]
- if bits not in bits_used:
- # Another image has been supplied for this size
- # with a different bit depth
- frames.append(other_im)
- bits_used.append(bits)
- break
- else:
- # TODO: invent a more convenient method for proportional scalings
- frame = provided_im.copy()
- frame.thumbnail(size, Image.Resampling.LANCZOS, reducing_gap=None)
- frames.append(frame)
- fp.write(o16(len(frames))) # idCount(2)
- offset = fp.tell() + len(frames) * 16
- for frame in frames:
- width, height = frame.size
- # 0 means 256
- fp.write(o8(width if width < 256 else 0)) # bWidth(1)
- fp.write(o8(height if height < 256 else 0)) # bHeight(1)
-
- bits, colors = BmpImagePlugin.SAVE[frame.mode][1:] if bmp else (32, 0)
- fp.write(o8(colors)) # bColorCount(1)
- fp.write(b"\0") # bReserved(1)
- fp.write(b"\0\0") # wPlanes(2)
- fp.write(o16(bits)) # wBitCount(2)
-
- image_io = BytesIO()
- if bmp:
- frame.save(image_io, "dib")
-
- if bits != 32:
- and_mask = Image.new("1", size)
- ImageFile._save(
- and_mask, image_io, [("raw", (0, 0) + size, 0, ("1", 0, -1))]
- )
- else:
- frame.save(image_io, "png")
- image_io.seek(0)
- image_bytes = image_io.read()
- if bmp:
- image_bytes = image_bytes[:8] + o32(height * 2) + image_bytes[12:]
- bytes_len = len(image_bytes)
- fp.write(o32(bytes_len)) # dwBytesInRes(4)
- fp.write(o32(offset)) # dwImageOffset(4)
- current = fp.tell()
- fp.seek(offset)
- fp.write(image_bytes)
- offset = offset + bytes_len
- fp.seek(current)
-
-
-def _accept(prefix):
- return prefix[:4] == _MAGIC
-
-
-class IcoFile:
- def __init__(self, buf):
- """
- Parse image from file-like object containing ico file data
- """
-
- # check magic
- s = buf.read(6)
- if not _accept(s):
- raise SyntaxError("not an ICO file")
-
- self.buf = buf
- self.entry = []
-
- # Number of items in file
- self.nb_items = i16(s, 4)
-
- # Get headers for each item
- for i in range(self.nb_items):
- s = buf.read(16)
-
- icon_header = {
- "width": s[0],
- "height": s[1],
- "nb_color": s[2], # No. of colors in image (0 if >=8bpp)
- "reserved": s[3],
- "planes": i16(s, 4),
- "bpp": i16(s, 6),
- "size": i32(s, 8),
- "offset": i32(s, 12),
- }
-
- # See Wikipedia
- for j in ("width", "height"):
- if not icon_header[j]:
- icon_header[j] = 256
-
- # See Wikipedia notes about color depth.
- # We need this just to differ images with equal sizes
- icon_header["color_depth"] = (
- icon_header["bpp"]
- or (
- icon_header["nb_color"] != 0
- and ceil(log(icon_header["nb_color"], 2))
- )
- or 256
- )
-
- icon_header["dim"] = (icon_header["width"], icon_header["height"])
- icon_header["square"] = icon_header["width"] * icon_header["height"]
-
- self.entry.append(icon_header)
-
- self.entry = sorted(self.entry, key=lambda x: x["color_depth"])
- # ICO images are usually squares
- # self.entry = sorted(self.entry, key=lambda x: x['width'])
- self.entry = sorted(self.entry, key=lambda x: x["square"])
- self.entry.reverse()
-
- def sizes(self):
- """
- Get a list of all available icon sizes and color depths.
- """
- return {(h["width"], h["height"]) for h in self.entry}
-
- def getentryindex(self, size, bpp=False):
- for (i, h) in enumerate(self.entry):
- if size == h["dim"] and (bpp is False or bpp == h["color_depth"]):
- return i
- return 0
-
- def getimage(self, size, bpp=False):
- """
- Get an image from the icon
- """
- return self.frame(self.getentryindex(size, bpp))
-
- def frame(self, idx):
- """
- Get an image from frame idx
- """
-
- header = self.entry[idx]
-
- self.buf.seek(header["offset"])
- data = self.buf.read(8)
- self.buf.seek(header["offset"])
-
- if data[:8] == PngImagePlugin._MAGIC:
- # png frame
- im = PngImagePlugin.PngImageFile(self.buf)
- Image._decompression_bomb_check(im.size)
- else:
- # XOR + AND mask bmp frame
- im = BmpImagePlugin.DibImageFile(self.buf)
- Image._decompression_bomb_check(im.size)
-
- # change tile dimension to only encompass XOR image
- im._size = (im.size[0], int(im.size[1] / 2))
- d, e, o, a = im.tile[0]
- im.tile[0] = d, (0, 0) + im.size, o, a
-
- # figure out where AND mask image starts
- bpp = header["bpp"]
- if 32 == bpp:
- # 32-bit color depth icon image allows semitransparent areas
- # PIL's DIB format ignores transparency bits, recover them.
- # The DIB is packed in BGRX byte order where X is the alpha
- # channel.
-
- # Back up to start of bmp data
- self.buf.seek(o)
- # extract every 4th byte (eg. 3,7,11,15,...)
- alpha_bytes = self.buf.read(im.size[0] * im.size[1] * 4)[3::4]
-
- # convert to an 8bpp grayscale image
- mask = Image.frombuffer(
- "L", # 8bpp
- im.size, # (w, h)
- alpha_bytes, # source chars
- "raw", # raw decoder
- ("L", 0, -1), # 8bpp inverted, unpadded, reversed
- )
- else:
- # get AND image from end of bitmap
- w = im.size[0]
- if (w % 32) > 0:
- # bitmap row data is aligned to word boundaries
- w += 32 - (im.size[0] % 32)
-
- # the total mask data is
- # padded row size * height / bits per char
-
- total_bytes = int((w * im.size[1]) / 8)
- and_mask_offset = header["offset"] + header["size"] - total_bytes
-
- self.buf.seek(and_mask_offset)
- mask_data = self.buf.read(total_bytes)
-
- # convert raw data to image
- mask = Image.frombuffer(
- "1", # 1 bpp
- im.size, # (w, h)
- mask_data, # source chars
- "raw", # raw decoder
- ("1;I", int(w / 8), -1), # 1bpp inverted, padded, reversed
- )
-
- # now we have two images, im is XOR image and mask is AND image
-
- # apply mask image as alpha channel
- im = im.convert("RGBA")
- im.putalpha(mask)
-
- return im
-
-
-##
-# Image plugin for Windows Icon files.
-
-
-class IcoImageFile(ImageFile.ImageFile):
- """
- PIL read-only image support for Microsoft Windows .ico files.
-
- By default the largest resolution image in the file will be loaded. This
- can be changed by altering the 'size' attribute before calling 'load'.
-
- The info dictionary has a key 'sizes' that is a list of the sizes available
- in the icon file.
-
- Handles classic, XP and Vista icon formats.
-
- When saving, PNG compression is used. Support for this was only added in
- Windows Vista. If you are unable to view the icon in Windows, convert the
- image to "RGBA" mode before saving.
-
- This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis
- .
- https://code.google.com/archive/p/casadebender/wikis/Win32IconImagePlugin.wiki
- """
-
- format = "ICO"
- format_description = "Windows Icon"
-
- def _open(self):
- self.ico = IcoFile(self.fp)
- self.info["sizes"] = self.ico.sizes()
- self.size = self.ico.entry[0]["dim"]
- self.load()
-
- @property
- def size(self):
- return self._size
-
- @size.setter
- def size(self, value):
- if value not in self.info["sizes"]:
- raise ValueError("This is not one of the allowed sizes of this image")
- self._size = value
-
- def load(self):
- if self.im is not None and self.im.size == self.size:
- # Already loaded
- return Image.Image.load(self)
- im = self.ico.getimage(self.size)
- # if tile is PNG, it won't really be loaded yet
- im.load()
- self.im = im.im
- self.mode = im.mode
- if im.size != self.size:
- warnings.warn("Image was not the expected size")
-
- index = self.ico.getentryindex(self.size)
- sizes = list(self.info["sizes"])
- sizes[index] = im.size
- self.info["sizes"] = set(sizes)
-
- self.size = im.size
-
- def load_seek(self):
- # Flag the ImageFile.Parser so that it
- # just does all the decode at the end.
- pass
-
-
-#
-# --------------------------------------------------------------------
-
-
-Image.register_open(IcoImageFile.format, IcoImageFile, _accept)
-Image.register_save(IcoImageFile.format, _save)
-Image.register_extension(IcoImageFile.format, ".ico")
-
-Image.register_mime(IcoImageFile.format, "image/x-icon")
diff --git a/venv/Lib/site-packages/PIL/ImImagePlugin.py b/venv/Lib/site-packages/PIL/ImImagePlugin.py
deleted file mode 100644
index f7e690b..0000000
--- a/venv/Lib/site-packages/PIL/ImImagePlugin.py
+++ /dev/null
@@ -1,376 +0,0 @@
-#
-# The Python Imaging Library.
-# $Id$
-#
-# IFUNC IM file handling for PIL
-#
-# history:
-# 1995-09-01 fl Created.
-# 1997-01-03 fl Save palette images
-# 1997-01-08 fl Added sequence support
-# 1997-01-23 fl Added P and RGB save support
-# 1997-05-31 fl Read floating point images
-# 1997-06-22 fl Save floating point images
-# 1997-08-27 fl Read and save 1-bit images
-# 1998-06-25 fl Added support for RGB+LUT images
-# 1998-07-02 fl Added support for YCC images
-# 1998-07-15 fl Renamed offset attribute to avoid name clash
-# 1998-12-29 fl Added I;16 support
-# 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.7)
-# 2003-09-26 fl Added LA/PA support
-#
-# Copyright (c) 1997-2003 by Secret Labs AB.
-# Copyright (c) 1995-2001 by Fredrik Lundh.
-#
-# See the README file for information on usage and redistribution.
-#
-
-
-import os
-import re
-
-from . import Image, ImageFile, ImagePalette
-
-# --------------------------------------------------------------------
-# Standard tags
-
-COMMENT = "Comment"
-DATE = "Date"
-EQUIPMENT = "Digitalization equipment"
-FRAMES = "File size (no of images)"
-LUT = "Lut"
-NAME = "Name"
-SCALE = "Scale (x,y)"
-SIZE = "Image size (x*y)"
-MODE = "Image type"
-
-TAGS = {
- COMMENT: 0,
- DATE: 0,
- EQUIPMENT: 0,
- FRAMES: 0,
- LUT: 0,
- NAME: 0,
- SCALE: 0,
- SIZE: 0,
- MODE: 0,
-}
-
-OPEN = {
- # ifunc93/p3cfunc formats
- "0 1 image": ("1", "1"),
- "L 1 image": ("1", "1"),
- "Greyscale image": ("L", "L"),
- "Grayscale image": ("L", "L"),
- "RGB image": ("RGB", "RGB;L"),
- "RLB image": ("RGB", "RLB"),
- "RYB image": ("RGB", "RLB"),
- "B1 image": ("1", "1"),
- "B2 image": ("P", "P;2"),
- "B4 image": ("P", "P;4"),
- "X 24 image": ("RGB", "RGB"),
- "L 32 S image": ("I", "I;32"),
- "L 32 F image": ("F", "F;32"),
- # old p3cfunc formats
- "RGB3 image": ("RGB", "RGB;T"),
- "RYB3 image": ("RGB", "RYB;T"),
- # extensions
- "LA image": ("LA", "LA;L"),
- "PA image": ("LA", "PA;L"),
- "RGBA image": ("RGBA", "RGBA;L"),
- "RGBX image": ("RGBX", "RGBX;L"),
- "CMYK image": ("CMYK", "CMYK;L"),
- "YCC image": ("YCbCr", "YCbCr;L"),
-}
-
-# ifunc95 extensions
-for i in ["8", "8S", "16", "16S", "32", "32F"]:
- OPEN[f"L {i} image"] = ("F", f"F;{i}")
- OPEN[f"L*{i} image"] = ("F", f"F;{i}")
-for i in ["16", "16L", "16B"]:
- OPEN[f"L {i} image"] = (f"I;{i}", f"I;{i}")
- OPEN[f"L*{i} image"] = (f"I;{i}", f"I;{i}")
-for i in ["32S"]:
- OPEN[f"L {i} image"] = ("I", f"I;{i}")
- OPEN[f"L*{i} image"] = ("I", f"I;{i}")
-for i in range(2, 33):
- OPEN[f"L*{i} image"] = ("F", f"F;{i}")
-
-
-# --------------------------------------------------------------------
-# Read IM directory
-
-split = re.compile(rb"^([A-Za-z][^:]*):[ \t]*(.*)[ \t]*$")
-
-
-def number(s):
- try:
- return int(s)
- except ValueError:
- return float(s)
-
-
-##
-# Image plugin for the IFUNC IM file format.
-
-
-class ImImageFile(ImageFile.ImageFile):
-
- format = "IM"
- format_description = "IFUNC Image Memory"
- _close_exclusive_fp_after_loading = False
-
- def _open(self):
-
- # Quick rejection: if there's not an LF among the first
- # 100 bytes, this is (probably) not a text header.
-
- if b"\n" not in self.fp.read(100):
- raise SyntaxError("not an IM file")
- self.fp.seek(0)
-
- n = 0
-
- # Default values
- self.info[MODE] = "L"
- self.info[SIZE] = (512, 512)
- self.info[FRAMES] = 1
-
- self.rawmode = "L"
-
- while True:
-
- s = self.fp.read(1)
-
- # Some versions of IFUNC uses \n\r instead of \r\n...
- if s == b"\r":
- continue
-
- if not s or s == b"\0" or s == b"\x1A":
- break
-
- # FIXME: this may read whole file if not a text file
- s = s + self.fp.readline()
-
- if len(s) > 100:
- raise SyntaxError("not an IM file")
-
- if s[-2:] == b"\r\n":
- s = s[:-2]
- elif s[-1:] == b"\n":
- s = s[:-1]
-
- try:
- m = split.match(s)
- except re.error as e:
- raise SyntaxError("not an IM file") from e
-
- if m:
-
- k, v = m.group(1, 2)
-
- # Don't know if this is the correct encoding,
- # but a decent guess (I guess)
- k = k.decode("latin-1", "replace")
- v = v.decode("latin-1", "replace")
-
- # Convert value as appropriate
- if k in [FRAMES, SCALE, SIZE]:
- v = v.replace("*", ",")
- v = tuple(map(number, v.split(",")))
- if len(v) == 1:
- v = v[0]
- elif k == MODE and v in OPEN:
- v, self.rawmode = OPEN[v]
-
- # Add to dictionary. Note that COMMENT tags are
- # combined into a list of strings.
- if k == COMMENT:
- if k in self.info:
- self.info[k].append(v)
- else:
- self.info[k] = [v]
- else:
- self.info[k] = v
-
- if k in TAGS:
- n += 1
-
- else:
-
- raise SyntaxError(
- "Syntax error in IM header: " + s.decode("ascii", "replace")
- )
-
- if not n:
- raise SyntaxError("Not an IM file")
-
- # Basic attributes
- self._size = self.info[SIZE]
- self.mode = self.info[MODE]
-
- # Skip forward to start of image data
- while s and s[0:1] != b"\x1A":
- s = self.fp.read(1)
- if not s:
- raise SyntaxError("File truncated")
-
- if LUT in self.info:
- # convert lookup table to palette or lut attribute
- palette = self.fp.read(768)
- greyscale = 1 # greyscale palette
- linear = 1 # linear greyscale palette
- for i in range(256):
- if palette[i] == palette[i + 256] == palette[i + 512]:
- if palette[i] != i:
- linear = 0
- else:
- greyscale = 0
- if self.mode in ["L", "LA", "P", "PA"]:
- if greyscale:
- if not linear:
- self.lut = list(palette[:256])
- else:
- if self.mode in ["L", "P"]:
- self.mode = self.rawmode = "P"
- elif self.mode in ["LA", "PA"]:
- self.mode = "PA"
- self.rawmode = "PA;L"
- self.palette = ImagePalette.raw("RGB;L", palette)
- elif self.mode == "RGB":
- if not greyscale or not linear:
- self.lut = list(palette)
-
- self.frame = 0
-
- self.__offset = offs = self.fp.tell()
-
- self.__fp = self.fp # FIXME: hack
-
- if self.rawmode[:2] == "F;":
-
- # ifunc95 formats
- try:
- # use bit decoder (if necessary)
- bits = int(self.rawmode[2:])
- if bits not in [8, 16, 32]:
- self.tile = [("bit", (0, 0) + self.size, offs, (bits, 8, 3, 0, -1))]
- return
- except ValueError:
- pass
-
- if self.rawmode in ["RGB;T", "RYB;T"]:
- # Old LabEye/3PC files. Would be very surprised if anyone
- # ever stumbled upon such a file ;-)
- size = self.size[0] * self.size[1]
- self.tile = [
- ("raw", (0, 0) + self.size, offs, ("G", 0, -1)),
- ("raw", (0, 0) + self.size, offs + size, ("R", 0, -1)),
- ("raw", (0, 0) + self.size, offs + 2 * size, ("B", 0, -1)),
- ]
- else:
- # LabEye/IFUNC files
- self.tile = [("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1))]
-
- @property
- def n_frames(self):
- return self.info[FRAMES]
-
- @property
- def is_animated(self):
- return self.info[FRAMES] > 1
-
- def seek(self, frame):
- if not self._seek_check(frame):
- return
-
- self.frame = frame
-
- if self.mode == "1":
- bits = 1
- else:
- bits = 8 * len(self.mode)
-
- size = ((self.size[0] * bits + 7) // 8) * self.size[1]
- offs = self.__offset + frame * size
-
- self.fp = self.__fp
-
- self.tile = [("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1))]
-
- def tell(self):
- return self.frame
-
- def _close__fp(self):
- try:
- if self.__fp != self.fp:
- self.__fp.close()
- except AttributeError:
- pass
- finally:
- self.__fp = None
-
-
-#
-# --------------------------------------------------------------------
-# Save IM files
-
-
-SAVE = {
- # mode: (im type, raw mode)
- "1": ("0 1", "1"),
- "L": ("Greyscale", "L"),
- "LA": ("LA", "LA;L"),
- "P": ("Greyscale", "P"),
- "PA": ("LA", "PA;L"),
- "I": ("L 32S", "I;32S"),
- "I;16": ("L 16", "I;16"),
- "I;16L": ("L 16L", "I;16L"),
- "I;16B": ("L 16B", "I;16B"),
- "F": ("L 32F", "F;32F"),
- "RGB": ("RGB", "RGB;L"),
- "RGBA": ("RGBA", "RGBA;L"),
- "RGBX": ("RGBX", "RGBX;L"),
- "CMYK": ("CMYK", "CMYK;L"),
- "YCbCr": ("YCC", "YCbCr;L"),
-}
-
-
-def _save(im, fp, filename):
-
- try:
- image_type, rawmode = SAVE[im.mode]
- except KeyError as e:
- raise ValueError(f"Cannot save {im.mode} images as IM") from e
-
- frames = im.encoderinfo.get("frames", 1)
-
- fp.write(f"Image type: {image_type} image\r\n".encode("ascii"))
- if filename:
- # Each line must be 100 characters or less,
- # or: SyntaxError("not an IM file")
- # 8 characters are used for "Name: " and "\r\n"
- # Keep just the filename, ditch the potentially overlong path
- name, ext = os.path.splitext(os.path.basename(filename))
- name = "".join([name[: 92 - len(ext)], ext])
-
- fp.write(f"Name: {name}\r\n".encode("ascii"))
- fp.write(("Image size (x*y): %d*%d\r\n" % im.size).encode("ascii"))
- fp.write(f"File size (no of images): {frames}\r\n".encode("ascii"))
- if im.mode in ["P", "PA"]:
- fp.write(b"Lut: 1\r\n")
- fp.write(b"\000" * (511 - fp.tell()) + b"\032")
- if im.mode in ["P", "PA"]:
- fp.write(im.im.getpalette("RGB", "RGB;L")) # 768 bytes
- ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, -1))])
-
-
-#
-# --------------------------------------------------------------------
-# Registry
-
-
-Image.register_open(ImImageFile.format, ImImageFile)
-Image.register_save(ImImageFile.format, _save)
-
-Image.register_extension(ImImageFile.format, ".im")
diff --git a/venv/Lib/site-packages/PIL/Image.py b/venv/Lib/site-packages/PIL/Image.py
deleted file mode 100644
index 813ac52..0000000
--- a/venv/Lib/site-packages/PIL/Image.py
+++ /dev/null
@@ -1,3697 +0,0 @@
-#
-# The Python Imaging Library.
-# $Id$
-#
-# the Image class wrapper
-#
-# partial release history:
-# 1995-09-09 fl Created
-# 1996-03-11 fl PIL release 0.0 (proof of concept)
-# 1996-04-30 fl PIL release 0.1b1
-# 1999-07-28 fl PIL release 1.0 final
-# 2000-06-07 fl PIL release 1.1
-# 2000-10-20 fl PIL release 1.1.1
-# 2001-05-07 fl PIL release 1.1.2
-# 2002-03-15 fl PIL release 1.1.3
-# 2003-05-10 fl PIL release 1.1.4
-# 2005-03-28 fl PIL release 1.1.5
-# 2006-12-02 fl PIL release 1.1.6
-# 2009-11-15 fl PIL release 1.1.7
-#
-# Copyright (c) 1997-2009 by Secret Labs AB. All rights reserved.
-# Copyright (c) 1995-2009 by Fredrik Lundh.
-#
-# See the README file for information on usage and redistribution.
-#
-
-import atexit
-import builtins
-import io
-import logging
-import math
-import numbers
-import os
-import re
-import struct
-import sys
-import tempfile
-import warnings
-from collections.abc import Callable, MutableMapping
-from enum import IntEnum
-from pathlib import Path
-
-try:
- import defusedxml.ElementTree as ElementTree
-except ImportError:
- ElementTree = None
-
-# VERSION was removed in Pillow 6.0.0.
-# PILLOW_VERSION was removed in Pillow 9.0.0.
-# Use __version__ instead.
-from . import ImageMode, TiffTags, UnidentifiedImageError, __version__, _plugins
-from ._binary import i32le, o32be, o32le
-from ._util import deferred_error, isPath
-
-
-def __getattr__(name):
- deprecated = "deprecated and will be removed in Pillow 10 (2023-07-01). "
- categories = {"NORMAL": 0, "SEQUENCE": 1, "CONTAINER": 2}
- if name in categories:
- warnings.warn(
- "Image categories are " + deprecated + "Use is_animated instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- return categories[name]
- elif name in ("NEAREST", "NONE"):
- warnings.warn(
- name
- + " is "
- + deprecated
- + "Use Resampling.NEAREST or Dither.NONE instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- return 0
- old_resampling = {
- "LINEAR": "BILINEAR",
- "CUBIC": "BICUBIC",
- "ANTIALIAS": "LANCZOS",
- }
- if name in old_resampling:
- warnings.warn(
- name
- + " is "
- + deprecated
- + "Use Resampling."
- + old_resampling[name]
- + " instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- return Resampling[old_resampling[name]]
- for enum in (Transpose, Transform, Resampling, Dither, Palette, Quantize):
- if name in enum.__members__:
- warnings.warn(
- name
- + " is "
- + deprecated
- + "Use "
- + enum.__name__
- + "."
- + name
- + " instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- return enum[name]
- raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
-
-
-logger = logging.getLogger(__name__)
-
-
-class DecompressionBombWarning(RuntimeWarning):
- pass
-
-
-class DecompressionBombError(Exception):
- pass
-
-
-# Limit to around a quarter gigabyte for a 24-bit (3 bpp) image
-MAX_IMAGE_PIXELS = int(1024 * 1024 * 1024 // 4 // 3)
-
-
-try:
- # If the _imaging C module is not present, Pillow will not load.
- # Note that other modules should not refer to _imaging directly;
- # import Image and use the Image.core variable instead.
- # Also note that Image.core is not a publicly documented interface,
- # and should be considered private and subject to change.
- from . import _imaging as core
-
- if __version__ != getattr(core, "PILLOW_VERSION", None):
- raise ImportError(
- "The _imaging extension was built for another version of Pillow or PIL:\n"
- f"Core version: {getattr(core, 'PILLOW_VERSION', None)}\n"
- f"Pillow version: {__version__}"
- )
-
-except ImportError as v:
- core = deferred_error(ImportError("The _imaging C module is not installed."))
- # Explanations for ways that we know we might have an import error
- if str(v).startswith("Module use of python"):
- # The _imaging C module is present, but not compiled for
- # the right version (windows only). Print a warning, if
- # possible.
- warnings.warn(
- "The _imaging extension was built for another version of Python.",
- RuntimeWarning,
- )
- elif str(v).startswith("The _imaging extension"):
- warnings.warn(str(v), RuntimeWarning)
- # Fail here anyway. Don't let people run with a mostly broken Pillow.
- # see docs/porting.rst
- raise
-
-
-# works everywhere, win for pypy, not cpython
-USE_CFFI_ACCESS = hasattr(sys, "pypy_version_info")
-try:
- import cffi
-except ImportError:
- cffi = None
-
-
-def isImageType(t):
- """
- Checks if an object is an image object.
-
- .. warning::
-
- This function is for internal use only.
-
- :param t: object to check if it's an image
- :returns: True if the object is an image
- """
- return hasattr(t, "im")
-
-
-#
-# Constants
-
-# transpose
-class Transpose(IntEnum):
- FLIP_LEFT_RIGHT = 0
- FLIP_TOP_BOTTOM = 1
- ROTATE_90 = 2
- ROTATE_180 = 3
- ROTATE_270 = 4
- TRANSPOSE = 5
- TRANSVERSE = 6
-
-
-# transforms (also defined in Imaging.h)
-class Transform(IntEnum):
- AFFINE = 0
- EXTENT = 1
- PERSPECTIVE = 2
- QUAD = 3
- MESH = 4
-
-
-# resampling filters (also defined in Imaging.h)
-class Resampling(IntEnum):
- NEAREST = 0
- BOX = 4
- BILINEAR = 2
- HAMMING = 5
- BICUBIC = 3
- LANCZOS = 1
-
-
-_filters_support = {
- Resampling.BOX: 0.5,
- Resampling.BILINEAR: 1.0,
- Resampling.HAMMING: 1.0,
- Resampling.BICUBIC: 2.0,
- Resampling.LANCZOS: 3.0,
-}
-
-
-# dithers
-class Dither(IntEnum):
- NONE = 0
- ORDERED = 1 # Not yet implemented
- RASTERIZE = 2 # Not yet implemented
- FLOYDSTEINBERG = 3 # default
-
-
-# palettes/quantizers
-class Palette(IntEnum):
- WEB = 0
- ADAPTIVE = 1
-
-
-class Quantize(IntEnum):
- MEDIANCUT = 0
- MAXCOVERAGE = 1
- FASTOCTREE = 2
- LIBIMAGEQUANT = 3
-
-
-if hasattr(core, "DEFAULT_STRATEGY"):
- DEFAULT_STRATEGY = core.DEFAULT_STRATEGY
- FILTERED = core.FILTERED
- HUFFMAN_ONLY = core.HUFFMAN_ONLY
- RLE = core.RLE
- FIXED = core.FIXED
-
-
-# --------------------------------------------------------------------
-# Registries
-
-ID = []
-OPEN = {}
-MIME = {}
-SAVE = {}
-SAVE_ALL = {}
-EXTENSION = {}
-DECODERS = {}
-ENCODERS = {}
-
-# --------------------------------------------------------------------
-# Modes
-
-_ENDIAN = "<" if sys.byteorder == "little" else ">"
-
-
-def _conv_type_shape(im):
- m = ImageMode.getmode(im.mode)
- shape = (im.height, im.width)
- extra = len(m.bands)
- if extra != 1:
- shape += (extra,)
- return shape, m.typestr
-
-
-MODES = ["1", "CMYK", "F", "HSV", "I", "L", "LAB", "P", "RGB", "RGBA", "RGBX", "YCbCr"]
-
-# raw modes that may be memory mapped. NOTE: if you change this, you
-# may have to modify the stride calculation in map.c too!
-_MAPMODES = ("L", "P", "RGBX", "RGBA", "CMYK", "I;16", "I;16L", "I;16B")
-
-
-def getmodebase(mode):
- """
- Gets the "base" mode for given mode. This function returns "L" for
- images that contain grayscale data, and "RGB" for images that
- contain color data.
-
- :param mode: Input mode.
- :returns: "L" or "RGB".
- :exception KeyError: If the input mode was not a standard mode.
- """
- return ImageMode.getmode(mode).basemode
-
-
-def getmodetype(mode):
- """
- Gets the storage type mode. Given a mode, this function returns a
- single-layer mode suitable for storing individual bands.
-
- :param mode: Input mode.
- :returns: "L", "I", or "F".
- :exception KeyError: If the input mode was not a standard mode.
- """
- return ImageMode.getmode(mode).basetype
-
-
-def getmodebandnames(mode):
- """
- Gets a list of individual band names. Given a mode, this function returns
- a tuple containing the names of individual bands (use
- :py:method:`~PIL.Image.getmodetype` to get the mode used to store each
- individual band.
-
- :param mode: Input mode.
- :returns: A tuple containing band names. The length of the tuple
- gives the number of bands in an image of the given mode.
- :exception KeyError: If the input mode was not a standard mode.
- """
- return ImageMode.getmode(mode).bands
-
-
-def getmodebands(mode):
- """
- Gets the number of individual bands for this mode.
-
- :param mode: Input mode.
- :returns: The number of bands in this mode.
- :exception KeyError: If the input mode was not a standard mode.
- """
- return len(ImageMode.getmode(mode).bands)
-
-
-# --------------------------------------------------------------------
-# Helpers
-
-_initialized = 0
-
-
-def preinit():
- """Explicitly load standard file format drivers."""
-
- global _initialized
- if _initialized >= 1:
- return
-
- try:
- from . import BmpImagePlugin
-
- assert BmpImagePlugin
- except ImportError:
- pass
- try:
- from . import GifImagePlugin
-
- assert GifImagePlugin
- except ImportError:
- pass
- try:
- from . import JpegImagePlugin
-
- assert JpegImagePlugin
- except ImportError:
- pass
- try:
- from . import PpmImagePlugin
-
- assert PpmImagePlugin
- except ImportError:
- pass
- try:
- from . import PngImagePlugin
-
- assert PngImagePlugin
- except ImportError:
- pass
- # try:
- # import TiffImagePlugin
- # assert TiffImagePlugin
- # except ImportError:
- # pass
-
- _initialized = 1
-
-
-def init():
- """
- Explicitly initializes the Python Imaging Library. This function
- loads all available file format drivers.
- """
-
- global _initialized
- if _initialized >= 2:
- return 0
-
- for plugin in _plugins:
- try:
- logger.debug("Importing %s", plugin)
- __import__(f"PIL.{plugin}", globals(), locals(), [])
- except ImportError as e:
- logger.debug("Image: failed to import %s: %s", plugin, e)
-
- if OPEN or SAVE:
- _initialized = 2
- return 1
-
-
-# --------------------------------------------------------------------
-# Codec factories (used by tobytes/frombytes and ImageFile.load)
-
-
-def _getdecoder(mode, decoder_name, args, extra=()):
-
- # tweak arguments
- if args is None:
- args = ()
- elif not isinstance(args, tuple):
- args = (args,)
-
- try:
- decoder = DECODERS[decoder_name]
- except KeyError:
- pass
- else:
- return decoder(mode, *args + extra)
-
- try:
- # get decoder
- decoder = getattr(core, decoder_name + "_decoder")
- except AttributeError as e:
- raise OSError(f"decoder {decoder_name} not available") from e
- return decoder(mode, *args + extra)
-
-
-def _getencoder(mode, encoder_name, args, extra=()):
-
- # tweak arguments
- if args is None:
- args = ()
- elif not isinstance(args, tuple):
- args = (args,)
-
- try:
- encoder = ENCODERS[encoder_name]
- except KeyError:
- pass
- else:
- return encoder(mode, *args + extra)
-
- try:
- # get encoder
- encoder = getattr(core, encoder_name + "_encoder")
- except AttributeError as e:
- raise OSError(f"encoder {encoder_name} not available") from e
- return encoder(mode, *args + extra)
-
-
-# --------------------------------------------------------------------
-# Simple expression analyzer
-
-
-def coerce_e(value):
- return value if isinstance(value, _E) else _E(value)
-
-
-class _E:
- def __init__(self, data):
- self.data = data
-
- def __add__(self, other):
- return _E((self.data, "__add__", coerce_e(other).data))
-
- def __mul__(self, other):
- return _E((self.data, "__mul__", coerce_e(other).data))
-
-
-def _getscaleoffset(expr):
- stub = ["stub"]
- data = expr(_E(stub)).data
- try:
- (a, b, c) = data # simplified syntax
- if a is stub and b == "__mul__" and isinstance(c, numbers.Number):
- return c, 0.0
- if a is stub and b == "__add__" and isinstance(c, numbers.Number):
- return 1.0, c
- except TypeError:
- pass
- try:
- ((a, b, c), d, e) = data # full syntax
- if (
- a is stub
- and b == "__mul__"
- and isinstance(c, numbers.Number)
- and d == "__add__"
- and isinstance(e, numbers.Number)
- ):
- return c, e
- except TypeError:
- pass
- raise ValueError("illegal expression")
-
-
-# --------------------------------------------------------------------
-# Implementation wrapper
-
-
-class Image:
- """
- This class represents an image object. To create
- :py:class:`~PIL.Image.Image` objects, use the appropriate factory
- functions. There's hardly ever any reason to call the Image constructor
- directly.
-
- * :py:func:`~PIL.Image.open`
- * :py:func:`~PIL.Image.new`
- * :py:func:`~PIL.Image.frombytes`
- """
-
- format = None
- format_description = None
- _close_exclusive_fp_after_loading = True
-
- def __init__(self):
- # FIXME: take "new" parameters / other image?
- # FIXME: turn mode and size into delegating properties?
- self.im = None
- self.mode = ""
- self._size = (0, 0)
- self.palette = None
- self.info = {}
- self._category = 0
- self.readonly = 0
- self.pyaccess = None
- self._exif = None
-
- def __getattr__(self, name):
- if name == "category":
- warnings.warn(
- "Image categories are deprecated and will be removed in Pillow 10 "
- "(2023-07-01). Use is_animated instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- return self._category
- raise AttributeError(name)
-
- @property
- def width(self):
- return self.size[0]
-
- @property
- def height(self):
- return self.size[1]
-
- @property
- def size(self):
- return self._size
-
- def _new(self, im):
- new = Image()
- new.im = im
- new.mode = im.mode
- new._size = im.size
- if im.mode in ("P", "PA"):
- if self.palette:
- new.palette = self.palette.copy()
- else:
- from . import ImagePalette
-
- new.palette = ImagePalette.ImagePalette()
- new.info = self.info.copy()
- return new
-
- # Context manager support
- def __enter__(self):
- return self
-
- def __exit__(self, *args):
- if hasattr(self, "fp") and getattr(self, "_exclusive_fp", False):
- if hasattr(self, "_close__fp"):
- self._close__fp()
- if self.fp:
- self.fp.close()
- self.fp = None
-
- def close(self):
- """
- Closes the file pointer, if possible.
-
- This operation will destroy the image core and release its memory.
- The image data will be unusable afterward.
-
- This function is required to close images that have multiple frames or
- have not had their file read and closed by the
- :py:meth:`~PIL.Image.Image.load` method. See :ref:`file-handling` for
- more information.
- """
- try:
- if hasattr(self, "_close__fp"):
- self._close__fp()
- if self.fp:
- self.fp.close()
- self.fp = None
- except Exception as msg:
- logger.debug("Error closing: %s", msg)
-
- if getattr(self, "map", None):
- self.map = None
-
- # Instead of simply setting to None, we're setting up a
- # deferred error that will better explain that the core image
- # object is gone.
- self.im = deferred_error(ValueError("Operation on closed image"))
-
- def _copy(self):
- self.load()
- self.im = self.im.copy()
- self.pyaccess = None
- self.readonly = 0
-
- def _ensure_mutable(self):
- if self.readonly:
- self._copy()
- else:
- self.load()
-
- def _dump(self, file=None, format=None, **options):
- suffix = ""
- if format:
- suffix = "." + format
-
- if not file:
- f, filename = tempfile.mkstemp(suffix)
- os.close(f)
- else:
- filename = file
- if not filename.endswith(suffix):
- filename = filename + suffix
-
- self.load()
-
- if not format or format == "PPM":
- self.im.save_ppm(filename)
- else:
- self.save(filename, format, **options)
-
- return filename
-
- def __eq__(self, other):
- return (
- self.__class__ is other.__class__
- and self.mode == other.mode
- and self.size == other.size
- and self.info == other.info
- and self._category == other._category
- and self.getpalette() == other.getpalette()
- and self.tobytes() == other.tobytes()
- )
-
- def __repr__(self):
- return "<%s.%s image mode=%s size=%dx%d at 0x%X>" % (
- self.__class__.__module__,
- self.__class__.__name__,
- self.mode,
- self.size[0],
- self.size[1],
- id(self),
- )
-
- def _repr_pretty_(self, p, cycle):
- """IPython plain text display support"""
-
- # Same as __repr__ but without unpredicatable id(self),
- # to keep Jupyter notebook `text/plain` output stable.
- p.text(
- "<%s.%s image mode=%s size=%dx%d>"
- % (
- self.__class__.__module__,
- self.__class__.__name__,
- self.mode,
- self.size[0],
- self.size[1],
- )
- )
-
- def _repr_png_(self):
- """iPython display hook support
-
- :returns: png version of the image as bytes
- """
- b = io.BytesIO()
- try:
- self.save(b, "PNG")
- except Exception as e:
- raise ValueError("Could not save to PNG for display") from e
- return b.getvalue()
-
- class _ArrayData:
- def __init__(self, new):
- self.__array_interface__ = new
-
- def __array__(self, dtype=None):
- # numpy array interface support
- import numpy as np
-
- new = {}
- shape, typestr = _conv_type_shape(self)
- new["shape"] = shape
- new["typestr"] = typestr
- new["version"] = 3
- if self.mode == "1":
- # Binary images need to be extended from bits to bytes
- # See: https://github.com/python-pillow/Pillow/issues/350
- new["data"] = self.tobytes("raw", "L")
- else:
- new["data"] = self.tobytes()
-
- return np.array(self._ArrayData(new), dtype)
-
- def __getstate__(self):
- return [self.info, self.mode, self.size, self.getpalette(), self.tobytes()]
-
- def __setstate__(self, state):
- Image.__init__(self)
- self.tile = []
- info, mode, size, palette, data = state
- self.info = info
- self.mode = mode
- self._size = size
- self.im = core.new(mode, size)
- if mode in ("L", "LA", "P", "PA") and palette:
- self.putpalette(palette)
- self.frombytes(data)
-
- def tobytes(self, encoder_name="raw", *args):
- """
- Return image as a bytes object.
-
- .. warning::
-
- This method returns the raw image data from the internal
- storage. For compressed image data (e.g. PNG, JPEG) use
- :meth:`~.save`, with a BytesIO parameter for in-memory
- data.
-
- :param encoder_name: What encoder to use. The default is to
- use the standard "raw" encoder.
- :param args: Extra arguments to the encoder.
- :returns: A :py:class:`bytes` object.
- """
-
- # may pass tuple instead of argument list
- if len(args) == 1 and isinstance(args[0], tuple):
- args = args[0]
-
- if encoder_name == "raw" and args == ():
- args = self.mode
-
- self.load()
-
- if self.width == 0 or self.height == 0:
- return b""
-
- # unpack data
- e = _getencoder(self.mode, encoder_name, args)
- e.setimage(self.im)
-
- bufsize = max(65536, self.size[0] * 4) # see RawEncode.c
-
- data = []
- while True:
- l, s, d = e.encode(bufsize)
- data.append(d)
- if s:
- break
- if s < 0:
- raise RuntimeError(f"encoder error {s} in tobytes")
-
- return b"".join(data)
-
- def tobitmap(self, name="image"):
- """
- Returns the image converted to an X11 bitmap.
-
- .. note:: This method only works for mode "1" images.
-
- :param name: The name prefix to use for the bitmap variables.
- :returns: A string containing an X11 bitmap.
- :raises ValueError: If the mode is not "1"
- """
-
- self.load()
- if self.mode != "1":
- raise ValueError("not a bitmap")
- data = self.tobytes("xbm")
- return b"".join(
- [
- f"#define {name}_width {self.size[0]}\n".encode("ascii"),
- f"#define {name}_height {self.size[1]}\n".encode("ascii"),
- f"static char {name}_bits[] = {{\n".encode("ascii"),
- data,
- b"};",
- ]
- )
-
- def frombytes(self, data, decoder_name="raw", *args):
- """
- Loads this image with pixel data from a bytes object.
-
- This method is similar to the :py:func:`~PIL.Image.frombytes` function,
- but loads data into this image instead of creating a new image object.
- """
-
- # may pass tuple instead of argument list
- if len(args) == 1 and isinstance(args[0], tuple):
- args = args[0]
-
- # default format
- if decoder_name == "raw" and args == ():
- args = self.mode
-
- # unpack data
- d = _getdecoder(self.mode, decoder_name, args)
- d.setimage(self.im)
- s = d.decode(data)
-
- if s[0] >= 0:
- raise ValueError("not enough image data")
- if s[1] != 0:
- raise ValueError("cannot decode image data")
-
- def load(self):
- """
- Allocates storage for the image and loads the pixel data. In
- normal cases, you don't need to call this method, since the
- Image class automatically loads an opened image when it is
- accessed for the first time.
-
- If the file associated with the image was opened by Pillow, then this
- method will close it. The exception to this is if the image has
- multiple frames, in which case the file will be left open for seek
- operations. See :ref:`file-handling` for more information.
-
- :returns: An image access object.
- :rtype: :ref:`PixelAccess` or :py:class:`PIL.PyAccess`
- """
- if self.im is not None and self.palette and self.palette.dirty:
- # realize palette
- mode, arr = self.palette.getdata()
- self.im.putpalette(mode, arr)
- self.palette.dirty = 0
- self.palette.rawmode = None
- if "transparency" in self.info and mode in ("LA", "PA"):
- if isinstance(self.info["transparency"], int):
- self.im.putpalettealpha(self.info["transparency"], 0)
- else:
- self.im.putpalettealphas(self.info["transparency"])
- self.palette.mode = "RGBA"
- else:
- palette_mode = "RGBA" if mode.startswith("RGBA") else "RGB"
- self.palette.mode = palette_mode
- self.palette.palette = self.im.getpalette(palette_mode, palette_mode)
-
- if self.im is not None:
- if cffi and USE_CFFI_ACCESS:
- if self.pyaccess:
- return self.pyaccess
- from . import PyAccess
-
- self.pyaccess = PyAccess.new(self, self.readonly)
- if self.pyaccess:
- return self.pyaccess
- return self.im.pixel_access(self.readonly)
-
- def verify(self):
- """
- Verifies the contents of a file. For data read from a file, this
- method attempts to determine if the file is broken, without
- actually decoding the image data. If this method finds any
- problems, it raises suitable exceptions. If you need to load
- the image after using this method, you must reopen the image
- file.
- """
- pass
-
- def convert(
- self, mode=None, matrix=None, dither=None, palette=Palette.WEB, colors=256
- ):
- """
- Returns a converted copy of this image. For the "P" mode, this
- method translates pixels through the palette. If mode is
- omitted, a mode is chosen so that all information in the image
- and the palette can be represented without a palette.
-
- The current version supports all possible conversions between
- "L", "RGB" and "CMYK." The ``matrix`` argument only supports "L"
- and "RGB".
-
- When translating a color image to greyscale (mode "L"),
- the library uses the ITU-R 601-2 luma transform::
-
- L = R * 299/1000 + G * 587/1000 + B * 114/1000
-
- The default method of converting a greyscale ("L") or "RGB"
- image into a bilevel (mode "1") image uses Floyd-Steinberg
- dither to approximate the original image luminosity levels. If
- dither is ``None``, all values larger than 127 are set to 255 (white),
- all other values to 0 (black). To use other thresholds, use the
- :py:meth:`~PIL.Image.Image.point` method.
-
- When converting from "RGBA" to "P" without a ``matrix`` argument,
- this passes the operation to :py:meth:`~PIL.Image.Image.quantize`,
- and ``dither`` and ``palette`` are ignored.
-
- :param mode: The requested mode. See: :ref:`concept-modes`.
- :param matrix: An optional conversion matrix. If given, this
- should be 4- or 12-tuple containing floating point values.
- :param dither: Dithering method, used when converting from
- mode "RGB" to "P" or from "RGB" or "L" to "1".
- Available methods are :data:`Dither.NONE` or :data:`Dither.FLOYDSTEINBERG`
- (default). Note that this is not used when ``matrix`` is supplied.
- :param palette: Palette to use when converting from mode "RGB"
- to "P". Available palettes are :data:`Palette.WEB` or
- :data:`Palette.ADAPTIVE`.
- :param colors: Number of colors to use for the :data:`Palette.ADAPTIVE`
- palette. Defaults to 256.
- :rtype: :py:class:`~PIL.Image.Image`
- :returns: An :py:class:`~PIL.Image.Image` object.
- """
-
- self.load()
-
- has_transparency = self.info.get("transparency") is not None
- if not mode and self.mode == "P":
- # determine default mode
- if self.palette:
- mode = self.palette.mode
- else:
- mode = "RGB"
- if mode == "RGB" and has_transparency:
- mode = "RGBA"
- if not mode or (mode == self.mode and not matrix):
- return self.copy()
-
- if matrix:
- # matrix conversion
- if mode not in ("L", "RGB"):
- raise ValueError("illegal conversion")
- im = self.im.convert_matrix(mode, matrix)
- new = self._new(im)
- if has_transparency and self.im.bands == 3:
- transparency = new.info["transparency"]
-
- def convert_transparency(m, v):
- v = m[0] * v[0] + m[1] * v[1] + m[2] * v[2] + m[3] * 0.5
- return max(0, min(255, int(v)))
-
- if mode == "L":
- transparency = convert_transparency(matrix, transparency)
- elif len(mode) == 3:
- transparency = tuple(
- convert_transparency(matrix[i * 4 : i * 4 + 4], transparency)
- for i in range(0, len(transparency))
- )
- new.info["transparency"] = transparency
- return new
-
- if mode == "P" and self.mode == "RGBA":
- return self.quantize(colors)
-
- trns = None
- delete_trns = False
- # transparency handling
- if has_transparency:
- if (self.mode in ("1", "L", "I") and mode in ("LA", "RGBA")) or (
- self.mode == "RGB" and mode == "RGBA"
- ):
- # Use transparent conversion to promote from transparent
- # color to an alpha channel.
- new_im = self._new(
- self.im.convert_transparent(mode, self.info["transparency"])
- )
- del new_im.info["transparency"]
- return new_im
- elif self.mode in ("L", "RGB", "P") and mode in ("L", "RGB", "P"):
- t = self.info["transparency"]
- if isinstance(t, bytes):
- # Dragons. This can't be represented by a single color
- warnings.warn(
- "Palette images with Transparency expressed in bytes should be "
- "converted to RGBA images"
- )
- delete_trns = True
- else:
- # get the new transparency color.
- # use existing conversions
- trns_im = Image()._new(core.new(self.mode, (1, 1)))
- if self.mode == "P":
- trns_im.putpalette(self.palette)
- if isinstance(t, tuple):
- err = "Couldn't allocate a palette color for transparency"
- try:
- t = trns_im.palette.getcolor(t, self)
- except ValueError as e:
- if str(e) == "cannot allocate more than 256 colors":
- # If all 256 colors are in use,
- # then there is no need for transparency
- t = None
- else:
- raise ValueError(err) from e
- if t is None:
- trns = None
- else:
- trns_im.putpixel((0, 0), t)
-
- if mode in ("L", "RGB"):
- trns_im = trns_im.convert(mode)
- else:
- # can't just retrieve the palette number, got to do it
- # after quantization.
- trns_im = trns_im.convert("RGB")
- trns = trns_im.getpixel((0, 0))
-
- elif self.mode == "P" and mode in ("LA", "PA", "RGBA"):
- t = self.info["transparency"]
- delete_trns = True
-
- if isinstance(t, bytes):
- self.im.putpalettealphas(t)
- elif isinstance(t, int):
- self.im.putpalettealpha(t, 0)
- else:
- raise ValueError("Transparency for P mode should be bytes or int")
-
- if mode == "P" and palette == Palette.ADAPTIVE:
- im = self.im.quantize(colors)
- new = self._new(im)
- from . import ImagePalette
-
- new.palette = ImagePalette.ImagePalette("RGB", new.im.getpalette("RGB"))
- if delete_trns:
- # This could possibly happen if we requantize to fewer colors.
- # The transparency would be totally off in that case.
- del new.info["transparency"]
- if trns is not None:
- try:
- new.info["transparency"] = new.palette.getcolor(trns, new)
- except Exception:
- # if we can't make a transparent color, don't leave the old
- # transparency hanging around to mess us up.
- del new.info["transparency"]
- warnings.warn("Couldn't allocate palette entry for transparency")
- return new
-
- # colorspace conversion
- if dither is None:
- dither = Dither.FLOYDSTEINBERG
-
- try:
- im = self.im.convert(mode, dither)
- except ValueError:
- try:
- # normalize source image and try again
- im = self.im.convert(getmodebase(self.mode))
- im = im.convert(mode, dither)
- except KeyError as e:
- raise ValueError("illegal conversion") from e
-
- new_im = self._new(im)
- if mode == "P" and palette != Palette.ADAPTIVE:
- from . import ImagePalette
-
- new_im.palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3)
- if delete_trns:
- # crash fail if we leave a bytes transparency in an rgb/l mode.
- del new_im.info["transparency"]
- if trns is not None:
- if new_im.mode == "P":
- try:
- new_im.info["transparency"] = new_im.palette.getcolor(trns, new_im)
- except ValueError as e:
- del new_im.info["transparency"]
- if str(e) != "cannot allocate more than 256 colors":
- # If all 256 colors are in use,
- # then there is no need for transparency
- warnings.warn(
- "Couldn't allocate palette entry for transparency"
- )
- else:
- new_im.info["transparency"] = trns
- return new_im
-
- def quantize(
- self,
- colors=256,
- method=None,
- kmeans=0,
- palette=None,
- dither=Dither.FLOYDSTEINBERG,
- ):
- """
- Convert the image to 'P' mode with the specified number
- of colors.
-
- :param colors: The desired number of colors, <= 256
- :param method: :data:`Quantize.MEDIANCUT` (median cut),
- :data:`Quantize.MAXCOVERAGE` (maximum coverage),
- :data:`Quantize.FASTOCTREE` (fast octree),
- :data:`Quantize.LIBIMAGEQUANT` (libimagequant; check support
- using :py:func:`PIL.features.check_feature` with
- ``feature="libimagequant"``).
-
- By default, :data:`Quantize.MEDIANCUT` will be used.
-
- The exception to this is RGBA images. :data:`Quantize.MEDIANCUT`
- and :data:`Quantize.MAXCOVERAGE` do not support RGBA images, so
- :data:`Quantize.FASTOCTREE` is used by default instead.
- :param kmeans: Integer
- :param palette: Quantize to the palette of given
- :py:class:`PIL.Image.Image`.
- :param dither: Dithering method, used when converting from
- mode "RGB" to "P" or from "RGB" or "L" to "1".
- Available methods are :data:`Dither.NONE` or :data:`Dither.FLOYDSTEINBERG`
- (default).
- :returns: A new image
-
- """
-
- self.load()
-
- if method is None:
- # defaults:
- method = Quantize.MEDIANCUT
- if self.mode == "RGBA":
- method = Quantize.FASTOCTREE
-
- if self.mode == "RGBA" and method not in (
- Quantize.FASTOCTREE,
- Quantize.LIBIMAGEQUANT,
- ):
- # Caller specified an invalid mode.
- raise ValueError(
- "Fast Octree (method == 2) and libimagequant (method == 3) "
- "are the only valid methods for quantizing RGBA images"
- )
-
- if palette:
- # use palette from reference image
- palette.load()
- if palette.mode != "P":
- raise ValueError("bad mode for palette image")
- if self.mode != "RGB" and self.mode != "L":
- raise ValueError(
- "only RGB or L mode images can be quantized to a palette"
- )
- im = self.im.convert("P", dither, palette.im)
- new_im = self._new(im)
- new_im.palette = palette.palette.copy()
- return new_im
-
- im = self._new(self.im.quantize(colors, method, kmeans))
-
- from . import ImagePalette
-
- mode = im.im.getpalettemode()
- palette = im.im.getpalette(mode, mode)[: colors * len(mode)]
- im.palette = ImagePalette.ImagePalette(mode, palette)
-
- return im
-
- def copy(self):
- """
- Copies this image. Use this method if you wish to paste things
- into an image, but still retain the original.
-
- :rtype: :py:class:`~PIL.Image.Image`
- :returns: An :py:class:`~PIL.Image.Image` object.
- """
- self.load()
- return self._new(self.im.copy())
-
- __copy__ = copy
-
- def crop(self, box=None):
- """
- Returns a rectangular region from this image. The box is a
- 4-tuple defining the left, upper, right, and lower pixel
- coordinate. See :ref:`coordinate-system`.
-
- Note: Prior to Pillow 3.4.0, this was a lazy operation.
-
- :param box: The crop rectangle, as a (left, upper, right, lower)-tuple.
- :rtype: :py:class:`~PIL.Image.Image`
- :returns: An :py:class:`~PIL.Image.Image` object.
- """
-
- if box is None:
- return self.copy()
-
- if box[2] < box[0]:
- raise ValueError("Coordinate 'right' is less than 'left'")
- elif box[3] < box[1]:
- raise ValueError("Coordinate 'lower' is less than 'upper'")
-
- self.load()
- return self._new(self._crop(self.im, box))
-
- def _crop(self, im, box):
- """
- Returns a rectangular region from the core image object im.
-
- This is equivalent to calling im.crop((x0, y0, x1, y1)), but
- includes additional sanity checks.
-
- :param im: a core image object
- :param box: The crop rectangle, as a (left, upper, right, lower)-tuple.
- :returns: A core image object.
- """
-
- x0, y0, x1, y1 = map(int, map(round, box))
-
- absolute_values = (abs(x1 - x0), abs(y1 - y0))
-
- _decompression_bomb_check(absolute_values)
-
- return im.crop((x0, y0, x1, y1))
-
- def draft(self, mode, size):
- """
- Configures the image file loader so it returns a version of the
- image that as closely as possible matches the given mode and
- size. For example, you can use this method to convert a color
- JPEG to greyscale while loading it.
-
- If any changes are made, returns a tuple with the chosen ``mode`` and
- ``box`` with coordinates of the original image within the altered one.
-
- Note that this method modifies the :py:class:`~PIL.Image.Image` object
- in place. If the image has already been loaded, this method has no
- effect.
-
- Note: This method is not implemented for most images. It is
- currently implemented only for JPEG and MPO images.
-
- :param mode: The requested mode.
- :param size: The requested size.
- """
- pass
-
- def _expand(self, xmargin, ymargin=None):
- if ymargin is None:
- ymargin = xmargin
- self.load()
- return self._new(self.im.expand(xmargin, ymargin, 0))
-
- def filter(self, filter):
- """
- Filters this image using the given filter. For a list of
- available filters, see the :py:mod:`~PIL.ImageFilter` module.
-
- :param filter: Filter kernel.
- :returns: An :py:class:`~PIL.Image.Image` object."""
-
- from . import ImageFilter
-
- self.load()
-
- if isinstance(filter, Callable):
- filter = filter()
- if not hasattr(filter, "filter"):
- raise TypeError(
- "filter argument should be ImageFilter.Filter instance or class"
- )
-
- multiband = isinstance(filter, ImageFilter.MultibandFilter)
- if self.im.bands == 1 or multiband:
- return self._new(filter.filter(self.im))
-
- ims = []
- for c in range(self.im.bands):
- ims.append(self._new(filter.filter(self.im.getband(c))))
- return merge(self.mode, ims)
-
- def getbands(self):
- """
- Returns a tuple containing the name of each band in this image.
- For example, ``getbands`` on an RGB image returns ("R", "G", "B").
-
- :returns: A tuple containing band names.
- :rtype: tuple
- """
- return ImageMode.getmode(self.mode).bands
-
- def getbbox(self):
- """
- Calculates the bounding box of the non-zero regions in the
- image.
-
- :returns: The bounding box is returned as a 4-tuple defining the
- left, upper, right, and lower pixel coordinate. See
- :ref:`coordinate-system`. If the image is completely empty, this
- method returns None.
-
- """
-
- self.load()
- return self.im.getbbox()
-
- def getcolors(self, maxcolors=256):
- """
- Returns a list of colors used in this image.
-
- The colors will be in the image's mode. For example, an RGB image will
- return a tuple of (red, green, blue) color values, and a P image will
- return the index of the color in the palette.
-
- :param maxcolors: Maximum number of colors. If this number is
- exceeded, this method returns None. The default limit is
- 256 colors.
- :returns: An unsorted list of (count, pixel) values.
- """
-
- self.load()
- if self.mode in ("1", "L", "P"):
- h = self.im.histogram()
- out = []
- for i in range(256):
- if h[i]:
- out.append((h[i], i))
- if len(out) > maxcolors:
- return None
- return out
- return self.im.getcolors(maxcolors)
-
- def getdata(self, band=None):
- """
- Returns the contents of this image as a sequence object
- containing pixel values. The sequence object is flattened, so
- that values for line one follow directly after the values of
- line zero, and so on.
-
- Note that the sequence object returned by this method is an
- internal PIL data type, which only supports certain sequence
- operations. To convert it to an ordinary sequence (e.g. for
- printing), use ``list(im.getdata())``.
-
- :param band: What band to return. The default is to return
- all bands. To return a single band, pass in the index
- value (e.g. 0 to get the "R" band from an "RGB" image).
- :returns: A sequence-like object.
- """
-
- self.load()
- if band is not None:
- return self.im.getband(band)
- return self.im # could be abused
-
- def getextrema(self):
- """
- Gets the the minimum and maximum pixel values for each band in
- the image.
-
- :returns: For a single-band image, a 2-tuple containing the
- minimum and maximum pixel value. For a multi-band image,
- a tuple containing one 2-tuple for each band.
- """
-
- self.load()
- if self.im.bands > 1:
- extrema = []
- for i in range(self.im.bands):
- extrema.append(self.im.getband(i).getextrema())
- return tuple(extrema)
- return self.im.getextrema()
-
- def _getxmp(self, xmp_tags):
- def get_name(tag):
- return tag.split("}")[1]
-
- def get_value(element):
- value = {get_name(k): v for k, v in element.attrib.items()}
- children = list(element)
- if children:
- for child in children:
- name = get_name(child.tag)
- child_value = get_value(child)
- if name in value:
- if not isinstance(value[name], list):
- value[name] = [value[name]]
- value[name].append(child_value)
- else:
- value[name] = child_value
- elif value:
- if element.text:
- value["text"] = element.text
- else:
- return element.text
- return value
-
- if ElementTree is None:
- warnings.warn("XMP data cannot be read without defusedxml dependency")
- return {}
- else:
- root = ElementTree.fromstring(xmp_tags)
- return {get_name(root.tag): get_value(root)}
-
- def getexif(self):
- if self._exif is None:
- self._exif = Exif()
-
- exif_info = self.info.get("exif")
- if exif_info is None:
- if "Raw profile type exif" in self.info:
- exif_info = bytes.fromhex(
- "".join(self.info["Raw profile type exif"].split("\n")[3:])
- )
- elif hasattr(self, "tag_v2"):
- self._exif.bigtiff = self.tag_v2._bigtiff
- self._exif.endian = self.tag_v2._endian
- self._exif.load_from_fp(self.fp, self.tag_v2._offset)
- if exif_info is not None:
- self._exif.load(exif_info)
-
- # XMP tags
- if 0x0112 not in self._exif:
- xmp_tags = self.info.get("XML:com.adobe.xmp")
- if xmp_tags:
- match = re.search(r'tiff:Orientation="([0-9])"', xmp_tags)
- if match:
- self._exif[0x0112] = int(match[1])
-
- return self._exif
-
- def getim(self):
- """
- Returns a capsule that points to the internal image memory.
-
- :returns: A capsule object.
- """
-
- self.load()
- return self.im.ptr
-
- def getpalette(self, rawmode="RGB"):
- """
- Returns the image palette as a list.
-
- :param rawmode: The mode in which to return the palette. ``None`` will
- return the palette in its current mode.
-
- .. versionadded:: 9.1.0
-
- :returns: A list of color values [r, g, b, ...], or None if the
- image has no palette.
- """
-
- self.load()
- try:
- mode = self.im.getpalettemode()
- except ValueError:
- return None # no palette
- if rawmode is None:
- rawmode = mode
- return list(self.im.getpalette(mode, rawmode))
-
- def getpixel(self, xy):
- """
- Returns the pixel value at a given position.
-
- :param xy: The coordinate, given as (x, y). See
- :ref:`coordinate-system`.
- :returns: The pixel value. If the image is a multi-layer image,
- this method returns a tuple.
- """
-
- self.load()
- if self.pyaccess:
- return self.pyaccess.getpixel(xy)
- return self.im.getpixel(xy)
-
- def getprojection(self):
- """
- Get projection to x and y axes
-
- :returns: Two sequences, indicating where there are non-zero
- pixels along the X-axis and the Y-axis, respectively.
- """
-
- self.load()
- x, y = self.im.getprojection()
- return list(x), list(y)
-
- def histogram(self, mask=None, extrema=None):
- """
- Returns a histogram for the image. The histogram is returned as a
- list of pixel counts, one for each pixel value in the source
- image. Counts are grouped into 256 bins for each band, even if
- the image has more than 8 bits per band. If the image has more
- than one band, the histograms for all bands are concatenated (for
- example, the histogram for an "RGB" image contains 768 values).
-
- A bilevel image (mode "1") is treated as a greyscale ("L") image
- by this method.
-
- If a mask is provided, the method returns a histogram for those
- parts of the image where the mask image is non-zero. The mask
- image must have the same size as the image, and be either a
- bi-level image (mode "1") or a greyscale image ("L").
-
- :param mask: An optional mask.
- :param extrema: An optional tuple of manually-specified extrema.
- :returns: A list containing pixel counts.
- """
- self.load()
- if mask:
- mask.load()
- return self.im.histogram((0, 0), mask.im)
- if self.mode in ("I", "F"):
- if extrema is None:
- extrema = self.getextrema()
- return self.im.histogram(extrema)
- return self.im.histogram()
-
- def entropy(self, mask=None, extrema=None):
- """
- Calculates and returns the entropy for the image.
-
- A bilevel image (mode "1") is treated as a greyscale ("L")
- image by this method.
-
- If a mask is provided, the method employs the histogram for
- those parts of the image where the mask image is non-zero.
- The mask image must have the same size as the image, and be
- either a bi-level image (mode "1") or a greyscale image ("L").
-
- :param mask: An optional mask.
- :param extrema: An optional tuple of manually-specified extrema.
- :returns: A float value representing the image entropy
- """
- self.load()
- if mask:
- mask.load()
- return self.im.entropy((0, 0), mask.im)
- if self.mode in ("I", "F"):
- if extrema is None:
- extrema = self.getextrema()
- return self.im.entropy(extrema)
- return self.im.entropy()
-
- def paste(self, im, box=None, mask=None):
- """
- Pastes another image into this image. The box argument is either
- a 2-tuple giving the upper left corner, a 4-tuple defining the
- left, upper, right, and lower pixel coordinate, or None (same as
- (0, 0)). See :ref:`coordinate-system`. If a 4-tuple is given, the size
- of the pasted image must match the size of the region.
-
- If the modes don't match, the pasted image is converted to the mode of
- this image (see the :py:meth:`~PIL.Image.Image.convert` method for
- details).
-
- Instead of an image, the source can be a integer or tuple
- containing pixel values. The method then fills the region
- with the given color. When creating RGB images, you can
- also use color strings as supported by the ImageColor module.
-
- If a mask is given, this method updates only the regions
- indicated by the mask. You can use either "1", "L", "LA", "RGBA"
- or "RGBa" images (if present, the alpha band is used as mask).
- Where the mask is 255, the given image is copied as is. Where
- the mask is 0, the current value is preserved. Intermediate
- values will mix the two images together, including their alpha
- channels if they have them.
-
- See :py:meth:`~PIL.Image.Image.alpha_composite` if you want to
- combine images with respect to their alpha channels.
-
- :param im: Source image or pixel value (integer or tuple).
- :param box: An optional 4-tuple giving the region to paste into.
- If a 2-tuple is used instead, it's treated as the upper left
- corner. If omitted or None, the source is pasted into the
- upper left corner.
-
- If an image is given as the second argument and there is no
- third, the box defaults to (0, 0), and the second argument
- is interpreted as a mask image.
- :param mask: An optional mask image.
- """
-
- if isImageType(box) and mask is None:
- # abbreviated paste(im, mask) syntax
- mask = box
- box = None
-
- if box is None:
- box = (0, 0)
-
- if len(box) == 2:
- # upper left corner given; get size from image or mask
- if isImageType(im):
- size = im.size
- elif isImageType(mask):
- size = mask.size
- else:
- # FIXME: use self.size here?
- raise ValueError("cannot determine region size; use 4-item box")
- box += (box[0] + size[0], box[1] + size[1])
-
- if isinstance(im, str):
- from . import ImageColor
-
- im = ImageColor.getcolor(im, self.mode)
-
- elif isImageType(im):
- im.load()
- if self.mode != im.mode:
- if self.mode != "RGB" or im.mode not in ("LA", "RGBA", "RGBa"):
- # should use an adapter for this!
- im = im.convert(self.mode)
- im = im.im
-
- self._ensure_mutable()
-
- if mask:
- mask.load()
- self.im.paste(im, box, mask.im)
- else:
- self.im.paste(im, box)
-
- def alpha_composite(self, im, dest=(0, 0), source=(0, 0)):
- """'In-place' analog of Image.alpha_composite. Composites an image
- onto this image.
-
- :param im: image to composite over this one
- :param dest: Optional 2 tuple (left, top) specifying the upper
- left corner in this (destination) image.
- :param source: Optional 2 (left, top) tuple for the upper left
- corner in the overlay source image, or 4 tuple (left, top, right,
- bottom) for the bounds of the source rectangle
-
- Performance Note: Not currently implemented in-place in the core layer.
- """
-
- if not isinstance(source, (list, tuple)):
- raise ValueError("Source must be a tuple")
- if not isinstance(dest, (list, tuple)):
- raise ValueError("Destination must be a tuple")
- if not len(source) in (2, 4):
- raise ValueError("Source must be a 2 or 4-tuple")
- if not len(dest) == 2:
- raise ValueError("Destination must be a 2-tuple")
- if min(source) < 0:
- raise ValueError("Source must be non-negative")
-
- if len(source) == 2:
- source = source + im.size
-
- # over image, crop if it's not the whole thing.
- if source == (0, 0) + im.size:
- overlay = im
- else:
- overlay = im.crop(source)
-
- # target for the paste
- box = dest + (dest[0] + overlay.width, dest[1] + overlay.height)
-
- # destination image. don't copy if we're using the whole image.
- if box == (0, 0) + self.size:
- background = self
- else:
- background = self.crop(box)
-
- result = alpha_composite(background, overlay)
- self.paste(result, box)
-
- def point(self, lut, mode=None):
- """
- Maps this image through a lookup table or function.
-
- :param lut: A lookup table, containing 256 (or 65536 if
- self.mode=="I" and mode == "L") values per band in the
- image. A function can be used instead, it should take a
- single argument. The function is called once for each
- possible pixel value, and the resulting table is applied to
- all bands of the image.
-
- It may also be an :py:class:`~PIL.Image.ImagePointHandler`
- object::
-
- class Example(Image.ImagePointHandler):
- def point(self, data):
- # Return result
- :param mode: Output mode (default is same as input). In the
- current version, this can only be used if the source image
- has mode "L" or "P", and the output has mode "1" or the
- source image mode is "I" and the output mode is "L".
- :returns: An :py:class:`~PIL.Image.Image` object.
- """
-
- self.load()
-
- if isinstance(lut, ImagePointHandler):
- return lut.point(self)
-
- if callable(lut):
- # if it isn't a list, it should be a function
- if self.mode in ("I", "I;16", "F"):
- # check if the function can be used with point_transform
- # UNDONE wiredfool -- I think this prevents us from ever doing
- # a gamma function point transform on > 8bit images.
- scale, offset = _getscaleoffset(lut)
- return self._new(self.im.point_transform(scale, offset))
- # for other modes, convert the function to a table
- lut = [lut(i) for i in range(256)] * self.im.bands
-
- if self.mode == "F":
- # FIXME: _imaging returns a confusing error message for this case
- raise ValueError("point operation not supported for this mode")
-
- return self._new(self.im.point(lut, mode))
-
- def putalpha(self, alpha):
- """
- Adds or replaces the alpha layer in this image. If the image
- does not have an alpha layer, it's converted to "LA" or "RGBA".
- The new layer must be either "L" or "1".
-
- :param alpha: The new alpha layer. This can either be an "L" or "1"
- image having the same size as this image, or an integer or
- other color value.
- """
-
- self._ensure_mutable()
-
- if self.mode not in ("LA", "PA", "RGBA"):
- # attempt to promote self to a matching alpha mode
- try:
- mode = getmodebase(self.mode) + "A"
- try:
- self.im.setmode(mode)
- except (AttributeError, ValueError) as e:
- # do things the hard way
- im = self.im.convert(mode)
- if im.mode not in ("LA", "PA", "RGBA"):
- raise ValueError from e # sanity check
- self.im = im
- self.pyaccess = None
- self.mode = self.im.mode
- except KeyError as e:
- raise ValueError("illegal image mode") from e
-
- if self.mode in ("LA", "PA"):
- band = 1
- else:
- band = 3
-
- if isImageType(alpha):
- # alpha layer
- if alpha.mode not in ("1", "L"):
- raise ValueError("illegal image mode")
- alpha.load()
- if alpha.mode == "1":
- alpha = alpha.convert("L")
- else:
- # constant alpha
- try:
- self.im.fillband(band, alpha)
- except (AttributeError, ValueError):
- # do things the hard way
- alpha = new("L", self.size, alpha)
- else:
- return
-
- self.im.putband(alpha.im, band)
-
- def putdata(self, data, scale=1.0, offset=0.0):
- """
- Copies pixel data from a flattened sequence object into the image. The
- values should start at the upper left corner (0, 0), continue to the
- end of the line, followed directly by the first value of the second
- line, and so on. Data will be read until either the image or the
- sequence ends. The scale and offset values are used to adjust the
- sequence values: **pixel = value*scale + offset**.
-
- :param data: A flattened sequence object.
- :param scale: An optional scale value. The default is 1.0.
- :param offset: An optional offset value. The default is 0.0.
- """
-
- self._ensure_mutable()
-
- self.im.putdata(data, scale, offset)
-
- def putpalette(self, data, rawmode="RGB"):
- """
- Attaches a palette to this image. The image must be a "P", "PA", "L"
- or "LA" image.
-
- The palette sequence must contain at most 256 colors, made up of one
- integer value for each channel in the raw mode.
- For example, if the raw mode is "RGB", then it can contain at most 768
- values, made up of red, green and blue values for the corresponding pixel
- index in the 256 colors.
- If the raw mode is "RGBA", then it can contain at most 1024 values,
- containing red, green, blue and alpha values.
-
- Alternatively, an 8-bit string may be used instead of an integer sequence.
-
- :param data: A palette sequence (either a list or a string).
- :param rawmode: The raw mode of the palette. Either "RGB", "RGBA", or a mode
- that can be transformed to "RGB" or "RGBA" (e.g. "R", "BGR;15", "RGBA;L").
- """
- from . import ImagePalette
-
- if self.mode not in ("L", "LA", "P", "PA"):
- raise ValueError("illegal image mode")
- if isinstance(data, ImagePalette.ImagePalette):
- palette = ImagePalette.raw(data.rawmode, data.palette)
- else:
- if not isinstance(data, bytes):
- data = bytes(data)
- palette = ImagePalette.raw(rawmode, data)
- self.mode = "PA" if "A" in self.mode else "P"
- self.palette = palette
- self.palette.mode = "RGB"
- self.load() # install new palette
-
- def putpixel(self, xy, value):
- """
- Modifies the pixel at the given position. The color is given as
- a single numerical value for single-band images, and a tuple for
- multi-band images. In addition to this, RGB and RGBA tuples are
- accepted for P images.
-
- Note that this method is relatively slow. For more extensive changes,
- use :py:meth:`~PIL.Image.Image.paste` or the :py:mod:`~PIL.ImageDraw`
- module instead.
-
- See:
-
- * :py:meth:`~PIL.Image.Image.paste`
- * :py:meth:`~PIL.Image.Image.putdata`
- * :py:mod:`~PIL.ImageDraw`
-
- :param xy: The pixel coordinate, given as (x, y). See
- :ref:`coordinate-system`.
- :param value: The pixel value.
- """
-
- if self.readonly:
- self._copy()
- self.load()
-
- if self.pyaccess:
- return self.pyaccess.putpixel(xy, value)
-
- if (
- self.mode == "P"
- and isinstance(value, (list, tuple))
- and len(value) in [3, 4]
- ):
- # RGB or RGBA value for a P image
- value = self.palette.getcolor(value, self)
- return self.im.putpixel(xy, value)
-
- def remap_palette(self, dest_map, source_palette=None):
- """
- Rewrites the image to reorder the palette.
-
- :param dest_map: A list of indexes into the original palette.
- e.g. ``[1,0]`` would swap a two item palette, and ``list(range(256))``
- is the identity transform.
- :param source_palette: Bytes or None.
- :returns: An :py:class:`~PIL.Image.Image` object.
-
- """
- from . import ImagePalette
-
- if self.mode not in ("L", "P"):
- raise ValueError("illegal image mode")
-
- if source_palette is None:
- if self.mode == "P":
- self.load()
- source_palette = self.im.getpalette("RGB")[:768]
- else: # L-mode
- source_palette = bytearray(i // 3 for i in range(768))
-
- palette_bytes = b""
- new_positions = [0] * 256
-
- # pick only the used colors from the palette
- for i, oldPosition in enumerate(dest_map):
- palette_bytes += source_palette[oldPosition * 3 : oldPosition * 3 + 3]
- new_positions[oldPosition] = i
-
- # replace the palette color id of all pixel with the new id
-
- # Palette images are [0..255], mapped through a 1 or 3
- # byte/color map. We need to remap the whole image
- # from palette 1 to palette 2. New_positions is
- # an array of indexes into palette 1. Palette 2 is
- # palette 1 with any holes removed.
-
- # We're going to leverage the convert mechanism to use the
- # C code to remap the image from palette 1 to palette 2,
- # by forcing the source image into 'L' mode and adding a
- # mapping 'L' mode palette, then converting back to 'L'
- # sans palette thus converting the image bytes, then
- # assigning the optimized RGB palette.
-
- # perf reference, 9500x4000 gif, w/~135 colors
- # 14 sec prepatch, 1 sec postpatch with optimization forced.
-
- mapping_palette = bytearray(new_positions)
-
- m_im = self.copy()
- m_im.mode = "P"
-
- m_im.palette = ImagePalette.ImagePalette("RGB", palette=mapping_palette * 3)
- # possibly set palette dirty, then
- # m_im.putpalette(mapping_palette, 'L') # converts to 'P'
- # or just force it.
- # UNDONE -- this is part of the general issue with palettes
- m_im.im.putpalette("RGB;L", m_im.palette.tobytes())
-
- m_im = m_im.convert("L")
-
- # Internally, we require 768 bytes for a palette.
- new_palette_bytes = palette_bytes + (768 - len(palette_bytes)) * b"\x00"
- m_im.putpalette(new_palette_bytes)
- m_im.palette = ImagePalette.ImagePalette("RGB", palette=palette_bytes)
-
- return m_im
-
- def _get_safe_box(self, size, resample, box):
- """Expands the box so it includes adjacent pixels
- that may be used by resampling with the given resampling filter.
- """
- filter_support = _filters_support[resample] - 0.5
- scale_x = (box[2] - box[0]) / size[0]
- scale_y = (box[3] - box[1]) / size[1]
- support_x = filter_support * scale_x
- support_y = filter_support * scale_y
-
- return (
- max(0, int(box[0] - support_x)),
- max(0, int(box[1] - support_y)),
- min(self.size[0], math.ceil(box[2] + support_x)),
- min(self.size[1], math.ceil(box[3] + support_y)),
- )
-
- def resize(self, size, resample=None, box=None, reducing_gap=None):
- """
- Returns a resized copy of this image.
-
- :param size: The requested size in pixels, as a 2-tuple:
- (width, height).
- :param resample: An optional resampling filter. This can be
- one of :py:data:`PIL.Image.Resampling.NEAREST`,
- :py:data:`PIL.Image.Resampling.BOX`,
- :py:data:`PIL.Image.Resampling.BILINEAR`,
- :py:data:`PIL.Image.Resampling.HAMMING`,
- :py:data:`PIL.Image.Resampling.BICUBIC` or
- :py:data:`PIL.Image.Resampling.LANCZOS`.
- If the image has mode "1" or "P", it is always set to
- :py:data:`PIL.Image.Resampling.NEAREST`.
- If the image mode specifies a number of bits, such as "I;16", then the
- default filter is :py:data:`PIL.Image.Resampling.NEAREST`.
- Otherwise, the default filter is
- :py:data:`PIL.Image.Resampling.BICUBIC`. See: :ref:`concept-filters`.
- :param box: An optional 4-tuple of floats providing
- the source image region to be scaled.
- The values must be within (0, 0, width, height) rectangle.
- If omitted or None, the entire source is used.
- :param reducing_gap: Apply optimization by resizing the image
- in two steps. First, reducing the image by integer times
- using :py:meth:`~PIL.Image.Image.reduce`.
- Second, resizing using regular resampling. The last step
- changes size no less than by ``reducing_gap`` times.
- ``reducing_gap`` may be None (no first step is performed)
- or should be greater than 1.0. The bigger ``reducing_gap``,
- the closer the result to the fair resampling.
- The smaller ``reducing_gap``, the faster resizing.
- With ``reducing_gap`` greater or equal to 3.0, the result is
- indistinguishable from fair resampling in most cases.
- The default value is None (no optimization).
- :returns: An :py:class:`~PIL.Image.Image` object.
- """
-
- if resample is None:
- type_special = ";" in self.mode
- resample = Resampling.NEAREST if type_special else Resampling.BICUBIC
- elif resample not in (
- Resampling.NEAREST,
- Resampling.BILINEAR,
- Resampling.BICUBIC,
- Resampling.LANCZOS,
- Resampling.BOX,
- Resampling.HAMMING,
- ):
- message = f"Unknown resampling filter ({resample})."
-
- filters = [
- f"{filter[1]} ({filter[0]})"
- for filter in (
- (Resampling.NEAREST, "Image.Resampling.NEAREST"),
- (Resampling.LANCZOS, "Image.Resampling.LANCZOS"),
- (Resampling.BILINEAR, "Image.Resampling.BILINEAR"),
- (Resampling.BICUBIC, "Image.Resampling.BICUBIC"),
- (Resampling.BOX, "Image.Resampling.BOX"),
- (Resampling.HAMMING, "Image.Resampling.HAMMING"),
- )
- ]
- raise ValueError(
- message + " Use " + ", ".join(filters[:-1]) + " or " + filters[-1]
- )
-
- if reducing_gap is not None and reducing_gap < 1.0:
- raise ValueError("reducing_gap must be 1.0 or greater")
-
- size = tuple(size)
-
- if box is None:
- box = (0, 0) + self.size
- else:
- box = tuple(box)
-
- if self.size == size and box == (0, 0) + self.size:
- return self.copy()
-
- if self.mode in ("1", "P"):
- resample = Resampling.NEAREST
-
- if self.mode in ["LA", "RGBA"] and resample != Resampling.NEAREST:
- im = self.convert({"LA": "La", "RGBA": "RGBa"}[self.mode])
- im = im.resize(size, resample, box)
- return im.convert(self.mode)
-
- self.load()
-
- if reducing_gap is not None and resample != Resampling.NEAREST:
- factor_x = int((box[2] - box[0]) / size[0] / reducing_gap) or 1
- factor_y = int((box[3] - box[1]) / size[1] / reducing_gap) or 1
- if factor_x > 1 or factor_y > 1:
- reduce_box = self._get_safe_box(size, resample, box)
- factor = (factor_x, factor_y)
- if callable(self.reduce):
- self = self.reduce(factor, box=reduce_box)
- else:
- self = Image.reduce(self, factor, box=reduce_box)
- box = (
- (box[0] - reduce_box[0]) / factor_x,
- (box[1] - reduce_box[1]) / factor_y,
- (box[2] - reduce_box[0]) / factor_x,
- (box[3] - reduce_box[1]) / factor_y,
- )
-
- return self._new(self.im.resize(size, resample, box))
-
- def reduce(self, factor, box=None):
- """
- Returns a copy of the image reduced ``factor`` times.
- If the size of the image is not dividable by ``factor``,
- the resulting size will be rounded up.
-
- :param factor: A greater than 0 integer or tuple of two integers
- for width and height separately.
- :param box: An optional 4-tuple of ints providing
- the source image region to be reduced.
- The values must be within ``(0, 0, width, height)`` rectangle.
- If omitted or ``None``, the entire source is used.
- """
- if not isinstance(factor, (list, tuple)):
- factor = (factor, factor)
-
- if box is None:
- box = (0, 0) + self.size
- else:
- box = tuple(box)
-
- if factor == (1, 1) and box == (0, 0) + self.size:
- return self.copy()
-
- if self.mode in ["LA", "RGBA"]:
- im = self.convert({"LA": "La", "RGBA": "RGBa"}[self.mode])
- im = im.reduce(factor, box)
- return im.convert(self.mode)
-
- self.load()
-
- return self._new(self.im.reduce(factor, box))
-
- def rotate(
- self,
- angle,
- resample=Resampling.NEAREST,
- expand=0,
- center=None,
- translate=None,
- fillcolor=None,
- ):
- """
- Returns a rotated copy of this image. This method returns a
- copy of this image, rotated the given number of degrees counter
- clockwise around its centre.
-
- :param angle: In degrees counter clockwise.
- :param resample: An optional resampling filter. This can be
- one of :py:data:`PIL.Image.Resampling.NEAREST` (use nearest neighbour),
- :py:data:`PIL.Image.BILINEAR` (linear interpolation in a 2x2
- environment), or :py:data:`PIL.Image.Resampling.BICUBIC`
- (cubic spline interpolation in a 4x4 environment).
- If omitted, or if the image has mode "1" or "P", it is
- set to :py:data:`PIL.Image.Resampling.NEAREST`. See :ref:`concept-filters`.
- :param expand: Optional expansion flag. If true, expands the output
- image to make it large enough to hold the entire rotated image.
- If false or omitted, make the output image the same size as the
- input image. Note that the expand flag assumes rotation around
- the center and no translation.
- :param center: Optional center of rotation (a 2-tuple). Origin is
- the upper left corner. Default is the center of the image.
- :param translate: An optional post-rotate translation (a 2-tuple).
- :param fillcolor: An optional color for area outside the rotated image.
- :returns: An :py:class:`~PIL.Image.Image` object.
- """
-
- angle = angle % 360.0
-
- # Fast paths regardless of filter, as long as we're not
- # translating or changing the center.
- if not (center or translate):
- if angle == 0:
- return self.copy()
- if angle == 180:
- return self.transpose(Transpose.ROTATE_180)
- if angle in (90, 270) and (expand or self.width == self.height):
- return self.transpose(
- Transpose.ROTATE_90 if angle == 90 else Transpose.ROTATE_270
- )
-
- # Calculate the affine matrix. Note that this is the reverse
- # transformation (from destination image to source) because we
- # want to interpolate the (discrete) destination pixel from
- # the local area around the (floating) source pixel.
-
- # The matrix we actually want (note that it operates from the right):
- # (1, 0, tx) (1, 0, cx) ( cos a, sin a, 0) (1, 0, -cx)
- # (0, 1, ty) * (0, 1, cy) * (-sin a, cos a, 0) * (0, 1, -cy)
- # (0, 0, 1) (0, 0, 1) ( 0, 0, 1) (0, 0, 1)
-
- # The reverse matrix is thus:
- # (1, 0, cx) ( cos -a, sin -a, 0) (1, 0, -cx) (1, 0, -tx)
- # (0, 1, cy) * (-sin -a, cos -a, 0) * (0, 1, -cy) * (0, 1, -ty)
- # (0, 0, 1) ( 0, 0, 1) (0, 0, 1) (0, 0, 1)
-
- # In any case, the final translation may be updated at the end to
- # compensate for the expand flag.
-
- w, h = self.size
-
- if translate is None:
- post_trans = (0, 0)
- else:
- post_trans = translate
- if center is None:
- # FIXME These should be rounded to ints?
- rotn_center = (w / 2.0, h / 2.0)
- else:
- rotn_center = center
-
- angle = -math.radians(angle)
- matrix = [
- round(math.cos(angle), 15),
- round(math.sin(angle), 15),
- 0.0,
- round(-math.sin(angle), 15),
- round(math.cos(angle), 15),
- 0.0,
- ]
-
- def transform(x, y, matrix):
- (a, b, c, d, e, f) = matrix
- return a * x + b * y + c, d * x + e * y + f
-
- matrix[2], matrix[5] = transform(
- -rotn_center[0] - post_trans[0], -rotn_center[1] - post_trans[1], matrix
- )
- matrix[2] += rotn_center[0]
- matrix[5] += rotn_center[1]
-
- if expand:
- # calculate output size
- xx = []
- yy = []
- for x, y in ((0, 0), (w, 0), (w, h), (0, h)):
- x, y = transform(x, y, matrix)
- xx.append(x)
- yy.append(y)
- nw = math.ceil(max(xx)) - math.floor(min(xx))
- nh = math.ceil(max(yy)) - math.floor(min(yy))
-
- # We multiply a translation matrix from the right. Because of its
- # special form, this is the same as taking the image of the
- # translation vector as new translation vector.
- matrix[2], matrix[5] = transform(-(nw - w) / 2.0, -(nh - h) / 2.0, matrix)
- w, h = nw, nh
-
- return self.transform(
- (w, h), Transform.AFFINE, matrix, resample, fillcolor=fillcolor
- )
-
- def save(self, fp, format=None, **params):
- """
- Saves this image under the given filename. If no format is
- specified, the format to use is determined from the filename
- extension, if possible.
-
- Keyword options can be used to provide additional instructions
- to the writer. If a writer doesn't recognise an option, it is
- silently ignored. The available options are described in the
- :doc:`image format documentation
- <../handbook/image-file-formats>` for each writer.
-
- You can use a file object instead of a filename. In this case,
- you must always specify the format. The file object must
- implement the ``seek``, ``tell``, and ``write``
- methods, and be opened in binary mode.
-
- :param fp: A filename (string), pathlib.Path object or file object.
- :param format: Optional format override. If omitted, the
- format to use is determined from the filename extension.
- If a file object was used instead of a filename, this
- parameter should always be used.
- :param params: Extra parameters to the image writer.
- :returns: None
- :exception ValueError: If the output format could not be determined
- from the file name. Use the format option to solve this.
- :exception OSError: If the file could not be written. The file
- may have been created, and may contain partial data.
- """
-
- filename = ""
- open_fp = False
- if isinstance(fp, Path):
- filename = str(fp)
- open_fp = True
- elif isPath(fp):
- filename = fp
- open_fp = True
- elif fp == sys.stdout:
- try:
- fp = sys.stdout.buffer
- except AttributeError:
- pass
- if not filename and hasattr(fp, "name") and isPath(fp.name):
- # only set the name for metadata purposes
- filename = fp.name
-
- # may mutate self!
- self._ensure_mutable()
-
- save_all = params.pop("save_all", False)
- self.encoderinfo = params
- self.encoderconfig = ()
-
- preinit()
-
- ext = os.path.splitext(filename)[1].lower()
-
- if not format:
- if ext not in EXTENSION:
- init()
- try:
- format = EXTENSION[ext]
- except KeyError as e:
- raise ValueError(f"unknown file extension: {ext}") from e
-
- if format.upper() not in SAVE:
- init()
- if save_all:
- save_handler = SAVE_ALL[format.upper()]
- else:
- save_handler = SAVE[format.upper()]
-
- created = False
- if open_fp:
- created = not os.path.exists(filename)
- if params.get("append", False):
- # Open also for reading ("+"), because TIFF save_all
- # writer needs to go back and edit the written data.
- fp = builtins.open(filename, "r+b")
- else:
- fp = builtins.open(filename, "w+b")
-
- try:
- save_handler(self, fp, filename)
- except Exception:
- if open_fp:
- fp.close()
- if created:
- try:
- os.remove(filename)
- except PermissionError:
- pass
- raise
- if open_fp:
- fp.close()
-
- def seek(self, frame):
- """
- Seeks to the given frame in this sequence file. If you seek
- beyond the end of the sequence, the method raises an
- ``EOFError`` exception. When a sequence file is opened, the
- library automatically seeks to frame 0.
-
- See :py:meth:`~PIL.Image.Image.tell`.
-
- If defined, :attr:`~PIL.Image.Image.n_frames` refers to the
- number of available frames.
-
- :param frame: Frame number, starting at 0.
- :exception EOFError: If the call attempts to seek beyond the end
- of the sequence.
- """
-
- # overridden by file handlers
- if frame != 0:
- raise EOFError
-
- def show(self, title=None):
- """
- Displays this image. This method is mainly intended for debugging purposes.
-
- This method calls :py:func:`PIL.ImageShow.show` internally. You can use
- :py:func:`PIL.ImageShow.register` to override its default behaviour.
-
- The image is first saved to a temporary file. By default, it will be in
- PNG format.
-
- On Unix, the image is then opened using the **display**, **eog** or
- **xv** utility, depending on which one can be found.
-
- On macOS, the image is opened with the native Preview application.
-
- On Windows, the image is opened with the standard PNG display utility.
-
- :param title: Optional title to use for the image window, where possible.
- """
-
- _show(self, title=title)
-
- def split(self):
- """
- Split this image into individual bands. This method returns a
- tuple of individual image bands from an image. For example,
- splitting an "RGB" image creates three new images each
- containing a copy of one of the original bands (red, green,
- blue).
-
- If you need only one band, :py:meth:`~PIL.Image.Image.getchannel`
- method can be more convenient and faster.
-
- :returns: A tuple containing bands.
- """
-
- self.load()
- if self.im.bands == 1:
- ims = [self.copy()]
- else:
- ims = map(self._new, self.im.split())
- return tuple(ims)
-
- def getchannel(self, channel):
- """
- Returns an image containing a single channel of the source image.
-
- :param channel: What channel to return. Could be index
- (0 for "R" channel of "RGB") or channel name
- ("A" for alpha channel of "RGBA").
- :returns: An image in "L" mode.
-
- .. versionadded:: 4.3.0
- """
- self.load()
-
- if isinstance(channel, str):
- try:
- channel = self.getbands().index(channel)
- except ValueError as e:
- raise ValueError(f'The image has no channel "{channel}"') from e
-
- return self._new(self.im.getband(channel))
-
- def tell(self):
- """
- Returns the current frame number. See :py:meth:`~PIL.Image.Image.seek`.
-
- If defined, :attr:`~PIL.Image.Image.n_frames` refers to the
- number of available frames.
-
- :returns: Frame number, starting with 0.
- """
- return 0
-
- def thumbnail(self, size, resample=Resampling.BICUBIC, reducing_gap=2.0):
- """
- Make this image into a thumbnail. This method modifies the
- image to contain a thumbnail version of itself, no larger than
- the given size. This method calculates an appropriate thumbnail
- size to preserve the aspect of the image, calls the
- :py:meth:`~PIL.Image.Image.draft` method to configure the file reader
- (where applicable), and finally resizes the image.
-
- Note that this function modifies the :py:class:`~PIL.Image.Image`
- object in place. If you need to use the full resolution image as well,
- apply this method to a :py:meth:`~PIL.Image.Image.copy` of the original
- image.
-
- :param size: Requested size.
- :param resample: Optional resampling filter. This can be one
- of :py:data:`PIL.Image.Resampling.NEAREST`,
- :py:data:`PIL.Image.Resampling.BOX`,
- :py:data:`PIL.Image.Resampling.BILINEAR`,
- :py:data:`PIL.Image.Resampling.HAMMING`,
- :py:data:`PIL.Image.Resampling.BICUBIC` or
- :py:data:`PIL.Image.Resampling.LANCZOS`.
- If omitted, it defaults to :py:data:`PIL.Image.Resampling.BICUBIC`.
- (was :py:data:`PIL.Image.Resampling.NEAREST` prior to version 2.5.0).
- See: :ref:`concept-filters`.
- :param reducing_gap: Apply optimization by resizing the image
- in two steps. First, reducing the image by integer times
- using :py:meth:`~PIL.Image.Image.reduce` or
- :py:meth:`~PIL.Image.Image.draft` for JPEG images.
- Second, resizing using regular resampling. The last step
- changes size no less than by ``reducing_gap`` times.
- ``reducing_gap`` may be None (no first step is performed)
- or should be greater than 1.0. The bigger ``reducing_gap``,
- the closer the result to the fair resampling.
- The smaller ``reducing_gap``, the faster resizing.
- With ``reducing_gap`` greater or equal to 3.0, the result is
- indistinguishable from fair resampling in most cases.
- The default value is 2.0 (very close to fair resampling
- while still being faster in many cases).
- :returns: None
- """
-
- x, y = map(math.floor, size)
- if x >= self.width and y >= self.height:
- return
-
- def round_aspect(number, key):
- return max(min(math.floor(number), math.ceil(number), key=key), 1)
-
- # preserve aspect ratio
- aspect = self.width / self.height
- if x / y >= aspect:
- x = round_aspect(y * aspect, key=lambda n: abs(aspect - n / y))
- else:
- y = round_aspect(
- x / aspect, key=lambda n: 0 if n == 0 else abs(aspect - x / n)
- )
- size = (x, y)
-
- box = None
- if reducing_gap is not None:
- res = self.draft(None, (size[0] * reducing_gap, size[1] * reducing_gap))
- if res is not None:
- box = res[1]
-
- if self.size != size:
- im = self.resize(size, resample, box=box, reducing_gap=reducing_gap)
-
- self.im = im.im
- self._size = size
- self.mode = self.im.mode
-
- self.readonly = 0
- self.pyaccess = None
-
- # FIXME: the different transform methods need further explanation
- # instead of bloating the method docs, add a separate chapter.
- def transform(
- self,
- size,
- method,
- data=None,
- resample=Resampling.NEAREST,
- fill=1,
- fillcolor=None,
- ):
- """
- Transforms this image. This method creates a new image with the
- given size, and the same mode as the original, and copies data
- to the new image using the given transform.
-
- :param size: The output size.
- :param method: The transformation method. This is one of
- :py:data:`PIL.Image.Transform.EXTENT` (cut out a rectangular subregion),
- :py:data:`PIL.Image.Transform.AFFINE` (affine transform),
- :py:data:`PIL.Image.Transform.PERSPECTIVE` (perspective transform),
- :py:data:`PIL.Image.Transform.QUAD` (map a quadrilateral to a rectangle), or
- :py:data:`PIL.Image.Transform.MESH` (map a number of source quadrilaterals
- in one operation).
-
- It may also be an :py:class:`~PIL.Image.ImageTransformHandler`
- object::
-
- class Example(Image.ImageTransformHandler):
- def transform(self, size, data, resample, fill=1):
- # Return result
-
- It may also be an object with a ``method.getdata`` method
- that returns a tuple supplying new ``method`` and ``data`` values::
-
- class Example:
- def getdata(self):
- method = Image.Transform.EXTENT
- data = (0, 0, 100, 100)
- return method, data
- :param data: Extra data to the transformation method.
- :param resample: Optional resampling filter. It can be one of
- :py:data:`PIL.Image.Resampling.NEAREST` (use nearest neighbour),
- :py:data:`PIL.Image.Resampling.BILINEAR` (linear interpolation in a 2x2
- environment), or :py:data:`PIL.Image.BICUBIC` (cubic spline
- interpolation in a 4x4 environment). If omitted, or if the image
- has mode "1" or "P", it is set to :py:data:`PIL.Image.Resampling.NEAREST`.
- See: :ref:`concept-filters`.
- :param fill: If ``method`` is an
- :py:class:`~PIL.Image.ImageTransformHandler` object, this is one of
- the arguments passed to it. Otherwise, it is unused.
- :param fillcolor: Optional fill color for the area outside the
- transform in the output image.
- :returns: An :py:class:`~PIL.Image.Image` object.
- """
-
- if self.mode in ("LA", "RGBA") and resample != Resampling.NEAREST:
- return (
- self.convert({"LA": "La", "RGBA": "RGBa"}[self.mode])
- .transform(size, method, data, resample, fill, fillcolor)
- .convert(self.mode)
- )
-
- if isinstance(method, ImageTransformHandler):
- return method.transform(size, self, resample=resample, fill=fill)
-
- if hasattr(method, "getdata"):
- # compatibility w. old-style transform objects
- method, data = method.getdata()
-
- if data is None:
- raise ValueError("missing method data")
-
- im = new(self.mode, size, fillcolor)
- if self.mode == "P" and self.palette:
- im.palette = self.palette.copy()
- im.info = self.info.copy()
- if method == Transform.MESH:
- # list of quads
- for box, quad in data:
- im.__transformer(
- box, self, Transform.QUAD, quad, resample, fillcolor is None
- )
- else:
- im.__transformer(
- (0, 0) + size, self, method, data, resample, fillcolor is None
- )
-
- return im
-
- def __transformer(
- self, box, image, method, data, resample=Resampling.NEAREST, fill=1
- ):
- w = box[2] - box[0]
- h = box[3] - box[1]
-
- if method == Transform.AFFINE:
- data = data[0:6]
-
- elif method == Transform.EXTENT:
- # convert extent to an affine transform
- x0, y0, x1, y1 = data
- xs = (x1 - x0) / w
- ys = (y1 - y0) / h
- method = Transform.AFFINE
- data = (xs, 0, x0, 0, ys, y0)
-
- elif method == Transform.PERSPECTIVE:
- data = data[0:8]
-
- elif method == Transform.QUAD:
- # quadrilateral warp. data specifies the four corners
- # given as NW, SW, SE, and NE.
- nw = data[0:2]
- sw = data[2:4]
- se = data[4:6]
- ne = data[6:8]
- x0, y0 = nw
- As = 1.0 / w
- At = 1.0 / h
- data = (
- x0,
- (ne[0] - x0) * As,
- (sw[0] - x0) * At,
- (se[0] - sw[0] - ne[0] + x0) * As * At,
- y0,
- (ne[1] - y0) * As,
- (sw[1] - y0) * At,
- (se[1] - sw[1] - ne[1] + y0) * As * At,
- )
-
- else:
- raise ValueError("unknown transformation method")
-
- if resample not in (
- Resampling.NEAREST,
- Resampling.BILINEAR,
- Resampling.BICUBIC,
- ):
- if resample in (Resampling.BOX, Resampling.HAMMING, Resampling.LANCZOS):
- message = {
- Resampling.BOX: "Image.Resampling.BOX",
- Resampling.HAMMING: "Image.Resampling.HAMMING",
- Resampling.LANCZOS: "Image.Resampling.LANCZOS",
- }[resample] + f" ({resample}) cannot be used."
- else:
- message = f"Unknown resampling filter ({resample})."
-
- filters = [
- f"{filter[1]} ({filter[0]})"
- for filter in (
- (Resampling.NEAREST, "Image.Resampling.NEAREST"),
- (Resampling.BILINEAR, "Image.Resampling.BILINEAR"),
- (Resampling.BICUBIC, "Image.Resampling.BICUBIC"),
- )
- ]
- raise ValueError(
- message + " Use " + ", ".join(filters[:-1]) + " or " + filters[-1]
- )
-
- image.load()
-
- self.load()
-
- if image.mode in ("1", "P"):
- resample = Resampling.NEAREST
-
- self.im.transform2(box, image.im, method, data, resample, fill)
-
- def transpose(self, method):
- """
- Transpose image (flip or rotate in 90 degree steps)
-
- :param method: One of :py:data:`PIL.Image.Transpose.FLIP_LEFT_RIGHT`,
- :py:data:`PIL.Image.Transpose.FLIP_TOP_BOTTOM`,
- :py:data:`PIL.Image.Transpose.ROTATE_90`,
- :py:data:`PIL.Image.Transpose.ROTATE_180`,
- :py:data:`PIL.Image.Transpose.ROTATE_270`,
- :py:data:`PIL.Image.Transpose.TRANSPOSE` or
- :py:data:`PIL.Image.Transpose.TRANSVERSE`.
- :returns: Returns a flipped or rotated copy of this image.
- """
-
- self.load()
- return self._new(self.im.transpose(method))
-
- def effect_spread(self, distance):
- """
- Randomly spread pixels in an image.
-
- :param distance: Distance to spread pixels.
- """
- self.load()
- return self._new(self.im.effect_spread(distance))
-
- def toqimage(self):
- """Returns a QImage copy of this image"""
- from . import ImageQt
-
- if not ImageQt.qt_is_installed:
- raise ImportError("Qt bindings are not installed")
- return ImageQt.toqimage(self)
-
- def toqpixmap(self):
- """Returns a QPixmap copy of this image"""
- from . import ImageQt
-
- if not ImageQt.qt_is_installed:
- raise ImportError("Qt bindings are not installed")
- return ImageQt.toqpixmap(self)
-
-
-# --------------------------------------------------------------------
-# Abstract handlers.
-
-
-class ImagePointHandler:
- """
- Used as a mixin by point transforms
- (for use with :py:meth:`~PIL.Image.Image.point`)
- """
-
- pass
-
-
-class ImageTransformHandler:
- """
- Used as a mixin by geometry transforms
- (for use with :py:meth:`~PIL.Image.Image.transform`)
- """
-
- pass
-
-
-# --------------------------------------------------------------------
-# Factories
-
-#
-# Debugging
-
-
-def _wedge():
- """Create greyscale wedge (for debugging only)"""
-
- return Image()._new(core.wedge("L"))
-
-
-def _check_size(size):
- """
- Common check to enforce type and sanity check on size tuples
-
- :param size: Should be a 2 tuple of (width, height)
- :returns: True, or raises a ValueError
- """
-
- if not isinstance(size, (list, tuple)):
- raise ValueError("Size must be a tuple")
- if len(size) != 2:
- raise ValueError("Size must be a tuple of length 2")
- if size[0] < 0 or size[1] < 0:
- raise ValueError("Width and height must be >= 0")
-
- return True
-
-
-def new(mode, size, color=0):
- """
- Creates a new image with the given mode and size.
-
- :param mode: The mode to use for the new image. See:
- :ref:`concept-modes`.
- :param size: A 2-tuple, containing (width, height) in pixels.
- :param color: What color to use for the image. Default is black.
- If given, this should be a single integer or floating point value
- for single-band modes, and a tuple for multi-band modes (one value
- per band). When creating RGB images, you can also use color
- strings as supported by the ImageColor module. If the color is
- None, the image is not initialised.
- :returns: An :py:class:`~PIL.Image.Image` object.
- """
-
- _check_size(size)
-
- if color is None:
- # don't initialize
- return Image()._new(core.new(mode, size))
-
- if isinstance(color, str):
- # css3-style specifier
-
- from . import ImageColor
-
- color = ImageColor.getcolor(color, mode)
-
- im = Image()
- if mode == "P" and isinstance(color, (list, tuple)) and len(color) in [3, 4]:
- # RGB or RGBA value for a P image
- from . import ImagePalette
-
- im.palette = ImagePalette.ImagePalette()
- color = im.palette.getcolor(color)
- return im._new(core.fill(mode, size, color))
-
-
-def frombytes(mode, size, data, decoder_name="raw", *args):
- """
- Creates a copy of an image memory from pixel data in a buffer.
-
- In its simplest form, this function takes three arguments
- (mode, size, and unpacked pixel data).
-
- You can also use any pixel decoder supported by PIL. For more
- information on available decoders, see the section
- :ref:`Writing Your Own File Codec `.
-
- Note that this function decodes pixel data only, not entire images.
- If you have an entire image in a string, wrap it in a
- :py:class:`~io.BytesIO` object, and use :py:func:`~PIL.Image.open` to load
- it.
-
- :param mode: The image mode. See: :ref:`concept-modes`.
- :param size: The image size.
- :param data: A byte buffer containing raw data for the given mode.
- :param decoder_name: What decoder to use.
- :param args: Additional parameters for the given decoder.
- :returns: An :py:class:`~PIL.Image.Image` object.
- """
-
- _check_size(size)
-
- # may pass tuple instead of argument list
- if len(args) == 1 and isinstance(args[0], tuple):
- args = args[0]
-
- if decoder_name == "raw" and args == ():
- args = mode
-
- im = new(mode, size)
- im.frombytes(data, decoder_name, args)
- return im
-
-
-def frombuffer(mode, size, data, decoder_name="raw", *args):
- """
- Creates an image memory referencing pixel data in a byte buffer.
-
- This function is similar to :py:func:`~PIL.Image.frombytes`, but uses data
- in the byte buffer, where possible. This means that changes to the
- original buffer object are reflected in this image). Not all modes can
- share memory; supported modes include "L", "RGBX", "RGBA", and "CMYK".
-
- Note that this function decodes pixel data only, not entire images.
- If you have an entire image file in a string, wrap it in a
- :py:class:`~io.BytesIO` object, and use :py:func:`~PIL.Image.open` to load it.
-
- In the current version, the default parameters used for the "raw" decoder
- differs from that used for :py:func:`~PIL.Image.frombytes`. This is a
- bug, and will probably be fixed in a future release. The current release
- issues a warning if you do this; to disable the warning, you should provide
- the full set of parameters. See below for details.
-
- :param mode: The image mode. See: :ref:`concept-modes`.
- :param size: The image size.
- :param data: A bytes or other buffer object containing raw
- data for the given mode.
- :param decoder_name: What decoder to use.
- :param args: Additional parameters for the given decoder. For the
- default encoder ("raw"), it's recommended that you provide the
- full set of parameters::
-
- frombuffer(mode, size, data, "raw", mode, 0, 1)
-
- :returns: An :py:class:`~PIL.Image.Image` object.
-
- .. versionadded:: 1.1.4
- """
-
- _check_size(size)
-
- # may pass tuple instead of argument list
- if len(args) == 1 and isinstance(args[0], tuple):
- args = args[0]
-
- if decoder_name == "raw":
- if args == ():
- args = mode, 0, 1
- if args[0] in _MAPMODES:
- im = new(mode, (1, 1))
- im = im._new(core.map_buffer(data, size, decoder_name, 0, args))
- im.readonly = 1
- return im
-
- return frombytes(mode, size, data, decoder_name, args)
-
-
-def fromarray(obj, mode=None):
- """
- Creates an image memory from an object exporting the array interface
- (using the buffer protocol).
-
- If ``obj`` is not contiguous, then the ``tobytes`` method is called
- and :py:func:`~PIL.Image.frombuffer` is used.
-
- If you have an image in NumPy::
-
- from PIL import Image
- import numpy as np
- im = Image.open("hopper.jpg")
- a = np.asarray(im)
-
- Then this can be used to convert it to a Pillow image::
-
- im = Image.fromarray(a)
-
- :param obj: Object with array interface
- :param mode: Optional mode to use when reading ``obj``. Will be determined from
- type if ``None``.
-
- This will not be used to convert the data after reading, but will be used to
- change how the data is read::
-
- from PIL import Image
- import numpy as np
- a = np.full((1, 1), 300)
- im = Image.fromarray(a, mode="L")
- im.getpixel((0, 0)) # 44
- im = Image.fromarray(a, mode="RGB")
- im.getpixel((0, 0)) # (44, 1, 0)
-
- See: :ref:`concept-modes` for general information about modes.
- :returns: An image object.
-
- .. versionadded:: 1.1.6
- """
- arr = obj.__array_interface__
- shape = arr["shape"]
- ndim = len(shape)
- strides = arr.get("strides", None)
- if mode is None:
- try:
- typekey = (1, 1) + shape[2:], arr["typestr"]
- except KeyError as e:
- raise TypeError("Cannot handle this data type") from e
- try:
- mode, rawmode = _fromarray_typemap[typekey]
- except KeyError as e:
- raise TypeError("Cannot handle this data type: %s, %s" % typekey) from e
- else:
- rawmode = mode
- if mode in ["1", "L", "I", "P", "F"]:
- ndmax = 2
- elif mode == "RGB":
- ndmax = 3
- else:
- ndmax = 4
- if ndim > ndmax:
- raise ValueError(f"Too many dimensions: {ndim} > {ndmax}.")
-
- size = 1 if ndim == 1 else shape[1], shape[0]
- if strides is not None:
- if hasattr(obj, "tobytes"):
- obj = obj.tobytes()
- else:
- obj = obj.tostring()
-
- return frombuffer(mode, size, obj, "raw", rawmode, 0, 1)
-
-
-def fromqimage(im):
- """Creates an image instance from a QImage image"""
- from . import ImageQt
-
- if not ImageQt.qt_is_installed:
- raise ImportError("Qt bindings are not installed")
- return ImageQt.fromqimage(im)
-
-
-def fromqpixmap(im):
- """Creates an image instance from a QPixmap image"""
- from . import ImageQt
-
- if not ImageQt.qt_is_installed:
- raise ImportError("Qt bindings are not installed")
- return ImageQt.fromqpixmap(im)
-
-
-_fromarray_typemap = {
- # (shape, typestr) => mode, rawmode
- # first two members of shape are set to one
- ((1, 1), "|b1"): ("1", "1;8"),
- ((1, 1), "|u1"): ("L", "L"),
- ((1, 1), "|i1"): ("I", "I;8"),
- ((1, 1), "u2"): ("I", "I;16B"),
- ((1, 1), "i2"): ("I", "I;16BS"),
- ((1, 1), "u4"): ("I", "I;32B"),
- ((1, 1), "i4"): ("I", "I;32BS"),
- ((1, 1), "f4"): ("F", "F;32BF"),
- ((1, 1), "f8"): ("F", "F;64BF"),
- ((1, 1, 2), "|u1"): ("LA", "LA"),
- ((1, 1, 3), "|u1"): ("RGB", "RGB"),
- ((1, 1, 4), "|u1"): ("RGBA", "RGBA"),
-}
-
-# shortcuts
-_fromarray_typemap[((1, 1), _ENDIAN + "i4")] = ("I", "I")
-_fromarray_typemap[((1, 1), _ENDIAN + "f4")] = ("F", "F")
-
-
-def _decompression_bomb_check(size):
- if MAX_IMAGE_PIXELS is None:
- return
-
- pixels = size[0] * size[1]
-
- if pixels > 2 * MAX_IMAGE_PIXELS:
- raise DecompressionBombError(
- f"Image size ({pixels} pixels) exceeds limit of {2 * MAX_IMAGE_PIXELS} "
- "pixels, could be decompression bomb DOS attack."
- )
-
- if pixels > MAX_IMAGE_PIXELS:
- warnings.warn(
- f"Image size ({pixels} pixels) exceeds limit of {MAX_IMAGE_PIXELS} pixels, "
- "could be decompression bomb DOS attack.",
- DecompressionBombWarning,
- )
-
-
-def open(fp, mode="r", formats=None):
- """
- Opens and identifies the given image file.
-
- This is a lazy operation; this function identifies the file, but
- the file remains open and the actual image data is not read from
- the file until you try to process the data (or call the
- :py:meth:`~PIL.Image.Image.load` method). See
- :py:func:`~PIL.Image.new`. See :ref:`file-handling`.
-
- :param fp: A filename (string), pathlib.Path object or a file object.
- The file object must implement ``file.read``,
- ``file.seek``, and ``file.tell`` methods,
- and be opened in binary mode.
- :param mode: The mode. If given, this argument must be "r".
- :param formats: A list or tuple of formats to attempt to load the file in.
- This can be used to restrict the set of formats checked.
- Pass ``None`` to try all supported formats. You can print the set of
- available formats by running ``python3 -m PIL`` or using
- the :py:func:`PIL.features.pilinfo` function.
- :returns: An :py:class:`~PIL.Image.Image` object.
- :exception FileNotFoundError: If the file cannot be found.
- :exception PIL.UnidentifiedImageError: If the image cannot be opened and
- identified.
- :exception ValueError: If the ``mode`` is not "r", or if a ``StringIO``
- instance is used for ``fp``.
- :exception TypeError: If ``formats`` is not ``None``, a list or a tuple.
- """
-
- if mode != "r":
- raise ValueError(f"bad mode {repr(mode)}")
- elif isinstance(fp, io.StringIO):
- raise ValueError(
- "StringIO cannot be used to open an image. "
- "Binary data must be used instead."
- )
-
- if formats is None:
- formats = ID
- elif not isinstance(formats, (list, tuple)):
- raise TypeError("formats must be a list or tuple")
-
- exclusive_fp = False
- filename = ""
- if isinstance(fp, Path):
- filename = str(fp.resolve())
- elif isPath(fp):
- filename = fp
-
- if filename:
- fp = builtins.open(filename, "rb")
- exclusive_fp = True
-
- try:
- fp.seek(0)
- except (AttributeError, io.UnsupportedOperation):
- fp = io.BytesIO(fp.read())
- exclusive_fp = True
-
- prefix = fp.read(16)
-
- preinit()
-
- accept_warnings = []
-
- def _open_core(fp, filename, prefix, formats):
- for i in formats:
- i = i.upper()
- if i not in OPEN:
- init()
- try:
- factory, accept = OPEN[i]
- result = not accept or accept(prefix)
- if type(result) in [str, bytes]:
- accept_warnings.append(result)
- elif result:
- fp.seek(0)
- im = factory(fp, filename)
- _decompression_bomb_check(im.size)
- return im
- except (SyntaxError, IndexError, TypeError, struct.error):
- # Leave disabled by default, spams the logs with image
- # opening failures that are entirely expected.
- # logger.debug("", exc_info=True)
- continue
- except BaseException:
- if exclusive_fp:
- fp.close()
- raise
- return None
-
- im = _open_core(fp, filename, prefix, formats)
-
- if im is None:
- if init():
- im = _open_core(fp, filename, prefix, formats)
-
- if im:
- im._exclusive_fp = exclusive_fp
- return im
-
- if exclusive_fp:
- fp.close()
- for message in accept_warnings:
- warnings.warn(message)
- raise UnidentifiedImageError(
- "cannot identify image file %r" % (filename if filename else fp)
- )
-
-
-#
-# Image processing.
-
-
-def alpha_composite(im1, im2):
- """
- Alpha composite im2 over im1.
-
- :param im1: The first image. Must have mode RGBA.
- :param im2: The second image. Must have mode RGBA, and the same size as
- the first image.
- :returns: An :py:class:`~PIL.Image.Image` object.
- """
-
- im1.load()
- im2.load()
- return im1._new(core.alpha_composite(im1.im, im2.im))
-
-
-def blend(im1, im2, alpha):
- """
- Creates a new image by interpolating between two input images, using
- a constant alpha::
-
- out = image1 * (1.0 - alpha) + image2 * alpha
-
- :param im1: The first image.
- :param im2: The second image. Must have the same mode and size as
- the first image.
- :param alpha: The interpolation alpha factor. If alpha is 0.0, a
- copy of the first image is returned. If alpha is 1.0, a copy of
- the second image is returned. There are no restrictions on the
- alpha value. If necessary, the result is clipped to fit into
- the allowed output range.
- :returns: An :py:class:`~PIL.Image.Image` object.
- """
-
- im1.load()
- im2.load()
- return im1._new(core.blend(im1.im, im2.im, alpha))
-
-
-def composite(image1, image2, mask):
- """
- Create composite image by blending images using a transparency mask.
-
- :param image1: The first image.
- :param image2: The second image. Must have the same mode and
- size as the first image.
- :param mask: A mask image. This image can have mode
- "1", "L", or "RGBA", and must have the same size as the
- other two images.
- """
-
- image = image2.copy()
- image.paste(image1, None, mask)
- return image
-
-
-def eval(image, *args):
- """
- Applies the function (which should take one argument) to each pixel
- in the given image. If the image has more than one band, the same
- function is applied to each band. Note that the function is
- evaluated once for each possible pixel value, so you cannot use
- random components or other generators.
-
- :param image: The input image.
- :param function: A function object, taking one integer argument.
- :returns: An :py:class:`~PIL.Image.Image` object.
- """
-
- return image.point(args[0])
-
-
-def merge(mode, bands):
- """
- Merge a set of single band images into a new multiband image.
-
- :param mode: The mode to use for the output image. See:
- :ref:`concept-modes`.
- :param bands: A sequence containing one single-band image for
- each band in the output image. All bands must have the
- same size.
- :returns: An :py:class:`~PIL.Image.Image` object.
- """
-
- if getmodebands(mode) != len(bands) or "*" in mode:
- raise ValueError("wrong number of bands")
- for band in bands[1:]:
- if band.mode != getmodetype(mode):
- raise ValueError("mode mismatch")
- if band.size != bands[0].size:
- raise ValueError("size mismatch")
- for band in bands:
- band.load()
- return bands[0]._new(core.merge(mode, *[b.im for b in bands]))
-
-
-# --------------------------------------------------------------------
-# Plugin registry
-
-
-def register_open(id, factory, accept=None):
- """
- Register an image file plugin. This function should not be used
- in application code.
-
- :param id: An image format identifier.
- :param factory: An image file factory method.
- :param accept: An optional function that can be used to quickly
- reject images having another format.
- """
- id = id.upper()
- ID.append(id)
- OPEN[id] = factory, accept
-
-
-def register_mime(id, mimetype):
- """
- Registers an image MIME type. This function should not be used
- in application code.
-
- :param id: An image format identifier.
- :param mimetype: The image MIME type for this format.
- """
- MIME[id.upper()] = mimetype
-
-
-def register_save(id, driver):
- """
- Registers an image save function. This function should not be
- used in application code.
-
- :param id: An image format identifier.
- :param driver: A function to save images in this format.
- """
- SAVE[id.upper()] = driver
-
-
-def register_save_all(id, driver):
- """
- Registers an image function to save all the frames
- of a multiframe format. This function should not be
- used in application code.
-
- :param id: An image format identifier.
- :param driver: A function to save images in this format.
- """
- SAVE_ALL[id.upper()] = driver
-
-
-def register_extension(id, extension):
- """
- Registers an image extension. This function should not be
- used in application code.
-
- :param id: An image format identifier.
- :param extension: An extension used for this format.
- """
- EXTENSION[extension.lower()] = id.upper()
-
-
-def register_extensions(id, extensions):
- """
- Registers image extensions. This function should not be
- used in application code.
-
- :param id: An image format identifier.
- :param extensions: A list of extensions used for this format.
- """
- for extension in extensions:
- register_extension(id, extension)
-
-
-def registered_extensions():
- """
- Returns a dictionary containing all file extensions belonging
- to registered plugins
- """
- if not EXTENSION:
- init()
- return EXTENSION
-
-
-def register_decoder(name, decoder):
- """
- Registers an image decoder. This function should not be
- used in application code.
-
- :param name: The name of the decoder
- :param decoder: A callable(mode, args) that returns an
- ImageFile.PyDecoder object
-
- .. versionadded:: 4.1.0
- """
- DECODERS[name] = decoder
-
-
-def register_encoder(name, encoder):
- """
- Registers an image encoder. This function should not be
- used in application code.
-
- :param name: The name of the encoder
- :param encoder: A callable(mode, args) that returns an
- ImageFile.PyEncoder object
-
- .. versionadded:: 4.1.0
- """
- ENCODERS[name] = encoder
-
-
-# --------------------------------------------------------------------
-# Simple display support.
-
-
-def _show(image, **options):
- from . import ImageShow
-
- ImageShow.show(image, **options)
-
-
-# --------------------------------------------------------------------
-# Effects
-
-
-def effect_mandelbrot(size, extent, quality):
- """
- Generate a Mandelbrot set covering the given extent.
-
- :param size: The requested size in pixels, as a 2-tuple:
- (width, height).
- :param extent: The extent to cover, as a 4-tuple:
- (x0, y0, x1, y2).
- :param quality: Quality.
- """
- return Image()._new(core.effect_mandelbrot(size, extent, quality))
-
-
-def effect_noise(size, sigma):
- """
- Generate Gaussian noise centered around 128.
-
- :param size: The requested size in pixels, as a 2-tuple:
- (width, height).
- :param sigma: Standard deviation of noise.
- """
- return Image()._new(core.effect_noise(size, sigma))
-
-
-def linear_gradient(mode):
- """
- Generate 256x256 linear gradient from black to white, top to bottom.
-
- :param mode: Input mode.
- """
- return Image()._new(core.linear_gradient(mode))
-
-
-def radial_gradient(mode):
- """
- Generate 256x256 radial gradient from black to white, centre to edge.
-
- :param mode: Input mode.
- """
- return Image()._new(core.radial_gradient(mode))
-
-
-# --------------------------------------------------------------------
-# Resources
-
-
-def _apply_env_variables(env=None):
- if env is None:
- env = os.environ
-
- for var_name, setter in [
- ("PILLOW_ALIGNMENT", core.set_alignment),
- ("PILLOW_BLOCK_SIZE", core.set_block_size),
- ("PILLOW_BLOCKS_MAX", core.set_blocks_max),
- ]:
- if var_name not in env:
- continue
-
- var = env[var_name].lower()
-
- units = 1
- for postfix, mul in [("k", 1024), ("m", 1024 * 1024)]:
- if var.endswith(postfix):
- units = mul
- var = var[: -len(postfix)]
-
- try:
- var = int(var) * units
- except ValueError:
- warnings.warn(f"{var_name} is not int")
- continue
-
- try:
- setter(var)
- except ValueError as e:
- warnings.warn(f"{var_name}: {e}")
-
-
-_apply_env_variables()
-atexit.register(core.clear_cache)
-
-
-class Exif(MutableMapping):
- endian = None
- bigtiff = False
-
- def __init__(self):
- self._data = {}
- self._ifds = {}
- self._info = None
- self._loaded_exif = None
-
- def _fixup(self, value):
- try:
- if len(value) == 1 and isinstance(value, tuple):
- return value[0]
- except Exception:
- pass
- return value
-
- def _fixup_dict(self, src_dict):
- # Helper function
- # returns a dict with any single item tuples/lists as individual values
- return {k: self._fixup(v) for k, v in src_dict.items()}
-
- def _get_ifd_dict(self, offset):
- try:
- # an offset pointer to the location of the nested embedded IFD.
- # It should be a long, but may be corrupted.
- self.fp.seek(offset)
- except (KeyError, TypeError):
- pass
- else:
- from . import TiffImagePlugin
-
- info = TiffImagePlugin.ImageFileDirectory_v2(self.head)
- info.load(self.fp)
- return self._fixup_dict(info)
-
- def _get_head(self):
- version = b"\x2B" if self.bigtiff else b"\x2A"
- if self.endian == "<":
- head = b"II" + version + b"\x00" + o32le(8)
- else:
- head = b"MM\x00" + version + o32be(8)
- if self.bigtiff:
- head += o32le(8) if self.endian == "<" else o32be(8)
- head += b"\x00\x00\x00\x00"
- return head
-
- def load(self, data):
- # Extract EXIF information. This is highly experimental,
- # and is likely to be replaced with something better in a future
- # version.
-
- # The EXIF record consists of a TIFF file embedded in a JPEG
- # application marker (!).
- if data == self._loaded_exif:
- return
- self._loaded_exif = data
- self._data.clear()
- self._ifds.clear()
- if data and data.startswith(b"Exif\x00\x00"):
- data = data[6:]
- if not data:
- self._info = None
- return
-
- self.fp = io.BytesIO(data)
- self.head = self.fp.read(8)
- # process dictionary
- from . import TiffImagePlugin
-
- self._info = TiffImagePlugin.ImageFileDirectory_v2(self.head)
- self.endian = self._info._endian
- self.fp.seek(self._info.next)
- self._info.load(self.fp)
-
- def load_from_fp(self, fp, offset=None):
- self._loaded_exif = None
- self._data.clear()
- self._ifds.clear()
-
- # process dictionary
- from . import TiffImagePlugin
-
- self.fp = fp
- if offset is not None:
- self.head = self._get_head()
- else:
- self.head = self.fp.read(8)
- self._info = TiffImagePlugin.ImageFileDirectory_v2(self.head)
- if self.endian is None:
- self.endian = self._info._endian
- if offset is None:
- offset = self._info.next
- self.fp.seek(offset)
- self._info.load(self.fp)
-
- def _get_merged_dict(self):
- merged_dict = dict(self)
-
- # get EXIF extension
- if 0x8769 in self:
- ifd = self._get_ifd_dict(self[0x8769])
- if ifd:
- merged_dict.update(ifd)
-
- # GPS
- if 0x8825 in self:
- merged_dict[0x8825] = self._get_ifd_dict(self[0x8825])
-
- return merged_dict
-
- def tobytes(self, offset=8):
- from . import TiffImagePlugin
-
- head = self._get_head()
- ifd = TiffImagePlugin.ImageFileDirectory_v2(ifh=head)
- for tag, value in self.items():
- if tag in [0x8769, 0x8225, 0x8825] and not isinstance(value, dict):
- value = self.get_ifd(tag)
- if (
- tag == 0x8769
- and 0xA005 in value
- and not isinstance(value[0xA005], dict)
- ):
- value = value.copy()
- value[0xA005] = self.get_ifd(0xA005)
- ifd[tag] = value
- return b"Exif\x00\x00" + head + ifd.tobytes(offset)
-
- def get_ifd(self, tag):
- if tag not in self._ifds:
- if tag in [0x8769, 0x8825]:
- # exif, gpsinfo
- if tag in self:
- self._ifds[tag] = self._get_ifd_dict(self[tag])
- elif tag in [0xA005, 0x927C]:
- # interop, makernote
- if 0x8769 not in self._ifds:
- self.get_ifd(0x8769)
- tag_data = self._ifds[0x8769][tag]
- if tag == 0x927C:
- # makernote
- from .TiffImagePlugin import ImageFileDirectory_v2
-
- if tag_data[:8] == b"FUJIFILM":
- ifd_offset = i32le(tag_data, 8)
- ifd_data = tag_data[ifd_offset:]
-
- makernote = {}
- for i in range(0, struct.unpack(" 4:
- (offset,) = struct.unpack("H", tag_data[:2])[0]):
- ifd_tag, typ, count, data = struct.unpack(
- ">HHL4s", tag_data[i * 12 + 2 : (i + 1) * 12 + 2]
- )
- if ifd_tag == 0x1101:
- # CameraInfo
- (offset,) = struct.unpack(">L", data)
- self.fp.seek(offset)
-
- camerainfo = {"ModelID": self.fp.read(4)}
-
- self.fp.read(4)
- # Seconds since 2000
- camerainfo["TimeStamp"] = i32le(self.fp.read(12))
-
- self.fp.read(4)
- camerainfo["InternalSerialNumber"] = self.fp.read(4)
-
- self.fp.read(12)
- parallax = self.fp.read(4)
- handler = ImageFileDirectory_v2._load_dispatch[
- TiffTags.FLOAT
- ][1]
- camerainfo["Parallax"] = handler(
- ImageFileDirectory_v2(), parallax, False
- )
-
- self.fp.read(4)
- camerainfo["Category"] = self.fp.read(2)
-
- makernote = {0x1101: dict(self._fixup_dict(camerainfo))}
- self._ifds[tag] = makernote
- else:
- # interop
- self._ifds[tag] = self._get_ifd_dict(tag_data)
- return self._ifds.get(tag, {})
-
- def __str__(self):
- if self._info is not None:
- # Load all keys into self._data
- for tag in self._info.keys():
- self[tag]
-
- return str(self._data)
-
- def __len__(self):
- keys = set(self._data)
- if self._info is not None:
- keys.update(self._info)
- return len(keys)
-
- def __getitem__(self, tag):
- if self._info is not None and tag not in self._data and tag in self._info:
- self._data[tag] = self._fixup(self._info[tag])
- del self._info[tag]
- return self._data[tag]
-
- def __contains__(self, tag):
- return tag in self._data or (self._info is not None and tag in self._info)
-
- def __setitem__(self, tag, value):
- if self._info is not None and tag in self._info:
- del self._info[tag]
- self._data[tag] = value
-
- def __delitem__(self, tag):
- if self._info is not None and tag in self._info:
- del self._info[tag]
- else:
- del self._data[tag]
-
- def __iter__(self):
- keys = set(self._data)
- if self._info is not None:
- keys.update(self._info)
- return iter(keys)
diff --git a/venv/Lib/site-packages/PIL/ImageChops.py b/venv/Lib/site-packages/PIL/ImageChops.py
deleted file mode 100644
index 61d3a29..0000000
--- a/venv/Lib/site-packages/PIL/ImageChops.py
+++ /dev/null
@@ -1,328 +0,0 @@
-#
-# The Python Imaging Library.
-# $Id$
-#
-# standard channel operations
-#
-# History:
-# 1996-03-24 fl Created
-# 1996-08-13 fl Added logical operations (for "1" images)
-# 2000-10-12 fl Added offset method (from Image.py)
-#
-# Copyright (c) 1997-2000 by Secret Labs AB
-# Copyright (c) 1996-2000 by Fredrik Lundh
-#
-# See the README file for information on usage and redistribution.
-#
-
-from . import Image
-
-
-def constant(image, value):
- """Fill a channel with a given grey level.
-
- :rtype: :py:class:`~PIL.Image.Image`
- """
-
- return Image.new("L", image.size, value)
-
-
-def duplicate(image):
- """Copy a channel. Alias for :py:meth:`PIL.Image.Image.copy`.
-
- :rtype: :py:class:`~PIL.Image.Image`
- """
-
- return image.copy()
-
-
-def invert(image):
- """
- Invert an image (channel).
-
- .. code-block:: python
-
- out = MAX - image
-
- :rtype: :py:class:`~PIL.Image.Image`
- """
-
- image.load()
- return image._new(image.im.chop_invert())
-
-
-def lighter(image1, image2):
- """
- Compares the two images, pixel by pixel, and returns a new image containing
- the lighter values.
-
- .. code-block:: python
-
- out = max(image1, image2)
-
- :rtype: :py:class:`~PIL.Image.Image`
- """
-
- image1.load()
- image2.load()
- return image1._new(image1.im.chop_lighter(image2.im))
-
-
-def darker(image1, image2):
- """
- Compares the two images, pixel by pixel, and returns a new image containing
- the darker values.
-
- .. code-block:: python
-
- out = min(image1, image2)
-
- :rtype: :py:class:`~PIL.Image.Image`
- """
-
- image1.load()
- image2.load()
- return image1._new(image1.im.chop_darker(image2.im))
-
-
-def difference(image1, image2):
- """
- Returns the absolute value of the pixel-by-pixel difference between the two
- images.
-
- .. code-block:: python
-
- out = abs(image1 - image2)
-
- :rtype: :py:class:`~PIL.Image.Image`
- """
-
- image1.load()
- image2.load()
- return image1._new(image1.im.chop_difference(image2.im))
-
-
-def multiply(image1, image2):
- """
- Superimposes two images on top of each other.
-
- If you multiply an image with a solid black image, the result is black. If
- you multiply with a solid white image, the image is unaffected.
-
- .. code-block:: python
-
- out = image1 * image2 / MAX
-
- :rtype: :py:class:`~PIL.Image.Image`
- """
-
- image1.load()
- image2.load()
- return image1._new(image1.im.chop_multiply(image2.im))
-
-
-def screen(image1, image2):
- """
- Superimposes two inverted images on top of each other.
-
- .. code-block:: python
-
- out = MAX - ((MAX - image1) * (MAX - image2) / MAX)
-
- :rtype: :py:class:`~PIL.Image.Image`
- """
-
- image1.load()
- image2.load()
- return image1._new(image1.im.chop_screen(image2.im))
-
-
-def soft_light(image1, image2):
- """
- Superimposes two images on top of each other using the Soft Light algorithm
-
- :rtype: :py:class:`~PIL.Image.Image`
- """
-
- image1.load()
- image2.load()
- return image1._new(image1.im.chop_soft_light(image2.im))
-
-
-def hard_light(image1, image2):
- """
- Superimposes two images on top of each other using the Hard Light algorithm
-
- :rtype: :py:class:`~PIL.Image.Image`
- """
-
- image1.load()
- image2.load()
- return image1._new(image1.im.chop_hard_light(image2.im))
-
-
-def overlay(image1, image2):
- """
- Superimposes two images on top of each other using the Overlay algorithm
-
- :rtype: :py:class:`~PIL.Image.Image`
- """
-
- image1.load()
- image2.load()
- return image1._new(image1.im.chop_overlay(image2.im))
-
-
-def add(image1, image2, scale=1.0, offset=0):
- """
- Adds two images, dividing the result by scale and adding the
- offset. If omitted, scale defaults to 1.0, and offset to 0.0.
-
- .. code-block:: python
-
- out = ((image1 + image2) / scale + offset)
-
- :rtype: :py:class:`~PIL.Image.Image`
- """
-
- image1.load()
- image2.load()
- return image1._new(image1.im.chop_add(image2.im, scale, offset))
-
-
-def subtract(image1, image2, scale=1.0, offset=0):
- """
- Subtracts two images, dividing the result by scale and adding the offset.
- If omitted, scale defaults to 1.0, and offset to 0.0.
-
- .. code-block:: python
-
- out = ((image1 - image2) / scale + offset)
-
- :rtype: :py:class:`~PIL.Image.Image`
- """
-
- image1.load()
- image2.load()
- return image1._new(image1.im.chop_subtract(image2.im, scale, offset))
-
-
-def add_modulo(image1, image2):
- """Add two images, without clipping the result.
-
- .. code-block:: python
-
- out = ((image1 + image2) % MAX)
-
- :rtype: :py:class:`~PIL.Image.Image`
- """
-
- image1.load()
- image2.load()
- return image1._new(image1.im.chop_add_modulo(image2.im))
-
-
-def subtract_modulo(image1, image2):
- """Subtract two images, without clipping the result.
-
- .. code-block:: python
-
- out = ((image1 - image2) % MAX)
-
- :rtype: :py:class:`~PIL.Image.Image`
- """
-
- image1.load()
- image2.load()
- return image1._new(image1.im.chop_subtract_modulo(image2.im))
-
-
-def logical_and(image1, image2):
- """Logical AND between two images.
-
- Both of the images must have mode "1". If you would like to perform a
- logical AND on an image with a mode other than "1", try
- :py:meth:`~PIL.ImageChops.multiply` instead, using a black-and-white mask
- as the second image.
-
- .. code-block:: python
-
- out = ((image1 and image2) % MAX)
-
- :rtype: :py:class:`~PIL.Image.Image`
- """
-
- image1.load()
- image2.load()
- return image1._new(image1.im.chop_and(image2.im))
-
-
-def logical_or(image1, image2):
- """Logical OR between two images.
-
- Both of the images must have mode "1".
-
- .. code-block:: python
-
- out = ((image1 or image2) % MAX)
-
- :rtype: :py:class:`~PIL.Image.Image`
- """
-
- image1.load()
- image2.load()
- return image1._new(image1.im.chop_or(image2.im))
-
-
-def logical_xor(image1, image2):
- """Logical XOR between two images.
-
- Both of the images must have mode "1".
-
- .. code-block:: python
-
- out = ((bool(image1) != bool(image2)) % MAX)
-
- :rtype: :py:class:`~PIL.Image.Image`
- """
-
- image1.load()
- image2.load()
- return image1._new(image1.im.chop_xor(image2.im))
-
-
-def blend(image1, image2, alpha):
- """Blend images using constant transparency weight. Alias for
- :py:func:`PIL.Image.blend`.
-
- :rtype: :py:class:`~PIL.Image.Image`
- """
-
- return Image.blend(image1, image2, alpha)
-
-
-def composite(image1, image2, mask):
- """Create composite using transparency mask. Alias for
- :py:func:`PIL.Image.composite`.
-
- :rtype: :py:class:`~PIL.Image.Image`
- """
-
- return Image.composite(image1, image2, mask)
-
-
-def offset(image, xoffset, yoffset=None):
- """Returns a copy of the image where data has been offset by the given
- distances. Data wraps around the edges. If ``yoffset`` is omitted, it
- is assumed to be equal to ``xoffset``.
-
- :param xoffset: The horizontal distance.
- :param yoffset: The vertical distance. If omitted, both
- distances are set to the same value.
- :rtype: :py:class:`~PIL.Image.Image`
- """
-
- if yoffset is None:
- yoffset = xoffset
- image.load()
- return image._new(image.im.offset(xoffset, yoffset))
diff --git a/venv/Lib/site-packages/PIL/ImageCms.py b/venv/Lib/site-packages/PIL/ImageCms.py
deleted file mode 100644
index ea328e1..0000000
--- a/venv/Lib/site-packages/PIL/ImageCms.py
+++ /dev/null
@@ -1,1029 +0,0 @@
-# The Python Imaging Library.
-# $Id$
-
-# Optional color management support, based on Kevin Cazabon's PyCMS
-# library.
-
-# History:
-
-# 2009-03-08 fl Added to PIL.
-
-# Copyright (C) 2002-2003 Kevin Cazabon
-# Copyright (c) 2009 by Fredrik Lundh
-# Copyright (c) 2013 by Eric Soroos
-
-# See the README file for information on usage and redistribution. See
-# below for the original description.
-
-import sys
-import warnings
-from enum import IntEnum
-
-from PIL import Image
-
-try:
- from PIL import _imagingcms
-except ImportError as ex:
- # Allow error import for doc purposes, but error out when accessing
- # anything in core.
- from ._util import deferred_error
-
- _imagingcms = deferred_error(ex)
-
-DESCRIPTION = """
-pyCMS
-
- a Python / PIL interface to the littleCMS ICC Color Management System
- Copyright (C) 2002-2003 Kevin Cazabon
- kevin@cazabon.com
- https://www.cazabon.com
-
- pyCMS home page: https://www.cazabon.com/pyCMS
- littleCMS home page: https://www.littlecms.com
- (littleCMS is Copyright (C) 1998-2001 Marti Maria)
-
- Originally released under LGPL. Graciously donated to PIL in
- March 2009, for distribution under the standard PIL license
-
- The pyCMS.py module provides a "clean" interface between Python/PIL and
- pyCMSdll, taking care of some of the more complex handling of the direct
- pyCMSdll functions, as well as error-checking and making sure that all
- relevant data is kept together.
-
- While it is possible to call pyCMSdll functions directly, it's not highly
- recommended.
-
- Version History:
-
- 1.0.0 pil Oct 2013 Port to LCMS 2.
-
- 0.1.0 pil mod March 10, 2009
-
- Renamed display profile to proof profile. The proof
- profile is the profile of the device that is being
- simulated, not the profile of the device which is
- actually used to display/print the final simulation
- (that'd be the output profile) - also see LCMSAPI.txt
- input colorspace -> using 'renderingIntent' -> proof
- colorspace -> using 'proofRenderingIntent' -> output
- colorspace
-
- Added LCMS FLAGS support.
- Added FLAGS["SOFTPROOFING"] as default flag for
- buildProofTransform (otherwise the proof profile/intent
- would be ignored).
-
- 0.1.0 pil March 2009 - added to PIL, as PIL.ImageCms
-
- 0.0.2 alpha Jan 6, 2002
-
- Added try/except statements around type() checks of
- potential CObjects... Python won't let you use type()
- on them, and raises a TypeError (stupid, if you ask
- me!)
-
- Added buildProofTransformFromOpenProfiles() function.
- Additional fixes in DLL, see DLL code for details.
-
- 0.0.1 alpha first public release, Dec. 26, 2002
-
- Known to-do list with current version (of Python interface, not pyCMSdll):
-
- none
-
-"""
-
-VERSION = "1.0.0 pil"
-
-# --------------------------------------------------------------------.
-
-core = _imagingcms
-
-#
-# intent/direction values
-
-
-class Intent(IntEnum):
- PERCEPTUAL = 0
- RELATIVE_COLORIMETRIC = 1
- SATURATION = 2
- ABSOLUTE_COLORIMETRIC = 3
-
-
-class Direction(IntEnum):
- INPUT = 0
- OUTPUT = 1
- PROOF = 2
-
-
-def __getattr__(name):
- deprecated = "deprecated and will be removed in Pillow 10 (2023-07-01). "
- for enum, prefix in {Intent: "INTENT_", Direction: "DIRECTION_"}.items():
- if name.startswith(prefix):
- name = name[len(prefix) :]
- if name in enum.__members__:
- warnings.warn(
- prefix
- + name
- + " is "
- + deprecated
- + "Use "
- + enum.__name__
- + "."
- + name
- + " instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- return enum[name]
- raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
-
-
-#
-# flags
-
-FLAGS = {
- "MATRIXINPUT": 1,
- "MATRIXOUTPUT": 2,
- "MATRIXONLY": (1 | 2),
- "NOWHITEONWHITEFIXUP": 4, # Don't hot fix scum dot
- # Don't create prelinearization tables on precalculated transforms
- # (internal use):
- "NOPRELINEARIZATION": 16,
- "GUESSDEVICECLASS": 32, # Guess device class (for transform2devicelink)
- "NOTCACHE": 64, # Inhibit 1-pixel cache
- "NOTPRECALC": 256,
- "NULLTRANSFORM": 512, # Don't transform anyway
- "HIGHRESPRECALC": 1024, # Use more memory to give better accuracy
- "LOWRESPRECALC": 2048, # Use less memory to minimize resources
- "WHITEBLACKCOMPENSATION": 8192,
- "BLACKPOINTCOMPENSATION": 8192,
- "GAMUTCHECK": 4096, # Out of Gamut alarm
- "SOFTPROOFING": 16384, # Do softproofing
- "PRESERVEBLACK": 32768, # Black preservation
- "NODEFAULTRESOURCEDEF": 16777216, # CRD special
- "GRIDPOINTS": lambda n: ((n) & 0xFF) << 16, # Gridpoints
-}
-
-_MAX_FLAG = 0
-for flag in FLAGS.values():
- if isinstance(flag, int):
- _MAX_FLAG = _MAX_FLAG | flag
-
-
-# --------------------------------------------------------------------.
-# Experimental PIL-level API
-# --------------------------------------------------------------------.
-
-##
-# Profile.
-
-
-class ImageCmsProfile:
- def __init__(self, profile):
- """
- :param profile: Either a string representing a filename,
- a file like object containing a profile or a
- low-level profile object
-
- """
-
- if isinstance(profile, str):
- if sys.platform == "win32":
- profile_bytes_path = profile.encode()
- try:
- profile_bytes_path.decode("ascii")
- except UnicodeDecodeError:
- with open(profile, "rb") as f:
- self._set(core.profile_frombytes(f.read()))
- return
- self._set(core.profile_open(profile), profile)
- elif hasattr(profile, "read"):
- self._set(core.profile_frombytes(profile.read()))
- elif isinstance(profile, _imagingcms.CmsProfile):
- self._set(profile)
- else:
- raise TypeError("Invalid type for Profile")
-
- def _set(self, profile, filename=None):
- self.profile = profile
- self.filename = filename
- if profile:
- self.product_name = None # profile.product_name
- self.product_info = None # profile.product_info
- else:
- self.product_name = None
- self.product_info = None
-
- def tobytes(self):
- """
- Returns the profile in a format suitable for embedding in
- saved images.
-
- :returns: a bytes object containing the ICC profile.
- """
-
- return core.profile_tobytes(self.profile)
-
-
-class ImageCmsTransform(Image.ImagePointHandler):
-
- """
- Transform. This can be used with the procedural API, or with the standard
- :py:func:`~PIL.Image.Image.point` method.
-
- Will return the output profile in the ``output.info['icc_profile']``.
- """
-
- def __init__(
- self,
- input,
- output,
- input_mode,
- output_mode,
- intent=Intent.PERCEPTUAL,
- proof=None,
- proof_intent=Intent.ABSOLUTE_COLORIMETRIC,
- flags=0,
- ):
- if proof is None:
- self.transform = core.buildTransform(
- input.profile, output.profile, input_mode, output_mode, intent, flags
- )
- else:
- self.transform = core.buildProofTransform(
- input.profile,
- output.profile,
- proof.profile,
- input_mode,
- output_mode,
- intent,
- proof_intent,
- flags,
- )
- # Note: inputMode and outputMode are for pyCMS compatibility only
- self.input_mode = self.inputMode = input_mode
- self.output_mode = self.outputMode = output_mode
-
- self.output_profile = output
-
- def point(self, im):
- return self.apply(im)
-
- def apply(self, im, imOut=None):
- im.load()
- if imOut is None:
- imOut = Image.new(self.output_mode, im.size, None)
- self.transform.apply(im.im.id, imOut.im.id)
- imOut.info["icc_profile"] = self.output_profile.tobytes()
- return imOut
-
- def apply_in_place(self, im):
- im.load()
- if im.mode != self.output_mode:
- raise ValueError("mode mismatch") # wrong output mode
- self.transform.apply(im.im.id, im.im.id)
- im.info["icc_profile"] = self.output_profile.tobytes()
- return im
-
-
-def get_display_profile(handle=None):
- """
- (experimental) Fetches the profile for the current display device.
-
- :returns: ``None`` if the profile is not known.
- """
-
- if sys.platform != "win32":
- return None
-
- from PIL import ImageWin
-
- if isinstance(handle, ImageWin.HDC):
- profile = core.get_display_profile_win32(handle, 1)
- else:
- profile = core.get_display_profile_win32(handle or 0)
- if profile is None:
- return None
- return ImageCmsProfile(profile)
-
-
-# --------------------------------------------------------------------.
-# pyCMS compatible layer
-# --------------------------------------------------------------------.
-
-
-class PyCMSError(Exception):
-
- """(pyCMS) Exception class.
- This is used for all errors in the pyCMS API."""
-
- pass
-
-
-def profileToProfile(
- im,
- inputProfile,
- outputProfile,
- renderingIntent=Intent.PERCEPTUAL,
- outputMode=None,
- inPlace=False,
- flags=0,
-):
- """
- (pyCMS) Applies an ICC transformation to a given image, mapping from
- ``inputProfile`` to ``outputProfile``.
-
- If the input or output profiles specified are not valid filenames, a
- :exc:`PyCMSError` will be raised. If ``inPlace`` is ``True`` and
- ``outputMode != im.mode``, a :exc:`PyCMSError` will be raised.
- If an error occurs during application of the profiles,
- a :exc:`PyCMSError` will be raised.
- If ``outputMode`` is not a mode supported by the ``outputProfile`` (or by pyCMS),
- a :exc:`PyCMSError` will be raised.
-
- This function applies an ICC transformation to im from ``inputProfile``'s
- color space to ``outputProfile``'s color space using the specified rendering
- intent to decide how to handle out-of-gamut colors.
-
- ``outputMode`` can be used to specify that a color mode conversion is to
- be done using these profiles, but the specified profiles must be able
- to handle that mode. I.e., if converting im from RGB to CMYK using
- profiles, the input profile must handle RGB data, and the output
- profile must handle CMYK data.
-
- :param im: An open :py:class:`~PIL.Image.Image` object (i.e. Image.new(...)
- or Image.open(...), etc.)
- :param inputProfile: String, as a valid filename path to the ICC input
- profile you wish to use for this image, or a profile object
- :param outputProfile: String, as a valid filename path to the ICC output
- profile you wish to use for this image, or a profile object
- :param renderingIntent: Integer (0-3) specifying the rendering intent you
- wish to use for the transform
-
- ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT)
- ImageCms.Intent.RELATIVE_COLORIMETRIC = 1
- ImageCms.Intent.SATURATION = 2
- ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3
-
- see the pyCMS documentation for details on rendering intents and what
- they do.
- :param outputMode: A valid PIL mode for the output image (i.e. "RGB",
- "CMYK", etc.). Note: if rendering the image "inPlace", outputMode
- MUST be the same mode as the input, or omitted completely. If
- omitted, the outputMode will be the same as the mode of the input
- image (im.mode)
- :param inPlace: Boolean. If ``True``, the original image is modified in-place,
- and ``None`` is returned. If ``False`` (default), a new
- :py:class:`~PIL.Image.Image` object is returned with the transform applied.
- :param flags: Integer (0-...) specifying additional flags
- :returns: Either None or a new :py:class:`~PIL.Image.Image` object, depending on
- the value of ``inPlace``
- :exception PyCMSError:
- """
-
- if outputMode is None:
- outputMode = im.mode
-
- if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <= 3):
- raise PyCMSError("renderingIntent must be an integer between 0 and 3")
-
- if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
- raise PyCMSError("flags must be an integer between 0 and %s" + _MAX_FLAG)
-
- try:
- if not isinstance(inputProfile, ImageCmsProfile):
- inputProfile = ImageCmsProfile(inputProfile)
- if not isinstance(outputProfile, ImageCmsProfile):
- outputProfile = ImageCmsProfile(outputProfile)
- transform = ImageCmsTransform(
- inputProfile,
- outputProfile,
- im.mode,
- outputMode,
- renderingIntent,
- flags=flags,
- )
- if inPlace:
- transform.apply_in_place(im)
- imOut = None
- else:
- imOut = transform.apply(im)
- except (OSError, TypeError, ValueError) as v:
- raise PyCMSError(v) from v
-
- return imOut
-
-
-def getOpenProfile(profileFilename):
- """
- (pyCMS) Opens an ICC profile file.
-
- The PyCMSProfile object can be passed back into pyCMS for use in creating
- transforms and such (as in ImageCms.buildTransformFromOpenProfiles()).
-
- If ``profileFilename`` is not a valid filename for an ICC profile,
- a :exc:`PyCMSError` will be raised.
-
- :param profileFilename: String, as a valid filename path to the ICC profile
- you wish to open, or a file-like object.
- :returns: A CmsProfile class object.
- :exception PyCMSError:
- """
-
- try:
- return ImageCmsProfile(profileFilename)
- except (OSError, TypeError, ValueError) as v:
- raise PyCMSError(v) from v
-
-
-def buildTransform(
- inputProfile,
- outputProfile,
- inMode,
- outMode,
- renderingIntent=Intent.PERCEPTUAL,
- flags=0,
-):
- """
- (pyCMS) Builds an ICC transform mapping from the ``inputProfile`` to the
- ``outputProfile``. Use applyTransform to apply the transform to a given
- image.
-
- If the input or output profiles specified are not valid filenames, a
- :exc:`PyCMSError` will be raised. If an error occurs during creation
- of the transform, a :exc:`PyCMSError` will be raised.
-
- If ``inMode`` or ``outMode`` are not a mode supported by the ``outputProfile``
- (or by pyCMS), a :exc:`PyCMSError` will be raised.
-
- This function builds and returns an ICC transform from the ``inputProfile``
- to the ``outputProfile`` using the ``renderingIntent`` to determine what to do
- with out-of-gamut colors. It will ONLY work for converting images that
- are in ``inMode`` to images that are in ``outMode`` color format (PIL mode,
- i.e. "RGB", "RGBA", "CMYK", etc.).
-
- Building the transform is a fair part of the overhead in
- ImageCms.profileToProfile(), so if you're planning on converting multiple
- images using the same input/output settings, this can save you time.
- Once you have a transform object, it can be used with
- ImageCms.applyProfile() to convert images without the need to re-compute
- the lookup table for the transform.
-
- The reason pyCMS returns a class object rather than a handle directly
- to the transform is that it needs to keep track of the PIL input/output
- modes that the transform is meant for. These attributes are stored in
- the ``inMode`` and ``outMode`` attributes of the object (which can be
- manually overridden if you really want to, but I don't know of any
- time that would be of use, or would even work).
-
- :param inputProfile: String, as a valid filename path to the ICC input
- profile you wish to use for this transform, or a profile object
- :param outputProfile: String, as a valid filename path to the ICC output
- profile you wish to use for this transform, or a profile object
- :param inMode: String, as a valid PIL mode that the appropriate profile
- also supports (i.e. "RGB", "RGBA", "CMYK", etc.)
- :param outMode: String, as a valid PIL mode that the appropriate profile
- also supports (i.e. "RGB", "RGBA", "CMYK", etc.)
- :param renderingIntent: Integer (0-3) specifying the rendering intent you
- wish to use for the transform
-
- ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT)
- ImageCms.Intent.RELATIVE_COLORIMETRIC = 1
- ImageCms.Intent.SATURATION = 2
- ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3
-
- see the pyCMS documentation for details on rendering intents and what
- they do.
- :param flags: Integer (0-...) specifying additional flags
- :returns: A CmsTransform class object.
- :exception PyCMSError:
- """
-
- if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <= 3):
- raise PyCMSError("renderingIntent must be an integer between 0 and 3")
-
- if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
- raise PyCMSError("flags must be an integer between 0 and %s" + _MAX_FLAG)
-
- try:
- if not isinstance(inputProfile, ImageCmsProfile):
- inputProfile = ImageCmsProfile(inputProfile)
- if not isinstance(outputProfile, ImageCmsProfile):
- outputProfile = ImageCmsProfile(outputProfile)
- return ImageCmsTransform(
- inputProfile, outputProfile, inMode, outMode, renderingIntent, flags=flags
- )
- except (OSError, TypeError, ValueError) as v:
- raise PyCMSError(v) from v
-
-
-def buildProofTransform(
- inputProfile,
- outputProfile,
- proofProfile,
- inMode,
- outMode,
- renderingIntent=Intent.PERCEPTUAL,
- proofRenderingIntent=Intent.ABSOLUTE_COLORIMETRIC,
- flags=FLAGS["SOFTPROOFING"],
-):
- """
- (pyCMS) Builds an ICC transform mapping from the ``inputProfile`` to the
- ``outputProfile``, but tries to simulate the result that would be
- obtained on the ``proofProfile`` device.
-
- If the input, output, or proof profiles specified are not valid
- filenames, a :exc:`PyCMSError` will be raised.
-
- If an error occurs during creation of the transform,
- a :exc:`PyCMSError` will be raised.
-
- If ``inMode`` or ``outMode`` are not a mode supported by the ``outputProfile``
- (or by pyCMS), a :exc:`PyCMSError` will be raised.
-
- This function builds and returns an ICC transform from the ``inputProfile``
- to the ``outputProfile``, but tries to simulate the result that would be
- obtained on the ``proofProfile`` device using ``renderingIntent`` and
- ``proofRenderingIntent`` to determine what to do with out-of-gamut
- colors. This is known as "soft-proofing". It will ONLY work for
- converting images that are in ``inMode`` to images that are in outMode
- color format (PIL mode, i.e. "RGB", "RGBA", "CMYK", etc.).
-
- Usage of the resulting transform object is exactly the same as with
- ImageCms.buildTransform().
-
- Proof profiling is generally used when using an output device to get a
- good idea of what the final printed/displayed image would look like on
- the ``proofProfile`` device when it's quicker and easier to use the
- output device for judging color. Generally, this means that the
- output device is a monitor, or a dye-sub printer (etc.), and the simulated
- device is something more expensive, complicated, or time consuming
- (making it difficult to make a real print for color judgement purposes).
-
- Soft-proofing basically functions by adjusting the colors on the
- output device to match the colors of the device being simulated. However,
- when the simulated device has a much wider gamut than the output
- device, you may obtain marginal results.
-
- :param inputProfile: String, as a valid filename path to the ICC input
- profile you wish to use for this transform, or a profile object
- :param outputProfile: String, as a valid filename path to the ICC output
- (monitor, usually) profile you wish to use for this transform, or a
- profile object
- :param proofProfile: String, as a valid filename path to the ICC proof
- profile you wish to use for this transform, or a profile object
- :param inMode: String, as a valid PIL mode that the appropriate profile
- also supports (i.e. "RGB", "RGBA", "CMYK", etc.)
- :param outMode: String, as a valid PIL mode that the appropriate profile
- also supports (i.e. "RGB", "RGBA", "CMYK", etc.)
- :param renderingIntent: Integer (0-3) specifying the rendering intent you
- wish to use for the input->proof (simulated) transform
-
- ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT)
- ImageCms.Intent.RELATIVE_COLORIMETRIC = 1
- ImageCms.Intent.SATURATION = 2
- ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3
-
- see the pyCMS documentation for details on rendering intents and what
- they do.
- :param proofRenderingIntent: Integer (0-3) specifying the rendering intent
- you wish to use for proof->output transform
-
- ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT)
- ImageCms.Intent.RELATIVE_COLORIMETRIC = 1
- ImageCms.Intent.SATURATION = 2
- ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3
-
- see the pyCMS documentation for details on rendering intents and what
- they do.
- :param flags: Integer (0-...) specifying additional flags
- :returns: A CmsTransform class object.
- :exception PyCMSError:
- """
-
- if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <= 3):
- raise PyCMSError("renderingIntent must be an integer between 0 and 3")
-
- if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
- raise PyCMSError("flags must be an integer between 0 and %s" + _MAX_FLAG)
-
- try:
- if not isinstance(inputProfile, ImageCmsProfile):
- inputProfile = ImageCmsProfile(inputProfile)
- if not isinstance(outputProfile, ImageCmsProfile):
- outputProfile = ImageCmsProfile(outputProfile)
- if not isinstance(proofProfile, ImageCmsProfile):
- proofProfile = ImageCmsProfile(proofProfile)
- return ImageCmsTransform(
- inputProfile,
- outputProfile,
- inMode,
- outMode,
- renderingIntent,
- proofProfile,
- proofRenderingIntent,
- flags,
- )
- except (OSError, TypeError, ValueError) as v:
- raise PyCMSError(v) from v
-
-
-buildTransformFromOpenProfiles = buildTransform
-buildProofTransformFromOpenProfiles = buildProofTransform
-
-
-def applyTransform(im, transform, inPlace=False):
- """
- (pyCMS) Applies a transform to a given image.
-
- If ``im.mode != transform.inMode``, a :exc:`PyCMSError` is raised.
-
- If ``inPlace`` is ``True`` and ``transform.inMode != transform.outMode``, a
- :exc:`PyCMSError` is raised.
-
- If ``im.mode``, ``transform.inMode`` or ``transform.outMode`` is not
- supported by pyCMSdll or the profiles you used for the transform, a
- :exc:`PyCMSError` is raised.
-
- If an error occurs while the transform is being applied,
- a :exc:`PyCMSError` is raised.
-
- This function applies a pre-calculated transform (from
- ImageCms.buildTransform() or ImageCms.buildTransformFromOpenProfiles())
- to an image. The transform can be used for multiple images, saving
- considerable calculation time if doing the same conversion multiple times.
-
- If you want to modify im in-place instead of receiving a new image as
- the return value, set ``inPlace`` to ``True``. This can only be done if
- ``transform.inMode`` and ``transform.outMode`` are the same, because we can't
- change the mode in-place (the buffer sizes for some modes are
- different). The default behavior is to return a new :py:class:`~PIL.Image.Image`
- object of the same dimensions in mode ``transform.outMode``.
-
- :param im: An :py:class:`~PIL.Image.Image` object, and im.mode must be the same
- as the ``inMode`` supported by the transform.
- :param transform: A valid CmsTransform class object
- :param inPlace: Bool. If ``True``, ``im`` is modified in place and ``None`` is
- returned, if ``False``, a new :py:class:`~PIL.Image.Image` object with the
- transform applied is returned (and ``im`` is not changed). The default is
- ``False``.
- :returns: Either ``None``, or a new :py:class:`~PIL.Image.Image` object,
- depending on the value of ``inPlace``. The profile will be returned in
- the image's ``info['icc_profile']``.
- :exception PyCMSError:
- """
-
- try:
- if inPlace:
- transform.apply_in_place(im)
- imOut = None
- else:
- imOut = transform.apply(im)
- except (TypeError, ValueError) as v:
- raise PyCMSError(v) from v
-
- return imOut
-
-
-def createProfile(colorSpace, colorTemp=-1):
- """
- (pyCMS) Creates a profile.
-
- If colorSpace not in ``["LAB", "XYZ", "sRGB"]``,
- a :exc:`PyCMSError` is raised.
-
- If using LAB and ``colorTemp`` is not a positive integer,
- a :exc:`PyCMSError` is raised.
-
- If an error occurs while creating the profile,
- a :exc:`PyCMSError` is raised.
-
- Use this function to create common profiles on-the-fly instead of
- having to supply a profile on disk and knowing the path to it. It
- returns a normal CmsProfile object that can be passed to
- ImageCms.buildTransformFromOpenProfiles() to create a transform to apply
- to images.
-
- :param colorSpace: String, the color space of the profile you wish to
- create.
- Currently only "LAB", "XYZ", and "sRGB" are supported.
- :param colorTemp: Positive integer for the white point for the profile, in
- degrees Kelvin (i.e. 5000, 6500, 9600, etc.). The default is for D50
- illuminant if omitted (5000k). colorTemp is ONLY applied to LAB
- profiles, and is ignored for XYZ and sRGB.
- :returns: A CmsProfile class object
- :exception PyCMSError:
- """
-
- if colorSpace not in ["LAB", "XYZ", "sRGB"]:
- raise PyCMSError(
- f"Color space not supported for on-the-fly profile creation ({colorSpace})"
- )
-
- if colorSpace == "LAB":
- try:
- colorTemp = float(colorTemp)
- except (TypeError, ValueError) as e:
- raise PyCMSError(
- f'Color temperature must be numeric, "{colorTemp}" not valid'
- ) from e
-
- try:
- return core.createProfile(colorSpace, colorTemp)
- except (TypeError, ValueError) as v:
- raise PyCMSError(v) from v
-
-
-def getProfileName(profile):
- """
-
- (pyCMS) Gets the internal product name for the given profile.
-
- If ``profile`` isn't a valid CmsProfile object or filename to a profile,
- a :exc:`PyCMSError` is raised If an error occurs while trying
- to obtain the name tag, a :exc:`PyCMSError` is raised.
-
- Use this function to obtain the INTERNAL name of the profile (stored
- in an ICC tag in the profile itself), usually the one used when the
- profile was originally created. Sometimes this tag also contains
- additional information supplied by the creator.
-
- :param profile: EITHER a valid CmsProfile object, OR a string of the
- filename of an ICC profile.
- :returns: A string containing the internal name of the profile as stored
- in an ICC tag.
- :exception PyCMSError:
- """
-
- try:
- # add an extra newline to preserve pyCMS compatibility
- if not isinstance(profile, ImageCmsProfile):
- profile = ImageCmsProfile(profile)
- # do it in python, not c.
- # // name was "%s - %s" (model, manufacturer) || Description ,
- # // but if the Model and Manufacturer were the same or the model
- # // was long, Just the model, in 1.x
- model = profile.profile.model
- manufacturer = profile.profile.manufacturer
-
- if not (model or manufacturer):
- return (profile.profile.profile_description or "") + "\n"
- if not manufacturer or len(model) > 30:
- return model + "\n"
- return f"{model} - {manufacturer}\n"
-
- except (AttributeError, OSError, TypeError, ValueError) as v:
- raise PyCMSError(v) from v
-
-
-def getProfileInfo(profile):
- """
- (pyCMS) Gets the internal product information for the given profile.
-
- If ``profile`` isn't a valid CmsProfile object or filename to a profile,
- a :exc:`PyCMSError` is raised.
-
- If an error occurs while trying to obtain the info tag,
- a :exc:`PyCMSError` is raised.
-
- Use this function to obtain the information stored in the profile's
- info tag. This often contains details about the profile, and how it
- was created, as supplied by the creator.
-
- :param profile: EITHER a valid CmsProfile object, OR a string of the
- filename of an ICC profile.
- :returns: A string containing the internal profile information stored in
- an ICC tag.
- :exception PyCMSError:
- """
-
- try:
- if not isinstance(profile, ImageCmsProfile):
- profile = ImageCmsProfile(profile)
- # add an extra newline to preserve pyCMS compatibility
- # Python, not C. the white point bits weren't working well,
- # so skipping.
- # info was description \r\n\r\n copyright \r\n\r\n K007 tag \r\n\r\n whitepoint
- description = profile.profile.profile_description
- cpright = profile.profile.copyright
- arr = []
- for elt in (description, cpright):
- if elt:
- arr.append(elt)
- return "\r\n\r\n".join(arr) + "\r\n\r\n"
-
- except (AttributeError, OSError, TypeError, ValueError) as v:
- raise PyCMSError(v) from v
-
-
-def getProfileCopyright(profile):
- """
- (pyCMS) Gets the copyright for the given profile.
-
- If ``profile`` isn't a valid CmsProfile object or filename to a profile, a
- :exc:`PyCMSError` is raised.
-
- If an error occurs while trying to obtain the copyright tag,
- a :exc:`PyCMSError` is raised.
-
- Use this function to obtain the information stored in the profile's
- copyright tag.
-
- :param profile: EITHER a valid CmsProfile object, OR a string of the
- filename of an ICC profile.
- :returns: A string containing the internal profile information stored in
- an ICC tag.
- :exception PyCMSError:
- """
- try:
- # add an extra newline to preserve pyCMS compatibility
- if not isinstance(profile, ImageCmsProfile):
- profile = ImageCmsProfile(profile)
- return (profile.profile.copyright or "") + "\n"
- except (AttributeError, OSError, TypeError, ValueError) as v:
- raise PyCMSError(v) from v
-
-
-def getProfileManufacturer(profile):
- """
- (pyCMS) Gets the manufacturer for the given profile.
-
- If ``profile`` isn't a valid CmsProfile object or filename to a profile, a
- :exc:`PyCMSError` is raised.
-
- If an error occurs while trying to obtain the manufacturer tag, a
- :exc:`PyCMSError` is raised.
-
- Use this function to obtain the information stored in the profile's
- manufacturer tag.
-
- :param profile: EITHER a valid CmsProfile object, OR a string of the
- filename of an ICC profile.
- :returns: A string containing the internal profile information stored in
- an ICC tag.
- :exception PyCMSError:
- """
- try:
- # add an extra newline to preserve pyCMS compatibility
- if not isinstance(profile, ImageCmsProfile):
- profile = ImageCmsProfile(profile)
- return (profile.profile.manufacturer or "") + "\n"
- except (AttributeError, OSError, TypeError, ValueError) as v:
- raise PyCMSError(v) from v
-
-
-def getProfileModel(profile):
- """
- (pyCMS) Gets the model for the given profile.
-
- If ``profile`` isn't a valid CmsProfile object or filename to a profile, a
- :exc:`PyCMSError` is raised.
-
- If an error occurs while trying to obtain the model tag,
- a :exc:`PyCMSError` is raised.
-
- Use this function to obtain the information stored in the profile's
- model tag.
-
- :param profile: EITHER a valid CmsProfile object, OR a string of the
- filename of an ICC profile.
- :returns: A string containing the internal profile information stored in
- an ICC tag.
- :exception PyCMSError:
- """
-
- try:
- # add an extra newline to preserve pyCMS compatibility
- if not isinstance(profile, ImageCmsProfile):
- profile = ImageCmsProfile(profile)
- return (profile.profile.model or "") + "\n"
- except (AttributeError, OSError, TypeError, ValueError) as v:
- raise PyCMSError(v) from v
-
-
-def getProfileDescription(profile):
- """
- (pyCMS) Gets the description for the given profile.
-
- If ``profile`` isn't a valid CmsProfile object or filename to a profile, a
- :exc:`PyCMSError` is raised.
-
- If an error occurs while trying to obtain the description tag,
- a :exc:`PyCMSError` is raised.
-
- Use this function to obtain the information stored in the profile's
- description tag.
-
- :param profile: EITHER a valid CmsProfile object, OR a string of the
- filename of an ICC profile.
- :returns: A string containing the internal profile information stored in an
- ICC tag.
- :exception PyCMSError:
- """
-
- try:
- # add an extra newline to preserve pyCMS compatibility
- if not isinstance(profile, ImageCmsProfile):
- profile = ImageCmsProfile(profile)
- return (profile.profile.profile_description or "") + "\n"
- except (AttributeError, OSError, TypeError, ValueError) as v:
- raise PyCMSError(v) from v
-
-
-def getDefaultIntent(profile):
- """
- (pyCMS) Gets the default intent name for the given profile.
-
- If ``profile`` isn't a valid CmsProfile object or filename to a profile, a
- :exc:`PyCMSError` is raised.
-
- If an error occurs while trying to obtain the default intent, a
- :exc:`PyCMSError` is raised.
-
- Use this function to determine the default (and usually best optimized)
- rendering intent for this profile. Most profiles support multiple
- rendering intents, but are intended mostly for one type of conversion.
- If you wish to use a different intent than returned, use
- ImageCms.isIntentSupported() to verify it will work first.
-
- :param profile: EITHER a valid CmsProfile object, OR a string of the
- filename of an ICC profile.
- :returns: Integer 0-3 specifying the default rendering intent for this
- profile.
-
- ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT)
- ImageCms.Intent.RELATIVE_COLORIMETRIC = 1
- ImageCms.Intent.SATURATION = 2
- ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3
-
- see the pyCMS documentation for details on rendering intents and what
- they do.
- :exception PyCMSError:
- """
-
- try:
- if not isinstance(profile, ImageCmsProfile):
- profile = ImageCmsProfile(profile)
- return profile.profile.rendering_intent
- except (AttributeError, OSError, TypeError, ValueError) as v:
- raise PyCMSError(v) from v
-
-
-def isIntentSupported(profile, intent, direction):
- """
- (pyCMS) Checks if a given intent is supported.
-
- Use this function to verify that you can use your desired
- ``intent`` with ``profile``, and that ``profile`` can be used for the
- input/output/proof profile as you desire.
-
- Some profiles are created specifically for one "direction", can cannot
- be used for others. Some profiles can only be used for certain
- rendering intents, so it's best to either verify this before trying
- to create a transform with them (using this function), or catch the
- potential :exc:`PyCMSError` that will occur if they don't
- support the modes you select.
-
- :param profile: EITHER a valid CmsProfile object, OR a string of the
- filename of an ICC profile.
- :param intent: Integer (0-3) specifying the rendering intent you wish to
- use with this profile
-
- ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT)
- ImageCms.Intent.RELATIVE_COLORIMETRIC = 1
- ImageCms.Intent.SATURATION = 2
- ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3
-
- see the pyCMS documentation for details on rendering intents and what
- they do.
- :param direction: Integer specifying if the profile is to be used for
- input, output, or proof
-
- INPUT = 0 (or use ImageCms.Direction.INPUT)
- OUTPUT = 1 (or use ImageCms.Direction.OUTPUT)
- PROOF = 2 (or use ImageCms.Direction.PROOF)
-
- :returns: 1 if the intent/direction are supported, -1 if they are not.
- :exception PyCMSError:
- """
-
- try:
- if not isinstance(profile, ImageCmsProfile):
- profile = ImageCmsProfile(profile)
- # FIXME: I get different results for the same data w. different
- # compilers. Bug in LittleCMS or in the binding?
- if profile.profile.is_intent_supported(intent, direction):
- return 1
- else:
- return -1
- except (AttributeError, OSError, TypeError, ValueError) as v:
- raise PyCMSError(v) from v
-
-
-def versions():
- """
- (pyCMS) Fetches versions.
- """
-
- return (VERSION, core.littlecms_version, sys.version.split()[0], Image.__version__)
diff --git a/venv/Lib/site-packages/PIL/ImageColor.py b/venv/Lib/site-packages/PIL/ImageColor.py
deleted file mode 100644
index 25f92f2..0000000
--- a/venv/Lib/site-packages/PIL/ImageColor.py
+++ /dev/null
@@ -1,302 +0,0 @@
-#
-# The Python Imaging Library
-# $Id$
-#
-# map CSS3-style colour description strings to RGB
-#
-# History:
-# 2002-10-24 fl Added support for CSS-style color strings
-# 2002-12-15 fl Added RGBA support
-# 2004-03-27 fl Fixed remaining int() problems for Python 1.5.2
-# 2004-07-19 fl Fixed gray/grey spelling issues
-# 2009-03-05 fl Fixed rounding error in grayscale calculation
-#
-# Copyright (c) 2002-2004 by Secret Labs AB
-# Copyright (c) 2002-2004 by Fredrik Lundh
-#
-# See the README file for information on usage and redistribution.
-#
-
-import re
-
-from . import Image
-
-
-def getrgb(color):
- """
- Convert a color string to an RGB or RGBA tuple. If the string cannot be
- parsed, this function raises a :py:exc:`ValueError` exception.
-
- .. versionadded:: 1.1.4
-
- :param color: A color string
- :return: ``(red, green, blue[, alpha])``
- """
- if len(color) > 100:
- raise ValueError("color specifier is too long")
- color = color.lower()
-
- rgb = colormap.get(color, None)
- if rgb:
- if isinstance(rgb, tuple):
- return rgb
- colormap[color] = rgb = getrgb(rgb)
- return rgb
-
- # check for known string formats
- if re.match("#[a-f0-9]{3}$", color):
- return (int(color[1] * 2, 16), int(color[2] * 2, 16), int(color[3] * 2, 16))
-
- if re.match("#[a-f0-9]{4}$", color):
- return (
- int(color[1] * 2, 16),
- int(color[2] * 2, 16),
- int(color[3] * 2, 16),
- int(color[4] * 2, 16),
- )
-
- if re.match("#[a-f0-9]{6}$", color):
- return (int(color[1:3], 16), int(color[3:5], 16), int(color[5:7], 16))
-
- if re.match("#[a-f0-9]{8}$", color):
- return (
- int(color[1:3], 16),
- int(color[3:5], 16),
- int(color[5:7], 16),
- int(color[7:9], 16),
- )
-
- m = re.match(r"rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$", color)
- if m:
- return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
-
- m = re.match(r"rgb\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)$", color)
- if m:
- return (
- int((int(m.group(1)) * 255) / 100.0 + 0.5),
- int((int(m.group(2)) * 255) / 100.0 + 0.5),
- int((int(m.group(3)) * 255) / 100.0 + 0.5),
- )
-
- m = re.match(
- r"hsl\(\s*(\d+\.?\d*)\s*,\s*(\d+\.?\d*)%\s*,\s*(\d+\.?\d*)%\s*\)$", color
- )
- if m:
- from colorsys import hls_to_rgb
-
- rgb = hls_to_rgb(
- float(m.group(1)) / 360.0,
- float(m.group(3)) / 100.0,
- float(m.group(2)) / 100.0,
- )
- return (
- int(rgb[0] * 255 + 0.5),
- int(rgb[1] * 255 + 0.5),
- int(rgb[2] * 255 + 0.5),
- )
-
- m = re.match(
- r"hs[bv]\(\s*(\d+\.?\d*)\s*,\s*(\d+\.?\d*)%\s*,\s*(\d+\.?\d*)%\s*\)$", color
- )
- if m:
- from colorsys import hsv_to_rgb
-
- rgb = hsv_to_rgb(
- float(m.group(1)) / 360.0,
- float(m.group(2)) / 100.0,
- float(m.group(3)) / 100.0,
- )
- return (
- int(rgb[0] * 255 + 0.5),
- int(rgb[1] * 255 + 0.5),
- int(rgb[2] * 255 + 0.5),
- )
-
- m = re.match(r"rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$", color)
- if m:
- return (int(m.group(1)), int(m.group(2)), int(m.group(3)), int(m.group(4)))
- raise ValueError(f"unknown color specifier: {repr(color)}")
-
-
-def getcolor(color, mode):
- """
- Same as :py:func:`~PIL.ImageColor.getrgb`, but converts the RGB value to a
- greyscale value if the mode is not color or a palette image. If the string
- cannot be parsed, this function raises a :py:exc:`ValueError` exception.
-
- .. versionadded:: 1.1.4
-
- :param color: A color string
- :return: ``(graylevel [, alpha]) or (red, green, blue[, alpha])``
- """
- # same as getrgb, but converts the result to the given mode
- color, alpha = getrgb(color), 255
- if len(color) == 4:
- color, alpha = color[0:3], color[3]
-
- if Image.getmodebase(mode) == "L":
- r, g, b = color
- # ITU-R Recommendation 601-2 for nonlinear RGB
- # scaled to 24 bits to match the convert's implementation.
- color = (r * 19595 + g * 38470 + b * 7471 + 0x8000) >> 16
- if mode[-1] == "A":
- return (color, alpha)
- else:
- if mode[-1] == "A":
- return color + (alpha,)
- return color
-
-
-colormap = {
- # X11 colour table from https://drafts.csswg.org/css-color-4/, with
- # gray/grey spelling issues fixed. This is a superset of HTML 4.0
- # colour names used in CSS 1.
- "aliceblue": "#f0f8ff",
- "antiquewhite": "#faebd7",
- "aqua": "#00ffff",
- "aquamarine": "#7fffd4",
- "azure": "#f0ffff",
- "beige": "#f5f5dc",
- "bisque": "#ffe4c4",
- "black": "#000000",
- "blanchedalmond": "#ffebcd",
- "blue": "#0000ff",
- "blueviolet": "#8a2be2",
- "brown": "#a52a2a",
- "burlywood": "#deb887",
- "cadetblue": "#5f9ea0",
- "chartreuse": "#7fff00",
- "chocolate": "#d2691e",
- "coral": "#ff7f50",
- "cornflowerblue": "#6495ed",
- "cornsilk": "#fff8dc",
- "crimson": "#dc143c",
- "cyan": "#00ffff",
- "darkblue": "#00008b",
- "darkcyan": "#008b8b",
- "darkgoldenrod": "#b8860b",
- "darkgray": "#a9a9a9",
- "darkgrey": "#a9a9a9",
- "darkgreen": "#006400",
- "darkkhaki": "#bdb76b",
- "darkmagenta": "#8b008b",
- "darkolivegreen": "#556b2f",
- "darkorange": "#ff8c00",
- "darkorchid": "#9932cc",
- "darkred": "#8b0000",
- "darksalmon": "#e9967a",
- "darkseagreen": "#8fbc8f",
- "darkslateblue": "#483d8b",
- "darkslategray": "#2f4f4f",
- "darkslategrey": "#2f4f4f",
- "darkturquoise": "#00ced1",
- "darkviolet": "#9400d3",
- "deeppink": "#ff1493",
- "deepskyblue": "#00bfff",
- "dimgray": "#696969",
- "dimgrey": "#696969",
- "dodgerblue": "#1e90ff",
- "firebrick": "#b22222",
- "floralwhite": "#fffaf0",
- "forestgreen": "#228b22",
- "fuchsia": "#ff00ff",
- "gainsboro": "#dcdcdc",
- "ghostwhite": "#f8f8ff",
- "gold": "#ffd700",
- "goldenrod": "#daa520",
- "gray": "#808080",
- "grey": "#808080",
- "green": "#008000",
- "greenyellow": "#adff2f",
- "honeydew": "#f0fff0",
- "hotpink": "#ff69b4",
- "indianred": "#cd5c5c",
- "indigo": "#4b0082",
- "ivory": "#fffff0",
- "khaki": "#f0e68c",
- "lavender": "#e6e6fa",
- "lavenderblush": "#fff0f5",
- "lawngreen": "#7cfc00",
- "lemonchiffon": "#fffacd",
- "lightblue": "#add8e6",
- "lightcoral": "#f08080",
- "lightcyan": "#e0ffff",
- "lightgoldenrodyellow": "#fafad2",
- "lightgreen": "#90ee90",
- "lightgray": "#d3d3d3",
- "lightgrey": "#d3d3d3",
- "lightpink": "#ffb6c1",
- "lightsalmon": "#ffa07a",
- "lightseagreen": "#20b2aa",
- "lightskyblue": "#87cefa",
- "lightslategray": "#778899",
- "lightslategrey": "#778899",
- "lightsteelblue": "#b0c4de",
- "lightyellow": "#ffffe0",
- "lime": "#00ff00",
- "limegreen": "#32cd32",
- "linen": "#faf0e6",
- "magenta": "#ff00ff",
- "maroon": "#800000",
- "mediumaquamarine": "#66cdaa",
- "mediumblue": "#0000cd",
- "mediumorchid": "#ba55d3",
- "mediumpurple": "#9370db",
- "mediumseagreen": "#3cb371",
- "mediumslateblue": "#7b68ee",
- "mediumspringgreen": "#00fa9a",
- "mediumturquoise": "#48d1cc",
- "mediumvioletred": "#c71585",
- "midnightblue": "#191970",
- "mintcream": "#f5fffa",
- "mistyrose": "#ffe4e1",
- "moccasin": "#ffe4b5",
- "navajowhite": "#ffdead",
- "navy": "#000080",
- "oldlace": "#fdf5e6",
- "olive": "#808000",
- "olivedrab": "#6b8e23",
- "orange": "#ffa500",
- "orangered": "#ff4500",
- "orchid": "#da70d6",
- "palegoldenrod": "#eee8aa",
- "palegreen": "#98fb98",
- "paleturquoise": "#afeeee",
- "palevioletred": "#db7093",
- "papayawhip": "#ffefd5",
- "peachpuff": "#ffdab9",
- "peru": "#cd853f",
- "pink": "#ffc0cb",
- "plum": "#dda0dd",
- "powderblue": "#b0e0e6",
- "purple": "#800080",
- "rebeccapurple": "#663399",
- "red": "#ff0000",
- "rosybrown": "#bc8f8f",
- "royalblue": "#4169e1",
- "saddlebrown": "#8b4513",
- "salmon": "#fa8072",
- "sandybrown": "#f4a460",
- "seagreen": "#2e8b57",
- "seashell": "#fff5ee",
- "sienna": "#a0522d",
- "silver": "#c0c0c0",
- "skyblue": "#87ceeb",
- "slateblue": "#6a5acd",
- "slategray": "#708090",
- "slategrey": "#708090",
- "snow": "#fffafa",
- "springgreen": "#00ff7f",
- "steelblue": "#4682b4",
- "tan": "#d2b48c",
- "teal": "#008080",
- "thistle": "#d8bfd8",
- "tomato": "#ff6347",
- "turquoise": "#40e0d0",
- "violet": "#ee82ee",
- "wheat": "#f5deb3",
- "white": "#ffffff",
- "whitesmoke": "#f5f5f5",
- "yellow": "#ffff00",
- "yellowgreen": "#9acd32",
-}
diff --git a/venv/Lib/site-packages/PIL/ImageDraw.py b/venv/Lib/site-packages/PIL/ImageDraw.py
deleted file mode 100644
index 610ccd4..0000000
--- a/venv/Lib/site-packages/PIL/ImageDraw.py
+++ /dev/null
@@ -1,1004 +0,0 @@
-#
-# The Python Imaging Library
-# $Id$
-#
-# drawing interface operations
-#
-# History:
-# 1996-04-13 fl Created (experimental)
-# 1996-08-07 fl Filled polygons, ellipses.
-# 1996-08-13 fl Added text support
-# 1998-06-28 fl Handle I and F images
-# 1998-12-29 fl Added arc; use arc primitive to draw ellipses
-# 1999-01-10 fl Added shape stuff (experimental)
-# 1999-02-06 fl Added bitmap support
-# 1999-02-11 fl Changed all primitives to take options
-# 1999-02-20 fl Fixed backwards compatibility
-# 2000-10-12 fl Copy on write, when necessary
-# 2001-02-18 fl Use default ink for bitmap/text also in fill mode
-# 2002-10-24 fl Added support for CSS-style color strings
-# 2002-12-10 fl Added experimental support for RGBA-on-RGB drawing
-# 2002-12-11 fl Refactored low-level drawing API (work in progress)
-# 2004-08-26 fl Made Draw() a factory function, added getdraw() support
-# 2004-09-04 fl Added width support to line primitive
-# 2004-09-10 fl Added font mode handling
-# 2006-06-19 fl Added font bearing support (getmask2)
-#
-# Copyright (c) 1997-2006 by Secret Labs AB
-# Copyright (c) 1996-2006 by Fredrik Lundh
-#
-# See the README file for information on usage and redistribution.
-#
-
-import math
-import numbers
-
-from . import Image, ImageColor, ImageFont
-
-"""
-A simple 2D drawing interface for PIL images.
-
-
-# Pillow
-
-## Python Imaging Library (Fork)
-
-Pillow is the friendly PIL fork by [Alex Clark and
-Contributors](https://github.com/python-pillow/Pillow/graphs/contributors).
-PIL is the Python Imaging Library by Fredrik Lundh and Contributors.
-As of 2019, Pillow development is
-[supported by Tidelift](https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=readme&utm_campaign=enterprise).
-
-
-
-
docs
-
-
-
-
-
-
tests
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
package
-
-
-
-
-
-
-
-
-
social
-
-
-
-
-
-
-
-## Overview
-
-The Python Imaging Library adds image processing capabilities to your Python interpreter.
-
-This library provides extensive file format support, an efficient internal representation, and fairly powerful image processing capabilities.
-
-The core image library is designed for fast access to data stored in a few basic pixel formats. It should provide a solid foundation for a general image processing tool.
-
-## More Information
-
-- [Documentation](https://pillow.readthedocs.io/)
- - [Installation](https://pillow.readthedocs.io/en/latest/installation.html)
- - [Handbook](https://pillow.readthedocs.io/en/latest/handbook/index.html)
-- [Contribute](https://github.com/python-pillow/Pillow/blob/main/.github/CONTRIBUTING.md)
- - [Issues](https://github.com/python-pillow/Pillow/issues)
- - [Pull requests](https://github.com/python-pillow/Pillow/pulls)
-- [Release notes](https://pillow.readthedocs.io/en/stable/releasenotes/index.html)
-- [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)
- - [Pre-fork](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst#pre-fork)
-
-## Report a Vulnerability
-
-To report a security vulnerability, please follow the procedure described in the [Tidelift security policy](https://tidelift.com/docs/security).
-
-
diff --git a/venv/Lib/site-packages/Pillow-9.1.0.dist-info/RECORD b/venv/Lib/site-packages/Pillow-9.1.0.dist-info/RECORD
deleted file mode 100644
index 93f9a74..0000000
--- a/venv/Lib/site-packages/Pillow-9.1.0.dist-info/RECORD
+++ /dev/null
@@ -1,210 +0,0 @@
-PIL/BdfFontFile.py,sha256=hRnSgFZOIiTgWfJIaRHRQpU4TKVok2E31KJY6sbZPwc,2817
-PIL/BlpImagePlugin.py,sha256=SVs3I88sIWw7ibWRnAzDd9T-dIrWD6x00ZAf2HgNjh8,16143
-PIL/BmpImagePlugin.py,sha256=d9hGPxD0wjT_qhchHtiigxYLlFAGG61WcdUqEHqleTk,16252
-PIL/BufrStubImagePlugin.py,sha256=DE_t_ch4-YH_oimXYNMCefin4kcru6Uc2H_OTmwR6y4,1518
-PIL/ContainerIO.py,sha256=1U15zUXjWO8uWK-MyCp66Eh7djQEU-oUeCDoBqewNkA,2883
-PIL/CurImagePlugin.py,sha256=er_bI3V1Ezly0QfFJq0fZMlGwrD5izDutwF1FrOwiMA,1679
-PIL/DcxImagePlugin.py,sha256=bfESLTji9GerqI4oYsy5oTFyRMlr2mjSsXzpY9IuLsk,2145
-PIL/DdsImagePlugin.py,sha256=-sz60zvpuz89nyUobCPhdf-KWaT1yyeEa5PbRlxLMOw,8071
-PIL/EpsImagePlugin.py,sha256=qUxbQVsnzRyveDs9b8Co98sd0klsKr5GLCWtY3xbmB8,11949
-PIL/ExifTags.py,sha256=0YRoKyMwPabWOZZgVeLL6mlaGjbZgfF-z8WuUc6Ibb0,9446
-PIL/FitsImagePlugin.py,sha256=15BrvLXsw0F8WjBbP6-1RPHbJ4Lbd39OP4wkikcstC0,1971
-PIL/FitsStubImagePlugin.py,sha256=ETXbjvAFVMPfm51RNvPuo2B_pNRrJVuNNX-7-RmLUqw,1718
-PIL/FliImagePlugin.py,sha256=fR-Z9uY1udQu6FvzSqZJ3DAmIAaJUKsCNbO7OHN39cY,4239
-PIL/FontFile.py,sha256=LkQcbwUu1C4fokMnbg-ao9ksp2RX-saaPRie-z2rpH4,2765
-PIL/FpxImagePlugin.py,sha256=nKGioxa5C0q9X9qva3t_htRV_3jXQcFkclVxTEaSusk,6658
-PIL/FtexImagePlugin.py,sha256=TkvwTKeFRd1Qhcg6GyTBuFPI118MnegxC-JUoJMQVyY,4175
-PIL/GbrImagePlugin.py,sha256=K-olSg1M2bF2IofUeLABXfI1JLdrWsgiiU6yUTPhSWM,2795
-PIL/GdImageFile.py,sha256=JFWSUssG1z1r884GQtBbZ3T7uhPF4cDXSuW3ctgf3TU,2465
-PIL/GifImagePlugin.py,sha256=xA8QdF_rPNvr7Ceegl0I58FxsTxYaxuM4xpk9JoSZ3k,34458
-PIL/GimpGradientFile.py,sha256=G0ClRmjRHIJoU0nmG-P-tgehLHZip5i0rY4-5pjJ7bc,3353
-PIL/GimpPaletteFile.py,sha256=MGpf0WF_yTtMAXWvO_wlurgv_y80SX66EXprl6UIunM,1274
-PIL/GribStubImagePlugin.py,sha256=CocpZJIN8ckBtMQbq1VMA7NEKW5gwlzQ_mRZhHoyZho,1513
-PIL/Hdf5StubImagePlugin.py,sha256=FJ7-Vz1KY-DEOfrZg3cCMmG_wTa_qf6p41991P2Wfks,1515
-PIL/IcnsImagePlugin.py,sha256=x8JjanvXt_2BS-Qg8Jqt9XPsrCkhN2ESYGoKIoJ2WII,11755
-PIL/IcoImagePlugin.py,sha256=ZSfs8e9qJxIzcNUxuRC8S4KJgmvdH7KZOBuc70Ho9H0,11551
-PIL/ImImagePlugin.py,sha256=76DvUbRkFQ_DkEdthbApsuliNc5-FQHX3mnrYZdOkt4,10729
-PIL/Image.py,sha256=wQ34jHUxgvY6Udbz9u398kzpnEeZybBo5_ic8rQ4fZ0,125350
-PIL/ImageChops.py,sha256=HOGSnuU4EcCbdeUzEGPm54zewppHWWe12XLyOLLPgCw,7297
-PIL/ImageCms.py,sha256=MJHg18tKXzGIV0KZib3NQDIyaGI8XTJGIwzKoswfVbk,37951
-PIL/ImageColor.py,sha256=2e9xfO08S6afUzoahUIzyMN8RJcQsMz9E92rFnEhfP0,8727
-PIL/ImageDraw.py,sha256=rvMmVCjqAo_PRk41fOuOh3kkXYYTY8KinMvLkQ0RhO8,34710
-PIL/ImageDraw2.py,sha256=oBhpBTZhx3bd4D0s8E2kDjBzgThRkDU_TE_987l501k,5019
-PIL/ImageEnhance.py,sha256=CJnCouiBmxN2fE0xW7m_uMdBqcm-Fp0S3ruHhkygal4,3190
-PIL/ImageFile.py,sha256=_zfHA4FUsh2szsv0kd3LK1PywWNRH8krkvUVMi66DX8,22680
-PIL/ImageFilter.py,sha256=Sx99ij57imObeBdiR5w6cuhEG682SkfqtXx_vW7T_mk,16142
-PIL/ImageFont.py,sha256=TcCig_Hw5DbOnsZsWdLQeDEqplJotI2wG_Viw7P9W6o,45963
-PIL/ImageGrab.py,sha256=4W_qGYMJv7-5kWIvKnb3PzFMqdQERV32c43z-onj1CI,3823
-PIL/ImageMath.py,sha256=OsrEDBmoonjeOdcbuYQFEoU1sRT4sSCNO95EAq_CA_s,7253
-PIL/ImageMode.py,sha256=ZyTPlast0KeEp0-lbRcBoztKQzUY3FRaMAZza0Lm_mE,3006
-PIL/ImageMorph.py,sha256=KL2843wgfLyXPOWEJnTXRvySfbpRrlTqA_0M1j5xuD0,7773
-PIL/ImageOps.py,sha256=-MBNR_kztrdN6IAwTVXXHL2vvdO8ZkZjK-vMXVpmv5w,20504
-PIL/ImagePalette.py,sha256=rOpqcuH5DhJXPEvREna3Dg1N7ZK3TfnXHu5eZyltZTs,7841
-PIL/ImagePath.py,sha256=lVmH1-lCd0SyrFoqyhlstAFW2iJuC14fPcW8iewvxCQ,336
-PIL/ImageQt.py,sha256=hECe1rZpv1teaR5exrP39NbWBKwNGD7X5zoA5id_UJo,6698
-PIL/ImageSequence.py,sha256=3djA7vDH6wafTGbt4e_lPlVhy2TaKfdSrA1XQ4n-Uoc,1850
-PIL/ImageShow.py,sha256=Q_c_v9sy3wNnCnz7Ce1aM5vG1q74lFJ_ur6XvlNQXqc,12249
-PIL/ImageStat.py,sha256=Wdxu473_-bf3MeXLEj-9GrRftp6Ju_F7Sl_EKgzKd1Y,3899
-PIL/ImageTk.py,sha256=f6GGmApnpacVAHyOOVgG5PSLG6OCQInb5-2CSYfyTKg,9148
-PIL/ImageTransform.py,sha256=oO7Ir7j_5r4DeoZ-ZgqW9FO099cP2gHdE32SQdfmW_s,2883
-PIL/ImageWin.py,sha256=1MQBJS7tVrQzI9jN0nmeNeFpIaq8fXra9kQocHkiFxM,7191
-PIL/ImtImagePlugin.py,sha256=v_P09UT1Ae_HNUS-lTcMWfDTedfBDf-krhJRckDW6tg,2203
-PIL/IptcImagePlugin.py,sha256=-RZBUUodHcF5wLKanW1MxJj7cbLOpx5LvXqm0vDM22U,5714
-PIL/Jpeg2KImagePlugin.py,sha256=M8xsol1019D8hwtooNey-AGiNGaPPOqOat_0w4Tojaw,10455
-PIL/JpegImagePlugin.py,sha256=LRZGSeeoCbOyF3ISZp2VDYZGg5uL2JXLDf5AOCv3ghQ,28561
-PIL/JpegPresets.py,sha256=6nVnX_H8eA8ZO7AOVvkUx8gEN6QfI8zKnV6od16XgWE,12347
-PIL/McIdasImagePlugin.py,sha256=LrP5nA7l8IQG3WhlMI0Xs8fGXY_uf6IDmzNCERl3tGw,1754
-PIL/MicImagePlugin.py,sha256=Eh94vjTurXYkmm27hhooyNm9NkWWyVxP8Nq4thNLV6Y,2607
-PIL/MpegImagePlugin.py,sha256=n16Zgdy8Hcfke16lQwZWs53PZq4BA_OxPCMPDkW62nw,1803
-PIL/MpoImagePlugin.py,sha256=C-oosMx-C7dZT4QODBNYbX6LtfeEUxdpQ15Ychx9SuY,4478
-PIL/MspImagePlugin.py,sha256=ftTl14BpW1i3os_OUfusc7t4tRzBP4RrLxp76Sf9X4I,5527
-PIL/PSDraw.py,sha256=xmJ6GVUvDm1SC3QuUpYdeNfGu9lYBLX1ndCt96tObcc,6719
-PIL/PaletteFile.py,sha256=s3KtsDuY5S04MKDyiXK3iIbiOGzV9PvCDUpOQHI7yqc,1106
-PIL/PalmImagePlugin.py,sha256=lTVwwSPFrQ-IPFGU8_gRCMZ1Lb73cuVhQ-nkx1Q0oqc,9108
-PIL/PcdImagePlugin.py,sha256=cnBm_xKcpLGT6hZ8QKai9Up0gZERMxZwhDXl1hQtBm0,1476
-PIL/PcfFontFile.py,sha256=njhgblsjSVcITVz1DpWdEligmJgPMh5nTk_zDDWWTik,6348
-PIL/PcxImagePlugin.py,sha256=J-Pm2QBt5Hi4ObPeXDnc87X7nl1hbtTGqy4sTov6tug,5864
-PIL/PdfImagePlugin.py,sha256=f3foSWC1anwbnVBXVi-4wmtEnOR4_dbmqrbiQ--48Bk,7311
-PIL/PdfParser.py,sha256=Kxq4ZLMoayNODnpURMIcXljGJS-rX8AMBKA5iA0O29M,34561
-PIL/PixarImagePlugin.py,sha256=5MMcrrShVr511QKevK1ziKyJn0WllokWQxBhs8NWttY,1631
-PIL/PngImagePlugin.py,sha256=377uheEGeWvhlmTda0wRsQdVqAmOuboJWUMkHCl3Fs4,45016
-PIL/PpmImagePlugin.py,sha256=FclF4DGFyqWmqCOexRpzX47YuoylGNnVK1_VffYrP_s,5850
-PIL/PsdImagePlugin.py,sha256=8pYj9Sc4FYHl997QnJ6-79rAcS1flv7mIAMVR4_o1ws,7572
-PIL/PyAccess.py,sha256=SaGs2ZE4kjh-dybpAA5_Og4wuhA6d0LTPKK8t2aHffY,9607
-PIL/SgiImagePlugin.py,sha256=mqpi0G4aiKzWmJHk22WKZ0oGqsglcTNgDfp4H8S-GCM,6097
-PIL/SpiderImagePlugin.py,sha256=3weeJ7kc2t6gA-Hau9QdKgDdbXPcY8zrcTbR4cfAU-g,9554
-PIL/SunImagePlugin.py,sha256=bnjnVFRjvApCH1QC1F9HeynoCe5AZk3wa1tOhPvHzKU,4282
-PIL/TarIO.py,sha256=E_pjAxk9wHezXUuR_99liySBXfJoL2wjzdNDf0g1hTo,1440
-PIL/TgaImagePlugin.py,sha256=geeOJJJ-5Xz3u4JiDMrouyr-XFSqZ6Z48OuOaOY7_lI,6485
-PIL/TiffImagePlugin.py,sha256=uYKFj4zJivvZI_QSHRjR4uWJC_tHh4VgsegOAJPZCfY,75049
-PIL/TiffTags.py,sha256=CPaXv9s7T2oNFZFVbD-Kwz-K2V5ZcHKFkw3rT-Llkp4,15297
-PIL/WalImageFile.py,sha256=MhlGQBmSA_4OPBv6EL9bqFYe0YAf5rYtgAI_y0T920U,5520
-PIL/WebPImagePlugin.py,sha256=buw7FnrHviRmiYMcVSslJNohK3-OcwOUcnAkbZYJu-o,10924
-PIL/WmfImagePlugin.py,sha256=wvJeH9k4XJoUE2wVcf5G_8eeIuuO9BuGiV8jOZlcWrM,4625
-PIL/XVThumbImagePlugin.py,sha256=zmZ8Z4B8Kr6NOdUqSipW9_X5mKiLBLs-wxvPRRg1l0M,1940
-PIL/XbmImagePlugin.py,sha256=kuyd690rupwLFZj5r8hbGmI0Wr8sD_CceCuRew_PUew,2454
-PIL/XpmImagePlugin.py,sha256=1EBt-g678p0A0NXOkxq7sGM8dymneDMHHQmwJzAbrlw,3062
-PIL/__init__.py,sha256=3Z8lwq0danRE7WQFZxa7vMvfSjv_C4-Q73FUr_gHt4Y,1763
-PIL/__main__.py,sha256=axR7PO-HtXp-o0rBhKIxs0wark0rBfaDIhAIWqtWUo4,41
-PIL/__pycache__/BdfFontFile.cpython-39.pyc,,
-PIL/__pycache__/BlpImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/BmpImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/BufrStubImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/ContainerIO.cpython-39.pyc,,
-PIL/__pycache__/CurImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/DcxImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/DdsImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/EpsImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/ExifTags.cpython-39.pyc,,
-PIL/__pycache__/FitsImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/FitsStubImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/FliImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/FontFile.cpython-39.pyc,,
-PIL/__pycache__/FpxImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/FtexImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/GbrImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/GdImageFile.cpython-39.pyc,,
-PIL/__pycache__/GifImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/GimpGradientFile.cpython-39.pyc,,
-PIL/__pycache__/GimpPaletteFile.cpython-39.pyc,,
-PIL/__pycache__/GribStubImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/Hdf5StubImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/IcnsImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/IcoImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/ImImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/Image.cpython-39.pyc,,
-PIL/__pycache__/ImageChops.cpython-39.pyc,,
-PIL/__pycache__/ImageCms.cpython-39.pyc,,
-PIL/__pycache__/ImageColor.cpython-39.pyc,,
-PIL/__pycache__/ImageDraw.cpython-39.pyc,,
-PIL/__pycache__/ImageDraw2.cpython-39.pyc,,
-PIL/__pycache__/ImageEnhance.cpython-39.pyc,,
-PIL/__pycache__/ImageFile.cpython-39.pyc,,
-PIL/__pycache__/ImageFilter.cpython-39.pyc,,
-PIL/__pycache__/ImageFont.cpython-39.pyc,,
-PIL/__pycache__/ImageGrab.cpython-39.pyc,,
-PIL/__pycache__/ImageMath.cpython-39.pyc,,
-PIL/__pycache__/ImageMode.cpython-39.pyc,,
-PIL/__pycache__/ImageMorph.cpython-39.pyc,,
-PIL/__pycache__/ImageOps.cpython-39.pyc,,
-PIL/__pycache__/ImagePalette.cpython-39.pyc,,
-PIL/__pycache__/ImagePath.cpython-39.pyc,,
-PIL/__pycache__/ImageQt.cpython-39.pyc,,
-PIL/__pycache__/ImageSequence.cpython-39.pyc,,
-PIL/__pycache__/ImageShow.cpython-39.pyc,,
-PIL/__pycache__/ImageStat.cpython-39.pyc,,
-PIL/__pycache__/ImageTk.cpython-39.pyc,,
-PIL/__pycache__/ImageTransform.cpython-39.pyc,,
-PIL/__pycache__/ImageWin.cpython-39.pyc,,
-PIL/__pycache__/ImtImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/IptcImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/Jpeg2KImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/JpegImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/JpegPresets.cpython-39.pyc,,
-PIL/__pycache__/McIdasImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/MicImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/MpegImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/MpoImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/MspImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/PSDraw.cpython-39.pyc,,
-PIL/__pycache__/PaletteFile.cpython-39.pyc,,
-PIL/__pycache__/PalmImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/PcdImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/PcfFontFile.cpython-39.pyc,,
-PIL/__pycache__/PcxImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/PdfImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/PdfParser.cpython-39.pyc,,
-PIL/__pycache__/PixarImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/PngImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/PpmImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/PsdImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/PyAccess.cpython-39.pyc,,
-PIL/__pycache__/SgiImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/SpiderImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/SunImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/TarIO.cpython-39.pyc,,
-PIL/__pycache__/TgaImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/TiffImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/TiffTags.cpython-39.pyc,,
-PIL/__pycache__/WalImageFile.cpython-39.pyc,,
-PIL/__pycache__/WebPImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/WmfImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/XVThumbImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/XbmImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/XpmImagePlugin.cpython-39.pyc,,
-PIL/__pycache__/__init__.cpython-39.pyc,,
-PIL/__pycache__/__main__.cpython-39.pyc,,
-PIL/__pycache__/_binary.cpython-39.pyc,,
-PIL/__pycache__/_tkinter_finder.cpython-39.pyc,,
-PIL/__pycache__/_util.cpython-39.pyc,,
-PIL/__pycache__/_version.cpython-39.pyc,,
-PIL/__pycache__/features.cpython-39.pyc,,
-PIL/_binary.py,sha256=E5qhxNJ7hhbEoqu0mODOXHT8z-FDRShXG3jTJhsDdas,2043
-PIL/_imaging.cp39-win_amd64.pyd,sha256=rjBx1Em4OZEdaaB5EUE6ptH7zLbSBIHA54FxMdfCieM,3202048
-PIL/_imagingcms.cp39-win_amd64.pyd,sha256=T63FsiwkHW_aOGKZGm34AagOcdC2EyXluthlmv9f9iU,254976
-PIL/_imagingft.cp39-win_amd64.pyd,sha256=oudclQKBA5NhGs5sY2hVGBwtUsw81gkspvMS4QIhxkA,1505280
-PIL/_imagingmath.cp39-win_amd64.pyd,sha256=lSTNqJ-7fsT0uxDXrWisXD0H0ReS3r4XzoSTswIBbNk,25088
-PIL/_imagingmorph.cp39-win_amd64.pyd,sha256=PSQSThxHCz0soHLNAgh83gJyVfGtPSKxHLg_FbYCR4g,13824
-PIL/_imagingtk.cp39-win_amd64.pyd,sha256=kd8eB3Yid40OlxZToaIFyVZqDKyCbLn94wxfG22CUXs,15360
-PIL/_tkinter_finder.py,sha256=_h4IyntUxL3ZCMnuKGxvW5VwN9k8Yiel0E4j_i41nxk,752
-PIL/_util.py,sha256=pbjX5KY1W2oZyYVC4TE9ai2PfrJZrAsO5hAnz_JMees,359
-PIL/_version.py,sha256=TSDtIA_HTVdlnbVHs_Qn2GMMtftmiSHLYUnuWLa1_rk,50
-PIL/_webp.cp39-win_amd64.pyd,sha256=LdSJ36RBE2X0JvsZn5_DyMuhpgYf_gJGVKX2FROtna8,522752
-PIL/concrt140.dll,sha256=VzIpoH84q50vwuGluY6SQ7mzkQAyMYDIOtfdr5ju5Go,317864
-PIL/features.py,sha256=j2LT6v78cHWbR8z8OVaAGIbJWI-Bs62pfiB1i1fminM,9387
-PIL/msvcp140.dll,sha256=n-5vNlR9b26nygM4ZVVV26a7D3mLxgM00puU0VR9pNo,566704
-PIL/msvcp140_1.dll,sha256=hzGpPlGcJZXJ_UiebZrAfpZESMDaHI7p7lAKeYlIJhc,23944
-PIL/msvcp140_2.dll,sha256=Nmr44HHwBNpdlagypGsuiCGo4ClDQKk_fJXPSMRBBn4,186800
-PIL/msvcp140_atomic_wait.dll,sha256=xhoocR-Mbpv9SHnPX1OwE9ZTutrTCKvj6IfGlLIj1vA,57264
-PIL/msvcp140_codecvt_ids.dll,sha256=m0X9BpvQB22Kv-t8PDCh9cX8jnEkAXhTqT2DGjRsPSE,21424
-PIL/vccorlib140.dll,sha256=eShGWb9DAhYjAnN9JROxfgl0LN77lUDoD5fTDJMHfXw,335792
-PIL/vcruntime140.dll,sha256=nStA8DlcxdG01eoXuElwwplx1EjDcQRnbbV3WG1K0bE,98224
-PIL/vcruntime140_1.dll,sha256=NASKuqBw7ME7MYzqMUJfTKPt0TPTUDGKxlJZ5gWMizI,37256
-Pillow-9.1.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
-Pillow-9.1.0.dist-info/LICENSE,sha256=plVMtze6bJtH0zAfeN4DtO0NPwjWz5QAcU89TIlPaUM,1444
-Pillow-9.1.0.dist-info/METADATA,sha256=DLY0qLsMqaQS2OR2dtHBPI2XrMvxgvmrahFZ5Bdnq4U,8722
-Pillow-9.1.0.dist-info/RECORD,,
-Pillow-9.1.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
-Pillow-9.1.0.dist-info/WHEEL,sha256=fVcVlLzi8CGi_Ul8vjMdn8gER25dn5GBg9E6k9z41-Y,100
-Pillow-9.1.0.dist-info/top_level.txt,sha256=riZqrk-hyZqh5f1Z0Zwii3dKfxEsByhu9cU9IODF-NY,4
-Pillow-9.1.0.dist-info/zip-safe,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
diff --git a/venv/Lib/site-packages/Pillow-9.1.0.dist-info/REQUESTED b/venv/Lib/site-packages/Pillow-9.1.0.dist-info/REQUESTED
deleted file mode 100644
index e69de29..0000000
diff --git a/venv/Lib/site-packages/Pillow-9.1.0.dist-info/WHEEL b/venv/Lib/site-packages/Pillow-9.1.0.dist-info/WHEEL
deleted file mode 100644
index d5a9837..0000000
--- a/venv/Lib/site-packages/Pillow-9.1.0.dist-info/WHEEL
+++ /dev/null
@@ -1,5 +0,0 @@
-Wheel-Version: 1.0
-Generator: bdist_wheel (0.37.1)
-Root-Is-Purelib: false
-Tag: cp39-cp39-win_amd64
-
diff --git a/venv/Lib/site-packages/Pillow-9.1.0.dist-info/top_level.txt b/venv/Lib/site-packages/Pillow-9.1.0.dist-info/top_level.txt
deleted file mode 100644
index b338169..0000000
--- a/venv/Lib/site-packages/Pillow-9.1.0.dist-info/top_level.txt
+++ /dev/null
@@ -1 +0,0 @@
-PIL
diff --git a/venv/Lib/site-packages/Pillow-9.1.0.dist-info/zip-safe b/venv/Lib/site-packages/Pillow-9.1.0.dist-info/zip-safe
deleted file mode 100644
index 8b13789..0000000
--- a/venv/Lib/site-packages/Pillow-9.1.0.dist-info/zip-safe
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/venv/Lib/site-packages/_distutils_hack/__init__.py b/venv/Lib/site-packages/_distutils_hack/__init__.py
deleted file mode 100644
index c0170d0..0000000
--- a/venv/Lib/site-packages/_distutils_hack/__init__.py
+++ /dev/null
@@ -1,166 +0,0 @@
-import sys
-import os
-import re
-import importlib
-import warnings
-import contextlib
-
-
-is_pypy = '__pypy__' in sys.builtin_module_names
-
-
-warnings.filterwarnings('ignore',
- r'.+ distutils\b.+ deprecated',
- DeprecationWarning)
-
-
-def warn_distutils_present():
- if 'distutils' not in sys.modules:
- return
- if is_pypy and sys.version_info < (3, 7):
- # PyPy for 3.6 unconditionally imports distutils, so bypass the warning
- # https://foss.heptapod.net/pypy/pypy/-/blob/be829135bc0d758997b3566062999ee8b23872b4/lib-python/3/site.py#L250
- return
- warnings.warn(
- "Distutils was imported before Setuptools, but importing Setuptools "
- "also replaces the `distutils` module in `sys.modules`. This may lead "
- "to undesirable behaviors or errors. To avoid these issues, avoid "
- "using distutils directly, ensure that setuptools is installed in the "
- "traditional way (e.g. not an editable install), and/or make sure "
- "that setuptools is always imported before distutils.")
-
-
-def clear_distutils():
- if 'distutils' not in sys.modules:
- return
- warnings.warn("Setuptools is replacing distutils.")
- mods = [name for name in sys.modules if re.match(r'distutils\b', name)]
- for name in mods:
- del sys.modules[name]
-
-
-def enabled():
- """
- Allow selection of distutils by environment variable.
- """
- which = os.environ.get('SETUPTOOLS_USE_DISTUTILS', 'local')
- return which == 'local'
-
-
-def ensure_local_distutils():
- clear_distutils()
-
- # With the DistutilsMetaFinder in place,
- # perform an import to cause distutils to be
- # loaded from setuptools._distutils. Ref #2906.
- with shim():
- importlib.import_module('distutils')
-
- # check that submodules load as expected
- core = importlib.import_module('distutils.core')
- assert '_distutils' in core.__file__, core.__file__
-
-
-def do_override():
- """
- Ensure that the local copy of distutils is preferred over stdlib.
-
- See https://github.com/pypa/setuptools/issues/417#issuecomment-392298401
- for more motivation.
- """
- if enabled():
- warn_distutils_present()
- ensure_local_distutils()
-
-
-class DistutilsMetaFinder:
- def find_spec(self, fullname, path, target=None):
- if path is not None:
- return
-
- method_name = 'spec_for_{fullname}'.format(**locals())
- method = getattr(self, method_name, lambda: None)
- return method()
-
- def spec_for_distutils(self):
- import importlib.abc
- import importlib.util
-
- try:
- mod = importlib.import_module('setuptools._distutils')
- except Exception:
- # There are a couple of cases where setuptools._distutils
- # may not be present:
- # - An older Setuptools without a local distutils is
- # taking precedence. Ref #2957.
- # - Path manipulation during sitecustomize removes
- # setuptools from the path but only after the hook
- # has been loaded. Ref #2980.
- # In either case, fall back to stdlib behavior.
- return
-
- class DistutilsLoader(importlib.abc.Loader):
-
- def create_module(self, spec):
- return mod
-
- def exec_module(self, module):
- pass
-
- return importlib.util.spec_from_loader('distutils', DistutilsLoader())
-
- def spec_for_pip(self):
- """
- Ensure stdlib distutils when running under pip.
- See pypa/pip#8761 for rationale.
- """
- if self.pip_imported_during_build():
- return
- clear_distutils()
- self.spec_for_distutils = lambda: None
-
- @classmethod
- def pip_imported_during_build(cls):
- """
- Detect if pip is being imported in a build script. Ref #2355.
- """
- import traceback
- return any(
- cls.frame_file_is_setup(frame)
- for frame, line in traceback.walk_stack(None)
- )
-
- @staticmethod
- def frame_file_is_setup(frame):
- """
- Return True if the indicated frame suggests a setup.py file.
- """
- # some frames may not have __file__ (#2940)
- return frame.f_globals.get('__file__', '').endswith('setup.py')
-
-
-DISTUTILS_FINDER = DistutilsMetaFinder()
-
-
-def add_shim():
- DISTUTILS_FINDER in sys.meta_path or insert_shim()
-
-
-@contextlib.contextmanager
-def shim():
- insert_shim()
- try:
- yield
- finally:
- remove_shim()
-
-
-def insert_shim():
- sys.meta_path.insert(0, DISTUTILS_FINDER)
-
-
-def remove_shim():
- try:
- sys.meta_path.remove(DISTUTILS_FINDER)
- except ValueError:
- pass
diff --git a/venv/Lib/site-packages/_distutils_hack/override.py b/venv/Lib/site-packages/_distutils_hack/override.py
deleted file mode 100644
index 2cc433a..0000000
--- a/venv/Lib/site-packages/_distutils_hack/override.py
+++ /dev/null
@@ -1 +0,0 @@
-__import__('_distutils_hack').do_override()
diff --git a/venv/Lib/site-packages/backports/configparser/__init__.py b/venv/Lib/site-packages/backports/configparser/__init__.py
deleted file mode 100644
index 73386a9..0000000
--- a/venv/Lib/site-packages/backports/configparser/__init__.py
+++ /dev/null
@@ -1,1440 +0,0 @@
-"""Configuration file parser.
-
-A configuration file consists of sections, lead by a "[section]" header,
-and followed by "name: value" entries, with continuations and such in
-the style of RFC 822.
-
-Intrinsic defaults can be specified by passing them into the
-ConfigParser constructor as a dictionary.
-
-class:
-
-ConfigParser -- responsible for parsing a list of
- configuration files, and managing the parsed database.
-
- methods:
-
- __init__(defaults=None, dict_type=_default_dict, allow_no_value=False,
- delimiters=('=', ':'), comment_prefixes=('#', ';'),
- inline_comment_prefixes=None, strict=True,
- empty_lines_in_values=True, default_section='DEFAULT',
- interpolation=, converters=):
- Create the parser. When `defaults' is given, it is initialized into the
- dictionary or intrinsic defaults. The keys must be strings, the values
- must be appropriate for %()s string interpolation.
-
- When `dict_type' is given, it will be used to create the dictionary
- objects for the list of sections, for the options within a section, and
- for the default values.
-
- When `delimiters' is given, it will be used as the set of substrings
- that divide keys from values.
-
- When `comment_prefixes' is given, it will be used as the set of
- substrings that prefix comments in empty lines. Comments can be
- indented.
-
- When `inline_comment_prefixes' is given, it will be used as the set of
- substrings that prefix comments in non-empty lines.
-
- When `strict` is True, the parser won't allow for any section or option
- duplicates while reading from a single source (file, string or
- dictionary). Default is True.
-
- When `empty_lines_in_values' is False (default: True), each empty line
- marks the end of an option. Otherwise, internal empty lines of
- a multiline option are kept as part of the value.
-
- When `allow_no_value' is True (default: False), options without
- values are accepted; the value presented for these is None.
-
- When `default_section' is given, the name of the special section is
- named accordingly. By default it is called ``"DEFAULT"`` but this can
- be customized to point to any other valid section name. Its current
- value can be retrieved using the ``parser_instance.default_section``
- attribute and may be modified at runtime.
-
- When `interpolation` is given, it should be an Interpolation subclass
- instance. It will be used as the handler for option value
- pre-processing when using getters. RawConfigParser objects don't do
- any sort of interpolation, whereas ConfigParser uses an instance of
- BasicInterpolation. The library also provides a ``zc.buildbot``
- inspired ExtendedInterpolation implementation.
-
- When `converters` is given, it should be a dictionary where each key
- represents the name of a type converter and each value is a callable
- implementing the conversion from string to the desired datatype. Every
- converter gets its corresponding get*() method on the parser object and
- section proxies.
-
- sections()
- Return all the configuration section names, sans DEFAULT.
-
- has_section(section)
- Return whether the given section exists.
-
- has_option(section, option)
- Return whether the given option exists in the given section.
-
- options(section)
- Return list of configuration options for the named section.
-
- read(filenames, encoding=None)
- Read and parse the iterable of named configuration files, given by
- name. A single filename is also allowed. Non-existing files
- are ignored. Return list of successfully read files.
-
- read_file(f, filename=None)
- Read and parse one configuration file, given as a file object.
- The filename defaults to f.name; it is only used in error
- messages (if f has no `name' attribute, the string `??>' is used).
-
- read_string(string)
- Read configuration from a given string.
-
- read_dict(dictionary)
- Read configuration from a dictionary. Keys are section names,
- values are dictionaries with keys and values that should be present
- in the section. If the used dictionary type preserves order, sections
- and their keys will be added in order. Values are automatically
- converted to strings.
-
- get(section, option, raw=False, vars=None, fallback=_UNSET)
- Return a string value for the named option. All % interpolations are
- expanded in the return values, based on the defaults passed into the
- constructor and the DEFAULT section. Additional substitutions may be
- provided using the `vars' argument, which must be a dictionary whose
- contents override any pre-existing defaults. If `option' is a key in
- `vars', the value from `vars' is used.
-
- getint(section, options, raw=False, vars=None, fallback=_UNSET)
- Like get(), but convert value to an integer.
-
- getfloat(section, options, raw=False, vars=None, fallback=_UNSET)
- Like get(), but convert value to a float.
-
- getboolean(section, options, raw=False, vars=None, fallback=_UNSET)
- Like get(), but convert value to a boolean (currently case
- insensitively defined as 0, false, no, off for False, and 1, true,
- yes, on for True). Returns False or True.
-
- items(section=_UNSET, raw=False, vars=None)
- If section is given, return a list of tuples with (name, value) for
- each option in the section. Otherwise, return a list of tuples with
- (section_name, section_proxy) for each section, including DEFAULTSECT.
-
- remove_section(section)
- Remove the given file section and all its options.
-
- remove_option(section, option)
- Remove the given option from the given section.
-
- set(section, option, value)
- Set the given option.
-
- write(fp, space_around_delimiters=True)
- Write the configuration state in .ini format. If
- `space_around_delimiters' is True (the default), delimiters
- between keys and values are surrounded by spaces.
-"""
-
-from collections.abc import MutableMapping
-from collections import ChainMap as _ChainMap
-import functools
-from .compat import io
-import itertools
-import os
-import re
-import sys
-import warnings
-
-
-__all__ = [
- "NoSectionError",
- "DuplicateOptionError",
- "DuplicateSectionError",
- "NoOptionError",
- "InterpolationError",
- "InterpolationDepthError",
- "InterpolationMissingOptionError",
- "InterpolationSyntaxError",
- "ParsingError",
- "MissingSectionHeaderError",
- "ConfigParser",
- "SafeConfigParser",
- "RawConfigParser",
- "Interpolation",
- "BasicInterpolation",
- "ExtendedInterpolation",
- "LegacyInterpolation",
- "SectionProxy",
- "ConverterMapping",
- "DEFAULTSECT",
- "MAX_INTERPOLATION_DEPTH",
-]
-
-_default_dict = dict
-DEFAULTSECT = "DEFAULT"
-
-MAX_INTERPOLATION_DEPTH = 10
-
-
-# exception classes
-class Error(Exception):
- """Base class for ConfigParser exceptions."""
-
- def __init__(self, msg=''):
- self.message = msg
- Exception.__init__(self, msg)
-
- def __repr__(self):
- return self.message
-
- __str__ = __repr__
-
-
-class NoSectionError(Error):
- """Raised when no section matches a requested option."""
-
- def __init__(self, section):
- Error.__init__(self, 'No section: %r' % (section,))
- self.section = section
- self.args = (section,)
-
-
-class DuplicateSectionError(Error):
- """Raised when a section is repeated in an input source.
-
- Possible repetitions that raise this exception are: multiple creation
- using the API or in strict parsers when a section is found more than once
- in a single input file, string or dictionary.
- """
-
- def __init__(self, section, source=None, lineno=None):
- msg = [repr(section), " already exists"]
- if source is not None:
- message = ["While reading from ", repr(source)]
- if lineno is not None:
- message.append(" [line {0:2d}]".format(lineno))
- message.append(": section ")
- message.extend(msg)
- msg = message
- else:
- msg.insert(0, "Section ")
- Error.__init__(self, "".join(msg))
- self.section = section
- self.source = source
- self.lineno = lineno
- self.args = (section, source, lineno)
-
-
-class DuplicateOptionError(Error):
- """Raised by strict parsers when an option is repeated in an input source.
-
- Current implementation raises this exception only when an option is found
- more than once in a single file, string or dictionary.
- """
-
- def __init__(self, section, option, source=None, lineno=None):
- msg = [repr(option), " in section ", repr(section), " already exists"]
- if source is not None:
- message = ["While reading from ", repr(source)]
- if lineno is not None:
- message.append(" [line {0:2d}]".format(lineno))
- message.append(": option ")
- message.extend(msg)
- msg = message
- else:
- msg.insert(0, "Option ")
- Error.__init__(self, "".join(msg))
- self.section = section
- self.option = option
- self.source = source
- self.lineno = lineno
- self.args = (section, option, source, lineno)
-
-
-class NoOptionError(Error):
- """A requested option was not found."""
-
- def __init__(self, option, section):
- Error.__init__(self, "No option %r in section: %r" % (option, section))
- self.option = option
- self.section = section
- self.args = (option, section)
-
-
-class InterpolationError(Error):
- """Base class for interpolation-related exceptions."""
-
- def __init__(self, option, section, msg):
- Error.__init__(self, msg)
- self.option = option
- self.section = section
- self.args = (option, section, msg)
-
-
-class InterpolationMissingOptionError(InterpolationError):
- """A string substitution required a setting which was not available."""
-
- def __init__(self, option, section, rawval, reference):
- msg = (
- "Bad value substitution: option {!r} in section {!r} contains "
- "an interpolation key {!r} which is not a valid option name. "
- "Raw value: {!r}".format(option, section, reference, rawval)
- )
- InterpolationError.__init__(self, option, section, msg)
- self.reference = reference
- self.args = (option, section, rawval, reference)
-
-
-class InterpolationSyntaxError(InterpolationError):
- """Raised when the source text contains invalid syntax.
-
- Current implementation raises this exception when the source text into
- which substitutions are made does not conform to the required syntax.
- """
-
-
-class InterpolationDepthError(InterpolationError):
- """Raised when substitutions are nested too deeply."""
-
- def __init__(self, option, section, rawval):
- msg = (
- "Recursion limit exceeded in value substitution: option {!r} "
- "in section {!r} contains an interpolation key which "
- "cannot be substituted in {} steps. Raw value: {!r}"
- "".format(option, section, MAX_INTERPOLATION_DEPTH, rawval)
- )
- InterpolationError.__init__(self, option, section, msg)
- self.args = (option, section, rawval)
-
-
-class ParsingError(Error):
- """Raised when a configuration file does not follow legal syntax."""
-
- def __init__(self, source=None, filename=None):
- # Exactly one of `source'/`filename' arguments has to be given.
- # `filename' kept for compatibility.
- if filename and source:
- raise ValueError(
- "Cannot specify both `filename' and `source'. " "Use `source'."
- )
- elif not filename and not source:
- raise ValueError("Required argument `source' not given.")
- elif filename:
- source = filename
- Error.__init__(self, 'Source contains parsing errors: %r' % source)
- self.source = source
- self.errors = []
- self.args = (source,)
-
- @property
- def filename(self):
- """Deprecated, use `source'."""
- warnings.warn(
- "The 'filename' attribute will be removed in future versions. "
- "Use 'source' instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- return self.source
-
- @filename.setter
- def filename(self, value):
- """Deprecated, user `source'."""
- warnings.warn(
- "The 'filename' attribute will be removed in future versions. "
- "Use 'source' instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- self.source = value
-
- def append(self, lineno, line):
- self.errors.append((lineno, line))
- self.message += '\n\t[line %2d]: %s' % (lineno, line)
-
-
-class MissingSectionHeaderError(ParsingError):
- """Raised when a key-value pair is found before any section header."""
-
- def __init__(self, filename, lineno, line):
- Error.__init__(
- self,
- 'File contains no section headers.\nfile: %r, line: %d\n%r'
- % (filename, lineno, line),
- )
- self.source = filename
- self.lineno = lineno
- self.line = line
- self.args = (filename, lineno, line)
-
-
-# Used in parser getters to indicate the default behaviour when a specific
-# option is not found it to raise an exception. Created to enable `None' as
-# a valid fallback value.
-_UNSET = object()
-
-
-class Interpolation:
- """Dummy interpolation that passes the value through with no changes."""
-
- def before_get(self, parser, section, option, value, defaults):
- return value
-
- def before_set(self, parser, section, option, value):
- return value
-
- def before_read(self, parser, section, option, value):
- return value
-
- def before_write(self, parser, section, option, value):
- return value
-
-
-class BasicInterpolation(Interpolation):
- """Interpolation as implemented in the classic ConfigParser.
-
- The option values can contain format strings which refer to other values in
- the same section, or values in the special default section.
-
- For example:
-
- something: %(dir)s/whatever
-
- would resolve the "%(dir)s" to the value of dir. All reference
- expansions are done late, on demand. If a user needs to use a bare % in
- a configuration file, she can escape it by writing %%. Other % usage
- is considered a user error and raises `InterpolationSyntaxError'."""
-
- _KEYCRE = re.compile(r"%\(([^)]+)\)s")
-
- def before_get(self, parser, section, option, value, defaults):
- L = []
- self._interpolate_some(parser, option, L, value, section, defaults, 1)
- return ''.join(L)
-
- def before_set(self, parser, section, option, value):
- tmp_value = value.replace('%%', '') # escaped percent signs
- tmp_value = self._KEYCRE.sub('', tmp_value) # valid syntax
- if '%' in tmp_value:
- raise ValueError(
- "invalid interpolation syntax in %r at "
- "position %d" % (value, tmp_value.find('%'))
- )
- return value
-
- def _interpolate_some( # noqa: C901
- self, parser, option, accum, rest, section, map, depth
- ):
- rawval = parser.get(section, option, raw=True, fallback=rest)
- if depth > MAX_INTERPOLATION_DEPTH:
- raise InterpolationDepthError(option, section, rawval)
- while rest:
- p = rest.find("%")
- if p < 0:
- accum.append(rest)
- return
- if p > 0:
- accum.append(rest[:p])
- rest = rest[p:]
- # p is no longer used
- c = rest[1:2]
- if c == "%":
- accum.append("%")
- rest = rest[2:]
- elif c == "(":
- m = self._KEYCRE.match(rest)
- if m is None:
- raise InterpolationSyntaxError(
- option,
- section,
- "bad interpolation variable reference %r" % rest,
- )
- var = parser.optionxform(m.group(1))
- rest = rest[m.end() :]
- try:
- v = map[var]
- except KeyError:
- raise InterpolationMissingOptionError(
- option, section, rawval, var
- ) from None
- if "%" in v:
- self._interpolate_some(
- parser, option, accum, v, section, map, depth + 1
- )
- else:
- accum.append(v)
- else:
- raise InterpolationSyntaxError(
- option,
- section,
- "'%%' must be followed by '%%' or '(', " "found: %r" % (rest,),
- )
-
-
-class ExtendedInterpolation(Interpolation):
- """Advanced variant of interpolation, supports the syntax used by
- `zc.buildout'. Enables interpolation between sections."""
-
- _KEYCRE = re.compile(r"\$\{([^}]+)\}")
-
- def before_get(self, parser, section, option, value, defaults):
- L = []
- self._interpolate_some(parser, option, L, value, section, defaults, 1)
- return ''.join(L)
-
- def before_set(self, parser, section, option, value):
- tmp_value = value.replace('$$', '') # escaped dollar signs
- tmp_value = self._KEYCRE.sub('', tmp_value) # valid syntax
- if '$' in tmp_value:
- raise ValueError(
- "invalid interpolation syntax in %r at "
- "position %d" % (value, tmp_value.find('$'))
- )
- return value
-
- def _interpolate_some( # noqa: C901
- self, parser, option, accum, rest, section, map, depth
- ):
- rawval = parser.get(section, option, raw=True, fallback=rest)
- if depth > MAX_INTERPOLATION_DEPTH:
- raise InterpolationDepthError(option, section, rawval)
- while rest:
- p = rest.find("$")
- if p < 0:
- accum.append(rest)
- return
- if p > 0:
- accum.append(rest[:p])
- rest = rest[p:]
- # p is no longer used
- c = rest[1:2]
- if c == "$":
- accum.append("$")
- rest = rest[2:]
- elif c == "{":
- m = self._KEYCRE.match(rest)
- if m is None:
- raise InterpolationSyntaxError(
- option,
- section,
- "bad interpolation variable reference %r" % rest,
- )
- path = m.group(1).split(':')
- rest = rest[m.end() :]
- sect = section
- opt = option
- try:
- if len(path) == 1:
- opt = parser.optionxform(path[0])
- v = map[opt]
- elif len(path) == 2:
- sect = path[0]
- opt = parser.optionxform(path[1])
- v = parser.get(sect, opt, raw=True)
- else:
- raise InterpolationSyntaxError(
- option, section, "More than one ':' found: %r" % (rest,)
- )
- except (KeyError, NoSectionError, NoOptionError):
- raise InterpolationMissingOptionError(
- option, section, rawval, ":".join(path)
- ) from None
- if "$" in v:
- self._interpolate_some(
- parser,
- opt,
- accum,
- v,
- sect,
- dict(parser.items(sect, raw=True)),
- depth + 1,
- )
- else:
- accum.append(v)
- else:
- raise InterpolationSyntaxError(
- option,
- section,
- "'$' must be followed by '$' or '{', " "found: %r" % (rest,),
- )
-
-
-class LegacyInterpolation(Interpolation):
- """Deprecated interpolation used in old versions of ConfigParser.
- Use BasicInterpolation or ExtendedInterpolation instead."""
-
- _KEYCRE = re.compile(r"%\(([^)]*)\)s|.")
-
- def before_get(self, parser, section, option, value, vars):
- rawval = value
- depth = MAX_INTERPOLATION_DEPTH
- while depth: # Loop through this until it's done
- depth -= 1
- if value and "%(" in value:
- replace = functools.partial(self._interpolation_replace, parser=parser)
- value = self._KEYCRE.sub(replace, value)
- try:
- value = value % vars
- except KeyError as e:
- raise InterpolationMissingOptionError(
- option, section, rawval, e.args[0]
- ) from None
- else:
- break
- if value and "%(" in value:
- raise InterpolationDepthError(option, section, rawval)
- return value
-
- def before_set(self, parser, section, option, value):
- return value
-
- @staticmethod
- def _interpolation_replace(match, parser):
- s = match.group(1)
- if s is None:
- return match.group()
- else:
- return "%%(%s)s" % parser.optionxform(s)
-
-
-class RawConfigParser(MutableMapping):
- """ConfigParser that does not do interpolation."""
-
- # Regular expressions for parsing section headers and options
- _SECT_TMPL = r"""
- \[ # [
- (?P.+) # very permissive!
- \] # ]
- """
- _OPT_TMPL = r"""
- (?P
.*?) # very permissive!
- \s*(?P{delim})\s* # any number of space/tab,
- # followed by any of the
- # allowed delimiters,
- # followed by any space/tab
- (?P.*)$ # everything up to eol
- """
- _OPT_NV_TMPL = r"""
- (?P
.*?) # very permissive!
- \s*(?: # any number of space/tab,
- (?P{delim})\s* # optionally followed by
- # any of the allowed
- # delimiters, followed by any
- # space/tab
- (?P.*))?$ # everything up to eol
- """
- # Interpolation algorithm to be used if the user does not specify another
- _DEFAULT_INTERPOLATION = Interpolation()
- # Compiled regular expression for matching sections
- SECTCRE = re.compile(_SECT_TMPL, re.VERBOSE)
- # Compiled regular expression for matching options with typical separators
- OPTCRE = re.compile(_OPT_TMPL.format(delim="=|:"), re.VERBOSE)
- # Compiled regular expression for matching options with optional values
- # delimited using typical separators
- OPTCRE_NV = re.compile(_OPT_NV_TMPL.format(delim="=|:"), re.VERBOSE)
- # Compiled regular expression for matching leading whitespace in a line
- NONSPACECRE = re.compile(r"\S")
- # Possible boolean values in the configuration.
- BOOLEAN_STATES = {
- '1': True,
- 'yes': True,
- 'true': True,
- 'on': True,
- '0': False,
- 'no': False,
- 'false': False,
- 'off': False,
- }
-
- def __init__(
- self,
- defaults=None,
- dict_type=_default_dict,
- allow_no_value=False,
- *,
- delimiters=('=', ':'),
- comment_prefixes=('#', ';'),
- inline_comment_prefixes=None,
- strict=True,
- empty_lines_in_values=True,
- default_section=DEFAULTSECT,
- interpolation=_UNSET,
- converters=_UNSET,
- ):
-
- self._dict = dict_type
- self._sections = self._dict()
- self._defaults = self._dict()
- self._converters = ConverterMapping(self)
- self._proxies = self._dict()
- self._proxies[default_section] = SectionProxy(self, default_section)
- self._delimiters = tuple(delimiters)
- if delimiters == ('=', ':'):
- self._optcre = self.OPTCRE_NV if allow_no_value else self.OPTCRE
- else:
- d = "|".join(re.escape(d) for d in delimiters)
- if allow_no_value:
- self._optcre = re.compile(self._OPT_NV_TMPL.format(delim=d), re.VERBOSE)
- else:
- self._optcre = re.compile(self._OPT_TMPL.format(delim=d), re.VERBOSE)
- self._comment_prefixes = tuple(comment_prefixes or ())
- self._inline_comment_prefixes = tuple(inline_comment_prefixes or ())
- self._strict = strict
- self._allow_no_value = allow_no_value
- self._empty_lines_in_values = empty_lines_in_values
- self.default_section = default_section
- self._interpolation = interpolation
- if self._interpolation is _UNSET:
- self._interpolation = self._DEFAULT_INTERPOLATION
- if self._interpolation is None:
- self._interpolation = Interpolation()
- if converters is not _UNSET:
- self._converters.update(converters)
- if defaults:
- self._read_defaults(defaults)
-
- def defaults(self):
- return self._defaults
-
- def sections(self):
- """Return a list of section names, excluding [DEFAULT]"""
- # self._sections will never have [DEFAULT] in it
- return list(self._sections.keys())
-
- def add_section(self, section):
- """Create a new section in the configuration.
-
- Raise DuplicateSectionError if a section by the specified name
- already exists. Raise ValueError if name is DEFAULT.
- """
- if section == self.default_section:
- raise ValueError('Invalid section name: %r' % section)
-
- if section in self._sections:
- raise DuplicateSectionError(section)
- self._sections[section] = self._dict()
- self._proxies[section] = SectionProxy(self, section)
-
- def has_section(self, section):
- """Indicate whether the named section is present in the configuration.
-
- The DEFAULT section is not acknowledged.
- """
- return section in self._sections
-
- def options(self, section):
- """Return a list of option names for the given section name."""
- try:
- opts = self._sections[section].copy()
- except KeyError:
- raise NoSectionError(section) from None
- opts.update(self._defaults)
- return list(opts.keys())
-
- def read(self, filenames, encoding=None):
- """Read and parse a filename or an iterable of filenames.
-
- Files that cannot be opened are silently ignored; this is
- designed so that you can specify an iterable of potential
- configuration file locations (e.g. current directory, user's
- home directory, systemwide directory), and all existing
- configuration files in the iterable will be read. A single
- filename may also be given.
-
- Return list of successfully read files.
- """
- if isinstance(filenames, (str, bytes, os.PathLike)):
- filenames = [filenames]
- encoding = io.text_encoding(encoding)
- read_ok = []
- for filename in filenames:
- try:
- with open(filename, encoding=encoding) as fp:
- self._read(fp, filename)
- except OSError:
- continue
- if isinstance(filename, os.PathLike):
- filename = os.fspath(filename)
- read_ok.append(filename)
- return read_ok
-
- def read_file(self, f, source=None):
- """Like read() but the argument must be a file-like object.
-
- The `f' argument must be iterable, returning one line at a time.
- Optional second argument is the `source' specifying the name of the
- file being read. If not given, it is taken from f.name. If `f' has no
- `name' attribute, `??>' is used.
- """
- if source is None:
- try:
- source = f.name
- except AttributeError:
- source = '??>'
- self._read(f, source)
-
- def read_string(self, string, source=''):
- """Read configuration from a given string."""
- sfile = io.StringIO(string)
- self.read_file(sfile, source)
-
- def read_dict(self, dictionary, source=''):
- """Read configuration from a dictionary.
-
- Keys are section names, values are dictionaries with keys and values
- that should be present in the section. If the used dictionary type
- preserves order, sections and their keys will be added in order.
-
- All types held in the dictionary are converted to strings during
- reading, including section names, option names and keys.
-
- Optional second argument is the `source' specifying the name of the
- dictionary being read.
- """
- elements_added = set()
- for section, keys in dictionary.items():
- section = str(section)
- try:
- self.add_section(section)
- except (DuplicateSectionError, ValueError):
- if self._strict and section in elements_added:
- raise
- elements_added.add(section)
- for key, value in keys.items():
- key = self.optionxform(str(key))
- if value is not None:
- value = str(value)
- if self._strict and (section, key) in elements_added:
- raise DuplicateOptionError(section, key, source)
- elements_added.add((section, key))
- self.set(section, key, value)
-
- def readfp(self, fp, filename=None):
- """Deprecated, use read_file instead."""
- warnings.warn(
- "This method will be removed in future versions. "
- "Use 'parser.read_file()' instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- self.read_file(fp, source=filename)
-
- def get(self, section, option, *, raw=False, vars=None, fallback=_UNSET):
- """Get an option value for a given section.
-
- If `vars' is provided, it must be a dictionary. The option is looked up
- in `vars' (if provided), `section', and in `DEFAULTSECT' in that order.
- If the key is not found and `fallback' is provided, it is used as
- a fallback value. `None' can be provided as a `fallback' value.
-
- If interpolation is enabled and the optional argument `raw' is False,
- all interpolations are expanded in the return values.
-
- Arguments `raw', `vars', and `fallback' are keyword only.
-
- The section DEFAULT is special.
- """
- try:
- d = self._unify_values(section, vars)
- except NoSectionError:
- if fallback is _UNSET:
- raise
- else:
- return fallback
- option = self.optionxform(option)
- try:
- value = d[option]
- except KeyError:
- if fallback is _UNSET:
- raise NoOptionError(option, section)
- else:
- return fallback
-
- if raw or value is None:
- return value
- else:
- return self._interpolation.before_get(self, section, option, value, d)
-
- def _get(self, section, conv, option, **kwargs):
- return conv(self.get(section, option, **kwargs))
-
- def _get_conv(
- self, section, option, conv, *, raw=False, vars=None, fallback=_UNSET, **kwargs
- ):
- try:
- return self._get(section, conv, option, raw=raw, vars=vars, **kwargs)
- except (NoSectionError, NoOptionError):
- if fallback is _UNSET:
- raise
- return fallback
-
- # getint, getfloat and getboolean provided directly for backwards compat
- def getint(
- self, section, option, *, raw=False, vars=None, fallback=_UNSET, **kwargs
- ):
- return self._get_conv(
- section, option, int, raw=raw, vars=vars, fallback=fallback, **kwargs
- )
-
- def getfloat(
- self, section, option, *, raw=False, vars=None, fallback=_UNSET, **kwargs
- ):
- return self._get_conv(
- section, option, float, raw=raw, vars=vars, fallback=fallback, **kwargs
- )
-
- def getboolean(
- self, section, option, *, raw=False, vars=None, fallback=_UNSET, **kwargs
- ):
- return self._get_conv(
- section,
- option,
- self._convert_to_boolean,
- raw=raw,
- vars=vars,
- fallback=fallback,
- **kwargs,
- )
-
- def items(self, section=_UNSET, raw=False, vars=None):
- """Return a list of (name, value) tuples for each option in a section.
-
- All % interpolations are expanded in the return values, based on the
- defaults passed into the constructor, unless the optional argument
- `raw' is true. Additional substitutions may be provided using the
- `vars' argument, which must be a dictionary whose contents overrides
- any pre-existing defaults.
-
- The section DEFAULT is special.
- """
- if section is _UNSET:
- return super(RawConfigParser, self).items()
- d = self._defaults.copy()
- try:
- d.update(self._sections[section])
- except KeyError:
- if section != self.default_section:
- raise NoSectionError(section)
- orig_keys = list(d.keys())
- # Update with the entry specific variables
- if vars:
- for key, value in vars.items():
- d[self.optionxform(key)] = value
-
- def value_getter_interp(option):
- return self._interpolation.before_get(self, section, option, d[option], d)
-
- def value_getter_raw(option):
- return d[option]
-
- value_getter = value_getter_raw if raw else value_getter_interp
-
- return [(option, value_getter(option)) for option in orig_keys]
-
- def popitem(self):
- """Remove a section from the parser and return it as
- a (section_name, section_proxy) tuple. If no section is present, raise
- KeyError.
-
- The section DEFAULT is never returned because it cannot be removed.
- """
- for key in self.sections():
- value = self[key]
- del self[key]
- return key, value
- raise KeyError
-
- def optionxform(self, optionstr):
- return optionstr.lower()
-
- def has_option(self, section, option):
- """Check for the existence of a given option in a given section.
- If the specified `section' is None or an empty string, DEFAULT is
- assumed. If the specified `section' does not exist, returns False."""
- if not section or section == self.default_section:
- option = self.optionxform(option)
- return option in self._defaults
- elif section not in self._sections:
- return False
- else:
- option = self.optionxform(option)
- return option in self._sections[section] or option in self._defaults
-
- def set(self, section, option, value=None):
- """Set an option."""
- if value:
- value = self._interpolation.before_set(self, section, option, value)
- if not section or section == self.default_section:
- sectdict = self._defaults
- else:
- try:
- sectdict = self._sections[section]
- except KeyError:
- raise NoSectionError(section) from None
- sectdict[self.optionxform(option)] = value
-
- def write(self, fp, space_around_delimiters=True):
- """Write an .ini-format representation of the configuration state.
-
- If `space_around_delimiters' is True (the default), delimiters
- between keys and values are surrounded by spaces.
-
- Please note that comments in the original configuration file are not
- preserved when writing the configuration back.
- """
- if space_around_delimiters:
- d = " {} ".format(self._delimiters[0])
- else:
- d = self._delimiters[0]
- if self._defaults:
- self._write_section(fp, self.default_section, self._defaults.items(), d)
- for section in self._sections:
- self._write_section(fp, section, self._sections[section].items(), d)
-
- def _write_section(self, fp, section_name, section_items, delimiter):
- """Write a single section to the specified `fp'."""
- fp.write("[{}]\n".format(section_name))
- for key, value in section_items:
- value = self._interpolation.before_write(self, section_name, key, value)
- if value is not None or not self._allow_no_value:
- value = delimiter + str(value).replace('\n', '\n\t')
- else:
- value = ""
- fp.write("{}{}\n".format(key, value))
- fp.write("\n")
-
- def remove_option(self, section, option):
- """Remove an option."""
- if not section or section == self.default_section:
- sectdict = self._defaults
- else:
- try:
- sectdict = self._sections[section]
- except KeyError:
- raise NoSectionError(section) from None
- option = self.optionxform(option)
- existed = option in sectdict
- if existed:
- del sectdict[option]
- return existed
-
- def remove_section(self, section):
- """Remove a file section."""
- existed = section in self._sections
- if existed:
- del self._sections[section]
- del self._proxies[section]
- return existed
-
- def __getitem__(self, key):
- if key != self.default_section and not self.has_section(key):
- raise KeyError(key)
- return self._proxies[key]
-
- def __setitem__(self, key, value):
- # To conform with the mapping protocol, overwrites existing values in
- # the section.
- if key in self and self[key] is value:
- return
- # XXX this is not atomic if read_dict fails at any point. Then again,
- # no update method in configparser is atomic in this implementation.
- if key == self.default_section:
- self._defaults.clear()
- elif key in self._sections:
- self._sections[key].clear()
- self.read_dict({key: value})
-
- def __delitem__(self, key):
- if key == self.default_section:
- raise ValueError("Cannot remove the default section.")
- if not self.has_section(key):
- raise KeyError(key)
- self.remove_section(key)
-
- def __contains__(self, key):
- return key == self.default_section or self.has_section(key)
-
- def __len__(self):
- return len(self._sections) + 1 # the default section
-
- def __iter__(self):
- # XXX does it break when underlying container state changed?
- return itertools.chain((self.default_section,), self._sections.keys())
-
- def _read(self, fp, fpname): # noqa: C901
- """Parse a sectioned configuration file.
-
- Each section in a configuration file contains a header, indicated by
- a name in square brackets (`[]'), plus key/value options, indicated by
- `name' and `value' delimited with a specific substring (`=' or `:' by
- default).
-
- Values can span multiple lines, as long as they are indented deeper
- than the first line of the value. Depending on the parser's mode, blank
- lines may be treated as parts of multiline values or ignored.
-
- Configuration files may include comments, prefixed by specific
- characters (`#' and `;' by default). Comments may appear on their own
- in an otherwise empty line or may be entered in lines holding values or
- section names. Please note that comments get stripped off when reading
- configuration files.
- """
- elements_added = set()
- cursect = None # None, or a dictionary
- sectname = None
- optname = None
- lineno = 0
- indent_level = 0
- e = None # None, or an exception
- for lineno, line in enumerate(fp, start=1):
- comment_start = sys.maxsize
- # strip inline comments
- inline_prefixes = {p: -1 for p in self._inline_comment_prefixes}
- while comment_start == sys.maxsize and inline_prefixes:
- next_prefixes = {}
- for prefix, index in inline_prefixes.items():
- index = line.find(prefix, index + 1)
- if index == -1:
- continue
- next_prefixes[prefix] = index
- if index == 0 or (index > 0 and line[index - 1].isspace()):
- comment_start = min(comment_start, index)
- inline_prefixes = next_prefixes
- # strip full line comments
- for prefix in self._comment_prefixes:
- if line.strip().startswith(prefix):
- comment_start = 0
- break
- if comment_start == sys.maxsize:
- comment_start = None
- value = line[:comment_start].strip()
- if not value:
- if self._empty_lines_in_values:
- # add empty line to the value, but only if there was no
- # comment on the line
- if (
- comment_start is None
- and cursect is not None
- and optname
- and cursect[optname] is not None
- ):
- cursect[optname].append('') # newlines added at join
- else:
- # empty line marks end of value
- indent_level = sys.maxsize
- continue
- # continuation line?
- first_nonspace = self.NONSPACECRE.search(line)
- cur_indent_level = first_nonspace.start() if first_nonspace else 0
- if cursect is not None and optname and cur_indent_level > indent_level:
- cursect[optname].append(value)
- # a section header or option header?
- else:
- indent_level = cur_indent_level
- # is it a section header?
- mo = self.SECTCRE.match(value)
- if mo:
- sectname = mo.group('header')
- if sectname in self._sections:
- if self._strict and sectname in elements_added:
- raise DuplicateSectionError(sectname, fpname, lineno)
- cursect = self._sections[sectname]
- elements_added.add(sectname)
- elif sectname == self.default_section:
- cursect = self._defaults
- else:
- cursect = self._dict()
- self._sections[sectname] = cursect
- self._proxies[sectname] = SectionProxy(self, sectname)
- elements_added.add(sectname)
- # So sections can't start with a continuation line
- optname = None
- # no section header in the file?
- elif cursect is None:
- raise MissingSectionHeaderError(fpname, lineno, line)
- # an option line?
- else:
- mo = self._optcre.match(value)
- if mo:
- optname, vi, optval = mo.group('option', 'vi', 'value')
- if not optname:
- e = self._handle_error(e, fpname, lineno, line)
- optname = self.optionxform(optname.rstrip())
- if self._strict and (sectname, optname) in elements_added:
- raise DuplicateOptionError(
- sectname, optname, fpname, lineno
- )
- elements_added.add((sectname, optname))
- # This check is fine because the OPTCRE cannot
- # match if it would set optval to None
- if optval is not None:
- optval = optval.strip()
- cursect[optname] = [optval]
- else:
- # valueless option handling
- cursect[optname] = None
- else:
- # a non-fatal parsing error occurred. set up the
- # exception but keep going. the exception will be
- # raised at the end of the file and will contain a
- # list of all bogus lines
- e = self._handle_error(e, fpname, lineno, line)
- self._join_multiline_values()
- # if any parsing errors occurred, raise an exception
- if e:
- raise e
-
- def _join_multiline_values(self):
- defaults = self.default_section, self._defaults
- all_sections = itertools.chain((defaults,), self._sections.items())
- for section, options in all_sections:
- for name, val in options.items():
- if isinstance(val, list):
- val = '\n'.join(val).rstrip()
- options[name] = self._interpolation.before_read(
- self, section, name, val
- )
-
- def _read_defaults(self, defaults):
- """Read the defaults passed in the initializer.
- Note: values can be non-string."""
- for key, value in defaults.items():
- self._defaults[self.optionxform(key)] = value
-
- def _handle_error(self, exc, fpname, lineno, line):
- if not exc:
- exc = ParsingError(fpname)
- exc.append(lineno, repr(line))
- return exc
-
- def _unify_values(self, section, vars):
- """Create a sequence of lookups with 'vars' taking priority over
- the 'section' which takes priority over the DEFAULTSECT.
-
- """
- sectiondict = {}
- try:
- sectiondict = self._sections[section]
- except KeyError:
- if section != self.default_section:
- raise NoSectionError(section)
- # Update with the entry specific variables
- vardict = {}
- if vars:
- for key, value in vars.items():
- if value is not None:
- value = str(value)
- vardict[self.optionxform(key)] = value
- return _ChainMap(vardict, sectiondict, self._defaults)
-
- def _convert_to_boolean(self, value):
- """Return a boolean value translating from other types if necessary."""
- if value.lower() not in self.BOOLEAN_STATES:
- raise ValueError('Not a boolean: %s' % value)
- return self.BOOLEAN_STATES[value.lower()]
-
- def _validate_value_types(self, *, section="", option="", value=""):
- """Raises a TypeError for non-string values.
-
- The only legal non-string value if we allow valueless
- options is None, so we need to check if the value is a
- string if:
- - we do not allow valueless options, or
- - we allow valueless options but the value is not None
-
- For compatibility reasons this method is not used in classic set()
- for RawConfigParsers. It is invoked in every case for mapping protocol
- access and in ConfigParser.set().
- """
- if not isinstance(section, str):
- raise TypeError("section names must be strings")
- if not isinstance(option, str):
- raise TypeError("option keys must be strings")
- if not self._allow_no_value or value:
- if not isinstance(value, str):
- raise TypeError("option values must be strings")
-
- @property
- def converters(self):
- return self._converters
-
-
-class ConfigParser(RawConfigParser):
- """ConfigParser implementing interpolation."""
-
- _DEFAULT_INTERPOLATION = BasicInterpolation()
-
- def set(self, section, option, value=None):
- """Set an option. Extends RawConfigParser.set by validating type and
- interpolation syntax on the value."""
- self._validate_value_types(option=option, value=value)
- super().set(section, option, value)
-
- def add_section(self, section):
- """Create a new section in the configuration. Extends
- RawConfigParser.add_section by validating if the section name is
- a string."""
- self._validate_value_types(section=section)
- super().add_section(section)
-
- def _read_defaults(self, defaults):
- """Reads the defaults passed in the initializer, implicitly converting
- values to strings like the rest of the API.
-
- Does not perform interpolation for backwards compatibility.
- """
- try:
- hold_interpolation = self._interpolation
- self._interpolation = Interpolation()
- self.read_dict({self.default_section: defaults})
- finally:
- self._interpolation = hold_interpolation
-
-
-class SafeConfigParser(ConfigParser):
- """ConfigParser alias for backwards compatibility purposes."""
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- warnings.warn(
- "The SafeConfigParser class has been renamed to ConfigParser "
- "in Python 3.2. This alias will be removed in future versions."
- " Use ConfigParser directly instead.",
- DeprecationWarning,
- stacklevel=2,
- )
-
-
-class SectionProxy(MutableMapping):
- """A proxy for a single section from a parser."""
-
- def __init__(self, parser, name):
- """Creates a view on a section of the specified `name` in `parser`."""
- self._parser = parser
- self._name = name
- for conv in parser.converters:
- key = 'get' + conv
- getter = functools.partial(self.get, _impl=getattr(parser, key))
- setattr(self, key, getter)
-
- def __repr__(self):
- return ''.format(self._name)
-
- def __getitem__(self, key):
- if not self._parser.has_option(self._name, key):
- raise KeyError(key)
- return self._parser.get(self._name, key)
-
- def __setitem__(self, key, value):
- self._parser._validate_value_types(option=key, value=value)
- return self._parser.set(self._name, key, value)
-
- def __delitem__(self, key):
- if not (
- self._parser.has_option(self._name, key)
- and self._parser.remove_option(self._name, key)
- ):
- raise KeyError(key)
-
- def __contains__(self, key):
- return self._parser.has_option(self._name, key)
-
- def __len__(self):
- return len(self._options())
-
- def __iter__(self):
- return self._options().__iter__()
-
- def _options(self):
- if self._name != self._parser.default_section:
- return self._parser.options(self._name)
- else:
- return self._parser.defaults()
-
- @property
- def parser(self):
- # The parser object of the proxy is read-only.
- return self._parser
-
- @property
- def name(self):
- # The name of the section on a proxy is read-only.
- return self._name
-
- def get(self, option, fallback=None, *, raw=False, vars=None, _impl=None, **kwargs):
- """Get an option value.
-
- Unless `fallback` is provided, `None` will be returned if the option
- is not found.
-
- """
- # If `_impl` is provided, it should be a getter method on the parser
- # object that provides the desired type conversion.
- if not _impl:
- _impl = self._parser.get
- return _impl(
- self._name, option, raw=raw, vars=vars, fallback=fallback, **kwargs
- )
-
-
-class ConverterMapping(MutableMapping):
- """Enables reuse of get*() methods between the parser and section proxies.
-
- If a parser class implements a getter directly, the value for the given
- key will be ``None``. The presence of the converter name here enables
- section proxies to find and use the implementation on the parser class.
- """
-
- GETTERCRE = re.compile(r"^get(?P.+)$")
-
- def __init__(self, parser):
- self._parser = parser
- self._data = {}
- for getter in dir(self._parser):
- m = self.GETTERCRE.match(getter)
- if not m or not callable(getattr(self._parser, getter)):
- continue
- self._data[m.group('name')] = None # See class docstring.
-
- def __getitem__(self, key):
- return self._data[key]
-
- def __setitem__(self, key, value):
- try:
- k = 'get' + key
- except TypeError:
- raise ValueError(
- 'Incompatible key: {} (type: {})' ''.format(key, type(key))
- )
- if k == 'get':
- raise ValueError('Incompatible key: cannot use "" as a name')
- self._data[key] = value
- func = functools.partial(self._parser._get_conv, conv=value)
- func.converter = value
- setattr(self._parser, k, func)
- for proxy in self._parser.values():
- getter = functools.partial(proxy.get, _impl=func)
- setattr(proxy, k, getter)
-
- def __delitem__(self, key):
- try:
- k = 'get' + (key or None)
- except TypeError:
- raise KeyError(key)
- del self._data[key]
- for inst in itertools.chain((self._parser,), self._parser.values()):
- try:
- delattr(inst, k)
- except AttributeError:
- # don't raise since the entry was present in _data, silently
- # clean up
- continue
-
- def __iter__(self):
- return iter(self._data)
-
- def __len__(self):
- return len(self._data)
diff --git a/venv/Lib/site-packages/backports/configparser/compat.py b/venv/Lib/site-packages/backports/configparser/compat.py
deleted file mode 100644
index 951bfea..0000000
--- a/venv/Lib/site-packages/backports/configparser/compat.py
+++ /dev/null
@@ -1,19 +0,0 @@
-import types
-import io as _io
-
-
-def text_encoding(encoding, stacklevel=2):
- """
- Stubbed version of io.text_encoding as found in Python 3.10
- """
- return encoding
-
-
-def copy_module(mod, **defaults):
- copy = types.ModuleType(mod.__name__, doc=mod.__doc__)
- vars(copy).update(defaults)
- vars(copy).update(vars(mod))
- return copy
-
-
-io = copy_module(_io, text_encoding=text_encoding)
diff --git a/venv/Lib/site-packages/configparser-5.2.0.dist-info/INSTALLER b/venv/Lib/site-packages/configparser-5.2.0.dist-info/INSTALLER
deleted file mode 100644
index a1b589e..0000000
--- a/venv/Lib/site-packages/configparser-5.2.0.dist-info/INSTALLER
+++ /dev/null
@@ -1 +0,0 @@
-pip
diff --git a/venv/Lib/site-packages/configparser-5.2.0.dist-info/LICENSE b/venv/Lib/site-packages/configparser-5.2.0.dist-info/LICENSE
deleted file mode 100644
index 353924b..0000000
--- a/venv/Lib/site-packages/configparser-5.2.0.dist-info/LICENSE
+++ /dev/null
@@ -1,19 +0,0 @@
-Copyright Jason R. Coombs
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to
-deal in the Software without restriction, including without limitation the
-rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
-sell copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-IN THE SOFTWARE.
diff --git a/venv/Lib/site-packages/configparser-5.2.0.dist-info/METADATA b/venv/Lib/site-packages/configparser-5.2.0.dist-info/METADATA
deleted file mode 100644
index fe728ae..0000000
--- a/venv/Lib/site-packages/configparser-5.2.0.dist-info/METADATA
+++ /dev/null
@@ -1,267 +0,0 @@
-Metadata-Version: 2.1
-Name: configparser
-Version: 5.2.0
-Summary: Updated configparser from Python 3.8 for Python 2.6+.
-Home-page: https://github.com/jaraco/configparser/
-Author: Łukasz Langa
-Author-email: lukasz@langa.pl
-Maintainer: Jason R. Coombs
-Maintainer-email: jaraco@jaraco.com
-License: UNKNOWN
-Keywords: configparser ini parsing conf cfg configuration file
-Platform: any
-Classifier: Development Status :: 5 - Production/Stable
-Classifier: Intended Audience :: Developers
-Classifier: License :: OSI Approved :: MIT License
-Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3 :: Only
-Requires-Python: >=3.6
-License-File: LICENSE
-Provides-Extra: docs
-Requires-Dist: sphinx ; extra == 'docs'
-Requires-Dist: jaraco.packaging (>=8.2) ; extra == 'docs'
-Requires-Dist: rst.linker (>=1.9) ; extra == 'docs'
-Requires-Dist: jaraco.tidelift (>=1.4) ; extra == 'docs'
-Provides-Extra: testing
-Requires-Dist: pytest (>=6) ; extra == 'testing'
-Requires-Dist: pytest-checkdocs (>=2.4) ; extra == 'testing'
-Requires-Dist: pytest-flake8 ; extra == 'testing'
-Requires-Dist: pytest-cov ; extra == 'testing'
-Requires-Dist: pytest-enabler (>=1.0.1) ; extra == 'testing'
-Requires-Dist: types-backports ; extra == 'testing'
-Requires-Dist: pytest-black (>=0.3.7) ; (platform_python_implementation != "PyPy") and extra == 'testing'
-Requires-Dist: pytest-mypy ; (platform_python_implementation != "PyPy") and extra == 'testing'
-
-.. image:: https://img.shields.io/pypi/v/configparser.svg
- :target: `PyPI link`_
-
-.. image:: https://img.shields.io/pypi/pyversions/configparser.svg
- :target: `PyPI link`_
-
-.. _PyPI link: https://pypi.org/project/configparser
-
-.. image:: https://github.com/jaraco/configparser/workflows/tests/badge.svg
- :target: https://github.com/jaraco/configparser/actions?query=workflow%3A%22tests%22
- :alt: tests
-
-.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
- :target: https://github.com/psf/black
- :alt: Code style: Black
-
-.. image:: https://readthedocs.org/projects/configparser/badge/?version=latest
- :target: https://configparser.readthedocs.io/en/latest/?badge=latest
-
-.. image:: https://img.shields.io/badge/skeleton-2021-informational
- :target: https://blog.jaraco.com/skeleton
-
-.. image:: https://tidelift.com/badges/package/pypi/configparser
- :target: https://tidelift.com/subscription/pkg/pypi-configparser?utm_source=pypi-configparser&utm_medium=readme
-
-
-This package is a backport of the refreshed and enhanced ConfigParser from
-later Python versions. To use the backport instead of the built-in version,
-simply import it explicitly as a backport::
-
- from backports import configparser
-
-To use the backport on Python 2 and the built-in version on
-Python 3, use the standard invocation::
-
- import configparser
-
-For detailed documentation consult the vanilla version at
-http://docs.python.org/3/library/configparser.html.
-
-Why you'll love ``configparser``
---------------------------------
-
-Whereas almost completely compatible with its older brother, ``configparser``
-sports a bunch of interesting new features:
-
-* full mapping protocol access (`more info
- `_)::
-
- >>> parser = ConfigParser()
- >>> parser.read_string("""
- [DEFAULT]
- location = upper left
- visible = yes
- editable = no
- color = blue
-
- [main]
- title = Main Menu
- color = green
-
- [options]
- title = Options
- """)
- >>> parser['main']['color']
- 'green'
- >>> parser['main']['editable']
- 'no'
- >>> section = parser['options']
- >>> section['title']
- 'Options'
- >>> section['title'] = 'Options (editable: %(editable)s)'
- >>> section['title']
- 'Options (editable: no)'
-
-* there's now one default ``ConfigParser`` class, which basically is the old
- ``SafeConfigParser`` with a bunch of tweaks which make it more predictable for
- users. Don't need interpolation? Simply use
- ``ConfigParser(interpolation=None)``, no need to use a distinct
- ``RawConfigParser`` anymore.
-
-* the parser is highly `customizable upon instantiation
- `__
- supporting things like changing option delimiters, comment characters, the
- name of the DEFAULT section, the interpolation syntax, etc.
-
-* you can easily create your own interpolation syntax but there are two powerful
- implementations built-in (`more info
- `__):
-
- * the classic ``%(string-like)s`` syntax (called ``BasicInterpolation``)
-
- * a new ``${buildout:like}`` syntax (called ``ExtendedInterpolation``)
-
-* fallback values may be specified in getters (`more info
- `__)::
-
- >>> config.get('closet', 'monster',
- ... fallback='No such things as monsters')
- 'No such things as monsters'
-
-* ``ConfigParser`` objects can now read data directly `from strings
- `__
- and `from dictionaries
- `__.
- That means importing configuration from JSON or specifying default values for
- the whole configuration (multiple sections) is now a single line of code. Same
- goes for copying data from another ``ConfigParser`` instance, thanks to its
- mapping protocol support.
-
-* many smaller tweaks, updates and fixes
-
-A few words about Unicode
--------------------------
-
-``configparser`` comes from Python 3 and as such it works well with Unicode.
-The library is generally cleaned up in terms of internal data storage and
-reading/writing files. There are a couple of incompatibilities with the old
-``ConfigParser`` due to that. However, the work required to migrate is well
-worth it as it shows the issues that would likely come up during migration of
-your project to Python 3.
-
-The design assumes that Unicode strings are used whenever possible [1]_. That
-gives you the certainty that what's stored in a configuration object is text.
-Once your configuration is read, the rest of your application doesn't have to
-deal with encoding issues. All you have is text [2]_. The only two phases when
-you should explicitly state encoding is when you either read from an external
-source (e.g. a file) or write back.
-
-Versioning
-----------
-
-This project uses `semver `_ to
-communicate the impact of various releases while periodically syncing
-with the upstream implementation in CPython.
-The `history `_
-serves as a reference indicating which versions incorporate
-which upstream functionality.
-
-Prior to the ``4.0.0`` release, `another scheme
-`_
-was used to associate the CPython and backports releases.
-
-Maintenance
------------
-
-This backport was originally authored by Łukasz Langa, the current vanilla
-``configparser`` maintainer for CPython and is currently maintained by
-Jason R. Coombs:
-
-* `configparser repository `_
-
-* `configparser issue tracker `_
-
-For Enterprise
-==============
-
-Available as part of the Tidelift Subscription.
-
-This project and the maintainers of thousands of other packages are working with Tidelift to deliver one enterprise subscription that covers all of the open source you use.
-
-`Learn more `_.
-
-Security Contact
-----------------
-
-To report a security vulnerability, please use the
-`Tidelift security contact `_.
-Tidelift will coordinate the fix and disclosure.
-
-Conversion Process
-------------------
-
-This section is technical and should bother you only if you are wondering how
-this backport is produced. If the implementation details of this backport are
-not important for you, feel free to ignore the following content.
-
-The project takes the following branching approach:
-
-* The ``3.x`` branch holds unchanged files synchronized from the upstream
- CPython repository. The synchronization is currently done by manually copying
- the required files and stating from which CPython changeset they come.
-
-* The ``main`` branch holds a version of the ``3.x`` code with some tweaks
- that make it compatible with older Pythons. Code on this branch must work
- on all supported Python versions. Test with ``tox`` or in CI.
-
-The process works like this:
-
-1. In the ``3.x`` branch, run ``pip-run -- sync-upstream.py``, which
- downloads the latest stable release of Python and copies the relevant
- files from there into their new locations and then commits those
- changes with a nice reference to the relevant upstream commit hash.
-
-2. Check for new names in ``__all__`` and update imports in
- ``configparser.py`` accordingly. Optionally, run the tests on a late
- Python 3. Commit.
-
-3. Merge the new commit to ``main``. Run tests. Commit.
-
-4. Make any compatibility changes on ``main``. Run tests. Commit.
-
-5. Update the docs and release the new version.
-
-
-Footnotes
----------
-
-.. [1] To somewhat ease migration, passing bytestrings is still supported but
- they are converted to Unicode for internal storage anyway. This means
- that for the vast majority of strings used in configuration files, it
- won't matter if you pass them as bytestrings or Unicode. However, if you
- pass a bytestring that cannot be converted to Unicode using the naive
- ASCII codec, a ``UnicodeDecodeError`` will be raised. This is purposeful
- and helps you manage proper encoding for all content you store in
- memory, read from various sources and write back.
-
-.. [2] Life gets much easier when you understand that you basically manage
- **text** in your application. You don't care about bytes but about
- letters. In that regard the concept of content encoding is meaningless.
- The only time when you deal with raw bytes is when you write the data to
- a file. Then you have to specify how your text should be encoded. On
- the other end, to get meaningful text from a file, the application
- reading it has to know which encoding was used during its creation. But
- once the bytes are read and properly decoded, all you have is text. This
- is especially powerful when you start interacting with multiple data
- sources. Even if each of them uses a different encoding, inside your
- application data is held in abstract text form. You can program your
- business logic without worrying about which data came from which source.
- You can freely exchange the data you store between sources. Only
- reading/writing files requires encoding your text to bytes.
-
-
diff --git a/venv/Lib/site-packages/configparser-5.2.0.dist-info/RECORD b/venv/Lib/site-packages/configparser-5.2.0.dist-info/RECORD
deleted file mode 100644
index 87b0ea0..0000000
--- a/venv/Lib/site-packages/configparser-5.2.0.dist-info/RECORD
+++ /dev/null
@@ -1,13 +0,0 @@
-__pycache__/configparser.cpython-39.pyc,,
-backports/configparser/__init__.py,sha256=zjgAe9lH7_cTCyt7e-iyfyi007PfaMAVPhJCjM_TUPs,54655
-backports/configparser/__pycache__/__init__.cpython-39.pyc,,
-backports/configparser/__pycache__/compat.cpython-39.pyc,,
-backports/configparser/compat.py,sha256=Z3Fo6AI4BfFRfNd9Fj8n1Yw2NW2r112uQTFEnF659Lc,404
-configparser-5.2.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
-configparser-5.2.0.dist-info/LICENSE,sha256=2z8CRrH5J48VhFuZ_sR4uLUG63ZIeZNyL4xuJUKF-vg,1050
-configparser-5.2.0.dist-info/METADATA,sha256=4MeDipErpJIH61Zwu6BQ9f951Qu5HRg5RKFu3LYNZck,11053
-configparser-5.2.0.dist-info/RECORD,,
-configparser-5.2.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
-configparser-5.2.0.dist-info/WHEEL,sha256=ewwEueio1C2XeHTvT17n8dZUJgOvyCWCt0WVNLClP9o,92
-configparser-5.2.0.dist-info/top_level.txt,sha256=mIs8gajd7cvEWhVluv4u6ocaHw_TJ9rOrpkZEFv-7Hc,23
-configparser.py,sha256=4VADEswCwzy_RDVgvje3BmZhD6iwo3k4EkUZcgzLD4M,1546
diff --git a/venv/Lib/site-packages/configparser-5.2.0.dist-info/REQUESTED b/venv/Lib/site-packages/configparser-5.2.0.dist-info/REQUESTED
deleted file mode 100644
index e69de29..0000000
diff --git a/venv/Lib/site-packages/configparser-5.2.0.dist-info/WHEEL b/venv/Lib/site-packages/configparser-5.2.0.dist-info/WHEEL
deleted file mode 100644
index 5bad85f..0000000
--- a/venv/Lib/site-packages/configparser-5.2.0.dist-info/WHEEL
+++ /dev/null
@@ -1,5 +0,0 @@
-Wheel-Version: 1.0
-Generator: bdist_wheel (0.37.0)
-Root-Is-Purelib: true
-Tag: py3-none-any
-
diff --git a/venv/Lib/site-packages/configparser-5.2.0.dist-info/top_level.txt b/venv/Lib/site-packages/configparser-5.2.0.dist-info/top_level.txt
deleted file mode 100644
index a6cb03a..0000000
--- a/venv/Lib/site-packages/configparser-5.2.0.dist-info/top_level.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-backports
-configparser
diff --git a/venv/Lib/site-packages/configparser.py b/venv/Lib/site-packages/configparser.py
deleted file mode 100644
index 0a18360..0000000
--- a/venv/Lib/site-packages/configparser.py
+++ /dev/null
@@ -1,61 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-"""Convenience module importing everything from backports.configparser."""
-
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-from __future__ import unicode_literals
-
-from backports.configparser import (
- RawConfigParser,
- ConfigParser,
- SafeConfigParser,
- SectionProxy,
- Interpolation,
- BasicInterpolation,
- ExtendedInterpolation,
- LegacyInterpolation,
- NoSectionError,
- DuplicateSectionError,
- DuplicateOptionError,
- NoOptionError,
- InterpolationError,
- InterpolationMissingOptionError,
- InterpolationSyntaxError,
- InterpolationDepthError,
- ParsingError,
- MissingSectionHeaderError,
- ConverterMapping,
- DEFAULTSECT,
- MAX_INTERPOLATION_DEPTH,
-)
-
-from backports.configparser import Error, _UNSET, _default_dict, _ChainMap # noqa: F401
-
-__all__ = [
- "NoSectionError",
- "DuplicateOptionError",
- "DuplicateSectionError",
- "NoOptionError",
- "InterpolationError",
- "InterpolationDepthError",
- "InterpolationMissingOptionError",
- "InterpolationSyntaxError",
- "ParsingError",
- "MissingSectionHeaderError",
- "ConfigParser",
- "SafeConfigParser",
- "RawConfigParser",
- "Interpolation",
- "BasicInterpolation",
- "ExtendedInterpolation",
- "LegacyInterpolation",
- "SectionProxy",
- "ConverterMapping",
- "DEFAULTSECT",
- "MAX_INTERPOLATION_DEPTH",
-]
-
-# NOTE: names missing from __all__ imported anyway for backwards compatibility.
diff --git a/venv/Lib/site-packages/distutils-precedence.pth b/venv/Lib/site-packages/distutils-precedence.pth
deleted file mode 100644
index 7f009fe..0000000
--- a/venv/Lib/site-packages/distutils-precedence.pth
+++ /dev/null
@@ -1 +0,0 @@
-import os; var = 'SETUPTOOLS_USE_DISTUTILS'; enabled = os.environ.get(var, 'local') == 'local'; enabled and __import__('_distutils_hack').add_shim();
diff --git a/venv/Lib/site-packages/django/bin/django-admin.py b/venv/Lib/site-packages/django/bin/django-admin.py
deleted file mode 100644
index 594b0f1..0000000
--- a/venv/Lib/site-packages/django/bin/django-admin.py
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/usr/bin/env python
-# When the django-admin.py deprecation ends, remove this script.
-import warnings
-
-from django.core import management
-
-try:
- from django.utils.deprecation import RemovedInDjango40Warning
-except ImportError:
- raise ImportError(
- 'django-admin.py was deprecated in Django 3.1 and removed in Django '
- '4.0. Please manually remove this script from your virtual environment '
- 'and use django-admin instead.'
- )
-
-if __name__ == "__main__":
- warnings.warn(
- 'django-admin.py is deprecated in favor of django-admin.',
- RemovedInDjango40Warning,
- )
- management.execute_from_command_line()
diff --git a/venv/Lib/site-packages/django/contrib/postgres/forms/jsonb.py b/venv/Lib/site-packages/django/contrib/postgres/forms/jsonb.py
deleted file mode 100644
index ebc85ef..0000000
--- a/venv/Lib/site-packages/django/contrib/postgres/forms/jsonb.py
+++ /dev/null
@@ -1,16 +0,0 @@
-import warnings
-
-from django.forms import JSONField as BuiltinJSONField
-from django.utils.deprecation import RemovedInDjango40Warning
-
-__all__ = ['JSONField']
-
-
-class JSONField(BuiltinJSONField):
- def __init__(self, *args, **kwargs):
- warnings.warn(
- 'django.contrib.postgres.forms.JSONField is deprecated in favor '
- 'of django.forms.JSONField.',
- RemovedInDjango40Warning, stacklevel=2,
- )
- super().__init__(*args, **kwargs)
diff --git a/venv/Lib/site-packages/django/db/migrations/operations/utils.py b/venv/Lib/site-packages/django/db/migrations/operations/utils.py
deleted file mode 100644
index facfd9f..0000000
--- a/venv/Lib/site-packages/django/db/migrations/operations/utils.py
+++ /dev/null
@@ -1,102 +0,0 @@
-from collections import namedtuple
-
-from django.db.models.fields.related import RECURSIVE_RELATIONSHIP_CONSTANT
-
-
-def resolve_relation(model, app_label=None, model_name=None):
- """
- Turn a model class or model reference string and return a model tuple.
-
- app_label and model_name are used to resolve the scope of recursive and
- unscoped model relationship.
- """
- if isinstance(model, str):
- if model == RECURSIVE_RELATIONSHIP_CONSTANT:
- if app_label is None or model_name is None:
- raise TypeError(
- 'app_label and model_name must be provided to resolve '
- 'recursive relationships.'
- )
- return app_label, model_name
- if '.' in model:
- app_label, model_name = model.split('.', 1)
- return app_label, model_name.lower()
- if app_label is None:
- raise TypeError(
- 'app_label must be provided to resolve unscoped model '
- 'relationships.'
- )
- return app_label, model.lower()
- return model._meta.app_label, model._meta.model_name
-
-
-FieldReference = namedtuple('FieldReference', 'to through')
-
-
-def field_references(
- model_tuple,
- field,
- reference_model_tuple,
- reference_field_name=None,
- reference_field=None,
-):
- """
- Return either False or a FieldReference if `field` references provided
- context.
-
- False positives can be returned if `reference_field_name` is provided
- without `reference_field` because of the introspection limitation it
- incurs. This should not be an issue when this function is used to determine
- whether or not an optimization can take place.
- """
- remote_field = field.remote_field
- if not remote_field:
- return False
- references_to = None
- references_through = None
- if resolve_relation(remote_field.model, *model_tuple) == reference_model_tuple:
- to_fields = getattr(field, 'to_fields', None)
- if (
- reference_field_name is None or
- # Unspecified to_field(s).
- to_fields is None or
- # Reference to primary key.
- (None in to_fields and (reference_field is None or reference_field.primary_key)) or
- # Reference to field.
- reference_field_name in to_fields
- ):
- references_to = (remote_field, to_fields)
- through = getattr(remote_field, 'through', None)
- if through and resolve_relation(through, *model_tuple) == reference_model_tuple:
- through_fields = remote_field.through_fields
- if (
- reference_field_name is None or
- # Unspecified through_fields.
- through_fields is None or
- # Reference to field.
- reference_field_name in through_fields
- ):
- references_through = (remote_field, through_fields)
- if not (references_to or references_through):
- return False
- return FieldReference(references_to, references_through)
-
-
-def get_references(state, model_tuple, field_tuple=()):
- """
- Generator of (model_state, name, field, reference) referencing
- provided context.
-
- If field_tuple is provided only references to this particular field of
- model_tuple will be generated.
- """
- for state_model_tuple, model_state in state.models.items():
- for name, field in model_state.fields.items():
- reference = field_references(state_model_tuple, field, model_tuple, *field_tuple)
- if reference:
- yield model_state, name, field, reference
-
-
-def field_is_referenced(state, model_tuple, field_tuple):
- """Return whether `field_tuple` is referenced by any state models."""
- return next(get_references(state, model_tuple, field_tuple), None) is not None
diff --git a/venv/Lib/site-packages/django_filter-21.1.dist-info/INSTALLER b/venv/Lib/site-packages/django_filter-21.1.dist-info/INSTALLER
deleted file mode 100644
index a1b589e..0000000
--- a/venv/Lib/site-packages/django_filter-21.1.dist-info/INSTALLER
+++ /dev/null
@@ -1 +0,0 @@
-pip
diff --git a/venv/Lib/site-packages/django_filter-21.1.dist-info/LICENSE b/venv/Lib/site-packages/django_filter-21.1.dist-info/LICENSE
deleted file mode 100644
index 4b73093..0000000
--- a/venv/Lib/site-packages/django_filter-21.1.dist-info/LICENSE
+++ /dev/null
@@ -1,24 +0,0 @@
-Copyright (c) Alex Gaynor and individual contributors.
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
- * Redistributions of source code must retain the above copyright notice, this
- list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright notice,
- this list of conditions and the following disclaimer in the documentation
- and/or other materials provided with the distribution.
- * The names of its contributors may not be used to endorse or promote products
- derived from this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
-ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
-ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/venv/Lib/site-packages/django_filter-21.1.dist-info/METADATA b/venv/Lib/site-packages/django_filter-21.1.dist-info/METADATA
deleted file mode 100644
index 8f1b555..0000000
--- a/venv/Lib/site-packages/django_filter-21.1.dist-info/METADATA
+++ /dev/null
@@ -1,156 +0,0 @@
-Metadata-Version: 2.1
-Name: django-filter
-Version: 21.1
-Summary: Django-filter is a reusable Django application for allowing users to filter querysets dynamically.
-Home-page: https://github.com/carltongibson/django-filter/tree/main
-Author: Alex Gaynor
-Author-email: alex.gaynor@gmail.com
-Maintainer: Carlton Gibson
-Maintainer-email: carlton.gibson@noumenal.es
-License: BSD
-Project-URL: Documentation, https://django-filter.readthedocs.io/en/main/
-Project-URL: Changelog, https://github.com/carltongibson/django-filter/blob/main/CHANGES.rst
-Project-URL: Bug Tracker, https://github.com/carltongibson/django-filter/issues
-Project-URL: Source Code, https://github.com/carltongibson/django-filter
-Platform: UNKNOWN
-Classifier: Development Status :: 5 - Production/Stable
-Classifier: Environment :: Web Environment
-Classifier: Intended Audience :: Developers
-Classifier: License :: OSI Approved :: BSD License
-Classifier: Operating System :: OS Independent
-Classifier: Framework :: Django
-Classifier: Framework :: Django :: 2.2
-Classifier: Framework :: Django :: 3.1
-Classifier: Framework :: Django :: 3.2
-Classifier: Framework :: Django :: 4.0
-Classifier: Programming Language :: Python
-Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.6
-Classifier: Programming Language :: Python :: 3.7
-Classifier: Programming Language :: Python :: 3.8
-Classifier: Programming Language :: Python :: 3.9
-Requires-Python: >=3.6
-License-File: LICENSE
-Requires-Dist: Django (>=2.2)
-
-Django Filter
-=============
-
-Django-filter is a reusable Django application allowing users to declaratively
-add dynamic ``QuerySet`` filtering from URL parameters.
-
-Full documentation on `read the docs`_.
-
-.. image:: https://codecov.io/gh/carltongibson/django-filter/branch/develop/graph/badge.svg
- :target: https://codecov.io/gh/carltongibson/django-filter
-
-.. image:: https://badge.fury.io/py/django-filter.svg
- :target: http://badge.fury.io/py/django-filter
-
-
-Versioning and stability policy
--------------------------------
-
-Django-Filter is a mature and stable package. It uses a two-part CalVer
-versioning scheme, such as ``21.1``. The first number is the year. The second
-is the release number within that year.
-
-On an on-going basis, Django-Filter aims to support all current Django
-versions, the matching current Python versions, and the latest version of
-Django REST Framework.
-
-Please see:
-
-* `Status of supported Python branches `_
-* `List of supported Django versions `_
-
-Support for Python and Django versions will be dropped when they reach
-end-of-life. Support for Python versions will dropped when they reach
-end-of-life, even when still supported by a current version of Django.
-
-Other breaking changes are rare. Where required, every effort will be made to
-apply a "Year plus two" deprecation period. For example, a change initially
-introduced in ``23.x`` would offer a fallback where feasible and finally be
-removed in ``25.1``. Where fallbacks are not feasible, breaking changes without
-deprecation will be called out in the release notes.
-
-
-Installation
-------------
-
-Install using pip:
-
-.. code-block:: sh
-
- pip install django-filter
-
-Then add ``'django_filters'`` to your ``INSTALLED_APPS``.
-
-.. code-block:: python
-
- INSTALLED_APPS = [
- ...
- 'django_filters',
- ]
-
-
-Usage
------
-
-Django-filter can be used for generating interfaces similar to the Django
-admin's ``list_filter`` interface. It has an API very similar to Django's
-``ModelForms``. For example, if you had a Product model you could have a
-filterset for it with the code:
-
-.. code-block:: python
-
- import django_filters
-
- class ProductFilter(django_filters.FilterSet):
- class Meta:
- model = Product
- fields = ['name', 'price', 'manufacturer']
-
-
-And then in your view you could do:
-
-.. code-block:: python
-
- def product_list(request):
- filter = ProductFilter(request.GET, queryset=Product.objects.all())
- return render(request, 'my_app/template.html', {'filter': filter})
-
-
-Usage with Django REST Framework
---------------------------------
-
-Django-filter provides a custom ``FilterSet`` and filter backend for use with
-Django REST Framework.
-
-To use this adjust your import to use
-``django_filters.rest_framework.FilterSet``.
-
-.. code-block:: python
-
- from django_filters import rest_framework as filters
-
- class ProductFilter(filters.FilterSet):
- class Meta:
- model = Product
- fields = ('category', 'in_stock')
-
-
-For more details see the `DRF integration docs`_.
-
-
-Support
--------
-
-If you need help you can start a `discussion`_. For commercial support, please
-`contact Carlton Gibson via his website `_.
-
-.. _`discussion`: https://github.com/carltongibson/django-filter/discussions
-.. _`read the docs`: https://django-filter.readthedocs.io/en/main/
-.. _`DRF integration docs`: https://django-filter.readthedocs.io/en/stable/guide/rest_framework.html
-
-
diff --git a/venv/Lib/site-packages/django_filter-21.1.dist-info/RECORD b/venv/Lib/site-packages/django_filter-21.1.dist-info/RECORD
deleted file mode 100644
index f738f3e..0000000
--- a/venv/Lib/site-packages/django_filter-21.1.dist-info/RECORD
+++ /dev/null
@@ -1,74 +0,0 @@
-django_filter-21.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
-django_filter-21.1.dist-info/LICENSE,sha256=4UQ8qx2nFmTo4lASXOByK3RcVWDurx7_w9HozSy9mAI,1487
-django_filter-21.1.dist-info/METADATA,sha256=D5IHYcqiWsyxrQggFmRvz3-ncMtjEMdMWfhF1pXOq94,5097
-django_filter-21.1.dist-info/RECORD,,
-django_filter-21.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
-django_filter-21.1.dist-info/WHEEL,sha256=ewwEueio1C2XeHTvT17n8dZUJgOvyCWCt0WVNLClP9o,92
-django_filter-21.1.dist-info/top_level.txt,sha256=JVS5v6IKT4Q2Sqv3pRcYCkKZoSdRlH2KalrhTuAenas,15
-django_filters/__init__.py,sha256=x17jQnKLjLiVc0KXqMzaMOiONzH-Krz3WLSPDXA356g,643
-django_filters/__pycache__/__init__.cpython-39.pyc,,
-django_filters/__pycache__/compat.cpython-39.pyc,,
-django_filters/__pycache__/conf.cpython-39.pyc,,
-django_filters/__pycache__/constants.cpython-39.pyc,,
-django_filters/__pycache__/exceptions.cpython-39.pyc,,
-django_filters/__pycache__/fields.cpython-39.pyc,,
-django_filters/__pycache__/filters.cpython-39.pyc,,
-django_filters/__pycache__/filterset.cpython-39.pyc,,
-django_filters/__pycache__/utils.cpython-39.pyc,,
-django_filters/__pycache__/views.cpython-39.pyc,,
-django_filters/__pycache__/widgets.cpython-39.pyc,,
-django_filters/compat.py,sha256=5GeZj6vzL-B56z_KGF4MJq9GkxmNHX_xxGLuPjmqwIQ,545
-django_filters/conf.py,sha256=hfmpXUSr-SSO9kIl4d2KOCyyn7XtooCXmTR3-JqCtK4,3056
-django_filters/constants.py,sha256=LbxdUwNFU2QD8N2XT2Hqcog418gEJk5Ike_Pm08ieOw,64
-django_filters/exceptions.py,sha256=NHK-xlz1XmC_78yNI5-lH1kjwVMb-zrkSegU2Ga8-BY,254
-django_filters/fields.py,sha256=WdnyaoYs6_JmM0sOYnn89xTYLHGgbp_i4A5R_qLIKtQ,9892
-django_filters/filters.py,sha256=uODlEmDv571EqXUHh8Tr_thcus_rAzWbiQlgKrY8F_U,24875
-django_filters/filterset.py,sha256=FBj9WubrBMhYmlsUOtTqJoZLBm69FSOa7URGIzeoNHA,16523
-django_filters/locale/ar/LC_MESSAGES/django.mo,sha256=utzbP4BsdW91KwGgFwyvXVY1uNZ8otdcUDoZZpIZ9Pg,2568
-django_filters/locale/ar/LC_MESSAGES/django.po,sha256=UaO-74kymS4qUcCkGn4VgcoQOsFXoyat40_xT99mT2g,3621
-django_filters/locale/be/LC_MESSAGES/django.mo,sha256=lbp-b9nTHDvBb8ozSkyHWGlmi4X3WyKaObT9GB2fe9E,2819
-django_filters/locale/be/LC_MESSAGES/django.po,sha256=f6SF5hE7bSWaPNBkydB0T55APL4TmlEY7vDlk9bAVL8,3651
-django_filters/locale/bg/LC_MESSAGES/django.mo,sha256=ZPmu82dqvj3yd3-J0KLK-hxfwETzqKmq0c-Anozn5Go,2711
-django_filters/locale/bg/LC_MESSAGES/django.po,sha256=zUPOML-VeE0V-bnW4BNmhL9pewlwkLKfh1MXr8lH2J8,3736
-django_filters/locale/cs/LC_MESSAGES/django.mo,sha256=vZuyiklIF_I3qs9pdhb3OTT2d63aIttgtcHY1b9Gsps,2368
-django_filters/locale/cs/LC_MESSAGES/django.po,sha256=D_4W8R8y5s5x7Hd2xz-Y39zvlN2fr7Df2uCCJ_W-sUw,3144
-django_filters/locale/da/LC_MESSAGES/django.mo,sha256=gPy5CaNJWYbCPqeqb6XPr1uynW9FEn8zV_-85RMJaZc,2166
-django_filters/locale/da/LC_MESSAGES/django.po,sha256=5aCGYuKedqszhcntOw1OUF0_cUtGoXzZB9E1nOoOAnI,3037
-django_filters/locale/de/LC_MESSAGES/django.mo,sha256=IvgqQ0BQ7AiJSmdcGpKWheuLrzrXqs-lbp4Bac2jOdI,2277
-django_filters/locale/de/LC_MESSAGES/django.po,sha256=rbAvo_wdbA494qusvkRGESF4E77jizPafnZhgo9s2aw,3293
-django_filters/locale/el/LC_MESSAGES/django.mo,sha256=2--juTiXF9v6u95krY9VwZCv2cXoJai6CXi4RWpi39w,2836
-django_filters/locale/el/LC_MESSAGES/django.po,sha256=i8h_UnGeVEYAZNXDr6KMqalpq8g3hj6kbtEMcFoqgWE,3966
-django_filters/locale/es/LC_MESSAGES/django.mo,sha256=5KCl_uUwge5RuGStcyMSsVPD6AOunjNvjuE-32PqWis,2279
-django_filters/locale/es/LC_MESSAGES/django.po,sha256=tP1Zuzgz0v84c74PZRy6DuiFyRkN7NOTlMQigEhXL94,3293
-django_filters/locale/es_AR/LC_MESSAGES/django.mo,sha256=OCKAVbT3ct5gf2_t5XsKryjlkIQDYZjC67Oz0j-YE6s,703
-django_filters/locale/es_AR/LC_MESSAGES/django.po,sha256=jI7WMhsSWbTZ7mnLSzB4lsloohr-TtxxPdkNComOZHc,1015
-django_filters/locale/fr/LC_MESSAGES/django.mo,sha256=Bb39Mt6ocOXlcYvT3Om7xMLGNtHEKpzNPQzw7hXqsBE,727
-django_filters/locale/fr/LC_MESSAGES/django.po,sha256=JT34o_10l5Gxyqe5nqDWy8m1TKBsatTTuDE6AqlOhdw,1067
-django_filters/locale/it/LC_MESSAGES/django.mo,sha256=TKIdnZSuYtyCpnl8X9jDyKFuIX6G69CmCvVaWpcuPXM,2268
-django_filters/locale/it/LC_MESSAGES/django.po,sha256=Yo0WtfBI56qq8XWe0QGjYPOw7RexbyJtVRARROYUoYg,3209
-django_filters/locale/pl/LC_MESSAGES/django.mo,sha256=-9taafe4N3mKLdZ4fEXkrj-azO-L4F0fGoxnDgTBuwU,1859
-django_filters/locale/pl/LC_MESSAGES/django.po,sha256=SUzt2qncjYnCmkbgac1BxBz6ZrXhQh2MADSwioCYwCE,3607
-django_filters/locale/pt_BR/LC_MESSAGES/django.mo,sha256=GLakV-03XUsCNKaofuG2fGCBIRGVYEMJiC-kD1UX4D0,2263
-django_filters/locale/pt_BR/LC_MESSAGES/django.po,sha256=R955ohil0dtXI0HU54PhdCalMwb5x4iM2f33a4IHRyg,3217
-django_filters/locale/ru/LC_MESSAGES/django.mo,sha256=1KrtkfLhq0BiDskKFffF5i53pM7Tp-bwsbPDe9F4Co0,2796
-django_filters/locale/ru/LC_MESSAGES/django.po,sha256=ut8nJ7xfrVekgvgHvgTJwQu1CbcVjTHl8gQSuy5yhH8,3818
-django_filters/locale/sk/LC_MESSAGES/django.mo,sha256=em13cqJIPA3JLTp6JXPXuNNeDqJ7uaEuxxqtOvl9PLk,2394
-django_filters/locale/sk/LC_MESSAGES/django.po,sha256=1yaXj0PaV8Ik_SJAA0Wm95GpkYAruZGbIh8rNCkFItw,3386
-django_filters/locale/uk/LC_MESSAGES/django.mo,sha256=zgC01vyDPPS81GiD3C4WeQxtCt4_ift_pU-j_2l_LrU,2912
-django_filters/locale/uk/LC_MESSAGES/django.po,sha256=eX_FYkXmRcVZKYtBoNlZ12fHg8U1FhJ0lAelfI7PcsA,3694
-django_filters/locale/zh_CN/LC_MESSAGES/django.mo,sha256=2aSG7Whwpj7iRY_7QcTV-ReuCm8JKsV-ktlRaAbYC0U,852
-django_filters/locale/zh_CN/LC_MESSAGES/django.po,sha256=9Kj2VQ9TuPgAeQdauY5zR0wOZpkCL1L-GMUpm8fnxT4,1305
-django_filters/rest_framework/__init__.py,sha256=HpNAGIdsBRJSkyM1QmqyOTb7I9VVwoMTbexbD21X6vE,113
-django_filters/rest_framework/__pycache__/__init__.cpython-39.pyc,,
-django_filters/rest_framework/__pycache__/backends.cpython-39.pyc,,
-django_filters/rest_framework/__pycache__/filters.cpython-39.pyc,,
-django_filters/rest_framework/__pycache__/filterset.cpython-39.pyc,,
-django_filters/rest_framework/backends.py,sha256=3XIwJkguiioY4BWdjzcZJIHgN-HUzbNGS-sUP9-lqDo,6182
-django_filters/rest_framework/filters.py,sha256=DXDAE1--_os5SvoFic5VxVfIHvAzidm1Del9A0NCSSA,312
-django_filters/rest_framework/filterset.py,sha256=SHr213z6vpLybwpc9cN8dROBzeGU3Lc2RMhB2Gn--Gs,1200
-django_filters/templates/django_filters/rest_framework/crispy_form.html,sha256=_Mg40d_4sWAuy7_Mzf1HRACbRgeheu0pGXy2UKpzd3s,108
-django_filters/templates/django_filters/rest_framework/form.html,sha256=KoVGtezI-pWnC18jpCKy3vufR23QLpXXooCgmEFXjAA,211
-django_filters/templates/django_filters/widgets/multiwidget.html,sha256=W0RT7BL9-sF-hCA_Ut4MfWaDwE8Z32syJs3anyurceg,118
-django_filters/utils.py,sha256=Gql2Bq2Q7Os6xiASVe_FVizv0PhoaGqYhs7wWGkrQIw,10508
-django_filters/views.py,sha256=xMs37as1DHqM4l279kDs7IBJJKegw3vaddqqGcIjTDo,4181
-django_filters/widgets.py,sha256=wQv7b03h3rAHZFmAMxSy349paRB1WbUu5Vjcv6K1aEI,9126
diff --git a/venv/Lib/site-packages/django_filter-21.1.dist-info/REQUESTED b/venv/Lib/site-packages/django_filter-21.1.dist-info/REQUESTED
deleted file mode 100644
index e69de29..0000000
diff --git a/venv/Lib/site-packages/django_filter-21.1.dist-info/WHEEL b/venv/Lib/site-packages/django_filter-21.1.dist-info/WHEEL
deleted file mode 100644
index 5bad85f..0000000
--- a/venv/Lib/site-packages/django_filter-21.1.dist-info/WHEEL
+++ /dev/null
@@ -1,5 +0,0 @@
-Wheel-Version: 1.0
-Generator: bdist_wheel (0.37.0)
-Root-Is-Purelib: true
-Tag: py3-none-any
-
diff --git a/venv/Lib/site-packages/django_filter-21.1.dist-info/top_level.txt b/venv/Lib/site-packages/django_filter-21.1.dist-info/top_level.txt
deleted file mode 100644
index d34ce38..0000000
--- a/venv/Lib/site-packages/django_filter-21.1.dist-info/top_level.txt
+++ /dev/null
@@ -1 +0,0 @@
-django_filters
diff --git a/venv/Lib/site-packages/django_filters/__init__.py b/venv/Lib/site-packages/django_filters/__init__.py
deleted file mode 100644
index 1a9aead..0000000
--- a/venv/Lib/site-packages/django_filters/__init__.py
+++ /dev/null
@@ -1,30 +0,0 @@
-# flake8: noqa
-import pkgutil
-
-from .filters import *
-from .filterset import FilterSet
-
-# We make the `rest_framework` module available without an additional import.
-# If DRF is not installed, no-op.
-if pkgutil.find_loader('rest_framework') is not None:
- from . import rest_framework
-del pkgutil
-
-__version__ = '21.1'
-
-
-def parse_version(version):
- '''
- '0.1.2.dev1' -> (0, 1, 2, 'dev1')
- '0.1.2' -> (0, 1, 2)
- '''
- v = version.split('.')
- ret = []
- for p in v:
- if p.isdigit():
- ret.append(int(p))
- else:
- ret.append(p)
- return tuple(ret)
-
-VERSION = parse_version(__version__)
diff --git a/venv/Lib/site-packages/django_filters/compat.py b/venv/Lib/site-packages/django_filters/compat.py
deleted file mode 100644
index fcaa9e9..0000000
--- a/venv/Lib/site-packages/django_filters/compat.py
+++ /dev/null
@@ -1,25 +0,0 @@
-from django.conf import settings
-
-# django-crispy-forms is optional
-try:
- import crispy_forms
-except ImportError:
- crispy_forms = None
-
-
-def is_crispy():
- return 'crispy_forms' in settings.INSTALLED_APPS and crispy_forms
-
-
-# coreapi is optional (Note that uritemplate is a dependency of coreapi)
-# Fixes #525 - cannot simply import from rest_framework.compat, due to
-# import issues w/ django-guardian.
-try:
- import coreapi
-except ImportError:
- coreapi = None
-
-try:
- import coreschema
-except ImportError:
- coreschema = None
diff --git a/venv/Lib/site-packages/django_filters/conf.py b/venv/Lib/site-packages/django_filters/conf.py
deleted file mode 100644
index 6ca1767..0000000
--- a/venv/Lib/site-packages/django_filters/conf.py
+++ /dev/null
@@ -1,109 +0,0 @@
-from django.conf import settings as dj_settings
-from django.core.signals import setting_changed
-from django.utils.translation import gettext_lazy as _
-
-from .utils import deprecate
-
-DEFAULTS = {
- 'DISABLE_HELP_TEXT': False,
-
- 'DEFAULT_LOOKUP_EXPR': 'exact',
-
- # empty/null choices
- 'EMPTY_CHOICE_LABEL': '---------',
- 'NULL_CHOICE_LABEL': None,
- 'NULL_CHOICE_VALUE': 'null',
-
- 'VERBOSE_LOOKUPS': {
- # transforms don't need to be verbose, since their expressions are chained
- 'date': _('date'),
- 'year': _('year'),
- 'month': _('month'),
- 'day': _('day'),
- 'week_day': _('week day'),
- 'hour': _('hour'),
- 'minute': _('minute'),
- 'second': _('second'),
-
- # standard lookups
- 'exact': '',
- 'iexact': '',
- 'contains': _('contains'),
- 'icontains': _('contains'),
- 'in': _('is in'),
- 'gt': _('is greater than'),
- 'gte': _('is greater than or equal to'),
- 'lt': _('is less than'),
- 'lte': _('is less than or equal to'),
- 'startswith': _('starts with'),
- 'istartswith': _('starts with'),
- 'endswith': _('ends with'),
- 'iendswith': _('ends with'),
- 'range': _('is in range'),
- 'isnull': _('is null'),
- 'regex': _('matches regex'),
- 'iregex': _('matches regex'),
- 'search': _('search'),
-
- # postgres lookups
- 'contained_by': _('is contained by'),
- 'overlap': _('overlaps'),
- 'has_key': _('has key'),
- 'has_keys': _('has keys'),
- 'has_any_keys': _('has any keys'),
- 'trigram_similar': _('search'),
- },
-}
-
-
-DEPRECATED_SETTINGS = [
-]
-
-
-def is_callable(value):
- # check for callables, except types
- return callable(value) and not isinstance(value, type)
-
-
-class Settings:
-
- def __getattr__(self, name):
- if name not in DEFAULTS:
- msg = "'%s' object has no attribute '%s'"
- raise AttributeError(msg % (self.__class__.__name__, name))
-
- value = self.get_setting(name)
-
- if is_callable(value):
- value = value()
-
- # Cache the result
- setattr(self, name, value)
- return value
-
- def get_setting(self, setting):
- django_setting = 'FILTERS_%s' % setting
-
- if setting in DEPRECATED_SETTINGS and hasattr(dj_settings, django_setting):
- deprecate("The '%s' setting has been deprecated." % django_setting)
-
- return getattr(dj_settings, django_setting, DEFAULTS[setting])
-
- def change_setting(self, setting, value, enter, **kwargs):
- if not setting.startswith('FILTERS_'):
- return
- setting = setting[8:] # strip 'FILTERS_'
-
- # ensure a valid app setting is being overridden
- if setting not in DEFAULTS:
- return
-
- # if exiting, delete value to repopulate
- if enter:
- setattr(self, setting, value)
- else:
- delattr(self, setting)
-
-
-settings = Settings()
-setting_changed.connect(settings.change_setting)
diff --git a/venv/Lib/site-packages/django_filters/constants.py b/venv/Lib/site-packages/django_filters/constants.py
deleted file mode 100644
index 795d6cc..0000000
--- a/venv/Lib/site-packages/django_filters/constants.py
+++ /dev/null
@@ -1,5 +0,0 @@
-
-ALL_FIELDS = '__all__'
-
-
-EMPTY_VALUES = ([], (), {}, '', None)
diff --git a/venv/Lib/site-packages/django_filters/exceptions.py b/venv/Lib/site-packages/django_filters/exceptions.py
deleted file mode 100644
index 1d79e4d..0000000
--- a/venv/Lib/site-packages/django_filters/exceptions.py
+++ /dev/null
@@ -1,9 +0,0 @@
-
-from django.core.exceptions import FieldError
-
-
-class FieldLookupError(FieldError):
- def __init__(self, model_field, lookup_expr):
- super().__init__(
- "Unsupported lookup '%s' for field '%s'." % (lookup_expr, model_field)
- )
diff --git a/venv/Lib/site-packages/django_filters/fields.py b/venv/Lib/site-packages/django_filters/fields.py
deleted file mode 100644
index 13c9df2..0000000
--- a/venv/Lib/site-packages/django_filters/fields.py
+++ /dev/null
@@ -1,312 +0,0 @@
-from collections import namedtuple
-from datetime import datetime, time
-
-from django import forms
-from django.utils.dateparse import parse_datetime
-from django.utils.encoding import force_str
-from django.utils.translation import gettext_lazy as _
-
-from .conf import settings
-from .constants import EMPTY_VALUES
-from .utils import handle_timezone
-from .widgets import (
- BaseCSVWidget,
- CSVWidget,
- DateRangeWidget,
- LookupChoiceWidget,
- RangeWidget
-)
-
-
-class RangeField(forms.MultiValueField):
- widget = RangeWidget
-
- def __init__(self, fields=None, *args, **kwargs):
- if fields is None:
- fields = (
- forms.DecimalField(),
- forms.DecimalField())
- super().__init__(fields, *args, **kwargs)
-
- def compress(self, data_list):
- if data_list:
- return slice(*data_list)
- return None
-
-
-class DateRangeField(RangeField):
- widget = DateRangeWidget
-
- def __init__(self, *args, **kwargs):
- fields = (
- forms.DateField(),
- forms.DateField())
- super().__init__(fields, *args, **kwargs)
-
- def compress(self, data_list):
- if data_list:
- start_date, stop_date = data_list
- if start_date:
- start_date = handle_timezone(
- datetime.combine(start_date, time.min),
- False
- )
- if stop_date:
- stop_date = handle_timezone(
- datetime.combine(stop_date, time.max),
- False
- )
- return slice(start_date, stop_date)
- return None
-
-
-class DateTimeRangeField(RangeField):
- widget = DateRangeWidget
-
- def __init__(self, *args, **kwargs):
- fields = (
- forms.DateTimeField(),
- forms.DateTimeField())
- super().__init__(fields, *args, **kwargs)
-
-
-class IsoDateTimeRangeField(RangeField):
- widget = DateRangeWidget
-
- def __init__(self, *args, **kwargs):
- fields = (
- IsoDateTimeField(),
- IsoDateTimeField())
- super().__init__(fields, *args, **kwargs)
-
-
-class TimeRangeField(RangeField):
- widget = DateRangeWidget
-
- def __init__(self, *args, **kwargs):
- fields = (
- forms.TimeField(),
- forms.TimeField())
- super().__init__(fields, *args, **kwargs)
-
-
-class Lookup(namedtuple('Lookup', ('value', 'lookup_expr'))):
- def __new__(cls, value, lookup_expr):
- if value in EMPTY_VALUES or lookup_expr in EMPTY_VALUES:
- raise ValueError(
- "Empty values ([], (), {}, '', None) are not "
- "valid Lookup arguments. Return None instead."
- )
-
- return super().__new__(cls, value, lookup_expr)
-
-
-class LookupChoiceField(forms.MultiValueField):
- default_error_messages = {
- 'lookup_required': _('Select a lookup.'),
- }
-
- def __init__(self, field, lookup_choices, *args, **kwargs):
- empty_label = kwargs.pop('empty_label', settings.EMPTY_CHOICE_LABEL)
- fields = (field, ChoiceField(choices=lookup_choices, empty_label=empty_label))
- widget = LookupChoiceWidget(widgets=[f.widget for f in fields])
- kwargs['widget'] = widget
- kwargs['help_text'] = field.help_text
- super().__init__(fields, *args, **kwargs)
-
- def compress(self, data_list):
- if len(data_list) == 2:
- value, lookup_expr = data_list
- if value not in EMPTY_VALUES:
- if lookup_expr not in EMPTY_VALUES:
- return Lookup(value=value, lookup_expr=lookup_expr)
- else:
- raise forms.ValidationError(
- self.error_messages['lookup_required'],
- code='lookup_required')
- return None
-
-
-class IsoDateTimeField(forms.DateTimeField):
- """
- Supports 'iso-8601' date format too which is out the scope of
- the ``datetime.strptime`` standard library
-
- # ISO 8601: ``http://www.w3.org/TR/NOTE-datetime``
-
- Based on Gist example by David Medina https://gist.github.com/copitux/5773821
- """
- ISO_8601 = 'iso-8601'
- input_formats = [ISO_8601]
-
- def strptime(self, value, format):
- value = force_str(value)
-
- if format == self.ISO_8601:
- parsed = parse_datetime(value)
- if parsed is None: # Continue with other formats if doesn't match
- raise ValueError
- return handle_timezone(parsed)
- return super().strptime(value, format)
-
-
-class BaseCSVField(forms.Field):
- """
- Base field for validating CSV types. Value validation is performed by
- secondary base classes.
-
- ex::
- class IntegerCSVField(BaseCSVField, filters.IntegerField):
- pass
-
- """
- base_widget_class = BaseCSVWidget
-
- def __init__(self, *args, **kwargs):
- widget = kwargs.get('widget') or self.widget
- kwargs['widget'] = self._get_widget_class(widget)
-
- super().__init__(*args, **kwargs)
-
- def _get_widget_class(self, widget):
- # passthrough, allows for override
- if isinstance(widget, BaseCSVWidget) or (
- isinstance(widget, type) and
- issubclass(widget, BaseCSVWidget)):
- return widget
-
- # complain since we are unable to reconstruct widget instances
- assert isinstance(widget, type), \
- "'%s.widget' must be a widget class, not %s." \
- % (self.__class__.__name__, repr(widget))
-
- bases = (self.base_widget_class, widget, )
- return type(str('CSV%s' % widget.__name__), bases, {})
-
- def clean(self, value):
- if value in self.empty_values and self.required:
- raise forms.ValidationError(self.error_messages['required'], code='required')
-
- if value is None:
- return None
- return [super(BaseCSVField, self).clean(v) for v in value]
-
-
-class BaseRangeField(BaseCSVField):
- # Force use of text input, as range must always have two inputs. A date
- # input would only allow a user to input one value and would always fail.
- widget = CSVWidget
-
- default_error_messages = {
- 'invalid_values': _('Range query expects two values.')
- }
-
- def clean(self, value):
- value = super().clean(value)
-
- assert value is None or isinstance(value, list)
-
- if value and len(value) != 2:
- raise forms.ValidationError(
- self.error_messages['invalid_values'],
- code='invalid_values')
-
- return value
-
-
-class ChoiceIterator:
- # Emulates the behavior of ModelChoiceIterator, but instead wraps
- # the field's _choices iterable.
-
- def __init__(self, field, choices):
- self.field = field
- self.choices = choices
-
- def __iter__(self):
- if self.field.empty_label is not None:
- yield ("", self.field.empty_label)
- if self.field.null_label is not None:
- yield (self.field.null_value, self.field.null_label)
- yield from self.choices
-
- def __len__(self):
- add = 1 if self.field.empty_label is not None else 0
- add += 1 if self.field.null_label is not None else 0
- return len(self.choices) + add
-
-
-class ModelChoiceIterator(forms.models.ModelChoiceIterator):
- # Extends the base ModelChoiceIterator to add in 'null' choice handling.
- # This is a bit verbose since we have to insert the null choice after the
- # empty choice, but before the remainder of the choices.
-
- def __iter__(self):
- iterable = super().__iter__()
-
- if self.field.empty_label is not None:
- yield next(iterable)
- if self.field.null_label is not None:
- yield (self.field.null_value, self.field.null_label)
- yield from iterable
-
- def __len__(self):
- add = 1 if self.field.null_label is not None else 0
- return super().__len__() + add
-
-
-class ChoiceIteratorMixin:
- def __init__(self, *args, **kwargs):
- self.null_label = kwargs.pop('null_label', settings.NULL_CHOICE_LABEL)
- self.null_value = kwargs.pop('null_value', settings.NULL_CHOICE_VALUE)
-
- super().__init__(*args, **kwargs)
-
- def _get_choices(self):
- return super()._get_choices()
-
- def _set_choices(self, value):
- super()._set_choices(value)
- value = self.iterator(self, self._choices)
-
- self._choices = self.widget.choices = value
- choices = property(_get_choices, _set_choices)
-
-
-# Unlike their Model* counterparts, forms.ChoiceField and forms.MultipleChoiceField do not set empty_label
-class ChoiceField(ChoiceIteratorMixin, forms.ChoiceField):
- iterator = ChoiceIterator
-
- def __init__(self, *args, **kwargs):
- self.empty_label = kwargs.pop('empty_label', settings.EMPTY_CHOICE_LABEL)
- super().__init__(*args, **kwargs)
-
-
-class MultipleChoiceField(ChoiceIteratorMixin, forms.MultipleChoiceField):
- iterator = ChoiceIterator
-
- def __init__(self, *args, **kwargs):
- self.empty_label = None
- super().__init__(*args, **kwargs)
-
-
-class ModelChoiceField(ChoiceIteratorMixin, forms.ModelChoiceField):
- iterator = ModelChoiceIterator
-
- def to_python(self, value):
- # bypass the queryset value check
- if self.null_label is not None and value == self.null_value:
- return value
- return super().to_python(value)
-
-
-class ModelMultipleChoiceField(ChoiceIteratorMixin, forms.ModelMultipleChoiceField):
- iterator = ModelChoiceIterator
-
- def _check_values(self, value):
- null = self.null_label is not None and value and self.null_value in value
- if null: # remove the null value and any potential duplicates
- value = [v for v in value if v != self.null_value]
-
- result = list(super()._check_values(value))
- result += [self.null_value] if null else []
- return result
diff --git a/venv/Lib/site-packages/django_filters/filters.py b/venv/Lib/site-packages/django_filters/filters.py
deleted file mode 100644
index 4f6ebe3..0000000
--- a/venv/Lib/site-packages/django_filters/filters.py
+++ /dev/null
@@ -1,804 +0,0 @@
-from collections import OrderedDict
-from datetime import timedelta
-
-from django import forms
-from django.core.validators import MaxValueValidator
-from django.db.models import Q
-from django.db.models.constants import LOOKUP_SEP
-from django.forms.utils import pretty_name
-from django.utils.itercompat import is_iterable
-from django.utils.timezone import now
-from django.utils.translation import gettext_lazy as _
-
-from .conf import settings
-from .constants import EMPTY_VALUES
-from .fields import (
- BaseCSVField,
- BaseRangeField,
- ChoiceField,
- DateRangeField,
- DateTimeRangeField,
- IsoDateTimeField,
- IsoDateTimeRangeField,
- LookupChoiceField,
- ModelChoiceField,
- ModelMultipleChoiceField,
- MultipleChoiceField,
- RangeField,
- TimeRangeField
-)
-from .utils import get_model_field, label_for_filter
-
-__all__ = [
- 'AllValuesFilter',
- 'AllValuesMultipleFilter',
- 'BaseCSVFilter',
- 'BaseInFilter',
- 'BaseRangeFilter',
- 'BooleanFilter',
- 'CharFilter',
- 'ChoiceFilter',
- 'DateFilter',
- 'DateFromToRangeFilter',
- 'DateRangeFilter',
- 'DateTimeFilter',
- 'DateTimeFromToRangeFilter',
- 'DurationFilter',
- 'Filter',
- 'IsoDateTimeFilter',
- 'IsoDateTimeFromToRangeFilter',
- 'LookupChoiceFilter',
- 'ModelChoiceFilter',
- 'ModelMultipleChoiceFilter',
- 'MultipleChoiceFilter',
- 'NumberFilter',
- 'NumericRangeFilter',
- 'OrderingFilter',
- 'RangeFilter',
- 'TimeFilter',
- 'TimeRangeFilter',
- 'TypedChoiceFilter',
- 'TypedMultipleChoiceFilter',
- 'UUIDFilter',
-]
-
-
-class Filter:
- creation_counter = 0
- field_class = forms.Field
-
- def __init__(self, field_name=None, lookup_expr=None, *, label=None,
- method=None, distinct=False, exclude=False, **kwargs):
- if lookup_expr is None:
- lookup_expr = settings.DEFAULT_LOOKUP_EXPR
- self.field_name = field_name
- self.lookup_expr = lookup_expr
- self.label = label
- self.method = method
- self.distinct = distinct
- self.exclude = exclude
-
- self.extra = kwargs
- self.extra.setdefault('required', False)
-
- self.creation_counter = Filter.creation_counter
- Filter.creation_counter += 1
-
- def get_method(self, qs):
- """Return filter method based on whether we're excluding
- or simply filtering.
- """
- return qs.exclude if self.exclude else qs.filter
-
- def method():
- """
- Filter method needs to be lazily resolved, as it may be dependent on
- the 'parent' FilterSet.
- """
- def fget(self):
- return self._method
-
- def fset(self, value):
- self._method = value
-
- # clear existing FilterMethod
- if isinstance(self.filter, FilterMethod):
- del self.filter
-
- # override filter w/ FilterMethod.
- if value is not None:
- self.filter = FilterMethod(self)
-
- return locals()
- method = property(**method())
-
- def label():
- def fget(self):
- if self._label is None and hasattr(self, 'model'):
- self._label = label_for_filter(
- self.model, self.field_name, self.lookup_expr, self.exclude
- )
- return self._label
-
- def fset(self, value):
- self._label = value
-
- return locals()
- label = property(**label())
-
- @property
- def field(self):
- if not hasattr(self, '_field'):
- field_kwargs = self.extra.copy()
-
- if settings.DISABLE_HELP_TEXT:
- field_kwargs.pop('help_text', None)
-
- self._field = self.field_class(label=self.label, **field_kwargs)
- return self._field
-
- def filter(self, qs, value):
- if value in EMPTY_VALUES:
- return qs
- if self.distinct:
- qs = qs.distinct()
- lookup = '%s__%s' % (self.field_name, self.lookup_expr)
- qs = self.get_method(qs)(**{lookup: value})
- return qs
-
-
-class CharFilter(Filter):
- field_class = forms.CharField
-
-
-class BooleanFilter(Filter):
- field_class = forms.NullBooleanField
-
-
-class ChoiceFilter(Filter):
- field_class = ChoiceField
-
- def __init__(self, *args, **kwargs):
- self.null_value = kwargs.get('null_value', settings.NULL_CHOICE_VALUE)
- super().__init__(*args, **kwargs)
-
- def filter(self, qs, value):
- if value != self.null_value:
- return super().filter(qs, value)
-
- qs = self.get_method(qs)(**{'%s__%s' % (self.field_name, self.lookup_expr): None})
- return qs.distinct() if self.distinct else qs
-
-
-class TypedChoiceFilter(Filter):
- field_class = forms.TypedChoiceField
-
-
-class UUIDFilter(Filter):
- field_class = forms.UUIDField
-
-
-class MultipleChoiceFilter(Filter):
- """
- This filter performs OR(by default) or AND(using conjoined=True) query
- on the selected options.
-
- Advanced usage
- --------------
- Depending on your application logic, when all or no choices are selected,
- filtering may be a no-operation. In this case you may wish to avoid the
- filtering overhead, particularly if using a `distinct` call.
-
- You can override `get_filter_predicate` to use a custom filter.
- By default it will use the filter's name for the key, and the value will
- be the model object - or in case of passing in `to_field_name` the
- value of that attribute on the model.
-
- Set `always_filter` to `False` after instantiation to enable the default
- `is_noop` test. You can override `is_noop` if you need a different test
- for your application.
-
- `distinct` defaults to `True` as to-many relationships will generally
- require this.
- """
- field_class = MultipleChoiceField
-
- always_filter = True
-
- def __init__(self, *args, **kwargs):
- kwargs.setdefault('distinct', True)
- self.conjoined = kwargs.pop('conjoined', False)
- self.null_value = kwargs.get('null_value', settings.NULL_CHOICE_VALUE)
- super().__init__(*args, **kwargs)
-
- def is_noop(self, qs, value):
- """
- Return `True` to short-circuit unnecessary and potentially slow
- filtering.
- """
- if self.always_filter:
- return False
-
- # A reasonable default for being a noop...
- if self.extra.get('required') and len(value) == len(self.field.choices):
- return True
-
- return False
-
- def filter(self, qs, value):
- if not value:
- # Even though not a noop, no point filtering if empty.
- return qs
-
- if self.is_noop(qs, value):
- return qs
-
- if not self.conjoined:
- q = Q()
- for v in set(value):
- if v == self.null_value:
- v = None
- predicate = self.get_filter_predicate(v)
- if self.conjoined:
- qs = self.get_method(qs)(**predicate)
- else:
- q |= Q(**predicate)
-
- if not self.conjoined:
- qs = self.get_method(qs)(q)
-
- return qs.distinct() if self.distinct else qs
-
- def get_filter_predicate(self, v):
- name = self.field_name
- if name and self.lookup_expr != settings.DEFAULT_LOOKUP_EXPR:
- name = LOOKUP_SEP.join([name, self.lookup_expr])
- try:
- return {name: getattr(v, self.field.to_field_name)}
- except (AttributeError, TypeError):
- return {name: v}
-
-
-class TypedMultipleChoiceFilter(MultipleChoiceFilter):
- field_class = forms.TypedMultipleChoiceField
-
-
-class DateFilter(Filter):
- field_class = forms.DateField
-
-
-class DateTimeFilter(Filter):
- field_class = forms.DateTimeField
-
-
-class IsoDateTimeFilter(DateTimeFilter):
- """
- Uses IsoDateTimeField to support filtering on ISO 8601 formatted datetimes.
-
- For context see:
-
- * https://code.djangoproject.com/ticket/23448
- * https://github.com/encode/django-rest-framework/issues/1338
- * https://github.com/carltongibson/django-filter/pull/264
- """
- field_class = IsoDateTimeField
-
-
-class TimeFilter(Filter):
- field_class = forms.TimeField
-
-
-class DurationFilter(Filter):
- field_class = forms.DurationField
-
-
-class QuerySetRequestMixin:
- """
- Add callable functionality to filters that support the ``queryset``
- argument. If the ``queryset`` is callable, then it **must** accept the
- ``request`` object as a single argument.
-
- This is useful for filtering querysets by properties on the ``request``
- object, such as the user.
-
- Example::
-
- def departments(request):
- company = request.user.company
- return company.department_set.all()
-
- class EmployeeFilter(filters.FilterSet):
- department = filters.ModelChoiceFilter(queryset=departments)
- ...
-
- The above example restricts the set of departments to those in the logged-in
- user's associated company.
-
- """
- def __init__(self, *args, **kwargs):
- self.queryset = kwargs.get('queryset')
- super().__init__(*args, **kwargs)
-
- def get_request(self):
- try:
- return self.parent.request
- except AttributeError:
- return None
-
- def get_queryset(self, request):
- queryset = self.queryset
-
- if callable(queryset):
- return queryset(request)
- return queryset
-
- @property
- def field(self):
- request = self.get_request()
- queryset = self.get_queryset(request)
-
- if queryset is not None:
- self.extra['queryset'] = queryset
-
- return super().field
-
-
-class ModelChoiceFilter(QuerySetRequestMixin, ChoiceFilter):
- field_class = ModelChoiceField
-
- def __init__(self, *args, **kwargs):
- kwargs.setdefault('empty_label', settings.EMPTY_CHOICE_LABEL)
- super().__init__(*args, **kwargs)
-
-
-class ModelMultipleChoiceFilter(QuerySetRequestMixin, MultipleChoiceFilter):
- field_class = ModelMultipleChoiceField
-
-
-class NumberFilter(Filter):
- field_class = forms.DecimalField
-
- def get_max_validator(self):
- """
- Return a MaxValueValidator for the field, or None to disable.
- """
- return MaxValueValidator(1e50)
-
- @property
- def field(self):
- if not hasattr(self, '_field'):
- field = super().field
- max_validator = self.get_max_validator()
- if max_validator:
- field.validators.append(max_validator)
-
- self._field = field
- return self._field
-
-
-class NumericRangeFilter(Filter):
- field_class = RangeField
-
- def filter(self, qs, value):
- if value:
- if value.start is not None and value.stop is not None:
- value = (value.start, value.stop)
- elif value.start is not None:
- self.lookup_expr = 'startswith'
- value = value.start
- elif value.stop is not None:
- self.lookup_expr = 'endswith'
- value = value.stop
-
- return super().filter(qs, value)
-
-
-class RangeFilter(Filter):
- field_class = RangeField
-
- def filter(self, qs, value):
- if value:
- if value.start is not None and value.stop is not None:
- self.lookup_expr = 'range'
- value = (value.start, value.stop)
- elif value.start is not None:
- self.lookup_expr = 'gte'
- value = value.start
- elif value.stop is not None:
- self.lookup_expr = 'lte'
- value = value.stop
-
- return super().filter(qs, value)
-
-
-def _truncate(dt):
- return dt.date()
-
-
-class DateRangeFilter(ChoiceFilter):
- choices = [
- ('today', _('Today')),
- ('yesterday', _('Yesterday')),
- ('week', _('Past 7 days')),
- ('month', _('This month')),
- ('year', _('This year')),
- ]
-
- filters = {
- 'today': lambda qs, name: qs.filter(**{
- '%s__year' % name: now().year,
- '%s__month' % name: now().month,
- '%s__day' % name: now().day
- }),
- 'yesterday': lambda qs, name: qs.filter(**{
- '%s__year' % name: (now() - timedelta(days=1)).year,
- '%s__month' % name: (now() - timedelta(days=1)).month,
- '%s__day' % name: (now() - timedelta(days=1)).day,
- }),
- 'week': lambda qs, name: qs.filter(**{
- '%s__gte' % name: _truncate(now() - timedelta(days=7)),
- '%s__lt' % name: _truncate(now() + timedelta(days=1)),
- }),
- 'month': lambda qs, name: qs.filter(**{
- '%s__year' % name: now().year,
- '%s__month' % name: now().month
- }),
- 'year': lambda qs, name: qs.filter(**{
- '%s__year' % name: now().year,
- }),
- }
-
- def __init__(self, choices=None, filters=None, *args, **kwargs):
- if choices is not None:
- self.choices = choices
- if filters is not None:
- self.filters = filters
-
- unique = set([x[0] for x in self.choices]) ^ set(self.filters)
- assert not unique, \
- "Keys must be present in both 'choices' and 'filters'. Missing keys: " \
- "'%s'" % ', '.join(sorted(unique))
-
- # TODO: remove assertion in 2.1
- assert not hasattr(self, 'options'), \
- "The 'options' attribute has been replaced by 'choices' and 'filters'. " \
- "See: https://django-filter.readthedocs.io/en/main/guide/migration.html"
-
- # null choice not relevant
- kwargs.setdefault('null_label', None)
- super().__init__(choices=self.choices, *args, **kwargs)
-
- def filter(self, qs, value):
- if not value:
- return qs
-
- assert value in self.filters
-
- qs = self.filters[value](qs, self.field_name)
- return qs.distinct() if self.distinct else qs
-
-
-class DateFromToRangeFilter(RangeFilter):
- field_class = DateRangeField
-
-
-class DateTimeFromToRangeFilter(RangeFilter):
- field_class = DateTimeRangeField
-
-
-class IsoDateTimeFromToRangeFilter(RangeFilter):
- field_class = IsoDateTimeRangeField
-
-
-class TimeRangeFilter(RangeFilter):
- field_class = TimeRangeField
-
-
-class AllValuesFilter(ChoiceFilter):
- @property
- def field(self):
- qs = self.model._default_manager.distinct()
- qs = qs.order_by(self.field_name).values_list(self.field_name, flat=True)
- self.extra['choices'] = [(o, o) for o in qs]
- return super().field
-
-
-class AllValuesMultipleFilter(MultipleChoiceFilter):
- @property
- def field(self):
- qs = self.model._default_manager.distinct()
- qs = qs.order_by(self.field_name).values_list(self.field_name, flat=True)
- self.extra['choices'] = [(o, o) for o in qs]
- return super().field
-
-
-class BaseCSVFilter(Filter):
- """
- Base class for CSV type filters, such as IN and RANGE.
- """
- base_field_class = BaseCSVField
-
- def __init__(self, *args, **kwargs):
- kwargs.setdefault('help_text', _('Multiple values may be separated by commas.'))
- super().__init__(*args, **kwargs)
-
- class ConcreteCSVField(self.base_field_class, self.field_class):
- pass
- ConcreteCSVField.__name__ = self._field_class_name(
- self.field_class, self.lookup_expr
- )
-
- self.field_class = ConcreteCSVField
-
- @classmethod
- def _field_class_name(cls, field_class, lookup_expr):
- """
- Generate a suitable class name for the concrete field class. This is not
- completely reliable, as not all field class names are of the format
- Field.
-
- ex::
-
- BaseCSVFilter._field_class_name(DateTimeField, 'year__in')
-
- returns 'DateTimeYearInField'
-
- """
- # DateTimeField => DateTime
- type_name = field_class.__name__
- if type_name.endswith('Field'):
- type_name = type_name[:-5]
-
- # year__in => YearIn
- parts = lookup_expr.split(LOOKUP_SEP)
- expression_name = ''.join(p.capitalize() for p in parts)
-
- # DateTimeYearInField
- return str('%s%sField' % (type_name, expression_name))
-
-
-class BaseInFilter(BaseCSVFilter):
-
- def __init__(self, *args, **kwargs):
- kwargs.setdefault('lookup_expr', 'in')
- super().__init__(*args, **kwargs)
-
-
-class BaseRangeFilter(BaseCSVFilter):
- base_field_class = BaseRangeField
-
- def __init__(self, *args, **kwargs):
- kwargs.setdefault('lookup_expr', 'range')
- super().__init__(*args, **kwargs)
-
-
-class LookupChoiceFilter(Filter):
- """
- A combined filter that allows users to select the lookup expression from a dropdown.
-
- * ``lookup_choices`` is an optional argument that accepts multiple input
- formats, and is ultimately normlized as the choices used in the lookup
- dropdown. See ``.get_lookup_choices()`` for more information.
-
- * ``field_class`` is an optional argument that allows you to set the inner
- form field class used to validate the value. Default: ``forms.CharField``
-
- ex::
-
- price = django_filters.LookupChoiceFilter(
- field_class=forms.DecimalField,
- lookup_choices=[
- ('exact', 'Equals'),
- ('gt', 'Greater than'),
- ('lt', 'Less than'),
- ]
- )
-
- """
- field_class = forms.CharField
- outer_class = LookupChoiceField
-
- def __init__(self, field_name=None, lookup_choices=None, field_class=None, **kwargs):
- self.empty_label = kwargs.pop('empty_label', settings.EMPTY_CHOICE_LABEL)
-
- super(LookupChoiceFilter, self).__init__(field_name=field_name, **kwargs)
-
- self.lookup_choices = lookup_choices
- if field_class is not None:
- self.field_class = field_class
-
- @classmethod
- def normalize_lookup(cls, lookup):
- """
- Normalize the lookup into a tuple of ``(lookup expression, display value)``
-
- If the ``lookup`` is already a tuple, the tuple is not altered.
- If the ``lookup`` is a string, a tuple is returned with the lookup
- expression used as the basis for the display value.
-
- ex::
-
- >>> LookupChoiceFilter.normalize_lookup(('exact', 'Equals'))
- ('exact', 'Equals')
-
- >>> LookupChoiceFilter.normalize_lookup('has_key')
- ('has_key', 'Has key')
-
- """
- if isinstance(lookup, str):
- return (lookup, pretty_name(lookup))
- return (lookup[0], lookup[1])
-
- def get_lookup_choices(self):
- """
- Get the lookup choices in a format suitable for ``django.forms.ChoiceField``.
- If the filter is initialized with ``lookup_choices``, this value is normalized
- and passed to the underlying ``LookupChoiceField``. If no choices are provided,
- they are generated from the corresponding model field's registered lookups.
- """
- lookups = self.lookup_choices
- if lookups is None:
- field = get_model_field(self.model, self.field_name)
- lookups = field.get_lookups()
-
- return [self.normalize_lookup(lookup) for lookup in lookups]
-
- @property
- def field(self):
- if not hasattr(self, '_field'):
- inner_field = super().field
- lookups = self.get_lookup_choices()
-
- self._field = self.outer_class(
- inner_field, lookups,
- label=self.label,
- empty_label=self.empty_label,
- required=self.extra['required'],
- )
-
- return self._field
-
- def filter(self, qs, lookup):
- if not lookup:
- return super().filter(qs, None)
-
- self.lookup_expr = lookup.lookup_expr
- return super().filter(qs, lookup.value)
-
-
-class OrderingFilter(BaseCSVFilter, ChoiceFilter):
- """
- Enable queryset ordering. As an extension of ``ChoiceFilter`` it accepts
- two additional arguments that are used to build the ordering choices.
-
- * ``fields`` is a mapping of {model field name: parameter name}. The
- parameter names are exposed in the choices and mask/alias the field
- names used in the ``order_by()`` call. Similar to field ``choices``,
- ``fields`` accepts the 'list of two-tuples' syntax that retains order.
- ``fields`` may also just be an iterable of strings. In this case, the
- field names simply double as the exposed parameter names.
-
- * ``field_labels`` is an optional argument that allows you to customize
- the display label for the corresponding parameter. It accepts a mapping
- of {field name: human readable label}. Keep in mind that the key is the
- field name, and not the exposed parameter name.
-
- Additionally, you can just provide your own ``choices`` if you require
- explicit control over the exposed options. For example, when you might
- want to disable descending sort options.
-
- This filter is also CSV-based, and accepts multiple ordering params. The
- default select widget does not enable the use of this, but it is useful
- for APIs.
-
- """
- descending_fmt = _('%s (descending)')
-
- def __init__(self, *args, **kwargs):
- """
- ``fields`` may be either a mapping or an iterable.
- ``field_labels`` must be a map of field names to display labels
- """
- fields = kwargs.pop('fields', {})
- fields = self.normalize_fields(fields)
- field_labels = kwargs.pop('field_labels', {})
-
- self.param_map = {v: k for k, v in fields.items()}
-
- if 'choices' not in kwargs:
- kwargs['choices'] = self.build_choices(fields, field_labels)
-
- kwargs.setdefault('label', _('Ordering'))
- kwargs.setdefault('help_text', '')
- kwargs.setdefault('null_label', None)
- super().__init__(*args, **kwargs)
-
- def get_ordering_value(self, param):
- descending = param.startswith('-')
- param = param[1:] if descending else param
- field_name = self.param_map.get(param, param)
-
- return "-%s" % field_name if descending else field_name
-
- def filter(self, qs, value):
- if value in EMPTY_VALUES:
- return qs
-
- ordering = [self.get_ordering_value(param) for param in value]
- return qs.order_by(*ordering)
-
- @classmethod
- def normalize_fields(cls, fields):
- """
- Normalize the fields into an ordered map of {field name: param name}
- """
- # fields is a mapping, copy into new OrderedDict
- if isinstance(fields, dict):
- return OrderedDict(fields)
-
- # convert iterable of values => iterable of pairs (field name, param name)
- assert is_iterable(fields), \
- "'fields' must be an iterable (e.g., a list, tuple, or mapping)."
-
- # fields is an iterable of field names
- assert all(isinstance(field, str) or
- is_iterable(field) and len(field) == 2 # may need to be wrapped in parens
- for field in fields), \
- "'fields' must contain strings or (field name, param name) pairs."
-
- return OrderedDict([
- (f, f) if isinstance(f, str) else f for f in fields
- ])
-
- def build_choices(self, fields, labels):
- ascending = [
- (param, labels.get(field, _(pretty_name(param))))
- for field, param in fields.items()
- ]
- descending = [
- ('-%s' % param, labels.get('-%s' % param, self.descending_fmt % label))
- for param, label in ascending
- ]
-
- # interleave the ascending and descending choices
- return [val for pair in zip(ascending, descending) for val in pair]
-
-
-class FilterMethod:
- """
- This helper is used to override Filter.filter() when a 'method' argument
- is passed. It proxies the call to the actual method on the filter's parent.
- """
- def __init__(self, filter_instance):
- self.f = filter_instance
-
- def __call__(self, qs, value):
- if value in EMPTY_VALUES:
- return qs
-
- return self.method(qs, self.f.field_name, value)
-
- @property
- def method(self):
- """
- Resolve the method on the parent filterset.
- """
- instance = self.f
-
- # noop if 'method' is a function
- if callable(instance.method):
- return instance.method
-
- # otherwise, method is the name of a method on the parent FilterSet.
- assert hasattr(instance, 'parent'), \
- "Filter '%s' must have a parent FilterSet to find '.%s()'" % \
- (instance.field_name, instance.method)
-
- parent = instance.parent
- method = getattr(parent, instance.method, None)
-
- assert callable(method), \
- "Expected parent FilterSet '%s.%s' to have a '.%s()' method." % \
- (parent.__class__.__module__, parent.__class__.__name__, instance.method)
-
- return method
diff --git a/venv/Lib/site-packages/django_filters/filterset.py b/venv/Lib/site-packages/django_filters/filterset.py
deleted file mode 100644
index 9c2a4f7..0000000
--- a/venv/Lib/site-packages/django_filters/filterset.py
+++ /dev/null
@@ -1,470 +0,0 @@
-import copy
-from collections import OrderedDict
-
-from django import forms
-from django.db import models
-from django.db.models.constants import LOOKUP_SEP
-from django.db.models.fields.related import (
- ManyToManyRel,
- ManyToOneRel,
- OneToOneRel
-)
-
-from .conf import settings
-from .constants import ALL_FIELDS
-from .filters import (
- BaseInFilter,
- BaseRangeFilter,
- BooleanFilter,
- CharFilter,
- ChoiceFilter,
- DateFilter,
- DateTimeFilter,
- DurationFilter,
- Filter,
- ModelChoiceFilter,
- ModelMultipleChoiceFilter,
- NumberFilter,
- TimeFilter,
- UUIDFilter
-)
-from .utils import (
- get_all_model_fields,
- get_model_field,
- resolve_field,
- try_dbfield
-)
-
-
-def remote_queryset(field):
- """
- Get the queryset for the other side of a relationship. This works
- for both `RelatedField`s and `ForeignObjectRel`s.
- """
- model = field.related_model
-
- # Reverse relationships do not have choice limits
- if not hasattr(field, 'get_limit_choices_to'):
- return model._default_manager.all()
-
- limit_choices_to = field.get_limit_choices_to()
- return model._default_manager.complex_filter(limit_choices_to)
-
-
-class FilterSetOptions:
- def __init__(self, options=None):
- self.model = getattr(options, 'model', None)
- self.fields = getattr(options, 'fields', None)
- self.exclude = getattr(options, 'exclude', None)
-
- self.filter_overrides = getattr(options, 'filter_overrides', {})
-
- self.form = getattr(options, 'form', forms.Form)
-
-
-class FilterSetMetaclass(type):
- def __new__(cls, name, bases, attrs):
- attrs['declared_filters'] = cls.get_declared_filters(bases, attrs)
-
- new_class = super().__new__(cls, name, bases, attrs)
- new_class._meta = FilterSetOptions(getattr(new_class, 'Meta', None))
- new_class.base_filters = new_class.get_filters()
-
- # TODO: remove assertion in 2.1
- assert not hasattr(new_class, 'filter_for_reverse_field'), (
- "`%(cls)s.filter_for_reverse_field` has been removed. "
- "`%(cls)s.filter_for_field` now generates filters for reverse fields. "
- "See: https://django-filter.readthedocs.io/en/main/guide/migration.html"
- % {'cls': new_class.__name__}
- )
-
- return new_class
-
- @classmethod
- def get_declared_filters(cls, bases, attrs):
- filters = [
- (filter_name, attrs.pop(filter_name))
- for filter_name, obj in list(attrs.items())
- if isinstance(obj, Filter)
- ]
-
- # Default the `filter.field_name` to the attribute name on the filterset
- for filter_name, f in filters:
- if getattr(f, 'field_name', None) is None:
- f.field_name = filter_name
-
- filters.sort(key=lambda x: x[1].creation_counter)
-
- # Ensures a base class field doesn't override cls attrs, and maintains
- # field precedence when inheriting multiple parents. e.g. if there is a
- # class C(A, B), and A and B both define 'field', use 'field' from A.
- known = set(attrs)
-
- def visit(name):
- known.add(name)
- return name
-
- base_filters = [
- (visit(name), f)
- for base in bases if hasattr(base, 'declared_filters')
- for name, f in base.declared_filters.items() if name not in known
- ]
-
- return OrderedDict(base_filters + filters)
-
-
-FILTER_FOR_DBFIELD_DEFAULTS = {
- models.AutoField: {'filter_class': NumberFilter},
- models.CharField: {'filter_class': CharFilter},
- models.TextField: {'filter_class': CharFilter},
- models.BooleanField: {'filter_class': BooleanFilter},
- models.DateField: {'filter_class': DateFilter},
- models.DateTimeField: {'filter_class': DateTimeFilter},
- models.TimeField: {'filter_class': TimeFilter},
- models.DurationField: {'filter_class': DurationFilter},
- models.DecimalField: {'filter_class': NumberFilter},
- models.SmallIntegerField: {'filter_class': NumberFilter},
- models.IntegerField: {'filter_class': NumberFilter},
- models.PositiveIntegerField: {'filter_class': NumberFilter},
- models.PositiveSmallIntegerField: {'filter_class': NumberFilter},
- models.FloatField: {'filter_class': NumberFilter},
- models.NullBooleanField: {'filter_class': BooleanFilter},
- models.SlugField: {'filter_class': CharFilter},
- models.EmailField: {'filter_class': CharFilter},
- models.FilePathField: {'filter_class': CharFilter},
- models.URLField: {'filter_class': CharFilter},
- models.GenericIPAddressField: {'filter_class': CharFilter},
- models.CommaSeparatedIntegerField: {'filter_class': CharFilter},
- models.UUIDField: {'filter_class': UUIDFilter},
-
- # Forward relationships
- models.OneToOneField: {
- 'filter_class': ModelChoiceFilter,
- 'extra': lambda f: {
- 'queryset': remote_queryset(f),
- 'to_field_name': f.remote_field.field_name,
- 'null_label': settings.NULL_CHOICE_LABEL if f.null else None,
- }
- },
- models.ForeignKey: {
- 'filter_class': ModelChoiceFilter,
- 'extra': lambda f: {
- 'queryset': remote_queryset(f),
- 'to_field_name': f.remote_field.field_name,
- 'null_label': settings.NULL_CHOICE_LABEL if f.null else None,
- }
- },
- models.ManyToManyField: {
- 'filter_class': ModelMultipleChoiceFilter,
- 'extra': lambda f: {
- 'queryset': remote_queryset(f),
- }
- },
-
- # Reverse relationships
- OneToOneRel: {
- 'filter_class': ModelChoiceFilter,
- 'extra': lambda f: {
- 'queryset': remote_queryset(f),
- 'null_label': settings.NULL_CHOICE_LABEL if f.null else None,
- }
- },
- ManyToOneRel: {
- 'filter_class': ModelMultipleChoiceFilter,
- 'extra': lambda f: {
- 'queryset': remote_queryset(f),
- }
- },
- ManyToManyRel: {
- 'filter_class': ModelMultipleChoiceFilter,
- 'extra': lambda f: {
- 'queryset': remote_queryset(f),
- }
- },
-}
-
-
-class BaseFilterSet:
- FILTER_DEFAULTS = FILTER_FOR_DBFIELD_DEFAULTS
-
- def __init__(self, data=None, queryset=None, *, request=None, prefix=None):
- if queryset is None:
- queryset = self._meta.model._default_manager.all()
- model = queryset.model
-
- self.is_bound = data is not None
- self.data = data or {}
- self.queryset = queryset
- self.request = request
- self.form_prefix = prefix
-
- self.filters = copy.deepcopy(self.base_filters)
-
- # propagate the model and filterset to the filters
- for filter_ in self.filters.values():
- filter_.model = model
- filter_.parent = self
-
- def is_valid(self):
- """
- Return True if the underlying form has no errors, or False otherwise.
- """
- return self.is_bound and self.form.is_valid()
-
- @property
- def errors(self):
- """
- Return an ErrorDict for the data provided for the underlying form.
- """
- return self.form.errors
-
- def filter_queryset(self, queryset):
- """
- Filter the queryset with the underlying form's `cleaned_data`. You must
- call `is_valid()` or `errors` before calling this method.
-
- This method should be overridden if additional filtering needs to be
- applied to the queryset before it is cached.
- """
- for name, value in self.form.cleaned_data.items():
- queryset = self.filters[name].filter(queryset, value)
- assert isinstance(queryset, models.QuerySet), \
- "Expected '%s.%s' to return a QuerySet, but got a %s instead." \
- % (type(self).__name__, name, type(queryset).__name__)
- return queryset
-
- @property
- def qs(self):
- if not hasattr(self, '_qs'):
- qs = self.queryset.all()
- if self.is_bound:
- # ensure form validation before filtering
- self.errors
- qs = self.filter_queryset(qs)
- self._qs = qs
- return self._qs
-
- def get_form_class(self):
- """
- Returns a django Form suitable of validating the filterset data.
-
- This method should be overridden if the form class needs to be
- customized relative to the filterset instance.
- """
- fields = OrderedDict([
- (name, filter_.field)
- for name, filter_ in self.filters.items()])
-
- return type(str('%sForm' % self.__class__.__name__),
- (self._meta.form,), fields)
-
- @property
- def form(self):
- if not hasattr(self, '_form'):
- Form = self.get_form_class()
- if self.is_bound:
- self._form = Form(self.data, prefix=self.form_prefix)
- else:
- self._form = Form(prefix=self.form_prefix)
- return self._form
-
- @classmethod
- def get_fields(cls):
- """
- Resolve the 'fields' argument that should be used for generating filters on the
- filterset. This is 'Meta.fields' sans the fields in 'Meta.exclude'.
- """
- model = cls._meta.model
- fields = cls._meta.fields
- exclude = cls._meta.exclude
-
- assert not (fields is None and exclude is None), \
- "Setting 'Meta.model' without either 'Meta.fields' or 'Meta.exclude' " \
- "has been deprecated since 0.15.0 and is now disallowed. Add an explicit " \
- "'Meta.fields' or 'Meta.exclude' to the %s class." % cls.__name__
-
- # Setting exclude with no fields implies all other fields.
- if exclude is not None and fields is None:
- fields = ALL_FIELDS
-
- # Resolve ALL_FIELDS into all fields for the filterset's model.
- if fields == ALL_FIELDS:
- fields = get_all_model_fields(model)
-
- # Remove excluded fields
- exclude = exclude or []
- if not isinstance(fields, dict):
- fields = [(f, [settings.DEFAULT_LOOKUP_EXPR]) for f in fields if f not in exclude]
- else:
- fields = [(f, lookups) for f, lookups in fields.items() if f not in exclude]
-
- return OrderedDict(fields)
-
- @classmethod
- def get_filter_name(cls, field_name, lookup_expr):
- """
- Combine a field name and lookup expression into a usable filter name.
- Exact lookups are the implicit default, so "exact" is stripped from the
- end of the filter name.
- """
- filter_name = LOOKUP_SEP.join([field_name, lookup_expr])
-
- # This also works with transformed exact lookups, such as 'date__exact'
- _default_expr = LOOKUP_SEP + settings.DEFAULT_LOOKUP_EXPR
- if filter_name.endswith(_default_expr):
- filter_name = filter_name[:-len(_default_expr)]
-
- return filter_name
-
- @classmethod
- def get_filters(cls):
- """
- Get all filters for the filterset. This is the combination of declared and
- generated filters.
- """
-
- # No model specified - skip filter generation
- if not cls._meta.model:
- return cls.declared_filters.copy()
-
- # Determine the filters that should be included on the filterset.
- filters = OrderedDict()
- fields = cls.get_fields()
- undefined = []
-
- for field_name, lookups in fields.items():
- field = get_model_field(cls._meta.model, field_name)
-
- # warn if the field doesn't exist.
- if field is None:
- undefined.append(field_name)
-
- for lookup_expr in lookups:
- filter_name = cls.get_filter_name(field_name, lookup_expr)
-
- # If the filter is explicitly declared on the class, skip generation
- if filter_name in cls.declared_filters:
- filters[filter_name] = cls.declared_filters[filter_name]
- continue
-
- if field is not None:
- filters[filter_name] = cls.filter_for_field(field, field_name, lookup_expr)
-
- # Allow Meta.fields to contain declared filters *only* when a list/tuple
- if isinstance(cls._meta.fields, (list, tuple)):
- undefined = [f for f in undefined if f not in cls.declared_filters]
-
- if undefined:
- raise TypeError(
- "'Meta.fields' must not contain non-model field names: %s"
- % ', '.join(undefined)
- )
-
- # Add in declared filters. This is necessary since we don't enforce adding
- # declared filters to the 'Meta.fields' option
- filters.update(cls.declared_filters)
- return filters
-
- @classmethod
- def filter_for_field(cls, field, field_name, lookup_expr=None):
- if lookup_expr is None:
- lookup_expr = settings.DEFAULT_LOOKUP_EXPR
- field, lookup_type = resolve_field(field, lookup_expr)
-
- default = {
- 'field_name': field_name,
- 'lookup_expr': lookup_expr,
- }
-
- filter_class, params = cls.filter_for_lookup(field, lookup_type)
- default.update(params)
-
- assert filter_class is not None, (
- "%s resolved field '%s' with '%s' lookup to an unrecognized field "
- "type %s. Try adding an override to 'Meta.filter_overrides'. See: "
- "https://django-filter.readthedocs.io/en/main/ref/filterset.html"
- "#customise-filter-generation-with-filter-overrides"
- ) % (cls.__name__, field_name, lookup_expr, field.__class__.__name__)
-
- return filter_class(**default)
-
- @classmethod
- def filter_for_lookup(cls, field, lookup_type):
- DEFAULTS = dict(cls.FILTER_DEFAULTS)
- if hasattr(cls, '_meta'):
- DEFAULTS.update(cls._meta.filter_overrides)
-
- data = try_dbfield(DEFAULTS.get, field.__class__) or {}
- filter_class = data.get('filter_class')
- params = data.get('extra', lambda field: {})(field)
-
- # if there is no filter class, exit early
- if not filter_class:
- return None, {}
-
- # perform lookup specific checks
- if lookup_type == 'exact' and getattr(field, 'choices', None):
- return ChoiceFilter, {'choices': field.choices}
-
- if lookup_type == 'isnull':
- data = try_dbfield(DEFAULTS.get, models.BooleanField)
-
- filter_class = data.get('filter_class')
- params = data.get('extra', lambda field: {})(field)
- return filter_class, params
-
- if lookup_type == 'in':
- class ConcreteInFilter(BaseInFilter, filter_class):
- pass
- ConcreteInFilter.__name__ = cls._csv_filter_class_name(
- filter_class, lookup_type
- )
-
- return ConcreteInFilter, params
-
- if lookup_type == 'range':
- class ConcreteRangeFilter(BaseRangeFilter, filter_class):
- pass
- ConcreteRangeFilter.__name__ = cls._csv_filter_class_name(
- filter_class, lookup_type
- )
-
- return ConcreteRangeFilter, params
-
- return filter_class, params
-
- @classmethod
- def _csv_filter_class_name(cls, filter_class, lookup_type):
- """
- Generate a suitable class name for a concrete filter class. This is not
- completely reliable, as not all filter class names are of the format
- Filter.
-
- ex::
-
- FilterSet._csv_filter_class_name(DateTimeFilter, 'in')
-
- returns 'DateTimeInFilter'
-
- """
- # DateTimeFilter => DateTime
- type_name = filter_class.__name__
- if type_name.endswith('Filter'):
- type_name = type_name[:-6]
-
- # in => In
- lookup_name = lookup_type.capitalize()
-
- # DateTimeInFilter
- return str('%s%sFilter' % (type_name, lookup_name))
-
-
-class FilterSet(BaseFilterSet, metaclass=FilterSetMetaclass):
- pass
-
-
-def filterset_factory(model, fields=ALL_FIELDS):
- meta = type(str('Meta'), (object,), {'model': model, 'fields': fields})
- filterset = type(str('%sFilterSet' % model._meta.object_name),
- (FilterSet,), {'Meta': meta})
- return filterset
diff --git a/venv/Lib/site-packages/django_filters/locale/ar/LC_MESSAGES/django.mo b/venv/Lib/site-packages/django_filters/locale/ar/LC_MESSAGES/django.mo
deleted file mode 100644
index b1b876f..0000000
Binary files a/venv/Lib/site-packages/django_filters/locale/ar/LC_MESSAGES/django.mo and /dev/null differ
diff --git a/venv/Lib/site-packages/django_filters/locale/ar/LC_MESSAGES/django.po b/venv/Lib/site-packages/django_filters/locale/ar/LC_MESSAGES/django.po
deleted file mode 100644
index d35fe14..0000000
--- a/venv/Lib/site-packages/django_filters/locale/ar/LC_MESSAGES/django.po
+++ /dev/null
@@ -1,189 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR , YEAR.
-# FULL NAME , 2020.
-#
-#: conf.py:29 conf.py:30 conf.py:43
-msgid ""
-msgstr ""
-"Project-Id-Version: PACKAGE VERSION\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-03-24 00:19+0100\n"
-"PO-Revision-Date: 2020-03-24 00:48+0100\n"
-"Last-Translator: FULL NAME \n"
-"Language-Team: LANGUAGE \n"
-"Language: \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 "
-"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
-"X-Generator: Gtranslator 2.91.7\n"
-
-#: conf.py:19
-msgid "date"
-msgstr "تاريخ"
-
-#: conf.py:20
-msgid "year"
-msgstr "سنة"
-
-#: conf.py:21
-msgid "month"
-msgstr "شهر"
-
-#: conf.py:22
-msgid "day"
-msgstr "يوم"
-
-#: conf.py:23
-msgid "week day"
-msgstr "يوم الأسبوع"
-
-#: conf.py:24
-msgid "hour"
-msgstr "ساعة"
-
-#: conf.py:25
-msgid "minute"
-msgstr "دقيقة"
-
-#: conf.py:26
-msgid "second"
-msgstr "ثانية"
-
-#: conf.py:31 conf.py:32
-msgid "contains"
-msgstr "يحتوي على"
-
-#: conf.py:33
-msgid "is in"
-msgstr "في داخل"
-
-#: conf.py:34
-msgid "is greater than"
-msgstr "أكبر من"
-
-#: conf.py:35
-msgid "is greater than or equal to"
-msgstr "أكبر من أو يساوي"
-
-#: conf.py:36
-msgid "is less than"
-msgstr "أصغر من"
-
-#: conf.py:37
-msgid "is less than or equal to"
-msgstr "أصغر من أو يساوي"
-
-#: conf.py:38 conf.py:39
-msgid "starts with"
-msgstr "يبدأ ب"
-
-#: conf.py:40 conf.py:41
-msgid "ends with"
-msgstr "ينتهي ب"
-
-#: conf.py:42
-msgid "is in range"
-msgstr "في النطاق"
-
-#: conf.py:44 conf.py:45
-msgid "matches regex"
-msgstr "يطابق التعبير العادي"
-
-#: conf.py:46 conf.py:54
-msgid "search"
-msgstr "بحث"
-
-#: conf.py:49
-msgid "is contained by"
-msgstr "موجود في"
-
-#: conf.py:50
-msgid "overlaps"
-msgstr "يتداخل"
-
-#: conf.py:51
-msgid "has key"
-msgstr "لديه مفتاح"
-
-#: conf.py:52
-msgid "has keys"
-msgstr "لديه مفاتيح"
-
-#: conf.py:53
-msgid "has any keys"
-msgstr "لديه أي مفاتيح"
-
-#: fields.py:106
-msgid "Select a lookup."
-msgstr "حدد بحث"
-
-#: fields.py:198
-msgid "Range query expects two values."
-msgstr "إستعلام النطاق يتوقع قيمتين"
-
-#: filters.py:402
-msgid "Today"
-msgstr "اليوم"
-
-#: filters.py:403
-msgid "Yesterday"
-msgstr "أمس"
-
-#: filters.py:404
-msgid "Past 7 days"
-msgstr "الأيام السبعة الماضية"
-
-#: filters.py:405
-msgid "This month"
-msgstr "هذا الشهر"
-
-#: filters.py:406
-msgid "This year"
-msgstr "هذه السنة"
-
-#: filters.py:504
-msgid "Multiple values may be separated by commas."
-msgstr "يمكن فصل القيم المتعددة بفواصل."
-
-#: filters.py:677
-#, python-format
-msgid "%s (descending)"
-msgstr "%s (تنازلي)"
-
-#: filters.py:693
-msgid "Ordering"
-msgstr "الترتيب"
-
-#: rest_framework/filterset.py:31
-#: templates/django_filters/rest_framework/form.html:5
-msgid "Submit"
-msgstr "إرسال"
-
-#: templates/django_filters/rest_framework/crispy_form.html:4
-#: templates/django_filters/rest_framework/form.html:2
-msgid "Field filters"
-msgstr "مرشحات الحقل"
-
-#: utils.py:294
-msgid "exclude"
-msgstr "استبعاد"
-
-#: widgets.py:58
-msgid "All"
-msgstr "كل"
-
-#: widgets.py:160
-msgid "Unknown"
-msgstr "مجهول"
-
-#: widgets.py:161
-msgid "Yes"
-msgstr "نعم"
-
-#: widgets.py:162
-msgid "No"
-msgstr "لا"
diff --git a/venv/Lib/site-packages/django_filters/locale/be/LC_MESSAGES/django.mo b/venv/Lib/site-packages/django_filters/locale/be/LC_MESSAGES/django.mo
deleted file mode 100644
index 595dad9..0000000
Binary files a/venv/Lib/site-packages/django_filters/locale/be/LC_MESSAGES/django.mo and /dev/null differ
diff --git a/venv/Lib/site-packages/django_filters/locale/be/LC_MESSAGES/django.po b/venv/Lib/site-packages/django_filters/locale/be/LC_MESSAGES/django.po
deleted file mode 100644
index ebd0221..0000000
--- a/venv/Lib/site-packages/django_filters/locale/be/LC_MESSAGES/django.po
+++ /dev/null
@@ -1,185 +0,0 @@
-#
-#: conf.py:27 conf.py:28 conf.py:41
-msgid ""
-msgstr ""
-"Project-Id-Version: django-filter\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-01-24 18:51+0500\n"
-"PO-Revision-Date: 2016-09-29 11:47+0300\n"
-"Last-Translator: Eugena Mikhaylikova \n"
-"Language-Team: TextTempearture\n"
-"Language: be\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
-"%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n"
-"%100>=11 && n%100<=14)? 2 : 3);\n"
-"X-Generator: Poedit 1.8.9\n"
-
-#: conf.py:17
-msgid "date"
-msgstr "дата"
-
-#: conf.py:18
-msgid "year"
-msgstr "год"
-
-#: conf.py:19
-msgid "month"
-msgstr "месяц"
-
-#: conf.py:20
-msgid "day"
-msgstr "дзень"
-
-#: conf.py:21
-msgid "week day"
-msgstr "дзень тыдня"
-
-#: conf.py:22
-msgid "hour"
-msgstr "гадзіну"
-
-#: conf.py:23
-msgid "minute"
-msgstr "хвіліна"
-
-#: conf.py:24
-msgid "second"
-msgstr "секунда"
-
-#: conf.py:29 conf.py:30
-msgid "contains"
-msgstr "змяшчае"
-
-#: conf.py:31
-msgid "is in"
-msgstr "у"
-
-#: conf.py:32
-msgid "is greater than"
-msgstr "больш чым"
-
-#: conf.py:33
-msgid "is greater than or equal to"
-msgstr "больш або роўна"
-
-#: conf.py:34
-msgid "is less than"
-msgstr "менш чым"
-
-#: conf.py:35
-msgid "is less than or equal to"
-msgstr "менш або роўна"
-
-#: conf.py:36 conf.py:37
-msgid "starts with"
-msgstr "пачынаецца"
-
-#: conf.py:38 conf.py:39
-msgid "ends with"
-msgstr "заканчваецца"
-
-#: conf.py:40
-msgid "is in range"
-msgstr "у дыяпазоне"
-
-#: conf.py:42 conf.py:43
-msgid "matches regex"
-msgstr "адпавядае рэгулярнаму выразу"
-
-#: conf.py:44 conf.py:52
-msgid "search"
-msgstr "пошук"
-
-#: conf.py:47
-msgid "is contained by"
-msgstr "змяшчаецца ў"
-
-#: conf.py:48
-msgid "overlaps"
-msgstr "перакрываецца"
-
-#: conf.py:49
-msgid "has key"
-msgstr "мае ключ"
-
-#: conf.py:50
-msgid "has keys"
-msgstr "мае ключы"
-
-#: conf.py:51
-msgid "has any keys"
-msgstr "мае любыя ключы"
-
-#: fields.py:178
-msgid "Range query expects two values."
-msgstr "Запыт дыяпазону чакае два значэння."
-
-#: filters.py:429
-msgid "Any date"
-msgstr "Любая дата"
-
-#: filters.py:430
-msgid "Today"
-msgstr "Сёння"
-
-#: filters.py:435
-msgid "Past 7 days"
-msgstr "Мінулыя 7 дзён"
-
-#: filters.py:439
-msgid "This month"
-msgstr "За гэты месяц"
-
-#: filters.py:443
-msgid "This year"
-msgstr "У гэтым годзе"
-
-#: filters.py:446
-msgid "Yesterday"
-msgstr "Учора"
-
-#: filters.py:512
-msgid "Multiple values may be separated by commas."
-msgstr "Некалькі значэнняў могуць быць падзеленыя коскамі."
-
-#: filters.py:591
-#, python-format
-msgid "%s (descending)"
-msgstr "%s (па змяншэнні)"
-
-#: filters.py:607
-msgid "Ordering"
-msgstr "Парадак"
-
-#: rest_framework/filterset.py:30
-#: templates/django_filters/rest_framework/form.html:5
-msgid "Submit"
-msgstr "Адправіць"
-
-#: templates/django_filters/rest_framework/crispy_form.html:4
-#: templates/django_filters/rest_framework/form.html:2
-msgid "Field filters"
-msgstr "Фільтры па палях"
-
-#: utils.py:224
-msgid "exclude"
-msgstr "выключаючы"
-
-#: widgets.py:57
-msgid "All"
-msgstr "Усе"
-
-#: widgets.py:159
-msgid "Unknown"
-msgstr "Не было прапанавана"
-
-#: widgets.py:160
-msgid "Yes"
-msgstr "Ды"
-
-#: widgets.py:161
-msgid "No"
-msgstr "Няма"
diff --git a/venv/Lib/site-packages/django_filters/locale/bg/LC_MESSAGES/django.mo b/venv/Lib/site-packages/django_filters/locale/bg/LC_MESSAGES/django.mo
deleted file mode 100644
index 122fe4d..0000000
Binary files a/venv/Lib/site-packages/django_filters/locale/bg/LC_MESSAGES/django.mo and /dev/null differ
diff --git a/venv/Lib/site-packages/django_filters/locale/bg/LC_MESSAGES/django.po b/venv/Lib/site-packages/django_filters/locale/bg/LC_MESSAGES/django.po
deleted file mode 100644
index 9149eaa..0000000
--- a/venv/Lib/site-packages/django_filters/locale/bg/LC_MESSAGES/django.po
+++ /dev/null
@@ -1,187 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# Hristo Gatsinski , 2019.
-#
-#: conf.py:27 conf.py:28 conf.py:41
-msgid ""
-msgstr ""
-"Project-Id-Version: django-filter\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-12-22 19:45+0200\n"
-"PO-Revision-Date: 2019-12-21 19:36+0200\n"
-"Last-Translator: Hristo Gatsinski \n"
-"Language-Team: \n"
-"Language: bg\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-"X-Generator: Poedit 1.8.9\n"
-
-#: conf.py:17
-msgid "date"
-msgstr "дата"
-
-#: conf.py:18
-msgid "year"
-msgstr "година"
-
-#: conf.py:19
-msgid "month"
-msgstr "месец"
-
-#: conf.py:20
-msgid "day"
-msgstr "ден"
-
-#: conf.py:21
-msgid "week day"
-msgstr "ден от седмицата"
-
-#: conf.py:22
-msgid "hour"
-msgstr "час"
-
-#: conf.py:23
-msgid "minute"
-msgstr "минута"
-
-#: conf.py:24
-msgid "second"
-msgstr "секунда"
-
-#: conf.py:29 conf.py:30
-msgid "contains"
-msgstr "съдържа"
-
-#: conf.py:31
-msgid "is in"
-msgstr "в"
-
-#: conf.py:32
-msgid "is greater than"
-msgstr "е по-голям от"
-
-#: conf.py:33
-msgid "is greater than or equal to"
-msgstr "е по-голям или равен на"
-
-#: conf.py:34
-msgid "is less than"
-msgstr "е по-малък от"
-
-#: conf.py:35
-msgid "is less than or equal to"
-msgstr "е по-малък или равен на"
-
-#: conf.py:36 conf.py:37
-msgid "starts with"
-msgstr "започва с"
-
-#: conf.py:38 conf.py:39
-msgid "ends with"
-msgstr "завършва с"
-
-#: conf.py:40
-msgid "is in range"
-msgstr "е в диапазона"
-
-#: conf.py:42 conf.py:43
-msgid "matches regex"
-msgstr "съвпада с регуларен израз"
-
-#: conf.py:44 conf.py:52
-msgid "search"
-msgstr "търсене"
-
-#: conf.py:47
-msgid "is contained by"
-msgstr "се съдържа от"
-
-#: conf.py:48
-msgid "overlaps"
-msgstr "припокрива"
-
-#: conf.py:49
-msgid "has key"
-msgstr "има ключ"
-
-#: conf.py:50
-msgid "has keys"
-msgstr "има ключове"
-
-#: conf.py:51
-msgid "has any keys"
-msgstr "има който и да е ключ"
-
-#: fields.py:106
-msgid "Select a lookup."
-msgstr "Изберете справка"
-
-#: fields.py:198
-msgid "Range query expects two values."
-msgstr "Търсенето по диапазон изисква две стойности"
-
-#: filters.py:406
-msgid "Today"
-msgstr "Днес"
-
-#: filters.py:407
-msgid "Yesterday"
-msgstr "Вчера"
-
-#: filters.py:408
-msgid "Past 7 days"
-msgstr "Последните 7 дни"
-
-#: filters.py:409
-msgid "This month"
-msgstr "Този месец"
-
-#: filters.py:410
-msgid "This year"
-msgstr "Тази година"
-
-#: filters.py:508
-msgid "Multiple values may be separated by commas."
-msgstr "Множество стойности може да се разделят със запетая"
-
-#: filters.py:681
-#, python-format
-msgid "%s (descending)"
-msgstr "%s (намалавящ)"
-
-#: filters.py:697
-msgid "Ordering"
-msgstr "Подредба"
-
-#: rest_framework/filterset.py:31
-#: templates/django_filters/rest_framework/form.html:5
-msgid "Submit"
-msgstr "Изпращане"
-
-#: templates/django_filters/rest_framework/crispy_form.html:4
-#: templates/django_filters/rest_framework/form.html:2
-msgid "Field filters"
-msgstr "Филтри на полетата"
-
-#: utils.py:298
-msgid "exclude"
-msgstr "изключва"
-
-#: widgets.py:57
-msgid "All"
-msgstr "Всичко"
-
-#: widgets.py:159
-msgid "Unknown"
-msgstr "Неизвестен"
-
-#: widgets.py:160
-msgid "Yes"
-msgstr "Да"
-
-#: widgets.py:161
-msgid "No"
-msgstr "Не"
diff --git a/venv/Lib/site-packages/django_filters/locale/cs/LC_MESSAGES/django.mo b/venv/Lib/site-packages/django_filters/locale/cs/LC_MESSAGES/django.mo
deleted file mode 100644
index 54a8915..0000000
Binary files a/venv/Lib/site-packages/django_filters/locale/cs/LC_MESSAGES/django.mo and /dev/null differ
diff --git a/venv/Lib/site-packages/django_filters/locale/cs/LC_MESSAGES/django.po b/venv/Lib/site-packages/django_filters/locale/cs/LC_MESSAGES/django.po
deleted file mode 100644
index 40930a6..0000000
--- a/venv/Lib/site-packages/django_filters/locale/cs/LC_MESSAGES/django.po
+++ /dev/null
@@ -1,182 +0,0 @@
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: django-filter\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-01-24 11:03+0500\n"
-"PO-Revision-Date: 2016-09-29 11:47+0300\n"
-"Last-Translator: Eugena Mikhaylikova \n"
-"Language-Team: TextTempearture\n"
-"Language: cs\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n "
-"<= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n"
-"X-Generator: Poedit 1.8.9\n"
-
-#: conf.py:17
-msgid "date"
-msgstr "datum"
-
-#: conf.py:18
-msgid "year"
-msgstr "rok"
-
-#: conf.py:19
-msgid "month"
-msgstr "měsíc"
-
-#: conf.py:20
-msgid "day"
-msgstr "den"
-
-#: conf.py:21
-msgid "week day"
-msgstr "den v týdnu"
-
-#: conf.py:22
-msgid "hour"
-msgstr "hodinu"
-
-#: conf.py:23
-msgid "minute"
-msgstr "minutu"
-
-#: conf.py:24
-msgid "second"
-msgstr "vteřina"
-
-#: conf.py:29 conf.py:30
-msgid "contains"
-msgstr "obsahuje"
-
-#: conf.py:31
-msgid "is in"
-msgstr "v"
-
-#: conf.py:32
-msgid "is greater than"
-msgstr "více než"
-
-#: conf.py:33
-msgid "is greater than or equal to"
-msgstr "větší nebo roven"
-
-#: conf.py:34
-msgid "is less than"
-msgstr "méně než"
-
-#: conf.py:35
-msgid "is less than or equal to"
-msgstr "menší nebo rovné"
-
-#: conf.py:36 conf.py:37
-msgid "starts with"
-msgstr "začíná"
-
-#: conf.py:38 conf.py:39
-msgid "ends with"
-msgstr "končí"
-
-#: conf.py:40
-msgid "is in range"
-msgstr "v rozsahu"
-
-#: conf.py:42 conf.py:43
-msgid "matches regex"
-msgstr "odpovídá normálnímu výrazu"
-
-#: conf.py:44 conf.py:52
-msgid "search"
-msgstr "vyhledávání"
-
-#: conf.py:47
-msgid "is contained by"
-msgstr "je obsažen v"
-
-#: conf.py:48
-msgid "overlaps"
-msgstr "překrývají"
-
-#: conf.py:49
-msgid "has key"
-msgstr "má klíč"
-
-#: conf.py:50
-msgid "has keys"
-msgstr "má klíče"
-
-#: conf.py:51
-msgid "has any keys"
-msgstr "má nějaké klíče"
-
-#: fields.py:178
-msgid "Range query expects two values."
-msgstr "Rozsah dotazu očekává dvě hodnoty."
-
-#: filters.py:429
-msgid "Any date"
-msgstr "Jakékoliv datum"
-
-#: filters.py:430
-msgid "Today"
-msgstr "Dnes"
-
-#: filters.py:435
-msgid "Past 7 days"
-msgstr "Posledních 7 dní"
-
-#: filters.py:439
-msgid "This month"
-msgstr "Tento měsíc"
-
-#: filters.py:443
-msgid "This year"
-msgstr "Tento rok"
-
-#: filters.py:446
-msgid "Yesterday"
-msgstr "Včera"
-
-#: filters.py:512
-msgid "Multiple values may be separated by commas."
-msgstr "Více hodnot lze oddělit čárkami."
-
-#: filters.py:591
-msgid "%s (descending)"
-msgstr "%s (sestupně)"
-
-#: filters.py:607
-msgid "Ordering"
-msgstr "Řád z"
-
-#: rest_framework/filterset.py:30
-#: templates/django_filters/rest_framework/form.html:5
-msgid "Submit"
-msgstr "Odeslat"
-
-#: templates/django_filters/rest_framework/crispy_form.html:4
-#: templates/django_filters/rest_framework/form.html:2
-msgid "Field filters"
-msgstr "Filtry na polích"
-
-#: utils.py:224
-msgid "exclude"
-msgstr "s výjimkou"
-
-#: widgets.py:57
-msgid "All"
-msgstr "Všechno"
-
-#: widgets.py:159
-msgid "Unknown"
-msgstr "Není nastaveno"
-
-#: widgets.py:160
-msgid "Yes"
-msgstr "Ano"
-
-#: widgets.py:161
-msgid "No"
-msgstr "Ne"
diff --git a/venv/Lib/site-packages/django_filters/locale/da/LC_MESSAGES/django.mo b/venv/Lib/site-packages/django_filters/locale/da/LC_MESSAGES/django.mo
deleted file mode 100644
index 84f7e94..0000000
Binary files a/venv/Lib/site-packages/django_filters/locale/da/LC_MESSAGES/django.mo and /dev/null differ
diff --git a/venv/Lib/site-packages/django_filters/locale/da/LC_MESSAGES/django.po b/venv/Lib/site-packages/django_filters/locale/da/LC_MESSAGES/django.po
deleted file mode 100644
index 6260b3f..0000000
--- a/venv/Lib/site-packages/django_filters/locale/da/LC_MESSAGES/django.po
+++ /dev/null
@@ -1,181 +0,0 @@
-msgid ""
-msgstr ""
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Generator: Poedit 2.0.1\n"
-"Project-Id-Version: django-filter\n"
-"Language: da\n"
-"Last-Translator: Danni Randeris \n"
-"Language-Team: Danni Randeris \n"
-"POT-Creation-Date: 2017-10-28\n"
-"PO-Revision-Date: 2017-10-28\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#: conf.py:17
-msgid "date"
-msgstr "dato"
-
-#: conf.py:18
-msgid "year"
-msgstr "år"
-
-#: conf.py:19
-msgid "month"
-msgstr "måned"
-
-#: conf.py:20
-msgid "day"
-msgstr "dag"
-
-#: conf.py:21
-msgid "week day"
-msgstr "ugedag"
-
-#: conf.py:22
-msgid "hour"
-msgstr "time"
-
-#: conf.py:23
-msgid "minute"
-msgstr "minut"
-
-#: conf.py:24
-msgid "second"
-msgstr "sekund"
-
-#: conf.py:29 conf.py:30
-msgid "contains"
-msgstr "indeholder"
-
-#: conf.py:31
-msgid "is in"
-msgstr "er i"
-
-#: conf.py:32
-msgid "is greater than"
-msgstr "er større end"
-
-#: conf.py:33
-msgid "is greater than or equal to"
-msgstr "er større end eller lig med"
-
-#: conf.py:34
-msgid "is less than"
-msgstr "er mindre end"
-
-#: conf.py:35
-msgid "is less than or equal to"
-msgstr "er mindre end eller lig med"
-
-#: conf.py:36 conf.py:37
-msgid "starts with"
-msgstr "starter med"
-
-#: conf.py:38 conf.py:39
-msgid "ends with"
-msgstr "slutter med"
-
-#: conf.py:40
-msgid "is in range"
-msgstr "er i intervallet"
-
-#: conf.py:42 conf.py:43
-msgid "matches regex"
-msgstr "matcher regex"
-
-#: conf.py:44 conf.py:52
-msgid "search"
-msgstr "søg"
-
-#: conf.py:47
-msgid "is contained by"
-msgstr "er indeholdt af"
-
-#: conf.py:48
-msgid "overlaps"
-msgstr "overlapper"
-
-#: conf.py:49
-msgid "has key"
-msgstr "har string"
-
-#: conf.py:50
-msgid "has keys"
-msgstr "har stringe"
-
-#: conf.py:51
-msgid "has any keys"
-msgstr "har hvilken som helst string"
-
-#: fields.py:178
-msgid "Range query expects two values."
-msgstr "Interval forespørgslen forventer to værdier."
-
-#: filters.py:429
-msgid "Any date"
-msgstr "Hvilken som helst dag"
-
-#: filters.py:430
-msgid "Today"
-msgstr "I dag"
-
-#: filters.py:435
-msgid "Past 7 days"
-msgstr "Sidste 7 dage"
-
-#: filters.py:439
-msgid "This month"
-msgstr "Denne måned"
-
-#: filters.py:443
-msgid "This year"
-msgstr "Dette år"
-
-#: filters.py:446
-msgid "Yesterday"
-msgstr "I går"
-
-#: filters.py:512
-msgid "Multiple values may be separated by commas."
-msgstr "Flere værdier kan adskilles via komma."
-
-#: filters.py:591
-msgid "%s (descending)"
-msgstr "%s (aftagende)"
-
-#: filters.py:607
-msgid "Ordering"
-msgstr "Sortering"
-
-#: rest_framework/filterset.py:30
-#: templates/django_filters/rest_framework/form.html:5
-#, fuzzy
-msgid "Submit"
-msgstr "Indsend"
-
-#: templates/django_filters/rest_framework/crispy_form.html:4
-#: templates/django_filters/rest_framework/form.html:2
-#, fuzzy
-msgid "Field filters"
-msgstr "Felt filtre"
-
-#: utils.py:224
-msgid "exclude"
-msgstr "udelad"
-
-#: widgets.py:57
-msgid "All"
-msgstr "Alle"
-
-#: widgets.py:159
-msgid "Unknown"
-msgstr "Ukendt"
-
-#: widgets.py:160
-msgid "Yes"
-msgstr "Ja"
-
-#: widgets.py:161
-msgid "No"
-msgstr "Nej"
diff --git a/venv/Lib/site-packages/django_filters/locale/de/LC_MESSAGES/django.mo b/venv/Lib/site-packages/django_filters/locale/de/LC_MESSAGES/django.mo
deleted file mode 100644
index 9a48a0a..0000000
Binary files a/venv/Lib/site-packages/django_filters/locale/de/LC_MESSAGES/django.mo and /dev/null differ
diff --git a/venv/Lib/site-packages/django_filters/locale/de/LC_MESSAGES/django.po b/venv/Lib/site-packages/django_filters/locale/de/LC_MESSAGES/django.po
deleted file mode 100644
index 428a2ff..0000000
--- a/venv/Lib/site-packages/django_filters/locale/de/LC_MESSAGES/django.po
+++ /dev/null
@@ -1,187 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR , YEAR.
-#
-#: conf.py:27 conf.py:28 conf.py:41
-msgid ""
-msgstr ""
-"Project-Id-Version: django-filter\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-01-24 11:03+0500\n"
-"PO-Revision-Date: 2013-08-10 12:29+0100\n"
-"Last-Translator: Florian Apolloner \n"
-"Language-Team: \n"
-"Language: de\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-"X-Generator: Poedit 1.5.4\n"
-
-#: conf.py:17
-msgid "date"
-msgstr "Datum"
-
-#: conf.py:18
-msgid "year"
-msgstr "Jahr"
-
-#: conf.py:19
-msgid "month"
-msgstr "Monat"
-
-#: conf.py:20
-msgid "day"
-msgstr "Tag"
-
-#: conf.py:21
-msgid "week day"
-msgstr "Wochentag"
-
-#: conf.py:22
-msgid "hour"
-msgstr "Stunde"
-
-#: conf.py:23
-msgid "minute"
-msgstr "Minute"
-
-#: conf.py:24
-msgid "second"
-msgstr "Sekunde"
-
-#: conf.py:29 conf.py:30
-msgid "contains"
-msgstr "enthält"
-
-#: conf.py:31
-msgid "is in"
-msgstr "ist in"
-
-#: conf.py:32
-msgid "is greater than"
-msgstr "ist größer als"
-
-#: conf.py:33
-msgid "is greater than or equal to"
-msgstr "ist größer oder gleich"
-
-#: conf.py:34
-msgid "is less than"
-msgstr "ist kleiner als"
-
-#: conf.py:35
-msgid "is less than or equal to"
-msgstr "ist kleiner oder gleich"
-
-#: conf.py:36 conf.py:37
-msgid "starts with"
-msgstr "beginnt mit"
-
-#: conf.py:38 conf.py:39
-msgid "ends with"
-msgstr "endet mit"
-
-#: conf.py:40
-msgid "is in range"
-msgstr "ist im Bereich"
-
-#: conf.py:42 conf.py:43
-msgid "matches regex"
-msgstr "passt auf Regex"
-
-#: conf.py:44 conf.py:52
-msgid "search"
-msgstr "Suche"
-
-#: conf.py:47
-msgid "is contained by"
-msgstr "ist enthalten in"
-
-#: conf.py:48
-msgid "overlaps"
-msgstr "überlappen"
-
-#: conf.py:49
-msgid "has key"
-msgstr "hat Schlüssel"
-
-#: conf.py:50
-msgid "has keys"
-msgstr "hat Schlüssel"
-
-#: conf.py:51
-msgid "has any keys"
-msgstr "hat beliebige Schlüssel"
-
-#: fields.py:178
-msgid "Range query expects two values."
-msgstr "Die Bereichsabfrage erwartet zwei Werte."
-
-#: filters.py:429
-msgid "Any date"
-msgstr "Alle Daten"
-
-#: filters.py:430
-msgid "Today"
-msgstr "Heute"
-
-#: filters.py:435
-msgid "Past 7 days"
-msgstr "Letzte 7 Tage"
-
-#: filters.py:439
-msgid "This month"
-msgstr "Diesen Monat"
-
-#: filters.py:443
-msgid "This year"
-msgstr "Dieses Jahr"
-
-#: filters.py:446
-msgid "Yesterday"
-msgstr "Gestern"
-
-#: filters.py:512
-msgid "Multiple values may be separated by commas."
-msgstr "Mehrere Werte können durch Kommas getrennt sein."
-
-#: filters.py:591
-#, python-format
-msgid "%s (descending)"
-msgstr "%s (absteigend)"
-
-#: filters.py:607
-msgid "Ordering"
-msgstr "Sortierung"
-
-#: rest_framework/filterset.py:30
-#: templates/django_filters/rest_framework/form.html:5
-msgid "Submit"
-msgstr "Absenden"
-
-#: templates/django_filters/rest_framework/crispy_form.html:4
-#: templates/django_filters/rest_framework/form.html:2
-msgid "Field filters"
-msgstr "Feldfilter"
-
-#: utils.py:224
-msgid "exclude"
-msgstr "ausschließen"
-
-#: widgets.py:57
-msgid "All"
-msgstr "Alle"
-
-#: widgets.py:159
-msgid "Unknown"
-msgstr "Unbekannte"
-
-#: widgets.py:160
-msgid "Yes"
-msgstr "Ja"
-
-#: widgets.py:161
-msgid "No"
-msgstr "Nein"
diff --git a/venv/Lib/site-packages/django_filters/locale/el/LC_MESSAGES/django.mo b/venv/Lib/site-packages/django_filters/locale/el/LC_MESSAGES/django.mo
deleted file mode 100644
index 4e2258b..0000000
Binary files a/venv/Lib/site-packages/django_filters/locale/el/LC_MESSAGES/django.mo and /dev/null differ
diff --git a/venv/Lib/site-packages/django_filters/locale/el/LC_MESSAGES/django.po b/venv/Lib/site-packages/django_filters/locale/el/LC_MESSAGES/django.po
deleted file mode 100644
index 0018a2c..0000000
--- a/venv/Lib/site-packages/django_filters/locale/el/LC_MESSAGES/django.po
+++ /dev/null
@@ -1,187 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# Serafeim Papastefanos , 2017.
-#
-#: .\conf.py:27 .\conf.py:28 .\conf.py:41
-msgid ""
-msgstr ""
-"Project-Id-Version: django-filter\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-11-16 10:06+0200\n"
-"PO-Revision-Date: 2017-11-16 10:04+0200\n"
-"Last-Translator: Serafeim Papastefanos \n"
-"Language-Team: \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Language: de\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-"X-Generator: Poedit 1.6.5\n"
-
-#: .\conf.py:17
-msgid "date"
-msgstr "ημερομηνία"
-
-#: .\conf.py:18
-msgid "year"
-msgstr "έτος"
-
-#: .\conf.py:19
-msgid "month"
-msgstr "μήνας"
-
-#: .\conf.py:20
-msgid "day"
-msgstr "ημέρα"
-
-#: .\conf.py:21
-msgid "week day"
-msgstr "ημέρα της εβδομάδας"
-
-#: .\conf.py:22
-msgid "hour"
-msgstr "ώρα"
-
-#: .\conf.py:23
-msgid "minute"
-msgstr "λεπτό"
-
-#: .\conf.py:24
-msgid "second"
-msgstr "δευτερόλεπτο"
-
-#: .\conf.py:29 .\conf.py:30
-msgid "contains"
-msgstr "περιέχει"
-
-#: .\conf.py:31
-msgid "is in"
-msgstr "είναι εντός των"
-
-#: .\conf.py:32
-msgid "is greater than"
-msgstr "είναι μεγαλύτερο από"
-
-#: .\conf.py:33
-msgid "is greater than or equal to"
-msgstr "είναι μεγαλύτερο ή ίσο του"
-
-#: .\conf.py:34
-msgid "is less than"
-msgstr "είναι μικρότερο από"
-
-#: .\conf.py:35
-msgid "is less than or equal to"
-msgstr "είναι μικρότερο ή ίσο του"
-
-#: .\conf.py:36 .\conf.py:37
-msgid "starts with"
-msgstr "ξεκινά με"
-
-#: .\conf.py:38 .\conf.py:39
-msgid "ends with"
-msgstr "τελειώνει με"
-
-#: .\conf.py:40
-msgid "is in range"
-msgstr "είναι εντος του εύρους"
-
-#: .\conf.py:42 .\conf.py:43
-msgid "matches regex"
-msgstr "περιέχει regex"
-
-#: .\conf.py:44 .\conf.py:52
-msgid "search"
-msgstr "αναζήτηση"
-
-#: .\conf.py:47
-msgid "is contained by"
-msgstr "περιέχεται σε"
-
-#: .\conf.py:48
-msgid "overlaps"
-msgstr "επικαλύπτεται"
-
-#: .\conf.py:49
-msgid "has key"
-msgstr "έχει το κλειδί"
-
-#: .\conf.py:50
-msgid "has keys"
-msgstr "έχει τα κλειδιά"
-
-#: .\conf.py:51
-msgid "has any keys"
-msgstr "έχει οποιαδήποτε κλειδιά"
-
-#: .\fields.py:178
-msgid "Range query expects two values."
-msgstr "Το ερώτημα εύρους απαιτεί δύο τιμές,"
-
-#: .\filters.py:429
-msgid "Any date"
-msgstr "Οποιαδήποτε ημερομηνία"
-
-#: .\filters.py:430
-msgid "Today"
-msgstr "Σήμερα"
-
-#: .\filters.py:435
-msgid "Past 7 days"
-msgstr "Τις προηγούμενες 7 ημέρες"
-
-#: .\filters.py:439
-msgid "This month"
-msgstr "Αυτό το μήνα"
-
-#: .\filters.py:443
-msgid "This year"
-msgstr "Αυτό το έτος"
-
-#: .\filters.py:446
-msgid "Yesterday"
-msgstr "Χτες"
-
-#: .\filters.py:512
-msgid "Multiple values may be separated by commas."
-msgstr "Οι πολλαπλές τιμές πρέπει να διαχωρίζονται με κόμμα."
-
-#: .\filters.py:591
-#, python-format
-msgid "%s (descending)"
-msgstr "%s (φθίνουσα"
-
-#: .\filters.py:607
-msgid "Ordering"
-msgstr "Ταξινόμηση"
-
-#: .\rest_framework\filterset.py:30
-#: .\templates\django_filters\rest_framework\form.html:5
-msgid "Submit"
-msgstr "Υποβολή"
-
-#: .\templates\django_filters\rest_framework\crispy_form.html:4
-#: .\templates\django_filters\rest_framework\form.html:2
-msgid "Field filters"
-msgstr "Φίλτρα πεδίων"
-
-#: .\utils.py:224
-msgid "exclude"
-msgstr "απέκλεισε"
-
-#: .\widgets.py:57
-msgid "All"
-msgstr "Όλα"
-
-#: .\widgets.py:159
-msgid "Unknown"
-msgstr "Άγνωστο"
-
-#: .\widgets.py:160
-msgid "Yes"
-msgstr "Ναι"
-
-#: .\widgets.py:161
-msgid "No"
-msgstr "Όχι"
diff --git a/venv/Lib/site-packages/django_filters/locale/es/LC_MESSAGES/django.mo b/venv/Lib/site-packages/django_filters/locale/es/LC_MESSAGES/django.mo
deleted file mode 100644
index 3338ff7..0000000
Binary files a/venv/Lib/site-packages/django_filters/locale/es/LC_MESSAGES/django.mo and /dev/null differ
diff --git a/venv/Lib/site-packages/django_filters/locale/es/LC_MESSAGES/django.po b/venv/Lib/site-packages/django_filters/locale/es/LC_MESSAGES/django.po
deleted file mode 100644
index 5c4647f..0000000
--- a/venv/Lib/site-packages/django_filters/locale/es/LC_MESSAGES/django.po
+++ /dev/null
@@ -1,190 +0,0 @@
-# Django Filter translation.
-# Copyright (C) 2013
-# This file is distributed under the same license as the django_filter package.
-# Carlos Goce, 2017.
-# Nicolás Stuardo, 2020
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: \n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-12-12 17:58-0300\n"
-"PO-Revision-Date: 2020-12-12 17:57-0300\n"
-"Last-Translator: Nicolás Stuardo\n"
-"Language-Team: Spanish (España)\n"
-"Language: es\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Generator: Poedit 2.4.2\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#: conf.py:19
-msgid "date"
-msgstr "fecha"
-
-#: conf.py:20
-msgid "year"
-msgstr "año"
-
-#: conf.py:21
-msgid "month"
-msgstr "mes"
-
-#: conf.py:22
-msgid "day"
-msgstr "día"
-
-#: conf.py:23
-msgid "week day"
-msgstr "día de la semana"
-
-#: conf.py:24
-msgid "hour"
-msgstr "hora"
-
-#: conf.py:25
-msgid "minute"
-msgstr "minuto"
-
-#: conf.py:26
-msgid "second"
-msgstr "segundo"
-
-#: conf.py:31 conf.py:32
-msgid "contains"
-msgstr "contiene"
-
-#: conf.py:33
-msgid "is in"
-msgstr "presente en"
-
-#: conf.py:34
-msgid "is greater than"
-msgstr "mayor que"
-
-#: conf.py:35
-msgid "is greater than or equal to"
-msgstr "mayor o igual que"
-
-#: conf.py:36
-msgid "is less than"
-msgstr "menor que"
-
-#: conf.py:37
-msgid "is less than or equal to"
-msgstr "menor o igual que"
-
-#: conf.py:38 conf.py:39
-msgid "starts with"
-msgstr "comienza por"
-
-#: conf.py:40 conf.py:41
-msgid "ends with"
-msgstr "termina por"
-
-#: conf.py:42
-msgid "is in range"
-msgstr "en el rango"
-
-#: conf.py:44 conf.py:45
-msgid "matches regex"
-msgstr "coincide con la expresión regular"
-
-#: conf.py:46 conf.py:54
-msgid "search"
-msgstr "buscar"
-
-#: conf.py:49
-msgid "is contained by"
-msgstr "contenido en"
-
-#: conf.py:50
-msgid "overlaps"
-msgstr "solapado"
-
-#: conf.py:51
-msgid "has key"
-msgstr "contiene la clave"
-
-#: conf.py:52
-msgid "has keys"
-msgstr "contiene las claves"
-
-#: conf.py:53
-msgid "has any keys"
-msgstr "contiene alguna de las claves"
-
-#: fields.py:106
-msgid "Select a lookup."
-msgstr "Seleccione un operador de consulta."
-
-#: fields.py:198
-msgid "Range query expects two values."
-msgstr "Consultar un rango requiere dos valores."
-
-#: filters.py:420
-msgid "Today"
-msgstr "Hoy"
-
-#: filters.py:421
-msgid "Yesterday"
-msgstr "Ayer"
-
-#: filters.py:422
-msgid "Past 7 days"
-msgstr "Últimos 7 días"
-
-#: filters.py:423
-msgid "This month"
-msgstr "Este mes"
-
-#: filters.py:424
-msgid "This year"
-msgstr "Este año"
-
-#: filters.py:522
-msgid "Multiple values may be separated by commas."
-msgstr "Múltiples valores separados por comas."
-
-#: filters.py:695
-#, python-format
-msgid "%s (descending)"
-msgstr "%s (descendente)"
-
-#: filters.py:711
-msgid "Ordering"
-msgstr "Ordenado"
-
-#: rest_framework/filterset.py:31
-#: templates/django_filters/rest_framework/form.html:5
-msgid "Submit"
-msgstr "Enviar"
-
-#: templates/django_filters/rest_framework/crispy_form.html:4
-#: templates/django_filters/rest_framework/form.html:2
-msgid "Field filters"
-msgstr "Filtros de campo"
-
-#: utils.py:294
-msgid "exclude"
-msgstr "excluye"
-
-#: widgets.py:58
-msgid "All"
-msgstr "Todo"
-
-#: widgets.py:160
-msgid "Unknown"
-msgstr "Desconocido"
-
-#: widgets.py:161
-msgid "Yes"
-msgstr "Sí"
-
-#: widgets.py:162
-msgid "No"
-msgstr "No"
-
-#~ msgid "Any date"
-#~ msgstr "Cualquier fecha"
diff --git a/venv/Lib/site-packages/django_filters/locale/es_AR/LC_MESSAGES/django.mo b/venv/Lib/site-packages/django_filters/locale/es_AR/LC_MESSAGES/django.mo
deleted file mode 100644
index 7f4778a..0000000
Binary files a/venv/Lib/site-packages/django_filters/locale/es_AR/LC_MESSAGES/django.mo and /dev/null differ
diff --git a/venv/Lib/site-packages/django_filters/locale/es_AR/LC_MESSAGES/django.po b/venv/Lib/site-packages/django_filters/locale/es_AR/LC_MESSAGES/django.po
deleted file mode 100644
index 751dc8f..0000000
--- a/venv/Lib/site-packages/django_filters/locale/es_AR/LC_MESSAGES/django.po
+++ /dev/null
@@ -1,47 +0,0 @@
-# Django Filter translation.
-# Copyright (C) 2013
-# This file is distributed under the same license as the django_filter package.
-# Gonzalo Bustos, 2015.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: \n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2013-07-05 19:24+0200\n"
-"PO-Revision-Date: 2015-10-11 20:53-0300\n"
-"Last-Translator: Gonzalo Bustos\n"
-"Language-Team: Spanish (Argentina)\n"
-"Language: es_AR\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-"X-Generator: Poedit 1.6.10\n"
-
-#: filters.py:51
-msgid "This is an exclusion filter"
-msgstr "Este es un filtro de exclusión"
-
-#: filters.py:158
-msgid "Any date"
-msgstr "Cualquier fecha"
-
-#: filters.py:159
-msgid "Today"
-msgstr "Hoy"
-
-#: filters.py:164
-msgid "Past 7 days"
-msgstr "Últimos 7 días"
-
-#: filters.py:168
-msgid "This month"
-msgstr "Este mes"
-
-#: filters.py:172
-msgid "This year"
-msgstr "Este año"
-
-#: widgets.py:63
-msgid "All"
-msgstr "Todos"
diff --git a/venv/Lib/site-packages/django_filters/locale/fr/LC_MESSAGES/django.mo b/venv/Lib/site-packages/django_filters/locale/fr/LC_MESSAGES/django.mo
deleted file mode 100644
index 1b16915..0000000
Binary files a/venv/Lib/site-packages/django_filters/locale/fr/LC_MESSAGES/django.mo and /dev/null differ
diff --git a/venv/Lib/site-packages/django_filters/locale/fr/LC_MESSAGES/django.po b/venv/Lib/site-packages/django_filters/locale/fr/LC_MESSAGES/django.po
deleted file mode 100644
index 8926219..0000000
--- a/venv/Lib/site-packages/django_filters/locale/fr/LC_MESSAGES/django.po
+++ /dev/null
@@ -1,47 +0,0 @@
-# Django Filter translation.
-# Copyright (C) 2013
-# This file is distributed under the same license as the django_filter package.
-# Axel Haustant , 2013.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: PACKAGE VERSION\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2013-07-05 19:24+0200\n"
-"PO-Revision-Date: 2013-07-05 19:24+0200\n"
-"Last-Translator: Axel Haustant \n"
-"Language-Team: LANGUAGE \n"
-"Language: French\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=2; plural=(n > 1);\n"
-
-#: filters.py:51
-msgid "This is an exclusion filter"
-msgstr "Ceci est un filtre d'exclusion"
-
-#: filters.py:158
-msgid "Any date"
-msgstr "Toutes les dates"
-
-#: filters.py:159
-msgid "Today"
-msgstr "Aujourd'hui"
-
-#: filters.py:164
-msgid "Past 7 days"
-msgstr "7 derniers jours"
-
-#: filters.py:168
-msgid "This month"
-msgstr "Ce mois-ci"
-
-#: filters.py:172
-msgid "This year"
-msgstr "Cette année"
-
-#: widgets.py:63
-msgid "All"
-msgstr "Tous"
diff --git a/venv/Lib/site-packages/django_filters/locale/it/LC_MESSAGES/django.mo b/venv/Lib/site-packages/django_filters/locale/it/LC_MESSAGES/django.mo
deleted file mode 100644
index f53c4ea..0000000
Binary files a/venv/Lib/site-packages/django_filters/locale/it/LC_MESSAGES/django.mo and /dev/null differ
diff --git a/venv/Lib/site-packages/django_filters/locale/it/LC_MESSAGES/django.po b/venv/Lib/site-packages/django_filters/locale/it/LC_MESSAGES/django.po
deleted file mode 100644
index eb16bf2..0000000
--- a/venv/Lib/site-packages/django_filters/locale/it/LC_MESSAGES/django.po
+++ /dev/null
@@ -1,186 +0,0 @@
-# Django Filter translation.
-# Copyright (C) 2013
-# This file is distributed under the same license as the django_filter package.
-# Carlos Goce, 2017.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: \n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-01-26 20:32+0100\n"
-"PO-Revision-Date: 2017-01-26 20:52+0100\n"
-"Last-Translator: Carlos Goce\n"
-"Language-Team: Spanish (España)\n"
-"Language: es_ES\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Generator: Poedit 1.8.11\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#: conf.py:26
-msgid "date"
-msgstr "data"
-
-#: conf.py:27
-msgid "year"
-msgstr "anno"
-
-#: conf.py:28
-msgid "month"
-msgstr "mese"
-
-#: conf.py:29
-msgid "day"
-msgstr "giorno"
-
-#: conf.py:30
-msgid "week day"
-msgstr "giorno della settimana"
-
-#: conf.py:31
-msgid "hour"
-msgstr "ora"
-
-#: conf.py:32
-msgid "minute"
-msgstr "minuto"
-
-#: conf.py:33
-msgid "second"
-msgstr "secondo"
-
-#: conf.py:38 conf.py:39
-msgid "contains"
-msgstr "contiene"
-
-#: conf.py:40
-msgid "is in"
-msgstr "presente in"
-
-#: conf.py:41
-msgid "is greater than"
-msgstr "maggiore di"
-
-#: conf.py:42
-msgid "is greater than or equal to"
-msgstr "maggiore o uguale di"
-
-#: conf.py:43
-msgid "is less than"
-msgstr "minore di"
-
-#: conf.py:44
-msgid "is less than or equal to"
-msgstr "minore o uguale di"
-
-#: conf.py:45 conf.py:46
-msgid "starts with"
-msgstr "comincia per"
-
-#: conf.py:47 conf.py:48
-msgid "ends with"
-msgstr "termina per"
-
-#: conf.py:49
-msgid "is in range"
-msgstr "nell'intervallo"
-
-#: conf.py:51 conf.py:52
-msgid "matches regex"
-msgstr "coincide con la espressione regolare"
-
-#: conf.py:53 conf.py:61
-msgid "search"
-msgstr "cerca"
-
-#: conf.py:56
-msgid "is contained by"
-msgstr "contenuto in"
-
-#: conf.py:57
-msgid "overlaps"
-msgstr "sovrapposto"
-
-#: conf.py:58
-msgid "has key"
-msgstr "contiene la chiave"
-
-#: conf.py:59
-msgid "has keys"
-msgstr "contiene le chiavi"
-
-#: conf.py:60
-msgid "has any keys"
-msgstr "contiene qualsiasi chiave"
-
-#: fields.py:167
-msgid "Range query expects two values."
-msgstr "La query di intervallo richiede due valori"
-
-#: filters.py:443
-msgid "Any date"
-msgstr "Qualsiasi data"
-
-#: filters.py:444
-msgid "Today"
-msgstr "Oggi"
-
-#: filters.py:449
-msgid "Past 7 days"
-msgstr "Ultimi 7 giorni"
-
-#: filters.py:453
-msgid "This month"
-msgstr "Questo mese"
-
-#: filters.py:457
-msgid "This year"
-msgstr "Questo anno"
-
-#: filters.py:460
-msgid "Yesterday"
-msgstr "Ieri"
-
-#: filters.py:526
-msgid "Multiple values may be separated by commas."
-msgstr "Più valori separati da virgole."
-
-#: filters.py:605
-#, python-format
-msgid "%s (descending)"
-msgstr "%s (decrescente)"
-
-#: filters.py:621
-msgid "Ordering"
-msgstr "Ordinamento"
-
-#: utils.py:220
-msgid "exclude"
-msgstr "escludi"
-
-#: widgets.py:71
-msgid "All"
-msgstr "Tutti"
-
-#: widgets.py:119
-msgid "Unknown"
-msgstr "Sconosciuto"
-
-#: widgets.py:120
-msgid "Yes"
-msgstr "Sí"
-
-#: widgets.py:121
-msgid "No"
-msgstr "No"
-
-#: rest_framework/filterset.py:31
-#: templates/django_filters/rest_framework/form.html:5
-msgid "Submit"
-msgstr "Invia"
-
-#: templates/django_filters/rest_framework/crispy_form.html:4
-#: templates/django_filters/rest_framework/form.html:2
-msgid "Field filters"
-msgstr "Filtri del campo"
diff --git a/venv/Lib/site-packages/django_filters/locale/pl/LC_MESSAGES/django.mo b/venv/Lib/site-packages/django_filters/locale/pl/LC_MESSAGES/django.mo
deleted file mode 100644
index fdbf09d..0000000
Binary files a/venv/Lib/site-packages/django_filters/locale/pl/LC_MESSAGES/django.mo and /dev/null differ
diff --git a/venv/Lib/site-packages/django_filters/locale/pl/LC_MESSAGES/django.po b/venv/Lib/site-packages/django_filters/locale/pl/LC_MESSAGES/django.po
deleted file mode 100644
index 11abbad..0000000
--- a/venv/Lib/site-packages/django_filters/locale/pl/LC_MESSAGES/django.po
+++ /dev/null
@@ -1,202 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR , YEAR.
-#
-#: conf.py:35 conf.py:36 conf.py:49
-msgid ""
-msgstr ""
-"Project-Id-Version: django_filters 0.0.1\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-09-01 17:21+0000\n"
-"PO-Revision-Date: 2015-07-25 01:27+0100\n"
-"Last-Translator: Adam Dobrawy \n"
-"Language-Team: Adam Dobrawy \n"
-"Language: pl_PL\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n"
-"%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n"
-"%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
-"X-Generator: Poedit 1.5.4\n"
-
-#: conf.py:25
-#, fuzzy
-#| msgid "Any date"
-msgid "date"
-msgstr "Dowolna data"
-
-#: conf.py:26
-#, fuzzy
-#| msgid "This year"
-msgid "year"
-msgstr "Ten rok"
-
-#: conf.py:27
-#, fuzzy
-#| msgid "This month"
-msgid "month"
-msgstr "Ten miesiąc"
-
-#: conf.py:28
-#, fuzzy
-#| msgid "Today"
-msgid "day"
-msgstr "Dziś"
-
-#: conf.py:29
-msgid "week day"
-msgstr "dzień tygodnia"
-
-#: conf.py:30
-msgid "hour"
-msgstr "godzina"
-
-#: conf.py:31
-msgid "minute"
-msgstr "minuta"
-
-#: conf.py:32
-msgid "second"
-msgstr ""
-
-#: conf.py:37 conf.py:38
-msgid "contains"
-msgstr "zawiera"
-
-#: conf.py:39
-msgid "is in"
-msgstr "zawiera się w"
-
-#: conf.py:40
-msgid "is greater than"
-msgstr "powyżej"
-
-#: conf.py:41
-msgid "is greater than or equal to"
-msgstr "powyżej lub równe"
-
-#: conf.py:42
-msgid "is less than"
-msgstr "poniżej"
-
-#: conf.py:43
-msgid "is less than or equal to"
-msgstr "poniżej lub równe"
-
-#: conf.py:44 conf.py:45
-msgid "starts with"
-msgstr "zaczyna się od"
-
-#: conf.py:46 conf.py:47
-msgid "ends with"
-msgstr "kończy się na"
-
-#: conf.py:48
-msgid "is in range"
-msgstr "zawiera się w zakresie"
-
-#: conf.py:50 conf.py:51
-msgid "matches regex"
-msgstr "pasuje do wyrażenia regularnego"
-
-#: conf.py:52 conf.py:60
-msgid "search"
-msgstr "szukaj"
-
-#: conf.py:55
-msgid "is contained by"
-msgstr "zawiera się w"
-
-#: conf.py:56
-msgid "overlaps"
-msgstr ""
-
-#: conf.py:57
-msgid "has key"
-msgstr ""
-
-#: conf.py:58
-msgid "has keys"
-msgstr ""
-
-#: conf.py:59
-msgid "has any keys"
-msgstr ""
-
-#: fields.py:172
-msgid "Range query expects two values."
-msgstr ""
-
-#: filters.py:452
-msgid "Any date"
-msgstr "Dowolna data"
-
-#: filters.py:453
-msgid "Today"
-msgstr "Dziś"
-
-#: filters.py:458
-msgid "Past 7 days"
-msgstr "Ostatnie 7 dni"
-
-#: filters.py:462
-msgid "This month"
-msgstr "Ten miesiąc"
-
-#: filters.py:466
-msgid "This year"
-msgstr "Ten rok"
-
-#: filters.py:469
-msgid "Yesterday"
-msgstr "Wczoraj"
-
-#: filters.py:535
-msgid "Multiple values may be separated by commas."
-msgstr "Wiele wartości można rozdzielić przecinkami"
-
-#: filters.py:614
-#, python-format
-msgid "%s (descending)"
-msgstr "%s (malejąco)"
-
-#: filters.py:630
-msgid "Ordering"
-msgstr "Sortowanie"
-
-#: rest_framework/filterset.py:34
-#: templates/django_filters/rest_framework/form.html:5
-msgid "Submit"
-msgstr ""
-
-#: templates/django_filters/rest_framework/crispy_form.html:4
-#: templates/django_filters/rest_framework/form.html:2
-#, fuzzy
-#| msgid "Filter"
-msgid "Field filters"
-msgstr "Filter"
-
-#: utils.py:225
-msgid "exclude"
-msgstr ""
-
-#: widgets.py:66
-msgid "All"
-msgstr "Wszystko"
-
-#: widgets.py:173
-msgid "Unknown"
-msgstr ""
-
-#: widgets.py:174
-msgid "Yes"
-msgstr "Tak"
-
-#: widgets.py:175
-msgid "No"
-msgstr "Nie"
-
-#~ msgid "This is an exclusion filter"
-#~ msgstr "Jest to filtr wykluczający"
diff --git a/venv/Lib/site-packages/django_filters/locale/pt_BR/LC_MESSAGES/django.mo b/venv/Lib/site-packages/django_filters/locale/pt_BR/LC_MESSAGES/django.mo
deleted file mode 100644
index e9cc1a8..0000000
Binary files a/venv/Lib/site-packages/django_filters/locale/pt_BR/LC_MESSAGES/django.mo and /dev/null differ
diff --git a/venv/Lib/site-packages/django_filters/locale/pt_BR/LC_MESSAGES/django.po b/venv/Lib/site-packages/django_filters/locale/pt_BR/LC_MESSAGES/django.po
deleted file mode 100644
index 6f8eb0f..0000000
--- a/venv/Lib/site-packages/django_filters/locale/pt_BR/LC_MESSAGES/django.po
+++ /dev/null
@@ -1,186 +0,0 @@
-# Django Filter translation.
-# Copyright (C) 2017
-# This file is distributed under the same license as the django_filter package.
-# Anderson Scouto da Silva, 2017.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: \n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-12-11 22:04+0100\n"
-"PO-Revision-Date: 2017-12-11 22:07-0200\n"
-"Last-Translator: Anderson Scouto da Silva\n"
-"Language-Team: \n"
-"Language: pt_BR\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Generator: Poedit 1.8.13\n"
-"Plural-Forms: nplurals=2; plural=(n > 1);\n"
-
-#: conf.py:26
-msgid "date"
-msgstr "data"
-
-#: conf.py:27
-msgid "year"
-msgstr "ano"
-
-#: conf.py:28
-msgid "month"
-msgstr "mês"
-
-#: conf.py:29
-msgid "day"
-msgstr "dia"
-
-#: conf.py:30
-msgid "week day"
-msgstr "dia da semana"
-
-#: conf.py:31
-msgid "hour"
-msgstr "hora"
-
-#: conf.py:32
-msgid "minute"
-msgstr "minuto"
-
-#: conf.py:33
-msgid "second"
-msgstr "segundo"
-
-#: conf.py:38 conf.py:39
-msgid "contains"
-msgstr "contém"
-
-#: conf.py:40
-msgid "is in"
-msgstr "presente em"
-
-#: conf.py:41
-msgid "is greater than"
-msgstr "é maior que"
-
-#: conf.py:42
-msgid "is greater than or equal to"
-msgstr "é maior ou igual que"
-
-#: conf.py:43
-msgid "is less than"
-msgstr "é menor que"
-
-#: conf.py:44
-msgid "is less than or equal to"
-msgstr "é menor ou igual que"
-
-#: conf.py:45 conf.py:46
-msgid "starts with"
-msgstr "começa com"
-
-#: conf.py:47 conf.py:48
-msgid "ends with"
-msgstr "termina com"
-
-#: conf.py:49
-msgid "is in range"
-msgstr "está no range"
-
-#: conf.py:51 conf.py:52
-msgid "matches regex"
-msgstr "coincide com a expressão regular"
-
-#: conf.py:53 conf.py:61
-msgid "search"
-msgstr "buscar"
-
-#: conf.py:56
-msgid "is contained by"
-msgstr "está contido por"
-
-#: conf.py:57
-msgid "overlaps"
-msgstr "sobrepõe"
-
-#: conf.py:58
-msgid "has key"
-msgstr "contém a chave"
-
-#: conf.py:59
-msgid "has keys"
-msgstr "contém as chaves"
-
-#: conf.py:60
-msgid "has any keys"
-msgstr "contém uma das chaves"
-
-#: fields.py:167
-msgid "Range query expects two values."
-msgstr "Consulta por range requer dois valores."
-
-#: filters.py:443
-msgid "Any date"
-msgstr "Qualquer data"
-
-#: filters.py:444
-msgid "Today"
-msgstr "Hoje"
-
-#: filters.py:449
-msgid "Past 7 days"
-msgstr "Últimos 7 dias"
-
-#: filters.py:453
-msgid "This month"
-msgstr "Este mês"
-
-#: filters.py:457
-msgid "This year"
-msgstr "Este ano"
-
-#: filters.py:460
-msgid "Yesterday"
-msgstr "Ontem"
-
-#: filters.py:526
-msgid "Multiple values may be separated by commas."
-msgstr "Valores múltiplos podem ser separados por vírgulas."
-
-#: filters.py:605
-#, python-format
-msgid "%s (descending)"
-msgstr "%s (decrescente)"
-
-#: filters.py:621
-msgid "Ordering"
-msgstr "Ordenado"
-
-#: utils.py:220
-msgid "exclude"
-msgstr "excluir"
-
-#: widgets.py:71
-msgid "All"
-msgstr "Tudo"
-
-#: widgets.py:119
-msgid "Unknown"
-msgstr "Desconhecido"
-
-#: widgets.py:120
-msgid "Yes"
-msgstr "Sim"
-
-#: widgets.py:121
-msgid "No"
-msgstr "Não"
-
-#: rest_framework/filterset.py:31
-#: templates/django_filters/rest_framework/form.html:5
-msgid "Submit"
-msgstr "Enviar"
-
-#: templates/django_filters/rest_framework/crispy_form.html:4
-#: templates/django_filters/rest_framework/form.html:2
-msgid "Field filters"
-msgstr "Filtros de campo"
diff --git a/venv/Lib/site-packages/django_filters/locale/ru/LC_MESSAGES/django.mo b/venv/Lib/site-packages/django_filters/locale/ru/LC_MESSAGES/django.mo
deleted file mode 100644
index a4ff1ce..0000000
Binary files a/venv/Lib/site-packages/django_filters/locale/ru/LC_MESSAGES/django.mo and /dev/null differ
diff --git a/venv/Lib/site-packages/django_filters/locale/ru/LC_MESSAGES/django.po b/venv/Lib/site-packages/django_filters/locale/ru/LC_MESSAGES/django.po
deleted file mode 100644
index 4b54cb5..0000000
--- a/venv/Lib/site-packages/django_filters/locale/ru/LC_MESSAGES/django.po
+++ /dev/null
@@ -1,189 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR , YEAR.
-#
-#: conf.py:27 conf.py:28 conf.py:41
-msgid ""
-msgstr ""
-"Project-Id-Version: django-filter\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-01-24 11:03+0500\n"
-"PO-Revision-Date: 2016-09-29 11:47+0300\n"
-"Last-Translator: Mikhail Mitrofanov \n"
-"Language-Team: \n"
-"Language: ru\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
-"%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n"
-"%100>=11 && n%100<=14)? 2 : 3);\n"
-"X-Generator: Poedit 1.8.9\n"
-
-#: conf.py:17
-msgid "date"
-msgstr "дата"
-
-#: conf.py:18
-msgid "year"
-msgstr "год"
-
-#: conf.py:19
-msgid "month"
-msgstr "месяц"
-
-#: conf.py:20
-msgid "day"
-msgstr "день"
-
-#: conf.py:21
-msgid "week day"
-msgstr "день недели"
-
-#: conf.py:22
-msgid "hour"
-msgstr "час"
-
-#: conf.py:23
-msgid "minute"
-msgstr "минута"
-
-#: conf.py:24
-msgid "second"
-msgstr "секунда"
-
-#: conf.py:29 conf.py:30
-msgid "contains"
-msgstr "содержит"
-
-#: conf.py:31
-msgid "is in"
-msgstr "в"
-
-#: conf.py:32
-msgid "is greater than"
-msgstr "больше чем"
-
-#: conf.py:33
-msgid "is greater than or equal to"
-msgstr "больше или равно"
-
-#: conf.py:34
-msgid "is less than"
-msgstr "меньше чем"
-
-#: conf.py:35
-msgid "is less than or equal to"
-msgstr "меньше или равно"
-
-#: conf.py:36 conf.py:37
-msgid "starts with"
-msgstr "начинается"
-
-#: conf.py:38 conf.py:39
-msgid "ends with"
-msgstr "заканчивается"
-
-#: conf.py:40
-msgid "is in range"
-msgstr "в диапазоне"
-
-#: conf.py:42 conf.py:43
-msgid "matches regex"
-msgstr "соответствует регулярному выражению"
-
-#: conf.py:44 conf.py:52
-msgid "search"
-msgstr "поиск"
-
-#: conf.py:47
-msgid "is contained by"
-msgstr "содержится в"
-
-#: conf.py:48
-msgid "overlaps"
-msgstr "перекрывается"
-
-#: conf.py:49
-msgid "has key"
-msgstr "имеет ключ"
-
-#: conf.py:50
-msgid "has keys"
-msgstr "имеет ключи"
-
-#: conf.py:51
-msgid "has any keys"
-msgstr "имеет любые ключи"
-
-#: fields.py:178
-msgid "Range query expects two values."
-msgstr "Запрос диапазона ожидает два значения."
-
-#: filters.py:429
-msgid "Any date"
-msgstr "Любая дата"
-
-#: filters.py:430
-msgid "Today"
-msgstr "Сегодня"
-
-#: filters.py:435
-msgid "Past 7 days"
-msgstr "Прошедшие 7 дней"
-
-#: filters.py:439
-msgid "This month"
-msgstr "За этот месяц"
-
-#: filters.py:443
-msgid "This year"
-msgstr "В этом году"
-
-#: filters.py:446
-msgid "Yesterday"
-msgstr "Вчера"
-
-#: filters.py:512
-msgid "Multiple values may be separated by commas."
-msgstr "Несколько значений могут быть разделены запятыми."
-
-#: filters.py:591
-#, python-format
-msgid "%s (descending)"
-msgstr "%s (по убыванию)"
-
-#: filters.py:607
-msgid "Ordering"
-msgstr "Порядок"
-
-#: rest_framework/filterset.py:30
-#: templates/django_filters/rest_framework/form.html:5
-msgid "Submit"
-msgstr "Отправить"
-
-#: templates/django_filters/rest_framework/crispy_form.html:4
-#: templates/django_filters/rest_framework/form.html:2
-msgid "Field filters"
-msgstr "Фильтры по полям"
-
-#: utils.py:224
-msgid "exclude"
-msgstr "исключая"
-
-#: widgets.py:57
-msgid "All"
-msgstr "Все"
-
-#: widgets.py:159
-msgid "Unknown"
-msgstr "Не задано"
-
-#: widgets.py:160
-msgid "Yes"
-msgstr "Да"
-
-#: widgets.py:161
-msgid "No"
-msgstr "Нет"
diff --git a/venv/Lib/site-packages/django_filters/locale/sk/LC_MESSAGES/django.mo b/venv/Lib/site-packages/django_filters/locale/sk/LC_MESSAGES/django.mo
deleted file mode 100644
index d9c67d8..0000000
Binary files a/venv/Lib/site-packages/django_filters/locale/sk/LC_MESSAGES/django.mo and /dev/null differ
diff --git a/venv/Lib/site-packages/django_filters/locale/sk/LC_MESSAGES/django.po b/venv/Lib/site-packages/django_filters/locale/sk/LC_MESSAGES/django.po
deleted file mode 100644
index db19f3c..0000000
--- a/venv/Lib/site-packages/django_filters/locale/sk/LC_MESSAGES/django.po
+++ /dev/null
@@ -1,188 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR , YEAR.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: PACKAGE VERSION\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-03-25 19:11+0200\n"
-"PO-Revision-Date: 2018-03-25 19:18+0058\n"
-"Last-Translator: b'Erik Telepovsky '\n"
-"Language-Team: LANGUAGE \n"
-"Language: \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=4; plural=(n % 1 == 0 && n == 1 ? 0 : n % 1 == 0 && n "
-">= 2 && n <= 4 ? 1 : n % 1 != 0 ? 2: 3);\n"
-"X-Translated-Using: django-rosetta 0.8.1\n"
-
-#: conf.py:17
-msgid "date"
-msgstr "dátum"
-
-#: conf.py:18
-msgid "year"
-msgstr "rok"
-
-#: conf.py:19
-msgid "month"
-msgstr "mesiac"
-
-#: conf.py:20
-msgid "day"
-msgstr "deň"
-
-#: conf.py:21
-msgid "week day"
-msgstr "deň týždňa"
-
-#: conf.py:22
-msgid "hour"
-msgstr "hodina"
-
-#: conf.py:23
-msgid "minute"
-msgstr "minúta"
-
-#: conf.py:24
-msgid "second"
-msgstr "sekunda"
-
-#: conf.py:29 conf.py:30
-msgid "contains"
-msgstr "obsahuje"
-
-#: conf.py:31
-msgid "is in"
-msgstr "je v"
-
-#: conf.py:32
-msgid "is greater than"
-msgstr "je vačší než"
-
-#: conf.py:33
-msgid "is greater than or equal to"
-msgstr "je vačší alebo rovný ako"
-
-#: conf.py:34
-msgid "is less than"
-msgstr "je menší než"
-
-#: conf.py:35
-msgid "is less than or equal to"
-msgstr "je menší alebo rovný ako"
-
-#: conf.py:36 conf.py:37
-msgid "starts with"
-msgstr "začína s"
-
-#: conf.py:38 conf.py:39
-msgid "ends with"
-msgstr "končí s"
-
-#: conf.py:40
-msgid "is in range"
-msgstr "je v rozsahu"
-
-#: conf.py:42 conf.py:43
-msgid "matches regex"
-msgstr "spĺňa regex"
-
-#: conf.py:44 conf.py:52
-msgid "search"
-msgstr "hľadať"
-
-#: conf.py:47
-msgid "is contained by"
-msgstr "je obsiahnutý"
-
-#: conf.py:48
-msgid "overlaps"
-msgstr "presahuje"
-
-#: conf.py:49
-msgid "has key"
-msgstr "má kľúč"
-
-#: conf.py:50
-msgid "has keys"
-msgstr "má kľúče"
-
-#: conf.py:51
-msgid "has any keys"
-msgstr "má akékoľvek kľúče"
-
-#: fields.py:178
-msgid "Range query expects two values."
-msgstr "Rozsah očakáva dve hodnoty."
-
-#: filters.py:429
-msgid "Any date"
-msgstr "Akýkoľvek dátum"
-
-#: filters.py:430
-msgid "Today"
-msgstr "Dnes"
-
-#: filters.py:435
-msgid "Past 7 days"
-msgstr "Posledných 7 dní"
-
-#: filters.py:439
-msgid "This month"
-msgstr "Tento mesiac"
-
-#: filters.py:443
-msgid "This year"
-msgstr "Tento rok"
-
-#: filters.py:446
-msgid "Yesterday"
-msgstr "Včera"
-
-#: filters.py:512
-msgid "Multiple values may be separated by commas."
-msgstr "Viacero hodnôt môže byť oddelených čiarkami."
-
-#: filters.py:591
-#, python-format
-msgid "%s (descending)"
-msgstr "%s (klesajúco)"
-
-#: filters.py:607
-msgid "Ordering"
-msgstr "Zoradenie"
-
-#: rest_framework/filterset.py:30
-#: templates/django_filters/rest_framework/form.html:5
-msgid "Submit"
-msgstr "Potvrdiť"
-
-#: templates/django_filters/rest_framework/crispy_form.html:4
-#: templates/django_filters/rest_framework/form.html:2
-msgid "Field filters"
-msgstr "Filtre poľa"
-
-#: utils.py:224
-msgid "exclude"
-msgstr "neobsahuje"
-
-#: widgets.py:57
-msgid "All"
-msgstr "Všetky"
-
-#: widgets.py:159
-msgid "Unknown"
-msgstr "Neznáme"
-
-#: widgets.py:160
-msgid "Yes"
-msgstr "Áno"
-
-#: widgets.py:161
-msgid "No"
-msgstr "Nie"
diff --git a/venv/Lib/site-packages/django_filters/locale/uk/LC_MESSAGES/django.mo b/venv/Lib/site-packages/django_filters/locale/uk/LC_MESSAGES/django.mo
deleted file mode 100644
index e16148d..0000000
Binary files a/venv/Lib/site-packages/django_filters/locale/uk/LC_MESSAGES/django.mo and /dev/null differ
diff --git a/venv/Lib/site-packages/django_filters/locale/uk/LC_MESSAGES/django.po b/venv/Lib/site-packages/django_filters/locale/uk/LC_MESSAGES/django.po
deleted file mode 100644
index 0314b7f..0000000
--- a/venv/Lib/site-packages/django_filters/locale/uk/LC_MESSAGES/django.po
+++ /dev/null
@@ -1,184 +0,0 @@
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: django-filter\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-01-24 11:03+0500\n"
-"PO-Revision-Date: 2016-09-29 11:47+0300\n"
-"Last-Translator: Eugena Mikhaylikova \n"
-"Language-Team: TextTempearture\n"
-"Language: uk\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != "
-"11 ? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % "
-"100 > 14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || "
-"(n % 100 >=11 && n % 100 <=14 )) ? 2: 3);\n"
-"X-Generator: Poedit 1.8.9\n"
-
-#: conf.py:17
-msgid "date"
-msgstr "дата"
-
-#: conf.py:18
-msgid "year"
-msgstr "рік"
-
-#: conf.py:19
-msgid "month"
-msgstr "місяць"
-
-#: conf.py:20
-msgid "day"
-msgstr "день"
-
-#: conf.py:21
-msgid "week day"
-msgstr "день тижня"
-
-#: conf.py:22
-msgid "hour"
-msgstr "година"
-
-#: conf.py:23
-msgid "minute"
-msgstr "хвилина"
-
-#: conf.py:24
-msgid "second"
-msgstr "секунда"
-
-#: conf.py:29 conf.py:30
-msgid "contains"
-msgstr "містить"
-
-#: conf.py:31
-msgid "is in"
-msgstr "в"
-
-#: conf.py:32
-msgid "is greater than"
-msgstr "більше ніж"
-
-#: conf.py:33
-msgid "is greater than or equal to"
-msgstr "більше або дорівнює"
-
-#: conf.py:34
-msgid "is less than"
-msgstr "менше ніж"
-
-#: conf.py:35
-msgid "is less than or equal to"
-msgstr "менше або дорівнює"
-
-#: conf.py:36 conf.py:37
-msgid "starts with"
-msgstr "починається"
-
-#: conf.py:38 conf.py:39
-msgid "ends with"
-msgstr "закінчується"
-
-#: conf.py:40
-msgid "is in range"
-msgstr "в діапазоні"
-
-#: conf.py:42 conf.py:43
-msgid "matches regex"
-msgstr "відповідає регулярному виразу"
-
-#: conf.py:44 conf.py:52
-msgid "search"
-msgstr "пошук"
-
-#: conf.py:47
-msgid "is contained by"
-msgstr "міститься в"
-
-#: conf.py:48
-msgid "overlaps"
-msgstr "перекривається"
-
-#: conf.py:49
-msgid "has key"
-msgstr "має ключ"
-
-#: conf.py:50
-msgid "has keys"
-msgstr "має ключі"
-
-#: conf.py:51
-msgid "has any keys"
-msgstr "має будь-які ключі"
-
-#: fields.py:178
-msgid "Range query expects two values."
-msgstr "Запит діапазону очікує два значення."
-
-#: filters.py:429
-msgid "Any date"
-msgstr "Будь-яка дата"
-
-#: filters.py:430
-msgid "Today"
-msgstr "Сьогодні"
-
-#: filters.py:435
-msgid "Past 7 days"
-msgstr "Минулі 7 днів"
-
-#: filters.py:439
-msgid "This month"
-msgstr "За цей місяць"
-
-#: filters.py:443
-msgid "This year"
-msgstr "В цьому році"
-
-#: filters.py:446
-msgid "Yesterday"
-msgstr "Вчора"
-
-#: filters.py:512
-msgid "Multiple values may be separated by commas."
-msgstr "Кілька значень можуть бути розділені комами."
-
-#: filters.py:591
-msgid "%s (descending)"
-msgstr "%s (по спадаючій)"
-
-#: filters.py:607
-msgid "Ordering"
-msgstr "Порядок"
-
-#: rest_framework/filterset.py:30
-#: templates/django_filters/rest_framework/form.html:5
-msgid "Submit"
-msgstr "Відправити"
-
-#: templates/django_filters/rest_framework/crispy_form.html:4
-#: templates/django_filters/rest_framework/form.html:2
-msgid "Field filters"
-msgstr "Фільтри по полях"
-
-#: utils.py:224
-msgid "exclude"
-msgstr "виключаючи"
-
-#: widgets.py:57
-msgid "All"
-msgstr "Усе"
-
-#: widgets.py:159
-msgid "Unknown"
-msgstr "Не задано"
-
-#: widgets.py:160
-msgid "Yes"
-msgstr "Так"
-
-#: widgets.py:161
-msgid "No"
-msgstr "Немає"
diff --git a/venv/Lib/site-packages/django_filters/locale/zh_CN/LC_MESSAGES/django.mo b/venv/Lib/site-packages/django_filters/locale/zh_CN/LC_MESSAGES/django.mo
deleted file mode 100644
index 7047a5e..0000000
Binary files a/venv/Lib/site-packages/django_filters/locale/zh_CN/LC_MESSAGES/django.mo and /dev/null differ
diff --git a/venv/Lib/site-packages/django_filters/locale/zh_CN/LC_MESSAGES/django.po b/venv/Lib/site-packages/django_filters/locale/zh_CN/LC_MESSAGES/django.po
deleted file mode 100644
index de067b9..0000000
--- a/venv/Lib/site-packages/django_filters/locale/zh_CN/LC_MESSAGES/django.po
+++ /dev/null
@@ -1,64 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# Kane Blueriver , 2016.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: PACKAGE VERSION\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2016-01-30 17:39+0800\n"
-"PO-Revision-Date: 2016-01-30 17:50+0800\n"
-"Last-Translator: Kane Blueriver \n"
-"Language-Team: LANGUAGE \n"
-"Language: \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=1; plural=0;\n"
-
-#: filters.py:62
-msgid "This is an exclusion filter"
-msgstr "未启用该过滤器"
-
-#: filters.py:62
-msgid "Filter"
-msgstr "过滤器"
-
-#: filters.py:264
-msgid "Any date"
-msgstr "任何时刻"
-
-#: filters.py:265
-msgid "Today"
-msgstr "今日"
-
-#: filters.py:270
-msgid "Past 7 days"
-msgstr "过去 7 日"
-
-#: filters.py:274
-msgid "This month"
-msgstr "本月"
-
-#: filters.py:278
-msgid "This year"
-msgstr "今年"
-
-#: filters.py:281
-msgid "Yesterday"
-msgstr "昨日"
-
-#: filterset.py:398 filterset.py:409
-#, python-format
-msgid "%s (descending)"
-msgstr "%s(降序)"
-
-#: filterset.py:411
-msgid "Ordering"
-msgstr "排序"
-
-#: widgets.py:60
-msgid "All"
-msgstr "全部"
diff --git a/venv/Lib/site-packages/django_filters/rest_framework/__init__.py b/venv/Lib/site-packages/django_filters/rest_framework/__init__.py
deleted file mode 100644
index 4ffc408..0000000
--- a/venv/Lib/site-packages/django_filters/rest_framework/__init__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-# flake8: noqa
-from .backends import DjangoFilterBackend
-from .filters import *
-from .filterset import FilterSet
diff --git a/venv/Lib/site-packages/django_filters/rest_framework/backends.py b/venv/Lib/site-packages/django_filters/rest_framework/backends.py
deleted file mode 100644
index 835a67f..0000000
--- a/venv/Lib/site-packages/django_filters/rest_framework/backends.py
+++ /dev/null
@@ -1,170 +0,0 @@
-import warnings
-
-from django.template import loader
-from django.utils.deprecation import RenameMethodsBase
-
-from .. import compat, utils
-from . import filters, filterset
-
-
-# TODO: remove metaclass in 2.1
-class RenameAttributes(utils.RenameAttributesBase, RenameMethodsBase):
- renamed_attributes = (
- ('default_filter_set', 'filterset_base', utils.MigrationNotice),
- )
- renamed_methods = (
- ('get_filter_class', 'get_filterset_class', utils.MigrationNotice),
- )
-
-
-class DjangoFilterBackend(metaclass=RenameAttributes):
- filterset_base = filterset.FilterSet
- raise_exception = True
-
- @property
- def template(self):
- if compat.is_crispy():
- return 'django_filters/rest_framework/crispy_form.html'
- return 'django_filters/rest_framework/form.html'
-
- def get_filterset(self, request, queryset, view):
- filterset_class = self.get_filterset_class(view, queryset)
- if filterset_class is None:
- return None
-
- kwargs = self.get_filterset_kwargs(request, queryset, view)
- return filterset_class(**kwargs)
-
- def get_filterset_class(self, view, queryset=None):
- """
- Return the `FilterSet` class used to filter the queryset.
- """
- filterset_class = getattr(view, 'filterset_class', None)
- filterset_fields = getattr(view, 'filterset_fields', None)
-
- # TODO: remove assertion in 2.1
- if filterset_class is None and hasattr(view, 'filter_class'):
- utils.deprecate(
- "`%s.filter_class` attribute should be renamed `filterset_class`."
- % view.__class__.__name__)
- filterset_class = getattr(view, 'filter_class', None)
-
- # TODO: remove assertion in 2.1
- if filterset_fields is None and hasattr(view, 'filter_fields'):
- utils.deprecate(
- "`%s.filter_fields` attribute should be renamed `filterset_fields`."
- % view.__class__.__name__)
- filterset_fields = getattr(view, 'filter_fields', None)
-
- if filterset_class:
- filterset_model = filterset_class._meta.model
-
- # FilterSets do not need to specify a Meta class
- if filterset_model and queryset is not None:
- assert issubclass(queryset.model, filterset_model), \
- 'FilterSet model %s does not match queryset model %s' % \
- (filterset_model, queryset.model)
-
- return filterset_class
-
- if filterset_fields and queryset is not None:
- MetaBase = getattr(self.filterset_base, 'Meta', object)
-
- class AutoFilterSet(self.filterset_base):
- class Meta(MetaBase):
- model = queryset.model
- fields = filterset_fields
-
- return AutoFilterSet
-
- return None
-
- def get_filterset_kwargs(self, request, queryset, view):
- return {
- 'data': request.query_params,
- 'queryset': queryset,
- 'request': request,
- }
-
- def filter_queryset(self, request, queryset, view):
- filterset = self.get_filterset(request, queryset, view)
- if filterset is None:
- return queryset
-
- if not filterset.is_valid() and self.raise_exception:
- raise utils.translate_validation(filterset.errors)
- return filterset.qs
-
- def to_html(self, request, queryset, view):
- filterset = self.get_filterset(request, queryset, view)
- if filterset is None:
- return None
-
- template = loader.get_template(self.template)
- context = {'filter': filterset}
- return template.render(context, request)
-
- def get_coreschema_field(self, field):
- if isinstance(field, filters.NumberFilter):
- field_cls = compat.coreschema.Number
- else:
- field_cls = compat.coreschema.String
- return field_cls(
- description=str(field.extra.get('help_text', ''))
- )
-
- def get_schema_fields(self, view):
- # This is not compatible with widgets where the query param differs from the
- # filter's attribute name. Notably, this includes `MultiWidget`, where query
- # params will be of the format `_0`, `_1`, etc...
- assert compat.coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`'
- assert compat.coreschema is not None, 'coreschema must be installed to use `get_schema_fields()`'
-
- try:
- queryset = view.get_queryset()
- except Exception:
- queryset = None
- warnings.warn(
- "{} is not compatible with schema generation".format(view.__class__)
- )
-
- filterset_class = self.get_filterset_class(view, queryset)
-
- return [] if not filterset_class else [
- compat.coreapi.Field(
- name=field_name,
- required=field.extra['required'],
- location='query',
- schema=self.get_coreschema_field(field)
- ) for field_name, field in filterset_class.base_filters.items()
- ]
-
- def get_schema_operation_parameters(self, view):
- try:
- queryset = view.get_queryset()
- except Exception:
- queryset = None
- warnings.warn(
- "{} is not compatible with schema generation".format(view.__class__)
- )
-
- filterset_class = self.get_filterset_class(view, queryset)
-
- if not filterset_class:
- return []
-
- parameters = []
- for field_name, field in filterset_class.base_filters.items():
- parameter = {
- 'name': field_name,
- 'required': field.extra['required'],
- 'in': 'query',
- 'description': field.label if field.label is not None else field_name,
- 'schema': {
- 'type': 'string',
- },
- }
- if field.extra and 'choices' in field.extra:
- parameter['schema']['enum'] = [c[0] for c in field.extra['choices']]
- parameters.append(parameter)
- return parameters
diff --git a/venv/Lib/site-packages/django_filters/rest_framework/filters.py b/venv/Lib/site-packages/django_filters/rest_framework/filters.py
deleted file mode 100644
index 4c5755e..0000000
--- a/venv/Lib/site-packages/django_filters/rest_framework/filters.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from django_filters import filters
-
-from ..filters import * # noqa
-from ..widgets import BooleanWidget
-
-__all__ = filters.__all__
-
-
-class BooleanFilter(filters.BooleanFilter):
- def __init__(self, *args, **kwargs):
- kwargs.setdefault('widget', BooleanWidget)
-
- super().__init__(*args, **kwargs)
diff --git a/venv/Lib/site-packages/django_filters/rest_framework/filterset.py b/venv/Lib/site-packages/django_filters/rest_framework/filterset.py
deleted file mode 100644
index 8c42304..0000000
--- a/venv/Lib/site-packages/django_filters/rest_framework/filterset.py
+++ /dev/null
@@ -1,40 +0,0 @@
-from copy import deepcopy
-
-from django.db import models
-from django.utils.translation import gettext_lazy as _
-
-from django_filters import filterset
-
-from .. import compat
-from .filters import BooleanFilter, IsoDateTimeFilter
-
-FILTER_FOR_DBFIELD_DEFAULTS = deepcopy(filterset.FILTER_FOR_DBFIELD_DEFAULTS)
-FILTER_FOR_DBFIELD_DEFAULTS.update({
- models.DateTimeField: {'filter_class': IsoDateTimeFilter},
- models.BooleanField: {'filter_class': BooleanFilter},
- models.NullBooleanField: {'filter_class': BooleanFilter},
-})
-
-
-class FilterSet(filterset.FilterSet):
- FILTER_DEFAULTS = FILTER_FOR_DBFIELD_DEFAULTS
-
- @property
- def form(self):
- form = super().form
-
- if compat.is_crispy():
- from crispy_forms.helper import FormHelper
- from crispy_forms.layout import Layout, Submit
-
- layout_components = list(form.fields.keys()) + [
- Submit('', _('Submit'), css_class='btn-default'),
- ]
- helper = FormHelper()
- helper.form_method = 'GET'
- helper.template_pack = 'bootstrap3'
- helper.layout = Layout(*layout_components)
-
- form.helper = helper
-
- return form
diff --git a/venv/Lib/site-packages/django_filters/templates/django_filters/rest_framework/crispy_form.html b/venv/Lib/site-packages/django_filters/templates/django_filters/rest_framework/crispy_form.html
deleted file mode 100644
index 171767c..0000000
--- a/venv/Lib/site-packages/django_filters/templates/django_filters/rest_framework/crispy_form.html
+++ /dev/null
@@ -1,5 +0,0 @@
-{% load crispy_forms_tags %}
-{% load i18n %}
-
-
-
diff --git a/venv/Lib/site-packages/django_filters/templates/django_filters/widgets/multiwidget.html b/venv/Lib/site-packages/django_filters/templates/django_filters/widgets/multiwidget.html
deleted file mode 100644
index 089ddb2..0000000
--- a/venv/Lib/site-packages/django_filters/templates/django_filters/widgets/multiwidget.html
+++ /dev/null
@@ -1 +0,0 @@
-{% for widget in widget.subwidgets %}{% include widget.template_name %}{% if forloop.first %}-{% endif %}{% endfor %}
diff --git a/venv/Lib/site-packages/django_filters/utils.py b/venv/Lib/site-packages/django_filters/utils.py
deleted file mode 100644
index 67a071a..0000000
--- a/venv/Lib/site-packages/django_filters/utils.py
+++ /dev/null
@@ -1,328 +0,0 @@
-import warnings
-from collections import OrderedDict
-
-from django.conf import settings
-from django.core.exceptions import FieldDoesNotExist, FieldError
-from django.db import models
-from django.db.models.constants import LOOKUP_SEP
-from django.db.models.expressions import Expression
-from django.db.models.fields.related import ForeignObjectRel, RelatedField
-from django.utils import timezone
-from django.utils.encoding import force_str
-from django.utils.text import capfirst
-from django.utils.translation import gettext as _
-
-from .exceptions import FieldLookupError
-
-
-def deprecate(msg, level_modifier=0):
- warnings.warn(msg, MigrationNotice, stacklevel=3 + level_modifier)
-
-
-class MigrationNotice(DeprecationWarning):
- url = 'https://django-filter.readthedocs.io/en/main/guide/migration.html'
-
- def __init__(self, message):
- super().__init__('%s See: %s' % (message, self.url))
-
-
-class RenameAttributesBase(type):
- """
- Handles the deprecation paths when renaming an attribute.
-
- It does the following:
- - Defines accessors that redirect to the renamed attributes.
- - Complain whenever an old attribute is accessed.
-
- This is conceptually based on `django.utils.deprecation.RenameMethodsBase`.
- """
- renamed_attributes = ()
-
- def __new__(metacls, name, bases, attrs):
- # remove old attributes before creating class
- old_names = [r[0] for r in metacls.renamed_attributes]
- old_names = [name for name in old_names if name in attrs]
- old_attrs = {name: attrs.pop(name) for name in old_names}
-
- # get a handle to any accessors defined on the class
- cls_getattr = attrs.pop('__getattr__', None)
- cls_setattr = attrs.pop('__setattr__', None)
-
- new_class = super().__new__(metacls, name, bases, attrs)
-
- def __getattr__(self, name):
- name = type(self).get_name(name)
- if cls_getattr is not None:
- return cls_getattr(self, name)
- elif hasattr(super(new_class, self), '__getattr__'):
- return super(new_class, self).__getattr__(name)
- return self.__getattribute__(name)
-
- def __setattr__(self, name, value):
- name = type(self).get_name(name)
- if cls_setattr is not None:
- return cls_setattr(self, name, value)
- return super(new_class, self).__setattr__(name, value)
-
- new_class.__getattr__ = __getattr__
- new_class.__setattr__ = __setattr__
-
- # set renamed attributes
- for name, value in old_attrs.items():
- setattr(new_class, name, value)
-
- return new_class
-
- def get_name(metacls, name):
- """
- Get the real attribute name. If the attribute has been renamed,
- the new name will be returned and a deprecation warning issued.
- """
- for renamed_attribute in metacls.renamed_attributes:
- old_name, new_name, deprecation_warning = renamed_attribute
-
- if old_name == name:
- warnings.warn("`%s.%s` attribute should be renamed `%s`."
- % (metacls.__name__, old_name, new_name),
- deprecation_warning, 3)
- return new_name
-
- return name
-
- def __getattr__(metacls, name):
- return super().__getattribute__(metacls.get_name(name))
-
- def __setattr__(metacls, name, value):
- return super().__setattr__(metacls.get_name(name), value)
-
-
-def try_dbfield(fn, field_class):
- """
- Try ``fn`` with the DB ``field_class`` by walking its
- MRO until a result is found.
-
- ex::
- _try_dbfield(field_dict.get, models.CharField)
-
- """
- # walk the mro, as field_class could be a derived model field.
- for cls in field_class.mro():
- # skip if cls is models.Field
- if cls is models.Field:
- continue
-
- data = fn(cls)
- if data:
- return data
-
-
-def get_all_model_fields(model):
- opts = model._meta
-
- return [
- f.name for f in sorted(opts.fields + opts.many_to_many)
- if not isinstance(f, models.AutoField) and
- not (getattr(f.remote_field, 'parent_link', False))
- ]
-
-
-def get_model_field(model, field_name):
- """
- Get a ``model`` field, traversing relationships
- in the ``field_name``.
-
- ex::
-
- f = get_model_field(Book, 'author__first_name')
-
- """
- fields = get_field_parts(model, field_name)
- return fields[-1] if fields else None
-
-
-def get_field_parts(model, field_name):
- """
- Get the field parts that represent the traversable relationships from the
- base ``model`` to the final field, described by ``field_name``.
-
- ex::
-
- >>> parts = get_field_parts(Book, 'author__first_name')
- >>> [p.verbose_name for p in parts]
- ['author', 'first name']
-
- """
- parts = field_name.split(LOOKUP_SEP)
- opts = model._meta
- fields = []
-
- # walk relationships
- for name in parts:
- try:
- field = opts.get_field(name)
- except FieldDoesNotExist:
- return None
-
- fields.append(field)
- try:
- if isinstance(field, RelatedField):
- opts = field.remote_field.model._meta
- elif isinstance(field, ForeignObjectRel):
- opts = field.related_model._meta
- except AttributeError:
- # Lazy relationships are not resolved until registry is populated.
- raise RuntimeError(
- "Unable to resolve relationship `%s` for `%s`. Django is most "
- "likely not initialized, and its apps registry not populated. "
- "Ensure Django has finished setup before loading `FilterSet`s."
- % (field_name, model._meta.label))
-
- return fields
-
-
-def resolve_field(model_field, lookup_expr):
- """
- Resolves a ``lookup_expr`` into its final output field, given
- the initial ``model_field``. The lookup expression should only contain
- transforms and lookups, not intermediary model field parts.
-
- Note:
- This method is based on django.db.models.sql.query.Query.build_lookup
-
- For more info on the lookup API:
- https://docs.djangoproject.com/en/stable/ref/models/lookups/
-
- """
- query = model_field.model._default_manager.all().query
- lhs = Expression(model_field)
- lookups = lookup_expr.split(LOOKUP_SEP)
-
- assert len(lookups) > 0
-
- try:
- while lookups:
- name = lookups[0]
- args = (lhs, name)
- # If there is just one part left, try first get_lookup() so
- # that if the lhs supports both transform and lookup for the
- # name, then lookup will be picked.
- if len(lookups) == 1:
- final_lookup = lhs.get_lookup(name)
- if not final_lookup:
- # We didn't find a lookup. We are going to interpret
- # the name as transform, and do an Exact lookup against
- # it.
- lhs = query.try_transform(*args)
- final_lookup = lhs.get_lookup('exact')
- return lhs.output_field, final_lookup.lookup_name
- lhs = query.try_transform(*args)
- lookups = lookups[1:]
- except FieldError as e:
- raise FieldLookupError(model_field, lookup_expr) from e
-
-
-def handle_timezone(value, is_dst=None):
- if settings.USE_TZ and timezone.is_naive(value):
- return timezone.make_aware(value, timezone.get_current_timezone(), is_dst)
- elif not settings.USE_TZ and timezone.is_aware(value):
- return timezone.make_naive(value, timezone.utc)
- return value
-
-
-def verbose_field_name(model, field_name):
- """
- Get the verbose name for a given ``field_name``. The ``field_name``
- will be traversed across relationships. Returns '[invalid name]' for
- any field name that cannot be traversed.
-
- ex::
-
- >>> verbose_field_name(Article, 'author__name')
- 'author name'
-
- """
- if field_name is None:
- return '[invalid name]'
-
- parts = get_field_parts(model, field_name)
- if not parts:
- return '[invalid name]'
-
- names = []
- for part in parts:
- if isinstance(part, ForeignObjectRel):
- if part.related_name:
- names.append(part.related_name.replace('_', ' '))
- else:
- return '[invalid name]'
- else:
- names.append(force_str(part.verbose_name))
-
- return ' '.join(names)
-
-
-def verbose_lookup_expr(lookup_expr):
- """
- Get a verbose, more humanized expression for a given ``lookup_expr``.
- Each part in the expression is looked up in the ``FILTERS_VERBOSE_LOOKUPS``
- dictionary. Missing keys will simply default to itself.
-
- ex::
-
- >>> verbose_lookup_expr('year__lt')
- 'year is less than'
-
- # with `FILTERS_VERBOSE_LOOKUPS = {}`
- >>> verbose_lookup_expr('year__lt')
- 'year lt'
-
- """
- from .conf import settings as app_settings
-
- VERBOSE_LOOKUPS = app_settings.VERBOSE_LOOKUPS or {}
- lookups = [
- force_str(VERBOSE_LOOKUPS.get(lookup, _(lookup)))
- for lookup in lookup_expr.split(LOOKUP_SEP)
- ]
-
- return ' '.join(lookups)
-
-
-def label_for_filter(model, field_name, lookup_expr, exclude=False):
- """
- Create a generic label suitable for a filter.
-
- ex::
-
- >>> label_for_filter(Article, 'author__name', 'in')
- 'auther name is in'
-
- """
- name = verbose_field_name(model, field_name)
- verbose_expression = [_('exclude'), name] if exclude else [name]
-
- # iterable lookups indicate a LookupTypeField, which should not be verbose
- if isinstance(lookup_expr, str):
- verbose_expression += [verbose_lookup_expr(lookup_expr)]
-
- verbose_expression = [force_str(part) for part in verbose_expression if part]
- verbose_expression = capfirst(' '.join(verbose_expression))
-
- return verbose_expression
-
-
-def translate_validation(error_dict):
- """
- Translate a Django ErrorDict into its DRF ValidationError.
- """
- # it's necessary to lazily import the exception, as it can otherwise create
- # an import loop when importing django_filters inside the project settings.
- from rest_framework.exceptions import ErrorDetail, ValidationError
-
- exc = OrderedDict(
- (key, [ErrorDetail(e.message % (e.params or ()), code=e.code)
- for e in error_list])
- for key, error_list in error_dict.as_data().items()
- )
-
- return ValidationError(exc)
diff --git a/venv/Lib/site-packages/django_filters/views.py b/venv/Lib/site-packages/django_filters/views.py
deleted file mode 100644
index e9160ad..0000000
--- a/venv/Lib/site-packages/django_filters/views.py
+++ /dev/null
@@ -1,116 +0,0 @@
-from django.core.exceptions import ImproperlyConfigured
-from django.views.generic import View
-from django.views.generic.list import (
- MultipleObjectMixin,
- MultipleObjectTemplateResponseMixin
-)
-
-from .constants import ALL_FIELDS
-from .filterset import filterset_factory
-from .utils import MigrationNotice, RenameAttributesBase
-
-
-# TODO: remove metaclass in 2.1
-class FilterMixinRenames(RenameAttributesBase):
- renamed_attributes = (
- ('filter_fields', 'filterset_fields', MigrationNotice),
- )
-
-
-class FilterMixin(metaclass=FilterMixinRenames):
- """
- A mixin that provides a way to show and handle a FilterSet in a request.
- """
- filterset_class = None
- filterset_fields = ALL_FIELDS
- strict = True
-
- def get_filterset_class(self):
- """
- Returns the filterset class to use in this view
- """
- if self.filterset_class:
- return self.filterset_class
- elif self.model:
- return filterset_factory(model=self.model, fields=self.filterset_fields)
- else:
- msg = "'%s' must define 'filterset_class' or 'model'"
- raise ImproperlyConfigured(msg % self.__class__.__name__)
-
- def get_filterset(self, filterset_class):
- """
- Returns an instance of the filterset to be used in this view.
- """
- kwargs = self.get_filterset_kwargs(filterset_class)
- return filterset_class(**kwargs)
-
- def get_filterset_kwargs(self, filterset_class):
- """
- Returns the keyword arguments for instantiating the filterset.
- """
- kwargs = {
- 'data': self.request.GET or None,
- 'request': self.request,
- }
- try:
- kwargs.update({
- 'queryset': self.get_queryset(),
- })
- except ImproperlyConfigured:
- # ignore the error here if the filterset has a model defined
- # to acquire a queryset from
- if filterset_class._meta.model is None:
- msg = ("'%s' does not define a 'model' and the view '%s' does "
- "not return a valid queryset from 'get_queryset'. You "
- "must fix one of them.")
- args = (filterset_class.__name__, self.__class__.__name__)
- raise ImproperlyConfigured(msg % args)
- return kwargs
-
- def get_strict(self):
- return self.strict
-
-
-class BaseFilterView(FilterMixin, MultipleObjectMixin, View):
-
- def get(self, request, *args, **kwargs):
- filterset_class = self.get_filterset_class()
- self.filterset = self.get_filterset(filterset_class)
-
- if not self.filterset.is_bound or self.filterset.is_valid() or not self.get_strict():
- self.object_list = self.filterset.qs
- else:
- self.object_list = self.filterset.queryset.none()
-
- context = self.get_context_data(filter=self.filterset,
- object_list=self.object_list)
- return self.render_to_response(context)
-
-
-class FilterView(MultipleObjectTemplateResponseMixin, BaseFilterView):
- """
- Render some list of objects with filter, set by `self.model` or
- `self.queryset`.
- `self.queryset` can actually be any iterable of items, not just a queryset.
- """
- template_name_suffix = '_filter'
-
-
-def object_filter(request, model=None, queryset=None, template_name=None,
- extra_context=None, context_processors=None,
- filter_class=None):
- class ECFilterView(FilterView):
- """Handle the extra_context from the functional object_filter view"""
- def get_context_data(self, **kwargs):
- context = super().get_context_data(**kwargs)
- extra_context = self.kwargs.get('extra_context') or {}
- for k, v in extra_context.items():
- if callable(v):
- v = v()
- context[k] = v
- return context
-
- kwargs = dict(model=model, queryset=queryset, template_name=template_name,
- filterset_class=filter_class)
- view = ECFilterView.as_view(**kwargs)
- return view(request, extra_context=extra_context)
diff --git a/venv/Lib/site-packages/django_filters/widgets.py b/venv/Lib/site-packages/django_filters/widgets.py
deleted file mode 100644
index 889fd81..0000000
--- a/venv/Lib/site-packages/django_filters/widgets.py
+++ /dev/null
@@ -1,270 +0,0 @@
-from collections.abc import Iterable
-from copy import deepcopy
-from itertools import chain
-from re import search, sub
-
-from django import forms
-from django.db.models.fields import BLANK_CHOICE_DASH
-from django.forms.utils import flatatt
-from django.utils.datastructures import MultiValueDict
-from django.utils.encoding import force_str
-from django.utils.http import urlencode
-from django.utils.safestring import mark_safe
-from django.utils.translation import gettext as _
-
-
-class LinkWidget(forms.Widget):
- def __init__(self, attrs=None, choices=()):
- super().__init__(attrs)
-
- self.choices = choices
-
- def value_from_datadict(self, data, files, name):
- value = super().value_from_datadict(data, files, name)
- self.data = data
- return value
-
- def render(self, name, value, attrs=None, choices=(), renderer=None):
- if not hasattr(self, 'data'):
- self.data = {}
- if value is None:
- value = ''
- final_attrs = self.build_attrs(self.attrs, extra_attrs=attrs)
- output = ['
` tag.
- # See https://w3c.github.io/html/grouping-content.html#the-p-element
- 'address', 'article', 'aside', 'blockquote', 'details', 'div', 'dl',
- 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3',
- 'h4', 'h5', 'h6', 'header', 'hgroup', 'hr', 'main', 'menu', 'nav', 'ol',
- 'p', 'pre', 'section', 'table', 'ul',
- # Other elements which Markdown should not be mucking up the contents of.
- 'canvas', 'colgroup', 'dd', 'body', 'dt', 'group', 'iframe', 'li', 'legend',
- 'math', 'map', 'noscript', 'output', 'object', 'option', 'progress', 'script',
- 'style', 'summary', 'tbody', 'td', 'textarea', 'tfoot', 'th', 'thead', 'tr', 'video'
- ]
-
- self.registeredExtensions = []
- self.docType = ""
- self.stripTopLevelTags = True
-
- self.build_parser()
-
- self.references = {}
- self.htmlStash = util.HtmlStash()
- self.registerExtensions(extensions=kwargs.get('extensions', []),
- configs=kwargs.get('extension_configs', {}))
- self.set_output_format(kwargs.get('output_format', 'xhtml'))
- self.reset()
-
- def build_parser(self):
- """ Build the parser from the various parts. """
- self.preprocessors = build_preprocessors(self)
- self.parser = build_block_parser(self)
- self.inlinePatterns = build_inlinepatterns(self)
- self.treeprocessors = build_treeprocessors(self)
- self.postprocessors = build_postprocessors(self)
- return self
-
- def registerExtensions(self, extensions, configs):
- """
- Register extensions with this instance of Markdown.
-
- Keyword arguments:
-
- * extensions: A list of extensions, which can either
- be strings or objects.
- * configs: A dictionary mapping extension names to config options.
-
- """
- for ext in extensions:
- if isinstance(ext, str):
- ext = self.build_extension(ext, configs.get(ext, {}))
- if isinstance(ext, Extension):
- ext._extendMarkdown(self)
- logger.debug(
- 'Successfully loaded extension "%s.%s".'
- % (ext.__class__.__module__, ext.__class__.__name__)
- )
- elif ext is not None:
- raise TypeError(
- 'Extension "{}.{}" must be of type: "{}.{}"'.format(
- ext.__class__.__module__, ext.__class__.__name__,
- Extension.__module__, Extension.__name__
- )
- )
- return self
-
- def build_extension(self, ext_name, configs):
- """
- Build extension from a string name, then return an instance.
-
- First attempt to load an entry point. The string name must be registered as an entry point in the
- `markdown.extensions` group which points to a subclass of the `markdown.extensions.Extension` class.
- If multiple distributions have registered the same name, the first one found is returned.
-
- If no entry point is found, assume dot notation (`path.to.module:ClassName`). Load the specified class and
- return an instance. If no class is specified, import the module and call a `makeExtension` function and return
- the Extension instance returned by that function.
- """
- configs = dict(configs)
-
- entry_points = [ep for ep in util.INSTALLED_EXTENSIONS if ep.name == ext_name]
- if entry_points:
- ext = entry_points[0].load()
- return ext(**configs)
-
- # Get class name (if provided): `path.to.module:ClassName`
- ext_name, class_name = ext_name.split(':', 1) if ':' in ext_name else (ext_name, '')
-
- try:
- module = importlib.import_module(ext_name)
- logger.debug(
- 'Successfully imported extension module "%s".' % ext_name
- )
- except ImportError as e:
- message = 'Failed loading extension "%s".' % ext_name
- e.args = (message,) + e.args[1:]
- raise
-
- if class_name:
- # Load given class name from module.
- return getattr(module, class_name)(**configs)
- else:
- # Expect makeExtension() function to return a class.
- try:
- return module.makeExtension(**configs)
- except AttributeError as e:
- message = e.args[0]
- message = "Failed to initiate extension " \
- "'%s': %s" % (ext_name, message)
- e.args = (message,) + e.args[1:]
- raise
-
- def registerExtension(self, extension):
- """ This gets called by the extension """
- self.registeredExtensions.append(extension)
- return self
-
- def reset(self):
- """
- Resets all state variables so that we can start with a new text.
- """
- self.htmlStash.reset()
- self.references.clear()
-
- for extension in self.registeredExtensions:
- if hasattr(extension, 'reset'):
- extension.reset()
-
- return self
-
- def set_output_format(self, format):
- """ Set the output format for the class instance. """
- self.output_format = format.lower().rstrip('145') # ignore num
- try:
- self.serializer = self.output_formats[self.output_format]
- except KeyError as e:
- valid_formats = list(self.output_formats.keys())
- valid_formats.sort()
- message = 'Invalid Output Format: "%s". Use one of %s.' \
- % (self.output_format,
- '"' + '", "'.join(valid_formats) + '"')
- e.args = (message,) + e.args[1:]
- raise
- return self
-
- def is_block_level(self, tag):
- """Check if the tag is a block level HTML tag."""
- if isinstance(tag, str):
- return tag.lower().rstrip('/') in self.block_level_elements
- # Some ElementTree tags are not strings, so return False.
- return False
-
- def convert(self, source):
- """
- Convert markdown to serialized XHTML or HTML.
-
- Keyword arguments:
-
- * source: Source text as a Unicode string.
-
- Markdown processing takes place in five steps:
-
- 1. A bunch of "preprocessors" munge the input text.
- 2. BlockParser() parses the high-level structural elements of the
- pre-processed text into an ElementTree.
- 3. A bunch of "treeprocessors" are run against the ElementTree. One
- such treeprocessor runs InlinePatterns against the ElementTree,
- detecting inline markup.
- 4. Some post-processors are run against the text after the ElementTree
- has been serialized into text.
- 5. The output is written to a string.
-
- """
-
- # Fixup the source text
- if not source.strip():
- return '' # a blank unicode string
-
- try:
- source = str(source)
- except UnicodeDecodeError as e: # pragma: no cover
- # Customise error message while maintaining original trackback
- e.reason += '. -- Note: Markdown only accepts unicode input!'
- raise
-
- # Split into lines and run the line preprocessors.
- self.lines = source.split("\n")
- for prep in self.preprocessors:
- self.lines = prep.run(self.lines)
-
- # Parse the high-level elements.
- root = self.parser.parseDocument(self.lines).getroot()
-
- # Run the tree-processors
- for treeprocessor in self.treeprocessors:
- newRoot = treeprocessor.run(root)
- if newRoot is not None:
- root = newRoot
-
- # Serialize _properly_. Strip top-level tags.
- output = self.serializer(root)
- if self.stripTopLevelTags:
- try:
- start = output.index(
- '<%s>' % self.doc_tag) + len(self.doc_tag) + 2
- end = output.rindex('%s>' % self.doc_tag)
- output = output[start:end].strip()
- except ValueError as e: # pragma: no cover
- if output.strip().endswith('<%s />' % self.doc_tag):
- # We have an empty document
- output = ''
- else:
- # We have a serious problem
- raise ValueError('Markdown failed to strip top-level '
- 'tags. Document=%r' % output.strip()) from e
-
- # Run the text post-processors
- for pp in self.postprocessors:
- output = pp.run(output)
-
- return output.strip()
-
- def convertFile(self, input=None, output=None, encoding=None):
- """Converts a markdown file and returns the HTML as a unicode string.
-
- Decodes the file using the provided encoding (defaults to utf-8),
- passes the file content to markdown, and outputs the html to either
- the provided stream or the file with provided name, using the same
- encoding as the source file. The 'xmlcharrefreplace' error handler is
- used when encoding the output.
-
- **Note:** This is the only place that decoding and encoding of unicode
- takes place in Python-Markdown. (All other code is unicode-in /
- unicode-out.)
-
- Keyword arguments:
-
- * input: File object or path. Reads from stdin if `None`.
- * output: File object or path. Writes to stdout if `None`.
- * encoding: Encoding of input and output files. Defaults to utf-8.
-
- """
-
- encoding = encoding or "utf-8"
-
- # Read the source
- if input:
- if isinstance(input, str):
- input_file = codecs.open(input, mode="r", encoding=encoding)
- else:
- input_file = codecs.getreader(encoding)(input)
- text = input_file.read()
- input_file.close()
- else:
- text = sys.stdin.read()
- if not isinstance(text, str): # pragma: no cover
- text = text.decode(encoding)
-
- text = text.lstrip('\ufeff') # remove the byte-order mark
-
- # Convert
- html = self.convert(text)
-
- # Write to file or stdout
- if output:
- if isinstance(output, str):
- output_file = codecs.open(output, "w",
- encoding=encoding,
- errors="xmlcharrefreplace")
- output_file.write(html)
- output_file.close()
- else:
- writer = codecs.getwriter(encoding)
- output_file = writer(output, errors="xmlcharrefreplace")
- output_file.write(html)
- # Don't close here. User may want to write more.
- else:
- # Encode manually and write bytes to stdout.
- html = html.encode(encoding, "xmlcharrefreplace")
- try:
- # Write bytes directly to buffer (Python 3).
- sys.stdout.buffer.write(html)
- except AttributeError: # pragma: no cover
- # Probably Python 2, which works with bytes by default.
- sys.stdout.write(html)
-
- return self
-
-
-"""
-EXPORTED FUNCTIONS
-=============================================================================
-
-Those are the two functions we really mean to export: markdown() and
-markdownFromFile().
-"""
-
-
-def markdown(text, **kwargs):
- """Convert a markdown string to HTML and return HTML as a unicode string.
-
- This is a shortcut function for `Markdown` class to cover the most
- basic use case. It initializes an instance of Markdown, loads the
- necessary extensions and runs the parser on the given text.
-
- Keyword arguments:
-
- * text: Markdown formatted text as Unicode or ASCII string.
- * Any arguments accepted by the Markdown class.
-
- Returns: An HTML document as a string.
-
- """
- md = Markdown(**kwargs)
- return md.convert(text)
-
-
-def markdownFromFile(**kwargs):
- """Read markdown code from a file and write it to a file or a stream.
-
- This is a shortcut function which initializes an instance of Markdown,
- and calls the convertFile method rather than convert.
-
- Keyword arguments:
-
- * input: a file name or readable object.
- * output: a file name or writable object.
- * encoding: Encoding of input and output.
- * Any arguments accepted by the Markdown class.
-
- """
- md = Markdown(**kwargs)
- md.convertFile(kwargs.get('input', None),
- kwargs.get('output', None),
- kwargs.get('encoding', None))
diff --git a/venv/Lib/site-packages/markdown/extensions/__init__.py b/venv/Lib/site-packages/markdown/extensions/__init__.py
deleted file mode 100644
index 4bc8e5f..0000000
--- a/venv/Lib/site-packages/markdown/extensions/__init__.py
+++ /dev/null
@@ -1,107 +0,0 @@
-"""
-Python Markdown
-
-A Python implementation of John Gruber's Markdown.
-
-Documentation: https://python-markdown.github.io/
-GitHub: https://github.com/Python-Markdown/markdown/
-PyPI: https://pypi.org/project/Markdown/
-
-Started by Manfred Stienstra (http://www.dwerg.net/).
-Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org).
-Currently maintained by Waylan Limberg (https://github.com/waylan),
-Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser).
-
-Copyright 2007-2018 The Python Markdown Project (v. 1.7 and later)
-Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b)
-Copyright 2004 Manfred Stienstra (the original version)
-
-License: BSD (see LICENSE.md for details).
-"""
-
-import warnings
-from ..util import parseBoolValue
-
-
-class Extension:
- """ Base class for extensions to subclass. """
-
- # Default config -- to be overriden by a subclass
- # Must be of the following format:
- # {
- # 'key': ['value', 'description']
- # }
- # Note that Extension.setConfig will raise a KeyError
- # if a default is not set here.
- config = {}
-
- def __init__(self, **kwargs):
- """ Initiate Extension and set up configs. """
- self.setConfigs(kwargs)
-
- def getConfig(self, key, default=''):
- """ Return a setting for the given key or an empty string. """
- if key in self.config:
- return self.config[key][0]
- else:
- return default
-
- def getConfigs(self):
- """ Return all configs settings as a dict. """
- return {key: self.getConfig(key) for key in self.config.keys()}
-
- def getConfigInfo(self):
- """ Return all config descriptions as a list of tuples. """
- return [(key, self.config[key][1]) for key in self.config.keys()]
-
- def setConfig(self, key, value):
- """ Set a config setting for `key` with the given `value`. """
- if isinstance(self.config[key][0], bool):
- value = parseBoolValue(value)
- if self.config[key][0] is None:
- value = parseBoolValue(value, preserve_none=True)
- self.config[key][0] = value
-
- def setConfigs(self, items):
- """ Set multiple config settings given a dict or list of tuples. """
- if hasattr(items, 'items'):
- # it's a dict
- items = items.items()
- for key, value in items:
- self.setConfig(key, value)
-
- def _extendMarkdown(self, *args):
- """ Private wrapper around extendMarkdown. """
- md = args[0]
- try:
- self.extendMarkdown(md)
- except TypeError as e:
- if "missing 1 required positional argument" in str(e):
- # Must be a 2.x extension. Pass in a dumby md_globals.
- self.extendMarkdown(md, {})
- warnings.warn(
- "The 'md_globals' parameter of '{}.{}.extendMarkdown' is "
- "deprecated.".format(self.__class__.__module__, self.__class__.__name__),
- category=DeprecationWarning,
- stacklevel=2
- )
- else:
- raise
-
- def extendMarkdown(self, md):
- """
- Add the various proccesors and patterns to the Markdown Instance.
-
- This method must be overriden by every extension.
-
- Keyword arguments:
-
- * md: The Markdown instance.
-
- * md_globals: Global variables in the markdown module namespace.
-
- """
- raise NotImplementedError(
- 'Extension "%s.%s" must define an "extendMarkdown"'
- 'method.' % (self.__class__.__module__, self.__class__.__name__)
- )
diff --git a/venv/Lib/site-packages/markdown/extensions/abbr.py b/venv/Lib/site-packages/markdown/extensions/abbr.py
deleted file mode 100644
index 9879314..0000000
--- a/venv/Lib/site-packages/markdown/extensions/abbr.py
+++ /dev/null
@@ -1,99 +0,0 @@
-'''
-Abbreviation Extension for Python-Markdown
-==========================================
-
-This extension adds abbreviation handling to Python-Markdown.
-
-See
-for documentation.
-
-Oringinal code Copyright 2007-2008 [Waylan Limberg](http://achinghead.com/) and
- [Seemant Kulleen](http://www.kulleen.org/)
-
-All changes Copyright 2008-2014 The Python Markdown Project
-
-License: [BSD](https://opensource.org/licenses/bsd-license.php)
-
-'''
-
-from . import Extension
-from ..blockprocessors import BlockProcessor
-from ..inlinepatterns import InlineProcessor
-from ..util import AtomicString
-import re
-import xml.etree.ElementTree as etree
-
-
-class AbbrExtension(Extension):
- """ Abbreviation Extension for Python-Markdown. """
-
- def extendMarkdown(self, md):
- """ Insert AbbrPreprocessor before ReferencePreprocessor. """
- md.parser.blockprocessors.register(AbbrPreprocessor(md.parser), 'abbr', 16)
-
-
-class AbbrPreprocessor(BlockProcessor):
- """ Abbreviation Preprocessor - parse text for abbr references. """
-
- RE = re.compile(r'^[*]\[(?P[^\]]*)\][ ]?:[ ]*\n?[ ]*(?P.*)$', re.MULTILINE)
-
- def test(self, parent, block):
- return True
-
- def run(self, parent, blocks):
- '''
- Find and remove all Abbreviation references from the text.
- Each reference is set as a new AbbrPattern in the markdown instance.
-
- '''
- block = blocks.pop(0)
- m = self.RE.search(block)
- if m:
- abbr = m.group('abbr').strip()
- title = m.group('title').strip()
- self.parser.md.inlinePatterns.register(
- AbbrInlineProcessor(self._generate_pattern(abbr), title), 'abbr-%s' % abbr, 2
- )
- if block[m.end():].strip():
- # Add any content after match back to blocks as separate block
- blocks.insert(0, block[m.end():].lstrip('\n'))
- if block[:m.start()].strip():
- # Add any content before match back to blocks as separate block
- blocks.insert(0, block[:m.start()].rstrip('\n'))
- return True
- # No match. Restore block.
- blocks.insert(0, block)
- return False
-
- def _generate_pattern(self, text):
- '''
- Given a string, returns an regex pattern to match that string.
-
- 'HTML' -> r'(?P[H][T][M][L])'
-
- Note: we force each char as a literal match (in brackets) as we don't
- know what they will be beforehand.
-
- '''
- chars = list(text)
- for i in range(len(chars)):
- chars[i] = r'[%s]' % chars[i]
- return r'(?P\b%s\b)' % (r''.join(chars))
-
-
-class AbbrInlineProcessor(InlineProcessor):
- """ Abbreviation inline pattern. """
-
- def __init__(self, pattern, title):
- super().__init__(pattern)
- self.title = title
-
- def handleMatch(self, m, data):
- abbr = etree.Element('abbr')
- abbr.text = AtomicString(m.group('abbr'))
- abbr.set('title', self.title)
- return abbr, m.start(0), m.end(0)
-
-
-def makeExtension(**kwargs): # pragma: no cover
- return AbbrExtension(**kwargs)
diff --git a/venv/Lib/site-packages/markdown/extensions/admonition.py b/venv/Lib/site-packages/markdown/extensions/admonition.py
deleted file mode 100644
index cb8d901..0000000
--- a/venv/Lib/site-packages/markdown/extensions/admonition.py
+++ /dev/null
@@ -1,170 +0,0 @@
-"""
-Admonition extension for Python-Markdown
-========================================
-
-Adds rST-style admonitions. Inspired by [rST][] feature with the same name.
-
-[rST]: http://docutils.sourceforge.net/docs/ref/rst/directives.html#specific-admonitions # noqa
-
-See
-for documentation.
-
-Original code Copyright [Tiago Serafim](https://www.tiagoserafim.com/).
-
-All changes Copyright The Python Markdown Project
-
-License: [BSD](https://opensource.org/licenses/bsd-license.php)
-
-"""
-
-from . import Extension
-from ..blockprocessors import BlockProcessor
-import xml.etree.ElementTree as etree
-import re
-
-
-class AdmonitionExtension(Extension):
- """ Admonition extension for Python-Markdown. """
-
- def extendMarkdown(self, md):
- """ Add Admonition to Markdown instance. """
- md.registerExtension(self)
-
- md.parser.blockprocessors.register(AdmonitionProcessor(md.parser), 'admonition', 105)
-
-
-class AdmonitionProcessor(BlockProcessor):
-
- CLASSNAME = 'admonition'
- CLASSNAME_TITLE = 'admonition-title'
- RE = re.compile(r'(?:^|\n)!!! ?([\w\-]+(?: +[\w\-]+)*)(?: +"(.*?)")? *(?:\n|$)')
- RE_SPACES = re.compile(' +')
-
- def __init__(self, parser):
- """Initialization."""
-
- super().__init__(parser)
-
- self.current_sibling = None
- self.content_indention = 0
-
- def parse_content(self, parent, block):
- """Get sibling admonition.
-
- Retrieve the appropriate sibling element. This can get tricky when
- dealing with lists.
-
- """
-
- old_block = block
- the_rest = ''
-
- # We already acquired the block via test
- if self.current_sibling is not None:
- sibling = self.current_sibling
- block, the_rest = self.detab(block, self.content_indent)
- self.current_sibling = None
- self.content_indent = 0
- return sibling, block, the_rest
-
- sibling = self.lastChild(parent)
-
- if sibling is None or sibling.get('class', '').find(self.CLASSNAME) == -1:
- sibling = None
- else:
- # If the last child is a list and the content is sufficiently indented
- # to be under it, then the content's sibling is in the list.
- last_child = self.lastChild(sibling)
- indent = 0
- while last_child:
- if (
- sibling and block.startswith(' ' * self.tab_length * 2) and
- last_child and last_child.tag in ('ul', 'ol', 'dl')
- ):
-
- # The expectation is that we'll find an
or
.
- # We should get its last child as well.
- sibling = self.lastChild(last_child)
- last_child = self.lastChild(sibling) if sibling else None
-
- # Context has been lost at this point, so we must adjust the
- # text's indentation level so it will be evaluated correctly
- # under the list.
- block = block[self.tab_length:]
- indent += self.tab_length
- else:
- last_child = None
-
- if not block.startswith(' ' * self.tab_length):
- sibling = None
-
- if sibling is not None:
- indent += self.tab_length
- block, the_rest = self.detab(old_block, indent)
- self.current_sibling = sibling
- self.content_indent = indent
-
- return sibling, block, the_rest
-
- def test(self, parent, block):
-
- if self.RE.search(block):
- return True
- else:
- return self.parse_content(parent, block)[0] is not None
-
- def run(self, parent, blocks):
- block = blocks.pop(0)
- m = self.RE.search(block)
-
- if m:
- if m.start() > 0:
- self.parser.parseBlocks(parent, [block[:m.start()]])
- block = block[m.end():] # removes the first line
- block, theRest = self.detab(block)
- else:
- sibling, block, theRest = self.parse_content(parent, block)
-
- if m:
- klass, title = self.get_class_and_title(m)
- div = etree.SubElement(parent, 'div')
- div.set('class', '{} {}'.format(self.CLASSNAME, klass))
- if title:
- p = etree.SubElement(div, 'p')
- p.text = title
- p.set('class', self.CLASSNAME_TITLE)
- else:
- # Sibling is a list item, but we need to wrap it's content should be wrapped in
- if sibling.tag in ('li', 'dd') and sibling.text:
- text = sibling.text
- sibling.text = ''
- p = etree.SubElement(sibling, 'p')
- p.text = text
-
- div = sibling
-
- self.parser.parseChunk(div, block)
-
- if theRest:
- # This block contained unindented line(s) after the first indented
- # line. Insert these lines as the first block of the master blocks
- # list for future processing.
- blocks.insert(0, theRest)
-
- def get_class_and_title(self, match):
- klass, title = match.group(1).lower(), match.group(2)
- klass = self.RE_SPACES.sub(' ', klass)
- if title is None:
- # no title was provided, use the capitalized classname as title
- # e.g.: `!!! note` will render
- # `
Note
`
- title = klass.split(' ', 1)[0].capitalize()
- elif title == '':
- # an explicit blank title should not be rendered
- # e.g.: `!!! warning ""` will *not* render `p` with a title
- title = None
- return klass, title
-
-
-def makeExtension(**kwargs): # pragma: no cover
- return AdmonitionExtension(**kwargs)
diff --git a/venv/Lib/site-packages/markdown/extensions/attr_list.py b/venv/Lib/site-packages/markdown/extensions/attr_list.py
deleted file mode 100644
index 9a67551..0000000
--- a/venv/Lib/site-packages/markdown/extensions/attr_list.py
+++ /dev/null
@@ -1,166 +0,0 @@
-"""
-Attribute List Extension for Python-Markdown
-============================================
-
-Adds attribute list syntax. Inspired by
-[maruku](http://maruku.rubyforge.org/proposal.html#attribute_lists)'s
-feature of the same name.
-
-See
-for documentation.
-
-Original code Copyright 2011 [Waylan Limberg](http://achinghead.com/).
-
-All changes Copyright 2011-2014 The Python Markdown Project
-
-License: [BSD](https://opensource.org/licenses/bsd-license.php)
-
-"""
-
-from . import Extension
-from ..treeprocessors import Treeprocessor
-import re
-
-
-def _handle_double_quote(s, t):
- k, v = t.split('=', 1)
- return k, v.strip('"')
-
-
-def _handle_single_quote(s, t):
- k, v = t.split('=', 1)
- return k, v.strip("'")
-
-
-def _handle_key_value(s, t):
- return t.split('=', 1)
-
-
-def _handle_word(s, t):
- if t.startswith('.'):
- return '.', t[1:]
- if t.startswith('#'):
- return 'id', t[1:]
- return t, t
-
-
-_scanner = re.Scanner([
- (r'[^ =]+=".*?"', _handle_double_quote),
- (r"[^ =]+='.*?'", _handle_single_quote),
- (r'[^ =]+=[^ =]+', _handle_key_value),
- (r'[^ =]+', _handle_word),
- (r' ', None)
-])
-
-
-def get_attrs(str):
- """ Parse attribute list and return a list of attribute tuples. """
- return _scanner.scan(str)[0]
-
-
-def isheader(elem):
- return elem.tag in ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']
-
-
-class AttrListTreeprocessor(Treeprocessor):
-
- BASE_RE = r'\{\:?[ ]*([^\}\n ][^\}\n]*)[ ]*\}'
- HEADER_RE = re.compile(r'[ ]+{}[ ]*$'.format(BASE_RE))
- BLOCK_RE = re.compile(r'\n[ ]*{}[ ]*$'.format(BASE_RE))
- INLINE_RE = re.compile(r'^{}'.format(BASE_RE))
- NAME_RE = re.compile(r'[^A-Z_a-z\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u02ff'
- r'\u0370-\u037d\u037f-\u1fff\u200c-\u200d'
- r'\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff'
- r'\uf900-\ufdcf\ufdf0-\ufffd'
- r'\:\-\.0-9\u00b7\u0300-\u036f\u203f-\u2040]+')
-
- def run(self, doc):
- for elem in doc.iter():
- if self.md.is_block_level(elem.tag):
- # Block level: check for attrs on last line of text
- RE = self.BLOCK_RE
- if isheader(elem) or elem.tag in ['dt', 'td', 'th']:
- # header, def-term, or table cell: check for attrs at end of element
- RE = self.HEADER_RE
- if len(elem) and elem.tag == 'li':
- # special case list items. children may include a ul or ol.
- pos = None
- # find the ul or ol position
- for i, child in enumerate(elem):
- if child.tag in ['ul', 'ol']:
- pos = i
- break
- if pos is None and elem[-1].tail:
- # use tail of last child. no ul or ol.
- m = RE.search(elem[-1].tail)
- if m:
- self.assign_attrs(elem, m.group(1))
- elem[-1].tail = elem[-1].tail[:m.start()]
- elif pos is not None and pos > 0 and elem[pos-1].tail:
- # use tail of last child before ul or ol
- m = RE.search(elem[pos-1].tail)
- if m:
- self.assign_attrs(elem, m.group(1))
- elem[pos-1].tail = elem[pos-1].tail[:m.start()]
- elif elem.text:
- # use text. ul is first child.
- m = RE.search(elem.text)
- if m:
- self.assign_attrs(elem, m.group(1))
- elem.text = elem.text[:m.start()]
- elif len(elem) and elem[-1].tail:
- # has children. Get from tail of last child
- m = RE.search(elem[-1].tail)
- if m:
- self.assign_attrs(elem, m.group(1))
- elem[-1].tail = elem[-1].tail[:m.start()]
- if isheader(elem):
- # clean up trailing #s
- elem[-1].tail = elem[-1].tail.rstrip('#').rstrip()
- elif elem.text:
- # no children. Get from text.
- m = RE.search(elem.text)
- if m:
- self.assign_attrs(elem, m.group(1))
- elem.text = elem.text[:m.start()]
- if isheader(elem):
- # clean up trailing #s
- elem.text = elem.text.rstrip('#').rstrip()
- else:
- # inline: check for attrs at start of tail
- if elem.tail:
- m = self.INLINE_RE.match(elem.tail)
- if m:
- self.assign_attrs(elem, m.group(1))
- elem.tail = elem.tail[m.end():]
-
- def assign_attrs(self, elem, attrs):
- """ Assign attrs to element. """
- for k, v in get_attrs(attrs):
- if k == '.':
- # add to class
- cls = elem.get('class')
- if cls:
- elem.set('class', '{} {}'.format(cls, v))
- else:
- elem.set('class', v)
- else:
- # assign attr k with v
- elem.set(self.sanitize_name(k), v)
-
- def sanitize_name(self, name):
- """
- Sanitize name as 'an XML Name, minus the ":"'.
- See https://www.w3.org/TR/REC-xml-names/#NT-NCName
- """
- return self.NAME_RE.sub('_', name)
-
-
-class AttrListExtension(Extension):
- def extendMarkdown(self, md):
- md.treeprocessors.register(AttrListTreeprocessor(md), 'attr_list', 8)
- md.registerExtension(self)
-
-
-def makeExtension(**kwargs): # pragma: no cover
- return AttrListExtension(**kwargs)
diff --git a/venv/Lib/site-packages/markdown/extensions/codehilite.py b/venv/Lib/site-packages/markdown/extensions/codehilite.py
deleted file mode 100644
index e1c2218..0000000
--- a/venv/Lib/site-packages/markdown/extensions/codehilite.py
+++ /dev/null
@@ -1,307 +0,0 @@
-"""
-CodeHilite Extension for Python-Markdown
-========================================
-
-Adds code/syntax highlighting to standard Python-Markdown code blocks.
-
-See
-for documentation.
-
-Original code Copyright 2006-2008 [Waylan Limberg](http://achinghead.com/).
-
-All changes Copyright 2008-2014 The Python Markdown Project
-
-License: [BSD](https://opensource.org/licenses/bsd-license.php)
-
-"""
-
-from . import Extension
-from ..treeprocessors import Treeprocessor
-from ..util import parseBoolValue
-
-try: # pragma: no cover
- from pygments import highlight
- from pygments.lexers import get_lexer_by_name, guess_lexer
- from pygments.formatters import get_formatter_by_name
- pygments = True
-except ImportError: # pragma: no cover
- pygments = False
-
-
-def parse_hl_lines(expr):
- """Support our syntax for emphasizing certain lines of code.
-
- expr should be like '1 2' to emphasize lines 1 and 2 of a code block.
- Returns a list of ints, the line numbers to emphasize.
- """
- if not expr:
- return []
-
- try:
- return list(map(int, expr.split()))
- except ValueError: # pragma: no cover
- return []
-
-
-# ------------------ The Main CodeHilite Class ----------------------
-class CodeHilite:
- """
- Determine language of source code, and pass it on to the Pygments highlighter.
-
- Usage:
- code = CodeHilite(src=some_code, lang='python')
- html = code.hilite()
-
- Arguments:
- * src: Source string or any object with a .readline attribute.
-
- * lang: String name of Pygments lexer to use for highlighting. Default: `None`.
-
- * guess_lang: Auto-detect which lexer to use. Ignored if `lang` is set to a valid
- value. Default: `True`.
-
- * use_pygments: Pass code to pygments for code highlighting. If `False`, the code is
- instead wrapped for highlighting by a JavaScript library. Default: `True`.
-
- * linenums: An alias to Pygments `linenos` formatter option. Default: `None`.
-
- * css_class: An alias to Pygments `cssclass` formatter option. Default: 'codehilite'.
-
- * lang_prefix: Prefix prepended to the language when `use_pygments` is `False`.
- Default: "language-".
-
- Other Options:
- Any other options are accepted and passed on to the lexer and formatter. Therefore,
- valid options include any options which are accepted by the `html` formatter or
- whichever lexer the code's language uses. Note that most lexers do not have any
- options. However, a few have very useful options, such as PHP's `startinline` option.
- Any invalid options are ignored without error.
-
- Formatter options: https://pygments.org/docs/formatters/#HtmlFormatter
- Lexer Options: https://pygments.org/docs/lexers/
-
- Advanced Usage:
- code = CodeHilite(
- src = some_code,
- lang = 'php',
- startinline = True, # Lexer option. Snippet does not start with `).
-
- returns : A string of html.
-
- """
-
- self.src = self.src.strip('\n')
-
- if self.lang is None and shebang:
- self._parseHeader()
-
- if pygments and self.use_pygments:
- try:
- lexer = get_lexer_by_name(self.lang, **self.options)
- except ValueError:
- try:
- if self.guess_lang:
- lexer = guess_lexer(self.src, **self.options)
- else:
- lexer = get_lexer_by_name('text', **self.options)
- except ValueError: # pragma: no cover
- lexer = get_lexer_by_name('text', **self.options)
- formatter = get_formatter_by_name('html', **self.options)
- return highlight(self.src, lexer, formatter)
- else:
- # just escape and build markup usable by JS highlighting libs
- txt = self.src.replace('&', '&')
- txt = txt.replace('<', '<')
- txt = txt.replace('>', '>')
- txt = txt.replace('"', '"')
- classes = []
- if self.lang:
- classes.append('{}{}'.format(self.lang_prefix, self.lang))
- if self.options['linenos']:
- classes.append('linenums')
- class_str = ''
- if classes:
- class_str = ' class="{}"'.format(' '.join(classes))
- return '
{}\n
\n'.format(
- self.options['cssclass'],
- class_str,
- txt
- )
-
- def _parseHeader(self):
- """
- Determines language of a code block from shebang line and whether the
- said line should be removed or left in place. If the sheband line
- contains a path (even a single /) then it is assumed to be a real
- shebang line and left alone. However, if no path is given
- (e.i.: #!python or :::python) then it is assumed to be a mock shebang
- for language identification of a code fragment and removed from the
- code block prior to processing for code highlighting. When a mock
- shebang (e.i: #!python) is found, line numbering is turned on. When
- colons are found in place of a shebang (e.i.: :::python), line
- numbering is left in the current state - off by default.
-
- Also parses optional list of highlight lines, like:
-
- :::python hl_lines="1 3"
- """
-
- import re
-
- # split text into lines
- lines = self.src.split("\n")
- # pull first line to examine
- fl = lines.pop(0)
-
- c = re.compile(r'''
- (?:(?:^::+)|(?P^[#]!)) # Shebang or 2 or more colons
- (?P(?:/\w+)*[/ ])? # Zero or 1 path
- (?P[\w#.+-]*) # The language
- \s* # Arbitrary whitespace
- # Optional highlight lines, single- or double-quote-delimited
- (hl_lines=(?P"|')(?P.*?)(?P=quot))?
- ''', re.VERBOSE)
- # search first line for shebang
- m = c.search(fl)
- if m:
- # we have a match
- try:
- self.lang = m.group('lang').lower()
- except IndexError: # pragma: no cover
- self.lang = None
- if m.group('path'):
- # path exists - restore first line
- lines.insert(0, fl)
- if self.options['linenos'] is None and m.group('shebang'):
- # Overridable and Shebang exists - use line numbers
- self.options['linenos'] = True
-
- self.options['hl_lines'] = parse_hl_lines(m.group('hl_lines'))
- else:
- # No match
- lines.insert(0, fl)
-
- self.src = "\n".join(lines).strip("\n")
-
-
-# ------------------ The Markdown Extension -------------------------------
-
-
-class HiliteTreeprocessor(Treeprocessor):
- """ Hilight source code in code blocks. """
-
- def code_unescape(self, text):
- """Unescape code."""
- text = text.replace("<", "<")
- text = text.replace(">", ">")
- # Escaped '&' should be replaced at the end to avoid
- # conflicting with < and >.
- text = text.replace("&", "&")
- return text
-
- def run(self, root):
- """ Find code blocks and store in htmlStash. """
- blocks = root.iter('pre')
- for block in blocks:
- if len(block) == 1 and block[0].tag == 'code':
- code = CodeHilite(
- self.code_unescape(block[0].text),
- tab_length=self.md.tab_length,
- style=self.config.pop('pygments_style', 'default'),
- **self.config
- )
- placeholder = self.md.htmlStash.store(code.hilite())
- # Clear codeblock in etree instance
- block.clear()
- # Change to p element which will later
- # be removed when inserting raw html
- block.tag = 'p'
- block.text = placeholder
-
-
-class CodeHiliteExtension(Extension):
- """ Add source code hilighting to markdown codeblocks. """
-
- def __init__(self, **kwargs):
- # define default configs
- self.config = {
- 'linenums': [None,
- "Use lines numbers. True|table|inline=yes, False=no, None=auto"],
- 'guess_lang': [True,
- "Automatic language detection - Default: True"],
- 'css_class': ["codehilite",
- "Set class name for wrapper
- "
- "Default: codehilite"],
- 'pygments_style': ['default',
- 'Pygments HTML Formatter Style '
- '(Colorscheme) - Default: default'],
- 'noclasses': [False,
- 'Use inline styles instead of CSS classes - '
- 'Default false'],
- 'use_pygments': [True,
- 'Use Pygments to Highlight code blocks. '
- 'Disable if using a JavaScript library. '
- 'Default: True'],
- 'lang_prefix': [
- 'language-',
- 'Prefix prepended to the language when use_pygments is false. Default: "language-"'
- ]
- }
-
- for key, value in kwargs.items():
- if key in self.config:
- self.setConfig(key, value)
- else:
- # manually set unknown keywords.
- if isinstance(value, str):
- try:
- # Attempt to parse str as a bool value
- value = parseBoolValue(value, preserve_none=True)
- except ValueError:
- pass # Assume it's not a bool value. Use as-is.
- self.config[key] = [value, '']
-
- def extendMarkdown(self, md):
- """ Add HilitePostprocessor to Markdown instance. """
- hiliter = HiliteTreeprocessor(md)
- hiliter.config = self.getConfigs()
- md.treeprocessors.register(hiliter, 'hilite', 30)
-
- md.registerExtension(self)
-
-
-def makeExtension(**kwargs): # pragma: no cover
- return CodeHiliteExtension(**kwargs)
diff --git a/venv/Lib/site-packages/markdown/extensions/def_list.py b/venv/Lib/site-packages/markdown/extensions/def_list.py
deleted file mode 100644
index 0e8e452..0000000
--- a/venv/Lib/site-packages/markdown/extensions/def_list.py
+++ /dev/null
@@ -1,111 +0,0 @@
-"""
-Definition List Extension for Python-Markdown
-=============================================
-
-Adds parsing of Definition Lists to Python-Markdown.
-
-See
-for documentation.
-
-Original code Copyright 2008 [Waylan Limberg](http://achinghead.com)
-
-All changes Copyright 2008-2014 The Python Markdown Project
-
-License: [BSD](https://opensource.org/licenses/bsd-license.php)
-
-"""
-
-from . import Extension
-from ..blockprocessors import BlockProcessor, ListIndentProcessor
-import xml.etree.ElementTree as etree
-import re
-
-
-class DefListProcessor(BlockProcessor):
- """ Process Definition Lists. """
-
- RE = re.compile(r'(^|\n)[ ]{0,3}:[ ]{1,3}(.*?)(\n|$)')
- NO_INDENT_RE = re.compile(r'^[ ]{0,3}[^ :]')
-
- def test(self, parent, block):
- return bool(self.RE.search(block))
-
- def run(self, parent, blocks):
-
- raw_block = blocks.pop(0)
- m = self.RE.search(raw_block)
- terms = [term.strip() for term in
- raw_block[:m.start()].split('\n') if term.strip()]
- block = raw_block[m.end():]
- no_indent = self.NO_INDENT_RE.match(block)
- if no_indent:
- d, theRest = (block, None)
- else:
- d, theRest = self.detab(block)
- if d:
- d = '{}\n{}'.format(m.group(2), d)
- else:
- d = m.group(2)
- sibling = self.lastChild(parent)
- if not terms and sibling is None:
- # This is not a definition item. Most likely a paragraph that
- # starts with a colon at the beginning of a document or list.
- blocks.insert(0, raw_block)
- return False
- if not terms and sibling.tag == 'p':
- # The previous paragraph contains the terms
- state = 'looselist'
- terms = sibling.text.split('\n')
- parent.remove(sibling)
- # Acquire new sibling
- sibling = self.lastChild(parent)
- else:
- state = 'list'
-
- if sibling is not None and sibling.tag == 'dl':
- # This is another item on an existing list
- dl = sibling
- if not terms and len(dl) and dl[-1].tag == 'dd' and len(dl[-1]):
- state = 'looselist'
- else:
- # This is a new list
- dl = etree.SubElement(parent, 'dl')
- # Add terms
- for term in terms:
- dt = etree.SubElement(dl, 'dt')
- dt.text = term
- # Add definition
- self.parser.state.set(state)
- dd = etree.SubElement(dl, 'dd')
- self.parser.parseBlocks(dd, [d])
- self.parser.state.reset()
-
- if theRest:
- blocks.insert(0, theRest)
-
-
-class DefListIndentProcessor(ListIndentProcessor):
- """ Process indented children of definition list items. """
-
- # Defintion lists need to be aware of all list types
- ITEM_TYPES = ['dd', 'li']
- LIST_TYPES = ['dl', 'ol', 'ul']
-
- def create_item(self, parent, block):
- """ Create a new dd or li (depending on parent) and parse the block with it as the parent. """
-
- dd = etree.SubElement(parent, 'dd')
- self.parser.parseBlocks(dd, [block])
-
-
-class DefListExtension(Extension):
- """ Add definition lists to Markdown. """
-
- def extendMarkdown(self, md):
- """ Add an instance of DefListProcessor to BlockParser. """
- md.parser.blockprocessors.register(DefListIndentProcessor(md.parser), 'defindent', 85)
- md.parser.blockprocessors.register(DefListProcessor(md.parser), 'deflist', 25)
-
-
-def makeExtension(**kwargs): # pragma: no cover
- return DefListExtension(**kwargs)
diff --git a/venv/Lib/site-packages/markdown/extensions/extra.py b/venv/Lib/site-packages/markdown/extensions/extra.py
deleted file mode 100644
index ebd168c..0000000
--- a/venv/Lib/site-packages/markdown/extensions/extra.py
+++ /dev/null
@@ -1,58 +0,0 @@
-"""
-Python-Markdown Extra Extension
-===============================
-
-A compilation of various Python-Markdown extensions that imitates
-[PHP Markdown Extra](http://michelf.com/projects/php-markdown/extra/).
-
-Note that each of the individual extensions still need to be available
-on your PYTHONPATH. This extension simply wraps them all up as a
-convenience so that only one extension needs to be listed when
-initiating Markdown. See the documentation for each individual
-extension for specifics about that extension.
-
-There may be additional extensions that are distributed with
-Python-Markdown that are not included here in Extra. Those extensions
-are not part of PHP Markdown Extra, and therefore, not part of
-Python-Markdown Extra. If you really would like Extra to include
-additional extensions, we suggest creating your own clone of Extra
-under a differant name. You could also edit the `extensions` global
-variable defined below, but be aware that such changes may be lost
-when you upgrade to any future version of Python-Markdown.
-
-See
-for documentation.
-
-Copyright The Python Markdown Project
-
-License: [BSD](https://opensource.org/licenses/bsd-license.php)
-
-"""
-
-from . import Extension
-
-extensions = [
- 'fenced_code',
- 'footnotes',
- 'attr_list',
- 'def_list',
- 'tables',
- 'abbr',
- 'md_in_html'
-]
-
-
-class ExtraExtension(Extension):
- """ Add various extensions to Markdown class."""
-
- def __init__(self, **kwargs):
- """ config is a dumb holder which gets passed to actual ext later. """
- self.config = kwargs
-
- def extendMarkdown(self, md):
- """ Register extension instances. """
- md.registerExtensions(extensions, self.config)
-
-
-def makeExtension(**kwargs): # pragma: no cover
- return ExtraExtension(**kwargs)
diff --git a/venv/Lib/site-packages/markdown/extensions/fenced_code.py b/venv/Lib/site-packages/markdown/extensions/fenced_code.py
deleted file mode 100644
index 9be0ca0..0000000
--- a/venv/Lib/site-packages/markdown/extensions/fenced_code.py
+++ /dev/null
@@ -1,179 +0,0 @@
-"""
-Fenced Code Extension for Python Markdown
-=========================================
-
-This extension adds Fenced Code Blocks to Python-Markdown.
-
-See
-for documentation.
-
-Original code Copyright 2007-2008 [Waylan Limberg](http://achinghead.com/).
-
-
-All changes Copyright 2008-2014 The Python Markdown Project
-
-License: [BSD](https://opensource.org/licenses/bsd-license.php)
-"""
-
-
-from textwrap import dedent
-from . import Extension
-from ..preprocessors import Preprocessor
-from .codehilite import CodeHilite, CodeHiliteExtension, parse_hl_lines
-from .attr_list import get_attrs, AttrListExtension
-from ..util import parseBoolValue
-import re
-
-
-class FencedCodeExtension(Extension):
- def __init__(self, **kwargs):
- self.config = {
- 'lang_prefix': ['language-', 'Prefix prepended to the language. Default: "language-"']
- }
- super().__init__(**kwargs)
-
- def extendMarkdown(self, md):
- """ Add FencedBlockPreprocessor to the Markdown instance. """
- md.registerExtension(self)
-
- md.preprocessors.register(FencedBlockPreprocessor(md, self.getConfigs()), 'fenced_code_block', 25)
-
-
-class FencedBlockPreprocessor(Preprocessor):
- FENCED_BLOCK_RE = re.compile(
- dedent(r'''
- (?P^(?:~{3,}|`{3,}))[ ]* # opening fence
- ((\{(?P[^\}\n]*)\})| # (optional {attrs} or
- (\.?(?P[\w#.+-]*)[ ]*)? # optional (.)lang
- (hl_lines=(?P"|')(?P.*?)(?P=quot)[ ]*)?) # optional hl_lines)
- \n # newline (end of opening fence)
- (?P.*?)(?<=\n) # the code block
- (?P=fence)[ ]*$ # closing fence
- '''),
- re.MULTILINE | re.DOTALL | re.VERBOSE
- )
-
- def __init__(self, md, config):
- super().__init__(md)
- self.config = config
- self.checked_for_deps = False
- self.codehilite_conf = {}
- self.use_attr_list = False
- # List of options to convert to bool values
- self.bool_options = [
- 'linenums',
- 'guess_lang',
- 'noclasses',
- 'use_pygments'
- ]
-
- def run(self, lines):
- """ Match and store Fenced Code Blocks in the HtmlStash. """
-
- # Check for dependent extensions
- if not self.checked_for_deps:
- for ext in self.md.registeredExtensions:
- if isinstance(ext, CodeHiliteExtension):
- self.codehilite_conf = ext.getConfigs()
- if isinstance(ext, AttrListExtension):
- self.use_attr_list = True
-
- self.checked_for_deps = True
-
- text = "\n".join(lines)
- while 1:
- m = self.FENCED_BLOCK_RE.search(text)
- if m:
- lang, id, classes, config = None, '', [], {}
- if m.group('attrs'):
- id, classes, config = self.handle_attrs(get_attrs(m.group('attrs')))
- if len(classes):
- lang = classes.pop(0)
- else:
- if m.group('lang'):
- lang = m.group('lang')
- if m.group('hl_lines'):
- # Support hl_lines outside of attrs for backward-compatibility
- config['hl_lines'] = parse_hl_lines(m.group('hl_lines'))
-
- # If config is not empty, then the codehighlite extension
- # is enabled, so we call it to highlight the code
- if self.codehilite_conf and self.codehilite_conf['use_pygments'] and config.get('use_pygments', True):
- local_config = self.codehilite_conf.copy()
- local_config.update(config)
- # Combine classes with cssclass. Ensure cssclass is at end
- # as pygments appends a suffix under certain circumstances.
- # Ignore ID as Pygments does not offer an option to set it.
- if classes:
- local_config['css_class'] = '{} {}'.format(
- ' '.join(classes),
- local_config['css_class']
- )
- highliter = CodeHilite(
- m.group('code'),
- lang=lang,
- style=local_config.pop('pygments_style', 'default'),
- **local_config
- )
-
- code = highliter.hilite(shebang=False)
- else:
- id_attr = lang_attr = class_attr = kv_pairs = ''
- if lang:
- lang_attr = ' class="{}{}"'.format(self.config.get('lang_prefix', 'language-'), lang)
- if classes:
- class_attr = ' class="{}"'.format(' '.join(classes))
- if id:
- id_attr = ' id="{}"'.format(id)
- if self.use_attr_list and config and not config.get('use_pygments', False):
- # Only assign key/value pairs to code element if attr_list ext is enabled, key/value pairs
- # were defined on the code block, and the `use_pygments` key was not set to True. The
- # `use_pygments` key could be either set to False or not defined. It is omitted from output.
- kv_pairs = ' ' + ' '.join(
- '{k}="{v}"'.format(k=k, v=v) for k, v in config.items() if k != 'use_pygments'
- )
- code = '
{code}
'.format(
- id=id_attr,
- cls=class_attr,
- lang=lang_attr,
- kv=kv_pairs,
- code=self._escape(m.group('code'))
- )
-
- placeholder = self.md.htmlStash.store(code)
- text = '{}\n{}\n{}'.format(text[:m.start()],
- placeholder,
- text[m.end():])
- else:
- break
- return text.split("\n")
-
- def handle_attrs(self, attrs):
- """ Return tuple: (id, [list, of, classes], {configs}) """
- id = ''
- classes = []
- configs = {}
- for k, v in attrs:
- if k == 'id':
- id = v
- elif k == '.':
- classes.append(v)
- elif k == 'hl_lines':
- configs[k] = parse_hl_lines(v)
- elif k in self.bool_options:
- configs[k] = parseBoolValue(v, fail_on_errors=False, preserve_none=True)
- else:
- configs[k] = v
- return id, classes, configs
-
- def _escape(self, txt):
- """ basic html escaping """
- txt = txt.replace('&', '&')
- txt = txt.replace('<', '<')
- txt = txt.replace('>', '>')
- txt = txt.replace('"', '"')
- return txt
-
-
-def makeExtension(**kwargs): # pragma: no cover
- return FencedCodeExtension(**kwargs)
diff --git a/venv/Lib/site-packages/markdown/extensions/footnotes.py b/venv/Lib/site-packages/markdown/extensions/footnotes.py
deleted file mode 100644
index f6f4c85..0000000
--- a/venv/Lib/site-packages/markdown/extensions/footnotes.py
+++ /dev/null
@@ -1,402 +0,0 @@
-"""
-Footnotes Extension for Python-Markdown
-=======================================
-
-Adds footnote handling to Python-Markdown.
-
-See
-for documentation.
-
-Copyright The Python Markdown Project
-
-License: [BSD](https://opensource.org/licenses/bsd-license.php)
-
-"""
-
-from . import Extension
-from ..blockprocessors import BlockProcessor
-from ..inlinepatterns import InlineProcessor
-from ..treeprocessors import Treeprocessor
-from ..postprocessors import Postprocessor
-from .. import util
-from collections import OrderedDict
-import re
-import copy
-import xml.etree.ElementTree as etree
-
-FN_BACKLINK_TEXT = util.STX + "zz1337820767766393qq" + util.ETX
-NBSP_PLACEHOLDER = util.STX + "qq3936677670287331zz" + util.ETX
-RE_REF_ID = re.compile(r'(fnref)(\d+)')
-
-
-class FootnoteExtension(Extension):
- """ Footnote Extension. """
-
- def __init__(self, **kwargs):
- """ Setup configs. """
-
- self.config = {
- 'PLACE_MARKER':
- ["///Footnotes Go Here///",
- "The text string that marks where the footnotes go"],
- 'UNIQUE_IDS':
- [False,
- "Avoid name collisions across "
- "multiple calls to reset()."],
- "BACKLINK_TEXT":
- ["↩",
- "The text string that links from the footnote "
- "to the reader's place."],
- "BACKLINK_TITLE":
- ["Jump back to footnote %d in the text",
- "The text string used for the title HTML attribute "
- "of the backlink. %d will be replaced by the "
- "footnote number."],
- "SEPARATOR":
- [":",
- "Footnote separator."]
- }
- super().__init__(**kwargs)
-
- # In multiple invocations, emit links that don't get tangled.
- self.unique_prefix = 0
- self.found_refs = {}
- self.used_refs = set()
-
- self.reset()
-
- def extendMarkdown(self, md):
- """ Add pieces to Markdown. """
- md.registerExtension(self)
- self.parser = md.parser
- self.md = md
- # Insert a blockprocessor before ReferencePreprocessor
- md.parser.blockprocessors.register(FootnoteBlockProcessor(self), 'footnote', 17)
-
- # Insert an inline pattern before ImageReferencePattern
- FOOTNOTE_RE = r'\[\^([^\]]*)\]' # blah blah [^1] blah
- md.inlinePatterns.register(FootnoteInlineProcessor(FOOTNOTE_RE, self), 'footnote', 175)
- # Insert a tree-processor that would actually add the footnote div
- # This must be before all other treeprocessors (i.e., inline and
- # codehilite) so they can run on the the contents of the div.
- md.treeprocessors.register(FootnoteTreeprocessor(self), 'footnote', 50)
-
- # Insert a tree-processor that will run after inline is done.
- # In this tree-processor we want to check our duplicate footnote tracker
- # And add additional backrefs to the footnote pointing back to the
- # duplicated references.
- md.treeprocessors.register(FootnotePostTreeprocessor(self), 'footnote-duplicate', 15)
-
- # Insert a postprocessor after amp_substitute processor
- md.postprocessors.register(FootnotePostprocessor(self), 'footnote', 25)
-
- def reset(self):
- """ Clear footnotes on reset, and prepare for distinct document. """
- self.footnotes = OrderedDict()
- self.unique_prefix += 1
- self.found_refs = {}
- self.used_refs = set()
-
- def unique_ref(self, reference, found=False):
- """ Get a unique reference if there are duplicates. """
- if not found:
- return reference
-
- original_ref = reference
- while reference in self.used_refs:
- ref, rest = reference.split(self.get_separator(), 1)
- m = RE_REF_ID.match(ref)
- if m:
- reference = '%s%d%s%s' % (m.group(1), int(m.group(2))+1, self.get_separator(), rest)
- else:
- reference = '%s%d%s%s' % (ref, 2, self.get_separator(), rest)
-
- self.used_refs.add(reference)
- if original_ref in self.found_refs:
- self.found_refs[original_ref] += 1
- else:
- self.found_refs[original_ref] = 1
- return reference
-
- def findFootnotesPlaceholder(self, root):
- """ Return ElementTree Element that contains Footnote placeholder. """
- def finder(element):
- for child in element:
- if child.text:
- if child.text.find(self.getConfig("PLACE_MARKER")) > -1:
- return child, element, True
- if child.tail:
- if child.tail.find(self.getConfig("PLACE_MARKER")) > -1:
- return child, element, False
- child_res = finder(child)
- if child_res is not None:
- return child_res
- return None
-
- res = finder(root)
- return res
-
- def setFootnote(self, id, text):
- """ Store a footnote for later retrieval. """
- self.footnotes[id] = text
-
- def get_separator(self):
- """ Get the footnote separator. """
- return self.getConfig("SEPARATOR")
-
- def makeFootnoteId(self, id):
- """ Return footnote link id. """
- if self.getConfig("UNIQUE_IDS"):
- return 'fn%s%d-%s' % (self.get_separator(), self.unique_prefix, id)
- else:
- return 'fn{}{}'.format(self.get_separator(), id)
-
- def makeFootnoteRefId(self, id, found=False):
- """ Return footnote back-link id. """
- if self.getConfig("UNIQUE_IDS"):
- return self.unique_ref('fnref%s%d-%s' % (self.get_separator(), self.unique_prefix, id), found)
- else:
- return self.unique_ref('fnref{}{}'.format(self.get_separator(), id), found)
-
- def makeFootnotesDiv(self, root):
- """ Return div of footnotes as et Element. """
-
- if not list(self.footnotes.keys()):
- return None
-
- div = etree.Element("div")
- div.set('class', 'footnote')
- etree.SubElement(div, "hr")
- ol = etree.SubElement(div, "ol")
- surrogate_parent = etree.Element("div")
-
- for index, id in enumerate(self.footnotes.keys(), start=1):
- li = etree.SubElement(ol, "li")
- li.set("id", self.makeFootnoteId(id))
- # Parse footnote with surrogate parent as li cannot be used.
- # List block handlers have special logic to deal with li.
- # When we are done parsing, we will copy everything over to li.
- self.parser.parseChunk(surrogate_parent, self.footnotes[id])
- for el in list(surrogate_parent):
- li.append(el)
- surrogate_parent.remove(el)
- backlink = etree.Element("a")
- backlink.set("href", "#" + self.makeFootnoteRefId(id))
- backlink.set("class", "footnote-backref")
- backlink.set(
- "title",
- self.getConfig("BACKLINK_TITLE") % (index)
- )
- backlink.text = FN_BACKLINK_TEXT
-
- if len(li):
- node = li[-1]
- if node.tag == "p":
- node.text = node.text + NBSP_PLACEHOLDER
- node.append(backlink)
- else:
- p = etree.SubElement(li, "p")
- p.append(backlink)
- return div
-
-
-class FootnoteBlockProcessor(BlockProcessor):
- """ Find all footnote references and store for later use. """
-
- RE = re.compile(r'^[ ]{0,3}\[\^([^\]]*)\]:[ ]*(.*)$', re.MULTILINE)
-
- def __init__(self, footnotes):
- super().__init__(footnotes.parser)
- self.footnotes = footnotes
-
- def test(self, parent, block):
- return True
-
- def run(self, parent, blocks):
- """ Find, set, and remove footnote definitions. """
- block = blocks.pop(0)
- m = self.RE.search(block)
- if m:
- id = m.group(1)
- fn_blocks = [m.group(2)]
-
- # Handle rest of block
- therest = block[m.end():].lstrip('\n')
- m2 = self.RE.search(therest)
- if m2:
- # Another footnote exists in the rest of this block.
- # Any content before match is continuation of this footnote, which may be lazily indented.
- before = therest[:m2.start()].rstrip('\n')
- fn_blocks[0] = '\n'.join([fn_blocks[0], self.detab(before)]).lstrip('\n')
- # Add back to blocks everything from begining of match forward for next iteration.
- blocks.insert(0, therest[m2.start():])
- else:
- # All remaining lines of block are continuation of this footnote, which may be lazily indented.
- fn_blocks[0] = '\n'.join([fn_blocks[0], self.detab(therest)]).strip('\n')
-
- # Check for child elements in remaining blocks.
- fn_blocks.extend(self.detectTabbed(blocks))
-
- footnote = "\n\n".join(fn_blocks)
- self.footnotes.setFootnote(id, footnote.rstrip())
-
- if block[:m.start()].strip():
- # Add any content before match back to blocks as separate block
- blocks.insert(0, block[:m.start()].rstrip('\n'))
- return True
- # No match. Restore block.
- blocks.insert(0, block)
- return False
-
- def detectTabbed(self, blocks):
- """ Find indented text and remove indent before further proccesing.
-
- Returns: a list of blocks with indentation removed.
- """
- fn_blocks = []
- while blocks:
- if blocks[0].startswith(' '*4):
- block = blocks.pop(0)
- # Check for new footnotes within this block and split at new footnote.
- m = self.RE.search(block)
- if m:
- # Another footnote exists in this block.
- # Any content before match is continuation of this footnote, which may be lazily indented.
- before = block[:m.start()].rstrip('\n')
- fn_blocks.append(self.detab(before))
- # Add back to blocks everything from begining of match forward for next iteration.
- blocks.insert(0, block[m.start():])
- # End of this footnote.
- break
- else:
- # Entire block is part of this footnote.
- fn_blocks.append(self.detab(block))
- else:
- # End of this footnote.
- break
- return fn_blocks
-
- def detab(self, block):
- """ Remove one level of indent from a block.
-
- Preserve lazily indented blocks by only removing indent from indented lines.
- """
- lines = block.split('\n')
- for i, line in enumerate(lines):
- if line.startswith(' '*4):
- lines[i] = line[4:]
- return '\n'.join(lines)
-
-
-class FootnoteInlineProcessor(InlineProcessor):
- """ InlinePattern for footnote markers in a document's body text. """
-
- def __init__(self, pattern, footnotes):
- super().__init__(pattern)
- self.footnotes = footnotes
-
- def handleMatch(self, m, data):
- id = m.group(1)
- if id in self.footnotes.footnotes.keys():
- sup = etree.Element("sup")
- a = etree.SubElement(sup, "a")
- sup.set('id', self.footnotes.makeFootnoteRefId(id, found=True))
- a.set('href', '#' + self.footnotes.makeFootnoteId(id))
- a.set('class', 'footnote-ref')
- a.text = str(list(self.footnotes.footnotes.keys()).index(id) + 1)
- return sup, m.start(0), m.end(0)
- else:
- return None, None, None
-
-
-class FootnotePostTreeprocessor(Treeprocessor):
- """ Amend footnote div with duplicates. """
-
- def __init__(self, footnotes):
- self.footnotes = footnotes
-
- def add_duplicates(self, li, duplicates):
- """ Adjust current li and add the duplicates: fnref2, fnref3, etc. """
- for link in li.iter('a'):
- # Find the link that needs to be duplicated.
- if link.attrib.get('class', '') == 'footnote-backref':
- ref, rest = link.attrib['href'].split(self.footnotes.get_separator(), 1)
- # Duplicate link the number of times we need to
- # and point the to the appropriate references.
- links = []
- for index in range(2, duplicates + 1):
- sib_link = copy.deepcopy(link)
- sib_link.attrib['href'] = '%s%d%s%s' % (ref, index, self.footnotes.get_separator(), rest)
- links.append(sib_link)
- self.offset += 1
- # Add all the new duplicate links.
- el = list(li)[-1]
- for link in links:
- el.append(link)
- break
-
- def get_num_duplicates(self, li):
- """ Get the number of duplicate refs of the footnote. """
- fn, rest = li.attrib.get('id', '').split(self.footnotes.get_separator(), 1)
- link_id = '{}ref{}{}'.format(fn, self.footnotes.get_separator(), rest)
- return self.footnotes.found_refs.get(link_id, 0)
-
- def handle_duplicates(self, parent):
- """ Find duplicate footnotes and format and add the duplicates. """
- for li in list(parent):
- # Check number of duplicates footnotes and insert
- # additional links if needed.
- count = self.get_num_duplicates(li)
- if count > 1:
- self.add_duplicates(li, count)
-
- def run(self, root):
- """ Crawl the footnote div and add missing duplicate footnotes. """
- self.offset = 0
- for div in root.iter('div'):
- if div.attrib.get('class', '') == 'footnote':
- # Footnotes shoul be under the first orderd list under
- # the footnote div. So once we find it, quit.
- for ol in div.iter('ol'):
- self.handle_duplicates(ol)
- break
-
-
-class FootnoteTreeprocessor(Treeprocessor):
- """ Build and append footnote div to end of document. """
-
- def __init__(self, footnotes):
- self.footnotes = footnotes
-
- def run(self, root):
- footnotesDiv = self.footnotes.makeFootnotesDiv(root)
- if footnotesDiv is not None:
- result = self.footnotes.findFootnotesPlaceholder(root)
- if result:
- child, parent, isText = result
- ind = list(parent).index(child)
- if isText:
- parent.remove(child)
- parent.insert(ind, footnotesDiv)
- else:
- parent.insert(ind + 1, footnotesDiv)
- child.tail = None
- else:
- root.append(footnotesDiv)
-
-
-class FootnotePostprocessor(Postprocessor):
- """ Replace placeholders with html entities. """
- def __init__(self, footnotes):
- self.footnotes = footnotes
-
- def run(self, text):
- text = text.replace(
- FN_BACKLINK_TEXT, self.footnotes.getConfig("BACKLINK_TEXT")
- )
- return text.replace(NBSP_PLACEHOLDER, " ")
-
-
-def makeExtension(**kwargs): # pragma: no cover
- """ Return an instance of the FootnoteExtension """
- return FootnoteExtension(**kwargs)
diff --git a/venv/Lib/site-packages/markdown/extensions/legacy_attrs.py b/venv/Lib/site-packages/markdown/extensions/legacy_attrs.py
deleted file mode 100644
index b51d778..0000000
--- a/venv/Lib/site-packages/markdown/extensions/legacy_attrs.py
+++ /dev/null
@@ -1,67 +0,0 @@
-"""
-Python Markdown
-
-A Python implementation of John Gruber's Markdown.
-
-Documentation: https://python-markdown.github.io/
-GitHub: https://github.com/Python-Markdown/markdown/
-PyPI: https://pypi.org/project/Markdown/
-
-Started by Manfred Stienstra (http://www.dwerg.net/).
-Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org).
-Currently maintained by Waylan Limberg (https://github.com/waylan),
-Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser).
-
-Copyright 2007-2018 The Python Markdown Project (v. 1.7 and later)
-Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b)
-Copyright 2004 Manfred Stienstra (the original version)
-
-License: BSD (see LICENSE.md for details).
-
-Legacy Attributes Extension
-===========================
-
-An extension to Python Markdown which implements legacy attributes.
-
-Prior to Python-Markdown version 3.0, the Markdown class had an `enable_attributes`
-keyword which was on by default and provided for attributes to be defined for elements
-using the format `{@key=value}`. This extension is provided as a replacement for
-backward compatability. New documents should be authored using attr_lists. However,
-numerious documents exist which have been using the old attribute format for many
-years. This extension can be used to continue to render those documents correctly.
-"""
-
-import re
-from markdown.treeprocessors import Treeprocessor, isString
-from markdown.extensions import Extension
-
-
-ATTR_RE = re.compile(r'\{@([^\}]*)=([^\}]*)}') # {@id=123}
-
-
-class LegacyAttrs(Treeprocessor):
- def run(self, doc):
- """Find and set values of attributes ({@key=value}). """
- for el in doc.iter():
- alt = el.get('alt', None)
- if alt is not None:
- el.set('alt', self.handleAttributes(el, alt))
- if el.text and isString(el.text):
- el.text = self.handleAttributes(el, el.text)
- if el.tail and isString(el.tail):
- el.tail = self.handleAttributes(el, el.tail)
-
- def handleAttributes(self, el, txt):
- """ Set attributes and return text without definitions. """
- def attributeCallback(match):
- el.set(match.group(1), match.group(2).replace('\n', ' '))
- return ATTR_RE.sub(attributeCallback, txt)
-
-
-class LegacyAttrExtension(Extension):
- def extendMarkdown(self, md):
- md.treeprocessors.register(LegacyAttrs(md), 'legacyattrs', 15)
-
-
-def makeExtension(**kwargs): # pragma: no cover
- return LegacyAttrExtension(**kwargs)
diff --git a/venv/Lib/site-packages/markdown/extensions/legacy_em.py b/venv/Lib/site-packages/markdown/extensions/legacy_em.py
deleted file mode 100644
index 7fddb77..0000000
--- a/venv/Lib/site-packages/markdown/extensions/legacy_em.py
+++ /dev/null
@@ -1,49 +0,0 @@
-'''
-Legacy Em Extension for Python-Markdown
-=======================================
-
-This extention provides legacy behavior for _connected_words_.
-
-Copyright 2015-2018 The Python Markdown Project
-
-License: [BSD](https://opensource.org/licenses/bsd-license.php)
-
-'''
-
-from . import Extension
-from ..inlinepatterns import UnderscoreProcessor, EmStrongItem, EM_STRONG2_RE, STRONG_EM2_RE
-import re
-
-# _emphasis_
-EMPHASIS_RE = r'(_)([^_]+)\1'
-
-# __strong__
-STRONG_RE = r'(_{2})(.+?)\1'
-
-# __strong_em___
-STRONG_EM_RE = r'(_)\1(?!\1)([^_]+?)\1(?!\1)(.+?)\1{3}'
-
-
-class LegacyUnderscoreProcessor(UnderscoreProcessor):
- """Emphasis processor for handling strong and em matches inside underscores."""
-
- PATTERNS = [
- EmStrongItem(re.compile(EM_STRONG2_RE, re.DOTALL | re.UNICODE), 'double', 'strong,em'),
- EmStrongItem(re.compile(STRONG_EM2_RE, re.DOTALL | re.UNICODE), 'double', 'em,strong'),
- EmStrongItem(re.compile(STRONG_EM_RE, re.DOTALL | re.UNICODE), 'double2', 'strong,em'),
- EmStrongItem(re.compile(STRONG_RE, re.DOTALL | re.UNICODE), 'single', 'strong'),
- EmStrongItem(re.compile(EMPHASIS_RE, re.DOTALL | re.UNICODE), 'single', 'em')
- ]
-
-
-class LegacyEmExtension(Extension):
- """ Add legacy_em extension to Markdown class."""
-
- def extendMarkdown(self, md):
- """ Modify inline patterns. """
- md.inlinePatterns.register(LegacyUnderscoreProcessor(r'_'), 'em_strong2', 50)
-
-
-def makeExtension(**kwargs): # pragma: no cover
- """ Return an instance of the LegacyEmExtension """
- return LegacyEmExtension(**kwargs)
diff --git a/venv/Lib/site-packages/markdown/extensions/md_in_html.py b/venv/Lib/site-packages/markdown/extensions/md_in_html.py
deleted file mode 100644
index 81cc15c..0000000
--- a/venv/Lib/site-packages/markdown/extensions/md_in_html.py
+++ /dev/null
@@ -1,364 +0,0 @@
-"""
-Python-Markdown Markdown in HTML Extension
-===============================
-
-An implementation of [PHP Markdown Extra](http://michelf.com/projects/php-markdown/extra/)'s
-parsing of Markdown syntax in raw HTML.
-
-See
-for documentation.
-
-Copyright The Python Markdown Project
-
-License: [BSD](https://opensource.org/licenses/bsd-license.php)
-
-"""
-
-from . import Extension
-from ..blockprocessors import BlockProcessor
-from ..preprocessors import Preprocessor
-from ..postprocessors import RawHtmlPostprocessor
-from .. import util
-from ..htmlparser import HTMLExtractor, blank_line_re
-import xml.etree.ElementTree as etree
-
-
-class HTMLExtractorExtra(HTMLExtractor):
- """
- Override HTMLExtractor and create etree Elements for any elements which should have content parsed as Markdown.
- """
-
- def __init__(self, md, *args, **kwargs):
- # All block-level tags.
- self.block_level_tags = set(md.block_level_elements.copy())
- # Block-level tags in which the content only gets span level parsing
- self.span_tags = set(
- ['address', 'dd', 'dt', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'legend', 'li', 'p', 'summary', 'td', 'th']
- )
- # Block-level tags which never get their content parsed.
- self.raw_tags = set(['canvas', 'math', 'option', 'pre', 'script', 'style', 'textarea'])
-
- super().__init__(md, *args, **kwargs)
-
- # Block-level tags in which the content gets parsed as blocks
- self.block_tags = set(self.block_level_tags) - (self.span_tags | self.raw_tags | self.empty_tags)
- self.span_and_blocks_tags = self.block_tags | self.span_tags
-
- def reset(self):
- """Reset this instance. Loses all unprocessed data."""
- self.mdstack = [] # When markdown=1, stack contains a list of tags
- self.treebuilder = etree.TreeBuilder()
- self.mdstate = [] # one of 'block', 'span', 'off', or None
- super().reset()
-
- def close(self):
- """Handle any buffered data."""
- super().close()
- # Handle any unclosed tags.
- if self.mdstack:
- # Close the outermost parent. handle_endtag will close all unclosed children.
- self.handle_endtag(self.mdstack[0])
-
- def get_element(self):
- """ Return element from treebuilder and reset treebuilder for later use. """
- element = self.treebuilder.close()
- self.treebuilder = etree.TreeBuilder()
- return element
-
- def get_state(self, tag, attrs):
- """ Return state from tag and `markdown` attr. One of 'block', 'span', or 'off'. """
- md_attr = attrs.get('markdown', '0')
- if md_attr == 'markdown':
- # `` is the same as ``.
- md_attr = '1'
- parent_state = self.mdstate[-1] if self.mdstate else None
- if parent_state == 'off' or (parent_state == 'span' and md_attr != '0'):
- # Only use the parent state if it is more restrictive than the markdown attribute.
- md_attr = parent_state
- if ((md_attr == '1' and tag in self.block_tags) or
- (md_attr == 'block' and tag in self.span_and_blocks_tags)):
- return 'block'
- elif ((md_attr == '1' and tag in self.span_tags) or
- (md_attr == 'span' and tag in self.span_and_blocks_tags)):
- return 'span'
- elif tag in self.block_level_tags:
- return 'off'
- else: # pragma: no cover
- return None
-
- def handle_starttag(self, tag, attrs):
- # Handle tags that should always be empty and do not specify a closing tag
- if tag in self.empty_tags and (self.at_line_start() or self.intail):
- attrs = {key: value if value is not None else key for key, value in attrs}
- if "markdown" in attrs:
- attrs.pop('markdown')
- element = etree.Element(tag, attrs)
- data = etree.tostring(element, encoding='unicode', method='html')
- else:
- data = self.get_starttag_text()
- self.handle_empty_tag(data, True)
- return
-
- if tag in self.block_level_tags and (self.at_line_start() or self.intail):
- # Valueless attr (ex: ``) results in `[('checked', None)]`.
- # Convert to `{'checked': 'checked'}`.
- attrs = {key: value if value is not None else key for key, value in attrs}
- state = self.get_state(tag, attrs)
- if self.inraw or (state in [None, 'off'] and not self.mdstack):
- # fall back to default behavior
- attrs.pop('markdown', None)
- super().handle_starttag(tag, attrs)
- else:
- if 'p' in self.mdstack and tag in self.block_level_tags:
- # Close unclosed 'p' tag
- self.handle_endtag('p')
- self.mdstate.append(state)
- self.mdstack.append(tag)
- attrs['markdown'] = state
- self.treebuilder.start(tag, attrs)
- else:
- # Span level tag
- if self.inraw:
- super().handle_starttag(tag, attrs)
- else:
- text = self.get_starttag_text()
- if self.mdstate and self.mdstate[-1] == "off":
- self.handle_data(self.md.htmlStash.store(text))
- else:
- self.handle_data(text)
- if tag in self.CDATA_CONTENT_ELEMENTS:
- # This is presumably a standalone tag in a code span (see #1036).
- self.clear_cdata_mode()
-
- def handle_endtag(self, tag):
- if tag in self.block_level_tags:
- if self.inraw:
- super().handle_endtag(tag)
- elif tag in self.mdstack:
- # Close element and any unclosed children
- while self.mdstack:
- item = self.mdstack.pop()
- self.mdstate.pop()
- self.treebuilder.end(item)
- if item == tag:
- break
- if not self.mdstack:
- # Last item in stack is closed. Stash it
- element = self.get_element()
- # Get last entry to see if it ends in newlines
- # If it is an element, assume there is no newlines
- item = self.cleandoc[-1] if self.cleandoc else ''
- # If we only have one newline before block element, add another
- if not item.endswith('\n\n') and item.endswith('\n'):
- self.cleandoc.append('\n')
- self.cleandoc.append(self.md.htmlStash.store(element))
- self.cleandoc.append('\n\n')
- self.state = []
- # Check if element has a tail
- if not blank_line_re.match(
- self.rawdata[self.line_offset + self.offset + len(self.get_endtag_text(tag)):]):
- # More content exists after endtag.
- self.intail = True
- else:
- # Treat orphan closing tag as a span level tag.
- text = self.get_endtag_text(tag)
- if self.mdstate and self.mdstate[-1] == "off":
- self.handle_data(self.md.htmlStash.store(text))
- else:
- self.handle_data(text)
- else:
- # Span level tag
- if self.inraw:
- super().handle_endtag(tag)
- else:
- text = self.get_endtag_text(tag)
- if self.mdstate and self.mdstate[-1] == "off":
- self.handle_data(self.md.htmlStash.store(text))
- else:
- self.handle_data(text)
-
- def handle_startendtag(self, tag, attrs):
- if tag in self.empty_tags:
- attrs = {key: value if value is not None else key for key, value in attrs}
- if "markdown" in attrs:
- attrs.pop('markdown')
- element = etree.Element(tag, attrs)
- data = etree.tostring(element, encoding='unicode', method='html')
- else:
- data = self.get_starttag_text()
- else:
- data = self.get_starttag_text()
- self.handle_empty_tag(data, is_block=self.md.is_block_level(tag))
-
- def handle_data(self, data):
- if self.intail and '\n' in data:
- self.intail = False
- if self.inraw or not self.mdstack:
- super().handle_data(data)
- else:
- self.treebuilder.data(data)
-
- def handle_empty_tag(self, data, is_block):
- if self.inraw or not self.mdstack:
- super().handle_empty_tag(data, is_block)
- else:
- if self.at_line_start() and is_block:
- self.handle_data('\n' + self.md.htmlStash.store(data) + '\n\n')
- else:
- self.handle_data(self.md.htmlStash.store(data))
-
- def parse_pi(self, i):
- if self.at_line_start() or self.intail or self.mdstack:
- # The same override exists in HTMLExtractor without the check
- # for mdstack. Therefore, use HTMLExtractor's parent instead.
- return super(HTMLExtractor, self).parse_pi(i)
- # This is not the beginning of a raw block so treat as plain data
- # and avoid consuming any tags which may follow (see #1066).
- self.handle_data('')
- return i + 2
-
- def parse_html_declaration(self, i):
- if self.at_line_start() or self.intail or self.mdstack:
- # The same override exists in HTMLExtractor without the check
- # for mdstack. Therefore, use HTMLExtractor's parent instead.
- return super(HTMLExtractor, self).parse_html_declaration(i)
- # This is not the beginning of a raw block so treat as plain data
- # and avoid consuming any tags which may follow (see #1066).
- self.handle_data('
-for documentation.
-
-Original code Copyright 2007-2008 [Waylan Limberg](http://achinghead.com).
-
-All changes Copyright 2008-2014 The Python Markdown Project
-
-License: [BSD](https://opensource.org/licenses/bsd-license.php)
-
-"""
-
-from . import Extension
-from ..preprocessors import Preprocessor
-import re
-import logging
-
-log = logging.getLogger('MARKDOWN')
-
-# Global Vars
-META_RE = re.compile(r'^[ ]{0,3}(?P[A-Za-z0-9_-]+):\s*(?P.*)')
-META_MORE_RE = re.compile(r'^[ ]{4,}(?P.*)')
-BEGIN_RE = re.compile(r'^-{3}(\s.*)?')
-END_RE = re.compile(r'^(-{3}|\.{3})(\s.*)?')
-
-
-class MetaExtension (Extension):
- """ Meta-Data extension for Python-Markdown. """
-
- def extendMarkdown(self, md):
- """ Add MetaPreprocessor to Markdown instance. """
- md.registerExtension(self)
- self.md = md
- md.preprocessors.register(MetaPreprocessor(md), 'meta', 27)
-
- def reset(self):
- self.md.Meta = {}
-
-
-class MetaPreprocessor(Preprocessor):
- """ Get Meta-Data. """
-
- def run(self, lines):
- """ Parse Meta-Data and store in Markdown.Meta. """
- meta = {}
- key = None
- if lines and BEGIN_RE.match(lines[0]):
- lines.pop(0)
- while lines:
- line = lines.pop(0)
- m1 = META_RE.match(line)
- if line.strip() == '' or END_RE.match(line):
- break # blank line or end of YAML header - done
- if m1:
- key = m1.group('key').lower().strip()
- value = m1.group('value').strip()
- try:
- meta[key].append(value)
- except KeyError:
- meta[key] = [value]
- else:
- m2 = META_MORE_RE.match(line)
- if m2 and key:
- # Add another line to existing key
- meta[key].append(m2.group('value').strip())
- else:
- lines.insert(0, line)
- break # no meta data - done
- self.md.Meta = meta
- return lines
-
-
-def makeExtension(**kwargs): # pragma: no cover
- return MetaExtension(**kwargs)
diff --git a/venv/Lib/site-packages/markdown/extensions/nl2br.py b/venv/Lib/site-packages/markdown/extensions/nl2br.py
deleted file mode 100644
index 6c7491b..0000000
--- a/venv/Lib/site-packages/markdown/extensions/nl2br.py
+++ /dev/null
@@ -1,33 +0,0 @@
-"""
-NL2BR Extension
-===============
-
-A Python-Markdown extension to treat newlines as hard breaks; like
-GitHub-flavored Markdown does.
-
-See
-for documentation.
-
-Oringinal code Copyright 2011 [Brian Neal](https://deathofagremmie.com/)
-
-All changes Copyright 2011-2014 The Python Markdown Project
-
-License: [BSD](https://opensource.org/licenses/bsd-license.php)
-
-"""
-
-from . import Extension
-from ..inlinepatterns import SubstituteTagInlineProcessor
-
-BR_RE = r'\n'
-
-
-class Nl2BrExtension(Extension):
-
- def extendMarkdown(self, md):
- br_tag = SubstituteTagInlineProcessor(BR_RE, 'br')
- md.inlinePatterns.register(br_tag, 'nl', 5)
-
-
-def makeExtension(**kwargs): # pragma: no cover
- return Nl2BrExtension(**kwargs)
diff --git a/venv/Lib/site-packages/markdown/extensions/sane_lists.py b/venv/Lib/site-packages/markdown/extensions/sane_lists.py
deleted file mode 100644
index e27eb18..0000000
--- a/venv/Lib/site-packages/markdown/extensions/sane_lists.py
+++ /dev/null
@@ -1,54 +0,0 @@
-"""
-Sane List Extension for Python-Markdown
-=======================================
-
-Modify the behavior of Lists in Python-Markdown to act in a sane manor.
-
-See
-for documentation.
-
-Original code Copyright 2011 [Waylan Limberg](http://achinghead.com)
-
-All changes Copyright 2011-2014 The Python Markdown Project
-
-License: [BSD](https://opensource.org/licenses/bsd-license.php)
-
-"""
-
-from . import Extension
-from ..blockprocessors import OListProcessor, UListProcessor
-import re
-
-
-class SaneOListProcessor(OListProcessor):
-
- SIBLING_TAGS = ['ol']
- LAZY_OL = False
-
- def __init__(self, parser):
- super().__init__(parser)
- self.CHILD_RE = re.compile(r'^[ ]{0,%d}((\d+\.))[ ]+(.*)' %
- (self.tab_length - 1))
-
-
-class SaneUListProcessor(UListProcessor):
-
- SIBLING_TAGS = ['ul']
-
- def __init__(self, parser):
- super().__init__(parser)
- self.CHILD_RE = re.compile(r'^[ ]{0,%d}(([*+-]))[ ]+(.*)' %
- (self.tab_length - 1))
-
-
-class SaneListExtension(Extension):
- """ Add sane lists to Markdown. """
-
- def extendMarkdown(self, md):
- """ Override existing Processors. """
- md.parser.blockprocessors.register(SaneOListProcessor(md.parser), 'olist', 40)
- md.parser.blockprocessors.register(SaneUListProcessor(md.parser), 'ulist', 30)
-
-
-def makeExtension(**kwargs): # pragma: no cover
- return SaneListExtension(**kwargs)
diff --git a/venv/Lib/site-packages/markdown/extensions/smarty.py b/venv/Lib/site-packages/markdown/extensions/smarty.py
deleted file mode 100644
index 894805f..0000000
--- a/venv/Lib/site-packages/markdown/extensions/smarty.py
+++ /dev/null
@@ -1,263 +0,0 @@
-'''
-Smarty extension for Python-Markdown
-====================================
-
-Adds conversion of ASCII dashes, quotes and ellipses to their HTML
-entity equivalents.
-
-See
-for documentation.
-
-Author: 2013, Dmitry Shachnev
-
-All changes Copyright 2013-2014 The Python Markdown Project
-
-License: [BSD](https://opensource.org/licenses/bsd-license.php)
-
-SmartyPants license:
-
- Copyright (c) 2003 John Gruber
- All rights reserved.
-
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are
- met:
-
- * Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
-
- * Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in
- the documentation and/or other materials provided with the
- distribution.
-
- * Neither the name "SmartyPants" nor the names of its contributors
- may be used to endorse or promote products derived from this
- software without specific prior written permission.
-
- This software is provided by the copyright holders and contributors "as
- is" and any express or implied warranties, including, but not limited
- to, the implied warranties of merchantability and fitness for a
- particular purpose are disclaimed. In no event shall the copyright
- owner or contributors be liable for any direct, indirect, incidental,
- special, exemplary, or consequential damages (including, but not
- limited to, procurement of substitute goods or services; loss of use,
- data, or profits; or business interruption) however caused and on any
- theory of liability, whether in contract, strict liability, or tort
- (including negligence or otherwise) arising in any way out of the use
- of this software, even if advised of the possibility of such damage.
-
-
-smartypants.py license:
-
- smartypants.py is a derivative work of SmartyPants.
- Copyright (c) 2004, 2007 Chad Miller
-
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are
- met:
-
- * Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
-
- * Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in
- the documentation and/or other materials provided with the
- distribution.
-
- This software is provided by the copyright holders and contributors "as
- is" and any express or implied warranties, including, but not limited
- to, the implied warranties of merchantability and fitness for a
- particular purpose are disclaimed. In no event shall the copyright
- owner or contributors be liable for any direct, indirect, incidental,
- special, exemplary, or consequential damages (including, but not
- limited to, procurement of substitute goods or services; loss of use,
- data, or profits; or business interruption) however caused and on any
- theory of liability, whether in contract, strict liability, or tort
- (including negligence or otherwise) arising in any way out of the use
- of this software, even if advised of the possibility of such damage.
-
-'''
-
-
-from . import Extension
-from ..inlinepatterns import HtmlInlineProcessor, HTML_RE
-from ..treeprocessors import InlineProcessor
-from ..util import Registry, deprecated
-
-
-# Constants for quote education.
-punctClass = r"""[!"#\$\%'()*+,-.\/:;<=>?\@\[\\\]\^_`{|}~]"""
-endOfWordClass = r"[\s.,;:!?)]"
-closeClass = r"[^\ \t\r\n\[\{\(\-\u0002\u0003]"
-
-openingQuotesBase = (
- r'(\s' # a whitespace char
- r'| ' # or a non-breaking space entity
- r'|--' # or dashes
- r'|–|—' # or unicode
- r'|&[mn]dash;' # or named dash entities
- r'|–|—' # or decimal entities
- r')'
-)
-
-substitutions = {
- 'mdash': '—',
- 'ndash': '–',
- 'ellipsis': '…',
- 'left-angle-quote': '«',
- 'right-angle-quote': '»',
- 'left-single-quote': '‘',
- 'right-single-quote': '’',
- 'left-double-quote': '“',
- 'right-double-quote': '”',
-}
-
-
-# Special case if the very first character is a quote
-# followed by punctuation at a non-word-break. Close the quotes by brute force:
-singleQuoteStartRe = r"^'(?=%s\B)" % punctClass
-doubleQuoteStartRe = r'^"(?=%s\B)' % punctClass
-
-# Special case for double sets of quotes, e.g.:
-#
He said, "'Quoted' words in a larger quote."
-doubleQuoteSetsRe = r""""'(?=\w)"""
-singleQuoteSetsRe = r"""'"(?=\w)"""
-
-# Special case for decade abbreviations (the '80s):
-decadeAbbrRe = r"(?)'
-
-
-class SubstituteTextPattern(HtmlInlineProcessor):
- def __init__(self, pattern, replace, md):
- """ Replaces matches with some text. """
- HtmlInlineProcessor.__init__(self, pattern)
- self.replace = replace
- self.md = md
-
- @property
- @deprecated("Use 'md' instead.")
- def markdown(self):
- # TODO: remove this later
- return self.md
-
- def handleMatch(self, m, data):
- result = ''
- for part in self.replace:
- if isinstance(part, int):
- result += m.group(part)
- else:
- result += self.md.htmlStash.store(part)
- return result, m.start(0), m.end(0)
-
-
-class SmartyExtension(Extension):
- def __init__(self, **kwargs):
- self.config = {
- 'smart_quotes': [True, 'Educate quotes'],
- 'smart_angled_quotes': [False, 'Educate angled quotes'],
- 'smart_dashes': [True, 'Educate dashes'],
- 'smart_ellipses': [True, 'Educate ellipses'],
- 'substitutions': [{}, 'Overwrite default substitutions'],
- }
- super().__init__(**kwargs)
- self.substitutions = dict(substitutions)
- self.substitutions.update(self.getConfig('substitutions', default={}))
-
- def _addPatterns(self, md, patterns, serie, priority):
- for ind, pattern in enumerate(patterns):
- pattern += (md,)
- pattern = SubstituteTextPattern(*pattern)
- name = 'smarty-%s-%d' % (serie, ind)
- self.inlinePatterns.register(pattern, name, priority-ind)
-
- def educateDashes(self, md):
- emDashesPattern = SubstituteTextPattern(
- r'(?\>', (self.substitutions['right-angle-quote'],), md
- )
- self.inlinePatterns.register(leftAngledQuotePattern, 'smarty-left-angle-quotes', 40)
- self.inlinePatterns.register(rightAngledQuotePattern, 'smarty-right-angle-quotes', 35)
-
- def educateQuotes(self, md):
- lsquo = self.substitutions['left-single-quote']
- rsquo = self.substitutions['right-single-quote']
- ldquo = self.substitutions['left-double-quote']
- rdquo = self.substitutions['right-double-quote']
- patterns = (
- (singleQuoteStartRe, (rsquo,)),
- (doubleQuoteStartRe, (rdquo,)),
- (doubleQuoteSetsRe, (ldquo + lsquo,)),
- (singleQuoteSetsRe, (lsquo + ldquo,)),
- (decadeAbbrRe, (rsquo,)),
- (openingSingleQuotesRegex, (1, lsquo)),
- (closingSingleQuotesRegex, (rsquo,)),
- (closingSingleQuotesRegex2, (rsquo, 1)),
- (remainingSingleQuotesRegex, (lsquo,)),
- (openingDoubleQuotesRegex, (1, ldquo)),
- (closingDoubleQuotesRegex, (rdquo,)),
- (closingDoubleQuotesRegex2, (rdquo,)),
- (remainingDoubleQuotesRegex, (ldquo,))
- )
- self._addPatterns(md, patterns, 'quotes', 30)
-
- def extendMarkdown(self, md):
- configs = self.getConfigs()
- self.inlinePatterns = Registry()
- if configs['smart_ellipses']:
- self.educateEllipses(md)
- if configs['smart_quotes']:
- self.educateQuotes(md)
- if configs['smart_angled_quotes']:
- self.educateAngledQuotes(md)
- # Override HTML_RE from inlinepatterns.py so that it does not
- # process tags with duplicate closing quotes.
- md.inlinePatterns.register(HtmlInlineProcessor(HTML_STRICT_RE, md), 'html', 90)
- if configs['smart_dashes']:
- self.educateDashes(md)
- inlineProcessor = InlineProcessor(md)
- inlineProcessor.inlinePatterns = self.inlinePatterns
- md.treeprocessors.register(inlineProcessor, 'smarty', 2)
- md.ESCAPED_CHARS.extend(['"', "'"])
-
-
-def makeExtension(**kwargs): # pragma: no cover
- return SmartyExtension(**kwargs)
diff --git a/venv/Lib/site-packages/markdown/extensions/tables.py b/venv/Lib/site-packages/markdown/extensions/tables.py
deleted file mode 100644
index 4b027bb..0000000
--- a/venv/Lib/site-packages/markdown/extensions/tables.py
+++ /dev/null
@@ -1,223 +0,0 @@
-"""
-Tables Extension for Python-Markdown
-====================================
-
-Added parsing of tables to Python-Markdown.
-
-See
-for documentation.
-
-Original code Copyright 2009 [Waylan Limberg](http://achinghead.com)
-
-All changes Copyright 2008-2014 The Python Markdown Project
-
-License: [BSD](https://opensource.org/licenses/bsd-license.php)
-
-"""
-
-from . import Extension
-from ..blockprocessors import BlockProcessor
-import xml.etree.ElementTree as etree
-import re
-PIPE_NONE = 0
-PIPE_LEFT = 1
-PIPE_RIGHT = 2
-
-
-class TableProcessor(BlockProcessor):
- """ Process Tables. """
-
- RE_CODE_PIPES = re.compile(r'(?:(\\\\)|(\\`+)|(`+)|(\\\|)|(\|))')
- RE_END_BORDER = re.compile(r'(? 1:
- header0 = rows[0]
- self.border = PIPE_NONE
- if header0.startswith('|'):
- self.border |= PIPE_LEFT
- if self.RE_END_BORDER.search(header0) is not None:
- self.border |= PIPE_RIGHT
- row = self._split_row(header0)
- row0_len = len(row)
- is_table = row0_len > 1
-
- # Each row in a single column table needs at least one pipe.
- if not is_table and row0_len == 1 and self.border:
- for index in range(1, len(rows)):
- is_table = rows[index].startswith('|')
- if not is_table:
- is_table = self.RE_END_BORDER.search(rows[index]) is not None
- if not is_table:
- break
-
- if is_table:
- row = self._split_row(rows[1])
- is_table = (len(row) == row0_len) and set(''.join(row)) <= set('|:- ')
- if is_table:
- self.separator = row
-
- return is_table
-
- def run(self, parent, blocks):
- """ Parse a table block and build table. """
- block = blocks.pop(0).split('\n')
- header = block[0].strip(' ')
- rows = [] if len(block) < 3 else block[2:]
-
- # Get alignment of columns
- align = []
- for c in self.separator:
- c = c.strip(' ')
- if c.startswith(':') and c.endswith(':'):
- align.append('center')
- elif c.startswith(':'):
- align.append('left')
- elif c.endswith(':'):
- align.append('right')
- else:
- align.append(None)
-
- # Build table
- table = etree.SubElement(parent, 'table')
- thead = etree.SubElement(table, 'thead')
- self._build_row(header, thead, align)
- tbody = etree.SubElement(table, 'tbody')
- if len(rows) == 0:
- # Handle empty table
- self._build_empty_row(tbody, align)
- else:
- for row in rows:
- self._build_row(row.strip(' '), tbody, align)
-
- def _build_empty_row(self, parent, align):
- """Build an empty row."""
- tr = etree.SubElement(parent, 'tr')
- count = len(align)
- while count:
- etree.SubElement(tr, 'td')
- count -= 1
-
- def _build_row(self, row, parent, align):
- """ Given a row of text, build table cells. """
- tr = etree.SubElement(parent, 'tr')
- tag = 'td'
- if parent.tag == 'thead':
- tag = 'th'
- cells = self._split_row(row)
- # We use align here rather than cells to ensure every row
- # contains the same number of columns.
- for i, a in enumerate(align):
- c = etree.SubElement(tr, tag)
- try:
- c.text = cells[i].strip(' ')
- except IndexError: # pragma: no cover
- c.text = ""
- if a:
- c.set('align', a)
-
- def _split_row(self, row):
- """ split a row of text into list of cells. """
- if self.border:
- if row.startswith('|'):
- row = row[1:]
- row = self.RE_END_BORDER.sub('', row)
- return self._split(row)
-
- def _split(self, row):
- """ split a row of text with some code into a list of cells. """
- elements = []
- pipes = []
- tics = []
- tic_points = []
- tic_region = []
- good_pipes = []
-
- # Parse row
- # Throw out \\, and \|
- for m in self.RE_CODE_PIPES.finditer(row):
- # Store ` data (len, start_pos, end_pos)
- if m.group(2):
- # \`+
- # Store length of each tic group: subtract \
- tics.append(len(m.group(2)) - 1)
- # Store start of group, end of group, and escape length
- tic_points.append((m.start(2), m.end(2) - 1, 1))
- elif m.group(3):
- # `+
- # Store length of each tic group
- tics.append(len(m.group(3)))
- # Store start of group, end of group, and escape length
- tic_points.append((m.start(3), m.end(3) - 1, 0))
- # Store pipe location
- elif m.group(5):
- pipes.append(m.start(5))
-
- # Pair up tics according to size if possible
- # Subtract the escape length *only* from the opening.
- # Walk through tic list and see if tic has a close.
- # Store the tic region (start of region, end of region).
- pos = 0
- tic_len = len(tics)
- while pos < tic_len:
- try:
- tic_size = tics[pos] - tic_points[pos][2]
- if tic_size == 0:
- raise ValueError
- index = tics[pos + 1:].index(tic_size) + 1
- tic_region.append((tic_points[pos][0], tic_points[pos + index][1]))
- pos += index + 1
- except ValueError:
- pos += 1
-
- # Resolve pipes. Check if they are within a tic pair region.
- # Walk through pipes comparing them to each region.
- # - If pipe position is less that a region, it isn't in a region
- # - If it is within a region, we don't want it, so throw it out
- # - If we didn't throw it out, it must be a table pipe
- for pipe in pipes:
- throw_out = False
- for region in tic_region:
- if pipe < region[0]:
- # Pipe is not in a region
- break
- elif region[0] <= pipe <= region[1]:
- # Pipe is within a code region. Throw it out.
- throw_out = True
- break
- if not throw_out:
- good_pipes.append(pipe)
-
- # Split row according to table delimeters.
- pos = 0
- for pipe in good_pipes:
- elements.append(row[pos:pipe])
- pos = pipe + 1
- elements.append(row[pos:])
- return elements
-
-
-class TableExtension(Extension):
- """ Add tables to Markdown. """
-
- def extendMarkdown(self, md):
- """ Add an instance of TableProcessor to BlockParser. """
- if '|' not in md.ESCAPED_CHARS:
- md.ESCAPED_CHARS.append('|')
- md.parser.blockprocessors.register(TableProcessor(md.parser), 'table', 75)
-
-
-def makeExtension(**kwargs): # pragma: no cover
- return TableExtension(**kwargs)
diff --git a/venv/Lib/site-packages/markdown/extensions/toc.py b/venv/Lib/site-packages/markdown/extensions/toc.py
deleted file mode 100644
index e4dc378..0000000
--- a/venv/Lib/site-packages/markdown/extensions/toc.py
+++ /dev/null
@@ -1,380 +0,0 @@
-"""
-Table of Contents Extension for Python-Markdown
-===============================================
-
-See
-for documentation.
-
-Oringinal code Copyright 2008 [Jack Miller](https://codezen.org/)
-
-All changes Copyright 2008-2014 The Python Markdown Project
-
-License: [BSD](https://opensource.org/licenses/bsd-license.php)
-
-"""
-
-from . import Extension
-from ..treeprocessors import Treeprocessor
-from ..util import code_escape, parseBoolValue, AMP_SUBSTITUTE, HTML_PLACEHOLDER_RE, AtomicString
-from ..postprocessors import UnescapePostprocessor
-import re
-import html
-import unicodedata
-import xml.etree.ElementTree as etree
-
-
-def slugify(value, separator, unicode=False):
- """ Slugify a string, to make it URL friendly. """
- if not unicode:
- # Replace Extended Latin characters with ASCII, i.e. žlutý → zluty
- value = unicodedata.normalize('NFKD', value)
- value = value.encode('ascii', 'ignore').decode('ascii')
- value = re.sub(r'[^\w\s-]', '', value).strip().lower()
- return re.sub(r'[{}\s]+'.format(separator), separator, value)
-
-
-def slugify_unicode(value, separator):
- """ Slugify a string, to make it URL friendly while preserving Unicode characters. """
- return slugify(value, separator, unicode=True)
-
-
-IDCOUNT_RE = re.compile(r'^(.*)_([0-9]+)$')
-
-
-def unique(id, ids):
- """ Ensure id is unique in set of ids. Append '_1', '_2'... if not """
- while id in ids or not id:
- m = IDCOUNT_RE.match(id)
- if m:
- id = '%s_%d' % (m.group(1), int(m.group(2))+1)
- else:
- id = '%s_%d' % (id, 1)
- ids.add(id)
- return id
-
-
-def get_name(el):
- """Get title name."""
-
- text = []
- for c in el.itertext():
- if isinstance(c, AtomicString):
- text.append(html.unescape(c))
- else:
- text.append(c)
- return ''.join(text).strip()
-
-
-def stashedHTML2text(text, md, strip_entities=True):
- """ Extract raw HTML from stash, reduce to plain text and swap with placeholder. """
- def _html_sub(m):
- """ Substitute raw html with plain text. """
- try:
- raw = md.htmlStash.rawHtmlBlocks[int(m.group(1))]
- except (IndexError, TypeError): # pragma: no cover
- return m.group(0)
- # Strip out tags and/or entities - leaving text
- res = re.sub(r'(<[^>]+>)', '', raw)
- if strip_entities:
- res = re.sub(r'(&[\#a-zA-Z0-9]+;)', '', res)
- return res
-
- return HTML_PLACEHOLDER_RE.sub(_html_sub, text)
-
-
-def unescape(text):
- """ Unescape escaped text. """
- c = UnescapePostprocessor()
- return c.run(text)
-
-
-def nest_toc_tokens(toc_list):
- """Given an unsorted list with errors and skips, return a nested one.
- [{'level': 1}, {'level': 2}]
- =>
- [{'level': 1, 'children': [{'level': 2, 'children': []}]}]
-
- A wrong list is also converted:
- [{'level': 2}, {'level': 1}]
- =>
- [{'level': 2, 'children': []}, {'level': 1, 'children': []}]
- """
-
- ordered_list = []
- if len(toc_list):
- # Initialize everything by processing the first entry
- last = toc_list.pop(0)
- last['children'] = []
- levels = [last['level']]
- ordered_list.append(last)
- parents = []
-
- # Walk the rest nesting the entries properly
- while toc_list:
- t = toc_list.pop(0)
- current_level = t['level']
- t['children'] = []
-
- # Reduce depth if current level < last item's level
- if current_level < levels[-1]:
- # Pop last level since we know we are less than it
- levels.pop()
-
- # Pop parents and levels we are less than or equal to
- to_pop = 0
- for p in reversed(parents):
- if current_level <= p['level']:
- to_pop += 1
- else: # pragma: no cover
- break
- if to_pop:
- levels = levels[:-to_pop]
- parents = parents[:-to_pop]
-
- # Note current level as last
- levels.append(current_level)
-
- # Level is the same, so append to
- # the current parent (if available)
- if current_level == levels[-1]:
- (parents[-1]['children'] if parents
- else ordered_list).append(t)
-
- # Current level is > last item's level,
- # So make last item a parent and append current as child
- else:
- last['children'].append(t)
- parents.append(last)
- levels.append(current_level)
- last = t
-
- return ordered_list
-
-
-class TocTreeprocessor(Treeprocessor):
- def __init__(self, md, config):
- super().__init__(md)
-
- self.marker = config["marker"]
- self.title = config["title"]
- self.base_level = int(config["baselevel"]) - 1
- self.slugify = config["slugify"]
- self.sep = config["separator"]
- self.use_anchors = parseBoolValue(config["anchorlink"])
- self.anchorlink_class = config["anchorlink_class"]
- self.use_permalinks = parseBoolValue(config["permalink"], False)
- if self.use_permalinks is None:
- self.use_permalinks = config["permalink"]
- self.permalink_class = config["permalink_class"]
- self.permalink_title = config["permalink_title"]
- self.header_rgx = re.compile("[Hh][123456]")
- if isinstance(config["toc_depth"], str) and '-' in config["toc_depth"]:
- self.toc_top, self.toc_bottom = [int(x) for x in config["toc_depth"].split('-')]
- else:
- self.toc_top = 1
- self.toc_bottom = int(config["toc_depth"])
-
- def iterparent(self, node):
- ''' Iterator wrapper to get allowed parent and child all at once. '''
-
- # We do not allow the marker inside a header as that
- # would causes an enless loop of placing a new TOC
- # inside previously generated TOC.
- for child in node:
- if not self.header_rgx.match(child.tag) and child.tag not in ['pre', 'code']:
- yield node, child
- yield from self.iterparent(child)
-
- def replace_marker(self, root, elem):
- ''' Replace marker with elem. '''
- for (p, c) in self.iterparent(root):
- text = ''.join(c.itertext()).strip()
- if not text:
- continue
-
- # To keep the output from screwing up the
- # validation by putting a
inside of a
- # we actually replace the
in its entirety.
-
- # The
element may contain more than a single text content
- # (nl2br can introduce a ). In this situation, c.text returns
- # the very first content, ignore children contents or tail content.
- # len(c) == 0 is here to ensure there is only text in the
.
- if c.text and c.text.strip() == self.marker and len(c) == 0:
- for i in range(len(p)):
- if p[i] == c:
- p[i] = elem
- break
-
- def set_level(self, elem):
- ''' Adjust header level according to base level. '''
- level = int(elem.tag[-1]) + self.base_level
- if level > 6:
- level = 6
- elem.tag = 'h%d' % level
-
- def add_anchor(self, c, elem_id): # @ReservedAssignment
- anchor = etree.Element("a")
- anchor.text = c.text
- anchor.attrib["href"] = "#" + elem_id
- anchor.attrib["class"] = self.anchorlink_class
- c.text = ""
- for elem in c:
- anchor.append(elem)
- while len(c):
- c.remove(c[0])
- c.append(anchor)
-
- def add_permalink(self, c, elem_id):
- permalink = etree.Element("a")
- permalink.text = ("%spara;" % AMP_SUBSTITUTE
- if self.use_permalinks is True
- else self.use_permalinks)
- permalink.attrib["href"] = "#" + elem_id
- permalink.attrib["class"] = self.permalink_class
- if self.permalink_title:
- permalink.attrib["title"] = self.permalink_title
- c.append(permalink)
-
- def build_toc_div(self, toc_list):
- """ Return a string div given a toc list. """
- div = etree.Element("div")
- div.attrib["class"] = "toc"
-
- # Add title to the div
- if self.title:
- header = etree.SubElement(div, "span")
- header.attrib["class"] = "toctitle"
- header.text = self.title
-
- def build_etree_ul(toc_list, parent):
- ul = etree.SubElement(parent, "ul")
- for item in toc_list:
- # List item link, to be inserted into the toc div
- li = etree.SubElement(ul, "li")
- link = etree.SubElement(li, "a")
- link.text = item.get('name', '')
- link.attrib["href"] = '#' + item.get('id', '')
- if item['children']:
- build_etree_ul(item['children'], li)
- return ul
-
- build_etree_ul(toc_list, div)
-
- if 'prettify' in self.md.treeprocessors:
- self.md.treeprocessors['prettify'].run(div)
-
- return div
-
- def run(self, doc):
- # Get a list of id attributes
- used_ids = set()
- for el in doc.iter():
- if "id" in el.attrib:
- used_ids.add(el.attrib["id"])
-
- toc_tokens = []
- for el in doc.iter():
- if isinstance(el.tag, str) and self.header_rgx.match(el.tag):
- self.set_level(el)
- text = get_name(el)
-
- # Do not override pre-existing ids
- if "id" not in el.attrib:
- innertext = unescape(stashedHTML2text(text, self.md))
- el.attrib["id"] = unique(self.slugify(innertext, self.sep), used_ids)
-
- if int(el.tag[-1]) >= self.toc_top and int(el.tag[-1]) <= self.toc_bottom:
- toc_tokens.append({
- 'level': int(el.tag[-1]),
- 'id': el.attrib["id"],
- 'name': unescape(stashedHTML2text(
- code_escape(el.attrib.get('data-toc-label', text)),
- self.md, strip_entities=False
- ))
- })
-
- # Remove the data-toc-label attribute as it is no longer needed
- if 'data-toc-label' in el.attrib:
- del el.attrib['data-toc-label']
-
- if self.use_anchors:
- self.add_anchor(el, el.attrib["id"])
- if self.use_permalinks not in [False, None]:
- self.add_permalink(el, el.attrib["id"])
-
- toc_tokens = nest_toc_tokens(toc_tokens)
- div = self.build_toc_div(toc_tokens)
- if self.marker:
- self.replace_marker(doc, div)
-
- # serialize and attach to markdown instance.
- toc = self.md.serializer(div)
- for pp in self.md.postprocessors:
- toc = pp.run(toc)
- self.md.toc_tokens = toc_tokens
- self.md.toc = toc
-
-
-class TocExtension(Extension):
-
- TreeProcessorClass = TocTreeprocessor
-
- def __init__(self, **kwargs):
- self.config = {
- "marker": ['[TOC]',
- 'Text to find and replace with Table of Contents - '
- 'Set to an empty string to disable. Defaults to "[TOC]"'],
- "title": ["",
- "Title to insert into TOC
- "
- "Defaults to an empty string"],
- "anchorlink": [False,
- "True if header should be a self link - "
- "Defaults to False"],
- "anchorlink_class": ['toclink',
- 'CSS class(es) used for the link. '
- 'Defaults to "toclink"'],
- "permalink": [0,
- "True or link text if a Sphinx-style permalink should "
- "be added - Defaults to False"],
- "permalink_class": ['headerlink',
- 'CSS class(es) used for the link. '
- 'Defaults to "headerlink"'],
- "permalink_title": ["Permanent link",
- "Title attribute of the permalink - "
- "Defaults to 'Permanent link'"],
- "baselevel": ['1', 'Base level for headers.'],
- "slugify": [slugify,
- "Function to generate anchors based on header text - "
- "Defaults to the headerid ext's slugify function."],
- 'separator': ['-', 'Word separator. Defaults to "-".'],
- "toc_depth": [6,
- 'Define the range of section levels to include in'
- 'the Table of Contents. A single integer (b) defines'
- 'the bottom section level (