docker setup
This commit is contained in:
		
							
								
								
									
										62
									
								
								srcs/.venv/lib/python3.11/site-packages/django/utils/_os.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								srcs/.venv/lib/python3.11/site-packages/django/utils/_os.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,62 @@ | ||||
| import os | ||||
| import tempfile | ||||
| from os.path import abspath, dirname, join, normcase, sep | ||||
| from pathlib import Path | ||||
|  | ||||
| from django.core.exceptions import SuspiciousFileOperation | ||||
|  | ||||
|  | ||||
| def safe_join(base, *paths): | ||||
|     """ | ||||
|     Join one or more path components to the base path component intelligently. | ||||
|     Return a normalized, absolute version of the final path. | ||||
|  | ||||
|     Raise ValueError if the final path isn't located inside of the base path | ||||
|     component. | ||||
|     """ | ||||
|     final_path = abspath(join(base, *paths)) | ||||
|     base_path = abspath(base) | ||||
|     # Ensure final_path starts with base_path (using normcase to ensure we | ||||
|     # don't false-negative on case insensitive operating systems like Windows), | ||||
|     # further, one of the following conditions must be true: | ||||
|     #  a) The next character is the path separator (to prevent conditions like | ||||
|     #     safe_join("/dir", "/../d")) | ||||
|     #  b) The final path must be the same as the base path. | ||||
|     #  c) The base path must be the most root path (meaning either "/" or "C:\\") | ||||
|     if ( | ||||
|         not normcase(final_path).startswith(normcase(base_path + sep)) | ||||
|         and normcase(final_path) != normcase(base_path) | ||||
|         and dirname(normcase(base_path)) != normcase(base_path) | ||||
|     ): | ||||
|         raise SuspiciousFileOperation( | ||||
|             "The joined path ({}) is located outside of the base path " | ||||
|             "component ({})".format(final_path, base_path) | ||||
|         ) | ||||
|     return final_path | ||||
|  | ||||
|  | ||||
| def symlinks_supported(): | ||||
|     """ | ||||
|     Return whether or not creating symlinks are supported in the host platform | ||||
|     and/or if they are allowed to be created (e.g. on Windows it requires admin | ||||
|     permissions). | ||||
|     """ | ||||
|     with tempfile.TemporaryDirectory() as temp_dir: | ||||
|         original_path = os.path.join(temp_dir, "original") | ||||
|         symlink_path = os.path.join(temp_dir, "symlink") | ||||
|         os.makedirs(original_path) | ||||
|         try: | ||||
|             os.symlink(original_path, symlink_path) | ||||
|             supported = True | ||||
|         except (OSError, NotImplementedError): | ||||
|             supported = False | ||||
|         return supported | ||||
|  | ||||
|  | ||||
| def to_path(value): | ||||
|     """Convert value to a pathlib.Path instance, if not already a Path.""" | ||||
|     if isinstance(value, Path): | ||||
|         return value | ||||
|     elif not isinstance(value, str): | ||||
|         raise TypeError("Invalid path type: %s" % type(value).__name__) | ||||
|     return Path(value) | ||||
							
								
								
									
										257
									
								
								srcs/.venv/lib/python3.11/site-packages/django/utils/archive.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										257
									
								
								srcs/.venv/lib/python3.11/site-packages/django/utils/archive.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,257 @@ | ||||
| """ | ||||
| Based on "python-archive" -- https://pypi.org/project/python-archive/ | ||||
|  | ||||
| Copyright (c) 2010 Gary Wilson Jr. <gary.wilson@gmail.com> and contributors. | ||||
|  | ||||
| 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. | ||||
| """ | ||||
| import os | ||||
| import shutil | ||||
| import stat | ||||
| import tarfile | ||||
| import zipfile | ||||
|  | ||||
| from django.core.exceptions import SuspiciousOperation | ||||
|  | ||||
|  | ||||
| class ArchiveException(Exception): | ||||
|     """ | ||||
|     Base exception class for all archive errors. | ||||
|     """ | ||||
|  | ||||
|  | ||||
| class UnrecognizedArchiveFormat(ArchiveException): | ||||
|     """ | ||||
|     Error raised when passed file is not a recognized archive format. | ||||
|     """ | ||||
|  | ||||
|  | ||||
| def extract(path, to_path): | ||||
|     """ | ||||
|     Unpack the tar or zip file at the specified path to the directory | ||||
|     specified by to_path. | ||||
|     """ | ||||
|     with Archive(path) as archive: | ||||
|         archive.extract(to_path) | ||||
|  | ||||
|  | ||||
| class Archive: | ||||
|     """ | ||||
|     The external API class that encapsulates an archive implementation. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, file): | ||||
|         self._archive = self._archive_cls(file)(file) | ||||
|  | ||||
|     @staticmethod | ||||
|     def _archive_cls(file): | ||||
|         cls = None | ||||
|         if isinstance(file, str): | ||||
|             filename = file | ||||
|         else: | ||||
|             try: | ||||
|                 filename = file.name | ||||
|             except AttributeError: | ||||
|                 raise UnrecognizedArchiveFormat( | ||||
|                     "File object not a recognized archive format." | ||||
|                 ) | ||||
|         base, tail_ext = os.path.splitext(filename.lower()) | ||||
|         cls = extension_map.get(tail_ext) | ||||
|         if not cls: | ||||
|             base, ext = os.path.splitext(base) | ||||
|             cls = extension_map.get(ext) | ||||
|         if not cls: | ||||
|             raise UnrecognizedArchiveFormat( | ||||
|                 "Path not a recognized archive format: %s" % filename | ||||
|             ) | ||||
|         return cls | ||||
|  | ||||
|     def __enter__(self): | ||||
|         return self | ||||
|  | ||||
|     def __exit__(self, exc_type, exc_value, traceback): | ||||
|         self.close() | ||||
|  | ||||
|     def extract(self, to_path): | ||||
|         self._archive.extract(to_path) | ||||
|  | ||||
|     def list(self): | ||||
|         self._archive.list() | ||||
|  | ||||
|     def close(self): | ||||
|         self._archive.close() | ||||
|  | ||||
|  | ||||
| class BaseArchive: | ||||
|     """ | ||||
|     Base Archive class.  Implementations should inherit this class. | ||||
|     """ | ||||
|  | ||||
|     @staticmethod | ||||
|     def _copy_permissions(mode, filename): | ||||
|         """ | ||||
|         If the file in the archive has some permissions (this assumes a file | ||||
|         won't be writable/executable without being readable), apply those | ||||
|         permissions to the unarchived file. | ||||
|         """ | ||||
|         if mode & stat.S_IROTH: | ||||
|             os.chmod(filename, mode) | ||||
|  | ||||
|     def split_leading_dir(self, path): | ||||
|         path = str(path) | ||||
|         path = path.lstrip("/").lstrip("\\") | ||||
|         if "/" in path and ( | ||||
|             ("\\" in path and path.find("/") < path.find("\\")) or "\\" not in path | ||||
|         ): | ||||
|             return path.split("/", 1) | ||||
|         elif "\\" in path: | ||||
|             return path.split("\\", 1) | ||||
|         else: | ||||
|             return path, "" | ||||
|  | ||||
|     def has_leading_dir(self, paths): | ||||
|         """ | ||||
|         Return True if all the paths have the same leading path name | ||||
|         (i.e., everything is in one subdirectory in an archive). | ||||
|         """ | ||||
|         common_prefix = None | ||||
|         for path in paths: | ||||
|             prefix, rest = self.split_leading_dir(path) | ||||
|             if not prefix: | ||||
|                 return False | ||||
|             elif common_prefix is None: | ||||
|                 common_prefix = prefix | ||||
|             elif prefix != common_prefix: | ||||
|                 return False | ||||
|         return True | ||||
|  | ||||
|     def target_filename(self, to_path, name): | ||||
|         target_path = os.path.abspath(to_path) | ||||
|         filename = os.path.abspath(os.path.join(target_path, name)) | ||||
|         if not filename.startswith(target_path): | ||||
|             raise SuspiciousOperation("Archive contains invalid path: '%s'" % name) | ||||
|         return filename | ||||
|  | ||||
|     def extract(self): | ||||
|         raise NotImplementedError( | ||||
|             "subclasses of BaseArchive must provide an extract() method" | ||||
|         ) | ||||
|  | ||||
|     def list(self): | ||||
|         raise NotImplementedError( | ||||
|             "subclasses of BaseArchive must provide a list() method" | ||||
|         ) | ||||
|  | ||||
|  | ||||
| class TarArchive(BaseArchive): | ||||
|     def __init__(self, file): | ||||
|         self._archive = tarfile.open(file) | ||||
|  | ||||
|     def list(self, *args, **kwargs): | ||||
|         self._archive.list(*args, **kwargs) | ||||
|  | ||||
|     def extract(self, to_path): | ||||
|         members = self._archive.getmembers() | ||||
|         leading = self.has_leading_dir(x.name for x in members) | ||||
|         for member in members: | ||||
|             name = member.name | ||||
|             if leading: | ||||
|                 name = self.split_leading_dir(name)[1] | ||||
|             filename = self.target_filename(to_path, name) | ||||
|             if member.isdir(): | ||||
|                 if filename: | ||||
|                     os.makedirs(filename, exist_ok=True) | ||||
|             else: | ||||
|                 try: | ||||
|                     extracted = self._archive.extractfile(member) | ||||
|                 except (KeyError, AttributeError) as exc: | ||||
|                     # Some corrupt tar files seem to produce this | ||||
|                     # (specifically bad symlinks) | ||||
|                     print( | ||||
|                         "In the tar file %s the member %s is invalid: %s" | ||||
|                         % (name, member.name, exc) | ||||
|                     ) | ||||
|                 else: | ||||
|                     dirname = os.path.dirname(filename) | ||||
|                     if dirname: | ||||
|                         os.makedirs(dirname, exist_ok=True) | ||||
|                     with open(filename, "wb") as outfile: | ||||
|                         shutil.copyfileobj(extracted, outfile) | ||||
|                         self._copy_permissions(member.mode, filename) | ||||
|                 finally: | ||||
|                     if extracted: | ||||
|                         extracted.close() | ||||
|  | ||||
|     def close(self): | ||||
|         self._archive.close() | ||||
|  | ||||
|  | ||||
| class ZipArchive(BaseArchive): | ||||
|     def __init__(self, file): | ||||
|         self._archive = zipfile.ZipFile(file) | ||||
|  | ||||
|     def list(self, *args, **kwargs): | ||||
|         self._archive.printdir(*args, **kwargs) | ||||
|  | ||||
|     def extract(self, to_path): | ||||
|         namelist = self._archive.namelist() | ||||
|         leading = self.has_leading_dir(namelist) | ||||
|         for name in namelist: | ||||
|             data = self._archive.read(name) | ||||
|             info = self._archive.getinfo(name) | ||||
|             if leading: | ||||
|                 name = self.split_leading_dir(name)[1] | ||||
|             if not name: | ||||
|                 continue | ||||
|             filename = self.target_filename(to_path, name) | ||||
|             if name.endswith(("/", "\\")): | ||||
|                 # A directory | ||||
|                 os.makedirs(filename, exist_ok=True) | ||||
|             else: | ||||
|                 dirname = os.path.dirname(filename) | ||||
|                 if dirname: | ||||
|                     os.makedirs(dirname, exist_ok=True) | ||||
|                 with open(filename, "wb") as outfile: | ||||
|                     outfile.write(data) | ||||
|                 # Convert ZipInfo.external_attr to mode | ||||
|                 mode = info.external_attr >> 16 | ||||
|                 self._copy_permissions(mode, filename) | ||||
|  | ||||
|     def close(self): | ||||
|         self._archive.close() | ||||
|  | ||||
|  | ||||
| extension_map = dict.fromkeys( | ||||
|     ( | ||||
|         ".tar", | ||||
|         ".tar.bz2", | ||||
|         ".tbz2", | ||||
|         ".tbz", | ||||
|         ".tz2", | ||||
|         ".tar.gz", | ||||
|         ".tgz", | ||||
|         ".taz", | ||||
|         ".tar.lzma", | ||||
|         ".tlz", | ||||
|         ".tar.xz", | ||||
|         ".txz", | ||||
|     ), | ||||
|     TarArchive, | ||||
| ) | ||||
| extension_map[".zip"] = ZipArchive | ||||
| @ -0,0 +1,64 @@ | ||||
| import os | ||||
| from asyncio import get_running_loop | ||||
| from functools import wraps | ||||
|  | ||||
| from django.core.exceptions import SynchronousOnlyOperation | ||||
|  | ||||
|  | ||||
| def async_unsafe(message): | ||||
|     """ | ||||
|     Decorator to mark functions as async-unsafe. Someone trying to access | ||||
|     the function while in an async context will get an error message. | ||||
|     """ | ||||
|  | ||||
|     def decorator(func): | ||||
|         @wraps(func) | ||||
|         def inner(*args, **kwargs): | ||||
|             # Detect a running event loop in this thread. | ||||
|             try: | ||||
|                 get_running_loop() | ||||
|             except RuntimeError: | ||||
|                 pass | ||||
|             else: | ||||
|                 if not os.environ.get("DJANGO_ALLOW_ASYNC_UNSAFE"): | ||||
|                     raise SynchronousOnlyOperation(message) | ||||
|             # Pass onward. | ||||
|             return func(*args, **kwargs) | ||||
|  | ||||
|         return inner | ||||
|  | ||||
|     # If the message is actually a function, then be a no-arguments decorator. | ||||
|     if callable(message): | ||||
|         func = message | ||||
|         message = ( | ||||
|             "You cannot call this from an async context - use a thread or " | ||||
|             "sync_to_async." | ||||
|         ) | ||||
|         return decorator(func) | ||||
|     else: | ||||
|         return decorator | ||||
|  | ||||
|  | ||||
| try: | ||||
|     from contextlib import aclosing | ||||
| except ImportError: | ||||
|     # TODO: Remove when dropping support for PY39. | ||||
|     from contextlib import AbstractAsyncContextManager | ||||
|  | ||||
|     # Backport of contextlib.aclosing() from Python 3.10. Copyright (C) Python | ||||
|     # Software Foundation (see LICENSE.python). | ||||
|     class aclosing(AbstractAsyncContextManager): | ||||
|         """ | ||||
|         Async context manager for safely finalizing an asynchronously | ||||
|         cleaned-up resource such as an async generator, calling its | ||||
|         ``aclose()`` method. | ||||
|         """ | ||||
|  | ||||
|         def __init__(self, thing): | ||||
|             self.thing = thing | ||||
|  | ||||
|         async def __aenter__(self): | ||||
|             return self.thing | ||||
|  | ||||
|         async def __aexit__(self, *exc_info): | ||||
|             await self.thing.aclose() | ||||
| @ -0,0 +1,676 @@ | ||||
| import itertools | ||||
| import logging | ||||
| import os | ||||
| import signal | ||||
| import subprocess | ||||
| import sys | ||||
| import threading | ||||
| import time | ||||
| import traceback | ||||
| import weakref | ||||
| from collections import defaultdict | ||||
| from functools import lru_cache, wraps | ||||
| from pathlib import Path | ||||
| from types import ModuleType | ||||
| from zipimport import zipimporter | ||||
|  | ||||
| import django | ||||
| from django.apps import apps | ||||
| from django.core.signals import request_finished | ||||
| from django.dispatch import Signal | ||||
| from django.utils.functional import cached_property | ||||
| from django.utils.version import get_version_tuple | ||||
|  | ||||
| autoreload_started = Signal() | ||||
| file_changed = Signal() | ||||
|  | ||||
| DJANGO_AUTORELOAD_ENV = "RUN_MAIN" | ||||
|  | ||||
| logger = logging.getLogger("django.utils.autoreload") | ||||
|  | ||||
| # If an error is raised while importing a file, it's not placed in sys.modules. | ||||
| # This means that any future modifications aren't caught. Keep a list of these | ||||
| # file paths to allow watching them in the future. | ||||
| _error_files = [] | ||||
| _exception = None | ||||
|  | ||||
| try: | ||||
|     import termios | ||||
| except ImportError: | ||||
|     termios = None | ||||
|  | ||||
|  | ||||
| try: | ||||
|     import pywatchman | ||||
| except ImportError: | ||||
|     pywatchman = None | ||||
|  | ||||
|  | ||||
| def is_django_module(module): | ||||
|     """Return True if the given module is nested under Django.""" | ||||
|     return module.__name__.startswith("django.") | ||||
|  | ||||
|  | ||||
| def is_django_path(path): | ||||
|     """Return True if the given file path is nested under Django.""" | ||||
|     return Path(django.__file__).parent in Path(path).parents | ||||
|  | ||||
|  | ||||
| def check_errors(fn): | ||||
|     @wraps(fn) | ||||
|     def wrapper(*args, **kwargs): | ||||
|         global _exception | ||||
|         try: | ||||
|             fn(*args, **kwargs) | ||||
|         except Exception: | ||||
|             _exception = sys.exc_info() | ||||
|  | ||||
|             et, ev, tb = _exception | ||||
|  | ||||
|             if getattr(ev, "filename", None) is None: | ||||
|                 # get the filename from the last item in the stack | ||||
|                 filename = traceback.extract_tb(tb)[-1][0] | ||||
|             else: | ||||
|                 filename = ev.filename | ||||
|  | ||||
|             if filename not in _error_files: | ||||
|                 _error_files.append(filename) | ||||
|  | ||||
|             raise | ||||
|  | ||||
|     return wrapper | ||||
|  | ||||
|  | ||||
| def raise_last_exception(): | ||||
|     global _exception | ||||
|     if _exception is not None: | ||||
|         raise _exception[1] | ||||
|  | ||||
|  | ||||
| def ensure_echo_on(): | ||||
|     """ | ||||
|     Ensure that echo mode is enabled. Some tools such as PDB disable | ||||
|     it which causes usability issues after reload. | ||||
|     """ | ||||
|     if not termios or not sys.stdin.isatty(): | ||||
|         return | ||||
|     attr_list = termios.tcgetattr(sys.stdin) | ||||
|     if not attr_list[3] & termios.ECHO: | ||||
|         attr_list[3] |= termios.ECHO | ||||
|         if hasattr(signal, "SIGTTOU"): | ||||
|             old_handler = signal.signal(signal.SIGTTOU, signal.SIG_IGN) | ||||
|         else: | ||||
|             old_handler = None | ||||
|         termios.tcsetattr(sys.stdin, termios.TCSANOW, attr_list) | ||||
|         if old_handler is not None: | ||||
|             signal.signal(signal.SIGTTOU, old_handler) | ||||
|  | ||||
|  | ||||
| def iter_all_python_module_files(): | ||||
|     # This is a hot path during reloading. Create a stable sorted list of | ||||
|     # modules based on the module name and pass it to iter_modules_and_files(). | ||||
|     # This ensures cached results are returned in the usual case that modules | ||||
|     # aren't loaded on the fly. | ||||
|     keys = sorted(sys.modules) | ||||
|     modules = tuple( | ||||
|         m | ||||
|         for m in map(sys.modules.__getitem__, keys) | ||||
|         if not isinstance(m, weakref.ProxyTypes) | ||||
|     ) | ||||
|     return iter_modules_and_files(modules, frozenset(_error_files)) | ||||
|  | ||||
|  | ||||
| @lru_cache(maxsize=1) | ||||
| def iter_modules_and_files(modules, extra_files): | ||||
|     """Iterate through all modules needed to be watched.""" | ||||
|     sys_file_paths = [] | ||||
|     for module in modules: | ||||
|         # During debugging (with PyDev) the 'typing.io' and 'typing.re' objects | ||||
|         # are added to sys.modules, however they are types not modules and so | ||||
|         # cause issues here. | ||||
|         if not isinstance(module, ModuleType): | ||||
|             continue | ||||
|         if module.__name__ in ("__main__", "__mp_main__"): | ||||
|             # __main__ (usually manage.py) doesn't always have a __spec__ set. | ||||
|             # Handle this by falling back to using __file__, resolved below. | ||||
|             # See https://docs.python.org/reference/import.html#main-spec | ||||
|             # __file__ may not exists, e.g. when running ipdb debugger. | ||||
|             if hasattr(module, "__file__"): | ||||
|                 sys_file_paths.append(module.__file__) | ||||
|             continue | ||||
|         if getattr(module, "__spec__", None) is None: | ||||
|             continue | ||||
|         spec = module.__spec__ | ||||
|         # Modules could be loaded from places without a concrete location. If | ||||
|         # this is the case, skip them. | ||||
|         if spec.has_location: | ||||
|             origin = ( | ||||
|                 spec.loader.archive | ||||
|                 if isinstance(spec.loader, zipimporter) | ||||
|                 else spec.origin | ||||
|             ) | ||||
|             sys_file_paths.append(origin) | ||||
|  | ||||
|     results = set() | ||||
|     for filename in itertools.chain(sys_file_paths, extra_files): | ||||
|         if not filename: | ||||
|             continue | ||||
|         path = Path(filename) | ||||
|         try: | ||||
|             if not path.exists(): | ||||
|                 # The module could have been removed, don't fail loudly if this | ||||
|                 # is the case. | ||||
|                 continue | ||||
|         except ValueError as e: | ||||
|             # Network filesystems may return null bytes in file paths. | ||||
|             logger.debug('"%s" raised when resolving path: "%s"', e, path) | ||||
|             continue | ||||
|         resolved_path = path.resolve().absolute() | ||||
|         results.add(resolved_path) | ||||
|     return frozenset(results) | ||||
|  | ||||
|  | ||||
| @lru_cache(maxsize=1) | ||||
| def common_roots(paths): | ||||
|     """ | ||||
|     Return a tuple of common roots that are shared between the given paths. | ||||
|     File system watchers operate on directories and aren't cheap to create. | ||||
|     Try to find the minimum set of directories to watch that encompass all of | ||||
|     the files that need to be watched. | ||||
|     """ | ||||
|     # Inspired from Werkzeug: | ||||
|     # https://github.com/pallets/werkzeug/blob/7477be2853df70a022d9613e765581b9411c3c39/werkzeug/_reloader.py | ||||
|     # Create a sorted list of the path components, longest first. | ||||
|     path_parts = sorted([x.parts for x in paths], key=len, reverse=True) | ||||
|     tree = {} | ||||
|     for chunks in path_parts: | ||||
|         node = tree | ||||
|         # Add each part of the path to the tree. | ||||
|         for chunk in chunks: | ||||
|             node = node.setdefault(chunk, {}) | ||||
|         # Clear the last leaf in the tree. | ||||
|         node.clear() | ||||
|  | ||||
|     # Turn the tree into a list of Path instances. | ||||
|     def _walk(node, path): | ||||
|         for prefix, child in node.items(): | ||||
|             yield from _walk(child, path + (prefix,)) | ||||
|         if not node: | ||||
|             yield Path(*path) | ||||
|  | ||||
|     return tuple(_walk(tree, ())) | ||||
|  | ||||
|  | ||||
| def sys_path_directories(): | ||||
|     """ | ||||
|     Yield absolute directories from sys.path, ignoring entries that don't | ||||
|     exist. | ||||
|     """ | ||||
|     for path in sys.path: | ||||
|         path = Path(path) | ||||
|         if not path.exists(): | ||||
|             continue | ||||
|         resolved_path = path.resolve().absolute() | ||||
|         # If the path is a file (like a zip file), watch the parent directory. | ||||
|         if resolved_path.is_file(): | ||||
|             yield resolved_path.parent | ||||
|         else: | ||||
|             yield resolved_path | ||||
|  | ||||
|  | ||||
| def get_child_arguments(): | ||||
|     """ | ||||
|     Return the executable. This contains a workaround for Windows if the | ||||
|     executable is reported to not have the .exe extension which can cause bugs | ||||
|     on reloading. | ||||
|     """ | ||||
|     import __main__ | ||||
|  | ||||
|     py_script = Path(sys.argv[0]) | ||||
|  | ||||
|     args = [sys.executable] + ["-W%s" % o for o in sys.warnoptions] | ||||
|     if sys.implementation.name == "cpython": | ||||
|         args.extend( | ||||
|             f"-X{key}" if value is True else f"-X{key}={value}" | ||||
|             for key, value in sys._xoptions.items() | ||||
|         ) | ||||
|     # __spec__ is set when the server was started with the `-m` option, | ||||
|     # see https://docs.python.org/3/reference/import.html#main-spec | ||||
|     # __spec__ may not exist, e.g. when running in a Conda env. | ||||
|     if getattr(__main__, "__spec__", None) is not None: | ||||
|         spec = __main__.__spec__ | ||||
|         if (spec.name == "__main__" or spec.name.endswith(".__main__")) and spec.parent: | ||||
|             name = spec.parent | ||||
|         else: | ||||
|             name = spec.name | ||||
|         args += ["-m", name] | ||||
|         args += sys.argv[1:] | ||||
|     elif not py_script.exists(): | ||||
|         # sys.argv[0] may not exist for several reasons on Windows. | ||||
|         # It may exist with a .exe extension or have a -script.py suffix. | ||||
|         exe_entrypoint = py_script.with_suffix(".exe") | ||||
|         if exe_entrypoint.exists(): | ||||
|             # Should be executed directly, ignoring sys.executable. | ||||
|             return [exe_entrypoint, *sys.argv[1:]] | ||||
|         script_entrypoint = py_script.with_name("%s-script.py" % py_script.name) | ||||
|         if script_entrypoint.exists(): | ||||
|             # Should be executed as usual. | ||||
|             return [*args, script_entrypoint, *sys.argv[1:]] | ||||
|         raise RuntimeError("Script %s does not exist." % py_script) | ||||
|     else: | ||||
|         args += sys.argv | ||||
|     return args | ||||
|  | ||||
|  | ||||
| def trigger_reload(filename): | ||||
|     logger.info("%s changed, reloading.", filename) | ||||
|     sys.exit(3) | ||||
|  | ||||
|  | ||||
| def restart_with_reloader(): | ||||
|     new_environ = {**os.environ, DJANGO_AUTORELOAD_ENV: "true"} | ||||
|     args = get_child_arguments() | ||||
|     while True: | ||||
|         p = subprocess.run(args, env=new_environ, close_fds=False) | ||||
|         if p.returncode != 3: | ||||
|             return p.returncode | ||||
|  | ||||
|  | ||||
| class BaseReloader: | ||||
|     def __init__(self): | ||||
|         self.extra_files = set() | ||||
|         self.directory_globs = defaultdict(set) | ||||
|         self._stop_condition = threading.Event() | ||||
|  | ||||
|     def watch_dir(self, path, glob): | ||||
|         path = Path(path) | ||||
|         try: | ||||
|             path = path.absolute() | ||||
|         except FileNotFoundError: | ||||
|             logger.debug( | ||||
|                 "Unable to watch directory %s as it cannot be resolved.", | ||||
|                 path, | ||||
|                 exc_info=True, | ||||
|             ) | ||||
|             return | ||||
|         logger.debug("Watching dir %s with glob %s.", path, glob) | ||||
|         self.directory_globs[path].add(glob) | ||||
|  | ||||
|     def watched_files(self, include_globs=True): | ||||
|         """ | ||||
|         Yield all files that need to be watched, including module files and | ||||
|         files within globs. | ||||
|         """ | ||||
|         yield from iter_all_python_module_files() | ||||
|         yield from self.extra_files | ||||
|         if include_globs: | ||||
|             for directory, patterns in self.directory_globs.items(): | ||||
|                 for pattern in patterns: | ||||
|                     yield from directory.glob(pattern) | ||||
|  | ||||
|     def wait_for_apps_ready(self, app_reg, django_main_thread): | ||||
|         """ | ||||
|         Wait until Django reports that the apps have been loaded. If the given | ||||
|         thread has terminated before the apps are ready, then a SyntaxError or | ||||
|         other non-recoverable error has been raised. In that case, stop waiting | ||||
|         for the apps_ready event and continue processing. | ||||
|  | ||||
|         Return True if the thread is alive and the ready event has been | ||||
|         triggered, or False if the thread is terminated while waiting for the | ||||
|         event. | ||||
|         """ | ||||
|         while django_main_thread.is_alive(): | ||||
|             if app_reg.ready_event.wait(timeout=0.1): | ||||
|                 return True | ||||
|         else: | ||||
|             logger.debug("Main Django thread has terminated before apps are ready.") | ||||
|             return False | ||||
|  | ||||
|     def run(self, django_main_thread): | ||||
|         logger.debug("Waiting for apps ready_event.") | ||||
|         self.wait_for_apps_ready(apps, django_main_thread) | ||||
|         from django.urls import get_resolver | ||||
|  | ||||
|         # Prevent a race condition where URL modules aren't loaded when the | ||||
|         # reloader starts by accessing the urlconf_module property. | ||||
|         try: | ||||
|             get_resolver().urlconf_module | ||||
|         except Exception: | ||||
|             # Loading the urlconf can result in errors during development. | ||||
|             # If this occurs then swallow the error and continue. | ||||
|             pass | ||||
|         logger.debug("Apps ready_event triggered. Sending autoreload_started signal.") | ||||
|         autoreload_started.send(sender=self) | ||||
|         self.run_loop() | ||||
|  | ||||
|     def run_loop(self): | ||||
|         ticker = self.tick() | ||||
|         while not self.should_stop: | ||||
|             try: | ||||
|                 next(ticker) | ||||
|             except StopIteration: | ||||
|                 break | ||||
|         self.stop() | ||||
|  | ||||
|     def tick(self): | ||||
|         """ | ||||
|         This generator is called in a loop from run_loop. It's important that | ||||
|         the method takes care of pausing or otherwise waiting for a period of | ||||
|         time. This split between run_loop() and tick() is to improve the | ||||
|         testability of the reloader implementations by decoupling the work they | ||||
|         do from the loop. | ||||
|         """ | ||||
|         raise NotImplementedError("subclasses must implement tick().") | ||||
|  | ||||
|     @classmethod | ||||
|     def check_availability(cls): | ||||
|         raise NotImplementedError("subclasses must implement check_availability().") | ||||
|  | ||||
|     def notify_file_changed(self, path): | ||||
|         results = file_changed.send(sender=self, file_path=path) | ||||
|         logger.debug("%s notified as changed. Signal results: %s.", path, results) | ||||
|         if not any(res[1] for res in results): | ||||
|             trigger_reload(path) | ||||
|  | ||||
|     # These are primarily used for testing. | ||||
|     @property | ||||
|     def should_stop(self): | ||||
|         return self._stop_condition.is_set() | ||||
|  | ||||
|     def stop(self): | ||||
|         self._stop_condition.set() | ||||
|  | ||||
|  | ||||
| class StatReloader(BaseReloader): | ||||
|     SLEEP_TIME = 1  # Check for changes once per second. | ||||
|  | ||||
|     def tick(self): | ||||
|         mtimes = {} | ||||
|         while True: | ||||
|             for filepath, mtime in self.snapshot_files(): | ||||
|                 old_time = mtimes.get(filepath) | ||||
|                 mtimes[filepath] = mtime | ||||
|                 if old_time is None: | ||||
|                     logger.debug("File %s first seen with mtime %s", filepath, mtime) | ||||
|                     continue | ||||
|                 elif mtime > old_time: | ||||
|                     logger.debug( | ||||
|                         "File %s previous mtime: %s, current mtime: %s", | ||||
|                         filepath, | ||||
|                         old_time, | ||||
|                         mtime, | ||||
|                     ) | ||||
|                     self.notify_file_changed(filepath) | ||||
|  | ||||
|             time.sleep(self.SLEEP_TIME) | ||||
|             yield | ||||
|  | ||||
|     def snapshot_files(self): | ||||
|         # watched_files may produce duplicate paths if globs overlap. | ||||
|         seen_files = set() | ||||
|         for file in self.watched_files(): | ||||
|             if file in seen_files: | ||||
|                 continue | ||||
|             try: | ||||
|                 mtime = file.stat().st_mtime | ||||
|             except OSError: | ||||
|                 # This is thrown when the file does not exist. | ||||
|                 continue | ||||
|             seen_files.add(file) | ||||
|             yield file, mtime | ||||
|  | ||||
|     @classmethod | ||||
|     def check_availability(cls): | ||||
|         return True | ||||
|  | ||||
|  | ||||
| class WatchmanUnavailable(RuntimeError): | ||||
|     pass | ||||
|  | ||||
|  | ||||
| class WatchmanReloader(BaseReloader): | ||||
|     def __init__(self): | ||||
|         self.roots = defaultdict(set) | ||||
|         self.processed_request = threading.Event() | ||||
|         self.client_timeout = int(os.environ.get("DJANGO_WATCHMAN_TIMEOUT", 5)) | ||||
|         super().__init__() | ||||
|  | ||||
|     @cached_property | ||||
|     def client(self): | ||||
|         return pywatchman.client(timeout=self.client_timeout) | ||||
|  | ||||
|     def _watch_root(self, root): | ||||
|         # In practice this shouldn't occur, however, it's possible that a | ||||
|         # directory that doesn't exist yet is being watched. If it's outside of | ||||
|         # sys.path then this will end up a new root. How to handle this isn't | ||||
|         # clear: Not adding the root will likely break when subscribing to the | ||||
|         # changes, however, as this is currently an internal API,  no files | ||||
|         # will be being watched outside of sys.path. Fixing this by checking | ||||
|         # inside watch_glob() and watch_dir() is expensive, instead this could | ||||
|         # could fall back to the StatReloader if this case is detected? For | ||||
|         # now, watching its parent, if possible, is sufficient. | ||||
|         if not root.exists(): | ||||
|             if not root.parent.exists(): | ||||
|                 logger.warning( | ||||
|                     "Unable to watch root dir %s as neither it or its parent exist.", | ||||
|                     root, | ||||
|                 ) | ||||
|                 return | ||||
|             root = root.parent | ||||
|         result = self.client.query("watch-project", str(root.absolute())) | ||||
|         if "warning" in result: | ||||
|             logger.warning("Watchman warning: %s", result["warning"]) | ||||
|         logger.debug("Watchman watch-project result: %s", result) | ||||
|         return result["watch"], result.get("relative_path") | ||||
|  | ||||
|     @lru_cache | ||||
|     def _get_clock(self, root): | ||||
|         return self.client.query("clock", root)["clock"] | ||||
|  | ||||
|     def _subscribe(self, directory, name, expression): | ||||
|         root, rel_path = self._watch_root(directory) | ||||
|         # Only receive notifications of files changing, filtering out other types | ||||
|         # like special files: https://facebook.github.io/watchman/docs/type | ||||
|         only_files_expression = [ | ||||
|             "allof", | ||||
|             ["anyof", ["type", "f"], ["type", "l"]], | ||||
|             expression, | ||||
|         ] | ||||
|         query = { | ||||
|             "expression": only_files_expression, | ||||
|             "fields": ["name"], | ||||
|             "since": self._get_clock(root), | ||||
|             "dedup_results": True, | ||||
|         } | ||||
|         if rel_path: | ||||
|             query["relative_root"] = rel_path | ||||
|         logger.debug( | ||||
|             "Issuing watchman subscription %s, for root %s. Query: %s", | ||||
|             name, | ||||
|             root, | ||||
|             query, | ||||
|         ) | ||||
|         self.client.query("subscribe", root, name, query) | ||||
|  | ||||
|     def _subscribe_dir(self, directory, filenames): | ||||
|         if not directory.exists(): | ||||
|             if not directory.parent.exists(): | ||||
|                 logger.warning( | ||||
|                     "Unable to watch directory %s as neither it or its parent exist.", | ||||
|                     directory, | ||||
|                 ) | ||||
|                 return | ||||
|             prefix = "files-parent-%s" % directory.name | ||||
|             filenames = ["%s/%s" % (directory.name, filename) for filename in filenames] | ||||
|             directory = directory.parent | ||||
|             expression = ["name", filenames, "wholename"] | ||||
|         else: | ||||
|             prefix = "files" | ||||
|             expression = ["name", filenames] | ||||
|         self._subscribe(directory, "%s:%s" % (prefix, directory), expression) | ||||
|  | ||||
|     def _watch_glob(self, directory, patterns): | ||||
|         """ | ||||
|         Watch a directory with a specific glob. If the directory doesn't yet | ||||
|         exist, attempt to watch the parent directory and amend the patterns to | ||||
|         include this. It's important this method isn't called more than one per | ||||
|         directory when updating all subscriptions. Subsequent calls will | ||||
|         overwrite the named subscription, so it must include all possible glob | ||||
|         expressions. | ||||
|         """ | ||||
|         prefix = "glob" | ||||
|         if not directory.exists(): | ||||
|             if not directory.parent.exists(): | ||||
|                 logger.warning( | ||||
|                     "Unable to watch directory %s as neither it or its parent exist.", | ||||
|                     directory, | ||||
|                 ) | ||||
|                 return | ||||
|             prefix = "glob-parent-%s" % directory.name | ||||
|             patterns = ["%s/%s" % (directory.name, pattern) for pattern in patterns] | ||||
|             directory = directory.parent | ||||
|  | ||||
|         expression = ["anyof"] | ||||
|         for pattern in patterns: | ||||
|             expression.append(["match", pattern, "wholename"]) | ||||
|         self._subscribe(directory, "%s:%s" % (prefix, directory), expression) | ||||
|  | ||||
|     def watched_roots(self, watched_files): | ||||
|         extra_directories = self.directory_globs.keys() | ||||
|         watched_file_dirs = [f.parent for f in watched_files] | ||||
|         sys_paths = list(sys_path_directories()) | ||||
|         return frozenset((*extra_directories, *watched_file_dirs, *sys_paths)) | ||||
|  | ||||
|     def _update_watches(self): | ||||
|         watched_files = list(self.watched_files(include_globs=False)) | ||||
|         found_roots = common_roots(self.watched_roots(watched_files)) | ||||
|         logger.debug("Watching %s files", len(watched_files)) | ||||
|         logger.debug("Found common roots: %s", found_roots) | ||||
|         # Setup initial roots for performance, shortest roots first. | ||||
|         for root in sorted(found_roots): | ||||
|             self._watch_root(root) | ||||
|         for directory, patterns in self.directory_globs.items(): | ||||
|             self._watch_glob(directory, patterns) | ||||
|         # Group sorted watched_files by their parent directory. | ||||
|         sorted_files = sorted(watched_files, key=lambda p: p.parent) | ||||
|         for directory, group in itertools.groupby(sorted_files, key=lambda p: p.parent): | ||||
|             # These paths need to be relative to the parent directory. | ||||
|             self._subscribe_dir( | ||||
|                 directory, [str(p.relative_to(directory)) for p in group] | ||||
|             ) | ||||
|  | ||||
|     def update_watches(self): | ||||
|         try: | ||||
|             self._update_watches() | ||||
|         except Exception as ex: | ||||
|             # If the service is still available, raise the original exception. | ||||
|             if self.check_server_status(ex): | ||||
|                 raise | ||||
|  | ||||
|     def _check_subscription(self, sub): | ||||
|         subscription = self.client.getSubscription(sub) | ||||
|         if not subscription: | ||||
|             return | ||||
|         logger.debug("Watchman subscription %s has results.", sub) | ||||
|         for result in subscription: | ||||
|             # When using watch-project, it's not simple to get the relative | ||||
|             # directory without storing some specific state. Store the full | ||||
|             # path to the directory in the subscription name, prefixed by its | ||||
|             # type (glob, files). | ||||
|             root_directory = Path(result["subscription"].split(":", 1)[1]) | ||||
|             logger.debug("Found root directory %s", root_directory) | ||||
|             for file in result.get("files", []): | ||||
|                 self.notify_file_changed(root_directory / file) | ||||
|  | ||||
|     def request_processed(self, **kwargs): | ||||
|         logger.debug("Request processed. Setting update_watches event.") | ||||
|         self.processed_request.set() | ||||
|  | ||||
|     def tick(self): | ||||
|         request_finished.connect(self.request_processed) | ||||
|         self.update_watches() | ||||
|         while True: | ||||
|             if self.processed_request.is_set(): | ||||
|                 self.update_watches() | ||||
|                 self.processed_request.clear() | ||||
|             try: | ||||
|                 self.client.receive() | ||||
|             except pywatchman.SocketTimeout: | ||||
|                 pass | ||||
|             except pywatchman.WatchmanError as ex: | ||||
|                 logger.debug("Watchman error: %s, checking server status.", ex) | ||||
|                 self.check_server_status(ex) | ||||
|             else: | ||||
|                 for sub in list(self.client.subs.keys()): | ||||
|                     self._check_subscription(sub) | ||||
|             yield | ||||
|             # Protect against busy loops. | ||||
|             time.sleep(0.1) | ||||
|  | ||||
|     def stop(self): | ||||
|         self.client.close() | ||||
|         super().stop() | ||||
|  | ||||
|     def check_server_status(self, inner_ex=None): | ||||
|         """Return True if the server is available.""" | ||||
|         try: | ||||
|             self.client.query("version") | ||||
|         except Exception: | ||||
|             raise WatchmanUnavailable(str(inner_ex)) from inner_ex | ||||
|         return True | ||||
|  | ||||
|     @classmethod | ||||
|     def check_availability(cls): | ||||
|         if not pywatchman: | ||||
|             raise WatchmanUnavailable("pywatchman not installed.") | ||||
|         client = pywatchman.client(timeout=0.1) | ||||
|         try: | ||||
|             result = client.capabilityCheck() | ||||
|         except Exception: | ||||
|             # The service is down? | ||||
|             raise WatchmanUnavailable("Cannot connect to the watchman service.") | ||||
|         version = get_version_tuple(result["version"]) | ||||
|         # Watchman 4.9 includes multiple improvements to watching project | ||||
|         # directories as well as case insensitive filesystems. | ||||
|         logger.debug("Watchman version %s", version) | ||||
|         if version < (4, 9): | ||||
|             raise WatchmanUnavailable("Watchman 4.9 or later is required.") | ||||
|  | ||||
|  | ||||
| def get_reloader(): | ||||
|     """Return the most suitable reloader for this environment.""" | ||||
|     try: | ||||
|         WatchmanReloader.check_availability() | ||||
|     except WatchmanUnavailable: | ||||
|         return StatReloader() | ||||
|     return WatchmanReloader() | ||||
|  | ||||
|  | ||||
| def start_django(reloader, main_func, *args, **kwargs): | ||||
|     ensure_echo_on() | ||||
|  | ||||
|     main_func = check_errors(main_func) | ||||
|     django_main_thread = threading.Thread( | ||||
|         target=main_func, args=args, kwargs=kwargs, name="django-main-thread" | ||||
|     ) | ||||
|     django_main_thread.daemon = True | ||||
|     django_main_thread.start() | ||||
|  | ||||
|     while not reloader.should_stop: | ||||
|         reloader.run(django_main_thread) | ||||
|  | ||||
|  | ||||
| def run_with_reloader(main_func, *args, **kwargs): | ||||
|     signal.signal(signal.SIGTERM, lambda *args: sys.exit(0)) | ||||
|     try: | ||||
|         if os.environ.get(DJANGO_AUTORELOAD_ENV) == "true": | ||||
|             reloader = get_reloader() | ||||
|             logger.info( | ||||
|                 "Watching for file changes with %s", reloader.__class__.__name__ | ||||
|             ) | ||||
|             start_django(reloader, main_func, *args, **kwargs) | ||||
|         else: | ||||
|             exit_code = restart_with_reloader() | ||||
|             sys.exit(exit_code) | ||||
|     except KeyboardInterrupt: | ||||
|         pass | ||||
							
								
								
									
										115
									
								
								srcs/.venv/lib/python3.11/site-packages/django/utils/baseconv.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								srcs/.venv/lib/python3.11/site-packages/django/utils/baseconv.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,115 @@ | ||||
| # RemovedInDjango50Warning | ||||
| # Copyright (c) 2010 Guilherme Gondim. All rights reserved. | ||||
| # Copyright (c) 2009 Simon Willison. All rights reserved. | ||||
| # Copyright (c) 2002 Drew Perttula. All rights reserved. | ||||
| # | ||||
| # License: | ||||
| #   Python Software Foundation License version 2 | ||||
| # | ||||
| # See the file "LICENSE" for terms & conditions for usage, and a DISCLAIMER OF | ||||
| # ALL WARRANTIES. | ||||
| # | ||||
| # This Baseconv distribution contains no GNU General Public Licensed (GPLed) | ||||
| # code so it may be used in proprietary projects just like prior ``baseconv`` | ||||
| # distributions. | ||||
| # | ||||
| # All trademarks referenced herein are property of their respective holders. | ||||
| # | ||||
|  | ||||
| """ | ||||
| Convert numbers from base 10 integers to base X strings and back again. | ||||
|  | ||||
| Sample usage:: | ||||
|  | ||||
|   >>> base20 = BaseConverter('0123456789abcdefghij') | ||||
|   >>> base20.encode(1234) | ||||
|   '31e' | ||||
|   >>> base20.decode('31e') | ||||
|   1234 | ||||
|   >>> base20.encode(-1234) | ||||
|   '-31e' | ||||
|   >>> base20.decode('-31e') | ||||
|   -1234 | ||||
|   >>> base11 = BaseConverter('0123456789-', sign='$') | ||||
|   >>> base11.encode(-1234) | ||||
|   '$-22' | ||||
|   >>> base11.decode('$-22') | ||||
|   -1234 | ||||
|  | ||||
| """ | ||||
| import warnings | ||||
|  | ||||
| from django.utils.deprecation import RemovedInDjango50Warning | ||||
|  | ||||
| warnings.warn( | ||||
|     "The django.utils.baseconv module is deprecated.", | ||||
|     category=RemovedInDjango50Warning, | ||||
|     stacklevel=2, | ||||
| ) | ||||
|  | ||||
| BASE2_ALPHABET = "01" | ||||
| BASE16_ALPHABET = "0123456789ABCDEF" | ||||
| BASE56_ALPHABET = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz" | ||||
| BASE36_ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyz" | ||||
| BASE62_ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" | ||||
| BASE64_ALPHABET = BASE62_ALPHABET + "-_" | ||||
|  | ||||
|  | ||||
| class BaseConverter: | ||||
|     decimal_digits = "0123456789" | ||||
|  | ||||
|     def __init__(self, digits, sign="-"): | ||||
|         self.sign = sign | ||||
|         self.digits = digits | ||||
|         if sign in self.digits: | ||||
|             raise ValueError("Sign character found in converter base digits.") | ||||
|  | ||||
|     def __repr__(self): | ||||
|         return "<%s: base%s (%s)>" % ( | ||||
|             self.__class__.__name__, | ||||
|             len(self.digits), | ||||
|             self.digits, | ||||
|         ) | ||||
|  | ||||
|     def encode(self, i): | ||||
|         neg, value = self.convert(i, self.decimal_digits, self.digits, "-") | ||||
|         if neg: | ||||
|             return self.sign + value | ||||
|         return value | ||||
|  | ||||
|     def decode(self, s): | ||||
|         neg, value = self.convert(s, self.digits, self.decimal_digits, self.sign) | ||||
|         if neg: | ||||
|             value = "-" + value | ||||
|         return int(value) | ||||
|  | ||||
|     def convert(self, number, from_digits, to_digits, sign): | ||||
|         if str(number)[0] == sign: | ||||
|             number = str(number)[1:] | ||||
|             neg = 1 | ||||
|         else: | ||||
|             neg = 0 | ||||
|  | ||||
|         # make an integer out of the number | ||||
|         x = 0 | ||||
|         for digit in str(number): | ||||
|             x = x * len(from_digits) + from_digits.index(digit) | ||||
|  | ||||
|         # create the result in base 'len(to_digits)' | ||||
|         if x == 0: | ||||
|             res = to_digits[0] | ||||
|         else: | ||||
|             res = "" | ||||
|             while x > 0: | ||||
|                 digit = x % len(to_digits) | ||||
|                 res = to_digits[digit] + res | ||||
|                 x = int(x // len(to_digits)) | ||||
|         return neg, res | ||||
|  | ||||
|  | ||||
| base2 = BaseConverter(BASE2_ALPHABET) | ||||
| base16 = BaseConverter(BASE16_ALPHABET) | ||||
| base36 = BaseConverter(BASE36_ALPHABET) | ||||
| base56 = BaseConverter(BASE56_ALPHABET) | ||||
| base62 = BaseConverter(BASE62_ALPHABET) | ||||
| base64 = BaseConverter(BASE64_ALPHABET, sign="$") | ||||
							
								
								
									
										443
									
								
								srcs/.venv/lib/python3.11/site-packages/django/utils/cache.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										443
									
								
								srcs/.venv/lib/python3.11/site-packages/django/utils/cache.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,443 @@ | ||||
| """ | ||||
| This module contains helper functions for controlling caching. It does so by | ||||
| managing the "Vary" header of responses. It includes functions to patch the | ||||
| header of response objects directly and decorators that change functions to do | ||||
| that header-patching themselves. | ||||
|  | ||||
| For information on the Vary header, see RFC 9110 Section 12.5.5. | ||||
|  | ||||
| Essentially, the "Vary" HTTP header defines which headers a cache should take | ||||
| into account when building its cache key. Requests with the same path but | ||||
| different header content for headers named in "Vary" need to get different | ||||
| cache keys to prevent delivery of wrong content. | ||||
|  | ||||
| An example: i18n middleware would need to distinguish caches by the | ||||
| "Accept-language" header. | ||||
| """ | ||||
| import time | ||||
| from collections import defaultdict | ||||
|  | ||||
| from django.conf import settings | ||||
| from django.core.cache import caches | ||||
| from django.http import HttpResponse, HttpResponseNotModified | ||||
| from django.utils.crypto import md5 | ||||
| from django.utils.http import http_date, parse_etags, parse_http_date_safe, quote_etag | ||||
| from django.utils.log import log_response | ||||
| from django.utils.regex_helper import _lazy_re_compile | ||||
| from django.utils.timezone import get_current_timezone_name | ||||
| from django.utils.translation import get_language | ||||
|  | ||||
| cc_delim_re = _lazy_re_compile(r"\s*,\s*") | ||||
|  | ||||
|  | ||||
| def patch_cache_control(response, **kwargs): | ||||
|     """ | ||||
|     Patch the Cache-Control header by adding all keyword arguments to it. | ||||
|     The transformation is as follows: | ||||
|  | ||||
|     * All keyword parameter names are turned to lowercase, and underscores | ||||
|       are converted to hyphens. | ||||
|     * If the value of a parameter is True (exactly True, not just a | ||||
|       true value), only the parameter name is added to the header. | ||||
|     * All other parameters are added with their value, after applying | ||||
|       str() to it. | ||||
|     """ | ||||
|  | ||||
|     def dictitem(s): | ||||
|         t = s.split("=", 1) | ||||
|         if len(t) > 1: | ||||
|             return (t[0].lower(), t[1]) | ||||
|         else: | ||||
|             return (t[0].lower(), True) | ||||
|  | ||||
|     def dictvalue(*t): | ||||
|         if t[1] is True: | ||||
|             return t[0] | ||||
|         else: | ||||
|             return "%s=%s" % (t[0], t[1]) | ||||
|  | ||||
|     cc = defaultdict(set) | ||||
|     if response.get("Cache-Control"): | ||||
|         for field in cc_delim_re.split(response.headers["Cache-Control"]): | ||||
|             directive, value = dictitem(field) | ||||
|             if directive == "no-cache": | ||||
|                 # no-cache supports multiple field names. | ||||
|                 cc[directive].add(value) | ||||
|             else: | ||||
|                 cc[directive] = value | ||||
|  | ||||
|     # If there's already a max-age header but we're being asked to set a new | ||||
|     # max-age, use the minimum of the two ages. In practice this happens when | ||||
|     # a decorator and a piece of middleware both operate on a given view. | ||||
|     if "max-age" in cc and "max_age" in kwargs: | ||||
|         kwargs["max_age"] = min(int(cc["max-age"]), kwargs["max_age"]) | ||||
|  | ||||
|     # Allow overriding private caching and vice versa | ||||
|     if "private" in cc and "public" in kwargs: | ||||
|         del cc["private"] | ||||
|     elif "public" in cc and "private" in kwargs: | ||||
|         del cc["public"] | ||||
|  | ||||
|     for k, v in kwargs.items(): | ||||
|         directive = k.replace("_", "-") | ||||
|         if directive == "no-cache": | ||||
|             # no-cache supports multiple field names. | ||||
|             cc[directive].add(v) | ||||
|         else: | ||||
|             cc[directive] = v | ||||
|  | ||||
|     directives = [] | ||||
|     for directive, values in cc.items(): | ||||
|         if isinstance(values, set): | ||||
|             if True in values: | ||||
|                 # True takes precedence. | ||||
|                 values = {True} | ||||
|             directives.extend([dictvalue(directive, value) for value in values]) | ||||
|         else: | ||||
|             directives.append(dictvalue(directive, values)) | ||||
|     cc = ", ".join(directives) | ||||
|     response.headers["Cache-Control"] = cc | ||||
|  | ||||
|  | ||||
| def get_max_age(response): | ||||
|     """ | ||||
|     Return the max-age from the response Cache-Control header as an integer, | ||||
|     or None if it wasn't found or wasn't an integer. | ||||
|     """ | ||||
|     if not response.has_header("Cache-Control"): | ||||
|         return | ||||
|     cc = dict( | ||||
|         _to_tuple(el) for el in cc_delim_re.split(response.headers["Cache-Control"]) | ||||
|     ) | ||||
|     try: | ||||
|         return int(cc["max-age"]) | ||||
|     except (ValueError, TypeError, KeyError): | ||||
|         pass | ||||
|  | ||||
|  | ||||
| def set_response_etag(response): | ||||
|     if not response.streaming and response.content: | ||||
|         response.headers["ETag"] = quote_etag( | ||||
|             md5(response.content, usedforsecurity=False).hexdigest(), | ||||
|         ) | ||||
|     return response | ||||
|  | ||||
|  | ||||
| def _precondition_failed(request): | ||||
|     response = HttpResponse(status=412) | ||||
|     log_response( | ||||
|         "Precondition Failed: %s", | ||||
|         request.path, | ||||
|         response=response, | ||||
|         request=request, | ||||
|     ) | ||||
|     return response | ||||
|  | ||||
|  | ||||
| def _not_modified(request, response=None): | ||||
|     new_response = HttpResponseNotModified() | ||||
|     if response: | ||||
|         # Preserve the headers required by RFC 9110 Section 15.4.5, as well as | ||||
|         # Last-Modified. | ||||
|         for header in ( | ||||
|             "Cache-Control", | ||||
|             "Content-Location", | ||||
|             "Date", | ||||
|             "ETag", | ||||
|             "Expires", | ||||
|             "Last-Modified", | ||||
|             "Vary", | ||||
|         ): | ||||
|             if header in response: | ||||
|                 new_response.headers[header] = response.headers[header] | ||||
|  | ||||
|         # Preserve cookies as per the cookie specification: "If a proxy server | ||||
|         # receives a response which contains a Set-cookie header, it should | ||||
|         # propagate the Set-cookie header to the client, regardless of whether | ||||
|         # the response was 304 (Not Modified) or 200 (OK). | ||||
|         # https://curl.haxx.se/rfc/cookie_spec.html | ||||
|         new_response.cookies = response.cookies | ||||
|     return new_response | ||||
|  | ||||
|  | ||||
| def get_conditional_response(request, etag=None, last_modified=None, response=None): | ||||
|     # Only return conditional responses on successful requests. | ||||
|     if response and not (200 <= response.status_code < 300): | ||||
|         return response | ||||
|  | ||||
|     # Get HTTP request headers. | ||||
|     if_match_etags = parse_etags(request.META.get("HTTP_IF_MATCH", "")) | ||||
|     if_unmodified_since = request.META.get("HTTP_IF_UNMODIFIED_SINCE") | ||||
|     if_unmodified_since = if_unmodified_since and parse_http_date_safe( | ||||
|         if_unmodified_since | ||||
|     ) | ||||
|     if_none_match_etags = parse_etags(request.META.get("HTTP_IF_NONE_MATCH", "")) | ||||
|     if_modified_since = request.META.get("HTTP_IF_MODIFIED_SINCE") | ||||
|     if_modified_since = if_modified_since and parse_http_date_safe(if_modified_since) | ||||
|  | ||||
|     # Evaluation of request preconditions below follows RFC 9110 Section | ||||
|     # 13.2.2. | ||||
|     # Step 1: Test the If-Match precondition. | ||||
|     if if_match_etags and not _if_match_passes(etag, if_match_etags): | ||||
|         return _precondition_failed(request) | ||||
|  | ||||
|     # Step 2: Test the If-Unmodified-Since precondition. | ||||
|     if ( | ||||
|         not if_match_etags | ||||
|         and if_unmodified_since | ||||
|         and not _if_unmodified_since_passes(last_modified, if_unmodified_since) | ||||
|     ): | ||||
|         return _precondition_failed(request) | ||||
|  | ||||
|     # Step 3: Test the If-None-Match precondition. | ||||
|     if if_none_match_etags and not _if_none_match_passes(etag, if_none_match_etags): | ||||
|         if request.method in ("GET", "HEAD"): | ||||
|             return _not_modified(request, response) | ||||
|         else: | ||||
|             return _precondition_failed(request) | ||||
|  | ||||
|     # Step 4: Test the If-Modified-Since precondition. | ||||
|     if ( | ||||
|         not if_none_match_etags | ||||
|         and if_modified_since | ||||
|         and not _if_modified_since_passes(last_modified, if_modified_since) | ||||
|         and request.method in ("GET", "HEAD") | ||||
|     ): | ||||
|         return _not_modified(request, response) | ||||
|  | ||||
|     # Step 5: Test the If-Range precondition (not supported). | ||||
|     # Step 6: Return original response since there isn't a conditional response. | ||||
|     return response | ||||
|  | ||||
|  | ||||
| def _if_match_passes(target_etag, etags): | ||||
|     """ | ||||
|     Test the If-Match comparison as defined in RFC 9110 Section 13.1.1. | ||||
|     """ | ||||
|     if not target_etag: | ||||
|         # If there isn't an ETag, then there can't be a match. | ||||
|         return False | ||||
|     elif etags == ["*"]: | ||||
|         # The existence of an ETag means that there is "a current | ||||
|         # representation for the target resource", even if the ETag is weak, | ||||
|         # so there is a match to '*'. | ||||
|         return True | ||||
|     elif target_etag.startswith("W/"): | ||||
|         # A weak ETag can never strongly match another ETag. | ||||
|         return False | ||||
|     else: | ||||
|         # Since the ETag is strong, this will only return True if there's a | ||||
|         # strong match. | ||||
|         return target_etag in etags | ||||
|  | ||||
|  | ||||
| def _if_unmodified_since_passes(last_modified, if_unmodified_since): | ||||
|     """ | ||||
|     Test the If-Unmodified-Since comparison as defined in RFC 9110 Section | ||||
|     13.1.4. | ||||
|     """ | ||||
|     return last_modified and last_modified <= if_unmodified_since | ||||
|  | ||||
|  | ||||
| def _if_none_match_passes(target_etag, etags): | ||||
|     """ | ||||
|     Test the If-None-Match comparison as defined in RFC 9110 Section 13.1.2. | ||||
|     """ | ||||
|     if not target_etag: | ||||
|         # If there isn't an ETag, then there isn't a match. | ||||
|         return True | ||||
|     elif etags == ["*"]: | ||||
|         # The existence of an ETag means that there is "a current | ||||
|         # representation for the target resource", so there is a match to '*'. | ||||
|         return False | ||||
|     else: | ||||
|         # The comparison should be weak, so look for a match after stripping | ||||
|         # off any weak indicators. | ||||
|         target_etag = target_etag.strip("W/") | ||||
|         etags = (etag.strip("W/") for etag in etags) | ||||
|         return target_etag not in etags | ||||
|  | ||||
|  | ||||
| def _if_modified_since_passes(last_modified, if_modified_since): | ||||
|     """ | ||||
|     Test the If-Modified-Since comparison as defined in RFC 9110 Section | ||||
|     13.1.3. | ||||
|     """ | ||||
|     return not last_modified or last_modified > if_modified_since | ||||
|  | ||||
|  | ||||
| def patch_response_headers(response, cache_timeout=None): | ||||
|     """ | ||||
|     Add HTTP caching headers to the given HttpResponse: Expires and | ||||
|     Cache-Control. | ||||
|  | ||||
|     Each header is only added if it isn't already set. | ||||
|  | ||||
|     cache_timeout is in seconds. The CACHE_MIDDLEWARE_SECONDS setting is used | ||||
|     by default. | ||||
|     """ | ||||
|     if cache_timeout is None: | ||||
|         cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS | ||||
|     if cache_timeout < 0: | ||||
|         cache_timeout = 0  # Can't have max-age negative | ||||
|     if not response.has_header("Expires"): | ||||
|         response.headers["Expires"] = http_date(time.time() + cache_timeout) | ||||
|     patch_cache_control(response, max_age=cache_timeout) | ||||
|  | ||||
|  | ||||
| def add_never_cache_headers(response): | ||||
|     """ | ||||
|     Add headers to a response to indicate that a page should never be cached. | ||||
|     """ | ||||
|     patch_response_headers(response, cache_timeout=-1) | ||||
|     patch_cache_control( | ||||
|         response, no_cache=True, no_store=True, must_revalidate=True, private=True | ||||
|     ) | ||||
|  | ||||
|  | ||||
| def patch_vary_headers(response, newheaders): | ||||
|     """ | ||||
|     Add (or update) the "Vary" header in the given HttpResponse object. | ||||
|     newheaders is a list of header names that should be in "Vary". If headers | ||||
|     contains an asterisk, then "Vary" header will consist of a single asterisk | ||||
|     '*'. Otherwise, existing headers in "Vary" aren't removed. | ||||
|     """ | ||||
|     # Note that we need to keep the original order intact, because cache | ||||
|     # implementations may rely on the order of the Vary contents in, say, | ||||
|     # computing an MD5 hash. | ||||
|     if response.has_header("Vary"): | ||||
|         vary_headers = cc_delim_re.split(response.headers["Vary"]) | ||||
|     else: | ||||
|         vary_headers = [] | ||||
|     # Use .lower() here so we treat headers as case-insensitive. | ||||
|     existing_headers = {header.lower() for header in vary_headers} | ||||
|     additional_headers = [ | ||||
|         newheader | ||||
|         for newheader in newheaders | ||||
|         if newheader.lower() not in existing_headers | ||||
|     ] | ||||
|     vary_headers += additional_headers | ||||
|     if "*" in vary_headers: | ||||
|         response.headers["Vary"] = "*" | ||||
|     else: | ||||
|         response.headers["Vary"] = ", ".join(vary_headers) | ||||
|  | ||||
|  | ||||
| def has_vary_header(response, header_query): | ||||
|     """ | ||||
|     Check to see if the response has a given header name in its Vary header. | ||||
|     """ | ||||
|     if not response.has_header("Vary"): | ||||
|         return False | ||||
|     vary_headers = cc_delim_re.split(response.headers["Vary"]) | ||||
|     existing_headers = {header.lower() for header in vary_headers} | ||||
|     return header_query.lower() in existing_headers | ||||
|  | ||||
|  | ||||
| def _i18n_cache_key_suffix(request, cache_key): | ||||
|     """If necessary, add the current locale or time zone to the cache key.""" | ||||
|     if settings.USE_I18N: | ||||
|         # first check if LocaleMiddleware or another middleware added | ||||
|         # LANGUAGE_CODE to request, then fall back to the active language | ||||
|         # which in turn can also fall back to settings.LANGUAGE_CODE | ||||
|         cache_key += ".%s" % getattr(request, "LANGUAGE_CODE", get_language()) | ||||
|     if settings.USE_TZ: | ||||
|         cache_key += ".%s" % get_current_timezone_name() | ||||
|     return cache_key | ||||
|  | ||||
|  | ||||
| def _generate_cache_key(request, method, headerlist, key_prefix): | ||||
|     """Return a cache key from the headers given in the header list.""" | ||||
|     ctx = md5(usedforsecurity=False) | ||||
|     for header in headerlist: | ||||
|         value = request.META.get(header) | ||||
|         if value is not None: | ||||
|             ctx.update(value.encode()) | ||||
|     url = md5(request.build_absolute_uri().encode("ascii"), usedforsecurity=False) | ||||
|     cache_key = "views.decorators.cache.cache_page.%s.%s.%s.%s" % ( | ||||
|         key_prefix, | ||||
|         method, | ||||
|         url.hexdigest(), | ||||
|         ctx.hexdigest(), | ||||
|     ) | ||||
|     return _i18n_cache_key_suffix(request, cache_key) | ||||
|  | ||||
|  | ||||
| def _generate_cache_header_key(key_prefix, request): | ||||
|     """Return a cache key for the header cache.""" | ||||
|     url = md5(request.build_absolute_uri().encode("ascii"), usedforsecurity=False) | ||||
|     cache_key = "views.decorators.cache.cache_header.%s.%s" % ( | ||||
|         key_prefix, | ||||
|         url.hexdigest(), | ||||
|     ) | ||||
|     return _i18n_cache_key_suffix(request, cache_key) | ||||
|  | ||||
|  | ||||
| def get_cache_key(request, key_prefix=None, method="GET", cache=None): | ||||
|     """ | ||||
|     Return a cache key based on the request URL and query. It can be used | ||||
|     in the request phase because it pulls the list of headers to take into | ||||
|     account from the global URL registry and uses those to build a cache key | ||||
|     to check against. | ||||
|  | ||||
|     If there isn't a headerlist stored, return None, indicating that the page | ||||
|     needs to be rebuilt. | ||||
|     """ | ||||
|     if key_prefix is None: | ||||
|         key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX | ||||
|     cache_key = _generate_cache_header_key(key_prefix, request) | ||||
|     if cache is None: | ||||
|         cache = caches[settings.CACHE_MIDDLEWARE_ALIAS] | ||||
|     headerlist = cache.get(cache_key) | ||||
|     if headerlist is not None: | ||||
|         return _generate_cache_key(request, method, headerlist, key_prefix) | ||||
|     else: | ||||
|         return None | ||||
|  | ||||
|  | ||||
| def learn_cache_key(request, response, cache_timeout=None, key_prefix=None, cache=None): | ||||
|     """ | ||||
|     Learn what headers to take into account for some request URL from the | ||||
|     response object. Store those headers in a global URL registry so that | ||||
|     later access to that URL will know what headers to take into account | ||||
|     without building the response object itself. The headers are named in the | ||||
|     Vary header of the response, but we want to prevent response generation. | ||||
|  | ||||
|     The list of headers to use for cache key generation is stored in the same | ||||
|     cache as the pages themselves. If the cache ages some data out of the | ||||
|     cache, this just means that we have to build the response once to get at | ||||
|     the Vary header and so at the list of headers to use for the cache key. | ||||
|     """ | ||||
|     if key_prefix is None: | ||||
|         key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX | ||||
|     if cache_timeout is None: | ||||
|         cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS | ||||
|     cache_key = _generate_cache_header_key(key_prefix, request) | ||||
|     if cache is None: | ||||
|         cache = caches[settings.CACHE_MIDDLEWARE_ALIAS] | ||||
|     if response.has_header("Vary"): | ||||
|         is_accept_language_redundant = settings.USE_I18N | ||||
|         # If i18n is used, the generated cache key will be suffixed with the | ||||
|         # current locale. Adding the raw value of Accept-Language is redundant | ||||
|         # in that case and would result in storing the same content under | ||||
|         # multiple keys in the cache. See #18191 for details. | ||||
|         headerlist = [] | ||||
|         for header in cc_delim_re.split(response.headers["Vary"]): | ||||
|             header = header.upper().replace("-", "_") | ||||
|             if header != "ACCEPT_LANGUAGE" or not is_accept_language_redundant: | ||||
|                 headerlist.append("HTTP_" + header) | ||||
|         headerlist.sort() | ||||
|         cache.set(cache_key, headerlist, cache_timeout) | ||||
|         return _generate_cache_key(request, request.method, headerlist, key_prefix) | ||||
|     else: | ||||
|         # if there is no Vary header, we still need a cache key | ||||
|         # for the request.build_absolute_uri() | ||||
|         cache.set(cache_key, [], cache_timeout) | ||||
|         return _generate_cache_key(request, request.method, [], key_prefix) | ||||
|  | ||||
|  | ||||
| def _to_tuple(s): | ||||
|     t = s.split("=", 1) | ||||
|     if len(t) == 2: | ||||
|         return t[0].lower(), t[1] | ||||
|     return t[0].lower(), True | ||||
| @ -0,0 +1,85 @@ | ||||
| from asgiref.local import Local | ||||
|  | ||||
| from django.conf import settings as django_settings | ||||
| from django.utils.functional import cached_property | ||||
|  | ||||
|  | ||||
| class ConnectionProxy: | ||||
|     """Proxy for accessing a connection object's attributes.""" | ||||
|  | ||||
|     def __init__(self, connections, alias): | ||||
|         self.__dict__["_connections"] = connections | ||||
|         self.__dict__["_alias"] = alias | ||||
|  | ||||
|     def __getattr__(self, item): | ||||
|         return getattr(self._connections[self._alias], item) | ||||
|  | ||||
|     def __setattr__(self, name, value): | ||||
|         return setattr(self._connections[self._alias], name, value) | ||||
|  | ||||
|     def __delattr__(self, name): | ||||
|         return delattr(self._connections[self._alias], name) | ||||
|  | ||||
|     def __contains__(self, key): | ||||
|         return key in self._connections[self._alias] | ||||
|  | ||||
|     def __eq__(self, other): | ||||
|         return self._connections[self._alias] == other | ||||
|  | ||||
|  | ||||
| class ConnectionDoesNotExist(Exception): | ||||
|     pass | ||||
|  | ||||
|  | ||||
| class BaseConnectionHandler: | ||||
|     settings_name = None | ||||
|     exception_class = ConnectionDoesNotExist | ||||
|     thread_critical = False | ||||
|  | ||||
|     def __init__(self, settings=None): | ||||
|         self._settings = settings | ||||
|         self._connections = Local(self.thread_critical) | ||||
|  | ||||
|     @cached_property | ||||
|     def settings(self): | ||||
|         self._settings = self.configure_settings(self._settings) | ||||
|         return self._settings | ||||
|  | ||||
|     def configure_settings(self, settings): | ||||
|         if settings is None: | ||||
|             settings = getattr(django_settings, self.settings_name) | ||||
|         return settings | ||||
|  | ||||
|     def create_connection(self, alias): | ||||
|         raise NotImplementedError("Subclasses must implement create_connection().") | ||||
|  | ||||
|     def __getitem__(self, alias): | ||||
|         try: | ||||
|             return getattr(self._connections, alias) | ||||
|         except AttributeError: | ||||
|             if alias not in self.settings: | ||||
|                 raise self.exception_class(f"The connection '{alias}' doesn't exist.") | ||||
|         conn = self.create_connection(alias) | ||||
|         setattr(self._connections, alias, conn) | ||||
|         return conn | ||||
|  | ||||
|     def __setitem__(self, key, value): | ||||
|         setattr(self._connections, key, value) | ||||
|  | ||||
|     def __delitem__(self, key): | ||||
|         delattr(self._connections, key) | ||||
|  | ||||
|     def __iter__(self): | ||||
|         return iter(self.settings) | ||||
|  | ||||
|     def all(self, initialized_only=False): | ||||
|         return [ | ||||
|             self[alias] | ||||
|             for alias in self | ||||
|             # If initialized_only is True, return only initialized connections. | ||||
|             if not initialized_only or hasattr(self._connections, alias) | ||||
|         ] | ||||
|  | ||||
|     def close_all(self): | ||||
|         for conn in self.all(initialized_only=True): | ||||
|             conn.close() | ||||
| @ -0,0 +1,93 @@ | ||||
| """ | ||||
| Django's standard crypto functions and utilities. | ||||
| """ | ||||
| import hashlib | ||||
| import hmac | ||||
| import secrets | ||||
|  | ||||
| from django.conf import settings | ||||
| from django.utils.encoding import force_bytes | ||||
| from django.utils.inspect import func_supports_parameter | ||||
|  | ||||
|  | ||||
| class InvalidAlgorithm(ValueError): | ||||
|     """Algorithm is not supported by hashlib.""" | ||||
|  | ||||
|     pass | ||||
|  | ||||
|  | ||||
| def salted_hmac(key_salt, value, secret=None, *, algorithm="sha1"): | ||||
|     """ | ||||
|     Return the HMAC of 'value', using a key generated from key_salt and a | ||||
|     secret (which defaults to settings.SECRET_KEY). Default algorithm is SHA1, | ||||
|     but any algorithm name supported by hashlib can be passed. | ||||
|  | ||||
|     A different key_salt should be passed in for every application of HMAC. | ||||
|     """ | ||||
|     if secret is None: | ||||
|         secret = settings.SECRET_KEY | ||||
|  | ||||
|     key_salt = force_bytes(key_salt) | ||||
|     secret = force_bytes(secret) | ||||
|     try: | ||||
|         hasher = getattr(hashlib, algorithm) | ||||
|     except AttributeError as e: | ||||
|         raise InvalidAlgorithm( | ||||
|             "%r is not an algorithm accepted by the hashlib module." % algorithm | ||||
|         ) from e | ||||
|     # We need to generate a derived key from our base key.  We can do this by | ||||
|     # passing the key_salt and our base key through a pseudo-random function. | ||||
|     key = hasher(key_salt + secret).digest() | ||||
|     # If len(key_salt + secret) > block size of the hash algorithm, the above | ||||
|     # line is redundant and could be replaced by key = key_salt + secret, since | ||||
|     # the hmac module does the same thing for keys longer than the block size. | ||||
|     # However, we need to ensure that we *always* do this. | ||||
|     return hmac.new(key, msg=force_bytes(value), digestmod=hasher) | ||||
|  | ||||
|  | ||||
| RANDOM_STRING_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" | ||||
|  | ||||
|  | ||||
| def get_random_string(length, allowed_chars=RANDOM_STRING_CHARS): | ||||
|     """ | ||||
|     Return a securely generated random string. | ||||
|  | ||||
|     The bit length of the returned value can be calculated with the formula: | ||||
|         log_2(len(allowed_chars)^length) | ||||
|  | ||||
|     For example, with default `allowed_chars` (26+26+10), this gives: | ||||
|       * length: 12, bit length =~ 71 bits | ||||
|       * length: 22, bit length =~ 131 bits | ||||
|     """ | ||||
|     return "".join(secrets.choice(allowed_chars) for i in range(length)) | ||||
|  | ||||
|  | ||||
| def constant_time_compare(val1, val2): | ||||
|     """Return True if the two strings are equal, False otherwise.""" | ||||
|     return secrets.compare_digest(force_bytes(val1), force_bytes(val2)) | ||||
|  | ||||
|  | ||||
| def pbkdf2(password, salt, iterations, dklen=0, digest=None): | ||||
|     """Return the hash of password using pbkdf2.""" | ||||
|     if digest is None: | ||||
|         digest = hashlib.sha256 | ||||
|     dklen = dklen or None | ||||
|     password = force_bytes(password) | ||||
|     salt = force_bytes(salt) | ||||
|     return hashlib.pbkdf2_hmac(digest().name, password, salt, iterations, dklen) | ||||
|  | ||||
|  | ||||
| # TODO: Remove when dropping support for PY38. inspect.signature() is used to | ||||
| # detect whether the usedforsecurity argument is available as this fix may also | ||||
| # have been applied by downstream package maintainers to other versions in | ||||
| # their repositories. | ||||
| if func_supports_parameter(hashlib.md5, "usedforsecurity"): | ||||
|     md5 = hashlib.md5 | ||||
|     new_hash = hashlib.new | ||||
| else: | ||||
|  | ||||
|     def md5(data=b"", *, usedforsecurity=True): | ||||
|         return hashlib.md5(data) | ||||
|  | ||||
|     def new_hash(hash_algorithm, *, usedforsecurity=True): | ||||
|         return hashlib.new(hash_algorithm) | ||||
| @ -0,0 +1,346 @@ | ||||
| import copy | ||||
| from collections.abc import Mapping | ||||
|  | ||||
|  | ||||
| class OrderedSet: | ||||
|     """ | ||||
|     A set which keeps the ordering of the inserted items. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, iterable=None): | ||||
|         self.dict = dict.fromkeys(iterable or ()) | ||||
|  | ||||
|     def add(self, item): | ||||
|         self.dict[item] = None | ||||
|  | ||||
|     def remove(self, item): | ||||
|         del self.dict[item] | ||||
|  | ||||
|     def discard(self, item): | ||||
|         try: | ||||
|             self.remove(item) | ||||
|         except KeyError: | ||||
|             pass | ||||
|  | ||||
|     def __iter__(self): | ||||
|         return iter(self.dict) | ||||
|  | ||||
|     def __reversed__(self): | ||||
|         return reversed(self.dict) | ||||
|  | ||||
|     def __contains__(self, item): | ||||
|         return item in self.dict | ||||
|  | ||||
|     def __bool__(self): | ||||
|         return bool(self.dict) | ||||
|  | ||||
|     def __len__(self): | ||||
|         return len(self.dict) | ||||
|  | ||||
|     def __repr__(self): | ||||
|         data = repr(list(self.dict)) if self.dict else "" | ||||
|         return f"{self.__class__.__qualname__}({data})" | ||||
|  | ||||
|  | ||||
| class MultiValueDictKeyError(KeyError): | ||||
|     pass | ||||
|  | ||||
|  | ||||
| class MultiValueDict(dict): | ||||
|     """ | ||||
|     A subclass of dictionary customized to handle multiple values for the | ||||
|     same key. | ||||
|  | ||||
|     >>> d = MultiValueDict({'name': ['Adrian', 'Simon'], 'position': ['Developer']}) | ||||
|     >>> d['name'] | ||||
|     'Simon' | ||||
|     >>> d.getlist('name') | ||||
|     ['Adrian', 'Simon'] | ||||
|     >>> d.getlist('doesnotexist') | ||||
|     [] | ||||
|     >>> d.getlist('doesnotexist', ['Adrian', 'Simon']) | ||||
|     ['Adrian', 'Simon'] | ||||
|     >>> d.get('lastname', 'nonexistent') | ||||
|     'nonexistent' | ||||
|     >>> d.setlist('lastname', ['Holovaty', 'Willison']) | ||||
|  | ||||
|     This class exists to solve the irritating problem raised by cgi.parse_qs, | ||||
|     which returns a list for every key, even though most web forms submit | ||||
|     single name-value pairs. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, key_to_list_mapping=()): | ||||
|         super().__init__(key_to_list_mapping) | ||||
|  | ||||
|     def __repr__(self): | ||||
|         return "<%s: %s>" % (self.__class__.__name__, super().__repr__()) | ||||
|  | ||||
|     def __getitem__(self, key): | ||||
|         """ | ||||
|         Return the last data value for this key, or [] if it's an empty list; | ||||
|         raise KeyError if not found. | ||||
|         """ | ||||
|         try: | ||||
|             list_ = super().__getitem__(key) | ||||
|         except KeyError: | ||||
|             raise MultiValueDictKeyError(key) | ||||
|         try: | ||||
|             return list_[-1] | ||||
|         except IndexError: | ||||
|             return [] | ||||
|  | ||||
|     def __setitem__(self, key, value): | ||||
|         super().__setitem__(key, [value]) | ||||
|  | ||||
|     def __copy__(self): | ||||
|         return self.__class__([(k, v[:]) for k, v in self.lists()]) | ||||
|  | ||||
|     def __deepcopy__(self, memo): | ||||
|         result = self.__class__() | ||||
|         memo[id(self)] = result | ||||
|         for key, value in dict.items(self): | ||||
|             dict.__setitem__( | ||||
|                 result, copy.deepcopy(key, memo), copy.deepcopy(value, memo) | ||||
|             ) | ||||
|         return result | ||||
|  | ||||
|     def __getstate__(self): | ||||
|         return {**self.__dict__, "_data": {k: self._getlist(k) for k in self}} | ||||
|  | ||||
|     def __setstate__(self, obj_dict): | ||||
|         data = obj_dict.pop("_data", {}) | ||||
|         for k, v in data.items(): | ||||
|             self.setlist(k, v) | ||||
|         self.__dict__.update(obj_dict) | ||||
|  | ||||
|     def get(self, key, default=None): | ||||
|         """ | ||||
|         Return the last data value for the passed key. If key doesn't exist | ||||
|         or value is an empty list, return `default`. | ||||
|         """ | ||||
|         try: | ||||
|             val = self[key] | ||||
|         except KeyError: | ||||
|             return default | ||||
|         if val == []: | ||||
|             return default | ||||
|         return val | ||||
|  | ||||
|     def _getlist(self, key, default=None, force_list=False): | ||||
|         """ | ||||
|         Return a list of values for the key. | ||||
|  | ||||
|         Used internally to manipulate values list. If force_list is True, | ||||
|         return a new copy of values. | ||||
|         """ | ||||
|         try: | ||||
|             values = super().__getitem__(key) | ||||
|         except KeyError: | ||||
|             if default is None: | ||||
|                 return [] | ||||
|             return default | ||||
|         else: | ||||
|             if force_list: | ||||
|                 values = list(values) if values is not None else None | ||||
|             return values | ||||
|  | ||||
|     def getlist(self, key, default=None): | ||||
|         """ | ||||
|         Return the list of values for the key. If key doesn't exist, return a | ||||
|         default value. | ||||
|         """ | ||||
|         return self._getlist(key, default, force_list=True) | ||||
|  | ||||
|     def setlist(self, key, list_): | ||||
|         super().__setitem__(key, list_) | ||||
|  | ||||
|     def setdefault(self, key, default=None): | ||||
|         if key not in self: | ||||
|             self[key] = default | ||||
|             # Do not return default here because __setitem__() may store | ||||
|             # another value -- QueryDict.__setitem__() does. Look it up. | ||||
|         return self[key] | ||||
|  | ||||
|     def setlistdefault(self, key, default_list=None): | ||||
|         if key not in self: | ||||
|             if default_list is None: | ||||
|                 default_list = [] | ||||
|             self.setlist(key, default_list) | ||||
|             # Do not return default_list here because setlist() may store | ||||
|             # another value -- QueryDict.setlist() does. Look it up. | ||||
|         return self._getlist(key) | ||||
|  | ||||
|     def appendlist(self, key, value): | ||||
|         """Append an item to the internal list associated with key.""" | ||||
|         self.setlistdefault(key).append(value) | ||||
|  | ||||
|     def items(self): | ||||
|         """ | ||||
|         Yield (key, value) pairs, where value is the last item in the list | ||||
|         associated with the key. | ||||
|         """ | ||||
|         for key in self: | ||||
|             yield key, self[key] | ||||
|  | ||||
|     def lists(self): | ||||
|         """Yield (key, list) pairs.""" | ||||
|         return iter(super().items()) | ||||
|  | ||||
|     def values(self): | ||||
|         """Yield the last value on every key list.""" | ||||
|         for key in self: | ||||
|             yield self[key] | ||||
|  | ||||
|     def copy(self): | ||||
|         """Return a shallow copy of this object.""" | ||||
|         return copy.copy(self) | ||||
|  | ||||
|     def update(self, *args, **kwargs): | ||||
|         """Extend rather than replace existing key lists.""" | ||||
|         if len(args) > 1: | ||||
|             raise TypeError("update expected at most 1 argument, got %d" % len(args)) | ||||
|         if args: | ||||
|             arg = args[0] | ||||
|             if isinstance(arg, MultiValueDict): | ||||
|                 for key, value_list in arg.lists(): | ||||
|                     self.setlistdefault(key).extend(value_list) | ||||
|             else: | ||||
|                 if isinstance(arg, Mapping): | ||||
|                     arg = arg.items() | ||||
|                 for key, value in arg: | ||||
|                     self.setlistdefault(key).append(value) | ||||
|         for key, value in kwargs.items(): | ||||
|             self.setlistdefault(key).append(value) | ||||
|  | ||||
|     def dict(self): | ||||
|         """Return current object as a dict with singular values.""" | ||||
|         return {key: self[key] for key in self} | ||||
|  | ||||
|  | ||||
| class ImmutableList(tuple): | ||||
|     """ | ||||
|     A tuple-like object that raises useful errors when it is asked to mutate. | ||||
|  | ||||
|     Example:: | ||||
|  | ||||
|         >>> a = ImmutableList(range(5), warning="You cannot mutate this.") | ||||
|         >>> a[3] = '4' | ||||
|         Traceback (most recent call last): | ||||
|             ... | ||||
|         AttributeError: You cannot mutate this. | ||||
|     """ | ||||
|  | ||||
|     def __new__(cls, *args, warning="ImmutableList object is immutable.", **kwargs): | ||||
|         self = tuple.__new__(cls, *args, **kwargs) | ||||
|         self.warning = warning | ||||
|         return self | ||||
|  | ||||
|     def complain(self, *args, **kwargs): | ||||
|         raise AttributeError(self.warning) | ||||
|  | ||||
|     # All list mutation functions complain. | ||||
|     __delitem__ = complain | ||||
|     __delslice__ = complain | ||||
|     __iadd__ = complain | ||||
|     __imul__ = complain | ||||
|     __setitem__ = complain | ||||
|     __setslice__ = complain | ||||
|     append = complain | ||||
|     extend = complain | ||||
|     insert = complain | ||||
|     pop = complain | ||||
|     remove = complain | ||||
|     sort = complain | ||||
|     reverse = complain | ||||
|  | ||||
|  | ||||
| class DictWrapper(dict): | ||||
|     """ | ||||
|     Wrap accesses to a dictionary so that certain values (those starting with | ||||
|     the specified prefix) are passed through a function before being returned. | ||||
|     The prefix is removed before looking up the real value. | ||||
|  | ||||
|     Used by the SQL construction code to ensure that values are correctly | ||||
|     quoted before being used. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, data, func, prefix): | ||||
|         super().__init__(data) | ||||
|         self.func = func | ||||
|         self.prefix = prefix | ||||
|  | ||||
|     def __getitem__(self, key): | ||||
|         """ | ||||
|         Retrieve the real value after stripping the prefix string (if | ||||
|         present). If the prefix is present, pass the value through self.func | ||||
|         before returning, otherwise return the raw value. | ||||
|         """ | ||||
|         use_func = key.startswith(self.prefix) | ||||
|         if use_func: | ||||
|             key = key[len(self.prefix) :] | ||||
|         value = super().__getitem__(key) | ||||
|         if use_func: | ||||
|             return self.func(value) | ||||
|         return value | ||||
|  | ||||
|  | ||||
| class CaseInsensitiveMapping(Mapping): | ||||
|     """ | ||||
|     Mapping allowing case-insensitive key lookups. Original case of keys is | ||||
|     preserved for iteration and string representation. | ||||
|  | ||||
|     Example:: | ||||
|  | ||||
|         >>> ci_map = CaseInsensitiveMapping({'name': 'Jane'}) | ||||
|         >>> ci_map['Name'] | ||||
|         Jane | ||||
|         >>> ci_map['NAME'] | ||||
|         Jane | ||||
|         >>> ci_map['name'] | ||||
|         Jane | ||||
|         >>> ci_map  # original case preserved | ||||
|         {'name': 'Jane'} | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, data): | ||||
|         self._store = {k.lower(): (k, v) for k, v in self._unpack_items(data)} | ||||
|  | ||||
|     def __getitem__(self, key): | ||||
|         return self._store[key.lower()][1] | ||||
|  | ||||
|     def __len__(self): | ||||
|         return len(self._store) | ||||
|  | ||||
|     def __eq__(self, other): | ||||
|         return isinstance(other, Mapping) and { | ||||
|             k.lower(): v for k, v in self.items() | ||||
|         } == {k.lower(): v for k, v in other.items()} | ||||
|  | ||||
|     def __iter__(self): | ||||
|         return (original_key for original_key, value in self._store.values()) | ||||
|  | ||||
|     def __repr__(self): | ||||
|         return repr({key: value for key, value in self._store.values()}) | ||||
|  | ||||
|     def copy(self): | ||||
|         return self | ||||
|  | ||||
|     @staticmethod | ||||
|     def _unpack_items(data): | ||||
|         # Explicitly test for dict first as the common case for performance, | ||||
|         # avoiding abc's __instancecheck__ and _abc_instancecheck for the | ||||
|         # general Mapping case. | ||||
|         if isinstance(data, (dict, Mapping)): | ||||
|             yield from data.items() | ||||
|             return | ||||
|         for i, elem in enumerate(data): | ||||
|             if len(elem) != 2: | ||||
|                 raise ValueError( | ||||
|                     "dictionary update sequence element #{} has length {}; " | ||||
|                     "2 is required.".format(i, len(elem)) | ||||
|                 ) | ||||
|             if not isinstance(elem[0], str): | ||||
|                 raise ValueError( | ||||
|                     "Element key %r invalid, only strings are allowed" % elem[0] | ||||
|                 ) | ||||
|             yield elem | ||||
| @ -0,0 +1,330 @@ | ||||
| """ | ||||
| PHP date() style date formatting | ||||
| See https://www.php.net/date for format strings | ||||
|  | ||||
| Usage: | ||||
| >>> from datetime import datetime | ||||
| >>> d = datetime.now() | ||||
| >>> df = DateFormat(d) | ||||
| >>> print(df.format('jS F Y H:i')) | ||||
| 7th October 2003 11:39 | ||||
| >>> | ||||
| """ | ||||
| import calendar | ||||
| from datetime import date, datetime, time | ||||
| from email.utils import format_datetime as format_datetime_rfc5322 | ||||
|  | ||||
| from django.utils.dates import ( | ||||
|     MONTHS, | ||||
|     MONTHS_3, | ||||
|     MONTHS_ALT, | ||||
|     MONTHS_AP, | ||||
|     WEEKDAYS, | ||||
|     WEEKDAYS_ABBR, | ||||
| ) | ||||
| from django.utils.regex_helper import _lazy_re_compile | ||||
| from django.utils.timezone import ( | ||||
|     _datetime_ambiguous_or_imaginary, | ||||
|     get_default_timezone, | ||||
|     is_naive, | ||||
|     make_aware, | ||||
| ) | ||||
| from django.utils.translation import gettext as _ | ||||
|  | ||||
| re_formatchars = _lazy_re_compile(r"(?<!\\)([aAbcdDeEfFgGhHiIjlLmMnNoOPrsStTUuwWyYzZ])") | ||||
| re_escaped = _lazy_re_compile(r"\\(.)") | ||||
|  | ||||
|  | ||||
| class Formatter: | ||||
|     def format(self, formatstr): | ||||
|         pieces = [] | ||||
|         for i, piece in enumerate(re_formatchars.split(str(formatstr))): | ||||
|             if i % 2: | ||||
|                 if type(self.data) is date and hasattr(TimeFormat, piece): | ||||
|                     raise TypeError( | ||||
|                         "The format for date objects may not contain " | ||||
|                         "time-related format specifiers (found '%s')." % piece | ||||
|                     ) | ||||
|                 pieces.append(str(getattr(self, piece)())) | ||||
|             elif piece: | ||||
|                 pieces.append(re_escaped.sub(r"\1", piece)) | ||||
|         return "".join(pieces) | ||||
|  | ||||
|  | ||||
| class TimeFormat(Formatter): | ||||
|     def __init__(self, obj): | ||||
|         self.data = obj | ||||
|         self.timezone = None | ||||
|  | ||||
|         if isinstance(obj, datetime): | ||||
|             # Timezone is only supported when formatting datetime objects, not | ||||
|             # date objects (timezone information not appropriate), or time | ||||
|             # objects (against established django policy). | ||||
|             if is_naive(obj): | ||||
|                 timezone = get_default_timezone() | ||||
|             else: | ||||
|                 timezone = obj.tzinfo | ||||
|             if not _datetime_ambiguous_or_imaginary(obj, timezone): | ||||
|                 self.timezone = timezone | ||||
|  | ||||
|     def a(self): | ||||
|         "'a.m.' or 'p.m.'" | ||||
|         if self.data.hour > 11: | ||||
|             return _("p.m.") | ||||
|         return _("a.m.") | ||||
|  | ||||
|     def A(self): | ||||
|         "'AM' or 'PM'" | ||||
|         if self.data.hour > 11: | ||||
|             return _("PM") | ||||
|         return _("AM") | ||||
|  | ||||
|     def e(self): | ||||
|         """ | ||||
|         Timezone name. | ||||
|  | ||||
|         If timezone information is not available, return an empty string. | ||||
|         """ | ||||
|         if not self.timezone: | ||||
|             return "" | ||||
|  | ||||
|         try: | ||||
|             if getattr(self.data, "tzinfo", None): | ||||
|                 return self.data.tzname() or "" | ||||
|         except NotImplementedError: | ||||
|             pass | ||||
|         return "" | ||||
|  | ||||
|     def f(self): | ||||
|         """ | ||||
|         Time, in 12-hour hours and minutes, with minutes left off if they're | ||||
|         zero. | ||||
|         Examples: '1', '1:30', '2:05', '2' | ||||
|         Proprietary extension. | ||||
|         """ | ||||
|         hour = self.data.hour % 12 or 12 | ||||
|         minute = self.data.minute | ||||
|         return "%d:%02d" % (hour, minute) if minute else hour | ||||
|  | ||||
|     def g(self): | ||||
|         "Hour, 12-hour format without leading zeros; i.e. '1' to '12'" | ||||
|         return self.data.hour % 12 or 12 | ||||
|  | ||||
|     def G(self): | ||||
|         "Hour, 24-hour format without leading zeros; i.e. '0' to '23'" | ||||
|         return self.data.hour | ||||
|  | ||||
|     def h(self): | ||||
|         "Hour, 12-hour format; i.e. '01' to '12'" | ||||
|         return "%02d" % (self.data.hour % 12 or 12) | ||||
|  | ||||
|     def H(self): | ||||
|         "Hour, 24-hour format; i.e. '00' to '23'" | ||||
|         return "%02d" % self.data.hour | ||||
|  | ||||
|     def i(self): | ||||
|         "Minutes; i.e. '00' to '59'" | ||||
|         return "%02d" % self.data.minute | ||||
|  | ||||
|     def O(self):  # NOQA: E743, E741 | ||||
|         """ | ||||
|         Difference to Greenwich time in hours; e.g. '+0200', '-0430'. | ||||
|  | ||||
|         If timezone information is not available, return an empty string. | ||||
|         """ | ||||
|         if self.timezone is None: | ||||
|             return "" | ||||
|  | ||||
|         offset = self.timezone.utcoffset(self.data) | ||||
|         seconds = offset.days * 86400 + offset.seconds | ||||
|         sign = "-" if seconds < 0 else "+" | ||||
|         seconds = abs(seconds) | ||||
|         return "%s%02d%02d" % (sign, seconds // 3600, (seconds // 60) % 60) | ||||
|  | ||||
|     def P(self): | ||||
|         """ | ||||
|         Time, in 12-hour hours, minutes and 'a.m.'/'p.m.', with minutes left off | ||||
|         if they're zero and the strings 'midnight' and 'noon' if appropriate. | ||||
|         Examples: '1 a.m.', '1:30 p.m.', 'midnight', 'noon', '12:30 p.m.' | ||||
|         Proprietary extension. | ||||
|         """ | ||||
|         if self.data.minute == 0 and self.data.hour == 0: | ||||
|             return _("midnight") | ||||
|         if self.data.minute == 0 and self.data.hour == 12: | ||||
|             return _("noon") | ||||
|         return "%s %s" % (self.f(), self.a()) | ||||
|  | ||||
|     def s(self): | ||||
|         "Seconds; i.e. '00' to '59'" | ||||
|         return "%02d" % self.data.second | ||||
|  | ||||
|     def T(self): | ||||
|         """ | ||||
|         Time zone of this machine; e.g. 'EST' or 'MDT'. | ||||
|  | ||||
|         If timezone information is not available, return an empty string. | ||||
|         """ | ||||
|         if self.timezone is None: | ||||
|             return "" | ||||
|  | ||||
|         return str(self.timezone.tzname(self.data)) | ||||
|  | ||||
|     def u(self): | ||||
|         "Microseconds; i.e. '000000' to '999999'" | ||||
|         return "%06d" % self.data.microsecond | ||||
|  | ||||
|     def Z(self): | ||||
|         """ | ||||
|         Time zone offset in seconds (i.e. '-43200' to '43200'). The offset for | ||||
|         timezones west of UTC is always negative, and for those east of UTC is | ||||
|         always positive. | ||||
|  | ||||
|         If timezone information is not available, return an empty string. | ||||
|         """ | ||||
|         if self.timezone is None: | ||||
|             return "" | ||||
|  | ||||
|         offset = self.timezone.utcoffset(self.data) | ||||
|  | ||||
|         # `offset` is a datetime.timedelta. For negative values (to the west of | ||||
|         # UTC) only days can be negative (days=-1) and seconds are always | ||||
|         # positive. e.g. UTC-1 -> timedelta(days=-1, seconds=82800, microseconds=0) | ||||
|         # Positive offsets have days=0 | ||||
|         return offset.days * 86400 + offset.seconds | ||||
|  | ||||
|  | ||||
| class DateFormat(TimeFormat): | ||||
|     def b(self): | ||||
|         "Month, textual, 3 letters, lowercase; e.g. 'jan'" | ||||
|         return MONTHS_3[self.data.month] | ||||
|  | ||||
|     def c(self): | ||||
|         """ | ||||
|         ISO 8601 Format | ||||
|         Example : '2008-01-02T10:30:00.000123' | ||||
|         """ | ||||
|         return self.data.isoformat() | ||||
|  | ||||
|     def d(self): | ||||
|         "Day of the month, 2 digits with leading zeros; i.e. '01' to '31'" | ||||
|         return "%02d" % self.data.day | ||||
|  | ||||
|     def D(self): | ||||
|         "Day of the week, textual, 3 letters; e.g. 'Fri'" | ||||
|         return WEEKDAYS_ABBR[self.data.weekday()] | ||||
|  | ||||
|     def E(self): | ||||
|         "Alternative month names as required by some locales. Proprietary extension." | ||||
|         return MONTHS_ALT[self.data.month] | ||||
|  | ||||
|     def F(self): | ||||
|         "Month, textual, long; e.g. 'January'" | ||||
|         return MONTHS[self.data.month] | ||||
|  | ||||
|     def I(self):  # NOQA: E743, E741 | ||||
|         "'1' if daylight saving time, '0' otherwise." | ||||
|         if self.timezone is None: | ||||
|             return "" | ||||
|         return "1" if self.timezone.dst(self.data) else "0" | ||||
|  | ||||
|     def j(self): | ||||
|         "Day of the month without leading zeros; i.e. '1' to '31'" | ||||
|         return self.data.day | ||||
|  | ||||
|     def l(self):  # NOQA: E743, E741 | ||||
|         "Day of the week, textual, long; e.g. 'Friday'" | ||||
|         return WEEKDAYS[self.data.weekday()] | ||||
|  | ||||
|     def L(self): | ||||
|         "Boolean for whether it is a leap year; i.e. True or False" | ||||
|         return calendar.isleap(self.data.year) | ||||
|  | ||||
|     def m(self): | ||||
|         "Month; i.e. '01' to '12'" | ||||
|         return "%02d" % self.data.month | ||||
|  | ||||
|     def M(self): | ||||
|         "Month, textual, 3 letters; e.g. 'Jan'" | ||||
|         return MONTHS_3[self.data.month].title() | ||||
|  | ||||
|     def n(self): | ||||
|         "Month without leading zeros; i.e. '1' to '12'" | ||||
|         return self.data.month | ||||
|  | ||||
|     def N(self): | ||||
|         "Month abbreviation in Associated Press style. Proprietary extension." | ||||
|         return MONTHS_AP[self.data.month] | ||||
|  | ||||
|     def o(self): | ||||
|         "ISO 8601 year number matching the ISO week number (W)" | ||||
|         return self.data.isocalendar()[0] | ||||
|  | ||||
|     def r(self): | ||||
|         "RFC 5322 formatted date; e.g. 'Thu, 21 Dec 2000 16:01:07 +0200'" | ||||
|         value = self.data | ||||
|         if not isinstance(value, datetime): | ||||
|             # Assume midnight in default timezone if datetime.date provided. | ||||
|             default_timezone = get_default_timezone() | ||||
|             value = datetime.combine(value, time.min).replace(tzinfo=default_timezone) | ||||
|         elif is_naive(value): | ||||
|             value = make_aware(value, timezone=self.timezone) | ||||
|         return format_datetime_rfc5322(value) | ||||
|  | ||||
|     def S(self): | ||||
|         """ | ||||
|         English ordinal suffix for the day of the month, 2 characters; i.e. | ||||
|         'st', 'nd', 'rd' or 'th'. | ||||
|         """ | ||||
|         if self.data.day in (11, 12, 13):  # Special case | ||||
|             return "th" | ||||
|         last = self.data.day % 10 | ||||
|         if last == 1: | ||||
|             return "st" | ||||
|         if last == 2: | ||||
|             return "nd" | ||||
|         if last == 3: | ||||
|             return "rd" | ||||
|         return "th" | ||||
|  | ||||
|     def t(self): | ||||
|         "Number of days in the given month; i.e. '28' to '31'" | ||||
|         return calendar.monthrange(self.data.year, self.data.month)[1] | ||||
|  | ||||
|     def U(self): | ||||
|         "Seconds since the Unix epoch (January 1 1970 00:00:00 GMT)" | ||||
|         value = self.data | ||||
|         if not isinstance(value, datetime): | ||||
|             value = datetime.combine(value, time.min) | ||||
|         return int(value.timestamp()) | ||||
|  | ||||
|     def w(self): | ||||
|         "Day of the week, numeric, i.e. '0' (Sunday) to '6' (Saturday)" | ||||
|         return (self.data.weekday() + 1) % 7 | ||||
|  | ||||
|     def W(self): | ||||
|         "ISO-8601 week number of year, weeks starting on Monday" | ||||
|         return self.data.isocalendar()[1] | ||||
|  | ||||
|     def y(self): | ||||
|         """Year, 2 digits with leading zeros; e.g. '99'.""" | ||||
|         return "%02d" % (self.data.year % 100) | ||||
|  | ||||
|     def Y(self): | ||||
|         """Year, 4 digits with leading zeros; e.g. '1999'.""" | ||||
|         return "%04d" % self.data.year | ||||
|  | ||||
|     def z(self): | ||||
|         """Day of the year, i.e. 1 to 366.""" | ||||
|         return self.data.timetuple().tm_yday | ||||
|  | ||||
|  | ||||
| def format(value, format_string): | ||||
|     "Convenience function" | ||||
|     df = DateFormat(value) | ||||
|     return df.format(format_string) | ||||
|  | ||||
|  | ||||
| def time_format(value, format_string): | ||||
|     "Convenience function" | ||||
|     tf = TimeFormat(value) | ||||
|     return tf.format(format_string) | ||||
| @ -0,0 +1,154 @@ | ||||
| """Functions to parse datetime objects.""" | ||||
|  | ||||
| # We're using regular expressions rather than time.strptime because: | ||||
| # - They provide both validation and parsing. | ||||
| # - They're more flexible for datetimes. | ||||
| # - The date/datetime/time constructors produce friendlier error messages. | ||||
|  | ||||
| import datetime | ||||
|  | ||||
| from django.utils.regex_helper import _lazy_re_compile | ||||
| from django.utils.timezone import get_fixed_timezone | ||||
|  | ||||
| date_re = _lazy_re_compile(r"(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})$") | ||||
|  | ||||
| time_re = _lazy_re_compile( | ||||
|     r"(?P<hour>\d{1,2}):(?P<minute>\d{1,2})" | ||||
|     r"(?::(?P<second>\d{1,2})(?:[\.,](?P<microsecond>\d{1,6})\d{0,6})?)?$" | ||||
| ) | ||||
|  | ||||
| datetime_re = _lazy_re_compile( | ||||
|     r"(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})" | ||||
|     r"[T ](?P<hour>\d{1,2}):(?P<minute>\d{1,2})" | ||||
|     r"(?::(?P<second>\d{1,2})(?:[\.,](?P<microsecond>\d{1,6})\d{0,6})?)?" | ||||
|     r"\s*(?P<tzinfo>Z|[+-]\d{2}(?::?\d{2})?)?$" | ||||
| ) | ||||
|  | ||||
| standard_duration_re = _lazy_re_compile( | ||||
|     r"^" | ||||
|     r"(?:(?P<days>-?\d+) (days?, )?)?" | ||||
|     r"(?P<sign>-?)" | ||||
|     r"((?:(?P<hours>\d+):)(?=\d+:\d+))?" | ||||
|     r"(?:(?P<minutes>\d+):)?" | ||||
|     r"(?P<seconds>\d+)" | ||||
|     r"(?:[\.,](?P<microseconds>\d{1,6})\d{0,6})?" | ||||
|     r"$" | ||||
| ) | ||||
|  | ||||
| # Support the sections of ISO 8601 date representation that are accepted by | ||||
| # timedelta | ||||
| iso8601_duration_re = _lazy_re_compile( | ||||
|     r"^(?P<sign>[-+]?)" | ||||
|     r"P" | ||||
|     r"(?:(?P<days>\d+([\.,]\d+)?)D)?" | ||||
|     r"(?:T" | ||||
|     r"(?:(?P<hours>\d+([\.,]\d+)?)H)?" | ||||
|     r"(?:(?P<minutes>\d+([\.,]\d+)?)M)?" | ||||
|     r"(?:(?P<seconds>\d+([\.,]\d+)?)S)?" | ||||
|     r")?" | ||||
|     r"$" | ||||
| ) | ||||
|  | ||||
| # Support PostgreSQL's day-time interval format, e.g. "3 days 04:05:06". The | ||||
| # year-month and mixed intervals cannot be converted to a timedelta and thus | ||||
| # aren't accepted. | ||||
| postgres_interval_re = _lazy_re_compile( | ||||
|     r"^" | ||||
|     r"(?:(?P<days>-?\d+) (days? ?))?" | ||||
|     r"(?:(?P<sign>[-+])?" | ||||
|     r"(?P<hours>\d+):" | ||||
|     r"(?P<minutes>\d\d):" | ||||
|     r"(?P<seconds>\d\d)" | ||||
|     r"(?:\.(?P<microseconds>\d{1,6}))?" | ||||
|     r")?$" | ||||
| ) | ||||
|  | ||||
|  | ||||
| def parse_date(value): | ||||
|     """Parse a string and return a datetime.date. | ||||
|  | ||||
|     Raise ValueError if the input is well formatted but not a valid date. | ||||
|     Return None if the input isn't well formatted. | ||||
|     """ | ||||
|     try: | ||||
|         return datetime.date.fromisoformat(value) | ||||
|     except ValueError: | ||||
|         if match := date_re.match(value): | ||||
|             kw = {k: int(v) for k, v in match.groupdict().items()} | ||||
|             return datetime.date(**kw) | ||||
|  | ||||
|  | ||||
| def parse_time(value): | ||||
|     """Parse a string and return a datetime.time. | ||||
|  | ||||
|     This function doesn't support time zone offsets. | ||||
|  | ||||
|     Raise ValueError if the input is well formatted but not a valid time. | ||||
|     Return None if the input isn't well formatted, in particular if it | ||||
|     contains an offset. | ||||
|     """ | ||||
|     try: | ||||
|         # The fromisoformat() method takes time zone info into account and | ||||
|         # returns a time with a tzinfo component, if possible. However, there | ||||
|         # are no circumstances where aware datetime.time objects make sense, so | ||||
|         # remove the time zone offset. | ||||
|         return datetime.time.fromisoformat(value).replace(tzinfo=None) | ||||
|     except ValueError: | ||||
|         if match := time_re.match(value): | ||||
|             kw = match.groupdict() | ||||
|             kw["microsecond"] = kw["microsecond"] and kw["microsecond"].ljust(6, "0") | ||||
|             kw = {k: int(v) for k, v in kw.items() if v is not None} | ||||
|             return datetime.time(**kw) | ||||
|  | ||||
|  | ||||
| def parse_datetime(value): | ||||
|     """Parse a string and return a datetime.datetime. | ||||
|  | ||||
|     This function supports time zone offsets. When the input contains one, | ||||
|     the output uses a timezone with a fixed offset from UTC. | ||||
|  | ||||
|     Raise ValueError if the input is well formatted but not a valid datetime. | ||||
|     Return None if the input isn't well formatted. | ||||
|     """ | ||||
|     try: | ||||
|         return datetime.datetime.fromisoformat(value) | ||||
|     except ValueError: | ||||
|         if match := datetime_re.match(value): | ||||
|             kw = match.groupdict() | ||||
|             kw["microsecond"] = kw["microsecond"] and kw["microsecond"].ljust(6, "0") | ||||
|             tzinfo = kw.pop("tzinfo") | ||||
|             if tzinfo == "Z": | ||||
|                 tzinfo = datetime.timezone.utc | ||||
|             elif tzinfo is not None: | ||||
|                 offset_mins = int(tzinfo[-2:]) if len(tzinfo) > 3 else 0 | ||||
|                 offset = 60 * int(tzinfo[1:3]) + offset_mins | ||||
|                 if tzinfo[0] == "-": | ||||
|                     offset = -offset | ||||
|                 tzinfo = get_fixed_timezone(offset) | ||||
|             kw = {k: int(v) for k, v in kw.items() if v is not None} | ||||
|             return datetime.datetime(**kw, tzinfo=tzinfo) | ||||
|  | ||||
|  | ||||
| def parse_duration(value): | ||||
|     """Parse a duration string and return a datetime.timedelta. | ||||
|  | ||||
|     The preferred format for durations in Django is '%d %H:%M:%S.%f'. | ||||
|  | ||||
|     Also supports ISO 8601 representation and PostgreSQL's day-time interval | ||||
|     format. | ||||
|     """ | ||||
|     match = ( | ||||
|         standard_duration_re.match(value) | ||||
|         or iso8601_duration_re.match(value) | ||||
|         or postgres_interval_re.match(value) | ||||
|     ) | ||||
|     if match: | ||||
|         kw = match.groupdict() | ||||
|         sign = -1 if kw.pop("sign", "+") == "-" else 1 | ||||
|         if kw.get("microseconds"): | ||||
|             kw["microseconds"] = kw["microseconds"].ljust(6, "0") | ||||
|         kw = {k: float(v.replace(",", ".")) for k, v in kw.items() if v is not None} | ||||
|         days = datetime.timedelta(kw.pop("days", 0.0) or 0.0) | ||||
|         if match.re == iso8601_duration_re: | ||||
|             days *= sign | ||||
|         return days + sign * datetime.timedelta(**kw) | ||||
| @ -0,0 +1,79 @@ | ||||
| "Commonly-used date structures" | ||||
|  | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| from django.utils.translation import pgettext_lazy | ||||
|  | ||||
| WEEKDAYS = { | ||||
|     0: _("Monday"), | ||||
|     1: _("Tuesday"), | ||||
|     2: _("Wednesday"), | ||||
|     3: _("Thursday"), | ||||
|     4: _("Friday"), | ||||
|     5: _("Saturday"), | ||||
|     6: _("Sunday"), | ||||
| } | ||||
| WEEKDAYS_ABBR = { | ||||
|     0: _("Mon"), | ||||
|     1: _("Tue"), | ||||
|     2: _("Wed"), | ||||
|     3: _("Thu"), | ||||
|     4: _("Fri"), | ||||
|     5: _("Sat"), | ||||
|     6: _("Sun"), | ||||
| } | ||||
| MONTHS = { | ||||
|     1: _("January"), | ||||
|     2: _("February"), | ||||
|     3: _("March"), | ||||
|     4: _("April"), | ||||
|     5: _("May"), | ||||
|     6: _("June"), | ||||
|     7: _("July"), | ||||
|     8: _("August"), | ||||
|     9: _("September"), | ||||
|     10: _("October"), | ||||
|     11: _("November"), | ||||
|     12: _("December"), | ||||
| } | ||||
| MONTHS_3 = { | ||||
|     1: _("jan"), | ||||
|     2: _("feb"), | ||||
|     3: _("mar"), | ||||
|     4: _("apr"), | ||||
|     5: _("may"), | ||||
|     6: _("jun"), | ||||
|     7: _("jul"), | ||||
|     8: _("aug"), | ||||
|     9: _("sep"), | ||||
|     10: _("oct"), | ||||
|     11: _("nov"), | ||||
|     12: _("dec"), | ||||
| } | ||||
| MONTHS_AP = {  # month names in Associated Press style | ||||
|     1: pgettext_lazy("abbrev. month", "Jan."), | ||||
|     2: pgettext_lazy("abbrev. month", "Feb."), | ||||
|     3: pgettext_lazy("abbrev. month", "March"), | ||||
|     4: pgettext_lazy("abbrev. month", "April"), | ||||
|     5: pgettext_lazy("abbrev. month", "May"), | ||||
|     6: pgettext_lazy("abbrev. month", "June"), | ||||
|     7: pgettext_lazy("abbrev. month", "July"), | ||||
|     8: pgettext_lazy("abbrev. month", "Aug."), | ||||
|     9: pgettext_lazy("abbrev. month", "Sept."), | ||||
|     10: pgettext_lazy("abbrev. month", "Oct."), | ||||
|     11: pgettext_lazy("abbrev. month", "Nov."), | ||||
|     12: pgettext_lazy("abbrev. month", "Dec."), | ||||
| } | ||||
| MONTHS_ALT = {  # required for long date representation by some locales | ||||
|     1: pgettext_lazy("alt. month", "January"), | ||||
|     2: pgettext_lazy("alt. month", "February"), | ||||
|     3: pgettext_lazy("alt. month", "March"), | ||||
|     4: pgettext_lazy("alt. month", "April"), | ||||
|     5: pgettext_lazy("alt. month", "May"), | ||||
|     6: pgettext_lazy("alt. month", "June"), | ||||
|     7: pgettext_lazy("alt. month", "July"), | ||||
|     8: pgettext_lazy("alt. month", "August"), | ||||
|     9: pgettext_lazy("alt. month", "September"), | ||||
|     10: pgettext_lazy("alt. month", "October"), | ||||
|     11: pgettext_lazy("alt. month", "November"), | ||||
|     12: pgettext_lazy("alt. month", "December"), | ||||
| } | ||||
| @ -0,0 +1,118 @@ | ||||
| # These classes override date and datetime to ensure that strftime('%Y') | ||||
| # returns four digits (with leading zeros) on years < 1000. | ||||
| # https://bugs.python.org/issue13305 | ||||
| # | ||||
| # Based on code submitted to comp.lang.python by Andrew Dalke | ||||
| # | ||||
| # >>> datetime_safe.date(10, 8, 2).strftime("%Y/%m/%d was a %A") | ||||
| # '0010/08/02 was a Monday' | ||||
|  | ||||
| import time | ||||
| import warnings | ||||
| from datetime import date as real_date | ||||
| from datetime import datetime as real_datetime | ||||
|  | ||||
| from django.utils.deprecation import RemovedInDjango50Warning | ||||
| from django.utils.regex_helper import _lazy_re_compile | ||||
|  | ||||
| warnings.warn( | ||||
|     "The django.utils.datetime_safe module is deprecated.", | ||||
|     category=RemovedInDjango50Warning, | ||||
|     stacklevel=2, | ||||
| ) | ||||
|  | ||||
|  | ||||
| class date(real_date): | ||||
|     def strftime(self, fmt): | ||||
|         return strftime(self, fmt) | ||||
|  | ||||
|  | ||||
| class datetime(real_datetime): | ||||
|     def strftime(self, fmt): | ||||
|         return strftime(self, fmt) | ||||
|  | ||||
|     @classmethod | ||||
|     def combine(cls, date, time): | ||||
|         return cls( | ||||
|             date.year, | ||||
|             date.month, | ||||
|             date.day, | ||||
|             time.hour, | ||||
|             time.minute, | ||||
|             time.second, | ||||
|             time.microsecond, | ||||
|             time.tzinfo, | ||||
|         ) | ||||
|  | ||||
|     def date(self): | ||||
|         return date(self.year, self.month, self.day) | ||||
|  | ||||
|  | ||||
| def new_date(d): | ||||
|     "Generate a safe date from a datetime.date object." | ||||
|     return date(d.year, d.month, d.day) | ||||
|  | ||||
|  | ||||
| def new_datetime(d): | ||||
|     """ | ||||
|     Generate a safe datetime from a datetime.date or datetime.datetime object. | ||||
|     """ | ||||
|     kw = [d.year, d.month, d.day] | ||||
|     if isinstance(d, real_datetime): | ||||
|         kw.extend([d.hour, d.minute, d.second, d.microsecond, d.tzinfo]) | ||||
|     return datetime(*kw) | ||||
|  | ||||
|  | ||||
| # This library does not support strftime's "%s" or "%y" format strings. | ||||
| # Allowed if there's an even number of "%"s because they are escaped. | ||||
| _illegal_formatting = _lazy_re_compile(r"((^|[^%])(%%)*%[sy])") | ||||
|  | ||||
|  | ||||
| def _findall(text, substr): | ||||
|     # Also finds overlaps | ||||
|     sites = [] | ||||
|     i = 0 | ||||
|     while True: | ||||
|         i = text.find(substr, i) | ||||
|         if i == -1: | ||||
|             break | ||||
|         sites.append(i) | ||||
|         i += 1 | ||||
|     return sites | ||||
|  | ||||
|  | ||||
| def strftime(dt, fmt): | ||||
|     if dt.year >= 1000: | ||||
|         return super(type(dt), dt).strftime(fmt) | ||||
|     illegal_formatting = _illegal_formatting.search(fmt) | ||||
|     if illegal_formatting: | ||||
|         raise TypeError( | ||||
|             "strftime of dates before 1000 does not handle " + illegal_formatting[0] | ||||
|         ) | ||||
|  | ||||
|     year = dt.year | ||||
|     # For every non-leap year century, advance by | ||||
|     # 6 years to get into the 28-year repeat cycle | ||||
|     delta = 2000 - year | ||||
|     off = 6 * (delta // 100 + delta // 400) | ||||
|     year += off | ||||
|  | ||||
|     # Move to around the year 2000 | ||||
|     year += ((2000 - year) // 28) * 28 | ||||
|     timetuple = dt.timetuple() | ||||
|     s1 = time.strftime(fmt, (year,) + timetuple[1:]) | ||||
|     sites1 = _findall(s1, str(year)) | ||||
|  | ||||
|     s2 = time.strftime(fmt, (year + 28,) + timetuple[1:]) | ||||
|     sites2 = _findall(s2, str(year + 28)) | ||||
|  | ||||
|     sites = [] | ||||
|     for site in sites1: | ||||
|         if site in sites2: | ||||
|             sites.append(site) | ||||
|  | ||||
|     s = s1 | ||||
|     syear = "%04d" % dt.year | ||||
|     for site in sites: | ||||
|         s = s[:site] + syear + s[site + 4 :] | ||||
|     return s | ||||
| @ -0,0 +1,59 @@ | ||||
| from importlib import import_module | ||||
|  | ||||
| from django.utils.version import get_docs_version | ||||
|  | ||||
|  | ||||
| def deconstructible(*args, path=None): | ||||
|     """ | ||||
|     Class decorator that allows the decorated class to be serialized | ||||
|     by the migrations subsystem. | ||||
|  | ||||
|     The `path` kwarg specifies the import path. | ||||
|     """ | ||||
|  | ||||
|     def decorator(klass): | ||||
|         def __new__(cls, *args, **kwargs): | ||||
|             # We capture the arguments to make returning them trivial | ||||
|             obj = super(klass, cls).__new__(cls) | ||||
|             obj._constructor_args = (args, kwargs) | ||||
|             return obj | ||||
|  | ||||
|         def deconstruct(obj): | ||||
|             """ | ||||
|             Return a 3-tuple of class import path, positional arguments, | ||||
|             and keyword arguments. | ||||
|             """ | ||||
|             # Fallback version | ||||
|             if path and type(obj) is klass: | ||||
|                 module_name, _, name = path.rpartition(".") | ||||
|             else: | ||||
|                 module_name = obj.__module__ | ||||
|                 name = obj.__class__.__name__ | ||||
|             # Make sure it's actually there and not an inner class | ||||
|             module = import_module(module_name) | ||||
|             if not hasattr(module, name): | ||||
|                 raise ValueError( | ||||
|                     "Could not find object %s in %s.\n" | ||||
|                     "Please note that you cannot serialize things like inner " | ||||
|                     "classes. Please move the object into the main module " | ||||
|                     "body to use migrations.\n" | ||||
|                     "For more information, see " | ||||
|                     "https://docs.djangoproject.com/en/%s/topics/migrations/" | ||||
|                     "#serializing-values" % (name, module_name, get_docs_version()) | ||||
|                 ) | ||||
|             return ( | ||||
|                 path | ||||
|                 if path and type(obj) is klass | ||||
|                 else f"{obj.__class__.__module__}.{name}", | ||||
|                 obj._constructor_args[0], | ||||
|                 obj._constructor_args[1], | ||||
|             ) | ||||
|  | ||||
|         klass.__new__ = staticmethod(__new__) | ||||
|         klass.deconstruct = deconstruct | ||||
|  | ||||
|         return klass | ||||
|  | ||||
|     if not args: | ||||
|         return decorator | ||||
|     return decorator(*args) | ||||
| @ -0,0 +1,190 @@ | ||||
| "Functions that help with dynamically creating decorators for views." | ||||
|  | ||||
| from functools import partial, update_wrapper, wraps | ||||
|  | ||||
|  | ||||
| class classonlymethod(classmethod): | ||||
|     def __get__(self, instance, cls=None): | ||||
|         if instance is not None: | ||||
|             raise AttributeError( | ||||
|                 "This method is available only on the class, not on instances." | ||||
|             ) | ||||
|         return super().__get__(instance, cls) | ||||
|  | ||||
|  | ||||
| def _update_method_wrapper(_wrapper, decorator): | ||||
|     # _multi_decorate()'s bound_method isn't available in this scope. Cheat by | ||||
|     # using it on a dummy function. | ||||
|     @decorator | ||||
|     def dummy(*args, **kwargs): | ||||
|         pass | ||||
|  | ||||
|     update_wrapper(_wrapper, dummy) | ||||
|  | ||||
|  | ||||
| def _multi_decorate(decorators, method): | ||||
|     """ | ||||
|     Decorate `method` with one or more function decorators. `decorators` can be | ||||
|     a single decorator or an iterable of decorators. | ||||
|     """ | ||||
|     if hasattr(decorators, "__iter__"): | ||||
|         # Apply a list/tuple of decorators if 'decorators' is one. Decorator | ||||
|         # functions are applied so that the call order is the same as the | ||||
|         # order in which they appear in the iterable. | ||||
|         decorators = decorators[::-1] | ||||
|     else: | ||||
|         decorators = [decorators] | ||||
|  | ||||
|     def _wrapper(self, *args, **kwargs): | ||||
|         # bound_method has the signature that 'decorator' expects i.e. no | ||||
|         # 'self' argument, but it's a closure over self so it can call | ||||
|         # 'func'. Also, wrap method.__get__() in a function because new | ||||
|         # attributes can't be set on bound method objects, only on functions. | ||||
|         bound_method = wraps(method)(partial(method.__get__(self, type(self)))) | ||||
|         for dec in decorators: | ||||
|             bound_method = dec(bound_method) | ||||
|         return bound_method(*args, **kwargs) | ||||
|  | ||||
|     # Copy any attributes that a decorator adds to the function it decorates. | ||||
|     for dec in decorators: | ||||
|         _update_method_wrapper(_wrapper, dec) | ||||
|     # Preserve any existing attributes of 'method', including the name. | ||||
|     update_wrapper(_wrapper, method) | ||||
|     return _wrapper | ||||
|  | ||||
|  | ||||
| def method_decorator(decorator, name=""): | ||||
|     """ | ||||
|     Convert a function decorator into a method decorator | ||||
|     """ | ||||
|  | ||||
|     # 'obj' can be a class or a function. If 'obj' is a function at the time it | ||||
|     # is passed to _dec,  it will eventually be a method of the class it is | ||||
|     # defined on. If 'obj' is a class, the 'name' is required to be the name | ||||
|     # of the method that will be decorated. | ||||
|     def _dec(obj): | ||||
|         if not isinstance(obj, type): | ||||
|             return _multi_decorate(decorator, obj) | ||||
|         if not (name and hasattr(obj, name)): | ||||
|             raise ValueError( | ||||
|                 "The keyword argument `name` must be the name of a method " | ||||
|                 "of the decorated class: %s. Got '%s' instead." % (obj, name) | ||||
|             ) | ||||
|         method = getattr(obj, name) | ||||
|         if not callable(method): | ||||
|             raise TypeError( | ||||
|                 "Cannot decorate '%s' as it isn't a callable attribute of " | ||||
|                 "%s (%s)." % (name, obj, method) | ||||
|             ) | ||||
|         _wrapper = _multi_decorate(decorator, method) | ||||
|         setattr(obj, name, _wrapper) | ||||
|         return obj | ||||
|  | ||||
|     # Don't worry about making _dec look similar to a list/tuple as it's rather | ||||
|     # meaningless. | ||||
|     if not hasattr(decorator, "__iter__"): | ||||
|         update_wrapper(_dec, decorator) | ||||
|     # Change the name to aid debugging. | ||||
|     obj = decorator if hasattr(decorator, "__name__") else decorator.__class__ | ||||
|     _dec.__name__ = "method_decorator(%s)" % obj.__name__ | ||||
|     return _dec | ||||
|  | ||||
|  | ||||
| def decorator_from_middleware_with_args(middleware_class): | ||||
|     """ | ||||
|     Like decorator_from_middleware, but return a function | ||||
|     that accepts the arguments to be passed to the middleware_class. | ||||
|     Use like:: | ||||
|  | ||||
|          cache_page = decorator_from_middleware_with_args(CacheMiddleware) | ||||
|          # ... | ||||
|  | ||||
|          @cache_page(3600) | ||||
|          def my_view(request): | ||||
|              # ... | ||||
|     """ | ||||
|     return make_middleware_decorator(middleware_class) | ||||
|  | ||||
|  | ||||
| def decorator_from_middleware(middleware_class): | ||||
|     """ | ||||
|     Given a middleware class (not an instance), return a view decorator. This | ||||
|     lets you use middleware functionality on a per-view basis. The middleware | ||||
|     is created with no params passed. | ||||
|     """ | ||||
|     return make_middleware_decorator(middleware_class)() | ||||
|  | ||||
|  | ||||
| def make_middleware_decorator(middleware_class): | ||||
|     def _make_decorator(*m_args, **m_kwargs): | ||||
|         def _decorator(view_func): | ||||
|             middleware = middleware_class(view_func, *m_args, **m_kwargs) | ||||
|  | ||||
|             @wraps(view_func) | ||||
|             def _wrapper_view(request, *args, **kwargs): | ||||
|                 if hasattr(middleware, "process_request"): | ||||
|                     result = middleware.process_request(request) | ||||
|                     if result is not None: | ||||
|                         return result | ||||
|                 if hasattr(middleware, "process_view"): | ||||
|                     result = middleware.process_view(request, view_func, args, kwargs) | ||||
|                     if result is not None: | ||||
|                         return result | ||||
|                 try: | ||||
|                     response = view_func(request, *args, **kwargs) | ||||
|                 except Exception as e: | ||||
|                     if hasattr(middleware, "process_exception"): | ||||
|                         result = middleware.process_exception(request, e) | ||||
|                         if result is not None: | ||||
|                             return result | ||||
|                     raise | ||||
|                 if hasattr(response, "render") and callable(response.render): | ||||
|                     if hasattr(middleware, "process_template_response"): | ||||
|                         response = middleware.process_template_response( | ||||
|                             request, response | ||||
|                         ) | ||||
|                     # Defer running of process_response until after the template | ||||
|                     # has been rendered: | ||||
|                     if hasattr(middleware, "process_response"): | ||||
|  | ||||
|                         def callback(response): | ||||
|                             return middleware.process_response(request, response) | ||||
|  | ||||
|                         response.add_post_render_callback(callback) | ||||
|                 else: | ||||
|                     if hasattr(middleware, "process_response"): | ||||
|                         return middleware.process_response(request, response) | ||||
|                 return response | ||||
|  | ||||
|             return _wrapper_view | ||||
|  | ||||
|         return _decorator | ||||
|  | ||||
|     return _make_decorator | ||||
|  | ||||
|  | ||||
| def sync_and_async_middleware(func): | ||||
|     """ | ||||
|     Mark a middleware factory as returning a hybrid middleware supporting both | ||||
|     types of request. | ||||
|     """ | ||||
|     func.sync_capable = True | ||||
|     func.async_capable = True | ||||
|     return func | ||||
|  | ||||
|  | ||||
| def sync_only_middleware(func): | ||||
|     """ | ||||
|     Mark a middleware factory as returning a sync middleware. | ||||
|     This is the default. | ||||
|     """ | ||||
|     func.sync_capable = True | ||||
|     func.async_capable = False | ||||
|     return func | ||||
|  | ||||
|  | ||||
| def async_only_middleware(func): | ||||
|     """Mark a middleware factory as returning an async middleware.""" | ||||
|     func.sync_capable = False | ||||
|     func.async_capable = True | ||||
|     return func | ||||
| @ -0,0 +1,156 @@ | ||||
| import inspect | ||||
| import warnings | ||||
|  | ||||
| from asgiref.sync import iscoroutinefunction, markcoroutinefunction, sync_to_async | ||||
|  | ||||
|  | ||||
| class RemovedInDjango50Warning(DeprecationWarning): | ||||
|     pass | ||||
|  | ||||
|  | ||||
| class RemovedInDjango51Warning(PendingDeprecationWarning): | ||||
|     pass | ||||
|  | ||||
|  | ||||
| RemovedInNextVersionWarning = RemovedInDjango50Warning | ||||
| RemovedAfterNextVersionWarning = RemovedInDjango51Warning | ||||
|  | ||||
|  | ||||
| class warn_about_renamed_method: | ||||
|     def __init__( | ||||
|         self, class_name, old_method_name, new_method_name, deprecation_warning | ||||
|     ): | ||||
|         self.class_name = class_name | ||||
|         self.old_method_name = old_method_name | ||||
|         self.new_method_name = new_method_name | ||||
|         self.deprecation_warning = deprecation_warning | ||||
|  | ||||
|     def __call__(self, f): | ||||
|         def wrapper(*args, **kwargs): | ||||
|             warnings.warn( | ||||
|                 "`%s.%s` is deprecated, use `%s` instead." | ||||
|                 % (self.class_name, self.old_method_name, self.new_method_name), | ||||
|                 self.deprecation_warning, | ||||
|                 2, | ||||
|             ) | ||||
|             return f(*args, **kwargs) | ||||
|  | ||||
|         return wrapper | ||||
|  | ||||
|  | ||||
| class RenameMethodsBase(type): | ||||
|     """ | ||||
|     Handles the deprecation paths when renaming a method. | ||||
|  | ||||
|     It does the following: | ||||
|         1) Define the new method if missing and complain about it. | ||||
|         2) Define the old method if missing. | ||||
|         3) Complain whenever an old method is called. | ||||
|  | ||||
|     See #15363 for more details. | ||||
|     """ | ||||
|  | ||||
|     renamed_methods = () | ||||
|  | ||||
|     def __new__(cls, name, bases, attrs): | ||||
|         new_class = super().__new__(cls, name, bases, attrs) | ||||
|  | ||||
|         for base in inspect.getmro(new_class): | ||||
|             class_name = base.__name__ | ||||
|             for renamed_method in cls.renamed_methods: | ||||
|                 old_method_name = renamed_method[0] | ||||
|                 old_method = base.__dict__.get(old_method_name) | ||||
|                 new_method_name = renamed_method[1] | ||||
|                 new_method = base.__dict__.get(new_method_name) | ||||
|                 deprecation_warning = renamed_method[2] | ||||
|                 wrapper = warn_about_renamed_method(class_name, *renamed_method) | ||||
|  | ||||
|                 # Define the new method if missing and complain about it | ||||
|                 if not new_method and old_method: | ||||
|                     warnings.warn( | ||||
|                         "`%s.%s` method should be renamed `%s`." | ||||
|                         % (class_name, old_method_name, new_method_name), | ||||
|                         deprecation_warning, | ||||
|                         2, | ||||
|                     ) | ||||
|                     setattr(base, new_method_name, old_method) | ||||
|                     setattr(base, old_method_name, wrapper(old_method)) | ||||
|  | ||||
|                 # Define the old method as a wrapped call to the new method. | ||||
|                 if not old_method and new_method: | ||||
|                     setattr(base, old_method_name, wrapper(new_method)) | ||||
|  | ||||
|         return new_class | ||||
|  | ||||
|  | ||||
| class DeprecationInstanceCheck(type): | ||||
|     def __instancecheck__(self, instance): | ||||
|         warnings.warn( | ||||
|             "`%s` is deprecated, use `%s` instead." % (self.__name__, self.alternative), | ||||
|             self.deprecation_warning, | ||||
|             2, | ||||
|         ) | ||||
|         return super().__instancecheck__(instance) | ||||
|  | ||||
|  | ||||
| class MiddlewareMixin: | ||||
|     sync_capable = True | ||||
|     async_capable = True | ||||
|  | ||||
|     def __init__(self, get_response): | ||||
|         if get_response is None: | ||||
|             raise ValueError("get_response must be provided.") | ||||
|         self.get_response = get_response | ||||
|         self._async_check() | ||||
|         super().__init__() | ||||
|  | ||||
|     def __repr__(self): | ||||
|         return "<%s get_response=%s>" % ( | ||||
|             self.__class__.__qualname__, | ||||
|             getattr( | ||||
|                 self.get_response, | ||||
|                 "__qualname__", | ||||
|                 self.get_response.__class__.__name__, | ||||
|             ), | ||||
|         ) | ||||
|  | ||||
|     def _async_check(self): | ||||
|         """ | ||||
|         If get_response is a coroutine function, turns us into async mode so | ||||
|         a thread is not consumed during a whole request. | ||||
|         """ | ||||
|         if iscoroutinefunction(self.get_response): | ||||
|             # Mark the class as async-capable, but do the actual switch | ||||
|             # inside __call__ to avoid swapping out dunder methods | ||||
|             markcoroutinefunction(self) | ||||
|  | ||||
|     def __call__(self, request): | ||||
|         # Exit out to async mode, if needed | ||||
|         if iscoroutinefunction(self): | ||||
|             return self.__acall__(request) | ||||
|         response = None | ||||
|         if hasattr(self, "process_request"): | ||||
|             response = self.process_request(request) | ||||
|         response = response or self.get_response(request) | ||||
|         if hasattr(self, "process_response"): | ||||
|             response = self.process_response(request, response) | ||||
|         return response | ||||
|  | ||||
|     async def __acall__(self, request): | ||||
|         """ | ||||
|         Async version of __call__ that is swapped in when an async request | ||||
|         is running. | ||||
|         """ | ||||
|         response = None | ||||
|         if hasattr(self, "process_request"): | ||||
|             response = await sync_to_async( | ||||
|                 self.process_request, | ||||
|                 thread_sensitive=True, | ||||
|             )(request) | ||||
|         response = response or await self.get_response(request) | ||||
|         if hasattr(self, "process_response"): | ||||
|             response = await sync_to_async( | ||||
|                 self.process_response, | ||||
|                 thread_sensitive=True, | ||||
|             )(request, response) | ||||
|         return response | ||||
| @ -0,0 +1,46 @@ | ||||
| import datetime | ||||
|  | ||||
|  | ||||
| def _get_duration_components(duration): | ||||
|     days = duration.days | ||||
|     seconds = duration.seconds | ||||
|     microseconds = duration.microseconds | ||||
|  | ||||
|     minutes = seconds // 60 | ||||
|     seconds %= 60 | ||||
|  | ||||
|     hours = minutes // 60 | ||||
|     minutes %= 60 | ||||
|  | ||||
|     return days, hours, minutes, seconds, microseconds | ||||
|  | ||||
|  | ||||
| def duration_string(duration): | ||||
|     """Version of str(timedelta) which is not English specific.""" | ||||
|     days, hours, minutes, seconds, microseconds = _get_duration_components(duration) | ||||
|  | ||||
|     string = "{:02d}:{:02d}:{:02d}".format(hours, minutes, seconds) | ||||
|     if days: | ||||
|         string = "{} ".format(days) + string | ||||
|     if microseconds: | ||||
|         string += ".{:06d}".format(microseconds) | ||||
|  | ||||
|     return string | ||||
|  | ||||
|  | ||||
| def duration_iso_string(duration): | ||||
|     if duration < datetime.timedelta(0): | ||||
|         sign = "-" | ||||
|         duration *= -1 | ||||
|     else: | ||||
|         sign = "" | ||||
|  | ||||
|     days, hours, minutes, seconds, microseconds = _get_duration_components(duration) | ||||
|     ms = ".{:06d}".format(microseconds) if microseconds else "" | ||||
|     return "{}P{}DT{:02d}H{:02d}M{:02d}{}S".format( | ||||
|         sign, days, hours, minutes, seconds, ms | ||||
|     ) | ||||
|  | ||||
|  | ||||
| def duration_microseconds(delta): | ||||
|     return (24 * 60 * 60 * delta.days + delta.seconds) * 1000000 + delta.microseconds | ||||
							
								
								
									
										265
									
								
								srcs/.venv/lib/python3.11/site-packages/django/utils/encoding.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										265
									
								
								srcs/.venv/lib/python3.11/site-packages/django/utils/encoding.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,265 @@ | ||||
| import codecs | ||||
| import datetime | ||||
| import locale | ||||
| from decimal import Decimal | ||||
| from urllib.parse import quote | ||||
|  | ||||
| from django.utils.functional import Promise | ||||
|  | ||||
|  | ||||
| class DjangoUnicodeDecodeError(UnicodeDecodeError): | ||||
|     def __init__(self, obj, *args): | ||||
|         self.obj = obj | ||||
|         super().__init__(*args) | ||||
|  | ||||
|     def __str__(self): | ||||
|         return "%s. You passed in %r (%s)" % ( | ||||
|             super().__str__(), | ||||
|             self.obj, | ||||
|             type(self.obj), | ||||
|         ) | ||||
|  | ||||
|  | ||||
| def smart_str(s, encoding="utf-8", strings_only=False, errors="strict"): | ||||
|     """ | ||||
|     Return a string representing 's'. Treat bytestrings using the 'encoding' | ||||
|     codec. | ||||
|  | ||||
|     If strings_only is True, don't convert (some) non-string-like objects. | ||||
|     """ | ||||
|     if isinstance(s, Promise): | ||||
|         # The input is the result of a gettext_lazy() call. | ||||
|         return s | ||||
|     return force_str(s, encoding, strings_only, errors) | ||||
|  | ||||
|  | ||||
| _PROTECTED_TYPES = ( | ||||
|     type(None), | ||||
|     int, | ||||
|     float, | ||||
|     Decimal, | ||||
|     datetime.datetime, | ||||
|     datetime.date, | ||||
|     datetime.time, | ||||
| ) | ||||
|  | ||||
|  | ||||
| def is_protected_type(obj): | ||||
|     """Determine if the object instance is of a protected type. | ||||
|  | ||||
|     Objects of protected types are preserved as-is when passed to | ||||
|     force_str(strings_only=True). | ||||
|     """ | ||||
|     return isinstance(obj, _PROTECTED_TYPES) | ||||
|  | ||||
|  | ||||
| def force_str(s, encoding="utf-8", strings_only=False, errors="strict"): | ||||
|     """ | ||||
|     Similar to smart_str(), except that lazy instances are resolved to | ||||
|     strings, rather than kept as lazy objects. | ||||
|  | ||||
|     If strings_only is True, don't convert (some) non-string-like objects. | ||||
|     """ | ||||
|     # Handle the common case first for performance reasons. | ||||
|     if issubclass(type(s), str): | ||||
|         return s | ||||
|     if strings_only and is_protected_type(s): | ||||
|         return s | ||||
|     try: | ||||
|         if isinstance(s, bytes): | ||||
|             s = str(s, encoding, errors) | ||||
|         else: | ||||
|             s = str(s) | ||||
|     except UnicodeDecodeError as e: | ||||
|         raise DjangoUnicodeDecodeError(s, *e.args) | ||||
|     return s | ||||
|  | ||||
|  | ||||
| def smart_bytes(s, encoding="utf-8", strings_only=False, errors="strict"): | ||||
|     """ | ||||
|     Return a bytestring version of 's', encoded as specified in 'encoding'. | ||||
|  | ||||
|     If strings_only is True, don't convert (some) non-string-like objects. | ||||
|     """ | ||||
|     if isinstance(s, Promise): | ||||
|         # The input is the result of a gettext_lazy() call. | ||||
|         return s | ||||
|     return force_bytes(s, encoding, strings_only, errors) | ||||
|  | ||||
|  | ||||
| def force_bytes(s, encoding="utf-8", strings_only=False, errors="strict"): | ||||
|     """ | ||||
|     Similar to smart_bytes, except that lazy instances are resolved to | ||||
|     strings, rather than kept as lazy objects. | ||||
|  | ||||
|     If strings_only is True, don't convert (some) non-string-like objects. | ||||
|     """ | ||||
|     # Handle the common case first for performance reasons. | ||||
|     if isinstance(s, bytes): | ||||
|         if encoding == "utf-8": | ||||
|             return s | ||||
|         else: | ||||
|             return s.decode("utf-8", errors).encode(encoding, errors) | ||||
|     if strings_only and is_protected_type(s): | ||||
|         return s | ||||
|     if isinstance(s, memoryview): | ||||
|         return bytes(s) | ||||
|     return str(s).encode(encoding, errors) | ||||
|  | ||||
|  | ||||
| def iri_to_uri(iri): | ||||
|     """ | ||||
|     Convert an Internationalized Resource Identifier (IRI) portion to a URI | ||||
|     portion that is suitable for inclusion in a URL. | ||||
|  | ||||
|     This is the algorithm from RFC 3987 Section 3.1, slightly simplified since | ||||
|     the input is assumed to be a string rather than an arbitrary byte stream. | ||||
|  | ||||
|     Take an IRI (string or UTF-8 bytes, e.g. '/I ♥ Django/' or | ||||
|     b'/I \xe2\x99\xa5 Django/') and return a string containing the encoded | ||||
|     result with ASCII chars only (e.g. '/I%20%E2%99%A5%20Django/'). | ||||
|     """ | ||||
|     # The list of safe characters here is constructed from the "reserved" and | ||||
|     # "unreserved" characters specified in RFC 3986 Sections 2.2 and 2.3: | ||||
|     #     reserved    = gen-delims / sub-delims | ||||
|     #     gen-delims  = ":" / "/" / "?" / "#" / "[" / "]" / "@" | ||||
|     #     sub-delims  = "!" / "$" / "&" / "'" / "(" / ")" | ||||
|     #                   / "*" / "+" / "," / ";" / "=" | ||||
|     #     unreserved  = ALPHA / DIGIT / "-" / "." / "_" / "~" | ||||
|     # Of the unreserved characters, urllib.parse.quote() already considers all | ||||
|     # but the ~ safe. | ||||
|     # The % character is also added to the list of safe characters here, as the | ||||
|     # end of RFC 3987 Section 3.1 specifically mentions that % must not be | ||||
|     # converted. | ||||
|     if iri is None: | ||||
|         return iri | ||||
|     elif isinstance(iri, Promise): | ||||
|         iri = str(iri) | ||||
|     return quote(iri, safe="/#%[]=:;$&()+,!?*@'~") | ||||
|  | ||||
|  | ||||
| # List of byte values that uri_to_iri() decodes from percent encoding. | ||||
| # First, the unreserved characters from RFC 3986: | ||||
| _ascii_ranges = [[45, 46, 95, 126], range(65, 91), range(97, 123)] | ||||
| _hextobyte = { | ||||
|     (fmt % char).encode(): bytes((char,)) | ||||
|     for ascii_range in _ascii_ranges | ||||
|     for char in ascii_range | ||||
|     for fmt in ["%02x", "%02X"] | ||||
| } | ||||
| # And then everything above 128, because bytes ≥ 128 are part of multibyte | ||||
| # Unicode characters. | ||||
| _hexdig = "0123456789ABCDEFabcdef" | ||||
| _hextobyte.update( | ||||
|     {(a + b).encode(): bytes.fromhex(a + b) for a in _hexdig[8:] for b in _hexdig} | ||||
| ) | ||||
|  | ||||
|  | ||||
| def uri_to_iri(uri): | ||||
|     """ | ||||
|     Convert a Uniform Resource Identifier(URI) into an Internationalized | ||||
|     Resource Identifier(IRI). | ||||
|  | ||||
|     This is the algorithm from RFC 3987 Section 3.2, excluding step 4. | ||||
|  | ||||
|     Take an URI in ASCII bytes (e.g. '/I%20%E2%99%A5%20Django/') and return | ||||
|     a string containing the encoded result (e.g. '/I%20♥%20Django/'). | ||||
|     """ | ||||
|     if uri is None: | ||||
|         return uri | ||||
|     uri = force_bytes(uri) | ||||
|     # Fast selective unquote: First, split on '%' and then starting with the | ||||
|     # second block, decode the first 2 bytes if they represent a hex code to | ||||
|     # decode. The rest of the block is the part after '%AB', not containing | ||||
|     # any '%'. Add that to the output without further processing. | ||||
|     bits = uri.split(b"%") | ||||
|     if len(bits) == 1: | ||||
|         iri = uri | ||||
|     else: | ||||
|         parts = [bits[0]] | ||||
|         append = parts.append | ||||
|         hextobyte = _hextobyte | ||||
|         for item in bits[1:]: | ||||
|             hex = item[:2] | ||||
|             if hex in hextobyte: | ||||
|                 append(hextobyte[item[:2]]) | ||||
|                 append(item[2:]) | ||||
|             else: | ||||
|                 append(b"%") | ||||
|                 append(item) | ||||
|         iri = b"".join(parts) | ||||
|     return repercent_broken_unicode(iri).decode() | ||||
|  | ||||
|  | ||||
| def escape_uri_path(path): | ||||
|     """ | ||||
|     Escape the unsafe characters from the path portion of a Uniform Resource | ||||
|     Identifier (URI). | ||||
|     """ | ||||
|     # These are the "reserved" and "unreserved" characters specified in RFC | ||||
|     # 3986 Sections 2.2 and 2.3: | ||||
|     #   reserved    = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | "," | ||||
|     #   unreserved  = alphanum | mark | ||||
|     #   mark        = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")" | ||||
|     # The list of safe characters here is constructed subtracting ";", "=", | ||||
|     # and "?" according to RFC 3986 Section 3.3. | ||||
|     # The reason for not subtracting and escaping "/" is that we are escaping | ||||
|     # the entire path, not a path segment. | ||||
|     return quote(path, safe="/:@&+$,-_.!~*'()") | ||||
|  | ||||
|  | ||||
| def punycode(domain): | ||||
|     """Return the Punycode of the given domain if it's non-ASCII.""" | ||||
|     return domain.encode("idna").decode("ascii") | ||||
|  | ||||
|  | ||||
| def repercent_broken_unicode(path): | ||||
|     """ | ||||
|     As per RFC 3987 Section 3.2, step three of converting a URI into an IRI, | ||||
|     repercent-encode any octet produced that is not part of a strictly legal | ||||
|     UTF-8 octet sequence. | ||||
|     """ | ||||
|     changed_parts = [] | ||||
|     while True: | ||||
|         try: | ||||
|             path.decode() | ||||
|         except UnicodeDecodeError as e: | ||||
|             # CVE-2019-14235: A recursion shouldn't be used since the exception | ||||
|             # handling uses massive amounts of memory | ||||
|             repercent = quote(path[e.start : e.end], safe=b"/#%[]=:;$&()+,!?*@'~") | ||||
|             changed_parts.append(path[: e.start] + repercent.encode()) | ||||
|             path = path[e.end :] | ||||
|         else: | ||||
|             return b"".join(changed_parts) + path | ||||
|  | ||||
|  | ||||
| def filepath_to_uri(path): | ||||
|     """Convert a file system path to a URI portion that is suitable for | ||||
|     inclusion in a URL. | ||||
|  | ||||
|     Encode certain chars that would normally be recognized as special chars | ||||
|     for URIs. Do not encode the ' character, as it is a valid character | ||||
|     within URIs. See the encodeURIComponent() JavaScript function for details. | ||||
|     """ | ||||
|     if path is None: | ||||
|         return path | ||||
|     # I know about `os.sep` and `os.altsep` but I want to leave | ||||
|     # some flexibility for hardcoding separators. | ||||
|     return quote(str(path).replace("\\", "/"), safe="/~!*()'") | ||||
|  | ||||
|  | ||||
| def get_system_encoding(): | ||||
|     """ | ||||
|     The encoding for the character type functions. Fallback to 'ascii' if the | ||||
|     #encoding is unsupported by Python or could not be determined. See tickets | ||||
|     #10335 and #5846. | ||||
|     """ | ||||
|     try: | ||||
|         encoding = locale.getlocale()[1] or "ascii" | ||||
|         codecs.lookup(encoding) | ||||
|     except Exception: | ||||
|         encoding = "ascii" | ||||
|     return encoding | ||||
|  | ||||
|  | ||||
| DEFAULT_LOCALE_ENCODING = get_system_encoding() | ||||
| @ -0,0 +1,447 @@ | ||||
| """ | ||||
| Syndication feed generation library -- used for generating RSS, etc. | ||||
|  | ||||
| Sample usage: | ||||
|  | ||||
| >>> from django.utils import feedgenerator | ||||
| >>> feed = feedgenerator.Rss201rev2Feed( | ||||
| ...     title="Poynter E-Media Tidbits", | ||||
| ...     link="http://www.poynter.org/column.asp?id=31", | ||||
| ...     description="A group blog by the sharpest minds in online journalism.", | ||||
| ...     language="en", | ||||
| ... ) | ||||
| >>> feed.add_item( | ||||
| ...     title="Hello", | ||||
| ...     link="http://www.holovaty.com/test/", | ||||
| ...     description="Testing." | ||||
| ... ) | ||||
| >>> with open('test.rss', 'w') as fp: | ||||
| ...     feed.write(fp, 'utf-8') | ||||
|  | ||||
| For definitions of the different versions of RSS, see: | ||||
| https://web.archive.org/web/20110718035220/http://diveintomark.org/archives/2004/02/04/incompatible-rss | ||||
| """ | ||||
| import datetime | ||||
| import email | ||||
| from io import StringIO | ||||
| from urllib.parse import urlparse | ||||
|  | ||||
| from django.utils.encoding import iri_to_uri | ||||
| from django.utils.xmlutils import SimplerXMLGenerator | ||||
|  | ||||
|  | ||||
| def rfc2822_date(date): | ||||
|     if not isinstance(date, datetime.datetime): | ||||
|         date = datetime.datetime.combine(date, datetime.time()) | ||||
|     return email.utils.format_datetime(date) | ||||
|  | ||||
|  | ||||
| def rfc3339_date(date): | ||||
|     if not isinstance(date, datetime.datetime): | ||||
|         date = datetime.datetime.combine(date, datetime.time()) | ||||
|     return date.isoformat() + ("Z" if date.utcoffset() is None else "") | ||||
|  | ||||
|  | ||||
| def get_tag_uri(url, date): | ||||
|     """ | ||||
|     Create a TagURI. | ||||
|  | ||||
|     See | ||||
|     https://web.archive.org/web/20110514113830/http://diveintomark.org/archives/2004/05/28/howto-atom-id | ||||
|     """ | ||||
|     bits = urlparse(url) | ||||
|     d = "" | ||||
|     if date is not None: | ||||
|         d = ",%s" % date.strftime("%Y-%m-%d") | ||||
|     return "tag:%s%s:%s/%s" % (bits.hostname, d, bits.path, bits.fragment) | ||||
|  | ||||
|  | ||||
| class SyndicationFeed: | ||||
|     "Base class for all syndication feeds. Subclasses should provide write()" | ||||
|  | ||||
|     def __init__( | ||||
|         self, | ||||
|         title, | ||||
|         link, | ||||
|         description, | ||||
|         language=None, | ||||
|         author_email=None, | ||||
|         author_name=None, | ||||
|         author_link=None, | ||||
|         subtitle=None, | ||||
|         categories=None, | ||||
|         feed_url=None, | ||||
|         feed_copyright=None, | ||||
|         feed_guid=None, | ||||
|         ttl=None, | ||||
|         **kwargs, | ||||
|     ): | ||||
|         def to_str(s): | ||||
|             return str(s) if s is not None else s | ||||
|  | ||||
|         categories = categories and [str(c) for c in categories] | ||||
|         self.feed = { | ||||
|             "title": to_str(title), | ||||
|             "link": iri_to_uri(link), | ||||
|             "description": to_str(description), | ||||
|             "language": to_str(language), | ||||
|             "author_email": to_str(author_email), | ||||
|             "author_name": to_str(author_name), | ||||
|             "author_link": iri_to_uri(author_link), | ||||
|             "subtitle": to_str(subtitle), | ||||
|             "categories": categories or (), | ||||
|             "feed_url": iri_to_uri(feed_url), | ||||
|             "feed_copyright": to_str(feed_copyright), | ||||
|             "id": feed_guid or link, | ||||
|             "ttl": to_str(ttl), | ||||
|             **kwargs, | ||||
|         } | ||||
|         self.items = [] | ||||
|  | ||||
|     def add_item( | ||||
|         self, | ||||
|         title, | ||||
|         link, | ||||
|         description, | ||||
|         author_email=None, | ||||
|         author_name=None, | ||||
|         author_link=None, | ||||
|         pubdate=None, | ||||
|         comments=None, | ||||
|         unique_id=None, | ||||
|         unique_id_is_permalink=None, | ||||
|         categories=(), | ||||
|         item_copyright=None, | ||||
|         ttl=None, | ||||
|         updateddate=None, | ||||
|         enclosures=None, | ||||
|         **kwargs, | ||||
|     ): | ||||
|         """ | ||||
|         Add an item to the feed. All args are expected to be strings except | ||||
|         pubdate and updateddate, which are datetime.datetime objects, and | ||||
|         enclosures, which is an iterable of instances of the Enclosure class. | ||||
|         """ | ||||
|  | ||||
|         def to_str(s): | ||||
|             return str(s) if s is not None else s | ||||
|  | ||||
|         categories = categories and [to_str(c) for c in categories] | ||||
|         self.items.append( | ||||
|             { | ||||
|                 "title": to_str(title), | ||||
|                 "link": iri_to_uri(link), | ||||
|                 "description": to_str(description), | ||||
|                 "author_email": to_str(author_email), | ||||
|                 "author_name": to_str(author_name), | ||||
|                 "author_link": iri_to_uri(author_link), | ||||
|                 "pubdate": pubdate, | ||||
|                 "updateddate": updateddate, | ||||
|                 "comments": to_str(comments), | ||||
|                 "unique_id": to_str(unique_id), | ||||
|                 "unique_id_is_permalink": unique_id_is_permalink, | ||||
|                 "enclosures": enclosures or (), | ||||
|                 "categories": categories or (), | ||||
|                 "item_copyright": to_str(item_copyright), | ||||
|                 "ttl": to_str(ttl), | ||||
|                 **kwargs, | ||||
|             } | ||||
|         ) | ||||
|  | ||||
|     def num_items(self): | ||||
|         return len(self.items) | ||||
|  | ||||
|     def root_attributes(self): | ||||
|         """ | ||||
|         Return extra attributes to place on the root (i.e. feed/channel) element. | ||||
|         Called from write(). | ||||
|         """ | ||||
|         return {} | ||||
|  | ||||
|     def add_root_elements(self, handler): | ||||
|         """ | ||||
|         Add elements in the root (i.e. feed/channel) element. Called | ||||
|         from write(). | ||||
|         """ | ||||
|         pass | ||||
|  | ||||
|     def item_attributes(self, item): | ||||
|         """ | ||||
|         Return extra attributes to place on each item (i.e. item/entry) element. | ||||
|         """ | ||||
|         return {} | ||||
|  | ||||
|     def add_item_elements(self, handler, item): | ||||
|         """ | ||||
|         Add elements on each item (i.e. item/entry) element. | ||||
|         """ | ||||
|         pass | ||||
|  | ||||
|     def write(self, outfile, encoding): | ||||
|         """ | ||||
|         Output the feed in the given encoding to outfile, which is a file-like | ||||
|         object. Subclasses should override this. | ||||
|         """ | ||||
|         raise NotImplementedError( | ||||
|             "subclasses of SyndicationFeed must provide a write() method" | ||||
|         ) | ||||
|  | ||||
|     def writeString(self, encoding): | ||||
|         """ | ||||
|         Return the feed in the given encoding as a string. | ||||
|         """ | ||||
|         s = StringIO() | ||||
|         self.write(s, encoding) | ||||
|         return s.getvalue() | ||||
|  | ||||
|     def latest_post_date(self): | ||||
|         """ | ||||
|         Return the latest item's pubdate or updateddate. If no items | ||||
|         have either of these attributes this return the current UTC date/time. | ||||
|         """ | ||||
|         latest_date = None | ||||
|         date_keys = ("updateddate", "pubdate") | ||||
|  | ||||
|         for item in self.items: | ||||
|             for date_key in date_keys: | ||||
|                 item_date = item.get(date_key) | ||||
|                 if item_date: | ||||
|                     if latest_date is None or item_date > latest_date: | ||||
|                         latest_date = item_date | ||||
|  | ||||
|         return latest_date or datetime.datetime.now(tz=datetime.timezone.utc) | ||||
|  | ||||
|  | ||||
| class Enclosure: | ||||
|     """An RSS enclosure""" | ||||
|  | ||||
|     def __init__(self, url, length, mime_type): | ||||
|         "All args are expected to be strings" | ||||
|         self.length, self.mime_type = length, mime_type | ||||
|         self.url = iri_to_uri(url) | ||||
|  | ||||
|  | ||||
| class RssFeed(SyndicationFeed): | ||||
|     content_type = "application/rss+xml; charset=utf-8" | ||||
|  | ||||
|     def write(self, outfile, encoding): | ||||
|         handler = SimplerXMLGenerator(outfile, encoding, short_empty_elements=True) | ||||
|         handler.startDocument() | ||||
|         handler.startElement("rss", self.rss_attributes()) | ||||
|         handler.startElement("channel", self.root_attributes()) | ||||
|         self.add_root_elements(handler) | ||||
|         self.write_items(handler) | ||||
|         self.endChannelElement(handler) | ||||
|         handler.endElement("rss") | ||||
|  | ||||
|     def rss_attributes(self): | ||||
|         return { | ||||
|             "version": self._version, | ||||
|             "xmlns:atom": "http://www.w3.org/2005/Atom", | ||||
|         } | ||||
|  | ||||
|     def write_items(self, handler): | ||||
|         for item in self.items: | ||||
|             handler.startElement("item", self.item_attributes(item)) | ||||
|             self.add_item_elements(handler, item) | ||||
|             handler.endElement("item") | ||||
|  | ||||
|     def add_root_elements(self, handler): | ||||
|         handler.addQuickElement("title", self.feed["title"]) | ||||
|         handler.addQuickElement("link", self.feed["link"]) | ||||
|         handler.addQuickElement("description", self.feed["description"]) | ||||
|         if self.feed["feed_url"] is not None: | ||||
|             handler.addQuickElement( | ||||
|                 "atom:link", None, {"rel": "self", "href": self.feed["feed_url"]} | ||||
|             ) | ||||
|         if self.feed["language"] is not None: | ||||
|             handler.addQuickElement("language", self.feed["language"]) | ||||
|         for cat in self.feed["categories"]: | ||||
|             handler.addQuickElement("category", cat) | ||||
|         if self.feed["feed_copyright"] is not None: | ||||
|             handler.addQuickElement("copyright", self.feed["feed_copyright"]) | ||||
|         handler.addQuickElement("lastBuildDate", rfc2822_date(self.latest_post_date())) | ||||
|         if self.feed["ttl"] is not None: | ||||
|             handler.addQuickElement("ttl", self.feed["ttl"]) | ||||
|  | ||||
|     def endChannelElement(self, handler): | ||||
|         handler.endElement("channel") | ||||
|  | ||||
|  | ||||
| class RssUserland091Feed(RssFeed): | ||||
|     _version = "0.91" | ||||
|  | ||||
|     def add_item_elements(self, handler, item): | ||||
|         handler.addQuickElement("title", item["title"]) | ||||
|         handler.addQuickElement("link", item["link"]) | ||||
|         if item["description"] is not None: | ||||
|             handler.addQuickElement("description", item["description"]) | ||||
|  | ||||
|  | ||||
| class Rss201rev2Feed(RssFeed): | ||||
|     # Spec: https://cyber.harvard.edu/rss/rss.html | ||||
|     _version = "2.0" | ||||
|  | ||||
|     def add_item_elements(self, handler, item): | ||||
|         handler.addQuickElement("title", item["title"]) | ||||
|         handler.addQuickElement("link", item["link"]) | ||||
|         if item["description"] is not None: | ||||
|             handler.addQuickElement("description", item["description"]) | ||||
|  | ||||
|         # Author information. | ||||
|         if item["author_name"] and item["author_email"]: | ||||
|             handler.addQuickElement( | ||||
|                 "author", "%s (%s)" % (item["author_email"], item["author_name"]) | ||||
|             ) | ||||
|         elif item["author_email"]: | ||||
|             handler.addQuickElement("author", item["author_email"]) | ||||
|         elif item["author_name"]: | ||||
|             handler.addQuickElement( | ||||
|                 "dc:creator", | ||||
|                 item["author_name"], | ||||
|                 {"xmlns:dc": "http://purl.org/dc/elements/1.1/"}, | ||||
|             ) | ||||
|  | ||||
|         if item["pubdate"] is not None: | ||||
|             handler.addQuickElement("pubDate", rfc2822_date(item["pubdate"])) | ||||
|         if item["comments"] is not None: | ||||
|             handler.addQuickElement("comments", item["comments"]) | ||||
|         if item["unique_id"] is not None: | ||||
|             guid_attrs = {} | ||||
|             if isinstance(item.get("unique_id_is_permalink"), bool): | ||||
|                 guid_attrs["isPermaLink"] = str(item["unique_id_is_permalink"]).lower() | ||||
|             handler.addQuickElement("guid", item["unique_id"], guid_attrs) | ||||
|         if item["ttl"] is not None: | ||||
|             handler.addQuickElement("ttl", item["ttl"]) | ||||
|  | ||||
|         # Enclosure. | ||||
|         if item["enclosures"]: | ||||
|             enclosures = list(item["enclosures"]) | ||||
|             if len(enclosures) > 1: | ||||
|                 raise ValueError( | ||||
|                     "RSS feed items may only have one enclosure, see " | ||||
|                     "http://www.rssboard.org/rss-profile#element-channel-item-enclosure" | ||||
|                 ) | ||||
|             enclosure = enclosures[0] | ||||
|             handler.addQuickElement( | ||||
|                 "enclosure", | ||||
|                 "", | ||||
|                 { | ||||
|                     "url": enclosure.url, | ||||
|                     "length": enclosure.length, | ||||
|                     "type": enclosure.mime_type, | ||||
|                 }, | ||||
|             ) | ||||
|  | ||||
|         # Categories. | ||||
|         for cat in item["categories"]: | ||||
|             handler.addQuickElement("category", cat) | ||||
|  | ||||
|  | ||||
| class Atom1Feed(SyndicationFeed): | ||||
|     # Spec: https://tools.ietf.org/html/rfc4287 | ||||
|     content_type = "application/atom+xml; charset=utf-8" | ||||
|     ns = "http://www.w3.org/2005/Atom" | ||||
|  | ||||
|     def write(self, outfile, encoding): | ||||
|         handler = SimplerXMLGenerator(outfile, encoding, short_empty_elements=True) | ||||
|         handler.startDocument() | ||||
|         handler.startElement("feed", self.root_attributes()) | ||||
|         self.add_root_elements(handler) | ||||
|         self.write_items(handler) | ||||
|         handler.endElement("feed") | ||||
|  | ||||
|     def root_attributes(self): | ||||
|         if self.feed["language"] is not None: | ||||
|             return {"xmlns": self.ns, "xml:lang": self.feed["language"]} | ||||
|         else: | ||||
|             return {"xmlns": self.ns} | ||||
|  | ||||
|     def add_root_elements(self, handler): | ||||
|         handler.addQuickElement("title", self.feed["title"]) | ||||
|         handler.addQuickElement( | ||||
|             "link", "", {"rel": "alternate", "href": self.feed["link"]} | ||||
|         ) | ||||
|         if self.feed["feed_url"] is not None: | ||||
|             handler.addQuickElement( | ||||
|                 "link", "", {"rel": "self", "href": self.feed["feed_url"]} | ||||
|             ) | ||||
|         handler.addQuickElement("id", self.feed["id"]) | ||||
|         handler.addQuickElement("updated", rfc3339_date(self.latest_post_date())) | ||||
|         if self.feed["author_name"] is not None: | ||||
|             handler.startElement("author", {}) | ||||
|             handler.addQuickElement("name", self.feed["author_name"]) | ||||
|             if self.feed["author_email"] is not None: | ||||
|                 handler.addQuickElement("email", self.feed["author_email"]) | ||||
|             if self.feed["author_link"] is not None: | ||||
|                 handler.addQuickElement("uri", self.feed["author_link"]) | ||||
|             handler.endElement("author") | ||||
|         if self.feed["subtitle"] is not None: | ||||
|             handler.addQuickElement("subtitle", self.feed["subtitle"]) | ||||
|         for cat in self.feed["categories"]: | ||||
|             handler.addQuickElement("category", "", {"term": cat}) | ||||
|         if self.feed["feed_copyright"] is not None: | ||||
|             handler.addQuickElement("rights", self.feed["feed_copyright"]) | ||||
|  | ||||
|     def write_items(self, handler): | ||||
|         for item in self.items: | ||||
|             handler.startElement("entry", self.item_attributes(item)) | ||||
|             self.add_item_elements(handler, item) | ||||
|             handler.endElement("entry") | ||||
|  | ||||
|     def add_item_elements(self, handler, item): | ||||
|         handler.addQuickElement("title", item["title"]) | ||||
|         handler.addQuickElement("link", "", {"href": item["link"], "rel": "alternate"}) | ||||
|  | ||||
|         if item["pubdate"] is not None: | ||||
|             handler.addQuickElement("published", rfc3339_date(item["pubdate"])) | ||||
|  | ||||
|         if item["updateddate"] is not None: | ||||
|             handler.addQuickElement("updated", rfc3339_date(item["updateddate"])) | ||||
|  | ||||
|         # Author information. | ||||
|         if item["author_name"] is not None: | ||||
|             handler.startElement("author", {}) | ||||
|             handler.addQuickElement("name", item["author_name"]) | ||||
|             if item["author_email"] is not None: | ||||
|                 handler.addQuickElement("email", item["author_email"]) | ||||
|             if item["author_link"] is not None: | ||||
|                 handler.addQuickElement("uri", item["author_link"]) | ||||
|             handler.endElement("author") | ||||
|  | ||||
|         # Unique ID. | ||||
|         if item["unique_id"] is not None: | ||||
|             unique_id = item["unique_id"] | ||||
|         else: | ||||
|             unique_id = get_tag_uri(item["link"], item["pubdate"]) | ||||
|         handler.addQuickElement("id", unique_id) | ||||
|  | ||||
|         # Summary. | ||||
|         if item["description"] is not None: | ||||
|             handler.addQuickElement("summary", item["description"], {"type": "html"}) | ||||
|  | ||||
|         # Enclosures. | ||||
|         for enclosure in item["enclosures"]: | ||||
|             handler.addQuickElement( | ||||
|                 "link", | ||||
|                 "", | ||||
|                 { | ||||
|                     "rel": "enclosure", | ||||
|                     "href": enclosure.url, | ||||
|                     "length": enclosure.length, | ||||
|                     "type": enclosure.mime_type, | ||||
|                 }, | ||||
|             ) | ||||
|  | ||||
|         # Categories. | ||||
|         for cat in item["categories"]: | ||||
|             handler.addQuickElement("category", "", {"term": cat}) | ||||
|  | ||||
|         # Rights. | ||||
|         if item["item_copyright"] is not None: | ||||
|             handler.addQuickElement("rights", item["item_copyright"]) | ||||
|  | ||||
|  | ||||
| # This isolates the decision of what the system default is, so calling code can | ||||
| # do "feedgenerator.DefaultFeed" instead of "feedgenerator.Rss201rev2Feed". | ||||
| DefaultFeed = Rss201rev2Feed | ||||
							
								
								
									
										311
									
								
								srcs/.venv/lib/python3.11/site-packages/django/utils/formats.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										311
									
								
								srcs/.venv/lib/python3.11/site-packages/django/utils/formats.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,311 @@ | ||||
| import datetime | ||||
| import decimal | ||||
| import functools | ||||
| import re | ||||
| import unicodedata | ||||
| from importlib import import_module | ||||
|  | ||||
| from django.conf import settings | ||||
| from django.utils import dateformat, numberformat | ||||
| from django.utils.functional import lazy | ||||
| from django.utils.translation import check_for_language, get_language, to_locale | ||||
|  | ||||
| # format_cache is a mapping from (format_type, lang) to the format string. | ||||
| # By using the cache, it is possible to avoid running get_format_modules | ||||
| # repeatedly. | ||||
| _format_cache = {} | ||||
| _format_modules_cache = {} | ||||
|  | ||||
| ISO_INPUT_FORMATS = { | ||||
|     "DATE_INPUT_FORMATS": ["%Y-%m-%d"], | ||||
|     "TIME_INPUT_FORMATS": ["%H:%M:%S", "%H:%M:%S.%f", "%H:%M"], | ||||
|     "DATETIME_INPUT_FORMATS": [ | ||||
|         "%Y-%m-%d %H:%M:%S", | ||||
|         "%Y-%m-%d %H:%M:%S.%f", | ||||
|         "%Y-%m-%d %H:%M", | ||||
|         "%Y-%m-%d", | ||||
|     ], | ||||
| } | ||||
|  | ||||
|  | ||||
| FORMAT_SETTINGS = frozenset( | ||||
|     [ | ||||
|         "DECIMAL_SEPARATOR", | ||||
|         "THOUSAND_SEPARATOR", | ||||
|         "NUMBER_GROUPING", | ||||
|         "FIRST_DAY_OF_WEEK", | ||||
|         "MONTH_DAY_FORMAT", | ||||
|         "TIME_FORMAT", | ||||
|         "DATE_FORMAT", | ||||
|         "DATETIME_FORMAT", | ||||
|         "SHORT_DATE_FORMAT", | ||||
|         "SHORT_DATETIME_FORMAT", | ||||
|         "YEAR_MONTH_FORMAT", | ||||
|         "DATE_INPUT_FORMATS", | ||||
|         "TIME_INPUT_FORMATS", | ||||
|         "DATETIME_INPUT_FORMATS", | ||||
|     ] | ||||
| ) | ||||
|  | ||||
|  | ||||
| def reset_format_cache(): | ||||
|     """Clear any cached formats. | ||||
|  | ||||
|     This method is provided primarily for testing purposes, | ||||
|     so that the effects of cached formats can be removed. | ||||
|     """ | ||||
|     global _format_cache, _format_modules_cache | ||||
|     _format_cache = {} | ||||
|     _format_modules_cache = {} | ||||
|  | ||||
|  | ||||
| def iter_format_modules(lang, format_module_path=None): | ||||
|     """Find format modules.""" | ||||
|     if not check_for_language(lang): | ||||
|         return | ||||
|  | ||||
|     if format_module_path is None: | ||||
|         format_module_path = settings.FORMAT_MODULE_PATH | ||||
|  | ||||
|     format_locations = [] | ||||
|     if format_module_path: | ||||
|         if isinstance(format_module_path, str): | ||||
|             format_module_path = [format_module_path] | ||||
|         for path in format_module_path: | ||||
|             format_locations.append(path + ".%s") | ||||
|     format_locations.append("django.conf.locale.%s") | ||||
|     locale = to_locale(lang) | ||||
|     locales = [locale] | ||||
|     if "_" in locale: | ||||
|         locales.append(locale.split("_")[0]) | ||||
|     for location in format_locations: | ||||
|         for loc in locales: | ||||
|             try: | ||||
|                 yield import_module("%s.formats" % (location % loc)) | ||||
|             except ImportError: | ||||
|                 pass | ||||
|  | ||||
|  | ||||
| def get_format_modules(lang=None): | ||||
|     """Return a list of the format modules found.""" | ||||
|     if lang is None: | ||||
|         lang = get_language() | ||||
|     if lang not in _format_modules_cache: | ||||
|         _format_modules_cache[lang] = list( | ||||
|             iter_format_modules(lang, settings.FORMAT_MODULE_PATH) | ||||
|         ) | ||||
|     return _format_modules_cache[lang] | ||||
|  | ||||
|  | ||||
| def get_format(format_type, lang=None, use_l10n=None): | ||||
|     """ | ||||
|     For a specific format type, return the format for the current | ||||
|     language (locale). Default to the format in the settings. | ||||
|     format_type is the name of the format, e.g. 'DATE_FORMAT'. | ||||
|  | ||||
|     If use_l10n is provided and is not None, it forces the value to | ||||
|     be localized (or not), overriding the value of settings.USE_L10N. | ||||
|     """ | ||||
|     if use_l10n is None: | ||||
|         try: | ||||
|             use_l10n = settings._USE_L10N_INTERNAL | ||||
|         except AttributeError: | ||||
|             use_l10n = settings.USE_L10N | ||||
|     if use_l10n and lang is None: | ||||
|         lang = get_language() | ||||
|     format_type = str(format_type)  # format_type may be lazy. | ||||
|     cache_key = (format_type, lang) | ||||
|     try: | ||||
|         return _format_cache[cache_key] | ||||
|     except KeyError: | ||||
|         pass | ||||
|  | ||||
|     # The requested format_type has not been cached yet. Try to find it in any | ||||
|     # of the format_modules for the given lang if l10n is enabled. If it's not | ||||
|     # there or if l10n is disabled, fall back to the project settings. | ||||
|     val = None | ||||
|     if use_l10n: | ||||
|         for module in get_format_modules(lang): | ||||
|             val = getattr(module, format_type, None) | ||||
|             if val is not None: | ||||
|                 break | ||||
|     if val is None: | ||||
|         if format_type not in FORMAT_SETTINGS: | ||||
|             return format_type | ||||
|         val = getattr(settings, format_type) | ||||
|     elif format_type in ISO_INPUT_FORMATS: | ||||
|         # If a list of input formats from one of the format_modules was | ||||
|         # retrieved, make sure the ISO_INPUT_FORMATS are in this list. | ||||
|         val = list(val) | ||||
|         for iso_input in ISO_INPUT_FORMATS.get(format_type, ()): | ||||
|             if iso_input not in val: | ||||
|                 val.append(iso_input) | ||||
|     _format_cache[cache_key] = val | ||||
|     return val | ||||
|  | ||||
|  | ||||
| get_format_lazy = lazy(get_format, str, list, tuple) | ||||
|  | ||||
|  | ||||
| def date_format(value, format=None, use_l10n=None): | ||||
|     """ | ||||
|     Format a datetime.date or datetime.datetime object using a | ||||
|     localizable format. | ||||
|  | ||||
|     If use_l10n is provided and is not None, that will force the value to | ||||
|     be localized (or not), overriding the value of settings.USE_L10N. | ||||
|     """ | ||||
|     return dateformat.format( | ||||
|         value, get_format(format or "DATE_FORMAT", use_l10n=use_l10n) | ||||
|     ) | ||||
|  | ||||
|  | ||||
| def time_format(value, format=None, use_l10n=None): | ||||
|     """ | ||||
|     Format a datetime.time object using a localizable format. | ||||
|  | ||||
|     If use_l10n is provided and is not None, it forces the value to | ||||
|     be localized (or not), overriding the value of settings.USE_L10N. | ||||
|     """ | ||||
|     return dateformat.time_format( | ||||
|         value, get_format(format or "TIME_FORMAT", use_l10n=use_l10n) | ||||
|     ) | ||||
|  | ||||
|  | ||||
| def number_format(value, decimal_pos=None, use_l10n=None, force_grouping=False): | ||||
|     """ | ||||
|     Format a numeric value using localization settings. | ||||
|  | ||||
|     If use_l10n is provided and is not None, it forces the value to | ||||
|     be localized (or not), overriding the value of settings.USE_L10N. | ||||
|     """ | ||||
|     if use_l10n is None: | ||||
|         try: | ||||
|             use_l10n = settings._USE_L10N_INTERNAL | ||||
|         except AttributeError: | ||||
|             use_l10n = settings.USE_L10N | ||||
|     lang = get_language() if use_l10n else None | ||||
|     return numberformat.format( | ||||
|         value, | ||||
|         get_format("DECIMAL_SEPARATOR", lang, use_l10n=use_l10n), | ||||
|         decimal_pos, | ||||
|         get_format("NUMBER_GROUPING", lang, use_l10n=use_l10n), | ||||
|         get_format("THOUSAND_SEPARATOR", lang, use_l10n=use_l10n), | ||||
|         force_grouping=force_grouping, | ||||
|         use_l10n=use_l10n, | ||||
|     ) | ||||
|  | ||||
|  | ||||
| def localize(value, use_l10n=None): | ||||
|     """ | ||||
|     Check if value is a localizable type (date, number...) and return it | ||||
|     formatted as a string using current locale format. | ||||
|  | ||||
|     If use_l10n is provided and is not None, it forces the value to | ||||
|     be localized (or not), overriding the value of settings.USE_L10N. | ||||
|     """ | ||||
|     if isinstance(value, str):  # Handle strings first for performance reasons. | ||||
|         return value | ||||
|     elif isinstance(value, bool):  # Make sure booleans don't get treated as numbers | ||||
|         return str(value) | ||||
|     elif isinstance(value, (decimal.Decimal, float, int)): | ||||
|         if use_l10n is False: | ||||
|             return str(value) | ||||
|         return number_format(value, use_l10n=use_l10n) | ||||
|     elif isinstance(value, datetime.datetime): | ||||
|         return date_format(value, "DATETIME_FORMAT", use_l10n=use_l10n) | ||||
|     elif isinstance(value, datetime.date): | ||||
|         return date_format(value, use_l10n=use_l10n) | ||||
|     elif isinstance(value, datetime.time): | ||||
|         return time_format(value, use_l10n=use_l10n) | ||||
|     return value | ||||
|  | ||||
|  | ||||
| def localize_input(value, default=None): | ||||
|     """ | ||||
|     Check if an input value is a localizable type and return it | ||||
|     formatted with the appropriate formatting string of the current locale. | ||||
|     """ | ||||
|     if isinstance(value, str):  # Handle strings first for performance reasons. | ||||
|         return value | ||||
|     elif isinstance(value, bool):  # Don't treat booleans as numbers. | ||||
|         return str(value) | ||||
|     elif isinstance(value, (decimal.Decimal, float, int)): | ||||
|         return number_format(value) | ||||
|     elif isinstance(value, datetime.datetime): | ||||
|         format = default or get_format("DATETIME_INPUT_FORMATS")[0] | ||||
|         format = sanitize_strftime_format(format) | ||||
|         return value.strftime(format) | ||||
|     elif isinstance(value, datetime.date): | ||||
|         format = default or get_format("DATE_INPUT_FORMATS")[0] | ||||
|         format = sanitize_strftime_format(format) | ||||
|         return value.strftime(format) | ||||
|     elif isinstance(value, datetime.time): | ||||
|         format = default or get_format("TIME_INPUT_FORMATS")[0] | ||||
|         return value.strftime(format) | ||||
|     return value | ||||
|  | ||||
|  | ||||
| @functools.lru_cache | ||||
| def sanitize_strftime_format(fmt): | ||||
|     """ | ||||
|     Ensure that certain specifiers are correctly padded with leading zeros. | ||||
|  | ||||
|     For years < 1000 specifiers %C, %F, %G, and %Y don't work as expected for | ||||
|     strftime provided by glibc on Linux as they don't pad the year or century | ||||
|     with leading zeros. Support for specifying the padding explicitly is | ||||
|     available, however, which can be used to fix this issue. | ||||
|  | ||||
|     FreeBSD, macOS, and Windows do not support explicitly specifying the | ||||
|     padding, but return four digit years (with leading zeros) as expected. | ||||
|  | ||||
|     This function checks whether the %Y produces a correctly padded string and, | ||||
|     if not, makes the following substitutions: | ||||
|  | ||||
|     - %C → %02C | ||||
|     - %F → %010F | ||||
|     - %G → %04G | ||||
|     - %Y → %04Y | ||||
|  | ||||
|     See https://bugs.python.org/issue13305 for more details. | ||||
|     """ | ||||
|     if datetime.date(1, 1, 1).strftime("%Y") == "0001": | ||||
|         return fmt | ||||
|     mapping = {"C": 2, "F": 10, "G": 4, "Y": 4} | ||||
|     return re.sub( | ||||
|         r"((?:^|[^%])(?:%%)*)%([CFGY])", | ||||
|         lambda m: r"%s%%0%s%s" % (m[1], mapping[m[2]], m[2]), | ||||
|         fmt, | ||||
|     ) | ||||
|  | ||||
|  | ||||
| def sanitize_separators(value): | ||||
|     """ | ||||
|     Sanitize a value according to the current decimal and | ||||
|     thousand separator setting. Used with form field input. | ||||
|     """ | ||||
|     if isinstance(value, str): | ||||
|         parts = [] | ||||
|         decimal_separator = get_format("DECIMAL_SEPARATOR") | ||||
|         if decimal_separator in value: | ||||
|             value, decimals = value.split(decimal_separator, 1) | ||||
|             parts.append(decimals) | ||||
|         if settings.USE_THOUSAND_SEPARATOR: | ||||
|             thousand_sep = get_format("THOUSAND_SEPARATOR") | ||||
|             if ( | ||||
|                 thousand_sep == "." | ||||
|                 and value.count(".") == 1 | ||||
|                 and len(value.split(".")[-1]) != 3 | ||||
|             ): | ||||
|                 # Special case where we suspect a dot meant decimal separator | ||||
|                 # (see #22171). | ||||
|                 pass | ||||
|             else: | ||||
|                 for replacement in { | ||||
|                     thousand_sep, | ||||
|                     unicodedata.normalize("NFKD", thousand_sep), | ||||
|                 }: | ||||
|                     value = value.replace(replacement, "") | ||||
|         parts.append(value) | ||||
|         value = ".".join(reversed(parts)) | ||||
|     return value | ||||
| @ -0,0 +1,466 @@ | ||||
| import copy | ||||
| import itertools | ||||
| import operator | ||||
| import warnings | ||||
| from functools import total_ordering, wraps | ||||
|  | ||||
|  | ||||
| class cached_property: | ||||
|     """ | ||||
|     Decorator that converts a method with a single self argument into a | ||||
|     property cached on the instance. | ||||
|  | ||||
|     A cached property can be made out of an existing method: | ||||
|     (e.g. ``url = cached_property(get_absolute_url)``). | ||||
|     """ | ||||
|  | ||||
|     name = None | ||||
|  | ||||
|     @staticmethod | ||||
|     def func(instance): | ||||
|         raise TypeError( | ||||
|             "Cannot use cached_property instance without calling " | ||||
|             "__set_name__() on it." | ||||
|         ) | ||||
|  | ||||
|     def __init__(self, func, name=None): | ||||
|         from django.utils.deprecation import RemovedInDjango50Warning | ||||
|  | ||||
|         if name is not None: | ||||
|             warnings.warn( | ||||
|                 "The name argument is deprecated as it's unnecessary as of " | ||||
|                 "Python 3.6.", | ||||
|                 RemovedInDjango50Warning, | ||||
|                 stacklevel=2, | ||||
|             ) | ||||
|         self.real_func = func | ||||
|         self.__doc__ = getattr(func, "__doc__") | ||||
|  | ||||
|     def __set_name__(self, owner, name): | ||||
|         if self.name is None: | ||||
|             self.name = name | ||||
|             self.func = self.real_func | ||||
|         elif name != self.name: | ||||
|             raise TypeError( | ||||
|                 "Cannot assign the same cached_property to two different names " | ||||
|                 "(%r and %r)." % (self.name, name) | ||||
|             ) | ||||
|  | ||||
|     def __get__(self, instance, cls=None): | ||||
|         """ | ||||
|         Call the function and put the return value in instance.__dict__ so that | ||||
|         subsequent attribute access on the instance returns the cached value | ||||
|         instead of calling cached_property.__get__(). | ||||
|         """ | ||||
|         if instance is None: | ||||
|             return self | ||||
|         res = instance.__dict__[self.name] = self.func(instance) | ||||
|         return res | ||||
|  | ||||
|  | ||||
| class classproperty: | ||||
|     """ | ||||
|     Decorator that converts a method with a single cls argument into a property | ||||
|     that can be accessed directly from the class. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, method=None): | ||||
|         self.fget = method | ||||
|  | ||||
|     def __get__(self, instance, cls=None): | ||||
|         return self.fget(cls) | ||||
|  | ||||
|     def getter(self, method): | ||||
|         self.fget = method | ||||
|         return self | ||||
|  | ||||
|  | ||||
| class Promise: | ||||
|     """ | ||||
|     Base class for the proxy class created in the closure of the lazy function. | ||||
|     It's used to recognize promises in code. | ||||
|     """ | ||||
|  | ||||
|     pass | ||||
|  | ||||
|  | ||||
| def lazy(func, *resultclasses): | ||||
|     """ | ||||
|     Turn any callable into a lazy evaluated callable. result classes or types | ||||
|     is required -- at least one is needed so that the automatic forcing of | ||||
|     the lazy evaluation code is triggered. Results are not memoized; the | ||||
|     function is evaluated on every access. | ||||
|     """ | ||||
|  | ||||
|     @total_ordering | ||||
|     class __proxy__(Promise): | ||||
|         """ | ||||
|         Encapsulate a function call and act as a proxy for methods that are | ||||
|         called on the result of that function. The function is not evaluated | ||||
|         until one of the methods on the result is called. | ||||
|         """ | ||||
|  | ||||
|         __prepared = False | ||||
|  | ||||
|         def __init__(self, args, kw): | ||||
|             self.__args = args | ||||
|             self.__kw = kw | ||||
|             if not self.__prepared: | ||||
|                 self.__prepare_class__() | ||||
|             self.__class__.__prepared = True | ||||
|  | ||||
|         def __reduce__(self): | ||||
|             return ( | ||||
|                 _lazy_proxy_unpickle, | ||||
|                 (func, self.__args, self.__kw) + resultclasses, | ||||
|             ) | ||||
|  | ||||
|         def __repr__(self): | ||||
|             return repr(self.__cast()) | ||||
|  | ||||
|         @classmethod | ||||
|         def __prepare_class__(cls): | ||||
|             for resultclass in resultclasses: | ||||
|                 for type_ in resultclass.mro(): | ||||
|                     for method_name in type_.__dict__: | ||||
|                         # All __promise__ return the same wrapper method, they | ||||
|                         # look up the correct implementation when called. | ||||
|                         if hasattr(cls, method_name): | ||||
|                             continue | ||||
|                         meth = cls.__promise__(method_name) | ||||
|                         setattr(cls, method_name, meth) | ||||
|             cls._delegate_bytes = bytes in resultclasses | ||||
|             cls._delegate_text = str in resultclasses | ||||
|             if cls._delegate_bytes and cls._delegate_text: | ||||
|                 raise ValueError( | ||||
|                     "Cannot call lazy() with both bytes and text return types." | ||||
|                 ) | ||||
|             if cls._delegate_text: | ||||
|                 cls.__str__ = cls.__text_cast | ||||
|             elif cls._delegate_bytes: | ||||
|                 cls.__bytes__ = cls.__bytes_cast | ||||
|  | ||||
|         @classmethod | ||||
|         def __promise__(cls, method_name): | ||||
|             # Builds a wrapper around some magic method | ||||
|             def __wrapper__(self, *args, **kw): | ||||
|                 # Automatically triggers the evaluation of a lazy value and | ||||
|                 # applies the given magic method of the result type. | ||||
|                 res = func(*self.__args, **self.__kw) | ||||
|                 return getattr(res, method_name)(*args, **kw) | ||||
|  | ||||
|             return __wrapper__ | ||||
|  | ||||
|         def __text_cast(self): | ||||
|             return func(*self.__args, **self.__kw) | ||||
|  | ||||
|         def __bytes_cast(self): | ||||
|             return bytes(func(*self.__args, **self.__kw)) | ||||
|  | ||||
|         def __bytes_cast_encoded(self): | ||||
|             return func(*self.__args, **self.__kw).encode() | ||||
|  | ||||
|         def __cast(self): | ||||
|             if self._delegate_bytes: | ||||
|                 return self.__bytes_cast() | ||||
|             elif self._delegate_text: | ||||
|                 return self.__text_cast() | ||||
|             else: | ||||
|                 return func(*self.__args, **self.__kw) | ||||
|  | ||||
|         def __str__(self): | ||||
|             # object defines __str__(), so __prepare_class__() won't overload | ||||
|             # a __str__() method from the proxied class. | ||||
|             return str(self.__cast()) | ||||
|  | ||||
|         def __eq__(self, other): | ||||
|             if isinstance(other, Promise): | ||||
|                 other = other.__cast() | ||||
|             return self.__cast() == other | ||||
|  | ||||
|         def __lt__(self, other): | ||||
|             if isinstance(other, Promise): | ||||
|                 other = other.__cast() | ||||
|             return self.__cast() < other | ||||
|  | ||||
|         def __hash__(self): | ||||
|             return hash(self.__cast()) | ||||
|  | ||||
|         def __mod__(self, rhs): | ||||
|             if self._delegate_text: | ||||
|                 return str(self) % rhs | ||||
|             return self.__cast() % rhs | ||||
|  | ||||
|         def __add__(self, other): | ||||
|             return self.__cast() + other | ||||
|  | ||||
|         def __radd__(self, other): | ||||
|             return other + self.__cast() | ||||
|  | ||||
|         def __deepcopy__(self, memo): | ||||
|             # Instances of this class are effectively immutable. It's just a | ||||
|             # collection of functions. So we don't need to do anything | ||||
|             # complicated for copying. | ||||
|             memo[id(self)] = self | ||||
|             return self | ||||
|  | ||||
|     @wraps(func) | ||||
|     def __wrapper__(*args, **kw): | ||||
|         # Creates the proxy object, instead of the actual value. | ||||
|         return __proxy__(args, kw) | ||||
|  | ||||
|     return __wrapper__ | ||||
|  | ||||
|  | ||||
| def _lazy_proxy_unpickle(func, args, kwargs, *resultclasses): | ||||
|     return lazy(func, *resultclasses)(*args, **kwargs) | ||||
|  | ||||
|  | ||||
| def lazystr(text): | ||||
|     """ | ||||
|     Shortcut for the common case of a lazy callable that returns str. | ||||
|     """ | ||||
|     return lazy(str, str)(text) | ||||
|  | ||||
|  | ||||
| def keep_lazy(*resultclasses): | ||||
|     """ | ||||
|     A decorator that allows a function to be called with one or more lazy | ||||
|     arguments. If none of the args are lazy, the function is evaluated | ||||
|     immediately, otherwise a __proxy__ is returned that will evaluate the | ||||
|     function when needed. | ||||
|     """ | ||||
|     if not resultclasses: | ||||
|         raise TypeError("You must pass at least one argument to keep_lazy().") | ||||
|  | ||||
|     def decorator(func): | ||||
|         lazy_func = lazy(func, *resultclasses) | ||||
|  | ||||
|         @wraps(func) | ||||
|         def wrapper(*args, **kwargs): | ||||
|             if any( | ||||
|                 isinstance(arg, Promise) | ||||
|                 for arg in itertools.chain(args, kwargs.values()) | ||||
|             ): | ||||
|                 return lazy_func(*args, **kwargs) | ||||
|             return func(*args, **kwargs) | ||||
|  | ||||
|         return wrapper | ||||
|  | ||||
|     return decorator | ||||
|  | ||||
|  | ||||
| def keep_lazy_text(func): | ||||
|     """ | ||||
|     A decorator for functions that accept lazy arguments and return text. | ||||
|     """ | ||||
|     return keep_lazy(str)(func) | ||||
|  | ||||
|  | ||||
| empty = object() | ||||
|  | ||||
|  | ||||
| def new_method_proxy(func): | ||||
|     def inner(self, *args): | ||||
|         if (_wrapped := self._wrapped) is empty: | ||||
|             self._setup() | ||||
|             _wrapped = self._wrapped | ||||
|         return func(_wrapped, *args) | ||||
|  | ||||
|     inner._mask_wrapped = False | ||||
|     return inner | ||||
|  | ||||
|  | ||||
| class LazyObject: | ||||
|     """ | ||||
|     A wrapper for another class that can be used to delay instantiation of the | ||||
|     wrapped class. | ||||
|  | ||||
|     By subclassing, you have the opportunity to intercept and alter the | ||||
|     instantiation. If you don't need to do that, use SimpleLazyObject. | ||||
|     """ | ||||
|  | ||||
|     # Avoid infinite recursion when tracing __init__ (#19456). | ||||
|     _wrapped = None | ||||
|  | ||||
|     def __init__(self): | ||||
|         # Note: if a subclass overrides __init__(), it will likely need to | ||||
|         # override __copy__() and __deepcopy__() as well. | ||||
|         self._wrapped = empty | ||||
|  | ||||
|     def __getattribute__(self, name): | ||||
|         if name == "_wrapped": | ||||
|             # Avoid recursion when getting wrapped object. | ||||
|             return super().__getattribute__(name) | ||||
|         value = super().__getattribute__(name) | ||||
|         # If attribute is a proxy method, raise an AttributeError to call | ||||
|         # __getattr__() and use the wrapped object method. | ||||
|         if not getattr(value, "_mask_wrapped", True): | ||||
|             raise AttributeError | ||||
|         return value | ||||
|  | ||||
|     __getattr__ = new_method_proxy(getattr) | ||||
|  | ||||
|     def __setattr__(self, name, value): | ||||
|         if name == "_wrapped": | ||||
|             # Assign to __dict__ to avoid infinite __setattr__ loops. | ||||
|             self.__dict__["_wrapped"] = value | ||||
|         else: | ||||
|             if self._wrapped is empty: | ||||
|                 self._setup() | ||||
|             setattr(self._wrapped, name, value) | ||||
|  | ||||
|     def __delattr__(self, name): | ||||
|         if name == "_wrapped": | ||||
|             raise TypeError("can't delete _wrapped.") | ||||
|         if self._wrapped is empty: | ||||
|             self._setup() | ||||
|         delattr(self._wrapped, name) | ||||
|  | ||||
|     def _setup(self): | ||||
|         """ | ||||
|         Must be implemented by subclasses to initialize the wrapped object. | ||||
|         """ | ||||
|         raise NotImplementedError( | ||||
|             "subclasses of LazyObject must provide a _setup() method" | ||||
|         ) | ||||
|  | ||||
|     # Because we have messed with __class__ below, we confuse pickle as to what | ||||
|     # class we are pickling. We're going to have to initialize the wrapped | ||||
|     # object to successfully pickle it, so we might as well just pickle the | ||||
|     # wrapped object since they're supposed to act the same way. | ||||
|     # | ||||
|     # Unfortunately, if we try to simply act like the wrapped object, the ruse | ||||
|     # will break down when pickle gets our id(). Thus we end up with pickle | ||||
|     # thinking, in effect, that we are a distinct object from the wrapped | ||||
|     # object, but with the same __dict__. This can cause problems (see #25389). | ||||
|     # | ||||
|     # So instead, we define our own __reduce__ method and custom unpickler. We | ||||
|     # pickle the wrapped object as the unpickler's argument, so that pickle | ||||
|     # will pickle it normally, and then the unpickler simply returns its | ||||
|     # argument. | ||||
|     def __reduce__(self): | ||||
|         if self._wrapped is empty: | ||||
|             self._setup() | ||||
|         return (unpickle_lazyobject, (self._wrapped,)) | ||||
|  | ||||
|     def __copy__(self): | ||||
|         if self._wrapped is empty: | ||||
|             # If uninitialized, copy the wrapper. Use type(self), not | ||||
|             # self.__class__, because the latter is proxied. | ||||
|             return type(self)() | ||||
|         else: | ||||
|             # If initialized, return a copy of the wrapped object. | ||||
|             return copy.copy(self._wrapped) | ||||
|  | ||||
|     def __deepcopy__(self, memo): | ||||
|         if self._wrapped is empty: | ||||
|             # We have to use type(self), not self.__class__, because the | ||||
|             # latter is proxied. | ||||
|             result = type(self)() | ||||
|             memo[id(self)] = result | ||||
|             return result | ||||
|         return copy.deepcopy(self._wrapped, memo) | ||||
|  | ||||
|     __bytes__ = new_method_proxy(bytes) | ||||
|     __str__ = new_method_proxy(str) | ||||
|     __bool__ = new_method_proxy(bool) | ||||
|  | ||||
|     # Introspection support | ||||
|     __dir__ = new_method_proxy(dir) | ||||
|  | ||||
|     # Need to pretend to be the wrapped class, for the sake of objects that | ||||
|     # care about this (especially in equality tests) | ||||
|     __class__ = property(new_method_proxy(operator.attrgetter("__class__"))) | ||||
|     __eq__ = new_method_proxy(operator.eq) | ||||
|     __lt__ = new_method_proxy(operator.lt) | ||||
|     __gt__ = new_method_proxy(operator.gt) | ||||
|     __ne__ = new_method_proxy(operator.ne) | ||||
|     __hash__ = new_method_proxy(hash) | ||||
|  | ||||
|     # List/Tuple/Dictionary methods support | ||||
|     __getitem__ = new_method_proxy(operator.getitem) | ||||
|     __setitem__ = new_method_proxy(operator.setitem) | ||||
|     __delitem__ = new_method_proxy(operator.delitem) | ||||
|     __iter__ = new_method_proxy(iter) | ||||
|     __len__ = new_method_proxy(len) | ||||
|     __contains__ = new_method_proxy(operator.contains) | ||||
|  | ||||
|  | ||||
| def unpickle_lazyobject(wrapped): | ||||
|     """ | ||||
|     Used to unpickle lazy objects. Just return its argument, which will be the | ||||
|     wrapped object. | ||||
|     """ | ||||
|     return wrapped | ||||
|  | ||||
|  | ||||
| class SimpleLazyObject(LazyObject): | ||||
|     """ | ||||
|     A lazy object initialized from any function. | ||||
|  | ||||
|     Designed for compound objects of unknown type. For builtins or objects of | ||||
|     known type, use django.utils.functional.lazy. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, func): | ||||
|         """ | ||||
|         Pass in a callable that returns the object to be wrapped. | ||||
|  | ||||
|         If copies are made of the resulting SimpleLazyObject, which can happen | ||||
|         in various circumstances within Django, then you must ensure that the | ||||
|         callable can be safely run more than once and will return the same | ||||
|         value. | ||||
|         """ | ||||
|         self.__dict__["_setupfunc"] = func | ||||
|         super().__init__() | ||||
|  | ||||
|     def _setup(self): | ||||
|         self._wrapped = self._setupfunc() | ||||
|  | ||||
|     # Return a meaningful representation of the lazy object for debugging | ||||
|     # without evaluating the wrapped object. | ||||
|     def __repr__(self): | ||||
|         if self._wrapped is empty: | ||||
|             repr_attr = self._setupfunc | ||||
|         else: | ||||
|             repr_attr = self._wrapped | ||||
|         return "<%s: %r>" % (type(self).__name__, repr_attr) | ||||
|  | ||||
|     def __copy__(self): | ||||
|         if self._wrapped is empty: | ||||
|             # If uninitialized, copy the wrapper. Use SimpleLazyObject, not | ||||
|             # self.__class__, because the latter is proxied. | ||||
|             return SimpleLazyObject(self._setupfunc) | ||||
|         else: | ||||
|             # If initialized, return a copy of the wrapped object. | ||||
|             return copy.copy(self._wrapped) | ||||
|  | ||||
|     def __deepcopy__(self, memo): | ||||
|         if self._wrapped is empty: | ||||
|             # We have to use SimpleLazyObject, not self.__class__, because the | ||||
|             # latter is proxied. | ||||
|             result = SimpleLazyObject(self._setupfunc) | ||||
|             memo[id(self)] = result | ||||
|             return result | ||||
|         return copy.deepcopy(self._wrapped, memo) | ||||
|  | ||||
|     __add__ = new_method_proxy(operator.add) | ||||
|  | ||||
|     @new_method_proxy | ||||
|     def __radd__(self, other): | ||||
|         return other + self | ||||
|  | ||||
|  | ||||
| def partition(predicate, values): | ||||
|     """ | ||||
|     Split the values into two sets, based on the return value of the function | ||||
|     (True/False). e.g.: | ||||
|  | ||||
|         >>> partition(lambda x: x > 3, range(5)) | ||||
|         [0, 1, 2, 3], [4] | ||||
|     """ | ||||
|     results = ([], []) | ||||
|     for item in values: | ||||
|         results[predicate(item)].append(item) | ||||
|     return results | ||||
| @ -0,0 +1,26 @@ | ||||
| from django.utils.itercompat import is_iterable | ||||
|  | ||||
|  | ||||
| def make_hashable(value): | ||||
|     """ | ||||
|     Attempt to make value hashable or raise a TypeError if it fails. | ||||
|  | ||||
|     The returned value should generate the same hash for equal values. | ||||
|     """ | ||||
|     if isinstance(value, dict): | ||||
|         return tuple( | ||||
|             [ | ||||
|                 (key, make_hashable(nested_value)) | ||||
|                 for key, nested_value in sorted(value.items()) | ||||
|             ] | ||||
|         ) | ||||
|     # Try hash to avoid converting a hashable iterable (e.g. string, frozenset) | ||||
|     # to a tuple. | ||||
|     try: | ||||
|         hash(value) | ||||
|     except TypeError: | ||||
|         if is_iterable(value): | ||||
|             return tuple(map(make_hashable, value)) | ||||
|         # Non-hashable, non-iterable. | ||||
|         raise | ||||
|     return value | ||||
							
								
								
									
										422
									
								
								srcs/.venv/lib/python3.11/site-packages/django/utils/html.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										422
									
								
								srcs/.venv/lib/python3.11/site-packages/django/utils/html.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,422 @@ | ||||
| """HTML utilities suitable for global use.""" | ||||
|  | ||||
| import html | ||||
| import json | ||||
| import re | ||||
| from html.parser import HTMLParser | ||||
| from urllib.parse import parse_qsl, quote, unquote, urlencode, urlsplit, urlunsplit | ||||
|  | ||||
| from django.utils.encoding import punycode | ||||
| from django.utils.functional import Promise, keep_lazy, keep_lazy_text | ||||
| from django.utils.http import RFC3986_GENDELIMS, RFC3986_SUBDELIMS | ||||
| from django.utils.regex_helper import _lazy_re_compile | ||||
| from django.utils.safestring import SafeData, SafeString, mark_safe | ||||
| from django.utils.text import normalize_newlines | ||||
|  | ||||
|  | ||||
| @keep_lazy(SafeString) | ||||
| def escape(text): | ||||
|     """ | ||||
|     Return the given text with ampersands, quotes and angle brackets encoded | ||||
|     for use in HTML. | ||||
|  | ||||
|     Always escape input, even if it's already escaped and marked as such. | ||||
|     This may result in double-escaping. If this is a concern, use | ||||
|     conditional_escape() instead. | ||||
|     """ | ||||
|     return SafeString(html.escape(str(text))) | ||||
|  | ||||
|  | ||||
| _js_escapes = { | ||||
|     ord("\\"): "\\u005C", | ||||
|     ord("'"): "\\u0027", | ||||
|     ord('"'): "\\u0022", | ||||
|     ord(">"): "\\u003E", | ||||
|     ord("<"): "\\u003C", | ||||
|     ord("&"): "\\u0026", | ||||
|     ord("="): "\\u003D", | ||||
|     ord("-"): "\\u002D", | ||||
|     ord(";"): "\\u003B", | ||||
|     ord("`"): "\\u0060", | ||||
|     ord("\u2028"): "\\u2028", | ||||
|     ord("\u2029"): "\\u2029", | ||||
| } | ||||
|  | ||||
| # Escape every ASCII character with a value less than 32. | ||||
| _js_escapes.update((ord("%c" % z), "\\u%04X" % z) for z in range(32)) | ||||
|  | ||||
|  | ||||
| @keep_lazy(SafeString) | ||||
| def escapejs(value): | ||||
|     """Hex encode characters for use in JavaScript strings.""" | ||||
|     return mark_safe(str(value).translate(_js_escapes)) | ||||
|  | ||||
|  | ||||
| _json_script_escapes = { | ||||
|     ord(">"): "\\u003E", | ||||
|     ord("<"): "\\u003C", | ||||
|     ord("&"): "\\u0026", | ||||
| } | ||||
|  | ||||
|  | ||||
| def json_script(value, element_id=None, encoder=None): | ||||
|     """ | ||||
|     Escape all the HTML/XML special characters with their unicode escapes, so | ||||
|     value is safe to be output anywhere except for inside a tag attribute. Wrap | ||||
|     the escaped JSON in a script tag. | ||||
|     """ | ||||
|     from django.core.serializers.json import DjangoJSONEncoder | ||||
|  | ||||
|     json_str = json.dumps(value, cls=encoder or DjangoJSONEncoder).translate( | ||||
|         _json_script_escapes | ||||
|     ) | ||||
|     if element_id: | ||||
|         template = '<script id="{}" type="application/json">{}</script>' | ||||
|         args = (element_id, mark_safe(json_str)) | ||||
|     else: | ||||
|         template = '<script type="application/json">{}</script>' | ||||
|         args = (mark_safe(json_str),) | ||||
|     return format_html(template, *args) | ||||
|  | ||||
|  | ||||
| def conditional_escape(text): | ||||
|     """ | ||||
|     Similar to escape(), except that it doesn't operate on pre-escaped strings. | ||||
|  | ||||
|     This function relies on the __html__ convention used both by Django's | ||||
|     SafeData class and by third-party libraries like markupsafe. | ||||
|     """ | ||||
|     if isinstance(text, Promise): | ||||
|         text = str(text) | ||||
|     if hasattr(text, "__html__"): | ||||
|         return text.__html__() | ||||
|     else: | ||||
|         return escape(text) | ||||
|  | ||||
|  | ||||
| def format_html(format_string, *args, **kwargs): | ||||
|     """ | ||||
|     Similar to str.format, but pass all arguments through conditional_escape(), | ||||
|     and call mark_safe() on the result. This function should be used instead | ||||
|     of str.format or % interpolation to build up small HTML fragments. | ||||
|     """ | ||||
|     args_safe = map(conditional_escape, args) | ||||
|     kwargs_safe = {k: conditional_escape(v) for (k, v) in kwargs.items()} | ||||
|     return mark_safe(format_string.format(*args_safe, **kwargs_safe)) | ||||
|  | ||||
|  | ||||
| def format_html_join(sep, format_string, args_generator): | ||||
|     """ | ||||
|     A wrapper of format_html, for the common case of a group of arguments that | ||||
|     need to be formatted using the same format string, and then joined using | ||||
|     'sep'. 'sep' is also passed through conditional_escape. | ||||
|  | ||||
|     'args_generator' should be an iterator that returns the sequence of 'args' | ||||
|     that will be passed to format_html. | ||||
|  | ||||
|     Example: | ||||
|  | ||||
|       format_html_join('\n', "<li>{} {}</li>", ((u.first_name, u.last_name) | ||||
|                                                   for u in users)) | ||||
|     """ | ||||
|     return mark_safe( | ||||
|         conditional_escape(sep).join( | ||||
|             format_html(format_string, *args) for args in args_generator | ||||
|         ) | ||||
|     ) | ||||
|  | ||||
|  | ||||
| @keep_lazy_text | ||||
| def linebreaks(value, autoescape=False): | ||||
|     """Convert newlines into <p> and <br>s.""" | ||||
|     value = normalize_newlines(value) | ||||
|     paras = re.split("\n{2,}", str(value)) | ||||
|     if autoescape: | ||||
|         paras = ["<p>%s</p>" % escape(p).replace("\n", "<br>") for p in paras] | ||||
|     else: | ||||
|         paras = ["<p>%s</p>" % p.replace("\n", "<br>") for p in paras] | ||||
|     return "\n\n".join(paras) | ||||
|  | ||||
|  | ||||
| class MLStripper(HTMLParser): | ||||
|     def __init__(self): | ||||
|         super().__init__(convert_charrefs=False) | ||||
|         self.reset() | ||||
|         self.fed = [] | ||||
|  | ||||
|     def handle_data(self, d): | ||||
|         self.fed.append(d) | ||||
|  | ||||
|     def handle_entityref(self, name): | ||||
|         self.fed.append("&%s;" % name) | ||||
|  | ||||
|     def handle_charref(self, name): | ||||
|         self.fed.append("&#%s;" % name) | ||||
|  | ||||
|     def get_data(self): | ||||
|         return "".join(self.fed) | ||||
|  | ||||
|  | ||||
| def _strip_once(value): | ||||
|     """ | ||||
|     Internal tag stripping utility used by strip_tags. | ||||
|     """ | ||||
|     s = MLStripper() | ||||
|     s.feed(value) | ||||
|     s.close() | ||||
|     return s.get_data() | ||||
|  | ||||
|  | ||||
| @keep_lazy_text | ||||
| def strip_tags(value): | ||||
|     """Return the given HTML with all tags stripped.""" | ||||
|     # Note: in typical case this loop executes _strip_once once. Loop condition | ||||
|     # is redundant, but helps to reduce number of executions of _strip_once. | ||||
|     value = str(value) | ||||
|     while "<" in value and ">" in value: | ||||
|         new_value = _strip_once(value) | ||||
|         if value.count("<") == new_value.count("<"): | ||||
|             # _strip_once wasn't able to detect more tags. | ||||
|             break | ||||
|         value = new_value | ||||
|     return value | ||||
|  | ||||
|  | ||||
| @keep_lazy_text | ||||
| def strip_spaces_between_tags(value): | ||||
|     """Return the given HTML with spaces between tags removed.""" | ||||
|     return re.sub(r">\s+<", "><", str(value)) | ||||
|  | ||||
|  | ||||
| def smart_urlquote(url): | ||||
|     """Quote a URL if it isn't already quoted.""" | ||||
|  | ||||
|     def unquote_quote(segment): | ||||
|         segment = unquote(segment) | ||||
|         # Tilde is part of RFC 3986 Section 2.3 Unreserved Characters, | ||||
|         # see also https://bugs.python.org/issue16285 | ||||
|         return quote(segment, safe=RFC3986_SUBDELIMS + RFC3986_GENDELIMS + "~") | ||||
|  | ||||
|     # Handle IDN before quoting. | ||||
|     try: | ||||
|         scheme, netloc, path, query, fragment = urlsplit(url) | ||||
|     except ValueError: | ||||
|         # invalid IPv6 URL (normally square brackets in hostname part). | ||||
|         return unquote_quote(url) | ||||
|  | ||||
|     try: | ||||
|         netloc = punycode(netloc)  # IDN -> ACE | ||||
|     except UnicodeError:  # invalid domain part | ||||
|         return unquote_quote(url) | ||||
|  | ||||
|     if query: | ||||
|         # Separately unquoting key/value, so as to not mix querystring separators | ||||
|         # included in query values. See #22267. | ||||
|         query_parts = [ | ||||
|             (unquote(q[0]), unquote(q[1])) | ||||
|             for q in parse_qsl(query, keep_blank_values=True) | ||||
|         ] | ||||
|         # urlencode will take care of quoting | ||||
|         query = urlencode(query_parts) | ||||
|  | ||||
|     path = unquote_quote(path) | ||||
|     fragment = unquote_quote(fragment) | ||||
|  | ||||
|     return urlunsplit((scheme, netloc, path, query, fragment)) | ||||
|  | ||||
|  | ||||
| class Urlizer: | ||||
|     """ | ||||
|     Convert any URLs in text into clickable links. | ||||
|  | ||||
|     Work on http://, https://, www. links, and also on links ending in one of | ||||
|     the original seven gTLDs (.com, .edu, .gov, .int, .mil, .net, and .org). | ||||
|     Links can have trailing punctuation (periods, commas, close-parens) and | ||||
|     leading punctuation (opening parens) and it'll still do the right thing. | ||||
|     """ | ||||
|  | ||||
|     trailing_punctuation_chars = ".,:;!" | ||||
|     wrapping_punctuation = [("(", ")"), ("[", "]")] | ||||
|  | ||||
|     simple_url_re = _lazy_re_compile(r"^https?://\[?\w", re.IGNORECASE) | ||||
|     simple_url_2_re = _lazy_re_compile( | ||||
|         r"^www\.|^(?!http)\w[^@]+\.(com|edu|gov|int|mil|net|org)($|/.*)$", re.IGNORECASE | ||||
|     ) | ||||
|     word_split_re = _lazy_re_compile(r"""([\s<>"']+)""") | ||||
|  | ||||
|     mailto_template = "mailto:{local}@{domain}" | ||||
|     url_template = '<a href="{href}"{attrs}>{url}</a>' | ||||
|  | ||||
|     def __call__(self, text, trim_url_limit=None, nofollow=False, autoescape=False): | ||||
|         """ | ||||
|         If trim_url_limit is not None, truncate the URLs in the link text | ||||
|         longer than this limit to trim_url_limit - 1 characters and append an | ||||
|         ellipsis. | ||||
|  | ||||
|         If nofollow is True, give the links a rel="nofollow" attribute. | ||||
|  | ||||
|         If autoescape is True, autoescape the link text and URLs. | ||||
|         """ | ||||
|         safe_input = isinstance(text, SafeData) | ||||
|  | ||||
|         words = self.word_split_re.split(str(text)) | ||||
|         return "".join( | ||||
|             [ | ||||
|                 self.handle_word( | ||||
|                     word, | ||||
|                     safe_input=safe_input, | ||||
|                     trim_url_limit=trim_url_limit, | ||||
|                     nofollow=nofollow, | ||||
|                     autoescape=autoescape, | ||||
|                 ) | ||||
|                 for word in words | ||||
|             ] | ||||
|         ) | ||||
|  | ||||
|     def handle_word( | ||||
|         self, | ||||
|         word, | ||||
|         *, | ||||
|         safe_input, | ||||
|         trim_url_limit=None, | ||||
|         nofollow=False, | ||||
|         autoescape=False, | ||||
|     ): | ||||
|         if "." in word or "@" in word or ":" in word: | ||||
|             # lead: Punctuation trimmed from the beginning of the word. | ||||
|             # middle: State of the word. | ||||
|             # trail: Punctuation trimmed from the end of the word. | ||||
|             lead, middle, trail = self.trim_punctuation(word) | ||||
|             # Make URL we want to point to. | ||||
|             url = None | ||||
|             nofollow_attr = ' rel="nofollow"' if nofollow else "" | ||||
|             if self.simple_url_re.match(middle): | ||||
|                 url = smart_urlquote(html.unescape(middle)) | ||||
|             elif self.simple_url_2_re.match(middle): | ||||
|                 url = smart_urlquote("http://%s" % html.unescape(middle)) | ||||
|             elif ":" not in middle and self.is_email_simple(middle): | ||||
|                 local, domain = middle.rsplit("@", 1) | ||||
|                 try: | ||||
|                     domain = punycode(domain) | ||||
|                 except UnicodeError: | ||||
|                     return word | ||||
|                 url = self.mailto_template.format(local=local, domain=domain) | ||||
|                 nofollow_attr = "" | ||||
|             # Make link. | ||||
|             if url: | ||||
|                 trimmed = self.trim_url(middle, limit=trim_url_limit) | ||||
|                 if autoescape and not safe_input: | ||||
|                     lead, trail = escape(lead), escape(trail) | ||||
|                     trimmed = escape(trimmed) | ||||
|                 middle = self.url_template.format( | ||||
|                     href=escape(url), | ||||
|                     attrs=nofollow_attr, | ||||
|                     url=trimmed, | ||||
|                 ) | ||||
|                 return mark_safe(f"{lead}{middle}{trail}") | ||||
|             else: | ||||
|                 if safe_input: | ||||
|                     return mark_safe(word) | ||||
|                 elif autoescape: | ||||
|                     return escape(word) | ||||
|         elif safe_input: | ||||
|             return mark_safe(word) | ||||
|         elif autoescape: | ||||
|             return escape(word) | ||||
|         return word | ||||
|  | ||||
|     def trim_url(self, x, *, limit): | ||||
|         if limit is None or len(x) <= limit: | ||||
|             return x | ||||
|         return "%s…" % x[: max(0, limit - 1)] | ||||
|  | ||||
|     def trim_punctuation(self, word): | ||||
|         """ | ||||
|         Trim trailing and wrapping punctuation from `word`. Return the items of | ||||
|         the new state. | ||||
|         """ | ||||
|         lead, middle, trail = "", word, "" | ||||
|         # Continue trimming until middle remains unchanged. | ||||
|         trimmed_something = True | ||||
|         while trimmed_something: | ||||
|             trimmed_something = False | ||||
|             # Trim wrapping punctuation. | ||||
|             for opening, closing in self.wrapping_punctuation: | ||||
|                 if middle.startswith(opening): | ||||
|                     middle = middle[len(opening) :] | ||||
|                     lead += opening | ||||
|                     trimmed_something = True | ||||
|                 # Keep parentheses at the end only if they're balanced. | ||||
|                 if ( | ||||
|                     middle.endswith(closing) | ||||
|                     and middle.count(closing) == middle.count(opening) + 1 | ||||
|                 ): | ||||
|                     middle = middle[: -len(closing)] | ||||
|                     trail = closing + trail | ||||
|                     trimmed_something = True | ||||
|             # Trim trailing punctuation (after trimming wrapping punctuation, | ||||
|             # as encoded entities contain ';'). Unescape entities to avoid | ||||
|             # breaking them by removing ';'. | ||||
|             middle_unescaped = html.unescape(middle) | ||||
|             stripped = middle_unescaped.rstrip(self.trailing_punctuation_chars) | ||||
|             if middle_unescaped != stripped: | ||||
|                 punctuation_count = len(middle_unescaped) - len(stripped) | ||||
|                 trail = middle[-punctuation_count:] + trail | ||||
|                 middle = middle[:-punctuation_count] | ||||
|                 trimmed_something = True | ||||
|         return lead, middle, trail | ||||
|  | ||||
|     @staticmethod | ||||
|     def is_email_simple(value): | ||||
|         """Return True if value looks like an email address.""" | ||||
|         # An @ must be in the middle of the value. | ||||
|         if "@" not in value or value.startswith("@") or value.endswith("@"): | ||||
|             return False | ||||
|         try: | ||||
|             p1, p2 = value.split("@") | ||||
|         except ValueError: | ||||
|             # value contains more than one @. | ||||
|             return False | ||||
|         # Dot must be in p2 (e.g. example.com) | ||||
|         if "." not in p2 or p2.startswith("."): | ||||
|             return False | ||||
|         return True | ||||
|  | ||||
|  | ||||
| urlizer = Urlizer() | ||||
|  | ||||
|  | ||||
| @keep_lazy_text | ||||
| def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False): | ||||
|     return urlizer( | ||||
|         text, trim_url_limit=trim_url_limit, nofollow=nofollow, autoescape=autoescape | ||||
|     ) | ||||
|  | ||||
|  | ||||
| def avoid_wrapping(value): | ||||
|     """ | ||||
|     Avoid text wrapping in the middle of a phrase by adding non-breaking | ||||
|     spaces where there previously were normal spaces. | ||||
|     """ | ||||
|     return value.replace(" ", "\xa0") | ||||
|  | ||||
|  | ||||
| def html_safe(klass): | ||||
|     """ | ||||
|     A decorator that defines the __html__ method. This helps non-Django | ||||
|     templates to detect classes whose __str__ methods return SafeString. | ||||
|     """ | ||||
|     if "__html__" in klass.__dict__: | ||||
|         raise ValueError( | ||||
|             "can't apply @html_safe to %s because it defines " | ||||
|             "__html__()." % klass.__name__ | ||||
|         ) | ||||
|     if "__str__" not in klass.__dict__: | ||||
|         raise ValueError( | ||||
|             "can't apply @html_safe to %s because it doesn't " | ||||
|             "define __str__()." % klass.__name__ | ||||
|         ) | ||||
|     klass_str = klass.__str__ | ||||
|     klass.__str__ = lambda self: mark_safe(klass_str(self)) | ||||
|     klass.__html__ = lambda self: str(self) | ||||
|     return klass | ||||
							
								
								
									
										449
									
								
								srcs/.venv/lib/python3.11/site-packages/django/utils/http.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										449
									
								
								srcs/.venv/lib/python3.11/site-packages/django/utils/http.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,449 @@ | ||||
| import base64 | ||||
| import datetime | ||||
| import re | ||||
| import unicodedata | ||||
| from binascii import Error as BinasciiError | ||||
| from email.utils import formatdate | ||||
| from urllib.parse import ( | ||||
|     ParseResult, | ||||
|     SplitResult, | ||||
|     _coerce_args, | ||||
|     _splitnetloc, | ||||
|     _splitparams, | ||||
|     quote, | ||||
|     scheme_chars, | ||||
|     unquote, | ||||
| ) | ||||
| from urllib.parse import urlencode as original_urlencode | ||||
| from urllib.parse import uses_params | ||||
|  | ||||
| from django.utils.datastructures import MultiValueDict | ||||
| from django.utils.regex_helper import _lazy_re_compile | ||||
|  | ||||
| # Based on RFC 9110 Appendix A. | ||||
| ETAG_MATCH = _lazy_re_compile( | ||||
|     r""" | ||||
|     \A(      # start of string and capture group | ||||
|     (?:W/)?  # optional weak indicator | ||||
|     "        # opening quote | ||||
|     [^"]*    # any sequence of non-quote characters | ||||
|     "        # end quote | ||||
|     )\Z      # end of string and capture group | ||||
| """, | ||||
|     re.X, | ||||
| ) | ||||
|  | ||||
| MONTHS = "jan feb mar apr may jun jul aug sep oct nov dec".split() | ||||
| __D = r"(?P<day>[0-9]{2})" | ||||
| __D2 = r"(?P<day>[ 0-9][0-9])" | ||||
| __M = r"(?P<mon>\w{3})" | ||||
| __Y = r"(?P<year>[0-9]{4})" | ||||
| __Y2 = r"(?P<year>[0-9]{2})" | ||||
| __T = r"(?P<hour>[0-9]{2}):(?P<min>[0-9]{2}):(?P<sec>[0-9]{2})" | ||||
| RFC1123_DATE = _lazy_re_compile(r"^\w{3}, %s %s %s %s GMT$" % (__D, __M, __Y, __T)) | ||||
| RFC850_DATE = _lazy_re_compile(r"^\w{6,9}, %s-%s-%s %s GMT$" % (__D, __M, __Y2, __T)) | ||||
| ASCTIME_DATE = _lazy_re_compile(r"^\w{3} %s %s %s %s$" % (__M, __D2, __T, __Y)) | ||||
|  | ||||
| RFC3986_GENDELIMS = ":/?#[]@" | ||||
| RFC3986_SUBDELIMS = "!$&'()*+,;=" | ||||
|  | ||||
| # TODO: Remove when dropping support for PY38. | ||||
| # Unsafe bytes to be removed per WHATWG spec. | ||||
| _UNSAFE_URL_BYTES_TO_REMOVE = ["\t", "\r", "\n"] | ||||
|  | ||||
|  | ||||
| def urlencode(query, doseq=False): | ||||
|     """ | ||||
|     A version of Python's urllib.parse.urlencode() function that can operate on | ||||
|     MultiValueDict and non-string values. | ||||
|     """ | ||||
|     if isinstance(query, MultiValueDict): | ||||
|         query = query.lists() | ||||
|     elif hasattr(query, "items"): | ||||
|         query = query.items() | ||||
|     query_params = [] | ||||
|     for key, value in query: | ||||
|         if value is None: | ||||
|             raise TypeError( | ||||
|                 "Cannot encode None for key '%s' in a query string. Did you " | ||||
|                 "mean to pass an empty string or omit the value?" % key | ||||
|             ) | ||||
|         elif not doseq or isinstance(value, (str, bytes)): | ||||
|             query_val = value | ||||
|         else: | ||||
|             try: | ||||
|                 itr = iter(value) | ||||
|             except TypeError: | ||||
|                 query_val = value | ||||
|             else: | ||||
|                 # Consume generators and iterators, when doseq=True, to | ||||
|                 # work around https://bugs.python.org/issue31706. | ||||
|                 query_val = [] | ||||
|                 for item in itr: | ||||
|                     if item is None: | ||||
|                         raise TypeError( | ||||
|                             "Cannot encode None for key '%s' in a query " | ||||
|                             "string. Did you mean to pass an empty string or " | ||||
|                             "omit the value?" % key | ||||
|                         ) | ||||
|                     elif not isinstance(item, bytes): | ||||
|                         item = str(item) | ||||
|                     query_val.append(item) | ||||
|         query_params.append((key, query_val)) | ||||
|     return original_urlencode(query_params, doseq) | ||||
|  | ||||
|  | ||||
| def http_date(epoch_seconds=None): | ||||
|     """ | ||||
|     Format the time to match the RFC 5322 date format as specified by RFC 9110 | ||||
|     Section 5.6.7. | ||||
|  | ||||
|     `epoch_seconds` is a floating point number expressed in seconds since the | ||||
|     epoch, in UTC - such as that outputted by time.time(). If set to None, it | ||||
|     defaults to the current time. | ||||
|  | ||||
|     Output a string in the format 'Wdy, DD Mon YYYY HH:MM:SS GMT'. | ||||
|     """ | ||||
|     return formatdate(epoch_seconds, usegmt=True) | ||||
|  | ||||
|  | ||||
| def parse_http_date(date): | ||||
|     """ | ||||
|     Parse a date format as specified by HTTP RFC 9110 Section 5.6.7. | ||||
|  | ||||
|     The three formats allowed by the RFC are accepted, even if only the first | ||||
|     one is still in widespread use. | ||||
|  | ||||
|     Return an integer expressed in seconds since the epoch, in UTC. | ||||
|     """ | ||||
|     # email.utils.parsedate() does the job for RFC 1123 dates; unfortunately | ||||
|     # RFC 9110 makes it mandatory to support RFC 850 dates too. So we roll | ||||
|     # our own RFC-compliant parsing. | ||||
|     for regex in RFC1123_DATE, RFC850_DATE, ASCTIME_DATE: | ||||
|         m = regex.match(date) | ||||
|         if m is not None: | ||||
|             break | ||||
|     else: | ||||
|         raise ValueError("%r is not in a valid HTTP date format" % date) | ||||
|     try: | ||||
|         tz = datetime.timezone.utc | ||||
|         year = int(m["year"]) | ||||
|         if year < 100: | ||||
|             current_year = datetime.datetime.now(tz=tz).year | ||||
|             current_century = current_year - (current_year % 100) | ||||
|             if year - (current_year % 100) > 50: | ||||
|                 # year that appears to be more than 50 years in the future are | ||||
|                 # interpreted as representing the past. | ||||
|                 year += current_century - 100 | ||||
|             else: | ||||
|                 year += current_century | ||||
|         month = MONTHS.index(m["mon"].lower()) + 1 | ||||
|         day = int(m["day"]) | ||||
|         hour = int(m["hour"]) | ||||
|         min = int(m["min"]) | ||||
|         sec = int(m["sec"]) | ||||
|         result = datetime.datetime(year, month, day, hour, min, sec, tzinfo=tz) | ||||
|         return int(result.timestamp()) | ||||
|     except Exception as exc: | ||||
|         raise ValueError("%r is not a valid date" % date) from exc | ||||
|  | ||||
|  | ||||
| def parse_http_date_safe(date): | ||||
|     """ | ||||
|     Same as parse_http_date, but return None if the input is invalid. | ||||
|     """ | ||||
|     try: | ||||
|         return parse_http_date(date) | ||||
|     except Exception: | ||||
|         pass | ||||
|  | ||||
|  | ||||
| # Base 36 functions: useful for generating compact URLs | ||||
|  | ||||
|  | ||||
| def base36_to_int(s): | ||||
|     """ | ||||
|     Convert a base 36 string to an int. Raise ValueError if the input won't fit | ||||
|     into an int. | ||||
|     """ | ||||
|     # To prevent overconsumption of server resources, reject any | ||||
|     # base36 string that is longer than 13 base36 digits (13 digits | ||||
|     # is sufficient to base36-encode any 64-bit integer) | ||||
|     if len(s) > 13: | ||||
|         raise ValueError("Base36 input too large") | ||||
|     return int(s, 36) | ||||
|  | ||||
|  | ||||
| def int_to_base36(i): | ||||
|     """Convert an integer to a base36 string.""" | ||||
|     char_set = "0123456789abcdefghijklmnopqrstuvwxyz" | ||||
|     if i < 0: | ||||
|         raise ValueError("Negative base36 conversion input.") | ||||
|     if i < 36: | ||||
|         return char_set[i] | ||||
|     b36 = "" | ||||
|     while i != 0: | ||||
|         i, n = divmod(i, 36) | ||||
|         b36 = char_set[n] + b36 | ||||
|     return b36 | ||||
|  | ||||
|  | ||||
| def urlsafe_base64_encode(s): | ||||
|     """ | ||||
|     Encode a bytestring to a base64 string for use in URLs. Strip any trailing | ||||
|     equal signs. | ||||
|     """ | ||||
|     return base64.urlsafe_b64encode(s).rstrip(b"\n=").decode("ascii") | ||||
|  | ||||
|  | ||||
| def urlsafe_base64_decode(s): | ||||
|     """ | ||||
|     Decode a base64 encoded string. Add back any trailing equal signs that | ||||
|     might have been stripped. | ||||
|     """ | ||||
|     s = s.encode() | ||||
|     try: | ||||
|         return base64.urlsafe_b64decode(s.ljust(len(s) + len(s) % 4, b"=")) | ||||
|     except (LookupError, BinasciiError) as e: | ||||
|         raise ValueError(e) | ||||
|  | ||||
|  | ||||
| def parse_etags(etag_str): | ||||
|     """ | ||||
|     Parse a string of ETags given in an If-None-Match or If-Match header as | ||||
|     defined by RFC 9110. Return a list of quoted ETags, or ['*'] if all ETags | ||||
|     should be matched. | ||||
|     """ | ||||
|     if etag_str.strip() == "*": | ||||
|         return ["*"] | ||||
|     else: | ||||
|         # Parse each ETag individually, and return any that are valid. | ||||
|         etag_matches = (ETAG_MATCH.match(etag.strip()) for etag in etag_str.split(",")) | ||||
|         return [match[1] for match in etag_matches if match] | ||||
|  | ||||
|  | ||||
| def quote_etag(etag_str): | ||||
|     """ | ||||
|     If the provided string is already a quoted ETag, return it. Otherwise, wrap | ||||
|     the string in quotes, making it a strong ETag. | ||||
|     """ | ||||
|     if ETAG_MATCH.match(etag_str): | ||||
|         return etag_str | ||||
|     else: | ||||
|         return '"%s"' % etag_str | ||||
|  | ||||
|  | ||||
| def is_same_domain(host, pattern): | ||||
|     """ | ||||
|     Return ``True`` if the host is either an exact match or a match | ||||
|     to the wildcard pattern. | ||||
|  | ||||
|     Any pattern beginning with a period matches a domain and all of its | ||||
|     subdomains. (e.g. ``.example.com`` matches ``example.com`` and | ||||
|     ``foo.example.com``). Anything else is an exact string match. | ||||
|     """ | ||||
|     if not pattern: | ||||
|         return False | ||||
|  | ||||
|     pattern = pattern.lower() | ||||
|     return ( | ||||
|         pattern[0] == "." | ||||
|         and (host.endswith(pattern) or host == pattern[1:]) | ||||
|         or pattern == host | ||||
|     ) | ||||
|  | ||||
|  | ||||
| def url_has_allowed_host_and_scheme(url, allowed_hosts, require_https=False): | ||||
|     """ | ||||
|     Return ``True`` if the url uses an allowed host and a safe scheme. | ||||
|  | ||||
|     Always return ``False`` on an empty url. | ||||
|  | ||||
|     If ``require_https`` is ``True``, only 'https' will be considered a valid | ||||
|     scheme, as opposed to 'http' and 'https' with the default, ``False``. | ||||
|  | ||||
|     Note: "True" doesn't entail that a URL is "safe". It may still be e.g. | ||||
|     quoted incorrectly. Ensure to also use django.utils.encoding.iri_to_uri() | ||||
|     on the path component of untrusted URLs. | ||||
|     """ | ||||
|     if url is not None: | ||||
|         url = url.strip() | ||||
|     if not url: | ||||
|         return False | ||||
|     if allowed_hosts is None: | ||||
|         allowed_hosts = set() | ||||
|     elif isinstance(allowed_hosts, str): | ||||
|         allowed_hosts = {allowed_hosts} | ||||
|     # Chrome treats \ completely as / in paths but it could be part of some | ||||
|     # basic auth credentials so we need to check both URLs. | ||||
|     return _url_has_allowed_host_and_scheme( | ||||
|         url, allowed_hosts, require_https=require_https | ||||
|     ) and _url_has_allowed_host_and_scheme( | ||||
|         url.replace("\\", "/"), allowed_hosts, require_https=require_https | ||||
|     ) | ||||
|  | ||||
|  | ||||
| # TODO: Remove when dropping support for PY38. | ||||
| # Copied from urllib.parse.urlparse() but uses fixed urlsplit() function. | ||||
| def _urlparse(url, scheme="", allow_fragments=True): | ||||
|     """Parse a URL into 6 components: | ||||
|     <scheme>://<netloc>/<path>;<params>?<query>#<fragment> | ||||
|     Return a 6-tuple: (scheme, netloc, path, params, query, fragment). | ||||
|     Note that we don't break the components up in smaller bits | ||||
|     (e.g. netloc is a single string) and we don't expand % escapes.""" | ||||
|     url, scheme, _coerce_result = _coerce_args(url, scheme) | ||||
|     splitresult = _urlsplit(url, scheme, allow_fragments) | ||||
|     scheme, netloc, url, query, fragment = splitresult | ||||
|     if scheme in uses_params and ";" in url: | ||||
|         url, params = _splitparams(url) | ||||
|     else: | ||||
|         params = "" | ||||
|     result = ParseResult(scheme, netloc, url, params, query, fragment) | ||||
|     return _coerce_result(result) | ||||
|  | ||||
|  | ||||
| # TODO: Remove when dropping support for PY38. | ||||
| def _remove_unsafe_bytes_from_url(url): | ||||
|     for b in _UNSAFE_URL_BYTES_TO_REMOVE: | ||||
|         url = url.replace(b, "") | ||||
|     return url | ||||
|  | ||||
|  | ||||
| # TODO: Remove when dropping support for PY38. | ||||
| # Backport of urllib.parse.urlsplit() from Python 3.9. | ||||
| def _urlsplit(url, scheme="", allow_fragments=True): | ||||
|     """Parse a URL into 5 components: | ||||
|     <scheme>://<netloc>/<path>?<query>#<fragment> | ||||
|     Return a 5-tuple: (scheme, netloc, path, query, fragment). | ||||
|     Note that we don't break the components up in smaller bits | ||||
|     (e.g. netloc is a single string) and we don't expand % escapes.""" | ||||
|     url, scheme, _coerce_result = _coerce_args(url, scheme) | ||||
|     url = _remove_unsafe_bytes_from_url(url) | ||||
|     scheme = _remove_unsafe_bytes_from_url(scheme) | ||||
|  | ||||
|     netloc = query = fragment = "" | ||||
|     i = url.find(":") | ||||
|     if i > 0: | ||||
|         for c in url[:i]: | ||||
|             if c not in scheme_chars: | ||||
|                 break | ||||
|         else: | ||||
|             scheme, url = url[:i].lower(), url[i + 1 :] | ||||
|  | ||||
|     if url[:2] == "//": | ||||
|         netloc, url = _splitnetloc(url, 2) | ||||
|         if ("[" in netloc and "]" not in netloc) or ( | ||||
|             "]" in netloc and "[" not in netloc | ||||
|         ): | ||||
|             raise ValueError("Invalid IPv6 URL") | ||||
|     if allow_fragments and "#" in url: | ||||
|         url, fragment = url.split("#", 1) | ||||
|     if "?" in url: | ||||
|         url, query = url.split("?", 1) | ||||
|     v = SplitResult(scheme, netloc, url, query, fragment) | ||||
|     return _coerce_result(v) | ||||
|  | ||||
|  | ||||
| def _url_has_allowed_host_and_scheme(url, allowed_hosts, require_https=False): | ||||
|     # Chrome considers any URL with more than two slashes to be absolute, but | ||||
|     # urlparse is not so flexible. Treat any url with three slashes as unsafe. | ||||
|     if url.startswith("///"): | ||||
|         return False | ||||
|     try: | ||||
|         url_info = _urlparse(url) | ||||
|     except ValueError:  # e.g. invalid IPv6 addresses | ||||
|         return False | ||||
|     # Forbid URLs like http:///example.com - with a scheme, but without a hostname. | ||||
|     # In that URL, example.com is not the hostname but, a path component. However, | ||||
|     # Chrome will still consider example.com to be the hostname, so we must not | ||||
|     # allow this syntax. | ||||
|     if not url_info.netloc and url_info.scheme: | ||||
|         return False | ||||
|     # Forbid URLs that start with control characters. Some browsers (like | ||||
|     # Chrome) ignore quite a few control characters at the start of a | ||||
|     # URL and might consider the URL as scheme relative. | ||||
|     if unicodedata.category(url[0])[0] == "C": | ||||
|         return False | ||||
|     scheme = url_info.scheme | ||||
|     # Consider URLs without a scheme (e.g. //example.com/p) to be http. | ||||
|     if not url_info.scheme and url_info.netloc: | ||||
|         scheme = "http" | ||||
|     valid_schemes = ["https"] if require_https else ["http", "https"] | ||||
|     return (not url_info.netloc or url_info.netloc in allowed_hosts) and ( | ||||
|         not scheme or scheme in valid_schemes | ||||
|     ) | ||||
|  | ||||
|  | ||||
| def escape_leading_slashes(url): | ||||
|     """ | ||||
|     If redirecting to an absolute path (two leading slashes), a slash must be | ||||
|     escaped to prevent browsers from handling the path as schemaless and | ||||
|     redirecting to another host. | ||||
|     """ | ||||
|     if url.startswith("//"): | ||||
|         url = "/%2F{}".format(url[2:]) | ||||
|     return url | ||||
|  | ||||
|  | ||||
| def _parseparam(s): | ||||
|     while s[:1] == ";": | ||||
|         s = s[1:] | ||||
|         end = s.find(";") | ||||
|         while end > 0 and (s.count('"', 0, end) - s.count('\\"', 0, end)) % 2: | ||||
|             end = s.find(";", end + 1) | ||||
|         if end < 0: | ||||
|             end = len(s) | ||||
|         f = s[:end] | ||||
|         yield f.strip() | ||||
|         s = s[end:] | ||||
|  | ||||
|  | ||||
| def parse_header_parameters(line): | ||||
|     """ | ||||
|     Parse a Content-type like header. | ||||
|     Return the main content-type and a dictionary of options. | ||||
|     """ | ||||
|     parts = _parseparam(";" + line) | ||||
|     key = parts.__next__().lower() | ||||
|     pdict = {} | ||||
|     for p in parts: | ||||
|         i = p.find("=") | ||||
|         if i >= 0: | ||||
|             has_encoding = False | ||||
|             name = p[:i].strip().lower() | ||||
|             if name.endswith("*"): | ||||
|                 # Lang/encoding embedded in the value (like "filename*=UTF-8''file.ext") | ||||
|                 # https://tools.ietf.org/html/rfc2231#section-4 | ||||
|                 name = name[:-1] | ||||
|                 if p.count("'") == 2: | ||||
|                     has_encoding = True | ||||
|             value = p[i + 1 :].strip() | ||||
|             if len(value) >= 2 and value[0] == value[-1] == '"': | ||||
|                 value = value[1:-1] | ||||
|                 value = value.replace("\\\\", "\\").replace('\\"', '"') | ||||
|             if has_encoding: | ||||
|                 encoding, lang, value = value.split("'") | ||||
|                 value = unquote(value, encoding=encoding) | ||||
|             pdict[name] = value | ||||
|     return key, pdict | ||||
|  | ||||
|  | ||||
| def content_disposition_header(as_attachment, filename): | ||||
|     """ | ||||
|     Construct a Content-Disposition HTTP header value from the given filename | ||||
|     as specified by RFC 6266. | ||||
|     """ | ||||
|     if filename: | ||||
|         disposition = "attachment" if as_attachment else "inline" | ||||
|         try: | ||||
|             filename.encode("ascii") | ||||
|             file_expr = 'filename="{}"'.format( | ||||
|                 filename.replace("\\", "\\\\").replace('"', r"\"") | ||||
|             ) | ||||
|         except UnicodeEncodeError: | ||||
|             file_expr = "filename*=utf-8''{}".format(quote(filename)) | ||||
|         return f"{disposition}; {file_expr}" | ||||
|     elif as_attachment: | ||||
|         return "attachment" | ||||
|     else: | ||||
|         return None | ||||
| @ -0,0 +1,73 @@ | ||||
| import functools | ||||
| import inspect | ||||
|  | ||||
|  | ||||
| @functools.lru_cache(maxsize=512) | ||||
| def _get_func_parameters(func, remove_first): | ||||
|     parameters = tuple(inspect.signature(func).parameters.values()) | ||||
|     if remove_first: | ||||
|         parameters = parameters[1:] | ||||
|     return parameters | ||||
|  | ||||
|  | ||||
| def _get_callable_parameters(meth_or_func): | ||||
|     is_method = inspect.ismethod(meth_or_func) | ||||
|     func = meth_or_func.__func__ if is_method else meth_or_func | ||||
|     return _get_func_parameters(func, remove_first=is_method) | ||||
|  | ||||
|  | ||||
| def get_func_args(func): | ||||
|     params = _get_callable_parameters(func) | ||||
|     return [ | ||||
|         param.name | ||||
|         for param in params | ||||
|         if param.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD | ||||
|     ] | ||||
|  | ||||
|  | ||||
| def get_func_full_args(func): | ||||
|     """ | ||||
|     Return a list of (argument name, default value) tuples. If the argument | ||||
|     does not have a default value, omit it in the tuple. Arguments such as | ||||
|     *args and **kwargs are also included. | ||||
|     """ | ||||
|     params = _get_callable_parameters(func) | ||||
|     args = [] | ||||
|     for param in params: | ||||
|         name = param.name | ||||
|         # Ignore 'self' | ||||
|         if name == "self": | ||||
|             continue | ||||
|         if param.kind == inspect.Parameter.VAR_POSITIONAL: | ||||
|             name = "*" + name | ||||
|         elif param.kind == inspect.Parameter.VAR_KEYWORD: | ||||
|             name = "**" + name | ||||
|         if param.default != inspect.Parameter.empty: | ||||
|             args.append((name, param.default)) | ||||
|         else: | ||||
|             args.append((name,)) | ||||
|     return args | ||||
|  | ||||
|  | ||||
| def func_accepts_kwargs(func): | ||||
|     """Return True if function 'func' accepts keyword arguments **kwargs.""" | ||||
|     return any(p for p in _get_callable_parameters(func) if p.kind == p.VAR_KEYWORD) | ||||
|  | ||||
|  | ||||
| def func_accepts_var_args(func): | ||||
|     """ | ||||
|     Return True if function 'func' accepts positional arguments *args. | ||||
|     """ | ||||
|     return any(p for p in _get_callable_parameters(func) if p.kind == p.VAR_POSITIONAL) | ||||
|  | ||||
|  | ||||
| def method_has_no_args(meth): | ||||
|     """Return True if a method only accepts 'self'.""" | ||||
|     count = len( | ||||
|         [p for p in _get_callable_parameters(meth) if p.kind == p.POSITIONAL_OR_KEYWORD] | ||||
|     ) | ||||
|     return count == 0 if inspect.ismethod(meth) else count == 1 | ||||
|  | ||||
|  | ||||
| def func_supports_parameter(func, name): | ||||
|     return any(param.name == name for param in _get_callable_parameters(func)) | ||||
							
								
								
									
										47
									
								
								srcs/.venv/lib/python3.11/site-packages/django/utils/ipv6.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								srcs/.venv/lib/python3.11/site-packages/django/utils/ipv6.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,47 @@ | ||||
| import ipaddress | ||||
|  | ||||
| from django.core.exceptions import ValidationError | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
|  | ||||
|  | ||||
| def clean_ipv6_address( | ||||
|     ip_str, unpack_ipv4=False, error_message=_("This is not a valid IPv6 address.") | ||||
| ): | ||||
|     """ | ||||
|     Clean an IPv6 address string. | ||||
|  | ||||
|     Raise ValidationError if the address is invalid. | ||||
|  | ||||
|     Replace the longest continuous zero-sequence with "::", remove leading | ||||
|     zeroes, and make sure all hextets are lowercase. | ||||
|  | ||||
|     Args: | ||||
|         ip_str: A valid IPv6 address. | ||||
|         unpack_ipv4: if an IPv4-mapped address is found, | ||||
|         return the plain IPv4 address (default=False). | ||||
|         error_message: An error message used in the ValidationError. | ||||
|  | ||||
|     Return a compressed IPv6 address or the same value. | ||||
|     """ | ||||
|     try: | ||||
|         addr = ipaddress.IPv6Address(int(ipaddress.IPv6Address(ip_str))) | ||||
|     except ValueError: | ||||
|         raise ValidationError(error_message, code="invalid") | ||||
|  | ||||
|     if unpack_ipv4 and addr.ipv4_mapped: | ||||
|         return str(addr.ipv4_mapped) | ||||
|     elif addr.ipv4_mapped: | ||||
|         return "::ffff:%s" % str(addr.ipv4_mapped) | ||||
|  | ||||
|     return str(addr) | ||||
|  | ||||
|  | ||||
| def is_valid_ipv6_address(ip_str): | ||||
|     """ | ||||
|     Return whether or not the `ip_str` string is a valid IPv6 address. | ||||
|     """ | ||||
|     try: | ||||
|         ipaddress.IPv6Address(ip_str) | ||||
|     except ValueError: | ||||
|         return False | ||||
|     return True | ||||
| @ -0,0 +1,8 @@ | ||||
| def is_iterable(x): | ||||
|     "An implementation independent way of checking for iterables" | ||||
|     try: | ||||
|         iter(x) | ||||
|     except TypeError: | ||||
|         return False | ||||
|     else: | ||||
|         return True | ||||
							
								
								
									
										249
									
								
								srcs/.venv/lib/python3.11/site-packages/django/utils/jslex.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										249
									
								
								srcs/.venv/lib/python3.11/site-packages/django/utils/jslex.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,249 @@ | ||||
| """JsLex: a lexer for JavaScript""" | ||||
| # Originally from https://bitbucket.org/ned/jslex | ||||
| import re | ||||
|  | ||||
|  | ||||
| class Tok: | ||||
|     """ | ||||
|     A specification for a token class. | ||||
|     """ | ||||
|  | ||||
|     num = 0 | ||||
|  | ||||
|     def __init__(self, name, regex, next=None): | ||||
|         self.id = Tok.num | ||||
|         Tok.num += 1 | ||||
|         self.name = name | ||||
|         self.regex = regex | ||||
|         self.next = next | ||||
|  | ||||
|  | ||||
| def literals(choices, prefix="", suffix=""): | ||||
|     """ | ||||
|     Create a regex from a space-separated list of literal `choices`. | ||||
|  | ||||
|     If provided, `prefix` and `suffix` will be attached to each choice | ||||
|     individually. | ||||
|     """ | ||||
|     return "|".join(prefix + re.escape(c) + suffix for c in choices.split()) | ||||
|  | ||||
|  | ||||
| class Lexer: | ||||
|     """ | ||||
|     A generic multi-state regex-based lexer. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, states, first): | ||||
|         self.regexes = {} | ||||
|         self.toks = {} | ||||
|  | ||||
|         for state, rules in states.items(): | ||||
|             parts = [] | ||||
|             for tok in rules: | ||||
|                 groupid = "t%d" % tok.id | ||||
|                 self.toks[groupid] = tok | ||||
|                 parts.append("(?P<%s>%s)" % (groupid, tok.regex)) | ||||
|             self.regexes[state] = re.compile("|".join(parts), re.MULTILINE | re.VERBOSE) | ||||
|  | ||||
|         self.state = first | ||||
|  | ||||
|     def lex(self, text): | ||||
|         """ | ||||
|         Lexically analyze `text`. | ||||
|  | ||||
|         Yield pairs (`name`, `tokentext`). | ||||
|         """ | ||||
|         end = len(text) | ||||
|         state = self.state | ||||
|         regexes = self.regexes | ||||
|         toks = self.toks | ||||
|         start = 0 | ||||
|  | ||||
|         while start < end: | ||||
|             for match in regexes[state].finditer(text, start): | ||||
|                 name = match.lastgroup | ||||
|                 tok = toks[name] | ||||
|                 toktext = match[name] | ||||
|                 start += len(toktext) | ||||
|                 yield (tok.name, toktext) | ||||
|  | ||||
|                 if tok.next: | ||||
|                     state = tok.next | ||||
|                     break | ||||
|  | ||||
|         self.state = state | ||||
|  | ||||
|  | ||||
| class JsLexer(Lexer): | ||||
|     """ | ||||
|     A JavaScript lexer | ||||
|  | ||||
|     >>> lexer = JsLexer() | ||||
|     >>> list(lexer.lex("a = 1")) | ||||
|     [('id', 'a'), ('ws', ' '), ('punct', '='), ('ws', ' '), ('dnum', '1')] | ||||
|  | ||||
|     This doesn't properly handle non-ASCII characters in the JavaScript source. | ||||
|     """ | ||||
|  | ||||
|     # Because these tokens are matched as alternatives in a regex, longer | ||||
|     # possibilities must appear in the list before shorter ones, for example, | ||||
|     # '>>' before '>'. | ||||
|     # | ||||
|     # Note that we don't have to detect malformed JavaScript, only properly | ||||
|     # lex correct JavaScript, so much of this is simplified. | ||||
|  | ||||
|     # Details of JavaScript lexical structure are taken from | ||||
|     # https://www.ecma-international.org/publications-and-standards/standards/ecma-262/ | ||||
|  | ||||
|     # A useful explanation of automatic semicolon insertion is at | ||||
|     # http://inimino.org/~inimino/blog/javascript_semicolons | ||||
|  | ||||
|     both_before = [ | ||||
|         Tok("comment", r"/\*(.|\n)*?\*/"), | ||||
|         Tok("linecomment", r"//.*?$"), | ||||
|         Tok("ws", r"\s+"), | ||||
|         Tok( | ||||
|             "keyword", | ||||
|             literals( | ||||
|                 """ | ||||
|                            break case catch class const continue debugger | ||||
|                            default delete do else enum export extends | ||||
|                            finally for function if import in instanceof | ||||
|                            new return super switch this throw try typeof | ||||
|                            var void while with | ||||
|                            """, | ||||
|                 suffix=r"\b", | ||||
|             ), | ||||
|             next="reg", | ||||
|         ), | ||||
|         Tok("reserved", literals("null true false", suffix=r"\b"), next="div"), | ||||
|         Tok( | ||||
|             "id", | ||||
|             r""" | ||||
|                   ([a-zA-Z_$   ]|\\u[0-9a-fA-Z]{4})   # first char | ||||
|                   ([a-zA-Z_$0-9]|\\u[0-9a-fA-F]{4})*  # rest chars | ||||
|                   """, | ||||
|             next="div", | ||||
|         ), | ||||
|         Tok("hnum", r"0[xX][0-9a-fA-F]+", next="div"), | ||||
|         Tok("onum", r"0[0-7]+"), | ||||
|         Tok( | ||||
|             "dnum", | ||||
|             r""" | ||||
|                     (   (0|[1-9][0-9]*)     # DecimalIntegerLiteral | ||||
|                         \.                  # dot | ||||
|                         [0-9]*              # DecimalDigits-opt | ||||
|                         ([eE][-+]?[0-9]+)?  # ExponentPart-opt | ||||
|                     | | ||||
|                         \.                  # dot | ||||
|                         [0-9]+              # DecimalDigits | ||||
|                         ([eE][-+]?[0-9]+)?  # ExponentPart-opt | ||||
|                     | | ||||
|                         (0|[1-9][0-9]*)     # DecimalIntegerLiteral | ||||
|                         ([eE][-+]?[0-9]+)?  # ExponentPart-opt | ||||
|                     ) | ||||
|                     """, | ||||
|             next="div", | ||||
|         ), | ||||
|         Tok( | ||||
|             "punct", | ||||
|             literals( | ||||
|                 """ | ||||
|                          >>>= === !== >>> <<= >>= <= >= == != << >> && | ||||
|                          || += -= *= %= &= |= ^= | ||||
|                          """ | ||||
|             ), | ||||
|             next="reg", | ||||
|         ), | ||||
|         Tok("punct", literals("++ -- ) ]"), next="div"), | ||||
|         Tok("punct", literals("{ } ( [ . ; , < > + - * % & | ^ ! ~ ? : ="), next="reg"), | ||||
|         Tok("string", r'"([^"\\]|(\\(.|\n)))*?"', next="div"), | ||||
|         Tok("string", r"'([^'\\]|(\\(.|\n)))*?'", next="div"), | ||||
|     ] | ||||
|  | ||||
|     both_after = [ | ||||
|         Tok("other", r"."), | ||||
|     ] | ||||
|  | ||||
|     states = { | ||||
|         # slash will mean division | ||||
|         "div": both_before | ||||
|         + [ | ||||
|             Tok("punct", literals("/= /"), next="reg"), | ||||
|         ] | ||||
|         + both_after, | ||||
|         # slash will mean regex | ||||
|         "reg": both_before | ||||
|         + [ | ||||
|             Tok( | ||||
|                 "regex", | ||||
|                 r""" | ||||
|                     /                       # opening slash | ||||
|                     # First character is.. | ||||
|                     (   [^*\\/[]            # anything but * \ / or [ | ||||
|                     |   \\.                 # or an escape sequence | ||||
|                     |   \[                  # or a class, which has | ||||
|                             (   [^\]\\]     #   anything but \ or ] | ||||
|                             |   \\.         #   or an escape sequence | ||||
|                             )*              #   many times | ||||
|                         \] | ||||
|                     ) | ||||
|                     # Following characters are same, except for excluding a star | ||||
|                     (   [^\\/[]             # anything but \ / or [ | ||||
|                     |   \\.                 # or an escape sequence | ||||
|                     |   \[                  # or a class, which has | ||||
|                             (   [^\]\\]     #   anything but \ or ] | ||||
|                             |   \\.         #   or an escape sequence | ||||
|                             )*              #   many times | ||||
|                         \] | ||||
|                     )*                      # many times | ||||
|                     /                       # closing slash | ||||
|                     [a-zA-Z0-9]*            # trailing flags | ||||
|                 """, | ||||
|                 next="div", | ||||
|             ), | ||||
|         ] | ||||
|         + both_after, | ||||
|     } | ||||
|  | ||||
|     def __init__(self): | ||||
|         super().__init__(self.states, "reg") | ||||
|  | ||||
|  | ||||
| def prepare_js_for_gettext(js): | ||||
|     """ | ||||
|     Convert the JavaScript source `js` into something resembling C for | ||||
|     xgettext. | ||||
|  | ||||
|     What actually happens is that all the regex literals are replaced with | ||||
|     "REGEX". | ||||
|     """ | ||||
|  | ||||
|     def escape_quotes(m): | ||||
|         """Used in a regex to properly escape double quotes.""" | ||||
|         s = m[0] | ||||
|         if s == '"': | ||||
|             return r"\"" | ||||
|         else: | ||||
|             return s | ||||
|  | ||||
|     lexer = JsLexer() | ||||
|     c = [] | ||||
|     for name, tok in lexer.lex(js): | ||||
|         if name == "regex": | ||||
|             # C doesn't grok regexes, and they aren't needed for gettext, | ||||
|             # so just output a string instead. | ||||
|             tok = '"REGEX"' | ||||
|         elif name == "string": | ||||
|             # C doesn't have single-quoted strings, so make all strings | ||||
|             # double-quoted. | ||||
|             if tok.startswith("'"): | ||||
|                 guts = re.sub(r"\\.|.", escape_quotes, tok[1:-1]) | ||||
|                 tok = '"' + guts + '"' | ||||
|         elif name == "id": | ||||
|             # C can't deal with Unicode escapes in identifiers.  We don't | ||||
|             # need them for gettext anyway, so replace them with something | ||||
|             # innocuous | ||||
|             tok = tok.replace("\\", "U") | ||||
|         c.append(tok) | ||||
|     return "".join(c) | ||||
							
								
								
									
										250
									
								
								srcs/.venv/lib/python3.11/site-packages/django/utils/log.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										250
									
								
								srcs/.venv/lib/python3.11/site-packages/django/utils/log.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,250 @@ | ||||
| import logging | ||||
| import logging.config  # needed when logging_config doesn't start with logging.config | ||||
| from copy import copy | ||||
|  | ||||
| from django.conf import settings | ||||
| from django.core import mail | ||||
| from django.core.mail import get_connection | ||||
| from django.core.management.color import color_style | ||||
| from django.utils.module_loading import import_string | ||||
|  | ||||
| request_logger = logging.getLogger("django.request") | ||||
|  | ||||
| # Default logging for Django. This sends an email to the site admins on every | ||||
| # HTTP 500 error. Depending on DEBUG, all other log records are either sent to | ||||
| # the console (DEBUG=True) or discarded (DEBUG=False) by means of the | ||||
| # require_debug_true filter. This configuration is quoted in | ||||
| # docs/ref/logging.txt; please amend it there if edited here. | ||||
| DEFAULT_LOGGING = { | ||||
|     "version": 1, | ||||
|     "disable_existing_loggers": False, | ||||
|     "filters": { | ||||
|         "require_debug_false": { | ||||
|             "()": "django.utils.log.RequireDebugFalse", | ||||
|         }, | ||||
|         "require_debug_true": { | ||||
|             "()": "django.utils.log.RequireDebugTrue", | ||||
|         }, | ||||
|     }, | ||||
|     "formatters": { | ||||
|         "django.server": { | ||||
|             "()": "django.utils.log.ServerFormatter", | ||||
|             "format": "[{server_time}] {message}", | ||||
|             "style": "{", | ||||
|         } | ||||
|     }, | ||||
|     "handlers": { | ||||
|         "console": { | ||||
|             "level": "INFO", | ||||
|             "filters": ["require_debug_true"], | ||||
|             "class": "logging.StreamHandler", | ||||
|         }, | ||||
|         "django.server": { | ||||
|             "level": "INFO", | ||||
|             "class": "logging.StreamHandler", | ||||
|             "formatter": "django.server", | ||||
|         }, | ||||
|         "mail_admins": { | ||||
|             "level": "ERROR", | ||||
|             "filters": ["require_debug_false"], | ||||
|             "class": "django.utils.log.AdminEmailHandler", | ||||
|         }, | ||||
|     }, | ||||
|     "loggers": { | ||||
|         "django": { | ||||
|             "handlers": ["console", "mail_admins"], | ||||
|             "level": "INFO", | ||||
|         }, | ||||
|         "django.server": { | ||||
|             "handlers": ["django.server"], | ||||
|             "level": "INFO", | ||||
|             "propagate": False, | ||||
|         }, | ||||
|     }, | ||||
| } | ||||
|  | ||||
|  | ||||
| def configure_logging(logging_config, logging_settings): | ||||
|     if logging_config: | ||||
|         # First find the logging configuration function ... | ||||
|         logging_config_func = import_string(logging_config) | ||||
|  | ||||
|         logging.config.dictConfig(DEFAULT_LOGGING) | ||||
|  | ||||
|         # ... then invoke it with the logging settings | ||||
|         if logging_settings: | ||||
|             logging_config_func(logging_settings) | ||||
|  | ||||
|  | ||||
| class AdminEmailHandler(logging.Handler): | ||||
|     """An exception log handler that emails log entries to site admins. | ||||
|  | ||||
|     If the request is passed as the first argument to the log record, | ||||
|     request data will be provided in the email report. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, include_html=False, email_backend=None, reporter_class=None): | ||||
|         super().__init__() | ||||
|         self.include_html = include_html | ||||
|         self.email_backend = email_backend | ||||
|         self.reporter_class = import_string( | ||||
|             reporter_class or settings.DEFAULT_EXCEPTION_REPORTER | ||||
|         ) | ||||
|  | ||||
|     def emit(self, record): | ||||
|         try: | ||||
|             request = record.request | ||||
|             subject = "%s (%s IP): %s" % ( | ||||
|                 record.levelname, | ||||
|                 ( | ||||
|                     "internal" | ||||
|                     if request.META.get("REMOTE_ADDR") in settings.INTERNAL_IPS | ||||
|                     else "EXTERNAL" | ||||
|                 ), | ||||
|                 record.getMessage(), | ||||
|             ) | ||||
|         except Exception: | ||||
|             subject = "%s: %s" % (record.levelname, record.getMessage()) | ||||
|             request = None | ||||
|         subject = self.format_subject(subject) | ||||
|  | ||||
|         # Since we add a nicely formatted traceback on our own, create a copy | ||||
|         # of the log record without the exception data. | ||||
|         no_exc_record = copy(record) | ||||
|         no_exc_record.exc_info = None | ||||
|         no_exc_record.exc_text = None | ||||
|  | ||||
|         if record.exc_info: | ||||
|             exc_info = record.exc_info | ||||
|         else: | ||||
|             exc_info = (None, record.getMessage(), None) | ||||
|  | ||||
|         reporter = self.reporter_class(request, is_email=True, *exc_info) | ||||
|         message = "%s\n\n%s" % ( | ||||
|             self.format(no_exc_record), | ||||
|             reporter.get_traceback_text(), | ||||
|         ) | ||||
|         html_message = reporter.get_traceback_html() if self.include_html else None | ||||
|         self.send_mail(subject, message, fail_silently=True, html_message=html_message) | ||||
|  | ||||
|     def send_mail(self, subject, message, *args, **kwargs): | ||||
|         mail.mail_admins( | ||||
|             subject, message, *args, connection=self.connection(), **kwargs | ||||
|         ) | ||||
|  | ||||
|     def connection(self): | ||||
|         return get_connection(backend=self.email_backend, fail_silently=True) | ||||
|  | ||||
|     def format_subject(self, subject): | ||||
|         """ | ||||
|         Escape CR and LF characters. | ||||
|         """ | ||||
|         return subject.replace("\n", "\\n").replace("\r", "\\r") | ||||
|  | ||||
|  | ||||
| class CallbackFilter(logging.Filter): | ||||
|     """ | ||||
|     A logging filter that checks the return value of a given callable (which | ||||
|     takes the record-to-be-logged as its only parameter) to decide whether to | ||||
|     log a record. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, callback): | ||||
|         self.callback = callback | ||||
|  | ||||
|     def filter(self, record): | ||||
|         if self.callback(record): | ||||
|             return 1 | ||||
|         return 0 | ||||
|  | ||||
|  | ||||
| class RequireDebugFalse(logging.Filter): | ||||
|     def filter(self, record): | ||||
|         return not settings.DEBUG | ||||
|  | ||||
|  | ||||
| class RequireDebugTrue(logging.Filter): | ||||
|     def filter(self, record): | ||||
|         return settings.DEBUG | ||||
|  | ||||
|  | ||||
| class ServerFormatter(logging.Formatter): | ||||
|     default_time_format = "%d/%b/%Y %H:%M:%S" | ||||
|  | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         self.style = color_style() | ||||
|         super().__init__(*args, **kwargs) | ||||
|  | ||||
|     def format(self, record): | ||||
|         msg = record.msg | ||||
|         status_code = getattr(record, "status_code", None) | ||||
|  | ||||
|         if status_code: | ||||
|             if 200 <= status_code < 300: | ||||
|                 # Put 2XX first, since it should be the common case | ||||
|                 msg = self.style.HTTP_SUCCESS(msg) | ||||
|             elif 100 <= status_code < 200: | ||||
|                 msg = self.style.HTTP_INFO(msg) | ||||
|             elif status_code == 304: | ||||
|                 msg = self.style.HTTP_NOT_MODIFIED(msg) | ||||
|             elif 300 <= status_code < 400: | ||||
|                 msg = self.style.HTTP_REDIRECT(msg) | ||||
|             elif status_code == 404: | ||||
|                 msg = self.style.HTTP_NOT_FOUND(msg) | ||||
|             elif 400 <= status_code < 500: | ||||
|                 msg = self.style.HTTP_BAD_REQUEST(msg) | ||||
|             else: | ||||
|                 # Any 5XX, or any other status code | ||||
|                 msg = self.style.HTTP_SERVER_ERROR(msg) | ||||
|  | ||||
|         if self.uses_server_time() and not hasattr(record, "server_time"): | ||||
|             record.server_time = self.formatTime(record, self.datefmt) | ||||
|  | ||||
|         record.msg = msg | ||||
|         return super().format(record) | ||||
|  | ||||
|     def uses_server_time(self): | ||||
|         return self._fmt.find("{server_time}") >= 0 | ||||
|  | ||||
|  | ||||
| def log_response( | ||||
|     message, | ||||
|     *args, | ||||
|     response=None, | ||||
|     request=None, | ||||
|     logger=request_logger, | ||||
|     level=None, | ||||
|     exception=None, | ||||
| ): | ||||
|     """ | ||||
|     Log errors based on HttpResponse status. | ||||
|  | ||||
|     Log 5xx responses as errors and 4xx responses as warnings (unless a level | ||||
|     is given as a keyword argument). The HttpResponse status_code and the | ||||
|     request are passed to the logger's extra parameter. | ||||
|     """ | ||||
|     # Check if the response has already been logged. Multiple requests to log | ||||
|     # the same response can be received in some cases, e.g., when the | ||||
|     # response is the result of an exception and is logged when the exception | ||||
|     # is caught, to record the exception. | ||||
|     if getattr(response, "_has_been_logged", False): | ||||
|         return | ||||
|  | ||||
|     if level is None: | ||||
|         if response.status_code >= 500: | ||||
|             level = "error" | ||||
|         elif response.status_code >= 400: | ||||
|             level = "warning" | ||||
|         else: | ||||
|             level = "info" | ||||
|  | ||||
|     getattr(logger, level)( | ||||
|         message, | ||||
|         *args, | ||||
|         extra={ | ||||
|             "status_code": response.status_code, | ||||
|             "request": request, | ||||
|         }, | ||||
|         exc_info=exception, | ||||
|     ) | ||||
|     response._has_been_logged = True | ||||
| @ -0,0 +1,286 @@ | ||||
| """ | ||||
| Utility functions for generating "lorem ipsum" Latin text. | ||||
| """ | ||||
|  | ||||
| import random | ||||
|  | ||||
| COMMON_P = ( | ||||
|     "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod " | ||||
|     "tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim " | ||||
|     "veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea " | ||||
|     "commodo consequat. Duis aute irure dolor in reprehenderit in voluptate " | ||||
|     "velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint " | ||||
|     "occaecat cupidatat non proident, sunt in culpa qui officia deserunt " | ||||
|     "mollit anim id est laborum." | ||||
| ) | ||||
|  | ||||
| WORDS = ( | ||||
|     "exercitationem", | ||||
|     "perferendis", | ||||
|     "perspiciatis", | ||||
|     "laborum", | ||||
|     "eveniet", | ||||
|     "sunt", | ||||
|     "iure", | ||||
|     "nam", | ||||
|     "nobis", | ||||
|     "eum", | ||||
|     "cum", | ||||
|     "officiis", | ||||
|     "excepturi", | ||||
|     "odio", | ||||
|     "consectetur", | ||||
|     "quasi", | ||||
|     "aut", | ||||
|     "quisquam", | ||||
|     "vel", | ||||
|     "eligendi", | ||||
|     "itaque", | ||||
|     "non", | ||||
|     "odit", | ||||
|     "tempore", | ||||
|     "quaerat", | ||||
|     "dignissimos", | ||||
|     "facilis", | ||||
|     "neque", | ||||
|     "nihil", | ||||
|     "expedita", | ||||
|     "vitae", | ||||
|     "vero", | ||||
|     "ipsum", | ||||
|     "nisi", | ||||
|     "animi", | ||||
|     "cumque", | ||||
|     "pariatur", | ||||
|     "velit", | ||||
|     "modi", | ||||
|     "natus", | ||||
|     "iusto", | ||||
|     "eaque", | ||||
|     "sequi", | ||||
|     "illo", | ||||
|     "sed", | ||||
|     "ex", | ||||
|     "et", | ||||
|     "voluptatibus", | ||||
|     "tempora", | ||||
|     "veritatis", | ||||
|     "ratione", | ||||
|     "assumenda", | ||||
|     "incidunt", | ||||
|     "nostrum", | ||||
|     "placeat", | ||||
|     "aliquid", | ||||
|     "fuga", | ||||
|     "provident", | ||||
|     "praesentium", | ||||
|     "rem", | ||||
|     "necessitatibus", | ||||
|     "suscipit", | ||||
|     "adipisci", | ||||
|     "quidem", | ||||
|     "possimus", | ||||
|     "voluptas", | ||||
|     "debitis", | ||||
|     "sint", | ||||
|     "accusantium", | ||||
|     "unde", | ||||
|     "sapiente", | ||||
|     "voluptate", | ||||
|     "qui", | ||||
|     "aspernatur", | ||||
|     "laudantium", | ||||
|     "soluta", | ||||
|     "amet", | ||||
|     "quo", | ||||
|     "aliquam", | ||||
|     "saepe", | ||||
|     "culpa", | ||||
|     "libero", | ||||
|     "ipsa", | ||||
|     "dicta", | ||||
|     "reiciendis", | ||||
|     "nesciunt", | ||||
|     "doloribus", | ||||
|     "autem", | ||||
|     "impedit", | ||||
|     "minima", | ||||
|     "maiores", | ||||
|     "repudiandae", | ||||
|     "ipsam", | ||||
|     "obcaecati", | ||||
|     "ullam", | ||||
|     "enim", | ||||
|     "totam", | ||||
|     "delectus", | ||||
|     "ducimus", | ||||
|     "quis", | ||||
|     "voluptates", | ||||
|     "dolores", | ||||
|     "molestiae", | ||||
|     "harum", | ||||
|     "dolorem", | ||||
|     "quia", | ||||
|     "voluptatem", | ||||
|     "molestias", | ||||
|     "magni", | ||||
|     "distinctio", | ||||
|     "omnis", | ||||
|     "illum", | ||||
|     "dolorum", | ||||
|     "voluptatum", | ||||
|     "ea", | ||||
|     "quas", | ||||
|     "quam", | ||||
|     "corporis", | ||||
|     "quae", | ||||
|     "blanditiis", | ||||
|     "atque", | ||||
|     "deserunt", | ||||
|     "laboriosam", | ||||
|     "earum", | ||||
|     "consequuntur", | ||||
|     "hic", | ||||
|     "cupiditate", | ||||
|     "quibusdam", | ||||
|     "accusamus", | ||||
|     "ut", | ||||
|     "rerum", | ||||
|     "error", | ||||
|     "minus", | ||||
|     "eius", | ||||
|     "ab", | ||||
|     "ad", | ||||
|     "nemo", | ||||
|     "fugit", | ||||
|     "officia", | ||||
|     "at", | ||||
|     "in", | ||||
|     "id", | ||||
|     "quos", | ||||
|     "reprehenderit", | ||||
|     "numquam", | ||||
|     "iste", | ||||
|     "fugiat", | ||||
|     "sit", | ||||
|     "inventore", | ||||
|     "beatae", | ||||
|     "repellendus", | ||||
|     "magnam", | ||||
|     "recusandae", | ||||
|     "quod", | ||||
|     "explicabo", | ||||
|     "doloremque", | ||||
|     "aperiam", | ||||
|     "consequatur", | ||||
|     "asperiores", | ||||
|     "commodi", | ||||
|     "optio", | ||||
|     "dolor", | ||||
|     "labore", | ||||
|     "temporibus", | ||||
|     "repellat", | ||||
|     "veniam", | ||||
|     "architecto", | ||||
|     "est", | ||||
|     "esse", | ||||
|     "mollitia", | ||||
|     "nulla", | ||||
|     "a", | ||||
|     "similique", | ||||
|     "eos", | ||||
|     "alias", | ||||
|     "dolore", | ||||
|     "tenetur", | ||||
|     "deleniti", | ||||
|     "porro", | ||||
|     "facere", | ||||
|     "maxime", | ||||
|     "corrupti", | ||||
| ) | ||||
|  | ||||
| COMMON_WORDS = ( | ||||
|     "lorem", | ||||
|     "ipsum", | ||||
|     "dolor", | ||||
|     "sit", | ||||
|     "amet", | ||||
|     "consectetur", | ||||
|     "adipisicing", | ||||
|     "elit", | ||||
|     "sed", | ||||
|     "do", | ||||
|     "eiusmod", | ||||
|     "tempor", | ||||
|     "incididunt", | ||||
|     "ut", | ||||
|     "labore", | ||||
|     "et", | ||||
|     "dolore", | ||||
|     "magna", | ||||
|     "aliqua", | ||||
| ) | ||||
|  | ||||
|  | ||||
| def sentence(): | ||||
|     """ | ||||
|     Return a randomly generated sentence of lorem ipsum text. | ||||
|  | ||||
|     The first word is capitalized, and the sentence ends in either a period or | ||||
|     question mark. Commas are added at random. | ||||
|     """ | ||||
|     # Determine the number of comma-separated sections and number of words in | ||||
|     # each section for this sentence. | ||||
|     sections = [ | ||||
|         " ".join(random.sample(WORDS, random.randint(3, 12))) | ||||
|         for i in range(random.randint(1, 5)) | ||||
|     ] | ||||
|     s = ", ".join(sections) | ||||
|     # Convert to sentence case and add end punctuation. | ||||
|     return "%s%s%s" % (s[0].upper(), s[1:], random.choice("?.")) | ||||
|  | ||||
|  | ||||
| def paragraph(): | ||||
|     """ | ||||
|     Return a randomly generated paragraph of lorem ipsum text. | ||||
|  | ||||
|     The paragraph consists of between 1 and 4 sentences, inclusive. | ||||
|     """ | ||||
|     return " ".join(sentence() for i in range(random.randint(1, 4))) | ||||
|  | ||||
|  | ||||
| def paragraphs(count, common=True): | ||||
|     """ | ||||
|     Return a list of paragraphs as returned by paragraph(). | ||||
|  | ||||
|     If `common` is True, then the first paragraph will be the standard | ||||
|     'lorem ipsum' paragraph. Otherwise, the first paragraph will be random | ||||
|     Latin text. Either way, subsequent paragraphs will be random Latin text. | ||||
|     """ | ||||
|     paras = [] | ||||
|     for i in range(count): | ||||
|         if common and i == 0: | ||||
|             paras.append(COMMON_P) | ||||
|         else: | ||||
|             paras.append(paragraph()) | ||||
|     return paras | ||||
|  | ||||
|  | ||||
| def words(count, common=True): | ||||
|     """ | ||||
|     Return a string of `count` lorem ipsum words separated by a single space. | ||||
|  | ||||
|     If `common` is True, then the first 19 words will be the standard | ||||
|     'lorem ipsum' words. Otherwise, all words will be selected randomly. | ||||
|     """ | ||||
|     word_list = list(COMMON_WORDS) if common else [] | ||||
|     c = len(word_list) | ||||
|     if count > c: | ||||
|         count -= c | ||||
|         while count > 0: | ||||
|             c = min(count, len(WORDS)) | ||||
|             count -= c | ||||
|             word_list += random.sample(WORDS, c) | ||||
|     else: | ||||
|         word_list = word_list[:count] | ||||
|     return " ".join(word_list) | ||||
| @ -0,0 +1,107 @@ | ||||
| import copy | ||||
| import os | ||||
| import sys | ||||
| from importlib import import_module | ||||
| from importlib.util import find_spec as importlib_find | ||||
|  | ||||
|  | ||||
| def cached_import(module_path, class_name): | ||||
|     # Check whether module is loaded and fully initialized. | ||||
|     if not ( | ||||
|         (module := sys.modules.get(module_path)) | ||||
|         and (spec := getattr(module, "__spec__", None)) | ||||
|         and getattr(spec, "_initializing", False) is False | ||||
|     ): | ||||
|         module = import_module(module_path) | ||||
|     return getattr(module, class_name) | ||||
|  | ||||
|  | ||||
| def import_string(dotted_path): | ||||
|     """ | ||||
|     Import a dotted module path and return the attribute/class designated by the | ||||
|     last name in the path. Raise ImportError if the import failed. | ||||
|     """ | ||||
|     try: | ||||
|         module_path, class_name = dotted_path.rsplit(".", 1) | ||||
|     except ValueError as err: | ||||
|         raise ImportError("%s doesn't look like a module path" % dotted_path) from err | ||||
|  | ||||
|     try: | ||||
|         return cached_import(module_path, class_name) | ||||
|     except AttributeError as err: | ||||
|         raise ImportError( | ||||
|             'Module "%s" does not define a "%s" attribute/class' | ||||
|             % (module_path, class_name) | ||||
|         ) from err | ||||
|  | ||||
|  | ||||
| def autodiscover_modules(*args, **kwargs): | ||||
|     """ | ||||
|     Auto-discover INSTALLED_APPS modules and fail silently when | ||||
|     not present. This forces an import on them to register any admin bits they | ||||
|     may want. | ||||
|  | ||||
|     You may provide a register_to keyword parameter as a way to access a | ||||
|     registry. This register_to object must have a _registry instance variable | ||||
|     to access it. | ||||
|     """ | ||||
|     from django.apps import apps | ||||
|  | ||||
|     register_to = kwargs.get("register_to") | ||||
|     for app_config in apps.get_app_configs(): | ||||
|         for module_to_search in args: | ||||
|             # Attempt to import the app's module. | ||||
|             try: | ||||
|                 if register_to: | ||||
|                     before_import_registry = copy.copy(register_to._registry) | ||||
|  | ||||
|                 import_module("%s.%s" % (app_config.name, module_to_search)) | ||||
|             except Exception: | ||||
|                 # Reset the registry to the state before the last import | ||||
|                 # as this import will have to reoccur on the next request and | ||||
|                 # this could raise NotRegistered and AlreadyRegistered | ||||
|                 # exceptions (see #8245). | ||||
|                 if register_to: | ||||
|                     register_to._registry = before_import_registry | ||||
|  | ||||
|                 # Decide whether to bubble up this error. If the app just | ||||
|                 # doesn't have the module in question, we can ignore the error | ||||
|                 # attempting to import it, otherwise we want it to bubble up. | ||||
|                 if module_has_submodule(app_config.module, module_to_search): | ||||
|                     raise | ||||
|  | ||||
|  | ||||
| def module_has_submodule(package, module_name): | ||||
|     """See if 'module' is in 'package'.""" | ||||
|     try: | ||||
|         package_name = package.__name__ | ||||
|         package_path = package.__path__ | ||||
|     except AttributeError: | ||||
|         # package isn't a package. | ||||
|         return False | ||||
|  | ||||
|     full_module_name = package_name + "." + module_name | ||||
|     try: | ||||
|         return importlib_find(full_module_name, package_path) is not None | ||||
|     except ModuleNotFoundError: | ||||
|         # When module_name is an invalid dotted path, Python raises | ||||
|         # ModuleNotFoundError. | ||||
|         return False | ||||
|  | ||||
|  | ||||
| def module_dir(module): | ||||
|     """ | ||||
|     Find the name of the directory that contains a module, if possible. | ||||
|  | ||||
|     Raise ValueError otherwise, e.g. for namespace packages that are split | ||||
|     over several directories. | ||||
|     """ | ||||
|     # Convert to list because __path__ may not support indexing. | ||||
|     paths = list(getattr(module, "__path__", [])) | ||||
|     if len(paths) == 1: | ||||
|         return paths[0] | ||||
|     else: | ||||
|         filename = getattr(module, "__file__", None) | ||||
|         if filename is not None: | ||||
|             return os.path.dirname(filename) | ||||
|     raise ValueError("Cannot determine directory containing %s" % module) | ||||
| @ -0,0 +1,105 @@ | ||||
| from decimal import Decimal | ||||
|  | ||||
| from django.conf import settings | ||||
| from django.utils.safestring import mark_safe | ||||
|  | ||||
|  | ||||
| def format( | ||||
|     number, | ||||
|     decimal_sep, | ||||
|     decimal_pos=None, | ||||
|     grouping=0, | ||||
|     thousand_sep="", | ||||
|     force_grouping=False, | ||||
|     use_l10n=None, | ||||
| ): | ||||
|     """ | ||||
|     Get a number (as a number or string), and return it as a string, | ||||
|     using formats defined as arguments: | ||||
|  | ||||
|     * decimal_sep: Decimal separator symbol (for example ".") | ||||
|     * decimal_pos: Number of decimal positions | ||||
|     * grouping: Number of digits in every group limited by thousand separator. | ||||
|         For non-uniform digit grouping, it can be a sequence with the number | ||||
|         of digit group sizes following the format used by the Python locale | ||||
|         module in locale.localeconv() LC_NUMERIC grouping (e.g. (3, 2, 0)). | ||||
|     * thousand_sep: Thousand separator symbol (for example ",") | ||||
|     """ | ||||
|     if number is None or number == "": | ||||
|         return mark_safe(number) | ||||
|     use_grouping = ( | ||||
|         use_l10n or (use_l10n is None and settings.USE_L10N) | ||||
|     ) and settings.USE_THOUSAND_SEPARATOR | ||||
|     use_grouping = use_grouping or force_grouping | ||||
|     use_grouping = use_grouping and grouping != 0 | ||||
|     # Make the common case fast | ||||
|     if isinstance(number, int) and not use_grouping and not decimal_pos: | ||||
|         return mark_safe(number) | ||||
|     # sign | ||||
|     sign = "" | ||||
|     # Treat potentially very large/small floats as Decimals. | ||||
|     if isinstance(number, float) and "e" in str(number).lower(): | ||||
|         number = Decimal(str(number)) | ||||
|     if isinstance(number, Decimal): | ||||
|         if decimal_pos is not None: | ||||
|             # If the provided number is too small to affect any of the visible | ||||
|             # decimal places, consider it equal to '0'. | ||||
|             cutoff = Decimal("0." + "1".rjust(decimal_pos, "0")) | ||||
|             if abs(number) < cutoff: | ||||
|                 number = Decimal("0") | ||||
|  | ||||
|         # Format values with more than 200 digits (an arbitrary cutoff) using | ||||
|         # scientific notation to avoid high memory usage in {:f}'.format(). | ||||
|         _, digits, exponent = number.as_tuple() | ||||
|         if abs(exponent) + len(digits) > 200: | ||||
|             number = "{:e}".format(number) | ||||
|             coefficient, exponent = number.split("e") | ||||
|             # Format the coefficient. | ||||
|             coefficient = format( | ||||
|                 coefficient, | ||||
|                 decimal_sep, | ||||
|                 decimal_pos, | ||||
|                 grouping, | ||||
|                 thousand_sep, | ||||
|                 force_grouping, | ||||
|                 use_l10n, | ||||
|             ) | ||||
|             return "{}e{}".format(coefficient, exponent) | ||||
|         else: | ||||
|             str_number = "{:f}".format(number) | ||||
|     else: | ||||
|         str_number = str(number) | ||||
|     if str_number[0] == "-": | ||||
|         sign = "-" | ||||
|         str_number = str_number[1:] | ||||
|     # decimal part | ||||
|     if "." in str_number: | ||||
|         int_part, dec_part = str_number.split(".") | ||||
|         if decimal_pos is not None: | ||||
|             dec_part = dec_part[:decimal_pos] | ||||
|     else: | ||||
|         int_part, dec_part = str_number, "" | ||||
|     if decimal_pos is not None: | ||||
|         dec_part += "0" * (decimal_pos - len(dec_part)) | ||||
|     dec_part = dec_part and decimal_sep + dec_part | ||||
|     # grouping | ||||
|     if use_grouping: | ||||
|         try: | ||||
|             # if grouping is a sequence | ||||
|             intervals = list(grouping) | ||||
|         except TypeError: | ||||
|             # grouping is a single value | ||||
|             intervals = [grouping, 0] | ||||
|         active_interval = intervals.pop(0) | ||||
|         int_part_gd = "" | ||||
|         cnt = 0 | ||||
|         for digit in int_part[::-1]: | ||||
|             if cnt and cnt == active_interval: | ||||
|                 if intervals: | ||||
|                     active_interval = intervals.pop(0) or active_interval | ||||
|                 int_part_gd += thousand_sep[::-1] | ||||
|                 cnt = 0 | ||||
|             int_part_gd += digit | ||||
|             cnt += 1 | ||||
|         int_part = int_part_gd[::-1] | ||||
|     return sign + int_part + dec_part | ||||
| @ -0,0 +1,353 @@ | ||||
| """ | ||||
| Functions for reversing a regular expression (used in reverse URL resolving). | ||||
| Used internally by Django and not intended for external use. | ||||
|  | ||||
| This is not, and is not intended to be, a complete reg-exp decompiler. It | ||||
| should be good enough for a large class of URLS, however. | ||||
| """ | ||||
| import re | ||||
|  | ||||
| from django.utils.functional import SimpleLazyObject | ||||
|  | ||||
| # Mapping of an escape character to a representative of that class. So, e.g., | ||||
| # "\w" is replaced by "x" in a reverse URL. A value of None means to ignore | ||||
| # this sequence. Any missing key is mapped to itself. | ||||
| ESCAPE_MAPPINGS = { | ||||
|     "A": None, | ||||
|     "b": None, | ||||
|     "B": None, | ||||
|     "d": "0", | ||||
|     "D": "x", | ||||
|     "s": " ", | ||||
|     "S": "x", | ||||
|     "w": "x", | ||||
|     "W": "!", | ||||
|     "Z": None, | ||||
| } | ||||
|  | ||||
|  | ||||
| class Choice(list): | ||||
|     """Represent multiple possibilities at this point in a pattern string.""" | ||||
|  | ||||
|  | ||||
| class Group(list): | ||||
|     """Represent a capturing group in the pattern string.""" | ||||
|  | ||||
|  | ||||
| class NonCapture(list): | ||||
|     """Represent a non-capturing group in the pattern string.""" | ||||
|  | ||||
|  | ||||
| def normalize(pattern): | ||||
|     r""" | ||||
|     Given a reg-exp pattern, normalize it to an iterable of forms that | ||||
|     suffice for reverse matching. This does the following: | ||||
|  | ||||
|     (1) For any repeating sections, keeps the minimum number of occurrences | ||||
|         permitted (this means zero for optional groups). | ||||
|     (2) If an optional group includes parameters, include one occurrence of | ||||
|         that group (along with the zero occurrence case from step (1)). | ||||
|     (3) Select the first (essentially an arbitrary) element from any character | ||||
|         class. Select an arbitrary character for any unordered class (e.g. '.' | ||||
|         or '\w') in the pattern. | ||||
|     (4) Ignore look-ahead and look-behind assertions. | ||||
|     (5) Raise an error on any disjunctive ('|') constructs. | ||||
|  | ||||
|     Django's URLs for forward resolving are either all positional arguments or | ||||
|     all keyword arguments. That is assumed here, as well. Although reverse | ||||
|     resolving can be done using positional args when keyword args are | ||||
|     specified, the two cannot be mixed in the same reverse() call. | ||||
|     """ | ||||
|     # Do a linear scan to work out the special features of this pattern. The | ||||
|     # idea is that we scan once here and collect all the information we need to | ||||
|     # make future decisions. | ||||
|     result = [] | ||||
|     non_capturing_groups = [] | ||||
|     consume_next = True | ||||
|     pattern_iter = next_char(iter(pattern)) | ||||
|     num_args = 0 | ||||
|  | ||||
|     # A "while" loop is used here because later on we need to be able to peek | ||||
|     # at the next character and possibly go around without consuming another | ||||
|     # one at the top of the loop. | ||||
|     try: | ||||
|         ch, escaped = next(pattern_iter) | ||||
|     except StopIteration: | ||||
|         return [("", [])] | ||||
|  | ||||
|     try: | ||||
|         while True: | ||||
|             if escaped: | ||||
|                 result.append(ch) | ||||
|             elif ch == ".": | ||||
|                 # Replace "any character" with an arbitrary representative. | ||||
|                 result.append(".") | ||||
|             elif ch == "|": | ||||
|                 # FIXME: One day we'll should do this, but not in 1.0. | ||||
|                 raise NotImplementedError("Awaiting Implementation") | ||||
|             elif ch == "^": | ||||
|                 pass | ||||
|             elif ch == "$": | ||||
|                 break | ||||
|             elif ch == ")": | ||||
|                 # This can only be the end of a non-capturing group, since all | ||||
|                 # other unescaped parentheses are handled by the grouping | ||||
|                 # section later (and the full group is handled there). | ||||
|                 # | ||||
|                 # We regroup everything inside the capturing group so that it | ||||
|                 # can be quantified, if necessary. | ||||
|                 start = non_capturing_groups.pop() | ||||
|                 inner = NonCapture(result[start:]) | ||||
|                 result = result[:start] + [inner] | ||||
|             elif ch == "[": | ||||
|                 # Replace ranges with the first character in the range. | ||||
|                 ch, escaped = next(pattern_iter) | ||||
|                 result.append(ch) | ||||
|                 ch, escaped = next(pattern_iter) | ||||
|                 while escaped or ch != "]": | ||||
|                     ch, escaped = next(pattern_iter) | ||||
|             elif ch == "(": | ||||
|                 # Some kind of group. | ||||
|                 ch, escaped = next(pattern_iter) | ||||
|                 if ch != "?" or escaped: | ||||
|                     # A positional group | ||||
|                     name = "_%d" % num_args | ||||
|                     num_args += 1 | ||||
|                     result.append(Group((("%%(%s)s" % name), name))) | ||||
|                     walk_to_end(ch, pattern_iter) | ||||
|                 else: | ||||
|                     ch, escaped = next(pattern_iter) | ||||
|                     if ch in "!=<": | ||||
|                         # All of these are ignorable. Walk to the end of the | ||||
|                         # group. | ||||
|                         walk_to_end(ch, pattern_iter) | ||||
|                     elif ch == ":": | ||||
|                         # Non-capturing group | ||||
|                         non_capturing_groups.append(len(result)) | ||||
|                     elif ch != "P": | ||||
|                         # Anything else, other than a named group, is something | ||||
|                         # we cannot reverse. | ||||
|                         raise ValueError("Non-reversible reg-exp portion: '(?%s'" % ch) | ||||
|                     else: | ||||
|                         ch, escaped = next(pattern_iter) | ||||
|                         if ch not in ("<", "="): | ||||
|                             raise ValueError( | ||||
|                                 "Non-reversible reg-exp portion: '(?P%s'" % ch | ||||
|                             ) | ||||
|                         # We are in a named capturing group. Extra the name and | ||||
|                         # then skip to the end. | ||||
|                         if ch == "<": | ||||
|                             terminal_char = ">" | ||||
|                         # We are in a named backreference. | ||||
|                         else: | ||||
|                             terminal_char = ")" | ||||
|                         name = [] | ||||
|                         ch, escaped = next(pattern_iter) | ||||
|                         while ch != terminal_char: | ||||
|                             name.append(ch) | ||||
|                             ch, escaped = next(pattern_iter) | ||||
|                         param = "".join(name) | ||||
|                         # Named backreferences have already consumed the | ||||
|                         # parenthesis. | ||||
|                         if terminal_char != ")": | ||||
|                             result.append(Group((("%%(%s)s" % param), param))) | ||||
|                             walk_to_end(ch, pattern_iter) | ||||
|                         else: | ||||
|                             result.append(Group((("%%(%s)s" % param), None))) | ||||
|             elif ch in "*?+{": | ||||
|                 # Quantifiers affect the previous item in the result list. | ||||
|                 count, ch = get_quantifier(ch, pattern_iter) | ||||
|                 if ch: | ||||
|                     # We had to look ahead, but it wasn't need to compute the | ||||
|                     # quantifier, so use this character next time around the | ||||
|                     # main loop. | ||||
|                     consume_next = False | ||||
|  | ||||
|                 if count == 0: | ||||
|                     if contains(result[-1], Group): | ||||
|                         # If we are quantifying a capturing group (or | ||||
|                         # something containing such a group) and the minimum is | ||||
|                         # zero, we must also handle the case of one occurrence | ||||
|                         # being present. All the quantifiers (except {0,0}, | ||||
|                         # which we conveniently ignore) that have a 0 minimum | ||||
|                         # also allow a single occurrence. | ||||
|                         result[-1] = Choice([None, result[-1]]) | ||||
|                     else: | ||||
|                         result.pop() | ||||
|                 elif count > 1: | ||||
|                     result.extend([result[-1]] * (count - 1)) | ||||
|             else: | ||||
|                 # Anything else is a literal. | ||||
|                 result.append(ch) | ||||
|  | ||||
|             if consume_next: | ||||
|                 ch, escaped = next(pattern_iter) | ||||
|             consume_next = True | ||||
|     except StopIteration: | ||||
|         pass | ||||
|     except NotImplementedError: | ||||
|         # A case of using the disjunctive form. No results for you! | ||||
|         return [("", [])] | ||||
|  | ||||
|     return list(zip(*flatten_result(result))) | ||||
|  | ||||
|  | ||||
| def next_char(input_iter): | ||||
|     r""" | ||||
|     An iterator that yields the next character from "pattern_iter", respecting | ||||
|     escape sequences. An escaped character is replaced by a representative of | ||||
|     its class (e.g. \w -> "x"). If the escaped character is one that is | ||||
|     skipped, it is not returned (the next character is returned instead). | ||||
|  | ||||
|     Yield the next character, along with a boolean indicating whether it is a | ||||
|     raw (unescaped) character or not. | ||||
|     """ | ||||
|     for ch in input_iter: | ||||
|         if ch != "\\": | ||||
|             yield ch, False | ||||
|             continue | ||||
|         ch = next(input_iter) | ||||
|         representative = ESCAPE_MAPPINGS.get(ch, ch) | ||||
|         if representative is None: | ||||
|             continue | ||||
|         yield representative, True | ||||
|  | ||||
|  | ||||
| def walk_to_end(ch, input_iter): | ||||
|     """ | ||||
|     The iterator is currently inside a capturing group. Walk to the close of | ||||
|     this group, skipping over any nested groups and handling escaped | ||||
|     parentheses correctly. | ||||
|     """ | ||||
|     if ch == "(": | ||||
|         nesting = 1 | ||||
|     else: | ||||
|         nesting = 0 | ||||
|     for ch, escaped in input_iter: | ||||
|         if escaped: | ||||
|             continue | ||||
|         elif ch == "(": | ||||
|             nesting += 1 | ||||
|         elif ch == ")": | ||||
|             if not nesting: | ||||
|                 return | ||||
|             nesting -= 1 | ||||
|  | ||||
|  | ||||
| def get_quantifier(ch, input_iter): | ||||
|     """ | ||||
|     Parse a quantifier from the input, where "ch" is the first character in the | ||||
|     quantifier. | ||||
|  | ||||
|     Return the minimum number of occurrences permitted by the quantifier and | ||||
|     either None or the next character from the input_iter if the next character | ||||
|     is not part of the quantifier. | ||||
|     """ | ||||
|     if ch in "*?+": | ||||
|         try: | ||||
|             ch2, escaped = next(input_iter) | ||||
|         except StopIteration: | ||||
|             ch2 = None | ||||
|         if ch2 == "?": | ||||
|             ch2 = None | ||||
|         if ch == "+": | ||||
|             return 1, ch2 | ||||
|         return 0, ch2 | ||||
|  | ||||
|     quant = [] | ||||
|     while ch != "}": | ||||
|         ch, escaped = next(input_iter) | ||||
|         quant.append(ch) | ||||
|     quant = quant[:-1] | ||||
|     values = "".join(quant).split(",") | ||||
|  | ||||
|     # Consume the trailing '?', if necessary. | ||||
|     try: | ||||
|         ch, escaped = next(input_iter) | ||||
|     except StopIteration: | ||||
|         ch = None | ||||
|     if ch == "?": | ||||
|         ch = None | ||||
|     return int(values[0]), ch | ||||
|  | ||||
|  | ||||
| def contains(source, inst): | ||||
|     """ | ||||
|     Return True if the "source" contains an instance of "inst". False, | ||||
|     otherwise. | ||||
|     """ | ||||
|     if isinstance(source, inst): | ||||
|         return True | ||||
|     if isinstance(source, NonCapture): | ||||
|         for elt in source: | ||||
|             if contains(elt, inst): | ||||
|                 return True | ||||
|     return False | ||||
|  | ||||
|  | ||||
| def flatten_result(source): | ||||
|     """ | ||||
|     Turn the given source sequence into a list of reg-exp possibilities and | ||||
|     their arguments. Return a list of strings and a list of argument lists. | ||||
|     Each of the two lists will be of the same length. | ||||
|     """ | ||||
|     if source is None: | ||||
|         return [""], [[]] | ||||
|     if isinstance(source, Group): | ||||
|         if source[1] is None: | ||||
|             params = [] | ||||
|         else: | ||||
|             params = [source[1]] | ||||
|         return [source[0]], [params] | ||||
|     result = [""] | ||||
|     result_args = [[]] | ||||
|     pos = last = 0 | ||||
|     for pos, elt in enumerate(source): | ||||
|         if isinstance(elt, str): | ||||
|             continue | ||||
|         piece = "".join(source[last:pos]) | ||||
|         if isinstance(elt, Group): | ||||
|             piece += elt[0] | ||||
|             param = elt[1] | ||||
|         else: | ||||
|             param = None | ||||
|         last = pos + 1 | ||||
|         for i in range(len(result)): | ||||
|             result[i] += piece | ||||
|             if param: | ||||
|                 result_args[i].append(param) | ||||
|         if isinstance(elt, (Choice, NonCapture)): | ||||
|             if isinstance(elt, NonCapture): | ||||
|                 elt = [elt] | ||||
|             inner_result, inner_args = [], [] | ||||
|             for item in elt: | ||||
|                 res, args = flatten_result(item) | ||||
|                 inner_result.extend(res) | ||||
|                 inner_args.extend(args) | ||||
|             new_result = [] | ||||
|             new_args = [] | ||||
|             for item, args in zip(result, result_args): | ||||
|                 for i_item, i_args in zip(inner_result, inner_args): | ||||
|                     new_result.append(item + i_item) | ||||
|                     new_args.append(args[:] + i_args) | ||||
|             result = new_result | ||||
|             result_args = new_args | ||||
|     if pos >= last: | ||||
|         piece = "".join(source[last:]) | ||||
|         for i in range(len(result)): | ||||
|             result[i] += piece | ||||
|     return result, result_args | ||||
|  | ||||
|  | ||||
| def _lazy_re_compile(regex, flags=0): | ||||
|     """Lazily compile a regex with flags.""" | ||||
|  | ||||
|     def _compile(): | ||||
|         # Compile the regex if it was not passed pre-compiled. | ||||
|         if isinstance(regex, (str, bytes)): | ||||
|             return re.compile(regex, flags) | ||||
|         else: | ||||
|             assert not flags, "flags must be empty if regex is passed pre-compiled" | ||||
|             return regex | ||||
|  | ||||
|     return SimpleLazyObject(_compile) | ||||
| @ -0,0 +1,72 @@ | ||||
| """ | ||||
| Functions for working with "safe strings": strings that can be displayed safely | ||||
| without further escaping in HTML. Marking something as a "safe string" means | ||||
| that the producer of the string has already turned characters that should not | ||||
| be interpreted by the HTML engine (e.g. '<') into the appropriate entities. | ||||
| """ | ||||
|  | ||||
| from functools import wraps | ||||
|  | ||||
| from django.utils.functional import keep_lazy | ||||
|  | ||||
|  | ||||
| class SafeData: | ||||
|     __slots__ = () | ||||
|  | ||||
|     def __html__(self): | ||||
|         """ | ||||
|         Return the html representation of a string for interoperability. | ||||
|  | ||||
|         This allows other template engines to understand Django's SafeData. | ||||
|         """ | ||||
|         return self | ||||
|  | ||||
|  | ||||
| class SafeString(str, SafeData): | ||||
|     """ | ||||
|     A str subclass that has been specifically marked as "safe" for HTML output | ||||
|     purposes. | ||||
|     """ | ||||
|  | ||||
|     __slots__ = () | ||||
|  | ||||
|     def __add__(self, rhs): | ||||
|         """ | ||||
|         Concatenating a safe string with another safe bytestring or | ||||
|         safe string is safe. Otherwise, the result is no longer safe. | ||||
|         """ | ||||
|         t = super().__add__(rhs) | ||||
|         if isinstance(rhs, SafeData): | ||||
|             return SafeString(t) | ||||
|         return t | ||||
|  | ||||
|     def __str__(self): | ||||
|         return self | ||||
|  | ||||
|  | ||||
| SafeText = SafeString  # For backwards compatibility since Django 2.0. | ||||
|  | ||||
|  | ||||
| def _safety_decorator(safety_marker, func): | ||||
|     @wraps(func) | ||||
|     def wrapper(*args, **kwargs): | ||||
|         return safety_marker(func(*args, **kwargs)) | ||||
|  | ||||
|     return wrapper | ||||
|  | ||||
|  | ||||
| @keep_lazy(SafeString) | ||||
| def mark_safe(s): | ||||
|     """ | ||||
|     Explicitly mark a string as safe for (HTML) output purposes. The returned | ||||
|     object can be used everywhere a string is appropriate. | ||||
|  | ||||
|     If used on a method as a decorator, mark the returned data as safe. | ||||
|  | ||||
|     Can be called multiple times on a single string. | ||||
|     """ | ||||
|     if hasattr(s, "__html__"): | ||||
|         return s | ||||
|     if callable(s): | ||||
|         return _safety_decorator(mark_safe, s) | ||||
|     return SafeString(s) | ||||
| @ -0,0 +1,221 @@ | ||||
| """ | ||||
| termcolors.py | ||||
| """ | ||||
|  | ||||
| color_names = ("black", "red", "green", "yellow", "blue", "magenta", "cyan", "white") | ||||
| foreground = {color_names[x]: "3%s" % x for x in range(8)} | ||||
| background = {color_names[x]: "4%s" % x for x in range(8)} | ||||
|  | ||||
| RESET = "0" | ||||
| opt_dict = { | ||||
|     "bold": "1", | ||||
|     "underscore": "4", | ||||
|     "blink": "5", | ||||
|     "reverse": "7", | ||||
|     "conceal": "8", | ||||
| } | ||||
|  | ||||
|  | ||||
| def colorize(text="", opts=(), **kwargs): | ||||
|     """ | ||||
|     Return your text, enclosed in ANSI graphics codes. | ||||
|  | ||||
|     Depends on the keyword arguments 'fg' and 'bg', and the contents of | ||||
|     the opts tuple/list. | ||||
|  | ||||
|     Return the RESET code if no parameters are given. | ||||
|  | ||||
|     Valid colors: | ||||
|         'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white' | ||||
|  | ||||
|     Valid options: | ||||
|         'bold' | ||||
|         'underscore' | ||||
|         'blink' | ||||
|         'reverse' | ||||
|         'conceal' | ||||
|         'noreset' - string will not be auto-terminated with the RESET code | ||||
|  | ||||
|     Examples: | ||||
|         colorize('hello', fg='red', bg='blue', opts=('blink',)) | ||||
|         colorize() | ||||
|         colorize('goodbye', opts=('underscore',)) | ||||
|         print(colorize('first line', fg='red', opts=('noreset',))) | ||||
|         print('this should be red too') | ||||
|         print(colorize('and so should this')) | ||||
|         print('this should not be red') | ||||
|     """ | ||||
|     code_list = [] | ||||
|     if text == "" and len(opts) == 1 and opts[0] == "reset": | ||||
|         return "\x1b[%sm" % RESET | ||||
|     for k, v in kwargs.items(): | ||||
|         if k == "fg": | ||||
|             code_list.append(foreground[v]) | ||||
|         elif k == "bg": | ||||
|             code_list.append(background[v]) | ||||
|     for o in opts: | ||||
|         if o in opt_dict: | ||||
|             code_list.append(opt_dict[o]) | ||||
|     if "noreset" not in opts: | ||||
|         text = "%s\x1b[%sm" % (text or "", RESET) | ||||
|     return "%s%s" % (("\x1b[%sm" % ";".join(code_list)), text or "") | ||||
|  | ||||
|  | ||||
| def make_style(opts=(), **kwargs): | ||||
|     """ | ||||
|     Return a function with default parameters for colorize() | ||||
|  | ||||
|     Example: | ||||
|         bold_red = make_style(opts=('bold',), fg='red') | ||||
|         print(bold_red('hello')) | ||||
|         KEYWORD = make_style(fg='yellow') | ||||
|         COMMENT = make_style(fg='blue', opts=('bold',)) | ||||
|     """ | ||||
|     return lambda text: colorize(text, opts, **kwargs) | ||||
|  | ||||
|  | ||||
| NOCOLOR_PALETTE = "nocolor" | ||||
| DARK_PALETTE = "dark" | ||||
| LIGHT_PALETTE = "light" | ||||
|  | ||||
| PALETTES = { | ||||
|     NOCOLOR_PALETTE: { | ||||
|         "ERROR": {}, | ||||
|         "SUCCESS": {}, | ||||
|         "WARNING": {}, | ||||
|         "NOTICE": {}, | ||||
|         "SQL_FIELD": {}, | ||||
|         "SQL_COLTYPE": {}, | ||||
|         "SQL_KEYWORD": {}, | ||||
|         "SQL_TABLE": {}, | ||||
|         "HTTP_INFO": {}, | ||||
|         "HTTP_SUCCESS": {}, | ||||
|         "HTTP_REDIRECT": {}, | ||||
|         "HTTP_NOT_MODIFIED": {}, | ||||
|         "HTTP_BAD_REQUEST": {}, | ||||
|         "HTTP_NOT_FOUND": {}, | ||||
|         "HTTP_SERVER_ERROR": {}, | ||||
|         "MIGRATE_HEADING": {}, | ||||
|         "MIGRATE_LABEL": {}, | ||||
|     }, | ||||
|     DARK_PALETTE: { | ||||
|         "ERROR": {"fg": "red", "opts": ("bold",)}, | ||||
|         "SUCCESS": {"fg": "green", "opts": ("bold",)}, | ||||
|         "WARNING": {"fg": "yellow", "opts": ("bold",)}, | ||||
|         "NOTICE": {"fg": "red"}, | ||||
|         "SQL_FIELD": {"fg": "green", "opts": ("bold",)}, | ||||
|         "SQL_COLTYPE": {"fg": "green"}, | ||||
|         "SQL_KEYWORD": {"fg": "yellow"}, | ||||
|         "SQL_TABLE": {"opts": ("bold",)}, | ||||
|         "HTTP_INFO": {"opts": ("bold",)}, | ||||
|         "HTTP_SUCCESS": {}, | ||||
|         "HTTP_REDIRECT": {"fg": "green"}, | ||||
|         "HTTP_NOT_MODIFIED": {"fg": "cyan"}, | ||||
|         "HTTP_BAD_REQUEST": {"fg": "red", "opts": ("bold",)}, | ||||
|         "HTTP_NOT_FOUND": {"fg": "yellow"}, | ||||
|         "HTTP_SERVER_ERROR": {"fg": "magenta", "opts": ("bold",)}, | ||||
|         "MIGRATE_HEADING": {"fg": "cyan", "opts": ("bold",)}, | ||||
|         "MIGRATE_LABEL": {"opts": ("bold",)}, | ||||
|     }, | ||||
|     LIGHT_PALETTE: { | ||||
|         "ERROR": {"fg": "red", "opts": ("bold",)}, | ||||
|         "SUCCESS": {"fg": "green", "opts": ("bold",)}, | ||||
|         "WARNING": {"fg": "yellow", "opts": ("bold",)}, | ||||
|         "NOTICE": {"fg": "red"}, | ||||
|         "SQL_FIELD": {"fg": "green", "opts": ("bold",)}, | ||||
|         "SQL_COLTYPE": {"fg": "green"}, | ||||
|         "SQL_KEYWORD": {"fg": "blue"}, | ||||
|         "SQL_TABLE": {"opts": ("bold",)}, | ||||
|         "HTTP_INFO": {"opts": ("bold",)}, | ||||
|         "HTTP_SUCCESS": {}, | ||||
|         "HTTP_REDIRECT": {"fg": "green", "opts": ("bold",)}, | ||||
|         "HTTP_NOT_MODIFIED": {"fg": "green"}, | ||||
|         "HTTP_BAD_REQUEST": {"fg": "red", "opts": ("bold",)}, | ||||
|         "HTTP_NOT_FOUND": {"fg": "red"}, | ||||
|         "HTTP_SERVER_ERROR": {"fg": "magenta", "opts": ("bold",)}, | ||||
|         "MIGRATE_HEADING": {"fg": "cyan", "opts": ("bold",)}, | ||||
|         "MIGRATE_LABEL": {"opts": ("bold",)}, | ||||
|     }, | ||||
| } | ||||
| DEFAULT_PALETTE = DARK_PALETTE | ||||
|  | ||||
|  | ||||
| def parse_color_setting(config_string): | ||||
|     """Parse a DJANGO_COLORS environment variable to produce the system palette | ||||
|  | ||||
|     The general form of a palette definition is: | ||||
|  | ||||
|         "palette;role=fg;role=fg/bg;role=fg,option,option;role=fg/bg,option,option" | ||||
|  | ||||
|     where: | ||||
|         palette is a named palette; one of 'light', 'dark', or 'nocolor'. | ||||
|         role is a named style used by Django | ||||
|         fg is a foreground color. | ||||
|         bg is a background color. | ||||
|         option is a display options. | ||||
|  | ||||
|     Specifying a named palette is the same as manually specifying the individual | ||||
|     definitions for each role. Any individual definitions following the palette | ||||
|     definition will augment the base palette definition. | ||||
|  | ||||
|     Valid roles: | ||||
|         'error', 'success', 'warning', 'notice', 'sql_field', 'sql_coltype', | ||||
|         'sql_keyword', 'sql_table', 'http_info', 'http_success', | ||||
|         'http_redirect', 'http_not_modified', 'http_bad_request', | ||||
|         'http_not_found', 'http_server_error', 'migrate_heading', | ||||
|         'migrate_label' | ||||
|  | ||||
|     Valid colors: | ||||
|         'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white' | ||||
|  | ||||
|     Valid options: | ||||
|         'bold', 'underscore', 'blink', 'reverse', 'conceal', 'noreset' | ||||
|     """ | ||||
|     if not config_string: | ||||
|         return PALETTES[DEFAULT_PALETTE] | ||||
|  | ||||
|     # Split the color configuration into parts | ||||
|     parts = config_string.lower().split(";") | ||||
|     palette = PALETTES[NOCOLOR_PALETTE].copy() | ||||
|     for part in parts: | ||||
|         if part in PALETTES: | ||||
|             # A default palette has been specified | ||||
|             palette.update(PALETTES[part]) | ||||
|         elif "=" in part: | ||||
|             # Process a palette defining string | ||||
|             definition = {} | ||||
|  | ||||
|             # Break the definition into the role, | ||||
|             # plus the list of specific instructions. | ||||
|             # The role must be in upper case | ||||
|             role, instructions = part.split("=") | ||||
|             role = role.upper() | ||||
|  | ||||
|             styles = instructions.split(",") | ||||
|             styles.reverse() | ||||
|  | ||||
|             # The first instruction can contain a slash | ||||
|             # to break apart fg/bg. | ||||
|             colors = styles.pop().split("/") | ||||
|             colors.reverse() | ||||
|             fg = colors.pop() | ||||
|             if fg in color_names: | ||||
|                 definition["fg"] = fg | ||||
|             if colors and colors[-1] in color_names: | ||||
|                 definition["bg"] = colors[-1] | ||||
|  | ||||
|             # All remaining instructions are options | ||||
|             opts = tuple(s for s in styles if s in opt_dict) | ||||
|             if opts: | ||||
|                 definition["opts"] = opts | ||||
|  | ||||
|             # The nocolor palette has all available roles. | ||||
|             # Use that palette as the basis for determining | ||||
|             # if the role is valid. | ||||
|             if role in PALETTES[NOCOLOR_PALETTE] and definition: | ||||
|                 palette[role] = definition | ||||
|  | ||||
|     # If there are no colors specified, return the empty palette. | ||||
|     if palette == PALETTES[NOCOLOR_PALETTE]: | ||||
|         return None | ||||
|     return palette | ||||
							
								
								
									
										470
									
								
								srcs/.venv/lib/python3.11/site-packages/django/utils/text.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										470
									
								
								srcs/.venv/lib/python3.11/site-packages/django/utils/text.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,470 @@ | ||||
| import gzip | ||||
| import re | ||||
| import secrets | ||||
| import unicodedata | ||||
| from gzip import GzipFile | ||||
| from gzip import compress as gzip_compress | ||||
| from io import BytesIO | ||||
|  | ||||
| from django.core.exceptions import SuspiciousFileOperation | ||||
| from django.utils.functional import SimpleLazyObject, keep_lazy_text, lazy | ||||
| from django.utils.regex_helper import _lazy_re_compile | ||||
| from django.utils.translation import gettext as _ | ||||
| from django.utils.translation import gettext_lazy, pgettext | ||||
|  | ||||
|  | ||||
| @keep_lazy_text | ||||
| def capfirst(x): | ||||
|     """Capitalize the first letter of a string.""" | ||||
|     if not x: | ||||
|         return x | ||||
|     if not isinstance(x, str): | ||||
|         x = str(x) | ||||
|     return x[0].upper() + x[1:] | ||||
|  | ||||
|  | ||||
| # Set up regular expressions | ||||
| re_words = _lazy_re_compile(r"<[^>]+?>|([^<>\s]+)", re.S) | ||||
| re_chars = _lazy_re_compile(r"<[^>]+?>|(.)", re.S) | ||||
| re_tag = _lazy_re_compile(r"<(/)?(\S+?)(?:(\s*/)|\s.*?)?>", re.S) | ||||
| re_newlines = _lazy_re_compile(r"\r\n|\r")  # Used in normalize_newlines | ||||
| re_camel_case = _lazy_re_compile(r"(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))") | ||||
|  | ||||
|  | ||||
| @keep_lazy_text | ||||
| def wrap(text, width): | ||||
|     """ | ||||
|     A word-wrap function that preserves existing line breaks. Expects that | ||||
|     existing line breaks are posix newlines. | ||||
|  | ||||
|     Preserve all white space except added line breaks consume the space on | ||||
|     which they break the line. | ||||
|  | ||||
|     Don't wrap long words, thus the output text may have lines longer than | ||||
|     ``width``. | ||||
|     """ | ||||
|  | ||||
|     def _generator(): | ||||
|         for line in text.splitlines(True):  # True keeps trailing linebreaks | ||||
|             max_width = min((line.endswith("\n") and width + 1 or width), width) | ||||
|             while len(line) > max_width: | ||||
|                 space = line[: max_width + 1].rfind(" ") + 1 | ||||
|                 if space == 0: | ||||
|                     space = line.find(" ") + 1 | ||||
|                     if space == 0: | ||||
|                         yield line | ||||
|                         line = "" | ||||
|                         break | ||||
|                 yield "%s\n" % line[: space - 1] | ||||
|                 line = line[space:] | ||||
|                 max_width = min((line.endswith("\n") and width + 1 or width), width) | ||||
|             if line: | ||||
|                 yield line | ||||
|  | ||||
|     return "".join(_generator()) | ||||
|  | ||||
|  | ||||
| class Truncator(SimpleLazyObject): | ||||
|     """ | ||||
|     An object used to truncate text, either by characters or words. | ||||
|  | ||||
|     When truncating HTML text (either chars or words), input will be limited to | ||||
|     at most `MAX_LENGTH_HTML` characters. | ||||
|     """ | ||||
|  | ||||
|     # 5 million characters are approximately 4000 text pages or 3 web pages. | ||||
|     MAX_LENGTH_HTML = 5_000_000 | ||||
|  | ||||
|     def __init__(self, text): | ||||
|         super().__init__(lambda: str(text)) | ||||
|  | ||||
|     def add_truncation_text(self, text, truncate=None): | ||||
|         if truncate is None: | ||||
|             truncate = pgettext( | ||||
|                 "String to return when truncating text", "%(truncated_text)s…" | ||||
|             ) | ||||
|         if "%(truncated_text)s" in truncate: | ||||
|             return truncate % {"truncated_text": text} | ||||
|         # The truncation text didn't contain the %(truncated_text)s string | ||||
|         # replacement argument so just append it to the text. | ||||
|         if text.endswith(truncate): | ||||
|             # But don't append the truncation text if the current text already | ||||
|             # ends in this. | ||||
|             return text | ||||
|         return "%s%s" % (text, truncate) | ||||
|  | ||||
|     def chars(self, num, truncate=None, html=False): | ||||
|         """ | ||||
|         Return the text truncated to be no longer than the specified number | ||||
|         of characters. | ||||
|  | ||||
|         `truncate` specifies what should be used to notify that the string has | ||||
|         been truncated, defaulting to a translatable string of an ellipsis. | ||||
|         """ | ||||
|         self._setup() | ||||
|         length = int(num) | ||||
|         text = unicodedata.normalize("NFC", self._wrapped) | ||||
|  | ||||
|         # Calculate the length to truncate to (max length - end_text length) | ||||
|         truncate_len = length | ||||
|         for char in self.add_truncation_text("", truncate): | ||||
|             if not unicodedata.combining(char): | ||||
|                 truncate_len -= 1 | ||||
|                 if truncate_len == 0: | ||||
|                     break | ||||
|         if html: | ||||
|             return self._truncate_html(length, truncate, text, truncate_len, False) | ||||
|         return self._text_chars(length, truncate, text, truncate_len) | ||||
|  | ||||
|     def _text_chars(self, length, truncate, text, truncate_len): | ||||
|         """Truncate a string after a certain number of chars.""" | ||||
|         s_len = 0 | ||||
|         end_index = None | ||||
|         for i, char in enumerate(text): | ||||
|             if unicodedata.combining(char): | ||||
|                 # Don't consider combining characters | ||||
|                 # as adding to the string length | ||||
|                 continue | ||||
|             s_len += 1 | ||||
|             if end_index is None and s_len > truncate_len: | ||||
|                 end_index = i | ||||
|             if s_len > length: | ||||
|                 # Return the truncated string | ||||
|                 return self.add_truncation_text(text[: end_index or 0], truncate) | ||||
|  | ||||
|         # Return the original string since no truncation was necessary | ||||
|         return text | ||||
|  | ||||
|     def words(self, num, truncate=None, html=False): | ||||
|         """ | ||||
|         Truncate a string after a certain number of words. `truncate` specifies | ||||
|         what should be used to notify that the string has been truncated, | ||||
|         defaulting to ellipsis. | ||||
|         """ | ||||
|         self._setup() | ||||
|         length = int(num) | ||||
|         if html: | ||||
|             return self._truncate_html(length, truncate, self._wrapped, length, True) | ||||
|         return self._text_words(length, truncate) | ||||
|  | ||||
|     def _text_words(self, length, truncate): | ||||
|         """ | ||||
|         Truncate a string after a certain number of words. | ||||
|  | ||||
|         Strip newlines in the string. | ||||
|         """ | ||||
|         words = self._wrapped.split() | ||||
|         if len(words) > length: | ||||
|             words = words[:length] | ||||
|             return self.add_truncation_text(" ".join(words), truncate) | ||||
|         return " ".join(words) | ||||
|  | ||||
|     def _truncate_html(self, length, truncate, text, truncate_len, words): | ||||
|         """ | ||||
|         Truncate HTML to a certain number of chars (not counting tags and | ||||
|         comments), or, if words is True, then to a certain number of words. | ||||
|         Close opened tags if they were correctly closed in the given HTML. | ||||
|  | ||||
|         Preserve newlines in the HTML. | ||||
|         """ | ||||
|         if words and length <= 0: | ||||
|             return "" | ||||
|  | ||||
|         size_limited = False | ||||
|         if len(text) > self.MAX_LENGTH_HTML: | ||||
|             text = text[: self.MAX_LENGTH_HTML] | ||||
|             size_limited = True | ||||
|  | ||||
|         html4_singlets = ( | ||||
|             "br", | ||||
|             "col", | ||||
|             "link", | ||||
|             "base", | ||||
|             "img", | ||||
|             "param", | ||||
|             "area", | ||||
|             "hr", | ||||
|             "input", | ||||
|         ) | ||||
|  | ||||
|         # Count non-HTML chars/words and keep note of open tags | ||||
|         pos = 0 | ||||
|         end_text_pos = 0 | ||||
|         current_len = 0 | ||||
|         open_tags = [] | ||||
|  | ||||
|         regex = re_words if words else re_chars | ||||
|  | ||||
|         while current_len <= length: | ||||
|             m = regex.search(text, pos) | ||||
|             if not m: | ||||
|                 # Checked through whole string | ||||
|                 break | ||||
|             pos = m.end(0) | ||||
|             if m[1]: | ||||
|                 # It's an actual non-HTML word or char | ||||
|                 current_len += 1 | ||||
|                 if current_len == truncate_len: | ||||
|                     end_text_pos = pos | ||||
|                 continue | ||||
|             # Check for tag | ||||
|             tag = re_tag.match(m[0]) | ||||
|             if not tag or current_len >= truncate_len: | ||||
|                 # Don't worry about non tags or tags after our truncate point | ||||
|                 continue | ||||
|             closing_tag, tagname, self_closing = tag.groups() | ||||
|             # Element names are always case-insensitive | ||||
|             tagname = tagname.lower() | ||||
|             if self_closing or tagname in html4_singlets: | ||||
|                 pass | ||||
|             elif closing_tag: | ||||
|                 # Check for match in open tags list | ||||
|                 try: | ||||
|                     i = open_tags.index(tagname) | ||||
|                 except ValueError: | ||||
|                     pass | ||||
|                 else: | ||||
|                     # SGML: An end tag closes, back to the matching start tag, | ||||
|                     # all unclosed intervening start tags with omitted end tags | ||||
|                     open_tags = open_tags[i + 1 :] | ||||
|             else: | ||||
|                 # Add it to the start of the open tags list | ||||
|                 open_tags.insert(0, tagname) | ||||
|  | ||||
|         truncate_text = self.add_truncation_text("", truncate) | ||||
|  | ||||
|         if current_len <= length: | ||||
|             if size_limited and truncate_text: | ||||
|                 text += truncate_text | ||||
|             return text | ||||
|  | ||||
|         out = text[:end_text_pos] | ||||
|         if truncate_text: | ||||
|             out += truncate_text | ||||
|         # Close any tags still open | ||||
|         for tag in open_tags: | ||||
|             out += "</%s>" % tag | ||||
|         # Return string | ||||
|         return out | ||||
|  | ||||
|  | ||||
| @keep_lazy_text | ||||
| def get_valid_filename(name): | ||||
|     """ | ||||
|     Return the given string converted to a string that can be used for a clean | ||||
|     filename. Remove leading and trailing spaces; convert other spaces to | ||||
|     underscores; and remove anything that is not an alphanumeric, dash, | ||||
|     underscore, or dot. | ||||
|     >>> get_valid_filename("john's portrait in 2004.jpg") | ||||
|     'johns_portrait_in_2004.jpg' | ||||
|     """ | ||||
|     s = str(name).strip().replace(" ", "_") | ||||
|     s = re.sub(r"(?u)[^-\w.]", "", s) | ||||
|     if s in {"", ".", ".."}: | ||||
|         raise SuspiciousFileOperation("Could not derive file name from '%s'" % name) | ||||
|     return s | ||||
|  | ||||
|  | ||||
| @keep_lazy_text | ||||
| def get_text_list(list_, last_word=gettext_lazy("or")): | ||||
|     """ | ||||
|     >>> get_text_list(['a', 'b', 'c', 'd']) | ||||
|     'a, b, c or d' | ||||
|     >>> get_text_list(['a', 'b', 'c'], 'and') | ||||
|     'a, b and c' | ||||
|     >>> get_text_list(['a', 'b'], 'and') | ||||
|     'a and b' | ||||
|     >>> get_text_list(['a']) | ||||
|     'a' | ||||
|     >>> get_text_list([]) | ||||
|     '' | ||||
|     """ | ||||
|     if not list_: | ||||
|         return "" | ||||
|     if len(list_) == 1: | ||||
|         return str(list_[0]) | ||||
|     return "%s %s %s" % ( | ||||
|         # Translators: This string is used as a separator between list elements | ||||
|         _(", ").join(str(i) for i in list_[:-1]), | ||||
|         str(last_word), | ||||
|         str(list_[-1]), | ||||
|     ) | ||||
|  | ||||
|  | ||||
| @keep_lazy_text | ||||
| def normalize_newlines(text): | ||||
|     """Normalize CRLF and CR newlines to just LF.""" | ||||
|     return re_newlines.sub("\n", str(text)) | ||||
|  | ||||
|  | ||||
| @keep_lazy_text | ||||
| def phone2numeric(phone): | ||||
|     """Convert a phone number with letters into its numeric equivalent.""" | ||||
|     char2number = { | ||||
|         "a": "2", | ||||
|         "b": "2", | ||||
|         "c": "2", | ||||
|         "d": "3", | ||||
|         "e": "3", | ||||
|         "f": "3", | ||||
|         "g": "4", | ||||
|         "h": "4", | ||||
|         "i": "4", | ||||
|         "j": "5", | ||||
|         "k": "5", | ||||
|         "l": "5", | ||||
|         "m": "6", | ||||
|         "n": "6", | ||||
|         "o": "6", | ||||
|         "p": "7", | ||||
|         "q": "7", | ||||
|         "r": "7", | ||||
|         "s": "7", | ||||
|         "t": "8", | ||||
|         "u": "8", | ||||
|         "v": "8", | ||||
|         "w": "9", | ||||
|         "x": "9", | ||||
|         "y": "9", | ||||
|         "z": "9", | ||||
|     } | ||||
|     return "".join(char2number.get(c, c) for c in phone.lower()) | ||||
|  | ||||
|  | ||||
| def _get_random_filename(max_random_bytes): | ||||
|     return b"a" * secrets.randbelow(max_random_bytes) | ||||
|  | ||||
|  | ||||
| def compress_string(s, *, max_random_bytes=None): | ||||
|     compressed_data = gzip_compress(s, compresslevel=6, mtime=0) | ||||
|  | ||||
|     if not max_random_bytes: | ||||
|         return compressed_data | ||||
|  | ||||
|     compressed_view = memoryview(compressed_data) | ||||
|     header = bytearray(compressed_view[:10]) | ||||
|     header[3] = gzip.FNAME | ||||
|  | ||||
|     filename = _get_random_filename(max_random_bytes) + b"\x00" | ||||
|  | ||||
|     return bytes(header) + filename + compressed_view[10:] | ||||
|  | ||||
|  | ||||
| class StreamingBuffer(BytesIO): | ||||
|     def read(self): | ||||
|         ret = self.getvalue() | ||||
|         self.seek(0) | ||||
|         self.truncate() | ||||
|         return ret | ||||
|  | ||||
|  | ||||
| # Like compress_string, but for iterators of strings. | ||||
| def compress_sequence(sequence, *, max_random_bytes=None): | ||||
|     buf = StreamingBuffer() | ||||
|     filename = _get_random_filename(max_random_bytes) if max_random_bytes else None | ||||
|     with GzipFile( | ||||
|         filename=filename, mode="wb", compresslevel=6, fileobj=buf, mtime=0 | ||||
|     ) as zfile: | ||||
|         # Output headers... | ||||
|         yield buf.read() | ||||
|         for item in sequence: | ||||
|             zfile.write(item) | ||||
|             data = buf.read() | ||||
|             if data: | ||||
|                 yield data | ||||
|     yield buf.read() | ||||
|  | ||||
|  | ||||
| # Expression to match some_token and some_token="with spaces" (and similarly | ||||
| # for single-quoted strings). | ||||
| smart_split_re = _lazy_re_compile( | ||||
|     r""" | ||||
|     ((?: | ||||
|         [^\s'"]* | ||||
|         (?: | ||||
|             (?:"(?:[^"\\]|\\.)*" | '(?:[^'\\]|\\.)*') | ||||
|             [^\s'"]* | ||||
|         )+ | ||||
|     ) | \S+) | ||||
| """, | ||||
|     re.VERBOSE, | ||||
| ) | ||||
|  | ||||
|  | ||||
| def smart_split(text): | ||||
|     r""" | ||||
|     Generator that splits a string by spaces, leaving quoted phrases together. | ||||
|     Supports both single and double quotes, and supports escaping quotes with | ||||
|     backslashes. In the output, strings will keep their initial and trailing | ||||
|     quote marks and escaped quotes will remain escaped (the results can then | ||||
|     be further processed with unescape_string_literal()). | ||||
|  | ||||
|     >>> list(smart_split(r'This is "a person\'s" test.')) | ||||
|     ['This', 'is', '"a person\\\'s"', 'test.'] | ||||
|     >>> list(smart_split(r"Another 'person\'s' test.")) | ||||
|     ['Another', "'person\\'s'", 'test.'] | ||||
|     >>> list(smart_split(r'A "\"funky\" style" test.')) | ||||
|     ['A', '"\\"funky\\" style"', 'test.'] | ||||
|     """ | ||||
|     for bit in smart_split_re.finditer(str(text)): | ||||
|         yield bit[0] | ||||
|  | ||||
|  | ||||
| @keep_lazy_text | ||||
| def unescape_string_literal(s): | ||||
|     r""" | ||||
|     Convert quoted string literals to unquoted strings with escaped quotes and | ||||
|     backslashes unquoted:: | ||||
|  | ||||
|         >>> unescape_string_literal('"abc"') | ||||
|         'abc' | ||||
|         >>> unescape_string_literal("'abc'") | ||||
|         'abc' | ||||
|         >>> unescape_string_literal('"a \"bc\""') | ||||
|         'a "bc"' | ||||
|         >>> unescape_string_literal("'\'ab\' c'") | ||||
|         "'ab' c" | ||||
|     """ | ||||
|     if not s or s[0] not in "\"'" or s[-1] != s[0]: | ||||
|         raise ValueError("Not a string literal: %r" % s) | ||||
|     quote = s[0] | ||||
|     return s[1:-1].replace(r"\%s" % quote, quote).replace(r"\\", "\\") | ||||
|  | ||||
|  | ||||
| @keep_lazy_text | ||||
| def slugify(value, allow_unicode=False): | ||||
|     """ | ||||
|     Convert to ASCII if 'allow_unicode' is False. Convert spaces or repeated | ||||
|     dashes to single dashes. Remove characters that aren't alphanumerics, | ||||
|     underscores, or hyphens. Convert to lowercase. Also strip leading and | ||||
|     trailing whitespace, dashes, and underscores. | ||||
|     """ | ||||
|     value = str(value) | ||||
|     if allow_unicode: | ||||
|         value = unicodedata.normalize("NFKC", value) | ||||
|     else: | ||||
|         value = ( | ||||
|             unicodedata.normalize("NFKD", value) | ||||
|             .encode("ascii", "ignore") | ||||
|             .decode("ascii") | ||||
|         ) | ||||
|     value = re.sub(r"[^\w\s-]", "", value.lower()) | ||||
|     return re.sub(r"[-\s]+", "-", value).strip("-_") | ||||
|  | ||||
|  | ||||
| def camel_case_to_spaces(value): | ||||
|     """ | ||||
|     Split CamelCase and convert to lowercase. Strip surrounding whitespace. | ||||
|     """ | ||||
|     return re_camel_case.sub(r" \1", value).strip().lower() | ||||
|  | ||||
|  | ||||
| def _format_lazy(format_string, *args, **kwargs): | ||||
|     """ | ||||
|     Apply str.format() on 'format_string' where format_string, args, | ||||
|     and/or kwargs might be lazy. | ||||
|     """ | ||||
|     return format_string.format(*args, **kwargs) | ||||
|  | ||||
|  | ||||
| format_lazy = lazy(_format_lazy, str) | ||||
| @ -0,0 +1,142 @@ | ||||
| import datetime | ||||
|  | ||||
| from django.utils.html import avoid_wrapping | ||||
| from django.utils.timezone import is_aware | ||||
| from django.utils.translation import gettext, ngettext_lazy | ||||
|  | ||||
| TIME_STRINGS = { | ||||
|     "year": ngettext_lazy("%(num)d year", "%(num)d years", "num"), | ||||
|     "month": ngettext_lazy("%(num)d month", "%(num)d months", "num"), | ||||
|     "week": ngettext_lazy("%(num)d week", "%(num)d weeks", "num"), | ||||
|     "day": ngettext_lazy("%(num)d day", "%(num)d days", "num"), | ||||
|     "hour": ngettext_lazy("%(num)d hour", "%(num)d hours", "num"), | ||||
|     "minute": ngettext_lazy("%(num)d minute", "%(num)d minutes", "num"), | ||||
| } | ||||
|  | ||||
| TIME_STRINGS_KEYS = list(TIME_STRINGS.keys()) | ||||
|  | ||||
| TIME_CHUNKS = [ | ||||
|     60 * 60 * 24 * 7,  # week | ||||
|     60 * 60 * 24,  # day | ||||
|     60 * 60,  # hour | ||||
|     60,  # minute | ||||
| ] | ||||
|  | ||||
| MONTHS_DAYS = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) | ||||
|  | ||||
|  | ||||
| def timesince(d, now=None, reversed=False, time_strings=None, depth=2): | ||||
|     """ | ||||
|     Take two datetime objects and return the time between d and now as a nicely | ||||
|     formatted string, e.g. "10 minutes". If d occurs after now, return | ||||
|     "0 minutes". | ||||
|  | ||||
|     Units used are years, months, weeks, days, hours, and minutes. | ||||
|     Seconds and microseconds are ignored. | ||||
|  | ||||
|     The algorithm takes into account the varying duration of years and months. | ||||
|     There is exactly "1 year, 1 month" between 2013/02/10 and 2014/03/10, | ||||
|     but also between 2007/08/10 and 2008/09/10 despite the delta being 393 days | ||||
|     in the former case and 397 in the latter. | ||||
|  | ||||
|     Up to `depth` adjacent units will be displayed.  For example, | ||||
|     "2 weeks, 3 days" and "1 year, 3 months" are possible outputs, but | ||||
|     "2 weeks, 3 hours" and "1 year, 5 days" are not. | ||||
|  | ||||
|     `time_strings` is an optional dict of strings to replace the default | ||||
|     TIME_STRINGS dict. | ||||
|  | ||||
|     `depth` is an optional integer to control the number of adjacent time | ||||
|     units returned. | ||||
|  | ||||
|     Originally adapted from | ||||
|     https://web.archive.org/web/20060617175230/http://blog.natbat.co.uk/archive/2003/Jun/14/time_since | ||||
|     Modified to improve results for years and months. | ||||
|     """ | ||||
|     if time_strings is None: | ||||
|         time_strings = TIME_STRINGS | ||||
|     if depth <= 0: | ||||
|         raise ValueError("depth must be greater than 0.") | ||||
|     # Convert datetime.date to datetime.datetime for comparison. | ||||
|     if not isinstance(d, datetime.datetime): | ||||
|         d = datetime.datetime(d.year, d.month, d.day) | ||||
|     if now and not isinstance(now, datetime.datetime): | ||||
|         now = datetime.datetime(now.year, now.month, now.day) | ||||
|  | ||||
|     # Compared datetimes must be in the same time zone. | ||||
|     if not now: | ||||
|         now = datetime.datetime.now(d.tzinfo if is_aware(d) else None) | ||||
|     elif is_aware(now) and is_aware(d): | ||||
|         now = now.astimezone(d.tzinfo) | ||||
|  | ||||
|     if reversed: | ||||
|         d, now = now, d | ||||
|     delta = now - d | ||||
|  | ||||
|     # Ignore microseconds. | ||||
|     since = delta.days * 24 * 60 * 60 + delta.seconds | ||||
|     if since <= 0: | ||||
|         # d is in the future compared to now, stop processing. | ||||
|         return avoid_wrapping(time_strings["minute"] % {"num": 0}) | ||||
|  | ||||
|     # Get years and months. | ||||
|     total_months = (now.year - d.year) * 12 + (now.month - d.month) | ||||
|     if d.day > now.day or (d.day == now.day and d.time() > now.time()): | ||||
|         total_months -= 1 | ||||
|     years, months = divmod(total_months, 12) | ||||
|  | ||||
|     # Calculate the remaining time. | ||||
|     # Create a "pivot" datetime shifted from d by years and months, then use | ||||
|     # that to determine the other parts. | ||||
|     if years or months: | ||||
|         pivot_year = d.year + years | ||||
|         pivot_month = d.month + months | ||||
|         if pivot_month > 12: | ||||
|             pivot_month -= 12 | ||||
|             pivot_year += 1 | ||||
|         pivot = datetime.datetime( | ||||
|             pivot_year, | ||||
|             pivot_month, | ||||
|             min(MONTHS_DAYS[pivot_month - 1], d.day), | ||||
|             d.hour, | ||||
|             d.minute, | ||||
|             d.second, | ||||
|             tzinfo=d.tzinfo, | ||||
|         ) | ||||
|     else: | ||||
|         pivot = d | ||||
|     remaining_time = (now - pivot).total_seconds() | ||||
|     partials = [years, months] | ||||
|     for chunk in TIME_CHUNKS: | ||||
|         count = int(remaining_time // chunk) | ||||
|         partials.append(count) | ||||
|         remaining_time -= chunk * count | ||||
|  | ||||
|     # Find the first non-zero part (if any) and then build the result, until | ||||
|     # depth. | ||||
|     i = 0 | ||||
|     for i, value in enumerate(partials): | ||||
|         if value != 0: | ||||
|             break | ||||
|     else: | ||||
|         return avoid_wrapping(time_strings["minute"] % {"num": 0}) | ||||
|  | ||||
|     result = [] | ||||
|     current_depth = 0 | ||||
|     while i < len(TIME_STRINGS_KEYS) and current_depth < depth: | ||||
|         value = partials[i] | ||||
|         if value == 0: | ||||
|             break | ||||
|         name = TIME_STRINGS_KEYS[i] | ||||
|         result.append(avoid_wrapping(time_strings[name] % {"num": value})) | ||||
|         current_depth += 1 | ||||
|         i += 1 | ||||
|  | ||||
|     return gettext(", ").join(result) | ||||
|  | ||||
|  | ||||
| def timeuntil(d, now=None, time_strings=None, depth=2): | ||||
|     """ | ||||
|     Like timesince, but return a string measuring the time until the given time. | ||||
|     """ | ||||
|     return timesince(d, now, reversed=True, time_strings=time_strings, depth=depth) | ||||
							
								
								
									
										361
									
								
								srcs/.venv/lib/python3.11/site-packages/django/utils/timezone.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										361
									
								
								srcs/.venv/lib/python3.11/site-packages/django/utils/timezone.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,361 @@ | ||||
| """ | ||||
| Timezone-related classes and functions. | ||||
| """ | ||||
|  | ||||
| import functools | ||||
| import sys | ||||
| import warnings | ||||
|  | ||||
| try: | ||||
|     import zoneinfo | ||||
| except ImportError: | ||||
|     from backports import zoneinfo | ||||
|  | ||||
| from contextlib import ContextDecorator | ||||
| from datetime import datetime, timedelta, timezone, tzinfo | ||||
|  | ||||
| from asgiref.local import Local | ||||
|  | ||||
| from django.conf import settings | ||||
| from django.utils.deprecation import RemovedInDjango50Warning | ||||
|  | ||||
| __all__ = [  # noqa for utc RemovedInDjango50Warning. | ||||
|     "utc", | ||||
|     "get_fixed_timezone", | ||||
|     "get_default_timezone", | ||||
|     "get_default_timezone_name", | ||||
|     "get_current_timezone", | ||||
|     "get_current_timezone_name", | ||||
|     "activate", | ||||
|     "deactivate", | ||||
|     "override", | ||||
|     "localtime", | ||||
|     "localdate", | ||||
|     "now", | ||||
|     "is_aware", | ||||
|     "is_naive", | ||||
|     "make_aware", | ||||
|     "make_naive", | ||||
| ] | ||||
|  | ||||
| # RemovedInDjango50Warning: sentinel for deprecation of is_dst parameters. | ||||
| NOT_PASSED = object() | ||||
|  | ||||
|  | ||||
| def __getattr__(name): | ||||
|     if name != "utc": | ||||
|         raise AttributeError(f"module {__name__!r} has no attribute {name!r}") | ||||
|  | ||||
|     warnings.warn( | ||||
|         "The django.utils.timezone.utc alias is deprecated. " | ||||
|         "Please update your code to use datetime.timezone.utc instead.", | ||||
|         RemovedInDjango50Warning, | ||||
|         stacklevel=2, | ||||
|     ) | ||||
|  | ||||
|     return timezone.utc | ||||
|  | ||||
|  | ||||
| def get_fixed_timezone(offset): | ||||
|     """Return a tzinfo instance with a fixed offset from UTC.""" | ||||
|     if isinstance(offset, timedelta): | ||||
|         offset = offset.total_seconds() // 60 | ||||
|     sign = "-" if offset < 0 else "+" | ||||
|     hhmm = "%02d%02d" % divmod(abs(offset), 60) | ||||
|     name = sign + hhmm | ||||
|     return timezone(timedelta(minutes=offset), name) | ||||
|  | ||||
|  | ||||
| # In order to avoid accessing settings at compile time, | ||||
| # wrap the logic in a function and cache the result. | ||||
| @functools.lru_cache | ||||
| def get_default_timezone(): | ||||
|     """ | ||||
|     Return the default time zone as a tzinfo instance. | ||||
|  | ||||
|     This is the time zone defined by settings.TIME_ZONE. | ||||
|     """ | ||||
|     if settings.USE_DEPRECATED_PYTZ: | ||||
|         import pytz | ||||
|  | ||||
|         return pytz.timezone(settings.TIME_ZONE) | ||||
|     return zoneinfo.ZoneInfo(settings.TIME_ZONE) | ||||
|  | ||||
|  | ||||
| # This function exists for consistency with get_current_timezone_name | ||||
| def get_default_timezone_name(): | ||||
|     """Return the name of the default time zone.""" | ||||
|     return _get_timezone_name(get_default_timezone()) | ||||
|  | ||||
|  | ||||
| _active = Local() | ||||
|  | ||||
|  | ||||
| def get_current_timezone(): | ||||
|     """Return the currently active time zone as a tzinfo instance.""" | ||||
|     return getattr(_active, "value", get_default_timezone()) | ||||
|  | ||||
|  | ||||
| def get_current_timezone_name(): | ||||
|     """Return the name of the currently active time zone.""" | ||||
|     return _get_timezone_name(get_current_timezone()) | ||||
|  | ||||
|  | ||||
| def _get_timezone_name(timezone): | ||||
|     """ | ||||
|     Return the offset for fixed offset timezones, or the name of timezone if | ||||
|     not set. | ||||
|     """ | ||||
|     return timezone.tzname(None) or str(timezone) | ||||
|  | ||||
|  | ||||
| # Timezone selection functions. | ||||
|  | ||||
| # These functions don't change os.environ['TZ'] and call time.tzset() | ||||
| # because it isn't thread safe. | ||||
|  | ||||
|  | ||||
| def activate(timezone): | ||||
|     """ | ||||
|     Set the time zone for the current thread. | ||||
|  | ||||
|     The ``timezone`` argument must be an instance of a tzinfo subclass or a | ||||
|     time zone name. | ||||
|     """ | ||||
|     if isinstance(timezone, tzinfo): | ||||
|         _active.value = timezone | ||||
|     elif isinstance(timezone, str): | ||||
|         if settings.USE_DEPRECATED_PYTZ: | ||||
|             import pytz | ||||
|  | ||||
|             _active.value = pytz.timezone(timezone) | ||||
|         else: | ||||
|             _active.value = zoneinfo.ZoneInfo(timezone) | ||||
|     else: | ||||
|         raise ValueError("Invalid timezone: %r" % timezone) | ||||
|  | ||||
|  | ||||
| def deactivate(): | ||||
|     """ | ||||
|     Unset the time zone for the current thread. | ||||
|  | ||||
|     Django will then use the time zone defined by settings.TIME_ZONE. | ||||
|     """ | ||||
|     if hasattr(_active, "value"): | ||||
|         del _active.value | ||||
|  | ||||
|  | ||||
| class override(ContextDecorator): | ||||
|     """ | ||||
|     Temporarily set the time zone for the current thread. | ||||
|  | ||||
|     This is a context manager that uses django.utils.timezone.activate() | ||||
|     to set the timezone on entry and restores the previously active timezone | ||||
|     on exit. | ||||
|  | ||||
|     The ``timezone`` argument must be an instance of a ``tzinfo`` subclass, a | ||||
|     time zone name, or ``None``. If it is ``None``, Django enables the default | ||||
|     time zone. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, timezone): | ||||
|         self.timezone = timezone | ||||
|  | ||||
|     def __enter__(self): | ||||
|         self.old_timezone = getattr(_active, "value", None) | ||||
|         if self.timezone is None: | ||||
|             deactivate() | ||||
|         else: | ||||
|             activate(self.timezone) | ||||
|  | ||||
|     def __exit__(self, exc_type, exc_value, traceback): | ||||
|         if self.old_timezone is None: | ||||
|             deactivate() | ||||
|         else: | ||||
|             _active.value = self.old_timezone | ||||
|  | ||||
|  | ||||
| # Templates | ||||
|  | ||||
|  | ||||
| def template_localtime(value, use_tz=None): | ||||
|     """ | ||||
|     Check if value is a datetime and converts it to local time if necessary. | ||||
|  | ||||
|     If use_tz is provided and is not None, that will force the value to | ||||
|     be converted (or not), overriding the value of settings.USE_TZ. | ||||
|  | ||||
|     This function is designed for use by the template engine. | ||||
|     """ | ||||
|     should_convert = ( | ||||
|         isinstance(value, datetime) | ||||
|         and (settings.USE_TZ if use_tz is None else use_tz) | ||||
|         and not is_naive(value) | ||||
|         and getattr(value, "convert_to_local_time", True) | ||||
|     ) | ||||
|     return localtime(value) if should_convert else value | ||||
|  | ||||
|  | ||||
| # Utilities | ||||
|  | ||||
|  | ||||
| def localtime(value=None, timezone=None): | ||||
|     """ | ||||
|     Convert an aware datetime.datetime to local time. | ||||
|  | ||||
|     Only aware datetimes are allowed. When value is omitted, it defaults to | ||||
|     now(). | ||||
|  | ||||
|     Local time is defined by the current time zone, unless another time zone | ||||
|     is specified. | ||||
|     """ | ||||
|     if value is None: | ||||
|         value = now() | ||||
|     if timezone is None: | ||||
|         timezone = get_current_timezone() | ||||
|     # Emulate the behavior of astimezone() on Python < 3.6. | ||||
|     if is_naive(value): | ||||
|         raise ValueError("localtime() cannot be applied to a naive datetime") | ||||
|     return value.astimezone(timezone) | ||||
|  | ||||
|  | ||||
| def localdate(value=None, timezone=None): | ||||
|     """ | ||||
|     Convert an aware datetime to local time and return the value's date. | ||||
|  | ||||
|     Only aware datetimes are allowed. When value is omitted, it defaults to | ||||
|     now(). | ||||
|  | ||||
|     Local time is defined by the current time zone, unless another time zone is | ||||
|     specified. | ||||
|     """ | ||||
|     return localtime(value, timezone).date() | ||||
|  | ||||
|  | ||||
| def now(): | ||||
|     """ | ||||
|     Return an aware or naive datetime.datetime, depending on settings.USE_TZ. | ||||
|     """ | ||||
|     return datetime.now(tz=timezone.utc if settings.USE_TZ else None) | ||||
|  | ||||
|  | ||||
| # By design, these four functions don't perform any checks on their arguments. | ||||
| # The caller should ensure that they don't receive an invalid value like None. | ||||
|  | ||||
|  | ||||
| def is_aware(value): | ||||
|     """ | ||||
|     Determine if a given datetime.datetime is aware. | ||||
|  | ||||
|     The concept is defined in Python's docs: | ||||
|     https://docs.python.org/library/datetime.html#datetime.tzinfo | ||||
|  | ||||
|     Assuming value.tzinfo is either None or a proper datetime.tzinfo, | ||||
|     value.utcoffset() implements the appropriate logic. | ||||
|     """ | ||||
|     return value.utcoffset() is not None | ||||
|  | ||||
|  | ||||
| def is_naive(value): | ||||
|     """ | ||||
|     Determine if a given datetime.datetime is naive. | ||||
|  | ||||
|     The concept is defined in Python's docs: | ||||
|     https://docs.python.org/library/datetime.html#datetime.tzinfo | ||||
|  | ||||
|     Assuming value.tzinfo is either None or a proper datetime.tzinfo, | ||||
|     value.utcoffset() implements the appropriate logic. | ||||
|     """ | ||||
|     return value.utcoffset() is None | ||||
|  | ||||
|  | ||||
| def make_aware(value, timezone=None, is_dst=NOT_PASSED): | ||||
|     """Make a naive datetime.datetime in a given time zone aware.""" | ||||
|     if is_dst is NOT_PASSED: | ||||
|         is_dst = None | ||||
|     else: | ||||
|         warnings.warn( | ||||
|             "The is_dst argument to make_aware(), used by the Trunc() " | ||||
|             "database functions and QuerySet.datetimes(), is deprecated as it " | ||||
|             "has no effect with zoneinfo time zones.", | ||||
|             RemovedInDjango50Warning, | ||||
|         ) | ||||
|     if timezone is None: | ||||
|         timezone = get_current_timezone() | ||||
|     if _is_pytz_zone(timezone): | ||||
|         # This method is available for pytz time zones. | ||||
|         return timezone.localize(value, is_dst=is_dst) | ||||
|     else: | ||||
|         # Check that we won't overwrite the timezone of an aware datetime. | ||||
|         if is_aware(value): | ||||
|             raise ValueError("make_aware expects a naive datetime, got %s" % value) | ||||
|         # This may be wrong around DST changes! | ||||
|         return value.replace(tzinfo=timezone) | ||||
|  | ||||
|  | ||||
| def make_naive(value, timezone=None): | ||||
|     """Make an aware datetime.datetime naive in a given time zone.""" | ||||
|     if timezone is None: | ||||
|         timezone = get_current_timezone() | ||||
|     # Emulate the behavior of astimezone() on Python < 3.6. | ||||
|     if is_naive(value): | ||||
|         raise ValueError("make_naive() cannot be applied to a naive datetime") | ||||
|     return value.astimezone(timezone).replace(tzinfo=None) | ||||
|  | ||||
|  | ||||
| _PYTZ_IMPORTED = False | ||||
|  | ||||
|  | ||||
| def _pytz_imported(): | ||||
|     """ | ||||
|     Detects whether or not pytz has been imported without importing pytz. | ||||
|  | ||||
|     Copied from pytz_deprecation_shim with thanks to Paul Ganssle. | ||||
|     """ | ||||
|     global _PYTZ_IMPORTED | ||||
|  | ||||
|     if not _PYTZ_IMPORTED and "pytz" in sys.modules: | ||||
|         _PYTZ_IMPORTED = True | ||||
|  | ||||
|     return _PYTZ_IMPORTED | ||||
|  | ||||
|  | ||||
| def _is_pytz_zone(tz): | ||||
|     """Checks if a zone is a pytz zone.""" | ||||
|     # See if pytz was already imported rather than checking | ||||
|     # settings.USE_DEPRECATED_PYTZ to *allow* manually passing a pytz timezone, | ||||
|     # which some of the test cases (at least) rely on. | ||||
|     if not _pytz_imported(): | ||||
|         return False | ||||
|  | ||||
|     # If tz could be pytz, then pytz is needed here. | ||||
|     import pytz | ||||
|  | ||||
|     _PYTZ_BASE_CLASSES = (pytz.tzinfo.BaseTzInfo, pytz._FixedOffset) | ||||
|     # In releases prior to 2018.4, pytz.UTC was not a subclass of BaseTzInfo | ||||
|     if not isinstance(pytz.UTC, pytz._FixedOffset): | ||||
|         _PYTZ_BASE_CLASSES += (type(pytz.UTC),) | ||||
|  | ||||
|     return isinstance(tz, _PYTZ_BASE_CLASSES) | ||||
|  | ||||
|  | ||||
| def _datetime_ambiguous_or_imaginary(dt, tz): | ||||
|     if _is_pytz_zone(tz): | ||||
|         import pytz | ||||
|  | ||||
|         try: | ||||
|             tz.utcoffset(dt) | ||||
|         except (pytz.AmbiguousTimeError, pytz.NonExistentTimeError): | ||||
|             return True | ||||
|         else: | ||||
|             return False | ||||
|  | ||||
|     return tz.utcoffset(dt.replace(fold=not dt.fold)) != tz.utcoffset(dt) | ||||
|  | ||||
|  | ||||
| # RemovedInDjango50Warning. | ||||
| _DIR = dir() | ||||
|  | ||||
|  | ||||
| def __dir__(): | ||||
|     return sorted([*_DIR, "utc"]) | ||||
| @ -0,0 +1,42 @@ | ||||
| class CyclicDependencyError(ValueError): | ||||
|     pass | ||||
|  | ||||
|  | ||||
| def topological_sort_as_sets(dependency_graph): | ||||
|     """ | ||||
|     Variation of Kahn's algorithm (1962) that returns sets. | ||||
|  | ||||
|     Take a dependency graph as a dictionary of node => dependencies. | ||||
|  | ||||
|     Yield sets of items in topological order, where the first set contains | ||||
|     all nodes without dependencies, and each following set contains all | ||||
|     nodes that may depend on the nodes only in the previously yielded sets. | ||||
|     """ | ||||
|     todo = dependency_graph.copy() | ||||
|     while todo: | ||||
|         current = {node for node, deps in todo.items() if not deps} | ||||
|  | ||||
|         if not current: | ||||
|             raise CyclicDependencyError( | ||||
|                 "Cyclic dependency in graph: {}".format( | ||||
|                     ", ".join(repr(x) for x in todo.items()) | ||||
|                 ) | ||||
|             ) | ||||
|  | ||||
|         yield current | ||||
|  | ||||
|         # remove current from todo's nodes & dependencies | ||||
|         todo = { | ||||
|             node: (dependencies - current) | ||||
|             for node, dependencies in todo.items() | ||||
|             if node not in current | ||||
|         } | ||||
|  | ||||
|  | ||||
| def stable_topological_sort(nodes, dependency_graph): | ||||
|     result = [] | ||||
|     for layer in topological_sort_as_sets(dependency_graph): | ||||
|         for node in nodes: | ||||
|             if node in layer: | ||||
|                 result.append(node) | ||||
|     return result | ||||
| @ -0,0 +1,301 @@ | ||||
| """ | ||||
| Internationalization support. | ||||
| """ | ||||
| from contextlib import ContextDecorator | ||||
| from decimal import ROUND_UP, Decimal | ||||
|  | ||||
| from django.utils.autoreload import autoreload_started, file_changed | ||||
| from django.utils.functional import lazy | ||||
| from django.utils.regex_helper import _lazy_re_compile | ||||
|  | ||||
| __all__ = [ | ||||
|     "activate", | ||||
|     "deactivate", | ||||
|     "override", | ||||
|     "deactivate_all", | ||||
|     "get_language", | ||||
|     "get_language_from_request", | ||||
|     "get_language_info", | ||||
|     "get_language_bidi", | ||||
|     "check_for_language", | ||||
|     "to_language", | ||||
|     "to_locale", | ||||
|     "templatize", | ||||
|     "gettext", | ||||
|     "gettext_lazy", | ||||
|     "gettext_noop", | ||||
|     "ngettext", | ||||
|     "ngettext_lazy", | ||||
|     "pgettext", | ||||
|     "pgettext_lazy", | ||||
|     "npgettext", | ||||
|     "npgettext_lazy", | ||||
| ] | ||||
|  | ||||
|  | ||||
| class TranslatorCommentWarning(SyntaxWarning): | ||||
|     pass | ||||
|  | ||||
|  | ||||
| # Here be dragons, so a short explanation of the logic won't hurt: | ||||
| # We are trying to solve two problems: (1) access settings, in particular | ||||
| # settings.USE_I18N, as late as possible, so that modules can be imported | ||||
| # without having to first configure Django, and (2) if some other code creates | ||||
| # a reference to one of these functions, don't break that reference when we | ||||
| # replace the functions with their real counterparts (once we do access the | ||||
| # settings). | ||||
|  | ||||
|  | ||||
| class Trans: | ||||
|     """ | ||||
|     The purpose of this class is to store the actual translation function upon | ||||
|     receiving the first call to that function. After this is done, changes to | ||||
|     USE_I18N will have no effect to which function is served upon request. If | ||||
|     your tests rely on changing USE_I18N, you can delete all the functions | ||||
|     from _trans.__dict__. | ||||
|  | ||||
|     Note that storing the function with setattr will have a noticeable | ||||
|     performance effect, as access to the function goes the normal path, | ||||
|     instead of using __getattr__. | ||||
|     """ | ||||
|  | ||||
|     def __getattr__(self, real_name): | ||||
|         from django.conf import settings | ||||
|  | ||||
|         if settings.USE_I18N: | ||||
|             from django.utils.translation import trans_real as trans | ||||
|             from django.utils.translation.reloader import ( | ||||
|                 translation_file_changed, | ||||
|                 watch_for_translation_changes, | ||||
|             ) | ||||
|  | ||||
|             autoreload_started.connect( | ||||
|                 watch_for_translation_changes, dispatch_uid="translation_file_changed" | ||||
|             ) | ||||
|             file_changed.connect( | ||||
|                 translation_file_changed, dispatch_uid="translation_file_changed" | ||||
|             ) | ||||
|         else: | ||||
|             from django.utils.translation import trans_null as trans | ||||
|         setattr(self, real_name, getattr(trans, real_name)) | ||||
|         return getattr(trans, real_name) | ||||
|  | ||||
|  | ||||
| _trans = Trans() | ||||
|  | ||||
| # The Trans class is no more needed, so remove it from the namespace. | ||||
| del Trans | ||||
|  | ||||
|  | ||||
| def gettext_noop(message): | ||||
|     return _trans.gettext_noop(message) | ||||
|  | ||||
|  | ||||
| def gettext(message): | ||||
|     return _trans.gettext(message) | ||||
|  | ||||
|  | ||||
| def ngettext(singular, plural, number): | ||||
|     return _trans.ngettext(singular, plural, number) | ||||
|  | ||||
|  | ||||
| def pgettext(context, message): | ||||
|     return _trans.pgettext(context, message) | ||||
|  | ||||
|  | ||||
| def npgettext(context, singular, plural, number): | ||||
|     return _trans.npgettext(context, singular, plural, number) | ||||
|  | ||||
|  | ||||
| gettext_lazy = lazy(gettext, str) | ||||
| pgettext_lazy = lazy(pgettext, str) | ||||
|  | ||||
|  | ||||
| def lazy_number(func, resultclass, number=None, **kwargs): | ||||
|     if isinstance(number, int): | ||||
|         kwargs["number"] = number | ||||
|         proxy = lazy(func, resultclass)(**kwargs) | ||||
|     else: | ||||
|         original_kwargs = kwargs.copy() | ||||
|  | ||||
|         class NumberAwareString(resultclass): | ||||
|             def __bool__(self): | ||||
|                 return bool(kwargs["singular"]) | ||||
|  | ||||
|             def _get_number_value(self, values): | ||||
|                 try: | ||||
|                     return values[number] | ||||
|                 except KeyError: | ||||
|                     raise KeyError( | ||||
|                         "Your dictionary lacks key '%s'. Please provide " | ||||
|                         "it, because it is required to determine whether " | ||||
|                         "string is singular or plural." % number | ||||
|                     ) | ||||
|  | ||||
|             def _translate(self, number_value): | ||||
|                 kwargs["number"] = number_value | ||||
|                 return func(**kwargs) | ||||
|  | ||||
|             def format(self, *args, **kwargs): | ||||
|                 number_value = ( | ||||
|                     self._get_number_value(kwargs) if kwargs and number else args[0] | ||||
|                 ) | ||||
|                 return self._translate(number_value).format(*args, **kwargs) | ||||
|  | ||||
|             def __mod__(self, rhs): | ||||
|                 if isinstance(rhs, dict) and number: | ||||
|                     number_value = self._get_number_value(rhs) | ||||
|                 else: | ||||
|                     number_value = rhs | ||||
|                 translated = self._translate(number_value) | ||||
|                 try: | ||||
|                     translated %= rhs | ||||
|                 except TypeError: | ||||
|                     # String doesn't contain a placeholder for the number. | ||||
|                     pass | ||||
|                 return translated | ||||
|  | ||||
|         proxy = lazy(lambda **kwargs: NumberAwareString(), NumberAwareString)(**kwargs) | ||||
|         proxy.__reduce__ = lambda: ( | ||||
|             _lazy_number_unpickle, | ||||
|             (func, resultclass, number, original_kwargs), | ||||
|         ) | ||||
|     return proxy | ||||
|  | ||||
|  | ||||
| def _lazy_number_unpickle(func, resultclass, number, kwargs): | ||||
|     return lazy_number(func, resultclass, number=number, **kwargs) | ||||
|  | ||||
|  | ||||
| def ngettext_lazy(singular, plural, number=None): | ||||
|     return lazy_number(ngettext, str, singular=singular, plural=plural, number=number) | ||||
|  | ||||
|  | ||||
| def npgettext_lazy(context, singular, plural, number=None): | ||||
|     return lazy_number( | ||||
|         npgettext, str, context=context, singular=singular, plural=plural, number=number | ||||
|     ) | ||||
|  | ||||
|  | ||||
| def activate(language): | ||||
|     return _trans.activate(language) | ||||
|  | ||||
|  | ||||
| def deactivate(): | ||||
|     return _trans.deactivate() | ||||
|  | ||||
|  | ||||
| class override(ContextDecorator): | ||||
|     def __init__(self, language, deactivate=False): | ||||
|         self.language = language | ||||
|         self.deactivate = deactivate | ||||
|  | ||||
|     def __enter__(self): | ||||
|         self.old_language = get_language() | ||||
|         if self.language is not None: | ||||
|             activate(self.language) | ||||
|         else: | ||||
|             deactivate_all() | ||||
|  | ||||
|     def __exit__(self, exc_type, exc_value, traceback): | ||||
|         if self.old_language is None: | ||||
|             deactivate_all() | ||||
|         elif self.deactivate: | ||||
|             deactivate() | ||||
|         else: | ||||
|             activate(self.old_language) | ||||
|  | ||||
|  | ||||
| def get_language(): | ||||
|     return _trans.get_language() | ||||
|  | ||||
|  | ||||
| def get_language_bidi(): | ||||
|     return _trans.get_language_bidi() | ||||
|  | ||||
|  | ||||
| def check_for_language(lang_code): | ||||
|     return _trans.check_for_language(lang_code) | ||||
|  | ||||
|  | ||||
| def to_language(locale): | ||||
|     """Turn a locale name (en_US) into a language name (en-us).""" | ||||
|     p = locale.find("_") | ||||
|     if p >= 0: | ||||
|         return locale[:p].lower() + "-" + locale[p + 1 :].lower() | ||||
|     else: | ||||
|         return locale.lower() | ||||
|  | ||||
|  | ||||
| def to_locale(language): | ||||
|     """Turn a language name (en-us) into a locale name (en_US).""" | ||||
|     lang, _, country = language.lower().partition("-") | ||||
|     if not country: | ||||
|         return language[:3].lower() + language[3:] | ||||
|     # A language with > 2 characters after the dash only has its first | ||||
|     # character after the dash capitalized; e.g. sr-latn becomes sr_Latn. | ||||
|     # A language with 2 characters after the dash has both characters | ||||
|     # capitalized; e.g. en-us becomes en_US. | ||||
|     country, _, tail = country.partition("-") | ||||
|     country = country.title() if len(country) > 2 else country.upper() | ||||
|     if tail: | ||||
|         country += "-" + tail | ||||
|     return lang + "_" + country | ||||
|  | ||||
|  | ||||
| def get_language_from_request(request, check_path=False): | ||||
|     return _trans.get_language_from_request(request, check_path) | ||||
|  | ||||
|  | ||||
| def get_language_from_path(path): | ||||
|     return _trans.get_language_from_path(path) | ||||
|  | ||||
|  | ||||
| def get_supported_language_variant(lang_code, *, strict=False): | ||||
|     return _trans.get_supported_language_variant(lang_code, strict) | ||||
|  | ||||
|  | ||||
| def templatize(src, **kwargs): | ||||
|     from .template import templatize | ||||
|  | ||||
|     return templatize(src, **kwargs) | ||||
|  | ||||
|  | ||||
| def deactivate_all(): | ||||
|     return _trans.deactivate_all() | ||||
|  | ||||
|  | ||||
| def get_language_info(lang_code): | ||||
|     from django.conf.locale import LANG_INFO | ||||
|  | ||||
|     try: | ||||
|         lang_info = LANG_INFO[lang_code] | ||||
|         if "fallback" in lang_info and "name" not in lang_info: | ||||
|             info = get_language_info(lang_info["fallback"][0]) | ||||
|         else: | ||||
|             info = lang_info | ||||
|     except KeyError: | ||||
|         if "-" not in lang_code: | ||||
|             raise KeyError("Unknown language code %s." % lang_code) | ||||
|         generic_lang_code = lang_code.split("-")[0] | ||||
|         try: | ||||
|             info = LANG_INFO[generic_lang_code] | ||||
|         except KeyError: | ||||
|             raise KeyError( | ||||
|                 "Unknown language code %s and %s." % (lang_code, generic_lang_code) | ||||
|             ) | ||||
|  | ||||
|     if info: | ||||
|         info["name_translated"] = gettext_lazy(info["name"]) | ||||
|     return info | ||||
|  | ||||
|  | ||||
| trim_whitespace_re = _lazy_re_compile(r"\s*\n\s*") | ||||
|  | ||||
|  | ||||
| def trim_whitespace(s): | ||||
|     return trim_whitespace_re.sub(" ", s.strip()) | ||||
|  | ||||
|  | ||||
| def round_away_from_one(value): | ||||
|     return int(Decimal(value - 1).quantize(Decimal("0"), rounding=ROUND_UP)) + 1 | ||||
| @ -0,0 +1,36 @@ | ||||
| from pathlib import Path | ||||
|  | ||||
| from asgiref.local import Local | ||||
|  | ||||
| from django.apps import apps | ||||
| from django.utils.autoreload import is_django_module | ||||
|  | ||||
|  | ||||
| def watch_for_translation_changes(sender, **kwargs): | ||||
|     """Register file watchers for .mo files in potential locale paths.""" | ||||
|     from django.conf import settings | ||||
|  | ||||
|     if settings.USE_I18N: | ||||
|         directories = [Path("locale")] | ||||
|         directories.extend( | ||||
|             Path(config.path) / "locale" | ||||
|             for config in apps.get_app_configs() | ||||
|             if not is_django_module(config.module) | ||||
|         ) | ||||
|         directories.extend(Path(p) for p in settings.LOCALE_PATHS) | ||||
|         for path in directories: | ||||
|             sender.watch_dir(path, "**/*.mo") | ||||
|  | ||||
|  | ||||
| def translation_file_changed(sender, file_path, **kwargs): | ||||
|     """Clear the internal translations cache if a .mo file is modified.""" | ||||
|     if file_path.suffix == ".mo": | ||||
|         import gettext | ||||
|  | ||||
|         from django.utils.translation import trans_real | ||||
|  | ||||
|         gettext._translations = {} | ||||
|         trans_real._translations = {} | ||||
|         trans_real._default = None | ||||
|         trans_real._active = Local() | ||||
|         return True | ||||
| @ -0,0 +1,246 @@ | ||||
| import warnings | ||||
| from io import StringIO | ||||
|  | ||||
| from django.template.base import Lexer, TokenType | ||||
| from django.utils.regex_helper import _lazy_re_compile | ||||
|  | ||||
| from . import TranslatorCommentWarning, trim_whitespace | ||||
|  | ||||
| TRANSLATOR_COMMENT_MARK = "Translators" | ||||
|  | ||||
| dot_re = _lazy_re_compile(r"\S") | ||||
|  | ||||
|  | ||||
| def blankout(src, char): | ||||
|     """ | ||||
|     Change every non-whitespace character to the given char. | ||||
|     Used in the templatize function. | ||||
|     """ | ||||
|     return dot_re.sub(char, src) | ||||
|  | ||||
|  | ||||
| context_re = _lazy_re_compile(r"""^\s+.*context\s+((?:"[^"]*?")|(?:'[^']*?'))\s*""") | ||||
| inline_re = _lazy_re_compile( | ||||
|     # Match the trans/translate 'some text' part. | ||||
|     r"""^\s*trans(?:late)?\s+((?:"[^"]*?")|(?:'[^']*?'))""" | ||||
|     # Match and ignore optional filters | ||||
|     r"""(?:\s*\|\s*[^\s:]+(?::(?:[^\s'":]+|(?:"[^"]*?")|(?:'[^']*?')))?)*""" | ||||
|     # Match the optional context part | ||||
|     r"""(\s+.*context\s+((?:"[^"]*?")|(?:'[^']*?')))?\s*""" | ||||
| ) | ||||
| block_re = _lazy_re_compile( | ||||
|     r"""^\s*blocktrans(?:late)?(\s+.*context\s+((?:"[^"]*?")|(?:'[^']*?')))?(?:\s+|$)""" | ||||
| ) | ||||
| endblock_re = _lazy_re_compile(r"""^\s*endblocktrans(?:late)?$""") | ||||
| plural_re = _lazy_re_compile(r"""^\s*plural$""") | ||||
| constant_re = _lazy_re_compile(r"""_\(((?:".*?")|(?:'.*?'))\)""") | ||||
|  | ||||
|  | ||||
| def templatize(src, origin=None): | ||||
|     """ | ||||
|     Turn a Django template into something that is understood by xgettext. It | ||||
|     does so by translating the Django translation tags into standard gettext | ||||
|     function invocations. | ||||
|     """ | ||||
|     out = StringIO("") | ||||
|     message_context = None | ||||
|     intrans = False | ||||
|     inplural = False | ||||
|     trimmed = False | ||||
|     singular = [] | ||||
|     plural = [] | ||||
|     incomment = False | ||||
|     comment = [] | ||||
|     lineno_comment_map = {} | ||||
|     comment_lineno_cache = None | ||||
|     # Adding the u prefix allows gettext to recognize the string (#26093). | ||||
|     raw_prefix = "u" | ||||
|  | ||||
|     def join_tokens(tokens, trim=False): | ||||
|         message = "".join(tokens) | ||||
|         if trim: | ||||
|             message = trim_whitespace(message) | ||||
|         return message | ||||
|  | ||||
|     for t in Lexer(src).tokenize(): | ||||
|         if incomment: | ||||
|             if t.token_type == TokenType.BLOCK and t.contents == "endcomment": | ||||
|                 content = "".join(comment) | ||||
|                 translators_comment_start = None | ||||
|                 for lineno, line in enumerate(content.splitlines(True)): | ||||
|                     if line.lstrip().startswith(TRANSLATOR_COMMENT_MARK): | ||||
|                         translators_comment_start = lineno | ||||
|                 for lineno, line in enumerate(content.splitlines(True)): | ||||
|                     if ( | ||||
|                         translators_comment_start is not None | ||||
|                         and lineno >= translators_comment_start | ||||
|                     ): | ||||
|                         out.write(" # %s" % line) | ||||
|                     else: | ||||
|                         out.write(" #\n") | ||||
|                 incomment = False | ||||
|                 comment = [] | ||||
|             else: | ||||
|                 comment.append(t.contents) | ||||
|         elif intrans: | ||||
|             if t.token_type == TokenType.BLOCK: | ||||
|                 endbmatch = endblock_re.match(t.contents) | ||||
|                 pluralmatch = plural_re.match(t.contents) | ||||
|                 if endbmatch: | ||||
|                     if inplural: | ||||
|                         if message_context: | ||||
|                             out.write( | ||||
|                                 " npgettext({p}{!r}, {p}{!r}, {p}{!r},count) ".format( | ||||
|                                     message_context, | ||||
|                                     join_tokens(singular, trimmed), | ||||
|                                     join_tokens(plural, trimmed), | ||||
|                                     p=raw_prefix, | ||||
|                                 ) | ||||
|                             ) | ||||
|                         else: | ||||
|                             out.write( | ||||
|                                 " ngettext({p}{!r}, {p}{!r}, count) ".format( | ||||
|                                     join_tokens(singular, trimmed), | ||||
|                                     join_tokens(plural, trimmed), | ||||
|                                     p=raw_prefix, | ||||
|                                 ) | ||||
|                             ) | ||||
|                         for part in singular: | ||||
|                             out.write(blankout(part, "S")) | ||||
|                         for part in plural: | ||||
|                             out.write(blankout(part, "P")) | ||||
|                     else: | ||||
|                         if message_context: | ||||
|                             out.write( | ||||
|                                 " pgettext({p}{!r}, {p}{!r}) ".format( | ||||
|                                     message_context, | ||||
|                                     join_tokens(singular, trimmed), | ||||
|                                     p=raw_prefix, | ||||
|                                 ) | ||||
|                             ) | ||||
|                         else: | ||||
|                             out.write( | ||||
|                                 " gettext({p}{!r}) ".format( | ||||
|                                     join_tokens(singular, trimmed), | ||||
|                                     p=raw_prefix, | ||||
|                                 ) | ||||
|                             ) | ||||
|                         for part in singular: | ||||
|                             out.write(blankout(part, "S")) | ||||
|                     message_context = None | ||||
|                     intrans = False | ||||
|                     inplural = False | ||||
|                     singular = [] | ||||
|                     plural = [] | ||||
|                 elif pluralmatch: | ||||
|                     inplural = True | ||||
|                 else: | ||||
|                     filemsg = "" | ||||
|                     if origin: | ||||
|                         filemsg = "file %s, " % origin | ||||
|                     raise SyntaxError( | ||||
|                         "Translation blocks must not include other block tags: " | ||||
|                         "%s (%sline %d)" % (t.contents, filemsg, t.lineno) | ||||
|                     ) | ||||
|             elif t.token_type == TokenType.VAR: | ||||
|                 if inplural: | ||||
|                     plural.append("%%(%s)s" % t.contents) | ||||
|                 else: | ||||
|                     singular.append("%%(%s)s" % t.contents) | ||||
|             elif t.token_type == TokenType.TEXT: | ||||
|                 contents = t.contents.replace("%", "%%") | ||||
|                 if inplural: | ||||
|                     plural.append(contents) | ||||
|                 else: | ||||
|                     singular.append(contents) | ||||
|         else: | ||||
|             # Handle comment tokens (`{# ... #}`) plus other constructs on | ||||
|             # the same line: | ||||
|             if comment_lineno_cache is not None: | ||||
|                 cur_lineno = t.lineno + t.contents.count("\n") | ||||
|                 if comment_lineno_cache == cur_lineno: | ||||
|                     if t.token_type != TokenType.COMMENT: | ||||
|                         for c in lineno_comment_map[comment_lineno_cache]: | ||||
|                             filemsg = "" | ||||
|                             if origin: | ||||
|                                 filemsg = "file %s, " % origin | ||||
|                             warn_msg = ( | ||||
|                                 "The translator-targeted comment '%s' " | ||||
|                                 "(%sline %d) was ignored, because it wasn't " | ||||
|                                 "the last item on the line." | ||||
|                             ) % (c, filemsg, comment_lineno_cache) | ||||
|                             warnings.warn(warn_msg, TranslatorCommentWarning) | ||||
|                         lineno_comment_map[comment_lineno_cache] = [] | ||||
|                 else: | ||||
|                     out.write( | ||||
|                         "# %s" % " | ".join(lineno_comment_map[comment_lineno_cache]) | ||||
|                     ) | ||||
|                 comment_lineno_cache = None | ||||
|  | ||||
|             if t.token_type == TokenType.BLOCK: | ||||
|                 imatch = inline_re.match(t.contents) | ||||
|                 bmatch = block_re.match(t.contents) | ||||
|                 cmatches = constant_re.findall(t.contents) | ||||
|                 if imatch: | ||||
|                     g = imatch[1] | ||||
|                     if g[0] == '"': | ||||
|                         g = g.strip('"') | ||||
|                     elif g[0] == "'": | ||||
|                         g = g.strip("'") | ||||
|                     g = g.replace("%", "%%") | ||||
|                     if imatch[2]: | ||||
|                         # A context is provided | ||||
|                         context_match = context_re.match(imatch[2]) | ||||
|                         message_context = context_match[1] | ||||
|                         if message_context[0] == '"': | ||||
|                             message_context = message_context.strip('"') | ||||
|                         elif message_context[0] == "'": | ||||
|                             message_context = message_context.strip("'") | ||||
|                         out.write( | ||||
|                             " pgettext({p}{!r}, {p}{!r}) ".format( | ||||
|                                 message_context, g, p=raw_prefix | ||||
|                             ) | ||||
|                         ) | ||||
|                         message_context = None | ||||
|                     else: | ||||
|                         out.write(" gettext({p}{!r}) ".format(g, p=raw_prefix)) | ||||
|                 elif bmatch: | ||||
|                     for fmatch in constant_re.findall(t.contents): | ||||
|                         out.write(" _(%s) " % fmatch) | ||||
|                     if bmatch[1]: | ||||
|                         # A context is provided | ||||
|                         context_match = context_re.match(bmatch[1]) | ||||
|                         message_context = context_match[1] | ||||
|                         if message_context[0] == '"': | ||||
|                             message_context = message_context.strip('"') | ||||
|                         elif message_context[0] == "'": | ||||
|                             message_context = message_context.strip("'") | ||||
|                     intrans = True | ||||
|                     inplural = False | ||||
|                     trimmed = "trimmed" in t.split_contents() | ||||
|                     singular = [] | ||||
|                     plural = [] | ||||
|                 elif cmatches: | ||||
|                     for cmatch in cmatches: | ||||
|                         out.write(" _(%s) " % cmatch) | ||||
|                 elif t.contents == "comment": | ||||
|                     incomment = True | ||||
|                 else: | ||||
|                     out.write(blankout(t.contents, "B")) | ||||
|             elif t.token_type == TokenType.VAR: | ||||
|                 parts = t.contents.split("|") | ||||
|                 cmatch = constant_re.match(parts[0]) | ||||
|                 if cmatch: | ||||
|                     out.write(" _(%s) " % cmatch[1]) | ||||
|                 for p in parts[1:]: | ||||
|                     if p.find(":_(") >= 0: | ||||
|                         out.write(" %s " % p.split(":", 1)[1]) | ||||
|                     else: | ||||
|                         out.write(blankout(p, "F")) | ||||
|             elif t.token_type == TokenType.COMMENT: | ||||
|                 if t.contents.lstrip().startswith(TRANSLATOR_COMMENT_MARK): | ||||
|                     lineno_comment_map.setdefault(t.lineno, []).append(t.contents) | ||||
|                     comment_lineno_cache = t.lineno | ||||
|             else: | ||||
|                 out.write(blankout(t.contents, "X")) | ||||
|     return out.getvalue() | ||||
| @ -0,0 +1,67 @@ | ||||
| # These are versions of the functions in django.utils.translation.trans_real | ||||
| # that don't actually do anything. This is purely for performance, so that | ||||
| # settings.USE_I18N = False can use this module rather than trans_real.py. | ||||
|  | ||||
| from django.conf import settings | ||||
|  | ||||
|  | ||||
| def gettext(message): | ||||
|     return message | ||||
|  | ||||
|  | ||||
| gettext_noop = gettext_lazy = _ = gettext | ||||
|  | ||||
|  | ||||
| def ngettext(singular, plural, number): | ||||
|     if number == 1: | ||||
|         return singular | ||||
|     return plural | ||||
|  | ||||
|  | ||||
| ngettext_lazy = ngettext | ||||
|  | ||||
|  | ||||
| def pgettext(context, message): | ||||
|     return gettext(message) | ||||
|  | ||||
|  | ||||
| def npgettext(context, singular, plural, number): | ||||
|     return ngettext(singular, plural, number) | ||||
|  | ||||
|  | ||||
| def activate(x): | ||||
|     return None | ||||
|  | ||||
|  | ||||
| def deactivate(): | ||||
|     return None | ||||
|  | ||||
|  | ||||
| deactivate_all = deactivate | ||||
|  | ||||
|  | ||||
| def get_language(): | ||||
|     return settings.LANGUAGE_CODE | ||||
|  | ||||
|  | ||||
| def get_language_bidi(): | ||||
|     return settings.LANGUAGE_CODE in settings.LANGUAGES_BIDI | ||||
|  | ||||
|  | ||||
| def check_for_language(x): | ||||
|     return True | ||||
|  | ||||
|  | ||||
| def get_language_from_request(request, check_path=False): | ||||
|     return settings.LANGUAGE_CODE | ||||
|  | ||||
|  | ||||
| def get_language_from_path(request): | ||||
|     return None | ||||
|  | ||||
|  | ||||
| def get_supported_language_variant(lang_code, strict=False): | ||||
|     if lang_code and lang_code.lower() == settings.LANGUAGE_CODE.lower(): | ||||
|         return lang_code | ||||
|     else: | ||||
|         raise LookupError(lang_code) | ||||
| @ -0,0 +1,639 @@ | ||||
| """Translation helper functions.""" | ||||
| import functools | ||||
| import gettext as gettext_module | ||||
| import os | ||||
| import re | ||||
| import sys | ||||
| import warnings | ||||
|  | ||||
| from asgiref.local import Local | ||||
|  | ||||
| from django.apps import apps | ||||
| from django.conf import settings | ||||
| from django.conf.locale import LANG_INFO | ||||
| from django.core.exceptions import AppRegistryNotReady | ||||
| from django.core.signals import setting_changed | ||||
| from django.dispatch import receiver | ||||
| from django.utils.regex_helper import _lazy_re_compile | ||||
| from django.utils.safestring import SafeData, mark_safe | ||||
|  | ||||
| from . import to_language, to_locale | ||||
|  | ||||
| # Translations are cached in a dictionary for every language. | ||||
| # The active translations are stored by threadid to make them thread local. | ||||
| _translations = {} | ||||
| _active = Local() | ||||
|  | ||||
| # The default translation is based on the settings file. | ||||
| _default = None | ||||
|  | ||||
| # magic gettext number to separate context from message | ||||
| CONTEXT_SEPARATOR = "\x04" | ||||
|  | ||||
| # Maximum number of characters that will be parsed from the Accept-Language | ||||
| # header to prevent possible denial of service or memory exhaustion attacks. | ||||
| # About 10x longer than the longest value shown on MDN’s Accept-Language page. | ||||
| ACCEPT_LANGUAGE_HEADER_MAX_LENGTH = 500 | ||||
|  | ||||
| # Format of Accept-Language header values. From RFC 9110 Sections 12.4.2 and | ||||
| # 12.5.4, and RFC 5646 Section 2.1. | ||||
| accept_language_re = _lazy_re_compile( | ||||
|     r""" | ||||
|         # "en", "en-au", "x-y-z", "es-419", "*" | ||||
|         ([A-Za-z]{1,8}(?:-[A-Za-z0-9]{1,8})*|\*) | ||||
|         # Optional "q=1.00", "q=0.8" | ||||
|         (?:\s*;\s*q=(0(?:\.[0-9]{,3})?|1(?:\.0{,3})?))? | ||||
|         # Multiple accepts per header. | ||||
|         (?:\s*,\s*|$) | ||||
|     """, | ||||
|     re.VERBOSE, | ||||
| ) | ||||
|  | ||||
| language_code_re = _lazy_re_compile( | ||||
|     r"^[a-z]{1,8}(?:-[a-z0-9]{1,8})*(?:@[a-z0-9]{1,20})?$", re.IGNORECASE | ||||
| ) | ||||
|  | ||||
| language_code_prefix_re = _lazy_re_compile(r"^/(\w+([@-]\w+){0,2})(/|$)") | ||||
|  | ||||
|  | ||||
| @receiver(setting_changed) | ||||
| def reset_cache(*, setting, **kwargs): | ||||
|     """ | ||||
|     Reset global state when LANGUAGES setting has been changed, as some | ||||
|     languages should no longer be accepted. | ||||
|     """ | ||||
|     if setting in ("LANGUAGES", "LANGUAGE_CODE"): | ||||
|         check_for_language.cache_clear() | ||||
|         get_languages.cache_clear() | ||||
|         get_supported_language_variant.cache_clear() | ||||
|  | ||||
|  | ||||
| class TranslationCatalog: | ||||
|     """ | ||||
|     Simulate a dict for DjangoTranslation._catalog so as multiple catalogs | ||||
|     with different plural equations are kept separate. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, trans=None): | ||||
|         self._catalogs = [trans._catalog.copy()] if trans else [{}] | ||||
|         self._plurals = [trans.plural] if trans else [lambda n: int(n != 1)] | ||||
|  | ||||
|     def __getitem__(self, key): | ||||
|         for cat in self._catalogs: | ||||
|             try: | ||||
|                 return cat[key] | ||||
|             except KeyError: | ||||
|                 pass | ||||
|         raise KeyError(key) | ||||
|  | ||||
|     def __setitem__(self, key, value): | ||||
|         self._catalogs[0][key] = value | ||||
|  | ||||
|     def __contains__(self, key): | ||||
|         return any(key in cat for cat in self._catalogs) | ||||
|  | ||||
|     def items(self): | ||||
|         for cat in self._catalogs: | ||||
|             yield from cat.items() | ||||
|  | ||||
|     def keys(self): | ||||
|         for cat in self._catalogs: | ||||
|             yield from cat.keys() | ||||
|  | ||||
|     def update(self, trans): | ||||
|         # Merge if plural function is the same, else prepend. | ||||
|         for cat, plural in zip(self._catalogs, self._plurals): | ||||
|             if trans.plural.__code__ == plural.__code__: | ||||
|                 cat.update(trans._catalog) | ||||
|                 break | ||||
|         else: | ||||
|             self._catalogs.insert(0, trans._catalog.copy()) | ||||
|             self._plurals.insert(0, trans.plural) | ||||
|  | ||||
|     def get(self, key, default=None): | ||||
|         missing = object() | ||||
|         for cat in self._catalogs: | ||||
|             result = cat.get(key, missing) | ||||
|             if result is not missing: | ||||
|                 return result | ||||
|         return default | ||||
|  | ||||
|     def plural(self, msgid, num): | ||||
|         for cat, plural in zip(self._catalogs, self._plurals): | ||||
|             tmsg = cat.get((msgid, plural(num))) | ||||
|             if tmsg is not None: | ||||
|                 return tmsg | ||||
|         raise KeyError | ||||
|  | ||||
|  | ||||
| class DjangoTranslation(gettext_module.GNUTranslations): | ||||
|     """ | ||||
|     Set up the GNUTranslations context with regard to output charset. | ||||
|  | ||||
|     This translation object will be constructed out of multiple GNUTranslations | ||||
|     objects by merging their catalogs. It will construct an object for the | ||||
|     requested language and add a fallback to the default language, if it's | ||||
|     different from the requested language. | ||||
|     """ | ||||
|  | ||||
|     domain = "django" | ||||
|  | ||||
|     def __init__(self, language, domain=None, localedirs=None): | ||||
|         """Create a GNUTranslations() using many locale directories""" | ||||
|         gettext_module.GNUTranslations.__init__(self) | ||||
|         if domain is not None: | ||||
|             self.domain = domain | ||||
|  | ||||
|         self.__language = language | ||||
|         self.__to_language = to_language(language) | ||||
|         self.__locale = to_locale(language) | ||||
|         self._catalog = None | ||||
|         # If a language doesn't have a catalog, use the Germanic default for | ||||
|         # pluralization: anything except one is pluralized. | ||||
|         self.plural = lambda n: int(n != 1) | ||||
|  | ||||
|         if self.domain == "django": | ||||
|             if localedirs is not None: | ||||
|                 # A module-level cache is used for caching 'django' translations | ||||
|                 warnings.warn( | ||||
|                     "localedirs is ignored when domain is 'django'.", RuntimeWarning | ||||
|                 ) | ||||
|                 localedirs = None | ||||
|             self._init_translation_catalog() | ||||
|  | ||||
|         if localedirs: | ||||
|             for localedir in localedirs: | ||||
|                 translation = self._new_gnu_trans(localedir) | ||||
|                 self.merge(translation) | ||||
|         else: | ||||
|             self._add_installed_apps_translations() | ||||
|  | ||||
|         self._add_local_translations() | ||||
|         if ( | ||||
|             self.__language == settings.LANGUAGE_CODE | ||||
|             and self.domain == "django" | ||||
|             and self._catalog is None | ||||
|         ): | ||||
|             # default lang should have at least one translation file available. | ||||
|             raise OSError( | ||||
|                 "No translation files found for default language %s." | ||||
|                 % settings.LANGUAGE_CODE | ||||
|             ) | ||||
|         self._add_fallback(localedirs) | ||||
|         if self._catalog is None: | ||||
|             # No catalogs found for this language, set an empty catalog. | ||||
|             self._catalog = TranslationCatalog() | ||||
|  | ||||
|     def __repr__(self): | ||||
|         return "<DjangoTranslation lang:%s>" % self.__language | ||||
|  | ||||
|     def _new_gnu_trans(self, localedir, use_null_fallback=True): | ||||
|         """ | ||||
|         Return a mergeable gettext.GNUTranslations instance. | ||||
|  | ||||
|         A convenience wrapper. By default gettext uses 'fallback=False'. | ||||
|         Using param `use_null_fallback` to avoid confusion with any other | ||||
|         references to 'fallback'. | ||||
|         """ | ||||
|         return gettext_module.translation( | ||||
|             domain=self.domain, | ||||
|             localedir=localedir, | ||||
|             languages=[self.__locale], | ||||
|             fallback=use_null_fallback, | ||||
|         ) | ||||
|  | ||||
|     def _init_translation_catalog(self): | ||||
|         """Create a base catalog using global django translations.""" | ||||
|         settingsfile = sys.modules[settings.__module__].__file__ | ||||
|         localedir = os.path.join(os.path.dirname(settingsfile), "locale") | ||||
|         translation = self._new_gnu_trans(localedir) | ||||
|         self.merge(translation) | ||||
|  | ||||
|     def _add_installed_apps_translations(self): | ||||
|         """Merge translations from each installed app.""" | ||||
|         try: | ||||
|             app_configs = reversed(apps.get_app_configs()) | ||||
|         except AppRegistryNotReady: | ||||
|             raise AppRegistryNotReady( | ||||
|                 "The translation infrastructure cannot be initialized before the " | ||||
|                 "apps registry is ready. Check that you don't make non-lazy " | ||||
|                 "gettext calls at import time." | ||||
|             ) | ||||
|         for app_config in app_configs: | ||||
|             localedir = os.path.join(app_config.path, "locale") | ||||
|             if os.path.exists(localedir): | ||||
|                 translation = self._new_gnu_trans(localedir) | ||||
|                 self.merge(translation) | ||||
|  | ||||
|     def _add_local_translations(self): | ||||
|         """Merge translations defined in LOCALE_PATHS.""" | ||||
|         for localedir in reversed(settings.LOCALE_PATHS): | ||||
|             translation = self._new_gnu_trans(localedir) | ||||
|             self.merge(translation) | ||||
|  | ||||
|     def _add_fallback(self, localedirs=None): | ||||
|         """Set the GNUTranslations() fallback with the default language.""" | ||||
|         # Don't set a fallback for the default language or any English variant | ||||
|         # (as it's empty, so it'll ALWAYS fall back to the default language) | ||||
|         if self.__language == settings.LANGUAGE_CODE or self.__language.startswith( | ||||
|             "en" | ||||
|         ): | ||||
|             return | ||||
|         if self.domain == "django": | ||||
|             # Get from cache | ||||
|             default_translation = translation(settings.LANGUAGE_CODE) | ||||
|         else: | ||||
|             default_translation = DjangoTranslation( | ||||
|                 settings.LANGUAGE_CODE, domain=self.domain, localedirs=localedirs | ||||
|             ) | ||||
|         self.add_fallback(default_translation) | ||||
|  | ||||
|     def merge(self, other): | ||||
|         """Merge another translation into this catalog.""" | ||||
|         if not getattr(other, "_catalog", None): | ||||
|             return  # NullTranslations() has no _catalog | ||||
|         if self._catalog is None: | ||||
|             # Take plural and _info from first catalog found (generally Django's). | ||||
|             self.plural = other.plural | ||||
|             self._info = other._info.copy() | ||||
|             self._catalog = TranslationCatalog(other) | ||||
|         else: | ||||
|             self._catalog.update(other) | ||||
|         if other._fallback: | ||||
|             self.add_fallback(other._fallback) | ||||
|  | ||||
|     def language(self): | ||||
|         """Return the translation language.""" | ||||
|         return self.__language | ||||
|  | ||||
|     def to_language(self): | ||||
|         """Return the translation language name.""" | ||||
|         return self.__to_language | ||||
|  | ||||
|     def ngettext(self, msgid1, msgid2, n): | ||||
|         try: | ||||
|             tmsg = self._catalog.plural(msgid1, n) | ||||
|         except KeyError: | ||||
|             if self._fallback: | ||||
|                 return self._fallback.ngettext(msgid1, msgid2, n) | ||||
|             if n == 1: | ||||
|                 tmsg = msgid1 | ||||
|             else: | ||||
|                 tmsg = msgid2 | ||||
|         return tmsg | ||||
|  | ||||
|  | ||||
| def translation(language): | ||||
|     """ | ||||
|     Return a translation object in the default 'django' domain. | ||||
|     """ | ||||
|     global _translations | ||||
|     if language not in _translations: | ||||
|         _translations[language] = DjangoTranslation(language) | ||||
|     return _translations[language] | ||||
|  | ||||
|  | ||||
| def activate(language): | ||||
|     """ | ||||
|     Fetch the translation object for a given language and install it as the | ||||
|     current translation object for the current thread. | ||||
|     """ | ||||
|     if not language: | ||||
|         return | ||||
|     _active.value = translation(language) | ||||
|  | ||||
|  | ||||
| def deactivate(): | ||||
|     """ | ||||
|     Uninstall the active translation object so that further _() calls resolve | ||||
|     to the default translation object. | ||||
|     """ | ||||
|     if hasattr(_active, "value"): | ||||
|         del _active.value | ||||
|  | ||||
|  | ||||
| def deactivate_all(): | ||||
|     """ | ||||
|     Make the active translation object a NullTranslations() instance. This is | ||||
|     useful when we want delayed translations to appear as the original string | ||||
|     for some reason. | ||||
|     """ | ||||
|     _active.value = gettext_module.NullTranslations() | ||||
|     _active.value.to_language = lambda *args: None | ||||
|  | ||||
|  | ||||
| def get_language(): | ||||
|     """Return the currently selected language.""" | ||||
|     t = getattr(_active, "value", None) | ||||
|     if t is not None: | ||||
|         try: | ||||
|             return t.to_language() | ||||
|         except AttributeError: | ||||
|             pass | ||||
|     # If we don't have a real translation object, assume it's the default language. | ||||
|     return settings.LANGUAGE_CODE | ||||
|  | ||||
|  | ||||
| def get_language_bidi(): | ||||
|     """ | ||||
|     Return selected language's BiDi layout. | ||||
|  | ||||
|     * False = left-to-right layout | ||||
|     * True = right-to-left layout | ||||
|     """ | ||||
|     lang = get_language() | ||||
|     if lang is None: | ||||
|         return False | ||||
|     else: | ||||
|         base_lang = get_language().split("-")[0] | ||||
|         return base_lang in settings.LANGUAGES_BIDI | ||||
|  | ||||
|  | ||||
| def catalog(): | ||||
|     """ | ||||
|     Return the current active catalog for further processing. | ||||
|     This can be used if you need to modify the catalog or want to access the | ||||
|     whole message catalog instead of just translating one string. | ||||
|     """ | ||||
|     global _default | ||||
|  | ||||
|     t = getattr(_active, "value", None) | ||||
|     if t is not None: | ||||
|         return t | ||||
|     if _default is None: | ||||
|         _default = translation(settings.LANGUAGE_CODE) | ||||
|     return _default | ||||
|  | ||||
|  | ||||
| def gettext(message): | ||||
|     """ | ||||
|     Translate the 'message' string. It uses the current thread to find the | ||||
|     translation object to use. If no current translation is activated, the | ||||
|     message will be run through the default translation object. | ||||
|     """ | ||||
|     global _default | ||||
|  | ||||
|     eol_message = message.replace("\r\n", "\n").replace("\r", "\n") | ||||
|  | ||||
|     if eol_message: | ||||
|         _default = _default or translation(settings.LANGUAGE_CODE) | ||||
|         translation_object = getattr(_active, "value", _default) | ||||
|  | ||||
|         result = translation_object.gettext(eol_message) | ||||
|     else: | ||||
|         # Return an empty value of the corresponding type if an empty message | ||||
|         # is given, instead of metadata, which is the default gettext behavior. | ||||
|         result = type(message)("") | ||||
|  | ||||
|     if isinstance(message, SafeData): | ||||
|         return mark_safe(result) | ||||
|  | ||||
|     return result | ||||
|  | ||||
|  | ||||
| def pgettext(context, message): | ||||
|     msg_with_ctxt = "%s%s%s" % (context, CONTEXT_SEPARATOR, message) | ||||
|     result = gettext(msg_with_ctxt) | ||||
|     if CONTEXT_SEPARATOR in result: | ||||
|         # Translation not found | ||||
|         result = message | ||||
|     elif isinstance(message, SafeData): | ||||
|         result = mark_safe(result) | ||||
|     return result | ||||
|  | ||||
|  | ||||
| def gettext_noop(message): | ||||
|     """ | ||||
|     Mark strings for translation but don't translate them now. This can be | ||||
|     used to store strings in global variables that should stay in the base | ||||
|     language (because they might be used externally) and will be translated | ||||
|     later. | ||||
|     """ | ||||
|     return message | ||||
|  | ||||
|  | ||||
| def do_ntranslate(singular, plural, number, translation_function): | ||||
|     global _default | ||||
|  | ||||
|     t = getattr(_active, "value", None) | ||||
|     if t is not None: | ||||
|         return getattr(t, translation_function)(singular, plural, number) | ||||
|     if _default is None: | ||||
|         _default = translation(settings.LANGUAGE_CODE) | ||||
|     return getattr(_default, translation_function)(singular, plural, number) | ||||
|  | ||||
|  | ||||
| def ngettext(singular, plural, number): | ||||
|     """ | ||||
|     Return a string of the translation of either the singular or plural, | ||||
|     based on the number. | ||||
|     """ | ||||
|     return do_ntranslate(singular, plural, number, "ngettext") | ||||
|  | ||||
|  | ||||
| def npgettext(context, singular, plural, number): | ||||
|     msgs_with_ctxt = ( | ||||
|         "%s%s%s" % (context, CONTEXT_SEPARATOR, singular), | ||||
|         "%s%s%s" % (context, CONTEXT_SEPARATOR, plural), | ||||
|         number, | ||||
|     ) | ||||
|     result = ngettext(*msgs_with_ctxt) | ||||
|     if CONTEXT_SEPARATOR in result: | ||||
|         # Translation not found | ||||
|         result = ngettext(singular, plural, number) | ||||
|     return result | ||||
|  | ||||
|  | ||||
| def all_locale_paths(): | ||||
|     """ | ||||
|     Return a list of paths to user-provides languages files. | ||||
|     """ | ||||
|     globalpath = os.path.join( | ||||
|         os.path.dirname(sys.modules[settings.__module__].__file__), "locale" | ||||
|     ) | ||||
|     app_paths = [] | ||||
|     for app_config in apps.get_app_configs(): | ||||
|         locale_path = os.path.join(app_config.path, "locale") | ||||
|         if os.path.exists(locale_path): | ||||
|             app_paths.append(locale_path) | ||||
|     return [globalpath, *settings.LOCALE_PATHS, *app_paths] | ||||
|  | ||||
|  | ||||
| @functools.lru_cache(maxsize=1000) | ||||
| def check_for_language(lang_code): | ||||
|     """ | ||||
|     Check whether there is a global language file for the given language | ||||
|     code. This is used to decide whether a user-provided language is | ||||
|     available. | ||||
|  | ||||
|     lru_cache should have a maxsize to prevent from memory exhaustion attacks, | ||||
|     as the provided language codes are taken from the HTTP request. See also | ||||
|     <https://www.djangoproject.com/weblog/2007/oct/26/security-fix/>. | ||||
|     """ | ||||
|     # First, a quick check to make sure lang_code is well-formed (#21458) | ||||
|     if lang_code is None or not language_code_re.search(lang_code): | ||||
|         return False | ||||
|     return any( | ||||
|         gettext_module.find("django", path, [to_locale(lang_code)]) is not None | ||||
|         for path in all_locale_paths() | ||||
|     ) | ||||
|  | ||||
|  | ||||
| @functools.lru_cache | ||||
| def get_languages(): | ||||
|     """ | ||||
|     Cache of settings.LANGUAGES in a dictionary for easy lookups by key. | ||||
|     Convert keys to lowercase as they should be treated as case-insensitive. | ||||
|     """ | ||||
|     return {key.lower(): value for key, value in dict(settings.LANGUAGES).items()} | ||||
|  | ||||
|  | ||||
| @functools.lru_cache(maxsize=1000) | ||||
| def get_supported_language_variant(lang_code, strict=False): | ||||
|     """ | ||||
|     Return the language code that's listed in supported languages, possibly | ||||
|     selecting a more generic variant. Raise LookupError if nothing is found. | ||||
|  | ||||
|     If `strict` is False (the default), look for a country-specific variant | ||||
|     when neither the language code nor its generic variant is found. | ||||
|  | ||||
|     lru_cache should have a maxsize to prevent from memory exhaustion attacks, | ||||
|     as the provided language codes are taken from the HTTP request. See also | ||||
|     <https://www.djangoproject.com/weblog/2007/oct/26/security-fix/>. | ||||
|     """ | ||||
|     if lang_code: | ||||
|         # If 'zh-hant-tw' is not supported, try special fallback or subsequent | ||||
|         # language codes i.e. 'zh-hant' and 'zh'. | ||||
|         possible_lang_codes = [lang_code] | ||||
|         try: | ||||
|             possible_lang_codes.extend(LANG_INFO[lang_code]["fallback"]) | ||||
|         except KeyError: | ||||
|             pass | ||||
|         i = None | ||||
|         while (i := lang_code.rfind("-", 0, i)) > -1: | ||||
|             possible_lang_codes.append(lang_code[:i]) | ||||
|         generic_lang_code = possible_lang_codes[-1] | ||||
|         supported_lang_codes = get_languages() | ||||
|  | ||||
|         for code in possible_lang_codes: | ||||
|             if code.lower() in supported_lang_codes and check_for_language(code): | ||||
|                 return code | ||||
|         if not strict: | ||||
|             # if fr-fr is not supported, try fr-ca. | ||||
|             for supported_code in supported_lang_codes: | ||||
|                 if supported_code.startswith(generic_lang_code + "-"): | ||||
|                     return supported_code | ||||
|     raise LookupError(lang_code) | ||||
|  | ||||
|  | ||||
| def get_language_from_path(path, strict=False): | ||||
|     """ | ||||
|     Return the language code if there's a valid language code found in `path`. | ||||
|  | ||||
|     If `strict` is False (the default), look for a country-specific variant | ||||
|     when neither the language code nor its generic variant is found. | ||||
|     """ | ||||
|     regex_match = language_code_prefix_re.match(path) | ||||
|     if not regex_match: | ||||
|         return None | ||||
|     lang_code = regex_match[1] | ||||
|     try: | ||||
|         return get_supported_language_variant(lang_code, strict=strict) | ||||
|     except LookupError: | ||||
|         return None | ||||
|  | ||||
|  | ||||
| def get_language_from_request(request, check_path=False): | ||||
|     """ | ||||
|     Analyze the request to find what language the user wants the system to | ||||
|     show. Only languages listed in settings.LANGUAGES are taken into account. | ||||
|     If the user requests a sublanguage where we have a main language, we send | ||||
|     out the main language. | ||||
|  | ||||
|     If check_path is True, the URL path prefix will be checked for a language | ||||
|     code, otherwise this is skipped for backwards compatibility. | ||||
|     """ | ||||
|     if check_path: | ||||
|         lang_code = get_language_from_path(request.path_info) | ||||
|         if lang_code is not None: | ||||
|             return lang_code | ||||
|  | ||||
|     lang_code = request.COOKIES.get(settings.LANGUAGE_COOKIE_NAME) | ||||
|     if ( | ||||
|         lang_code is not None | ||||
|         and lang_code in get_languages() | ||||
|         and check_for_language(lang_code) | ||||
|     ): | ||||
|         return lang_code | ||||
|  | ||||
|     try: | ||||
|         return get_supported_language_variant(lang_code) | ||||
|     except LookupError: | ||||
|         pass | ||||
|  | ||||
|     accept = request.META.get("HTTP_ACCEPT_LANGUAGE", "") | ||||
|     for accept_lang, unused in parse_accept_lang_header(accept): | ||||
|         if accept_lang == "*": | ||||
|             break | ||||
|  | ||||
|         if not language_code_re.search(accept_lang): | ||||
|             continue | ||||
|  | ||||
|         try: | ||||
|             return get_supported_language_variant(accept_lang) | ||||
|         except LookupError: | ||||
|             continue | ||||
|  | ||||
|     try: | ||||
|         return get_supported_language_variant(settings.LANGUAGE_CODE) | ||||
|     except LookupError: | ||||
|         return settings.LANGUAGE_CODE | ||||
|  | ||||
|  | ||||
| @functools.lru_cache(maxsize=1000) | ||||
| def _parse_accept_lang_header(lang_string): | ||||
|     """ | ||||
|     Parse the lang_string, which is the body of an HTTP Accept-Language | ||||
|     header, and return a tuple of (lang, q-value), ordered by 'q' values. | ||||
|  | ||||
|     Return an empty tuple if there are any format errors in lang_string. | ||||
|     """ | ||||
|     result = [] | ||||
|     pieces = accept_language_re.split(lang_string.lower()) | ||||
|     if pieces[-1]: | ||||
|         return () | ||||
|     for i in range(0, len(pieces) - 1, 3): | ||||
|         first, lang, priority = pieces[i : i + 3] | ||||
|         if first: | ||||
|             return () | ||||
|         if priority: | ||||
|             priority = float(priority) | ||||
|         else: | ||||
|             priority = 1.0 | ||||
|         result.append((lang, priority)) | ||||
|     result.sort(key=lambda k: k[1], reverse=True) | ||||
|     return tuple(result) | ||||
|  | ||||
|  | ||||
| def parse_accept_lang_header(lang_string): | ||||
|     """ | ||||
|     Parse the value of the Accept-Language header up to a maximum length. | ||||
|  | ||||
|     The value of the header is truncated to a maximum length to avoid potential | ||||
|     denial of service and memory exhaustion attacks. Excessive memory could be | ||||
|     used if the raw value is very large as it would be cached due to the use of | ||||
|     functools.lru_cache() to avoid repetitive parsing of common header values. | ||||
|     """ | ||||
|     # If the header value doesn't exceed the maximum allowed length, parse it. | ||||
|     if len(lang_string) <= ACCEPT_LANGUAGE_HEADER_MAX_LENGTH: | ||||
|         return _parse_accept_lang_header(lang_string) | ||||
|  | ||||
|     # If there is at least one comma in the value, parse up to the last comma | ||||
|     # before the max length, skipping any truncated parts at the end of the | ||||
|     # header value. | ||||
|     if (index := lang_string.rfind(",", 0, ACCEPT_LANGUAGE_HEADER_MAX_LENGTH)) > 0: | ||||
|         return _parse_accept_lang_header(lang_string[:index]) | ||||
|  | ||||
|     # Don't attempt to parse if there is only one language-range value which is | ||||
|     # longer than the maximum allowed length and so truncated. | ||||
|     return () | ||||
							
								
								
									
										126
									
								
								srcs/.venv/lib/python3.11/site-packages/django/utils/tree.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								srcs/.venv/lib/python3.11/site-packages/django/utils/tree.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,126 @@ | ||||
| """ | ||||
| A class for storing a tree graph. Primarily used for filter constructs in the | ||||
| ORM. | ||||
| """ | ||||
|  | ||||
| import copy | ||||
|  | ||||
| from django.utils.hashable import make_hashable | ||||
|  | ||||
|  | ||||
| class Node: | ||||
|     """ | ||||
|     A single internal node in the tree graph. A Node should be viewed as a | ||||
|     connection (the root) with the children being either leaf nodes or other | ||||
|     Node instances. | ||||
|     """ | ||||
|  | ||||
|     # Standard connector type. Clients usually won't use this at all and | ||||
|     # subclasses will usually override the value. | ||||
|     default = "DEFAULT" | ||||
|  | ||||
|     def __init__(self, children=None, connector=None, negated=False): | ||||
|         """Construct a new Node. If no connector is given, use the default.""" | ||||
|         self.children = children[:] if children else [] | ||||
|         self.connector = connector or self.default | ||||
|         self.negated = negated | ||||
|  | ||||
|     @classmethod | ||||
|     def create(cls, children=None, connector=None, negated=False): | ||||
|         """ | ||||
|         Create a new instance using Node() instead of __init__() as some | ||||
|         subclasses, e.g. django.db.models.query_utils.Q, may implement a custom | ||||
|         __init__() with a signature that conflicts with the one defined in | ||||
|         Node.__init__(). | ||||
|         """ | ||||
|         obj = Node(children, connector or cls.default, negated) | ||||
|         obj.__class__ = cls | ||||
|         return obj | ||||
|  | ||||
|     def __str__(self): | ||||
|         template = "(NOT (%s: %s))" if self.negated else "(%s: %s)" | ||||
|         return template % (self.connector, ", ".join(str(c) for c in self.children)) | ||||
|  | ||||
|     def __repr__(self): | ||||
|         return "<%s: %s>" % (self.__class__.__name__, self) | ||||
|  | ||||
|     def __copy__(self): | ||||
|         obj = self.create(connector=self.connector, negated=self.negated) | ||||
|         obj.children = self.children  # Don't [:] as .__init__() via .create() does. | ||||
|         return obj | ||||
|  | ||||
|     copy = __copy__ | ||||
|  | ||||
|     def __deepcopy__(self, memodict): | ||||
|         obj = self.create(connector=self.connector, negated=self.negated) | ||||
|         obj.children = copy.deepcopy(self.children, memodict) | ||||
|         return obj | ||||
|  | ||||
|     def __len__(self): | ||||
|         """Return the number of children this node has.""" | ||||
|         return len(self.children) | ||||
|  | ||||
|     def __bool__(self): | ||||
|         """Return whether or not this node has children.""" | ||||
|         return bool(self.children) | ||||
|  | ||||
|     def __contains__(self, other): | ||||
|         """Return True if 'other' is a direct child of this instance.""" | ||||
|         return other in self.children | ||||
|  | ||||
|     def __eq__(self, other): | ||||
|         return ( | ||||
|             self.__class__ == other.__class__ | ||||
|             and self.connector == other.connector | ||||
|             and self.negated == other.negated | ||||
|             and self.children == other.children | ||||
|         ) | ||||
|  | ||||
|     def __hash__(self): | ||||
|         return hash( | ||||
|             ( | ||||
|                 self.__class__, | ||||
|                 self.connector, | ||||
|                 self.negated, | ||||
|                 *make_hashable(self.children), | ||||
|             ) | ||||
|         ) | ||||
|  | ||||
|     def add(self, data, conn_type): | ||||
|         """ | ||||
|         Combine this tree and the data represented by data using the | ||||
|         connector conn_type. The combine is done by squashing the node other | ||||
|         away if possible. | ||||
|  | ||||
|         This tree (self) will never be pushed to a child node of the | ||||
|         combined tree, nor will the connector or negated properties change. | ||||
|  | ||||
|         Return a node which can be used in place of data regardless if the | ||||
|         node other got squashed or not. | ||||
|         """ | ||||
|         if self.connector != conn_type: | ||||
|             obj = self.copy() | ||||
|             self.connector = conn_type | ||||
|             self.children = [obj, data] | ||||
|             return data | ||||
|         elif ( | ||||
|             isinstance(data, Node) | ||||
|             and not data.negated | ||||
|             and (data.connector == conn_type or len(data) == 1) | ||||
|         ): | ||||
|             # We can squash the other node's children directly into this node. | ||||
|             # We are just doing (AB)(CD) == (ABCD) here, with the addition that | ||||
|             # if the length of the other node is 1 the connector doesn't | ||||
|             # matter. However, for the len(self) == 1 case we don't want to do | ||||
|             # the squashing, as it would alter self.connector. | ||||
|             self.children.extend(data.children) | ||||
|             return self | ||||
|         else: | ||||
|             # We could use perhaps additional logic here to see if some | ||||
|             # children could be used for pushdown here. | ||||
|             self.children.append(data) | ||||
|             return data | ||||
|  | ||||
|     def negate(self): | ||||
|         """Negate the sense of the root connector.""" | ||||
|         self.negated = not self.negated | ||||
							
								
								
									
										121
									
								
								srcs/.venv/lib/python3.11/site-packages/django/utils/version.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								srcs/.venv/lib/python3.11/site-packages/django/utils/version.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,121 @@ | ||||
| import datetime | ||||
| import functools | ||||
| import os | ||||
| import subprocess | ||||
| import sys | ||||
|  | ||||
| from django.utils.regex_helper import _lazy_re_compile | ||||
|  | ||||
| # Private, stable API for detecting the Python version. PYXY means "Python X.Y | ||||
| # or later". So that third-party apps can use these values, each constant | ||||
| # should remain as long as the oldest supported Django version supports that | ||||
| # Python version. | ||||
| PY36 = sys.version_info >= (3, 6) | ||||
| PY37 = sys.version_info >= (3, 7) | ||||
| PY38 = sys.version_info >= (3, 8) | ||||
| PY39 = sys.version_info >= (3, 9) | ||||
| PY310 = sys.version_info >= (3, 10) | ||||
| PY311 = sys.version_info >= (3, 11) | ||||
| PY312 = sys.version_info >= (3, 12) | ||||
|  | ||||
|  | ||||
| def get_version(version=None): | ||||
|     """Return a PEP 440-compliant version number from VERSION.""" | ||||
|     version = get_complete_version(version) | ||||
|  | ||||
|     # Now build the two parts of the version number: | ||||
|     # main = X.Y[.Z] | ||||
|     # sub = .devN - for pre-alpha releases | ||||
|     #     | {a|b|rc}N - for alpha, beta, and rc releases | ||||
|  | ||||
|     main = get_main_version(version) | ||||
|  | ||||
|     sub = "" | ||||
|     if version[3] == "alpha" and version[4] == 0: | ||||
|         git_changeset = get_git_changeset() | ||||
|         if git_changeset: | ||||
|             sub = ".dev%s" % git_changeset | ||||
|  | ||||
|     elif version[3] != "final": | ||||
|         mapping = {"alpha": "a", "beta": "b", "rc": "rc"} | ||||
|         sub = mapping[version[3]] + str(version[4]) | ||||
|  | ||||
|     return main + sub | ||||
|  | ||||
|  | ||||
| def get_main_version(version=None): | ||||
|     """Return main version (X.Y[.Z]) from VERSION.""" | ||||
|     version = get_complete_version(version) | ||||
|     parts = 2 if version[2] == 0 else 3 | ||||
|     return ".".join(str(x) for x in version[:parts]) | ||||
|  | ||||
|  | ||||
| def get_complete_version(version=None): | ||||
|     """ | ||||
|     Return a tuple of the django version. If version argument is non-empty, | ||||
|     check for correctness of the tuple provided. | ||||
|     """ | ||||
|     if version is None: | ||||
|         from django import VERSION as version | ||||
|     else: | ||||
|         assert len(version) == 5 | ||||
|         assert version[3] in ("alpha", "beta", "rc", "final") | ||||
|  | ||||
|     return version | ||||
|  | ||||
|  | ||||
| def get_docs_version(version=None): | ||||
|     version = get_complete_version(version) | ||||
|     if version[3] != "final": | ||||
|         return "dev" | ||||
|     else: | ||||
|         return "%d.%d" % version[:2] | ||||
|  | ||||
|  | ||||
| @functools.lru_cache | ||||
| def get_git_changeset(): | ||||
|     """Return a numeric identifier of the latest git changeset. | ||||
|  | ||||
|     The result is the UTC timestamp of the changeset in YYYYMMDDHHMMSS format. | ||||
|     This value isn't guaranteed to be unique, but collisions are very unlikely, | ||||
|     so it's sufficient for generating the development version numbers. | ||||
|     """ | ||||
|     # Repository may not be found if __file__ is undefined, e.g. in a frozen | ||||
|     # module. | ||||
|     if "__file__" not in globals(): | ||||
|         return None | ||||
|     repo_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | ||||
|     git_log = subprocess.run( | ||||
|         "git log --pretty=format:%ct --quiet -1 HEAD", | ||||
|         capture_output=True, | ||||
|         shell=True, | ||||
|         cwd=repo_dir, | ||||
|         text=True, | ||||
|     ) | ||||
|     timestamp = git_log.stdout | ||||
|     tz = datetime.timezone.utc | ||||
|     try: | ||||
|         timestamp = datetime.datetime.fromtimestamp(int(timestamp), tz=tz) | ||||
|     except ValueError: | ||||
|         return None | ||||
|     return timestamp.strftime("%Y%m%d%H%M%S") | ||||
|  | ||||
|  | ||||
| version_component_re = _lazy_re_compile(r"(\d+|[a-z]+|\.)") | ||||
|  | ||||
|  | ||||
| def get_version_tuple(version): | ||||
|     """ | ||||
|     Return a tuple of version numbers (e.g. (1, 2, 3)) from the version | ||||
|     string (e.g. '1.2.3'). | ||||
|     """ | ||||
|     version_numbers = [] | ||||
|     for item in version_component_re.split(version): | ||||
|         if item and item != ".": | ||||
|             try: | ||||
|                 component = int(item) | ||||
|             except ValueError: | ||||
|                 break | ||||
|             else: | ||||
|                 version_numbers.append(component) | ||||
|     return tuple(version_numbers) | ||||
| @ -0,0 +1,35 @@ | ||||
| """ | ||||
| Utilities for XML generation/parsing. | ||||
| """ | ||||
|  | ||||
| import re | ||||
| from xml.sax.saxutils import XMLGenerator | ||||
|  | ||||
|  | ||||
| class UnserializableContentError(ValueError): | ||||
|     pass | ||||
|  | ||||
|  | ||||
| class SimplerXMLGenerator(XMLGenerator): | ||||
|     def addQuickElement(self, name, contents=None, attrs=None): | ||||
|         "Convenience method for adding an element with no children" | ||||
|         if attrs is None: | ||||
|             attrs = {} | ||||
|         self.startElement(name, attrs) | ||||
|         if contents is not None: | ||||
|             self.characters(contents) | ||||
|         self.endElement(name) | ||||
|  | ||||
|     def characters(self, content): | ||||
|         if content and re.search(r"[\x00-\x08\x0B-\x0C\x0E-\x1F]", content): | ||||
|             # Fail loudly when content has control chars (unsupported in XML 1.0) | ||||
|             # See https://www.w3.org/International/questions/qa-controls | ||||
|             raise UnserializableContentError( | ||||
|                 "Control characters are not supported in XML 1.0" | ||||
|             ) | ||||
|         XMLGenerator.characters(self, content) | ||||
|  | ||||
|     def startElement(self, name, attrs): | ||||
|         # Sort attrs for a deterministic output. | ||||
|         sorted_attrs = dict(sorted(attrs.items())) if attrs else attrs | ||||
|         super().startElement(name, sorted_attrs) | ||||
		Reference in New Issue
	
	Block a user