diff --git a/venv/lib/python3.10/site-packages/git/index/__init__.py b/venv/lib/python3.10/site-packages/git/index/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..ba48110fddcdf549b6e61f115d28b137a57cc219 --- /dev/null +++ b/venv/lib/python3.10/site-packages/git/index/__init__.py @@ -0,0 +1,16 @@ +# This module is part of GitPython and is released under the +# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/ + +"""Initialize the index package.""" + +__all__ = [ + "BaseIndexEntry", + "BlobFilter", + "CheckoutError", + "IndexEntry", + "IndexFile", + "StageType", +] + +from .base import CheckoutError, IndexFile +from .typ import BaseIndexEntry, BlobFilter, IndexEntry, StageType diff --git a/venv/lib/python3.10/site-packages/git/index/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/git/index/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2fec70f6b44891cc2f0c90652c4ed7f14a630c07 Binary files /dev/null and b/venv/lib/python3.10/site-packages/git/index/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/git/index/__pycache__/base.cpython-310.pyc b/venv/lib/python3.10/site-packages/git/index/__pycache__/base.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b5048a9fd8791769c72f860bbf22598038876b8c Binary files /dev/null and b/venv/lib/python3.10/site-packages/git/index/__pycache__/base.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/git/index/__pycache__/fun.cpython-310.pyc b/venv/lib/python3.10/site-packages/git/index/__pycache__/fun.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4162cfc61468952cb7a5045442fa83141e2a2c85 Binary files /dev/null and b/venv/lib/python3.10/site-packages/git/index/__pycache__/fun.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/git/index/__pycache__/typ.cpython-310.pyc b/venv/lib/python3.10/site-packages/git/index/__pycache__/typ.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..41c2054697e15bc9b3a7480e298a2b38e33f8826 Binary files /dev/null and b/venv/lib/python3.10/site-packages/git/index/__pycache__/typ.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/git/index/__pycache__/util.cpython-310.pyc b/venv/lib/python3.10/site-packages/git/index/__pycache__/util.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3f7aa82c5930d5e00aaa2fea3b7f9037c71fb8de Binary files /dev/null and b/venv/lib/python3.10/site-packages/git/index/__pycache__/util.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/git/index/base.py b/venv/lib/python3.10/site-packages/git/index/base.py new file mode 100644 index 0000000000000000000000000000000000000000..39cc9143cbc27ed66f0ab84c1639679b2fe16b71 --- /dev/null +++ b/venv/lib/python3.10/site-packages/git/index/base.py @@ -0,0 +1,1518 @@ +# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors +# +# This module is part of GitPython and is released under the +# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/ + +"""Module containing :class:`IndexFile`, an Index implementation facilitating all kinds +of index manipulations such as querying and merging.""" + +__all__ = ["IndexFile", "CheckoutError", "StageType"] + +import contextlib +import datetime +import glob +from io import BytesIO +import os +import os.path as osp +from stat import S_ISLNK +import subprocess +import sys +import tempfile + +from gitdb.base import IStream +from gitdb.db import MemoryDB + +from git.compat import defenc, force_bytes +import git.diff as git_diff +from git.exc import CheckoutError, GitCommandError, GitError, InvalidGitRepositoryError +from git.objects import Blob, Commit, Object, Submodule, Tree +from git.objects.util import Serializable +from git.util import ( + Actor, + LazyMixin, + LockedFD, + join_path_native, + file_contents_ro, + to_native_path_linux, + unbare_repo, + to_bin_sha, +) + +from .fun import ( + S_IFGITLINK, + aggressive_tree_merge, + entry_key, + read_cache, + run_commit_hook, + stat_mode_to_index_mode, + write_cache, + write_tree_from_cache, +) +from .typ import BaseIndexEntry, IndexEntry, StageType +from .util import TemporaryFileSwap, post_clear_cache, default_index, git_working_dir + +# typing ----------------------------------------------------------------------------- + +from typing import ( + Any, + BinaryIO, + Callable, + Dict, + Generator, + IO, + Iterable, + Iterator, + List, + NoReturn, + Sequence, + TYPE_CHECKING, + Tuple, + Union, +) + +from git.types import Literal, PathLike + +if TYPE_CHECKING: + from subprocess import Popen + + from git.refs.reference import Reference + from git.repo import Repo + + +Treeish = Union[Tree, Commit, str, bytes] + +# ------------------------------------------------------------------------------------ + + +@contextlib.contextmanager +def _named_temporary_file_for_subprocess(directory: PathLike) -> Generator[str, None, None]: + """Create a named temporary file git subprocesses can open, deleting it afterward. + + :param directory: + The directory in which the file is created. + + :return: + A context manager object that creates the file and provides its name on entry, + and deletes it on exit. + """ + if sys.platform == "win32": + fd, name = tempfile.mkstemp(dir=directory) + os.close(fd) + try: + yield name + finally: + os.remove(name) + else: + with tempfile.NamedTemporaryFile(dir=directory) as ctx: + yield ctx.name + + +class IndexFile(LazyMixin, git_diff.Diffable, Serializable): + """An Index that can be manipulated using a native implementation in order to save + git command function calls wherever possible. + + This provides custom merging facilities allowing to merge without actually changing + your index or your working tree. This way you can perform your own test merges based + on the index only without having to deal with the working copy. This is useful in + case of partial working trees. + + Entries: + + The index contains an entries dict whose keys are tuples of type + :class:`~git.index.typ.IndexEntry` to facilitate access. + + You may read the entries dict or manipulate it using IndexEntry instance, i.e.:: + + index.entries[index.entry_key(index_entry_instance)] = index_entry_instance + + Make sure you use :meth:`index.write() ` once you are done manipulating the + index directly before operating on it using the git command. + """ + + __slots__ = ("repo", "version", "entries", "_extension_data", "_file_path") + + _VERSION = 2 + """The latest version we support.""" + + S_IFGITLINK = S_IFGITLINK + """Flags for a submodule.""" + + def __init__(self, repo: "Repo", file_path: Union[PathLike, None] = None) -> None: + """Initialize this Index instance, optionally from the given `file_path`. + + If no `file_path` is given, we will be created from the current index file. + + If a stream is not given, the stream will be initialized from the current + repository's index on demand. + """ + self.repo = repo + self.version = self._VERSION + self._extension_data = b"" + self._file_path: PathLike = file_path or self._index_path() + + def _set_cache_(self, attr: str) -> None: + if attr == "entries": + try: + fd = os.open(self._file_path, os.O_RDONLY) + except OSError: + # In new repositories, there may be no index, which means we are empty. + self.entries: Dict[Tuple[PathLike, StageType], IndexEntry] = {} + return + # END exception handling + + try: + stream = file_contents_ro(fd, stream=True, allow_mmap=True) + finally: + os.close(fd) + + self._deserialize(stream) + else: + super()._set_cache_(attr) + + def _index_path(self) -> PathLike: + if self.repo.git_dir: + return join_path_native(self.repo.git_dir, "index") + else: + raise GitCommandError("No git directory given to join index path") + + @property + def path(self) -> PathLike: + """:return: Path to the index file we are representing""" + return self._file_path + + def _delete_entries_cache(self) -> None: + """Safely clear the entries cache so it can be recreated.""" + try: + del self.entries + except AttributeError: + # It failed in Python 2.6.5 with AttributeError. + # FIXME: Look into whether we can just remove this except clause now. + pass + # END exception handling + + # { Serializable Interface + + def _deserialize(self, stream: IO) -> "IndexFile": + """Initialize this instance with index values read from the given stream.""" + self.version, self.entries, self._extension_data, _conten_sha = read_cache(stream) + return self + + def _entries_sorted(self) -> List[IndexEntry]: + """:return: List of entries, in a sorted fashion, first by path, then by stage""" + return sorted(self.entries.values(), key=lambda e: (e.path, e.stage)) + + def _serialize(self, stream: IO, ignore_extension_data: bool = False) -> "IndexFile": + entries = self._entries_sorted() + extension_data = self._extension_data # type: Union[None, bytes] + if ignore_extension_data: + extension_data = None + write_cache(entries, stream, extension_data) + return self + + # } END serializable interface + + def write( + self, + file_path: Union[None, PathLike] = None, + ignore_extension_data: bool = False, + ) -> None: + """Write the current state to our file path or to the given one. + + :param file_path: + If ``None``, we will write to our stored file path from which we have been + initialized. Otherwise we write to the given file path. Please note that + this will change the `file_path` of this index to the one you gave. + + :param ignore_extension_data: + If ``True``, the TREE type extension data read in the index will not be + written to disk. NOTE that no extension data is actually written. Use this + if you have altered the index and would like to use + :manpage:`git-write-tree(1)` afterwards to create a tree representing your + written changes. If this data is present in the written index, + :manpage:`git-write-tree(1)` will instead write the stored/cached tree. + Alternatively, use :meth:`write_tree` to handle this case automatically. + """ + # Make sure we have our entries read before getting a write lock. + # Otherwise it would be done when streaming. + # This can happen if one doesn't change the index, but writes it right away. + self.entries # noqa: B018 + lfd = LockedFD(file_path or self._file_path) + stream = lfd.open(write=True, stream=True) + + try: + self._serialize(stream, ignore_extension_data) + except BaseException: + lfd.rollback() + raise + + lfd.commit() + + # Make sure we represent what we have written. + if file_path is not None: + self._file_path = file_path + + @post_clear_cache + @default_index + def merge_tree(self, rhs: Treeish, base: Union[None, Treeish] = None) -> "IndexFile": + """Merge the given `rhs` treeish into the current index, possibly taking + a common base treeish into account. + + As opposed to the :func:`from_tree` method, this allows you to use an already + existing tree as the left side of the merge. + + :param rhs: + Treeish reference pointing to the 'other' side of the merge. + + :param base: + Optional treeish reference pointing to the common base of `rhs` and this + index which equals lhs. + + :return: + self (containing the merge and possibly unmerged entries in case of + conflicts) + + :raise git.exc.GitCommandError: + If there is a merge conflict. The error will be raised at the first + conflicting path. If you want to have proper merge resolution to be done by + yourself, you have to commit the changed index (or make a valid tree from + it) and retry with a three-way :meth:`index.from_tree ` call. + """ + # -i : ignore working tree status + # --aggressive : handle more merge cases + # -m : do an actual merge + args: List[Union[Treeish, str]] = ["--aggressive", "-i", "-m"] + if base is not None: + args.append(base) + args.append(rhs) + + self.repo.git.read_tree(args) + return self + + @classmethod + def new(cls, repo: "Repo", *tree_sha: Union[str, Tree]) -> "IndexFile": + """Merge the given treeish revisions into a new index which is returned. + + This method behaves like ``git-read-tree --aggressive`` when doing the merge. + + :param repo: + The repository treeish are located in. + + :param tree_sha: + 20 byte or 40 byte tree sha or tree objects. + + :return: + New :class:`IndexFile` instance. Its path will be undefined. + If you intend to write such a merged Index, supply an alternate + ``file_path`` to its :meth:`write` method. + """ + tree_sha_bytes: List[bytes] = [to_bin_sha(str(t)) for t in tree_sha] + base_entries = aggressive_tree_merge(repo.odb, tree_sha_bytes) + + inst = cls(repo) + # Convert to entries dict. + entries: Dict[Tuple[PathLike, int], IndexEntry] = dict( + zip( + ((e.path, e.stage) for e in base_entries), + (IndexEntry.from_base(e) for e in base_entries), + ) + ) + + inst.entries = entries + return inst + + @classmethod + def from_tree(cls, repo: "Repo", *treeish: Treeish, **kwargs: Any) -> "IndexFile": + R"""Merge the given treeish revisions into a new index which is returned. + The original index will remain unaltered. + + :param repo: + The repository treeish are located in. + + :param treeish: + One, two or three :class:`~git.objects.tree.Tree` objects, + :class:`~git.objects.commit.Commit`\s or 40 byte hexshas. + + The result changes according to the amount of trees: + + 1. If 1 Tree is given, it will just be read into a new index. + 2. If 2 Trees are given, they will be merged into a new index using a two + way merge algorithm. Tree 1 is the 'current' tree, tree 2 is the 'other' + one. It behaves like a fast-forward. + 3. If 3 Trees are given, a 3-way merge will be performed with the first tree + being the common ancestor of tree 2 and tree 3. Tree 2 is the 'current' + tree, tree 3 is the 'other' one. + + :param kwargs: + Additional arguments passed to :manpage:`git-read-tree(1)`. + + :return: + New :class:`IndexFile` instance. It will point to a temporary index location + which does not exist anymore. If you intend to write such a merged Index, + supply an alternate ``file_path`` to its :meth:`write` method. + + :note: + In the three-way merge case, ``--aggressive`` will be specified to + automatically resolve more cases in a commonly correct manner. Specify + ``trivial=True`` as a keyword argument to override that. + + As the underlying :manpage:`git-read-tree(1)` command takes into account the + current index, it will be temporarily moved out of the way to prevent any + unexpected interference. + """ + if len(treeish) == 0 or len(treeish) > 3: + raise ValueError("Please specify between 1 and 3 treeish, got %i" % len(treeish)) + + arg_list: List[Union[Treeish, str]] = [] + # Ignore that the working tree and index possibly are out of date. + if len(treeish) > 1: + # Drop unmerged entries when reading our index and merging. + arg_list.append("--reset") + # Handle non-trivial cases the way a real merge does. + arg_list.append("--aggressive") + # END merge handling + + # Create the temporary file in the .git directory to be sure renaming + # works - /tmp/ directories could be on another device. + with _named_temporary_file_for_subprocess(repo.git_dir) as tmp_index: + arg_list.append("--index-output=%s" % tmp_index) + arg_list.extend(treeish) + + # Move the current index out of the way - otherwise the merge may fail as it + # considers existing entries. Moving it essentially clears the index. + # Unfortunately there is no 'soft' way to do it. + # The TemporaryFileSwap ensures the original file gets put back. + with TemporaryFileSwap(join_path_native(repo.git_dir, "index")): + repo.git.read_tree(*arg_list, **kwargs) + index = cls(repo, tmp_index) + index.entries # noqa: B018 # Force it to read the file as we will delete the temp-file. + return index + # END index merge handling + + # UTILITIES + + @unbare_repo + def _iter_expand_paths(self: "IndexFile", paths: Sequence[PathLike]) -> Iterator[PathLike]: + """Expand the directories in list of paths to the corresponding paths + accordingly. + + :note: + git will add items multiple times even if a glob overlapped with manually + specified paths or if paths where specified multiple times - we respect that + and do not prune. + """ + + def raise_exc(e: Exception) -> NoReturn: + raise e + + r = str(self.repo.working_tree_dir) + rs = r + os.sep + for path in paths: + abs_path = str(path) + if not osp.isabs(abs_path): + abs_path = osp.join(r, path) + # END make absolute path + + try: + st = os.lstat(abs_path) # Handles non-symlinks as well. + except OSError: + # The lstat call may fail as the path may contain globs as well. + pass + else: + if S_ISLNK(st.st_mode): + yield abs_path.replace(rs, "") + continue + # END check symlink + + # If the path is not already pointing to an existing file, resolve globs if possible. + if not os.path.exists(abs_path) and ("?" in abs_path or "*" in abs_path or "[" in abs_path): + resolved_paths = glob.glob(abs_path) + # not abs_path in resolved_paths: + # A glob() resolving to the same path we are feeding it with is a + # glob() that failed to resolve. If we continued calling ourselves + # we'd endlessly recurse. If the condition below evaluates to true + # then we are likely dealing with a file whose name contains wildcard + # characters. + if abs_path not in resolved_paths: + for f in self._iter_expand_paths(glob.glob(abs_path)): + yield str(f).replace(rs, "") + continue + # END glob handling + try: + for root, _dirs, files in os.walk(abs_path, onerror=raise_exc): + for rela_file in files: + # Add relative paths only. + yield osp.join(root.replace(rs, ""), rela_file) + # END for each file in subdir + # END for each subdirectory + except OSError: + # It was a file or something that could not be iterated. + yield abs_path.replace(rs, "") + # END path exception handling + # END for each path + + def _write_path_to_stdin( + self, + proc: "Popen", + filepath: PathLike, + item: PathLike, + fmakeexc: Callable[..., GitError], + fprogress: Callable[[PathLike, bool, PathLike], None], + read_from_stdout: bool = True, + ) -> Union[None, str]: + """Write path to ``proc.stdin`` and make sure it processes the item, including + progress. + + :return: + stdout string + + :param read_from_stdout: + If ``True``, ``proc.stdout`` will be read after the item was sent to stdin. + In that case, it will return ``None``. + + :note: + There is a bug in :manpage:`git-update-index(1)` that prevents it from + sending reports just in time. This is why we have a version that tries to + read stdout and one which doesn't. In fact, the stdout is not important as + the piped-in files are processed anyway and just in time. + + :note: + Newlines are essential here, git's behaviour is somewhat inconsistent on + this depending on the version, hence we try our best to deal with newlines + carefully. Usually the last newline will not be sent, instead we will close + stdin to break the pipe. + """ + fprogress(filepath, False, item) + rval: Union[None, str] = None + + if proc.stdin is not None: + try: + proc.stdin.write(("%s\n" % filepath).encode(defenc)) + except IOError as e: + # Pipe broke, usually because some error happened. + raise fmakeexc() from e + # END write exception handling + proc.stdin.flush() + + if read_from_stdout and proc.stdout is not None: + rval = proc.stdout.readline().strip() + fprogress(filepath, True, item) + return rval + + def iter_blobs( + self, predicate: Callable[[Tuple[StageType, Blob]], bool] = lambda t: True + ) -> Iterator[Tuple[StageType, Blob]]: + """ + :return: + Iterator yielding tuples of :class:`~git.objects.blob.Blob` objects and + stages, tuple(stage, Blob). + + :param predicate: + Function(t) returning ``True`` if tuple(stage, Blob) should be yielded by + the iterator. A default filter, the `~git.index.typ.BlobFilter`, allows you + to yield blobs only if they match a given list of paths. + """ + for entry in self.entries.values(): + blob = entry.to_blob(self.repo) + blob.size = entry.size + output = (entry.stage, blob) + if predicate(output): + yield output + # END for each entry + + def unmerged_blobs(self) -> Dict[PathLike, List[Tuple[StageType, Blob]]]: + """ + :return: + Dict(path : list(tuple(stage, Blob, ...))), being a dictionary associating a + path in the index with a list containing sorted stage/blob pairs. + + :note: + Blobs that have been removed in one side simply do not exist in the given + stage. That is, a file removed on the 'other' branch whose entries are at + stage 3 will not have a stage 3 entry. + """ + is_unmerged_blob = lambda t: t[0] != 0 + path_map: Dict[PathLike, List[Tuple[StageType, Blob]]] = {} + for stage, blob in self.iter_blobs(is_unmerged_blob): + path_map.setdefault(blob.path, []).append((stage, blob)) + # END for each unmerged blob + for line in path_map.values(): + line.sort() + + return path_map + + @classmethod + def entry_key(cls, *entry: Union[BaseIndexEntry, PathLike, StageType]) -> Tuple[PathLike, StageType]: + return entry_key(*entry) + + def resolve_blobs(self, iter_blobs: Iterator[Blob]) -> "IndexFile": + """Resolve the blobs given in blob iterator. + + This will effectively remove the index entries of the respective path at all + non-null stages and add the given blob as new stage null blob. + + For each path there may only be one blob, otherwise a :exc:`ValueError` will be + raised claiming the path is already at stage 0. + + :raise ValueError: + If one of the blobs already existed at stage 0. + + :return: + self + + :note: + You will have to write the index manually once you are done, i.e. + ``index.resolve_blobs(blobs).write()``. + """ + for blob in iter_blobs: + stage_null_key = (blob.path, 0) + if stage_null_key in self.entries: + raise ValueError("Path %r already exists at stage 0" % str(blob.path)) + # END assert blob is not stage 0 already + + # Delete all possible stages. + for stage in (1, 2, 3): + try: + del self.entries[(blob.path, stage)] + except KeyError: + pass + # END ignore key errors + # END for each possible stage + + self.entries[stage_null_key] = IndexEntry.from_blob(blob) + # END for each blob + + return self + + def update(self) -> "IndexFile": + """Reread the contents of our index file, discarding all cached information + we might have. + + :note: + This is a possibly dangerous operations as it will discard your changes to + :attr:`index.entries `. + + :return: + self + """ + self._delete_entries_cache() + # Allows to lazily reread on demand. + return self + + def write_tree(self) -> Tree: + """Write this index to a corresponding :class:`~git.objects.tree.Tree` object + into the repository's object database and return it. + + :return: + :class:`~git.objects.tree.Tree` object representing this index. + + :note: + The tree will be written even if one or more objects the tree refers to does + not yet exist in the object database. This could happen if you added entries + to the index directly. + + :raise ValueError: + If there are no entries in the cache. + + :raise git.exc.UnmergedEntriesError: + """ + # We obtain no lock as we just flush our contents to disk as tree. + # If we are a new index, the entries access will load our data accordingly. + mdb = MemoryDB() + entries = self._entries_sorted() + binsha, tree_items = write_tree_from_cache(entries, mdb, slice(0, len(entries))) + + # Copy changed trees only. + mdb.stream_copy(mdb.sha_iter(), self.repo.odb) + + # Note: Additional deserialization could be saved if write_tree_from_cache would + # return sorted tree entries. + root_tree = Tree(self.repo, binsha, path="") + root_tree._cache = tree_items + return root_tree + + def _process_diff_args( + self, + args: List[Union[PathLike, "git_diff.Diffable"]], + ) -> List[Union[PathLike, "git_diff.Diffable"]]: + try: + args.pop(args.index(self)) + except IndexError: + pass + # END remove self + return args + + def _to_relative_path(self, path: PathLike) -> PathLike: + """ + :return: + Version of path relative to our git directory or raise :exc:`ValueError` if + it is not within our git directory. + + :raise ValueError: + """ + if not osp.isabs(path): + return path + if self.repo.bare: + raise InvalidGitRepositoryError("require non-bare repository") + if not osp.normpath(str(path)).startswith(str(self.repo.working_tree_dir)): + raise ValueError("Absolute path %r is not in git repository at %r" % (path, self.repo.working_tree_dir)) + return os.path.relpath(path, self.repo.working_tree_dir) + + def _preprocess_add_items( + self, items: Union[PathLike, Sequence[Union[PathLike, Blob, BaseIndexEntry, "Submodule"]]] + ) -> Tuple[List[PathLike], List[BaseIndexEntry]]: + """Split the items into two lists of path strings and BaseEntries.""" + paths = [] + entries = [] + # if it is a string put in list + if isinstance(items, (str, os.PathLike)): + items = [items] + + for item in items: + if isinstance(item, (str, os.PathLike)): + paths.append(self._to_relative_path(item)) + elif isinstance(item, (Blob, Submodule)): + entries.append(BaseIndexEntry.from_blob(item)) + elif isinstance(item, BaseIndexEntry): + entries.append(item) + else: + raise TypeError("Invalid Type: %r" % item) + # END for each item + return paths, entries + + def _store_path(self, filepath: PathLike, fprogress: Callable) -> BaseIndexEntry: + """Store file at filepath in the database and return the base index entry. + + :note: + This needs the :func:`~git.index.util.git_working_dir` decorator active! + This must be ensured in the calling code. + """ + st = os.lstat(filepath) # Handles non-symlinks as well. + if S_ISLNK(st.st_mode): + # In PY3, readlink is a string, but we need bytes. + # In PY2, it was just OS encoded bytes, we assumed UTF-8. + open_stream: Callable[[], BinaryIO] = lambda: BytesIO(force_bytes(os.readlink(filepath), encoding=defenc)) + else: + open_stream = lambda: open(filepath, "rb") + with open_stream() as stream: + fprogress(filepath, False, filepath) + istream = self.repo.odb.store(IStream(Blob.type, st.st_size, stream)) + fprogress(filepath, True, filepath) + return BaseIndexEntry( + ( + stat_mode_to_index_mode(st.st_mode), + istream.binsha, + 0, + to_native_path_linux(filepath), + ) + ) + + @unbare_repo + @git_working_dir + def _entries_for_paths( + self, + paths: List[str], + path_rewriter: Union[Callable, None], + fprogress: Callable, + entries: List[BaseIndexEntry], + ) -> List[BaseIndexEntry]: + entries_added: List[BaseIndexEntry] = [] + if path_rewriter: + for path in paths: + if osp.isabs(path): + abspath = path + gitrelative_path = path[len(str(self.repo.working_tree_dir)) + 1 :] + else: + gitrelative_path = path + if self.repo.working_tree_dir: + abspath = osp.join(self.repo.working_tree_dir, gitrelative_path) + # END obtain relative and absolute paths + + blob = Blob( + self.repo, + Blob.NULL_BIN_SHA, + stat_mode_to_index_mode(os.stat(abspath).st_mode), + to_native_path_linux(gitrelative_path), + ) + # TODO: variable undefined + entries.append(BaseIndexEntry.from_blob(blob)) + # END for each path + del paths[:] + # END rewrite paths + + # HANDLE PATHS + assert len(entries_added) == 0 + for filepath in self._iter_expand_paths(paths): + entries_added.append(self._store_path(filepath, fprogress)) + # END for each filepath + # END path handling + return entries_added + + def add( + self, + items: Union[PathLike, Sequence[Union[PathLike, Blob, BaseIndexEntry, "Submodule"]]], + force: bool = True, + fprogress: Callable = lambda *args: None, + path_rewriter: Union[Callable[..., PathLike], None] = None, + write: bool = True, + write_extension_data: bool = False, + ) -> List[BaseIndexEntry]: + R"""Add files from the working tree, specific blobs, or + :class:`~git.index.typ.BaseIndexEntry`\s to the index. + + :param items: + Multiple types of items are supported, types can be mixed within one call. + Different types imply a different handling. File paths may generally be + relative or absolute. + + - path string + + Strings denote a relative or absolute path into the repository pointing + to an existing file, e.g., ``CHANGES``, `lib/myfile.ext``, + ``/home/gitrepo/lib/myfile.ext``. + + Absolute paths must start with working tree directory of this index's + repository to be considered valid. For example, if it was initialized + with a non-normalized path, like ``/root/repo/../repo``, absolute paths + to be added must start with ``/root/repo/../repo``. + + Paths provided like this must exist. When added, they will be written + into the object database. + + PathStrings may contain globs, such as ``lib/__init__*``. Or they can be + directories like ``lib``, which will add all the files within the + directory and subdirectories. + + This equals a straight :manpage:`git-add(1)`. + + They are added at stage 0. + + - :class:~`git.objects.blob.Blob` or + :class:`~git.objects.submodule.base.Submodule` object + + Blobs are added as they are assuming a valid mode is set. + + The file they refer to may or may not exist in the file system, but must + be a path relative to our repository. + + If their sha is null (40*0), their path must exist in the file system + relative to the git repository as an object will be created from the + data at the path. + + The handling now very much equals the way string paths are processed, + except that the mode you have set will be kept. This allows you to + create symlinks by settings the mode respectively and writing the target + of the symlink directly into the file. This equals a default Linux + symlink which is not dereferenced automatically, except that it can be + created on filesystems not supporting it as well. + + Please note that globs or directories are not allowed in + :class:`~git.objects.blob.Blob` objects. + + They are added at stage 0. + + - :class:`~git.index.typ.BaseIndexEntry` or type + + Handling equals the one of :class:~`git.objects.blob.Blob` objects, but + the stage may be explicitly set. Please note that Index Entries require + binary sha's. + + :param force: + **CURRENTLY INEFFECTIVE** + If ``True``, otherwise ignored or excluded files will be added anyway. As + opposed to the :manpage:`git-add(1)` command, we enable this flag by default + as the API user usually wants the item to be added even though they might be + excluded. + + :param fprogress: + Function with signature ``f(path, done=False, item=item)`` called for each + path to be added, one time once it is about to be added where ``done=False`` + and once after it was added where ``done=True``. + + ``item`` is set to the actual item we handle, either a path or a + :class:`~git.index.typ.BaseIndexEntry`. + + Please note that the processed path is not guaranteed to be present in the + index already as the index is currently being processed. + + :param path_rewriter: + Function, with signature ``(string) func(BaseIndexEntry)``, returning a path + for each passed entry which is the path to be actually recorded for the + object created from :attr:`entry.path `. + This allows you to write an index which is not identical to the layout of + the actual files on your hard-disk. If not ``None`` and `items` contain + plain paths, these paths will be converted to Entries beforehand and passed + to the path_rewriter. Please note that ``entry.path`` is relative to the git + repository. + + :param write: + If ``True``, the index will be written once it was altered. Otherwise the + changes only exist in memory and are not available to git commands. + + :param write_extension_data: + If ``True``, extension data will be written back to the index. This can lead + to issues in case it is containing the 'TREE' extension, which will cause + the :manpage:`git-commit(1)` command to write an old tree, instead of a new + one representing the now changed index. + + This doesn't matter if you use :meth:`IndexFile.commit`, which ignores the + 'TREE' extension altogether. You should set it to ``True`` if you intend to + use :meth:`IndexFile.commit` exclusively while maintaining support for + third-party extensions. Besides that, you can usually safely ignore the + built-in extensions when using GitPython on repositories that are not + handled manually at all. + + All current built-in extensions are listed here: + https://git-scm.com/docs/index-format + + :return: + List of :class:`~git.index.typ.BaseIndexEntry`\s representing the entries + just actually added. + + :raise OSError: + If a supplied path did not exist. Please note that + :class:`~git.index.typ.BaseIndexEntry` objects that do not have a null sha + will be added even if their paths do not exist. + """ + # Sort the entries into strings and Entries. + # Blobs are converted to entries automatically. + # Paths can be git-added. For everything else we use git-update-index. + paths, entries = self._preprocess_add_items(items) + entries_added: List[BaseIndexEntry] = [] + # This code needs a working tree, so we try not to run it unless required. + # That way, we are OK on a bare repository as well. + # If there are no paths, the rewriter has nothing to do either. + if paths: + entries_added.extend(self._entries_for_paths(paths, path_rewriter, fprogress, entries)) + + # HANDLE ENTRIES + if entries: + null_mode_entries = [e for e in entries if e.mode == 0] + if null_mode_entries: + raise ValueError( + "At least one Entry has a null-mode - please use index.remove to remove files for clarity" + ) + # END null mode should be remove + + # HANDLE ENTRY OBJECT CREATION + # Create objects if required, otherwise go with the existing shas. + null_entries_indices = [i for i, e in enumerate(entries) if e.binsha == Object.NULL_BIN_SHA] + if null_entries_indices: + + @git_working_dir + def handle_null_entries(self: "IndexFile") -> None: + for ei in null_entries_indices: + null_entry = entries[ei] + new_entry = self._store_path(null_entry.path, fprogress) + + # Update null entry. + entries[ei] = BaseIndexEntry( + ( + null_entry.mode, + new_entry.binsha, + null_entry.stage, + null_entry.path, + ) + ) + # END for each entry index + + # END closure + + handle_null_entries(self) + # END null_entry handling + + # REWRITE PATHS + # If we have to rewrite the entries, do so now, after we have generated all + # object sha's. + if path_rewriter: + for i, e in enumerate(entries): + entries[i] = BaseIndexEntry((e.mode, e.binsha, e.stage, path_rewriter(e))) + # END for each entry + # END handle path rewriting + + # Just go through the remaining entries and provide progress info. + for i, entry in enumerate(entries): + progress_sent = i in null_entries_indices + if not progress_sent: + fprogress(entry.path, False, entry) + fprogress(entry.path, True, entry) + # END handle progress + # END for each entry + entries_added.extend(entries) + # END if there are base entries + + # FINALIZE + # Add the new entries to this instance. + for entry in entries_added: + self.entries[(entry.path, 0)] = IndexEntry.from_base(entry) + + if write: + self.write(ignore_extension_data=not write_extension_data) + # END handle write + + return entries_added + + def _items_to_rela_paths( + self, + items: Union[PathLike, Sequence[Union[PathLike, BaseIndexEntry, Blob, Submodule]]], + ) -> List[PathLike]: + """Returns a list of repo-relative paths from the given items which + may be absolute or relative paths, entries or blobs.""" + paths = [] + # If string, put in list. + if isinstance(items, (str, os.PathLike)): + items = [items] + + for item in items: + if isinstance(item, (BaseIndexEntry, (Blob, Submodule))): + paths.append(self._to_relative_path(item.path)) + elif isinstance(item, (str, os.PathLike)): + paths.append(self._to_relative_path(item)) + else: + raise TypeError("Invalid item type: %r" % item) + # END for each item + return paths + + @post_clear_cache + @default_index + def remove( + self, + items: Union[PathLike, Sequence[Union[PathLike, Blob, BaseIndexEntry, "Submodule"]]], + working_tree: bool = False, + **kwargs: Any, + ) -> List[str]: + R"""Remove the given items from the index and optionally from the working tree + as well. + + :param items: + Multiple types of items are supported which may be be freely mixed. + + - path string + + Remove the given path at all stages. If it is a directory, you must + specify the ``r=True`` keyword argument to remove all file entries below + it. If absolute paths are given, they will be converted to a path + relative to the git repository directory containing the working tree + + The path string may include globs, such as ``*.c``. + + - :class:~`git.objects.blob.Blob` object + + Only the path portion is used in this case. + + - :class:`~git.index.typ.BaseIndexEntry` or compatible type + + The only relevant information here is the path. The stage is ignored. + + :param working_tree: + If ``True``, the entry will also be removed from the working tree, + physically removing the respective file. This may fail if there are + uncommitted changes in it. + + :param kwargs: + Additional keyword arguments to be passed to :manpage:`git-rm(1)`, such as + ``r`` to allow recursive removal. + + :return: + List(path_string, ...) list of repository relative paths that have been + removed effectively. + + This is interesting to know in case you have provided a directory or globs. + Paths are relative to the repository. + """ + args = [] + if not working_tree: + args.append("--cached") + args.append("--") + + # Preprocess paths. + paths = self._items_to_rela_paths(items) + removed_paths = self.repo.git.rm(args, paths, **kwargs).splitlines() + + # Process output to gain proper paths. + # rm 'path' + return [p[4:-1] for p in removed_paths] + + @post_clear_cache + @default_index + def move( + self, + items: Union[PathLike, Sequence[Union[PathLike, Blob, BaseIndexEntry, "Submodule"]]], + skip_errors: bool = False, + **kwargs: Any, + ) -> List[Tuple[str, str]]: + """Rename/move the items, whereas the last item is considered the destination of + the move operation. + + If the destination is a file, the first item (of two) must be a file as well. + + If the destination is a directory, it may be preceded by one or more directories + or files. + + The working tree will be affected in non-bare repositories. + + :param items: + Multiple types of items are supported, please see the :meth:`remove` method + for reference. + + :param skip_errors: + If ``True``, errors such as ones resulting from missing source files will be + skipped. + + :param kwargs: + Additional arguments you would like to pass to :manpage:`git-mv(1)`, such as + ``dry_run`` or ``force``. + + :return: + List(tuple(source_path_string, destination_path_string), ...) + + A list of pairs, containing the source file moved as well as its actual + destination. Relative to the repository root. + + :raise ValueError: + If only one item was given. + + :raise git.exc.GitCommandError: + If git could not handle your request. + """ + args = [] + if skip_errors: + args.append("-k") + + paths = self._items_to_rela_paths(items) + if len(paths) < 2: + raise ValueError("Please provide at least one source and one destination of the move operation") + + was_dry_run = kwargs.pop("dry_run", kwargs.pop("n", None)) + kwargs["dry_run"] = True + + # First execute rename in dry run so the command tells us what it actually does + # (for later output). + out = [] + mvlines = self.repo.git.mv(args, paths, **kwargs).splitlines() + + # Parse result - first 0:n/2 lines are 'checking ', the remaining ones are the + # 'renaming' ones which we parse. + for ln in range(int(len(mvlines) / 2), len(mvlines)): + tokens = mvlines[ln].split(" to ") + assert len(tokens) == 2, "Too many tokens in %s" % mvlines[ln] + + # [0] = Renaming x + # [1] = y + out.append((tokens[0][9:], tokens[1])) + # END for each line to parse + + # Either prepare for the real run, or output the dry-run result. + if was_dry_run: + return out + # END handle dry run + + # Now apply the actual operation. + kwargs.pop("dry_run") + self.repo.git.mv(args, paths, **kwargs) + + return out + + def commit( + self, + message: str, + parent_commits: Union[List[Commit], None] = None, + head: bool = True, + author: Union[None, Actor] = None, + committer: Union[None, Actor] = None, + author_date: Union[datetime.datetime, str, None] = None, + commit_date: Union[datetime.datetime, str, None] = None, + skip_hooks: bool = False, + ) -> Commit: + """Commit the current default index file, creating a + :class:`~git.objects.commit.Commit` object. + + For more information on the arguments, see + :meth:`Commit.create_from_tree `. + + :note: + If you have manually altered the :attr:`entries` member of this instance, + don't forget to :meth:`write` your changes to disk beforehand. + + :note: + Passing ``skip_hooks=True`` is the equivalent of using ``-n`` or + ``--no-verify`` on the command line. + + :return: + :class:`~git.objects.commit.Commit` object representing the new commit + """ + if not skip_hooks: + run_commit_hook("pre-commit", self) + + self._write_commit_editmsg(message) + run_commit_hook("commit-msg", self, self._commit_editmsg_filepath()) + message = self._read_commit_editmsg() + self._remove_commit_editmsg() + tree = self.write_tree() + rval = Commit.create_from_tree( + self.repo, + tree, + message, + parent_commits, + head, + author=author, + committer=committer, + author_date=author_date, + commit_date=commit_date, + ) + if not skip_hooks: + run_commit_hook("post-commit", self) + return rval + + def _write_commit_editmsg(self, message: str) -> None: + with open(self._commit_editmsg_filepath(), "wb") as commit_editmsg_file: + commit_editmsg_file.write(message.encode(defenc)) + + def _remove_commit_editmsg(self) -> None: + os.remove(self._commit_editmsg_filepath()) + + def _read_commit_editmsg(self) -> str: + with open(self._commit_editmsg_filepath(), "rb") as commit_editmsg_file: + return commit_editmsg_file.read().decode(defenc) + + def _commit_editmsg_filepath(self) -> str: + return osp.join(self.repo.common_dir, "COMMIT_EDITMSG") + + def _flush_stdin_and_wait(cls, proc: "Popen[bytes]", ignore_stdout: bool = False) -> bytes: + stdin_IO = proc.stdin + if stdin_IO: + stdin_IO.flush() + stdin_IO.close() + + stdout = b"" + if not ignore_stdout and proc.stdout: + stdout = proc.stdout.read() + + if proc.stdout: + proc.stdout.close() + proc.wait() + return stdout + + @default_index + def checkout( + self, + paths: Union[None, Iterable[PathLike]] = None, + force: bool = False, + fprogress: Callable = lambda *args: None, + **kwargs: Any, + ) -> Union[None, Iterator[PathLike], Sequence[PathLike]]: + """Check out the given paths or all files from the version known to the index + into the working tree. + + :note: + Be sure you have written pending changes using the :meth:`write` method in + case you have altered the entries dictionary directly. + + :param paths: + If ``None``, all paths in the index will be checked out. + Otherwise an iterable of relative or absolute paths or a single path + pointing to files or directories in the index is expected. + + :param force: + If ``True``, existing files will be overwritten even if they contain local + modifications. + If ``False``, these will trigger a :exc:`~git.exc.CheckoutError`. + + :param fprogress: + See :meth:`IndexFile.add` for signature and explanation. + + The provided progress information will contain ``None`` as path and item if + no explicit paths are given. Otherwise progress information will be send + prior and after a file has been checked out. + + :param kwargs: + Additional arguments to be passed to :manpage:`git-checkout-index(1)`. + + :return: + Iterable yielding paths to files which have been checked out and are + guaranteed to match the version stored in the index. + + :raise git.exc.CheckoutError: + * If at least one file failed to be checked out. This is a summary, hence it + will checkout as many files as it can anyway. + * If one of files or directories do not exist in the index (as opposed to + the original git command, which ignores them). + + :raise git.exc.GitCommandError: + If error lines could not be parsed - this truly is an exceptional state. + + :note: + The checkout is limited to checking out the files in the index. Files which + are not in the index anymore and exist in the working tree will not be + deleted. This behaviour is fundamentally different to ``head.checkout``, + i.e. if you want :manpage:`git-checkout(1)`-like behaviour, use + ``head.checkout`` instead of ``index.checkout``. + """ + args = ["--index"] + if force: + args.append("--force") + + failed_files = [] + failed_reasons = [] + unknown_lines = [] + + def handle_stderr(proc: "Popen[bytes]", iter_checked_out_files: Iterable[PathLike]) -> None: + stderr_IO = proc.stderr + if not stderr_IO: + return # Return early if stderr empty. + + stderr_bytes = stderr_IO.read() + # line contents: + stderr = stderr_bytes.decode(defenc) + # git-checkout-index: this already exists + endings = ( + " already exists", + " is not in the cache", + " does not exist at stage", + " is unmerged", + ) + for line in stderr.splitlines(): + if not line.startswith("git checkout-index: ") and not line.startswith("git-checkout-index: "): + is_a_dir = " is a directory" + unlink_issue = "unable to unlink old '" + already_exists_issue = " already exists, no checkout" # created by entry.c:checkout_entry(...) + if line.endswith(is_a_dir): + failed_files.append(line[: -len(is_a_dir)]) + failed_reasons.append(is_a_dir) + elif line.startswith(unlink_issue): + failed_files.append(line[len(unlink_issue) : line.rfind("'")]) + failed_reasons.append(unlink_issue) + elif line.endswith(already_exists_issue): + failed_files.append(line[: -len(already_exists_issue)]) + failed_reasons.append(already_exists_issue) + else: + unknown_lines.append(line) + continue + # END special lines parsing + + for e in endings: + if line.endswith(e): + failed_files.append(line[20 : -len(e)]) + failed_reasons.append(e) + break + # END if ending matches + # END for each possible ending + # END for each line + if unknown_lines: + raise GitCommandError(("git-checkout-index",), 128, stderr) + if failed_files: + valid_files = list(set(iter_checked_out_files) - set(failed_files)) + raise CheckoutError( + "Some files could not be checked out from the index due to local modifications", + failed_files, + valid_files, + failed_reasons, + ) + + # END stderr handler + + if paths is None: + args.append("--all") + kwargs["as_process"] = 1 + fprogress(None, False, None) + proc = self.repo.git.checkout_index(*args, **kwargs) + proc.wait() + fprogress(None, True, None) + rval_iter = (e.path for e in self.entries.values()) + handle_stderr(proc, rval_iter) + return rval_iter + else: + if isinstance(paths, str): + paths = [paths] + + # Make sure we have our entries loaded before we start checkout_index, which + # will hold a lock on it. We try to get the lock as well during our entries + # initialization. + self.entries # noqa: B018 + + args.append("--stdin") + kwargs["as_process"] = True + kwargs["istream"] = subprocess.PIPE + proc = self.repo.git.checkout_index(args, **kwargs) + # FIXME: Reading from GIL! + make_exc = lambda: GitCommandError(("git-checkout-index",) + tuple(args), 128, proc.stderr.read()) + checked_out_files: List[PathLike] = [] + + for path in paths: + co_path = to_native_path_linux(self._to_relative_path(path)) + # If the item is not in the index, it could be a directory. + path_is_directory = False + + try: + self.entries[(co_path, 0)] + except KeyError: + folder = str(co_path) + if not folder.endswith("/"): + folder += "/" + for entry in self.entries.values(): + if str(entry.path).startswith(folder): + p = entry.path + self._write_path_to_stdin(proc, p, p, make_exc, fprogress, read_from_stdout=False) + checked_out_files.append(p) + path_is_directory = True + # END if entry is in directory + # END for each entry + # END path exception handlnig + + if not path_is_directory: + self._write_path_to_stdin(proc, co_path, path, make_exc, fprogress, read_from_stdout=False) + checked_out_files.append(co_path) + # END path is a file + # END for each path + try: + self._flush_stdin_and_wait(proc, ignore_stdout=True) + except GitCommandError: + # Without parsing stdout we don't know what failed. + raise CheckoutError( # noqa: B904 + "Some files could not be checked out from the index, probably because they didn't exist.", + failed_files, + [], + failed_reasons, + ) + + handle_stderr(proc, checked_out_files) + return checked_out_files + # END paths handling + + @default_index + def reset( + self, + commit: Union[Commit, "Reference", str] = "HEAD", + working_tree: bool = False, + paths: Union[None, Iterable[PathLike]] = None, + head: bool = False, + **kwargs: Any, + ) -> "IndexFile": + """Reset the index to reflect the tree at the given commit. This will not adjust + our HEAD reference by default, as opposed to + :meth:`HEAD.reset `. + + :param commit: + Revision, :class:`~git.refs.reference.Reference` or + :class:`~git.objects.commit.Commit` specifying the commit we should + represent. + + If you want to specify a tree only, use :meth:`IndexFile.from_tree` and + overwrite the default index. + + :param working_tree: + If ``True``, the files in the working tree will reflect the changed index. + If ``False``, the working tree will not be touched. + Please note that changes to the working copy will be discarded without + warning! + + :param head: + If ``True``, the head will be set to the given commit. This is ``False`` by + default, but if ``True``, this method behaves like + :meth:`HEAD.reset `. + + :param paths: + If given as an iterable of absolute or repository-relative paths, only these + will be reset to their state at the given commit-ish. + The paths need to exist at the commit, otherwise an exception will be + raised. + + :param kwargs: + Additional keyword arguments passed to :manpage:`git-reset(1)`. + + :note: + :meth:`IndexFile.reset`, as opposed to + :meth:`HEAD.reset `, will not delete any files in + order to maintain a consistent working tree. Instead, it will just check out + the files according to their state in the index. + If you want :manpage:`git-reset(1)`-like behaviour, use + :meth:`HEAD.reset ` instead. + + :return: + self + """ + # What we actually want to do is to merge the tree into our existing index, + # which is what git-read-tree does. + new_inst = type(self).from_tree(self.repo, commit) + if not paths: + self.entries = new_inst.entries + else: + nie = new_inst.entries + for path in paths: + path = self._to_relative_path(path) + try: + key = entry_key(path, 0) + self.entries[key] = nie[key] + except KeyError: + # If key is not in theirs, it mustn't be in ours. + try: + del self.entries[key] + except KeyError: + pass + # END handle deletion keyerror + # END handle keyerror + # END for each path + # END handle paths + self.write() + + if working_tree: + self.checkout(paths=paths, force=True) + # END handle working tree + + if head: + self.repo.head.set_commit(self.repo.commit(commit), logmsg="%s: Updating HEAD" % commit) + # END handle head change + + return self + + # FIXME: This is documented to accept the same parameters as Diffable.diff, but this + # does not handle NULL_TREE for `other`. (The suppressed mypy error is about this.) + def diff( + self, + other: Union[ # type: ignore[override] + Literal[git_diff.DiffConstants.INDEX], + "Tree", + "Commit", + str, + None, + ] = git_diff.INDEX, + paths: Union[PathLike, List[PathLike], Tuple[PathLike, ...], None] = None, + create_patch: bool = False, + **kwargs: Any, + ) -> git_diff.DiffIndex[git_diff.Diff]: + """Diff this index against the working copy or a :class:`~git.objects.tree.Tree` + or :class:`~git.objects.commit.Commit` object. + + For documentation of the parameters and return values, see + :meth:`Diffable.diff `. + + :note: + Will only work with indices that represent the default git index as they + have not been initialized with a stream. + """ + # Only run if we are the default repository index. + if self._file_path != self._index_path(): + raise AssertionError("Cannot call %r on indices that do not represent the default git index" % self.diff()) + # Index against index is always empty. + if other is self.INDEX: + return git_diff.DiffIndex() + + # Index against anything but None is a reverse diff with the respective item. + # Handle existing -R flags properly. + # Transform strings to the object so that we can call diff on it. + if isinstance(other, str): + other = self.repo.rev_parse(other) + # END object conversion + + if isinstance(other, Object): # For Tree or Commit. + # Invert the existing R flag. + cur_val = kwargs.get("R", False) + kwargs["R"] = not cur_val + return other.diff(self.INDEX, paths, create_patch, **kwargs) + # END diff against other item handling + + # If other is not None here, something is wrong. + if other is not None: + raise ValueError("other must be None, Diffable.INDEX, a Tree or Commit, was %r" % other) + + # Diff against working copy - can be handled by superclass natively. + return super().diff(other, paths, create_patch, **kwargs) diff --git a/venv/lib/python3.10/site-packages/git/index/fun.py b/venv/lib/python3.10/site-packages/git/index/fun.py new file mode 100644 index 0000000000000000000000000000000000000000..59cce6ae6ef845d686b4724fa99681805d9638a1 --- /dev/null +++ b/venv/lib/python3.10/site-packages/git/index/fun.py @@ -0,0 +1,465 @@ +# This module is part of GitPython and is released under the +# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/ + +"""Standalone functions to accompany the index implementation and make it more +versatile.""" + +__all__ = [ + "write_cache", + "read_cache", + "write_tree_from_cache", + "entry_key", + "stat_mode_to_index_mode", + "S_IFGITLINK", + "run_commit_hook", + "hook_path", +] + +from io import BytesIO +import os +import os.path as osp +from pathlib import Path +from stat import S_IFDIR, S_IFLNK, S_IFMT, S_IFREG, S_ISDIR, S_ISLNK, S_IXUSR +import subprocess +import sys + +from gitdb.base import IStream +from gitdb.typ import str_tree_type + +from git.cmd import handle_process_output, safer_popen +from git.compat import defenc, force_bytes, force_text, safe_decode +from git.exc import HookExecutionError, UnmergedEntriesError +from git.objects.fun import ( + traverse_tree_recursive, + traverse_trees_recursive, + tree_to_stream, +) +from git.util import IndexFileSHA1Writer, finalize_process + +from .typ import BaseIndexEntry, IndexEntry, CE_NAMEMASK, CE_STAGESHIFT +from .util import pack, unpack + +# typing ----------------------------------------------------------------------------- + +from typing import Dict, IO, List, Sequence, TYPE_CHECKING, Tuple, Type, Union, cast + +from git.types import PathLike + +if TYPE_CHECKING: + from git.db import GitCmdObjectDB + from git.objects.tree import TreeCacheTup + + from .base import IndexFile + +# ------------------------------------------------------------------------------------ + +S_IFGITLINK = S_IFLNK | S_IFDIR +"""Flags for a submodule.""" + +CE_NAMEMASK_INV = ~CE_NAMEMASK + + +def hook_path(name: str, git_dir: PathLike) -> str: + """:return: path to the given named hook in the given git repository directory""" + return osp.join(git_dir, "hooks", name) + + +def _has_file_extension(path: str) -> str: + return osp.splitext(path)[1] + + +def run_commit_hook(name: str, index: "IndexFile", *args: str) -> None: + """Run the commit hook of the given name. Silently ignore hooks that do not exist. + + :param name: + Name of hook, like ``pre-commit``. + + :param index: + :class:`~git.index.base.IndexFile` instance. + + :param args: + Arguments passed to hook file. + + :raise git.exc.HookExecutionError: + """ + hp = hook_path(name, index.repo.git_dir) + if not os.access(hp, os.X_OK): + return + + env = os.environ.copy() + env["GIT_INDEX_FILE"] = safe_decode(str(index.path)) + env["GIT_EDITOR"] = ":" + cmd = [hp] + try: + if sys.platform == "win32" and not _has_file_extension(hp): + # Windows only uses extensions to determine how to open files + # (doesn't understand shebangs). Try using bash to run the hook. + relative_hp = Path(hp).relative_to(index.repo.working_dir).as_posix() + cmd = ["bash.exe", relative_hp] + + process = safer_popen( + cmd + list(args), + env=env, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=index.repo.working_dir, + ) + except Exception as ex: + raise HookExecutionError(hp, ex) from ex + else: + stdout_list: List[str] = [] + stderr_list: List[str] = [] + handle_process_output(process, stdout_list.append, stderr_list.append, finalize_process) + stdout = "".join(stdout_list) + stderr = "".join(stderr_list) + if process.returncode != 0: + stdout = force_text(stdout, defenc) + stderr = force_text(stderr, defenc) + raise HookExecutionError(hp, process.returncode, stderr, stdout) + # END handle return code + + +def stat_mode_to_index_mode(mode: int) -> int: + """Convert the given mode from a stat call to the corresponding index mode and + return it.""" + if S_ISLNK(mode): # symlinks + return S_IFLNK + if S_ISDIR(mode) or S_IFMT(mode) == S_IFGITLINK: # submodules + return S_IFGITLINK + return S_IFREG | (mode & S_IXUSR and 0o755 or 0o644) # blobs with or without executable bit + + +def write_cache( + entries: Sequence[Union[BaseIndexEntry, "IndexEntry"]], + stream: IO[bytes], + extension_data: Union[None, bytes] = None, + ShaStreamCls: Type[IndexFileSHA1Writer] = IndexFileSHA1Writer, +) -> None: + """Write the cache represented by entries to a stream. + + :param entries: + **Sorted** list of entries. + + :param stream: + Stream to wrap into the AdapterStreamCls - it is used for final output. + + :param ShaStreamCls: + Type to use when writing to the stream. It produces a sha while writing to it, + before the data is passed on to the wrapped stream. + + :param extension_data: + Any kind of data to write as a trailer, it must begin a 4 byte identifier, + followed by its size (4 bytes). + """ + # Wrap the stream into a compatible writer. + stream_sha = ShaStreamCls(stream) + + tell = stream_sha.tell + write = stream_sha.write + + # Header + version = 2 + write(b"DIRC") + write(pack(">LL", version, len(entries))) + + # Body + for entry in entries: + beginoffset = tell() + write(entry.ctime_bytes) # ctime + write(entry.mtime_bytes) # mtime + path_str = str(entry.path) + path: bytes = force_bytes(path_str, encoding=defenc) + plen = len(path) & CE_NAMEMASK # Path length + assert plen == len(path), "Path %s too long to fit into index" % entry.path + flags = plen | (entry.flags & CE_NAMEMASK_INV) # Clear possible previous values. + write( + pack( + ">LLLLLL20sH", + entry.dev, + entry.inode, + entry.mode, + entry.uid, + entry.gid, + entry.size, + entry.binsha, + flags, + ) + ) + write(path) + real_size = (tell() - beginoffset + 8) & ~7 + write(b"\0" * ((beginoffset + real_size) - tell())) + # END for each entry + + # Write previously cached extensions data. + if extension_data is not None: + stream_sha.write(extension_data) + + # Write the sha over the content. + stream_sha.write_sha() + + +def read_header(stream: IO[bytes]) -> Tuple[int, int]: + """Return tuple(version_long, num_entries) from the given stream.""" + type_id = stream.read(4) + if type_id != b"DIRC": + raise AssertionError("Invalid index file header: %r" % type_id) + unpacked = cast(Tuple[int, int], unpack(">LL", stream.read(4 * 2))) + version, num_entries = unpacked + + # TODO: Handle version 3: extended data, see read-cache.c. + assert version in (1, 2) + return version, num_entries + + +def entry_key(*entry: Union[BaseIndexEntry, PathLike, int]) -> Tuple[PathLike, int]: + """ + :return: + Key suitable to be used for the + :attr:`index.entries ` dictionary. + + :param entry: + One instance of type BaseIndexEntry or the path and the stage. + """ + + # def is_entry_key_tup(entry_key: Tuple) -> TypeGuard[Tuple[PathLike, int]]: + # return isinstance(entry_key, tuple) and len(entry_key) == 2 + + if len(entry) == 1: + entry_first = entry[0] + assert isinstance(entry_first, BaseIndexEntry) + return (entry_first.path, entry_first.stage) + else: + # assert is_entry_key_tup(entry) + entry = cast(Tuple[PathLike, int], entry) + return entry + # END handle entry + + +def read_cache( + stream: IO[bytes], +) -> Tuple[int, Dict[Tuple[PathLike, int], "IndexEntry"], bytes, bytes]: + """Read a cache file from the given stream. + + :return: + tuple(version, entries_dict, extension_data, content_sha) + + * *version* is the integer version number. + * *entries_dict* is a dictionary which maps IndexEntry instances to a path at a + stage. + * *extension_data* is ``""`` or 4 bytes of type + 4 bytes of size + size bytes. + * *content_sha* is a 20 byte sha on all cache file contents. + """ + version, num_entries = read_header(stream) + count = 0 + entries: Dict[Tuple[PathLike, int], "IndexEntry"] = {} + + read = stream.read + tell = stream.tell + while count < num_entries: + beginoffset = tell() + ctime = unpack(">8s", read(8))[0] + mtime = unpack(">8s", read(8))[0] + (dev, ino, mode, uid, gid, size, sha, flags) = unpack(">LLLLLL20sH", read(20 + 4 * 6 + 2)) + path_size = flags & CE_NAMEMASK + path = read(path_size).decode(defenc) + + real_size = (tell() - beginoffset + 8) & ~7 + read((beginoffset + real_size) - tell()) + entry = IndexEntry((mode, sha, flags, path, ctime, mtime, dev, ino, uid, gid, size)) + # entry_key would be the method to use, but we save the effort. + entries[(path, entry.stage)] = entry + count += 1 + # END for each entry + + # The footer contains extension data and a sha on the content so far. + # Keep the extension footer,and verify we have a sha in the end. + # Extension data format is: + # 4 bytes ID + # 4 bytes length of chunk + # Repeated 0 - N times + extension_data = stream.read(~0) + assert len(extension_data) > 19, ( + "Index Footer was not at least a sha on content as it was only %i bytes in size" % len(extension_data) + ) + + content_sha = extension_data[-20:] + + # Truncate the sha in the end as we will dynamically create it anyway. + extension_data = extension_data[:-20] + + return (version, entries, extension_data, content_sha) + + +def write_tree_from_cache( + entries: List[IndexEntry], odb: "GitCmdObjectDB", sl: slice, si: int = 0 +) -> Tuple[bytes, List["TreeCacheTup"]]: + R"""Create a tree from the given sorted list of entries and put the respective + trees into the given object database. + + :param entries: + **Sorted** list of :class:`~git.index.typ.IndexEntry`\s. + + :param odb: + Object database to store the trees in. + + :param si: + Start index at which we should start creating subtrees. + + :param sl: + Slice indicating the range we should process on the entries list. + + :return: + tuple(binsha, list(tree_entry, ...)) + + A tuple of a sha and a list of tree entries being a tuple of hexsha, mode, name. + """ + tree_items: List["TreeCacheTup"] = [] + + ci = sl.start + end = sl.stop + while ci < end: + entry = entries[ci] + if entry.stage != 0: + raise UnmergedEntriesError(entry) + # END abort on unmerged + ci += 1 + rbound = entry.path.find("/", si) + if rbound == -1: + # It's not a tree. + tree_items.append((entry.binsha, entry.mode, entry.path[si:])) + else: + # Find common base range. + base = entry.path[si:rbound] + xi = ci + while xi < end: + oentry = entries[xi] + orbound = oentry.path.find("/", si) + if orbound == -1 or oentry.path[si:orbound] != base: + break + # END abort on base mismatch + xi += 1 + # END find common base + + # Enter recursion. + # ci - 1 as we want to count our current item as well. + sha, _tree_entry_list = write_tree_from_cache(entries, odb, slice(ci - 1, xi), rbound + 1) + tree_items.append((sha, S_IFDIR, base)) + + # Skip ahead. + ci = xi + # END handle bounds + # END for each entry + + # Finally create the tree. + sio = BytesIO() + tree_to_stream(tree_items, sio.write) # Writes to stream as bytes, but doesn't change tree_items. + sio.seek(0) + + istream = odb.store(IStream(str_tree_type, len(sio.getvalue()), sio)) + return (istream.binsha, tree_items) + + +def _tree_entry_to_baseindexentry(tree_entry: "TreeCacheTup", stage: int) -> BaseIndexEntry: + return BaseIndexEntry((tree_entry[1], tree_entry[0], stage << CE_STAGESHIFT, tree_entry[2])) + + +def aggressive_tree_merge(odb: "GitCmdObjectDB", tree_shas: Sequence[bytes]) -> List[BaseIndexEntry]: + R""" + :return: + List of :class:`~git.index.typ.BaseIndexEntry`\s representing the aggressive + merge of the given trees. All valid entries are on stage 0, whereas the + conflicting ones are left on stage 1, 2 or 3, whereas stage 1 corresponds to the + common ancestor tree, 2 to our tree and 3 to 'their' tree. + + :param tree_shas: + 1, 2 or 3 trees as identified by their binary 20 byte shas. If 1 or two, the + entries will effectively correspond to the last given tree. If 3 are given, a 3 + way merge is performed. + """ + out: List[BaseIndexEntry] = [] + + # One and two way is the same for us, as we don't have to handle an existing + # index, instrea + if len(tree_shas) in (1, 2): + for entry in traverse_tree_recursive(odb, tree_shas[-1], ""): + out.append(_tree_entry_to_baseindexentry(entry, 0)) + # END for each entry + return out + # END handle single tree + + if len(tree_shas) > 3: + raise ValueError("Cannot handle %i trees at once" % len(tree_shas)) + + # Three trees. + for base, ours, theirs in traverse_trees_recursive(odb, tree_shas, ""): + if base is not None: + # Base version exists. + if ours is not None: + # Ours exists. + if theirs is not None: + # It exists in all branches. Ff it was changed in both + # its a conflict. Otherwise, we take the changed version. + # This should be the most common branch, so it comes first. + if (base[0] != ours[0] and base[0] != theirs[0] and ours[0] != theirs[0]) or ( + base[1] != ours[1] and base[1] != theirs[1] and ours[1] != theirs[1] + ): + # Changed by both. + out.append(_tree_entry_to_baseindexentry(base, 1)) + out.append(_tree_entry_to_baseindexentry(ours, 2)) + out.append(_tree_entry_to_baseindexentry(theirs, 3)) + elif base[0] != ours[0] or base[1] != ours[1]: + # Only we changed it. + out.append(_tree_entry_to_baseindexentry(ours, 0)) + else: + # Either nobody changed it, or they did. In either + # case, use theirs. + out.append(_tree_entry_to_baseindexentry(theirs, 0)) + # END handle modification + else: + if ours[0] != base[0] or ours[1] != base[1]: + # They deleted it, we changed it, conflict. + out.append(_tree_entry_to_baseindexentry(base, 1)) + out.append(_tree_entry_to_baseindexentry(ours, 2)) + # else: + # # We didn't change it, ignore. + # pass + # END handle our change + # END handle theirs + else: + if theirs is None: + # Deleted in both, its fine - it's out. + pass + else: + if theirs[0] != base[0] or theirs[1] != base[1]: + # Deleted in ours, changed theirs, conflict. + out.append(_tree_entry_to_baseindexentry(base, 1)) + out.append(_tree_entry_to_baseindexentry(theirs, 3)) + # END theirs changed + # else: + # # Theirs didn't change. + # pass + # END handle theirs + # END handle ours + else: + # All three can't be None. + if ours is None: + # Added in their branch. + assert theirs is not None + out.append(_tree_entry_to_baseindexentry(theirs, 0)) + elif theirs is None: + # Added in our branch. + out.append(_tree_entry_to_baseindexentry(ours, 0)) + else: + # Both have it, except for the base, see whether it changed. + if ours[0] != theirs[0] or ours[1] != theirs[1]: + out.append(_tree_entry_to_baseindexentry(ours, 2)) + out.append(_tree_entry_to_baseindexentry(theirs, 3)) + else: + # It was added the same in both. + out.append(_tree_entry_to_baseindexentry(ours, 0)) + # END handle two items + # END handle heads + # END handle base exists + # END for each entries tuple + + return out diff --git a/venv/lib/python3.10/site-packages/git/objects/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/git/objects/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..640bc35120473138639bd76b3f96b506f3ca647f Binary files /dev/null and b/venv/lib/python3.10/site-packages/git/objects/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/git/objects/__pycache__/blob.cpython-310.pyc b/venv/lib/python3.10/site-packages/git/objects/__pycache__/blob.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c87fcb18c7bec14af6f61ea412170798c3d66a7d Binary files /dev/null and b/venv/lib/python3.10/site-packages/git/objects/__pycache__/blob.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/git/objects/__pycache__/commit.cpython-310.pyc b/venv/lib/python3.10/site-packages/git/objects/__pycache__/commit.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0531e7026ad89d94e196c09398d03c11dabeace8 Binary files /dev/null and b/venv/lib/python3.10/site-packages/git/objects/__pycache__/commit.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/git/objects/__pycache__/util.cpython-310.pyc b/venv/lib/python3.10/site-packages/git/objects/__pycache__/util.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..29176e9eba3820f3c14c1a97fbb29f4e5467f042 Binary files /dev/null and b/venv/lib/python3.10/site-packages/git/objects/__pycache__/util.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/CodeScanAlertInstance.py b/venv/lib/python3.10/site-packages/github/CodeScanAlertInstance.py new file mode 100644 index 0000000000000000000000000000000000000000..5bc8a3b31ce561bc6d61a885552cff7ff2c8117c --- /dev/null +++ b/venv/lib/python3.10/site-packages/github/CodeScanAlertInstance.py @@ -0,0 +1,115 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2022 Eric Nieuwland # +# Copyright 2023 Enrico Minack # +# Copyright 2023 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2023 Trim21 # +# Copyright 2024 Enrico Minack # +# Copyright 2024 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2025 Enrico Minack # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +import github.CodeScanAlertInstanceLocation +from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet + +if TYPE_CHECKING: + from github.CodeScanAlertInstanceLocation import CodeScanAlertInstanceLocation + + +class CodeScanAlertInstance(NonCompletableGithubObject): + """ + This class represents code scanning alert instances. + + The reference can be found here + https://docs.github.com/en/rest/reference/code-scanning. + + """ + + def _initAttributes(self) -> None: + self._analysis_key: Attribute[str] = NotSet + self._classifications: Attribute[list[str]] = NotSet + self._commit_sha: Attribute[str] = NotSet + self._environment: Attribute[str] = NotSet + self._location: Attribute[CodeScanAlertInstanceLocation] = NotSet + self._message: Attribute[dict[str, Any]] = NotSet + self._ref: Attribute[str] = NotSet + self._state: Attribute[str] = NotSet + + def __repr__(self) -> str: + return self.get__repr__({"ref": self.ref, "analysis_key": self.analysis_key}) + + @property + def analysis_key(self) -> str: + return self._analysis_key.value + + @property + def classifications(self) -> list[str]: + return self._classifications.value + + @property + def commit_sha(self) -> str: + return self._commit_sha.value + + @property + def environment(self) -> str: + return self._environment.value + + @property + def location(self) -> CodeScanAlertInstanceLocation: + return self._location.value + + @property + def message(self) -> dict[str, Any]: + return self._message.value + + @property + def ref(self) -> str: + return self._ref.value + + @property + def state(self) -> str: + return self._state.value + + def _useAttributes(self, attributes: dict[str, Any]) -> None: + if "analysis_key" in attributes: # pragma no branch + self._analysis_key = self._makeStringAttribute(attributes["analysis_key"]) + if "classifications" in attributes: # pragma no branch + self._classifications = self._makeListOfStringsAttribute(attributes["classifications"]) + if "commit_sha" in attributes: # pragma no branch + self._commit_sha = self._makeStringAttribute(attributes["commit_sha"]) + if "environment" in attributes: # pragma no branch + self._environment = self._makeStringAttribute(attributes["environment"]) + if "environment" in attributes: # pragma no branch + self._environment = self._makeStringAttribute(attributes["environment"]) + if "location" in attributes: # pragma no branch + self._location = self._makeClassAttribute( + github.CodeScanAlertInstanceLocation.CodeScanAlertInstanceLocation, + attributes["location"], + ) + if "message" in attributes: # pragma no branch + self._message = self._makeDictAttribute(attributes["message"]) + if "ref" in attributes: # pragma no branch + self._ref = self._makeStringAttribute(attributes["ref"]) + if "state" in attributes: # pragma no branch + self._state = self._makeStringAttribute(attributes["state"]) diff --git a/venv/lib/python3.10/site-packages/github/CodeScanAlertInstanceLocation.py b/venv/lib/python3.10/site-packages/github/CodeScanAlertInstanceLocation.py new file mode 100644 index 0000000000000000000000000000000000000000..119982b6b5d462eb87767996ce19343cd9cb8b48 --- /dev/null +++ b/venv/lib/python3.10/site-packages/github/CodeScanAlertInstanceLocation.py @@ -0,0 +1,95 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2020 Dhruv Manilawala # +# Copyright 2020 Steve Kowalik # +# Copyright 2022 Eric Nieuwland # +# Copyright 2023 Enrico Minack # +# Copyright 2023 Trim21 # +# Copyright 2024 Enrico Minack # +# Copyright 2024 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2025 Enrico Minack # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from typing import Any, Dict + +from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet + + +class CodeScanAlertInstanceLocation(NonCompletableGithubObject): + """ + This class represents code scanning alert instance locations. + + The reference can be found here + https://docs.github.com/en/rest/reference/code-scanning. + + """ + + def _initAttributes(self) -> None: + self._end_column: Attribute[int] = NotSet + self._end_line: Attribute[int] = NotSet + self._path: Attribute[str] = NotSet + self._start_column: Attribute[int] = NotSet + self._start_line: Attribute[int] = NotSet + + def __repr__(self) -> str: + return self.get__repr__( + { + "path": self.path, + "start_line": self.start_line, + "start_column": self.start_column, + "end_line": self.end_line, + "end_column": self.end_column, + } + ) + + def __str__(self) -> str: + return f"{self.path} @ l{self.start_line}:c{self.start_column}-l{self.end_line}:c{self.end_column}" + + @property + def end_column(self) -> int: + return self._end_column.value + + @property + def end_line(self) -> int: + return self._end_line.value + + @property + def path(self) -> str: + return self._path.value + + @property + def start_column(self) -> int: + return self._start_column.value + + @property + def start_line(self) -> int: + return self._start_line.value + + def _useAttributes(self, attributes: Dict[str, Any]) -> None: + if "end_column" in attributes: # pragma no branch + self._end_column = self._makeIntAttribute(attributes["end_column"]) + if "end_line" in attributes: # pragma no branch + self._end_line = self._makeIntAttribute(attributes["end_line"]) + if "path" in attributes: # pragma no branch + self._path = self._makeStringAttribute(attributes["path"]) + if "start_column" in attributes: # pragma no branch + self._start_column = self._makeIntAttribute(attributes["start_column"]) + if "start_line" in attributes: # pragma no branch + self._start_line = self._makeIntAttribute(attributes["start_line"]) diff --git a/venv/lib/python3.10/site-packages/github/CodeScanRule.py b/venv/lib/python3.10/site-packages/github/CodeScanRule.py new file mode 100644 index 0000000000000000000000000000000000000000..03abb5c38acecff649eba457cb0472b72b14494a --- /dev/null +++ b/venv/lib/python3.10/site-packages/github/CodeScanRule.py @@ -0,0 +1,98 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2012 Vincent Jacques # +# Copyright 2012 Zearin # +# Copyright 2013 AKFish # +# Copyright 2013 Vincent Jacques # +# Copyright 2014 Vincent Jacques # +# Copyright 2016 Jannis Gebauer # +# Copyright 2016 Peter Buckley # +# Copyright 2018 Wan Liuyang # +# Copyright 2018 sfdye # +# Copyright 2019 Steve Kowalik # +# Copyright 2019 Wan Liuyang # +# Copyright 2020 Steve Kowalik # +# Copyright 2020 Victor Zeng # +# Copyright 2022 Eric Nieuwland # +# Copyright 2023 Enrico Minack # +# Copyright 2023 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2023 Trim21 # +# Copyright 2024 Enrico Minack # +# Copyright 2024 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2025 Enrico Minack # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from __future__ import annotations + +from typing import Any + +from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet + + +class CodeScanRule(NonCompletableGithubObject): + """ + This class represents Alerts from code scanning. + + The reference can be found here + https://docs.github.com/en/rest/reference/code-scanning. + + """ + + def _initAttributes(self) -> None: + self._description: Attribute[str] = NotSet + self._id: Attribute[str] = NotSet + self._name: Attribute[str] = NotSet + self._security_severity_level: Attribute[str] = NotSet + self._severity: Attribute[str] = NotSet + + def __repr__(self) -> str: + return self.get__repr__({"id": self.id, "name": self.name}) + + @property + def description(self) -> str: + return self._description.value + + @property + def id(self) -> str: + return self._id.value + + @property + def name(self) -> str: + return self._name.value + + @property + def security_severity_level(self) -> str: + return self._security_severity_level.value + + @property + def severity(self) -> str: + return self._severity.value + + def _useAttributes(self, attributes: dict[str, Any]) -> None: + if "description" in attributes: # pragma no branch + self._description = self._makeStringAttribute(attributes["description"]) + if "id" in attributes: # pragma no branch + self._id = self._makeStringAttribute(attributes["id"]) + if "name" in attributes: # pragma no branch + self._name = self._makeStringAttribute(attributes["name"]) + if "security_severity_level" in attributes: # pragma no branch + self._security_severity_level = self._makeStringAttribute(attributes["security_severity_level"]) + if "severity" in attributes: # pragma no branch + self._severity = self._makeStringAttribute(attributes["severity"]) diff --git a/venv/lib/python3.10/site-packages/github/CodeScanTool.py b/venv/lib/python3.10/site-packages/github/CodeScanTool.py new file mode 100644 index 0000000000000000000000000000000000000000..40843b616993c96d0f73f6e53e16f23478ffc873 --- /dev/null +++ b/venv/lib/python3.10/site-packages/github/CodeScanTool.py @@ -0,0 +1,87 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2012 Vincent Jacques # +# Copyright 2012 Zearin # +# Copyright 2013 AKFish # +# Copyright 2013 Vincent Jacques # +# Copyright 2014 Vincent Jacques # +# Copyright 2016 Jannis Gebauer # +# Copyright 2016 Peter Buckley # +# Copyright 2018 Wan Liuyang # +# Copyright 2018 sfdye # +# Copyright 2019 Steve Kowalik # +# Copyright 2019 Wan Liuyang # +# Copyright 2020 Steve Kowalik # +# Copyright 2021 Steve Kowalik # +# Copyright 2022 Eric Nieuwland # +# Copyright 2023 Enrico Minack # +# Copyright 2023 Trim21 # +# Copyright 2024 Enrico Minack # +# Copyright 2024 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2025 Enrico Minack # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from typing import Any, Dict + +from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet + + +class CodeScanTool(NonCompletableGithubObject): + """ + This class represents code scanning tools. + + The reference can be found here + https://docs.github.com/en/rest/reference/code-scanning. + + """ + + def _initAttributes(self) -> None: + self._guid: Attribute[str] = NotSet + self._name: Attribute[str] = NotSet + self._version: Attribute[str] = NotSet + + def __repr__(self) -> str: + return self.get__repr__( + { + "guid": self.guid, + "name": self.name, + "version": self.version, + } + ) + + @property + def guid(self) -> str: + return self._guid.value + + @property + def name(self) -> str: + return self._name.value + + @property + def version(self) -> str: + return self._version.value + + def _useAttributes(self, attributes: Dict[str, Any]) -> None: + if "guid" in attributes: # pragma no branch + self._guid = self._makeStringAttribute(attributes["guid"]) + if "name" in attributes: # pragma no branch + self._name = self._makeStringAttribute(attributes["name"]) + if "version" in attributes: # pragma no branch + self._version = self._makeStringAttribute(attributes["version"]) diff --git a/venv/lib/python3.10/site-packages/github/CodeSecurityConfig.py b/venv/lib/python3.10/site-packages/github/CodeSecurityConfig.py new file mode 100644 index 0000000000000000000000000000000000000000..cefca6d2644381af9650e4652a273a41cba7ccd2 --- /dev/null +++ b/venv/lib/python3.10/site-packages/github/CodeSecurityConfig.py @@ -0,0 +1,217 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2025 Bill Napier # +# Copyright 2025 Enrico Minack # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from __future__ import annotations + +from datetime import datetime +from typing import Any + +from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet + + +class CodeSecurityConfig(NonCompletableGithubObject): + """ + This class represents Configurations for Code Security. + + The reference can be found here + https://docs.github.com/en/rest/code-security/configurations. + + """ + + def _initAttributes(self) -> None: + self._id: Attribute[int] = NotSet + self._name: Attribute[str] = NotSet + self._advanced_security: Attribute[str] = NotSet + self._code_scanning_default_setup: Attribute[str] = NotSet + self._created_at: Attribute[datetime] = NotSet + self._dependabot_alerts: Attribute[str] = NotSet + self._dependabot_security_updates: Attribute[str] = NotSet + self._dependency_graph: Attribute[str] = NotSet + self._dependency_graph_autosubmit_action: Attribute[str] = NotSet + self._description: Attribute[str] = NotSet + self._enforcement: Attribute[str] = NotSet + self._html_url: Attribute[str] = NotSet + self._private_vulnerability_reporting: Attribute[str] = NotSet + self._secret_scanning: Attribute[str] = NotSet + self._secret_scanning_delegated_bypass: Attribute[str] = NotSet + self._secret_scanning_non_provider_patterns: Attribute[str] = NotSet + self._secret_scanning_push_protection: Attribute[str] = NotSet + self._secret_scanning_validity_checks: Attribute[str] = NotSet + self._target_type: Attribute[str] = NotSet + self._url: Attribute[str] = NotSet + self._updated_at: Attribute[datetime] = NotSet + + def __repr__(self) -> str: + return self.get__repr__( + { + "id": self.id, + "name": self.name, + "description": self.description, + } + ) + + @property + def advanced_security(self) -> str: + return self._advanced_security.value + + @property + def code_scanning_default_setup(self) -> str: + return self._code_scanning_default_setup.value + + @property + def created_at(self) -> datetime: + return self._created_at.value + + @property + def dependabot_alerts(self) -> str: + return self._dependabot_alerts.value + + @property + def dependabot_security_updates(self) -> str: + return self._dependabot_security_updates.value + + @property + def dependency_graph(self) -> str: + return self._dependency_graph.value + + @property + def dependency_graph_autosubmit_action(self) -> str: + return self._dependency_graph_autosubmit_action.value + + @property + def description(self) -> str: + return self._description.value + + @property + def enforcement(self) -> str: + return self._enforcement.value + + @property + def html_url(self) -> str: + return self._html_url.value + + @property + def id(self) -> int: + return self._id.value + + @property + def name(self) -> str: + return self._name.value + + @property + def private_vulnerability_reporting(self) -> str: + return self._private_vulnerability_reporting.value + + @property + def secret_scanning(self) -> str: + return self._secret_scanning.value + + @property + def secret_scanning_delegated_bypass(self) -> str: + return self._secret_scanning_delegated_bypass.value + + @property + def secret_scanning_non_provider_patterns(self) -> str: + return self._secret_scanning_non_provider_patterns.value + + @property + def secret_scanning_push_protection(self) -> str: + return self._secret_scanning_push_protection.value + + @property + def secret_scanning_validity_checks(self) -> str: + return self._secret_scanning_validity_checks.value + + @property + def target_type(self) -> str: + return self._target_type.value + + @property + def updated_at(self) -> datetime: + return self._updated_at.value + + @property + def url(self) -> str: + return self._url.value + + def _useAttributes(self, attributes: dict[str, Any]) -> None: + if "advanced_security" in attributes: # pragma no branch + self._advanced_security = self._makeStringAttribute(attributes["advanced_security"]) + if "code_scanning_default_setup" in attributes: # pragma no branch + self._code_scanning_default_setup = self._makeStringAttribute(attributes["code_scanning_default_setup"]) + if "created_at" in attributes: # pragma no branch + assert attributes["created_at"] is None or isinstance(attributes["created_at"], str), attributes[ + "created_at" + ] + self._created_at = self._makeDatetimeAttribute(attributes["created_at"]) + if "dependabot_alerts" in attributes: # pragma no branch + self._dependabot_alerts = self._makeStringAttribute(attributes["dependabot_alerts"]) + if "dependabot_security_updates" in attributes: # pragma no branch + self._dependabot_security_updates = self._makeStringAttribute(attributes["dependabot_security_updates"]) + if "dependency_graph" in attributes: # pragma no branch + self._dependency_graph = self._makeStringAttribute(attributes["dependency_graph"]) + if "dependency_graph_autosubmit_action" in attributes: # pragma no branch + self._dependency_graph_autosubmit_action = self._makeStringAttribute( + attributes["dependency_graph_autosubmit_action"] + ) + if "description" in attributes: # pragma no branch + self._description = self._makeStringAttribute(attributes["description"]) + if "enforcement" in attributes: # pragma no branch + self._enforcement = self._makeStringAttribute(attributes["enforcement"]) + if "html_url" in attributes: # pragma no branch + self._html_url = self._makeStringAttribute(attributes["html_url"]) + if "id" in attributes: # pragma no branch + self._id = self._makeIntAttribute(attributes["id"]) + if "name" in attributes: # pragma no branch + self._name = self._makeStringAttribute(attributes["name"]) + if "private_vulnerability_reporting" in attributes: # pragma no branch + self._private_vulnerability_reporting = self._makeStringAttribute( + attributes["private_vulnerability_reporting"] + ) + if "secret_scanning" in attributes: # pragma no branch + self._secret_scanning = self._makeStringAttribute(attributes["secret_scanning"]) + if "secret_scanning_delegated_bypass" in attributes: # pragma no branch + self._secret_scanning_delegated_bypass = self._makeStringAttribute( + attributes["secret_scanning_delegated_bypass"] + ) + if "secret_scanning_non_provider_patterns" in attributes: # pragma no branch + self._secret_scanning_non_provider_patterns = self._makeStringAttribute( + attributes["secret_scanning_non_provider_patterns"] + ) + if "secret_scanning_push_protection" in attributes: # pragma no branch + self._secret_scanning_push_protection = self._makeStringAttribute( + attributes["secret_scanning_push_protection"] + ) + if "secret_scanning_validity_checks" in attributes: # pragma no branch + self._secret_scanning_validity_checks = self._makeStringAttribute( + attributes["secret_scanning_validity_checks"] + ) + if "target_type" in attributes: # pragma no branch + self._target_type = self._makeStringAttribute(attributes["target_type"]) + if "updated_at" in attributes: # pragma no branch + assert attributes["updated_at"] is None or isinstance(attributes["updated_at"], str), attributes[ + "updated_at" + ] + self._updated_at = self._makeDatetimeAttribute(attributes["updated_at"]) + if "url" in attributes: # pragma no branch + self._url = self._makeStringAttribute(attributes["url"]) diff --git a/venv/lib/python3.10/site-packages/github/CodeSecurityConfigRepository.py b/venv/lib/python3.10/site-packages/github/CodeSecurityConfigRepository.py new file mode 100644 index 0000000000000000000000000000000000000000..e799b1d0fbe6d864c77157dc3fbcde24ea866b70 --- /dev/null +++ b/venv/lib/python3.10/site-packages/github/CodeSecurityConfigRepository.py @@ -0,0 +1,68 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2024 Enrico Minack # +# Copyright 2024 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2024 Thomas Cooper # +# Copyright 2025 Enrico Minack # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +import github.Repository +from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet + +if TYPE_CHECKING: + from github.Repository import Repository + + +class CodeSecurityConfigRepository(NonCompletableGithubObject): + """ + This class represents CodeSecurityConfigRepository. + + The reference can be found here + https://docs.github.com/en/rest/code-security/configurations + + The OpenAPI schema can be found at + - /components/schemas/code-security-configuration-repositories + + """ + + def _initAttributes(self) -> None: + self._repository: Attribute[Repository] = NotSet + self._status: Attribute[str] = NotSet + + def __repr__(self) -> str: + return self.repository.__repr__() + + @property + def repository(self) -> Repository: + return self._repository.value + + @property + def status(self) -> str: + return self._status.value + + def _useAttributes(self, attributes: dict[str, Any]) -> None: + if "repository" in attributes: # pragma no branch + self._repository = self._makeClassAttribute(github.Repository.Repository, attributes["repository"]) + if "status" in attributes: # pragma no branch + self._status = self._makeStringAttribute(attributes["status"]) diff --git a/venv/lib/python3.10/site-packages/github/Commit.py b/venv/lib/python3.10/site-packages/github/Commit.py new file mode 100644 index 0000000000000000000000000000000000000000..ee49f51262090e9e9d0eb46e2f76cf615ad16686 --- /dev/null +++ b/venv/lib/python3.10/site-packages/github/Commit.py @@ -0,0 +1,370 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2012 Vincent Jacques # +# Copyright 2012 Zearin # +# Copyright 2013 AKFish # +# Copyright 2013 Vincent Jacques # +# Copyright 2013 martinqt # +# Copyright 2014 Andy Casey # +# Copyright 2014 Vincent Jacques # +# Copyright 2016 Jannis Gebauer # +# Copyright 2016 John Eskew # +# Copyright 2016 Peter Buckley # +# Copyright 2018 sfdye # +# Copyright 2019 Steve Kowalik # +# Copyright 2019 Wan Liuyang # +# Copyright 2020 Danilo Martins # +# Copyright 2020 Dhruv Manilawala # +# Copyright 2020 Steve Kowalik # +# Copyright 2021 Mark Walker # +# Copyright 2021 Steve Kowalik # +# Copyright 2023 Enrico Minack # +# Copyright 2023 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2023 Trim21 # +# Copyright 2024 Enrico Minack # +# Copyright 2024 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2024 iarspider # +# Copyright 2025 Enrico Minack # +# Copyright 2025 xmo-odoo # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +import github.Branch +import github.CheckRun +import github.CheckSuite +import github.CommitCombinedStatus +import github.CommitComment +import github.CommitStats +import github.CommitStatus +import github.File +import github.GitCommit +import github.NamedUser +import github.PaginatedList +import github.Repository +from github.GithubObject import Attribute, CompletableGithubObject, NotSet, Opt, is_optional +from github.PaginatedList import PaginatedList + +if TYPE_CHECKING: + from github.Branch import Branch + from github.CheckRun import CheckRun + from github.CheckSuite import CheckSuite + from github.CommitCombinedStatus import CommitCombinedStatus + from github.CommitComment import CommitComment + from github.CommitStats import CommitStats + from github.CommitStatus import CommitStatus + from github.File import File + from github.GitCommit import GitCommit + from github.NamedUser import NamedUser + from github.PullRequest import PullRequest + from github.Repository import Repository + + +class Commit(CompletableGithubObject): + """ + This class represents Commits. + + The reference can be found here + https://docs.github.com/en/rest/commits/commits#get-a-commit-object + + The OpenAPI schema can be found at + - /components/schemas/branch-short/properties/commit + - /components/schemas/commit + - /components/schemas/commit-search-result-item + - /components/schemas/commit-search-result-item/properties/parents/items + - /components/schemas/commit/properties/parents/items + - /components/schemas/short-branch/properties/commit + - /components/schemas/tag/properties/commit + + """ + + def _initAttributes(self) -> None: + self._author: Attribute[NamedUser] = NotSet + self._comments_url: Attribute[str] = NotSet + self._commit: Attribute[GitCommit] = NotSet + self._committer: Attribute[NamedUser] = NotSet + self._files: Attribute[list[File]] = NotSet + self._html_url: Attribute[str] = NotSet + self._node_id: Attribute[str] = NotSet + self._parents: Attribute[list[Commit]] = NotSet + self._repository: Attribute[Repository] = NotSet + self._score: Attribute[float] = NotSet + self._sha: Attribute[str] = NotSet + self._stats: Attribute[CommitStats] = NotSet + self._text_matches: Attribute[dict[str, Any]] = NotSet + self._url: Attribute[str] = NotSet + + def __repr__(self) -> str: + return self.get__repr__({"sha": self._sha.value}) + + @property + def _identity(self) -> str: + return self.sha + + @property + def author(self) -> NamedUser: + self._completeIfNotSet(self._author) + return self._author.value + + @property + def comments_url(self) -> str: + self._completeIfNotSet(self._comments_url) + return self._comments_url.value + + @property + def commit(self) -> GitCommit: + self._completeIfNotSet(self._commit) + return self._commit.value + + @property + def committer(self) -> NamedUser: + self._completeIfNotSet(self._committer) + return self._committer.value + + # This should be a method, but this used to be a property and cannot be changed without breaking user code + # TODO: remove @property on version 3 + @property + def files(self) -> PaginatedList[File]: + return PaginatedList( + github.File.File, + self._requester, + self.url, + {}, + headers=None, + list_item="files", + total_count_item="total_files", + firstData=self.raw_data, + firstHeaders=self.raw_headers, + ) + + @property + def html_url(self) -> str: + self._completeIfNotSet(self._html_url) + return self._html_url.value + + @property + def node_id(self) -> str: + self._completeIfNotSet(self._node_id) + return self._node_id.value + + @property + def parents(self) -> list[Commit]: + self._completeIfNotSet(self._parents) + return self._parents.value + + @property + def repository(self) -> Repository: + self._completeIfNotSet(self._repository) + return self._repository.value + + @property + def score(self) -> float: + self._completeIfNotSet(self._score) + return self._score.value + + @property + def sha(self) -> str: + self._completeIfNotSet(self._sha) + return self._sha.value + + @property + def stats(self) -> CommitStats: + self._completeIfNotSet(self._stats) + return self._stats.value + + @property + def text_matches(self) -> dict[str, Any]: + self._completeIfNotSet(self._text_matches) + return self._text_matches.value + + @property + def url(self) -> str: + self._completeIfNotSet(self._url) + return self._url.value + + def create_comment( + self, + body: str, + line: Opt[int] = NotSet, + path: Opt[str] = NotSet, + position: Opt[int] = NotSet, + ) -> CommitComment: + """ + :calls: `POST /repos/{owner}/{repo}/commits/{sha}/comments `_ + """ + assert isinstance(body, str), body + assert is_optional(line, int), line + assert is_optional(path, str), path + assert is_optional(position, int), position + post_parameters = NotSet.remove_unset_items({"body": body, "line": line, "path": path, "position": position}) + + headers, data = self._requester.requestJsonAndCheck("POST", f"{self.url}/comments", input=post_parameters) + return github.CommitComment.CommitComment(self._requester, headers, data, completed=True) + + def create_status( + self, + state: str, + target_url: Opt[str] = NotSet, + description: Opt[str] = NotSet, + context: Opt[str] = NotSet, + ) -> CommitStatus: + """ + :calls: `POST /repos/{owner}/{repo}/statuses/{sha} `_ + """ + assert isinstance(state, str), state + assert is_optional(target_url, str), target_url + assert is_optional(description, str), description + assert is_optional(context, str), context + post_parameters = NotSet.remove_unset_items( + { + "state": state, + "target_url": target_url, + "description": description, + "context": context, + } + ) + + headers, data = self._requester.requestJsonAndCheck( + "POST", + f"{self._parentUrl(self._parentUrl(self.url))}/statuses/{self.sha}", + input=post_parameters, + ) + return github.CommitStatus.CommitStatus(self._requester, headers, data) + + def get_branches_where_head(self) -> list[Branch]: + """ + :calls: `GET /repos/{owner}/{repo}/commits/{commit_sha}/branches-where-head `_ + """ + headers, data = self._requester.requestJsonAndCheck("GET", f"{self.url}/branches-where-head") + return [github.Branch.Branch(self._requester, headers, item) for item in data] + + def get_comments(self) -> PaginatedList[CommitComment]: + """ + :calls: `GET /repos/{owner}/{repo}/commits/{sha}/comments `_ + """ + return PaginatedList( + github.CommitComment.CommitComment, + self._requester, + f"{self.url}/comments", + None, + ) + + def get_statuses(self) -> PaginatedList[CommitStatus]: + """ + :calls: `GET /repos/{owner}/{repo}/statuses/{ref} `_ + """ + return PaginatedList( + github.CommitStatus.CommitStatus, + self._requester, + f"{self._parentUrl(self._parentUrl(self.url))}/statuses/{self.sha}", + None, + ) + + def get_combined_status(self) -> CommitCombinedStatus: + """ + :calls: `GET /repos/{owner}/{repo}/commits/{ref}/status/ `_ + """ + headers, data = self._requester.requestJsonAndCheck("GET", f"{self.url}/status") + return github.CommitCombinedStatus.CommitCombinedStatus(self._requester, headers, data) + + def get_pulls(self) -> PaginatedList[PullRequest]: + """ + :calls: `GET /repos/{owner}/{repo}/commits/{sha}/pulls `_ + """ + return PaginatedList( + github.PullRequest.PullRequest, + self._requester, + f"{self.url}/pulls", + None, + headers={"Accept": "application/vnd.github.groot-preview+json"}, + ) + + def get_check_runs( + self, + check_name: Opt[str] = NotSet, + status: Opt[str] = NotSet, + filter: Opt[str] = NotSet, + ) -> PaginatedList[CheckRun]: + """ + :calls: `GET /repos/{owner}/{repo}/commits/{sha}/check-runs `_ + """ + assert is_optional(check_name, str), check_name + assert is_optional(status, str), status + assert is_optional(filter, str), filter + url_parameters = NotSet.remove_unset_items({"check_name": check_name, "status": status, "filter": filter}) + + return PaginatedList( + github.CheckRun.CheckRun, + self._requester, + f"{self.url}/check-runs", + url_parameters, + headers={"Accept": "application/vnd.github.v3+json"}, + list_item="check_runs", + ) + + def get_check_suites(self, app_id: Opt[int] = NotSet, check_name: Opt[str] = NotSet) -> PaginatedList[CheckSuite]: + """ + :class: `GET /repos/{owner}/{repo}/commits/{ref}/check-suites `_ + """ + assert is_optional(app_id, int), app_id + assert is_optional(check_name, str), check_name + parameters = NotSet.remove_unset_items({"app_id": app_id, "check_name": check_name}) + + request_headers = {"Accept": "application/vnd.github.v3+json"} + return PaginatedList( + github.CheckSuite.CheckSuite, + self._requester, + f"{self.url}/check-suites", + parameters, + headers=request_headers, + list_item="check_suites", + ) + + def _useAttributes(self, attributes: dict[str, Any]) -> None: + if "author" in attributes: # pragma no branch + self._author = self._makeClassAttribute(github.NamedUser.NamedUser, attributes["author"]) + if "comments_url" in attributes: # pragma no branch + self._comments_url = self._makeStringAttribute(attributes["comments_url"]) + if "commit" in attributes: # pragma no branch + self._commit = self._makeClassAttribute(github.GitCommit.GitCommit, attributes["commit"]) + if "committer" in attributes: # pragma no branch + self._committer = self._makeClassAttribute(github.NamedUser.NamedUser, attributes["committer"]) + if "files" in attributes: # pragma no branch + self._files = self._makeListOfClassesAttribute(github.File.File, attributes["files"]) + if "html_url" in attributes: # pragma no branch + self._html_url = self._makeStringAttribute(attributes["html_url"]) + if "node_id" in attributes: # pragma no branch + self._node_id = self._makeStringAttribute(attributes["node_id"]) + if "parents" in attributes: # pragma no branch + self._parents = self._makeListOfClassesAttribute(Commit, attributes["parents"]) + if "repository" in attributes: # pragma no branch + self._repository = self._makeClassAttribute(github.Repository.Repository, attributes["repository"]) + if "score" in attributes: # pragma no branch + self._score = self._makeFloatAttribute(attributes["score"]) + if "sha" in attributes: # pragma no branch + self._sha = self._makeStringAttribute(attributes["sha"]) + if "stats" in attributes: # pragma no branch + self._stats = self._makeClassAttribute(github.CommitStats.CommitStats, attributes["stats"]) + if "text_matches" in attributes: # pragma no branch + self._text_matches = self._makeDictAttribute(attributes["text_matches"]) + if "url" in attributes: # pragma no branch + self._url = self._makeStringAttribute(attributes["url"]) diff --git a/venv/lib/python3.10/site-packages/github/CommitCombinedStatus.py b/venv/lib/python3.10/site-packages/github/CommitCombinedStatus.py new file mode 100644 index 0000000000000000000000000000000000000000..6fe62ba54ff4d28ac8ed21fd2876c8182e3c34e6 --- /dev/null +++ b/venv/lib/python3.10/site-packages/github/CommitCombinedStatus.py @@ -0,0 +1,114 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2012 Vincent Jacques # +# Copyright 2012 Zearin # +# Copyright 2013 AKFish # +# Copyright 2013 Vincent Jacques # +# Copyright 2014 Vincent Jacques # +# Copyright 2016 Jannis Gebauer # +# Copyright 2016 John Eskew # +# Copyright 2016 Peter Buckley # +# Copyright 2018 Wan Liuyang # +# Copyright 2018 sfdye # +# Copyright 2019 Steve Kowalik # +# Copyright 2019 Wan Liuyang # +# Copyright 2020 Steve Kowalik # +# Copyright 2021 Steve Kowalik # +# Copyright 2023 Enrico Minack # +# Copyright 2023 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2023 Trim21 # +# Copyright 2024 Enrico Minack # +# Copyright 2024 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2025 Enrico Minack # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from __future__ import annotations + +from typing import Any + +import github.CommitStatus +import github.Repository +from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet + + +class CommitCombinedStatus(NonCompletableGithubObject): + """ + This class represents CommitCombinedStatuses. + + The reference can be found here + https://docs.github.com/en/rest/reference/repos#statuses + + """ + + def _initAttributes(self) -> None: + self._commit_url: Attribute[str] = NotSet + self._repository: Attribute[github.Repository.Repository] = NotSet + self._sha: Attribute[str] = NotSet + self._state: Attribute[str] = NotSet + self._statuses: Attribute[list[github.CommitStatus.CommitStatus]] = NotSet + self._total_count: Attribute[int] = NotSet + self._url: Attribute[str] = NotSet + + def __repr__(self) -> str: + return self.get__repr__({"sha": self._sha.value, "state": self._state.value}) + + @property + def commit_url(self) -> str: + return self._commit_url.value + + @property + def repository(self) -> github.Repository.Repository: + return self._repository.value + + @property + def sha(self) -> str: + return self._sha.value + + @property + def state(self) -> str: + return self._state.value + + @property + def statuses(self) -> list[github.CommitStatus.CommitStatus]: + return self._statuses.value + + @property + def total_count(self) -> int: + return self._total_count.value + + @property + def url(self) -> str: + return self._url.value + + def _useAttributes(self, attributes: dict[str, Any]) -> None: + if "commit_url" in attributes: # pragma no branch + self._commit_url = self._makeStringAttribute(attributes["commit_url"]) + if "repository" in attributes: # pragma no branch + self._repository = self._makeClassAttribute(github.Repository.Repository, attributes["repository"]) + if "sha" in attributes: # pragma no branch + self._sha = self._makeStringAttribute(attributes["sha"]) + if "state" in attributes: # pragma no branch + self._state = self._makeStringAttribute(attributes["state"]) + if "statuses" in attributes: # pragma no branch + self._statuses = self._makeListOfClassesAttribute(github.CommitStatus.CommitStatus, attributes["statuses"]) + if "total_count" in attributes: # pragma no branch + self._total_count = self._makeIntAttribute(attributes["total_count"]) + if "url" in attributes: # pragma no branch + self._url = self._makeStringAttribute(attributes["url"]) diff --git a/venv/lib/python3.10/site-packages/github/CommitComment.py b/venv/lib/python3.10/site-packages/github/CommitComment.py new file mode 100644 index 0000000000000000000000000000000000000000..f1fcb4837664f2d56fcbf3232203ce3284882c40 --- /dev/null +++ b/venv/lib/python3.10/site-packages/github/CommitComment.py @@ -0,0 +1,253 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2012 Vincent Jacques # +# Copyright 2012 Zearin # +# Copyright 2013 AKFish # +# Copyright 2013 Vincent Jacques # +# Copyright 2014 Vincent Jacques # +# Copyright 2016 Jannis Gebauer # +# Copyright 2016 Peter Buckley # +# Copyright 2017 Nicolas Agustín Torres # +# Copyright 2018 Wan Liuyang # +# Copyright 2018 per1234 # +# Copyright 2018 sfdye # +# Copyright 2019 Steve Kowalik # +# Copyright 2019 Wan Liuyang # +# Copyright 2020 Huan-Cheng Chang # +# Copyright 2020 Steve Kowalik # +# Copyright 2021 Mark Walker # +# Copyright 2021 Steve Kowalik # +# Copyright 2023 Enrico Minack # +# Copyright 2023 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2023 Trim21 # +# Copyright 2024 Enrico Minack # +# Copyright 2024 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2025 Enrico Minack # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from __future__ import annotations + +from datetime import datetime +from typing import TYPE_CHECKING, Any + +import github.GithubObject +import github.NamedUser +from github import Consts +from github.GithubObject import Attribute, CompletableGithubObject, NotSet +from github.PaginatedList import PaginatedList + +if TYPE_CHECKING: + from github.Reaction import Reaction + + +class CommitComment(CompletableGithubObject): + """ + This class represents CommitComments. + + The reference can be found here + https://docs.github.com/en/rest/reference/repos#comments + + The OpenAPI schema can be found at + - /components/schemas/commit-comment + + """ + + def _initAttributes(self) -> None: + self._author_association: Attribute[str] = NotSet + self._body: Attribute[str] = NotSet + self._commit_id: Attribute[str] = NotSet + self._created_at: Attribute[datetime] = NotSet + self._html_url: Attribute[str] = NotSet + self._id: Attribute[int] = NotSet + self._line: Attribute[int] = NotSet + self._node_id: Attribute[str] = NotSet + self._path: Attribute[str] = NotSet + self._position: Attribute[int] = NotSet + self._reactions: Attribute[dict[str, Any]] = NotSet + self._updated_at: Attribute[datetime] = NotSet + self._url: Attribute[str] = NotSet + self._user: Attribute[github.NamedUser.NamedUser] = NotSet + + def __repr__(self) -> str: + return self.get__repr__({"id": self._id.value, "user": self.user}) + + @property + def author_association(self) -> str: + self._completeIfNotSet(self._author_association) + return self._author_association.value + + @property + def body(self) -> str: + self._completeIfNotSet(self._body) + return self._body.value + + @property + def commit_id(self) -> str: + self._completeIfNotSet(self._commit_id) + return self._commit_id.value + + @property + def created_at(self) -> datetime: + self._completeIfNotSet(self._created_at) + return self._created_at.value + + @property + def html_url(self) -> str: + self._completeIfNotSet(self._html_url) + return self._html_url.value + + @property + def id(self) -> int: + self._completeIfNotSet(self._id) + return self._id.value + + @property + def line(self) -> int: + self._completeIfNotSet(self._line) + return self._line.value + + @property + def node_id(self) -> str: + self._completeIfNotSet(self._node_id) + return self._node_id.value + + @property + def path(self) -> str: + self._completeIfNotSet(self._path) + return self._path.value + + @property + def position(self) -> int: + self._completeIfNotSet(self._position) + return self._position.value + + @property + def reactions(self) -> dict[str, Any]: + self._completeIfNotSet(self._reactions) + return self._reactions.value + + @property + def updated_at(self) -> datetime: + self._completeIfNotSet(self._updated_at) + return self._updated_at.value + + @property + def url(self) -> str: + self._completeIfNotSet(self._url) + return self._url.value + + @property + def user(self) -> github.NamedUser.NamedUser: + self._completeIfNotSet(self._user) + return self._user.value + + def delete(self) -> None: + """ + :calls: `DELETE /repos/{owner}/{repo}/comments/{id} `_ + :rtype: None + """ + headers, data = self._requester.requestJsonAndCheck("DELETE", self.url) + + def edit(self, body: str) -> None: + """ + :calls: `PATCH /repos/{owner}/{repo}/comments/{id} `_ + """ + assert isinstance(body, str), body + post_parameters = { + "body": body, + } + headers, data = self._requester.requestJsonAndCheck("PATCH", self.url, input=post_parameters) + self._useAttributes(data) + + def get_reactions(self) -> PaginatedList[Reaction]: + """ + :calls: `GET /repos/{owner}/{repo}/comments/{id}/reactions + `_ + :return: :class: :class:`github.PaginatedList.PaginatedList` of :class:`github.Reaction.Reaction` + """ + return PaginatedList( + github.Reaction.Reaction, + self._requester, + f"{self.url}/reactions", + None, + headers={"Accept": Consts.mediaTypeReactionsPreview}, + ) + + def create_reaction(self, reaction_type: str) -> Reaction: + """ + :calls: `POST /repos/{owner}/{repo}/comments/{id}/reactions + `_ + """ + assert isinstance(reaction_type, str), reaction_type + post_parameters = { + "content": reaction_type, + } + headers, data = self._requester.requestJsonAndCheck( + "POST", + f"{self.url}/reactions", + input=post_parameters, + headers={"Accept": Consts.mediaTypeReactionsPreview}, + ) + return github.Reaction.Reaction(self._requester, headers, data, completed=True) + + def delete_reaction(self, reaction_id: int) -> bool: + """ + :calls: `DELETE /repos/{owner}/{repo}/comments/{comment_id}/reactions/{reaction_id} + `_ + :param reaction_id: integer + :rtype: bool + """ + assert isinstance(reaction_id, int), reaction_id + status, _, _ = self._requester.requestJson( + "DELETE", + f"{self.url}/reactions/{reaction_id}", + headers={"Accept": Consts.mediaTypeReactionsPreview}, + ) + return status == 204 + + def _useAttributes(self, attributes: dict[str, Any]) -> None: + if "author_association" in attributes: # pragma no branch + self._author_association = self._makeStringAttribute(attributes["author_association"]) + if "body" in attributes: # pragma no branch + self._body = self._makeStringAttribute(attributes["body"]) + if "commit_id" in attributes: # pragma no branch + self._commit_id = self._makeStringAttribute(attributes["commit_id"]) + if "created_at" in attributes: # pragma no branch + self._created_at = self._makeDatetimeAttribute(attributes["created_at"]) + if "html_url" in attributes: # pragma no branch + self._html_url = self._makeStringAttribute(attributes["html_url"]) + if "id" in attributes: # pragma no branch + self._id = self._makeIntAttribute(attributes["id"]) + if "line" in attributes: # pragma no branch + self._line = self._makeIntAttribute(attributes["line"]) + if "node_id" in attributes: # pragma no branch + self._node_id = self._makeStringAttribute(attributes["node_id"]) + if "path" in attributes: # pragma no branch + self._path = self._makeStringAttribute(attributes["path"]) + if "position" in attributes: # pragma no branch + self._position = self._makeIntAttribute(attributes["position"]) + if "reactions" in attributes: # pragma no branch + self._reactions = self._makeDictAttribute(attributes["reactions"]) + if "updated_at" in attributes: # pragma no branch + self._updated_at = self._makeDatetimeAttribute(attributes["updated_at"]) + if "url" in attributes: # pragma no branch + self._url = self._makeStringAttribute(attributes["url"]) + if "user" in attributes: # pragma no branch + self._user = self._makeClassAttribute(github.NamedUser.NamedUser, attributes["user"]) diff --git a/venv/lib/python3.10/site-packages/github/CommitStats.py b/venv/lib/python3.10/site-packages/github/CommitStats.py new file mode 100644 index 0000000000000000000000000000000000000000..1b5a61ce65e84f4e57626d1648c0d710d6def87d --- /dev/null +++ b/venv/lib/python3.10/site-packages/github/CommitStats.py @@ -0,0 +1,74 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2012 Vincent Jacques # +# Copyright 2012 Zearin # +# Copyright 2013 AKFish # +# Copyright 2013 Vincent Jacques # +# Copyright 2014 Vincent Jacques # +# Copyright 2016 Peter Buckley # +# Copyright 2018 Wan Liuyang # +# Copyright 2018 sfdye # +# Copyright 2019 Steve Kowalik # +# Copyright 2019 Wan Liuyang # +# Copyright 2020 Steve Kowalik # +# Copyright 2023 Enrico Minack # +# Copyright 2023 Trim21 # +# Copyright 2025 Enrico Minack # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from typing import Any, Dict + +from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet + + +class CommitStats(NonCompletableGithubObject): + """ + This class represents CommitStats. + + The OpenAPI schema can be found at + - /components/schemas/commit/properties/stats + - /components/schemas/gist-history/properties/change_status + + """ + + def _initAttributes(self) -> None: + self._additions: Attribute[int] = NotSet + self._deletions: Attribute[int] = NotSet + self._total: Attribute[int] = NotSet + + @property + def additions(self) -> int: + return self._additions.value + + @property + def deletions(self) -> int: + return self._deletions.value + + @property + def total(self) -> int: + return self._total.value + + def _useAttributes(self, attributes: Dict[str, Any]) -> None: + if "additions" in attributes: # pragma no branch + self._additions = self._makeIntAttribute(attributes["additions"]) + if "deletions" in attributes: # pragma no branch + self._deletions = self._makeIntAttribute(attributes["deletions"]) + if "total" in attributes: # pragma no branch + self._total = self._makeIntAttribute(attributes["total"]) diff --git a/venv/lib/python3.10/site-packages/github/CommitStatus.py b/venv/lib/python3.10/site-packages/github/CommitStatus.py new file mode 100644 index 0000000000000000000000000000000000000000..df16e49716e08dc2552d179a8238398f610e66ac --- /dev/null +++ b/venv/lib/python3.10/site-packages/github/CommitStatus.py @@ -0,0 +1,148 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2012 Vincent Jacques # +# Copyright 2012 Zearin # +# Copyright 2013 AKFish # +# Copyright 2013 Vincent Jacques # +# Copyright 2014 Vincent Jacques # +# Copyright 2015 Matt Babineau # +# Copyright 2016 Jannis Gebauer # +# Copyright 2016 Martijn Koster # +# Copyright 2016 Peter Buckley # +# Copyright 2018 Wan Liuyang # +# Copyright 2018 sfdye # +# Copyright 2019 Steve Kowalik # +# Copyright 2019 Wan Liuyang # +# Copyright 2020 Steve Kowalik # +# Copyright 2021 Steve Kowalik # +# Copyright 2023 Enrico Minack # +# Copyright 2023 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2023 Trim21 # +# Copyright 2025 Enrico Minack # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from __future__ import annotations + +from datetime import datetime +from typing import Any + +import github.GithubObject +import github.NamedUser +from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet + + +class CommitStatus(NonCompletableGithubObject): + """ + This class represents CommitStatuses.The reference can be found here https://docs.github.com/en/rest/reference/repos#statuses + + The OpenAPI schema can be found at + - /components/schemas/status + + """ + + def _initAttributes(self) -> None: + self._avatar_url: Attribute[str] = NotSet + self._context: Attribute[str] = NotSet + self._created_at: Attribute[datetime] = NotSet + self._creator: Attribute[github.NamedUser.NamedUser] = NotSet + self._description: Attribute[str] = NotSet + self._id: Attribute[int] = NotSet + self._node_id: Attribute[str] = NotSet + self._state: Attribute[str] = NotSet + self._target_url: Attribute[str] = NotSet + self._updated_at: Attribute[datetime] = NotSet + self._url: Attribute[str] = NotSet + + def __repr__(self) -> str: + return self.get__repr__( + { + "id": self._id.value, + "state": self._state.value, + "context": self._context.value, + } + ) + + @property + def avatar_url(self) -> str: + return self._avatar_url.value + + @property + def context(self) -> str: + return self._context.value + + @property + def created_at(self) -> datetime: + return self._created_at.value + + @property + def creator(self) -> github.NamedUser.NamedUser: + return self._creator.value + + @property + def description(self) -> str: + return self._description.value + + @property + def id(self) -> int: + return self._id.value + + @property + def node_id(self) -> str: + return self._node_id.value + + @property + def state(self) -> str: + return self._state.value + + @property + def target_url(self) -> str: + return self._target_url.value + + @property + def updated_at(self) -> datetime: + return self._updated_at.value + + @property + def url(self) -> str: + return self._url.value + + def _useAttributes(self, attributes: dict[str, Any]) -> None: + if "avatar_url" in attributes: # pragma no branch + self._avatar_url = self._makeStringAttribute(attributes["avatar_url"]) + if "context" in attributes: # pragma no branch + self._context = self._makeStringAttribute(attributes["context"]) + if "created_at" in attributes: # pragma no branch + self._created_at = self._makeDatetimeAttribute(attributes["created_at"]) + if "creator" in attributes: # pragma no branch + self._creator = self._makeClassAttribute(github.NamedUser.NamedUser, attributes["creator"]) + if "description" in attributes: # pragma no branch + self._description = self._makeStringAttribute(attributes["description"]) + if "id" in attributes: # pragma no branch + self._id = self._makeIntAttribute(attributes["id"]) + if "node_id" in attributes: # pragma no branch + self._node_id = self._makeStringAttribute(attributes["node_id"]) + if "state" in attributes: # pragma no branch + self._state = self._makeStringAttribute(attributes["state"]) + if "target_url" in attributes: # pragma no branch + self._target_url = self._makeStringAttribute(attributes["target_url"]) + if "updated_at" in attributes: # pragma no branch + self._updated_at = self._makeDatetimeAttribute(attributes["updated_at"]) + if "url" in attributes: # pragma no branch + self._url = self._makeStringAttribute(attributes["url"]) diff --git a/venv/lib/python3.10/site-packages/github/Comparison.py b/venv/lib/python3.10/site-packages/github/Comparison.py new file mode 100644 index 0000000000000000000000000000000000000000..af76539bad0cae374cf0a9c4e4c5c2616b310fb8 --- /dev/null +++ b/venv/lib/python3.10/site-packages/github/Comparison.py @@ -0,0 +1,170 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2012 Vincent Jacques # +# Copyright 2012 Zearin # +# Copyright 2013 AKFish # +# Copyright 2013 Vincent Jacques # +# Copyright 2014 Vincent Jacques # +# Copyright 2016 Peter Buckley # +# Copyright 2018 Wan Liuyang # +# Copyright 2018 sfdye # +# Copyright 2019 Steve Kowalik # +# Copyright 2019 Wan Liuyang # +# Copyright 2020 Steve Kowalik # +# Copyright 2023 Enrico Minack # +# Copyright 2023 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2023 Trim21 # +# Copyright 2024 Enrico Minack # +# Copyright 2024 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from __future__ import annotations + +from typing import Any + +import github.Commit +import github.File +from github.GithubObject import Attribute, CompletableGithubObject, NotSet +from github.PaginatedList import PaginatedList + + +class Comparison(CompletableGithubObject): + """ + This class represents Comparisons. + """ + + def _initAttributes(self) -> None: + self._ahead_by: Attribute[int] = NotSet + self._base_commit: Attribute[github.Commit.Commit] = NotSet + self._behind_by: Attribute[int] = NotSet + self._diff_url: Attribute[str] = NotSet + self._files: Attribute[list[github.File.File]] = NotSet + self._html_url: Attribute[str] = NotSet + self._merge_base_commit: Attribute[github.Commit.Commit] = NotSet + self._patch_url: Attribute[str] = NotSet + self._permalink_url: Attribute[str] = NotSet + self._status: Attribute[str] = NotSet + self._total_commits: Attribute[int] = NotSet + self._url: Attribute[str] = NotSet + + def __repr__(self) -> str: + return self.get__repr__({"url": self._url.value}) + + @property + def ahead_by(self) -> int: + self._completeIfNotSet(self._ahead_by) + return self._ahead_by.value + + @property + def base_commit(self) -> github.Commit.Commit: + self._completeIfNotSet(self._base_commit) + return self._base_commit.value + + @property + def behind_by(self) -> int: + self._completeIfNotSet(self._behind_by) + return self._behind_by.value + + # This should be a method, but this used to be a property and cannot be changed without breaking user code + # TODO: remove @property on version 3 + @property + def commits(self) -> PaginatedList[github.Commit.Commit]: + return PaginatedList( + github.Commit.Commit, + self._requester, + self.url, + {}, + headers=None, + list_item="commits", + total_count_item="total_commits", + firstData=self.raw_data, + firstHeaders=self.raw_headers, + ) + + @property + def diff_url(self) -> str: + self._completeIfNotSet(self._diff_url) + return self._diff_url.value + + @property + def files(self) -> list[github.File.File]: + self._completeIfNotSet(self._files) + return self._files.value + + @property + def html_url(self) -> str: + self._completeIfNotSet(self._html_url) + return self._html_url.value + + @property + def merge_base_commit(self) -> github.Commit.Commit: + self._completeIfNotSet(self._merge_base_commit) + return self._merge_base_commit.value + + @property + def patch_url(self) -> str: + self._completeIfNotSet(self._patch_url) + return self._patch_url.value + + @property + def permalink_url(self) -> str: + self._completeIfNotSet(self._permalink_url) + return self._permalink_url.value + + @property + def status(self) -> str: + self._completeIfNotSet(self._status) + return self._status.value + + @property + def total_commits(self) -> int: + self._completeIfNotSet(self._total_commits) + return self._total_commits.value + + @property + def url(self) -> str: + self._completeIfNotSet(self._url) + return self._url.value + + def _useAttributes(self, attributes: dict[str, Any]) -> None: + if "ahead_by" in attributes: # pragma no branch + self._ahead_by = self._makeIntAttribute(attributes["ahead_by"]) + if "base_commit" in attributes: # pragma no branch + self._base_commit = self._makeClassAttribute(github.Commit.Commit, attributes["base_commit"]) + if "behind_by" in attributes: # pragma no branch + self._behind_by = self._makeIntAttribute(attributes["behind_by"]) + if "diff_url" in attributes: # pragma no branch + self._diff_url = self._makeStringAttribute(attributes["diff_url"]) + if "files" in attributes: # pragma no branch + self._files = self._makeListOfClassesAttribute(github.File.File, attributes["files"]) + if "html_url" in attributes: # pragma no branch + self._html_url = self._makeStringAttribute(attributes["html_url"]) + if "merge_base_commit" in attributes: # pragma no branch + self._merge_base_commit = self._makeClassAttribute(github.Commit.Commit, attributes["merge_base_commit"]) + if "patch_url" in attributes: # pragma no branch + self._patch_url = self._makeStringAttribute(attributes["patch_url"]) + if "permalink_url" in attributes: # pragma no branch + self._permalink_url = self._makeStringAttribute(attributes["permalink_url"]) + if "status" in attributes: # pragma no branch + self._status = self._makeStringAttribute(attributes["status"]) + if "total_commits" in attributes: # pragma no branch + self._total_commits = self._makeIntAttribute(attributes["total_commits"]) + if "url" in attributes: # pragma no branch + self._url = self._makeStringAttribute(attributes["url"]) diff --git a/venv/lib/python3.10/site-packages/github/Consts.py b/venv/lib/python3.10/site-packages/github/Consts.py new file mode 100644 index 0000000000000000000000000000000000000000..da9f365ec4a970a368a8b29aa266b714230a3fe0 --- /dev/null +++ b/venv/lib/python3.10/site-packages/github/Consts.py @@ -0,0 +1,181 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2012 Vincent Jacques # +# Copyright 2012 Zearin # +# Copyright 2013 AKFish # +# Copyright 2013 Vincent Jacques # +# Copyright 2014 Vincent Jacques # +# Copyright 2016 Jakub Wilk # +# Copyright 2016 Peter Buckley # +# Copyright 2018 Aaron L. Levine # +# Copyright 2018 Alice GIRARD # +# Copyright 2018 Maarten Fonville # +# Copyright 2018 Shubham Singh <41840111+singh811@users.noreply.github.com> # +# Copyright 2018 Steve Kowalik # +# Copyright 2018 Wan Liuyang # +# Copyright 2018 Yossarian King # +# Copyright 2018 h.shi <10385628+AnYeMoWang@users.noreply.github.com> # +# Copyright 2018 sfdye # +# Copyright 2019 Adam Baratz # +# Copyright 2019 Nick Campbell # +# Copyright 2019 Steve Kowalik # +# Copyright 2019 Tim Gates # +# Copyright 2019 Wan Liuyang # +# Copyright 2019 Will Li # +# Copyright 2020 Adrian Bridgett <58699309+tl-adrian-bridgett@users.noreply.github.com># +# Copyright 2020 Anuj Bansal # +# Copyright 2020 Colby Gallup # +# Copyright 2020 Pascal Hofmann # +# Copyright 2020 Steve Kowalik # +# Copyright 2021 Steve Kowalik # +# Copyright 2021 Tanner <51724788+lightningboltemoji@users.noreply.github.com> # +# Copyright 2022 KimSia Sim <245021+simkimsia@users.noreply.github.com> # +# Copyright 2023 Denis Blanchette # +# Copyright 2023 Enrico Minack # +# Copyright 2023 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2024 Enrico Minack # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + + +REQ_IF_NONE_MATCH = "If-None-Match" +REQ_IF_MODIFIED_SINCE = "If-Modified-Since" +PROCESSING_202_WAIT_TIME = 2 + +# ############################################################################## +# Response Header # +# (Lower Case) # +# ############################################################################## +RES_ETAG = "etag" +RES_LAST_MODIFIED = "last-modified" + +# Inspired by https://github.com/google/go-github + +# Headers + +headerRateLimit = "x-ratelimit-limit" +headerRateRemaining = "x-ratelimit-remaining" +headerRateReset = "x-ratelimit-reset" +headerOAuthScopes = "x-oauth-scopes" +headerOTP = "x-github-otp" + +defaultMediaType = "application/octet-stream" + +# Custom media type for preview API + +# https://developer.github.com/changes/2014-12-09-new-attributes-for-stars-api/ +mediaTypeStarringPreview = "application/vnd.github.v3.star+json" + +# https://developer.github.com/changes/2016-02-19-source-import-preview-api/ +mediaTypeImportPreview = "application/vnd.github.barred-rock-preview" + +# https://developer.github.com/changes/2016-05-12-reactions-api-preview/ +mediaTypeReactionsPreview = "application/vnd.github.squirrel-girl-preview" + +# https://developer.github.com/changes/2016-09-14-Integrations-Early-Access/ +mediaTypeIntegrationPreview = "application/vnd.github.machine-man-preview+json" + +# https://developer.github.com/changes/2016-09-14-projects-api/ +mediaTypeProjectsPreview = "application/vnd.github.inertia-preview+json" + +# https://developer.github.com/changes/2017-01-05-commit-search-api/ +mediaTypeCommitSearchPreview = "application/vnd.github.cloak-preview" + +# https://developer.github.com/changes/2017-02-28-user-blocking-apis-and-webhook/ +mediaTypeBlockUsersPreview = "application/vnd.github.giant-sentry-fist-preview+json" + +# https://developer.github.com/changes/2017-07-17-update-topics-on-repositories/ +mediaTypeTopicsPreview = "application/vnd.github.mercy-preview+json" + +# https://developer.github.com/changes/2018-02-22-label-description-search-preview/ +mediaTypeLabelDescriptionSearchPreview = "application/vnd.github.symmetra-preview+json" + +# https://developer.github.com/changes/2018-01-10-lock-reason-api-preview/ +mediaTypeLockReasonPreview = "application/vnd.github.sailor-v-preview+json" + +# https://developer.github.com/changes/2018-01-25-organization-invitation-api-preview/ +mediaTypeOrganizationInvitationPreview = "application/vnd.github.dazzler-preview+json" + +# https://developer.github.com/changes/2018-02-07-team-discussions-api +mediaTypeTeamDiscussionsPreview = "application/vnd.github.echo-preview+json" + +# https://developer.github.com/changes/2018-03-16-protected-branches-required-approving-reviews/ +mediaTypeRequireMultipleApprovingReviews = "application/vnd.github.luke-cage-preview+json" + +# https://developer.github.com/changes/2018-05-24-user-migration-api/ +mediaTypeMigrationPreview = "application/vnd.github.wyandotte-preview+json" + +# https://developer.github.com/changes/2019-07-16-repository-templates-api/ +mediaTypeTemplatesPreview = "application/vnd.github.baptiste-preview+json" + +# https://docs.github.com/en/rest/reference/search#highlighting-code-search-results-1 +highLightSearchPreview = "application/vnd.github.v3.text-match+json" + +# https://developer.github.com/changes/2018-02-22-protected-branches-required-signatures/ +signaturesProtectedBranchesPreview = "application/vnd.github.zzzax-preview+json" + +# https://developer.github.com/changes/2019-04-24-vulnerability-alerts/ +vulnerabilityAlertsPreview = "application/vnd.github.dorian-preview+json" + +# https://developer.github.com/changes/2019-06-04-automated-security-fixes/ +automatedSecurityFixes = "application/vnd.github.london-preview+json" + +# https://developer.github.com/changes/2019-05-29-update-branch-api/ +updateBranchPreview = "application/vnd.github.lydian-preview+json" + +# https://developer.github.com/changes/2016-05-23-timeline-preview-api/ +issueTimelineEventsPreview = "application/vnd.github.mockingbird-preview" + +# https://docs.github.com/en/rest/reference/teams#check-if-a-team-manages-a-repository +teamRepositoryPermissions = "application/vnd.github.v3.repository+json" + +# https://developer.github.com/changes/2016-04-06-deployment-and-deployment-status-enhancements/ +deploymentEnhancementsPreview = "application/vnd.github.ant-man-preview+json" + +# https://developer.github.com/changes/2018-10-16-deployments-environments-states-and-auto-inactive-updates/ +deploymentStatusEnhancementsPreview = "application/vnd.github.flash-preview+json" + +# https://developer.github.com/changes/2019-12-03-internal-visibility-changes/ +repoVisibilityPreview = "application/vnd.github.nebula-preview+json" + +DEFAULT_BASE_URL = "https://api.github.com" +DEFAULT_OAUTH_URL = "https://github.com/login/oauth" +DEFAULT_STATUS_URL = "https://status.github.com" +DEFAULT_USER_AGENT = "PyGithub/Python" +# As of 2018-05-17, Github imposes a 10s limit for completion of API requests. +# Thus, the timeout should be slightly > 10s to account for network/front-end +# latency. +DEFAULT_TIMEOUT = 15 +DEFAULT_PER_PAGE = 30 + +# JWT expiry in seconds. Could be set for max 600 seconds (10 minutes). +# https://docs.github.com/en/developers/apps/building-github-apps/authenticating-with-github-apps#authenticating-as-a-github-app +DEFAULT_JWT_EXPIRY = 300 +MIN_JWT_EXPIRY = 15 +MAX_JWT_EXPIRY = 600 +# https://docs.github.com/en/developers/apps/building-github-apps/authenticating-with-github-apps#generating-a-json-web-token-jwt +# "The time the JWT was created. To protect against clock drift, we recommend you set this 60 seconds in the past." +DEFAULT_JWT_ISSUED_AT = -60 +# https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-json-web-token-jwt-for-a-github-app +# "Your JWT must be signed using the RS256 algorithm" +DEFAULT_JWT_ALGORITHM = "RS256" + +# https://docs.github.com/en/rest/guides/best-practices-for-integrators?apiVersion=2022-11-28#dealing-with-secondary-rate-limits +DEFAULT_SECONDS_BETWEEN_REQUESTS = 0.25 +DEFAULT_SECONDS_BETWEEN_WRITES = 1.0 diff --git a/venv/lib/python3.10/site-packages/github/ContentFile.py b/venv/lib/python3.10/site-packages/github/ContentFile.py new file mode 100644 index 0000000000000000000000000000000000000000..d3fd49c72269d3f4b3936c0325d57eade49db4dc --- /dev/null +++ b/venv/lib/python3.10/site-packages/github/ContentFile.py @@ -0,0 +1,280 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2012 Vincent Jacques # +# Copyright 2012 Zearin # +# Copyright 2013 AKFish # +# Copyright 2013 Vincent Jacques # +# Copyright 2014 Thialfihar # +# Copyright 2014 Vincent Jacques # +# Copyright 2016 Jannis Gebauer # +# Copyright 2016 Peter Buckley # +# Copyright 2018 Wan Liuyang # +# Copyright 2018 h.shi <10385628+AnYeMoWang@users.noreply.github.com> # +# Copyright 2018 sfdye # +# Copyright 2019 Adam Baratz # +# Copyright 2019 Steve Kowalik # +# Copyright 2019 Wan Liuyang # +# Copyright 2020 Alice GIRARD # +# Copyright 2020 Steve Kowalik # +# Copyright 2021 Steve Kowalik # +# Copyright 2023 Enrico Minack # +# Copyright 2023 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2023 Trim21 # +# Copyright 2024 Enrico Minack # +# Copyright 2024 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2025 Enrico Minack # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from __future__ import annotations + +import base64 +from datetime import datetime +from typing import TYPE_CHECKING, Any + +import github.GitCommit +import github.GithubObject +import github.License +import github.Repository +from github.GithubObject import Attribute, CompletableGithubObject, NotSet, _ValuedAttribute + +if TYPE_CHECKING: + from github.GitCommit import GitCommit + from github.License import License + from github.Repository import Repository + + +class ContentFile(CompletableGithubObject): + """ + This class represents ContentFiles. + + The reference can be found here + https://docs.github.com/en/rest/reference/repos#contents + + The OpenAPI schema can be found at + - /components/schemas/code-search-result-item + - /components/schemas/content-directory + - /components/schemas/content-file + - /components/schemas/content-submodule + - /components/schemas/content-symlink + - /components/schemas/file-commit + - /components/schemas/license-content + + """ + + def _initAttributes(self) -> None: + self.__links: Attribute[dict[str, Any]] = NotSet + self._commit: Attribute[GitCommit] = NotSet + self._content: Attribute[str] = NotSet + self._download_url: Attribute[str] = NotSet + self._encoding: Attribute[str] = NotSet + self._file_size: Attribute[int] = NotSet + self._git_url: Attribute[str] = NotSet + self._html_url: Attribute[str] = NotSet + self._language: Attribute[str] = NotSet + self._last_modified_at: Attribute[datetime] = NotSet + self._license: Attribute[License] = NotSet + self._line_numbers: Attribute[list[str]] = NotSet + self._name: Attribute[str] = NotSet + self._path: Attribute[str] = NotSet + self._repository: Attribute[Repository] = NotSet + self._score: Attribute[float] = NotSet + self._sha: Attribute[str] = NotSet + self._size: Attribute[int] = NotSet + self._submodule_git_url: Attribute[str] = NotSet + self._target: Attribute[str] = NotSet + self._text_matches: Attribute[str] = NotSet + self._type: Attribute[str] = NotSet + self._url: Attribute[str] = NotSet + + def __repr__(self) -> str: + return self.get__repr__({"path": self._path.value}) + + @property + def _links(self) -> dict[str, Any]: + self._completeIfNotSet(self.__links) + return self.__links.value + + @property + def commit(self) -> GitCommit: + self._completeIfNotSet(self._commit) + return self._commit.value + + @property + def content(self) -> str: + self._completeIfNotSet(self._content) + return self._content.value + + @property + def decoded_content(self) -> bytes: + assert self.encoding == "base64", f"unsupported encoding: {self.encoding}" + return base64.b64decode(bytearray(self.content, "utf-8")) + + @property + def download_url(self) -> str: + self._completeIfNotSet(self._download_url) + return self._download_url.value + + @property + def encoding(self) -> str: + self._completeIfNotSet(self._encoding) + return self._encoding.value + + @property + def file_size(self) -> int: + self._completeIfNotSet(self._file_size) + return self._file_size.value + + @property + def git_url(self) -> str: + self._completeIfNotSet(self._git_url) + return self._git_url.value + + @property + def html_url(self) -> str: + self._completeIfNotSet(self._html_url) + return self._html_url.value + + @property + def language(self) -> str: + self._completeIfNotSet(self._language) + return self._language.value + + @property + def last_modified_at(self) -> datetime: + self._completeIfNotSet(self._last_modified_at) + return self._last_modified_at.value + + @property + def license(self) -> License: + self._completeIfNotSet(self._license) + return self._license.value + + @property + def line_numbers(self) -> list[str]: + self._completeIfNotSet(self._line_numbers) + return self._line_numbers.value + + @property + def name(self) -> str: + self._completeIfNotSet(self._name) + return self._name.value + + @property + def path(self) -> str: + self._completeIfNotSet(self._path) + return self._path.value + + @property + def repository(self) -> Repository: + if self._repository is NotSet: + # The repository was not set automatically, so it must be looked up by url. + repo_url = "/".join(self.url.split("/")[:6]) # pragma no cover (Should be covered) + self._repository = _ValuedAttribute( + github.Repository.Repository(self._requester, self._headers, {"url": repo_url}, completed=False) + ) # pragma no cover (Should be covered) + return self._repository.value + + @property + def score(self) -> float: + self._completeIfNotSet(self._score) + return self._score.value + + @property + def sha(self) -> str: + self._completeIfNotSet(self._sha) + return self._sha.value + + @property + def size(self) -> int: + self._completeIfNotSet(self._size) + return self._size.value + + @property + def submodule_git_url(self) -> str: + self._completeIfNotSet(self._submodule_git_url) + return self._submodule_git_url.value + + @property + def target(self) -> str: + self._completeIfNotSet(self._target) + return self._target.value + + @property + def text_matches(self) -> str: + self._completeIfNotSet(self._text_matches) + return self._text_matches.value + + @property + def type(self) -> str: + self._completeIfNotSet(self._type) + return self._type.value + + @property + def url(self) -> str: + self._completeIfNotSet(self._url) + return self._url.value + + def _useAttributes(self, attributes: dict[str, Any]) -> None: + if "_links" in attributes: # pragma no branch + self.__links = self._makeDictAttribute(attributes["_links"]) + if "commit" in attributes: # pragma no branch + self._commit = self._makeClassAttribute(github.GitCommit.GitCommit, attributes["commit"]) + if "content" in attributes: # pragma no branch + self._content = self._makeStringAttribute(attributes["content"]) + if "download_url" in attributes: # pragma no branch + self._download_url = self._makeStringAttribute(attributes["download_url"]) + if "encoding" in attributes: # pragma no branch + self._encoding = self._makeStringAttribute(attributes["encoding"]) + if "file_size" in attributes: # pragma no branch + self._file_size = self._makeIntAttribute(attributes["file_size"]) + if "git_url" in attributes: # pragma no branch + self._git_url = self._makeStringAttribute(attributes["git_url"]) + if "html_url" in attributes: # pragma no branch + self._html_url = self._makeStringAttribute(attributes["html_url"]) + if "language" in attributes: # pragma no branch + self._language = self._makeStringAttribute(attributes["language"]) + if "last_modified_at" in attributes: # pragma no branch + self._last_modified_at = self._makeDatetimeAttribute(attributes["last_modified_at"]) + if "license" in attributes: # pragma no branch + self._license = self._makeClassAttribute(github.License.License, attributes["license"]) + if "line_numbers" in attributes: # pragma no branch + self._line_numbers = self._makeListOfStringsAttribute(attributes["line_numbers"]) + if "name" in attributes: # pragma no branch + self._name = self._makeStringAttribute(attributes["name"]) + if "path" in attributes: # pragma no branch + self._path = self._makeStringAttribute(attributes["path"]) + if "repository" in attributes: # pragma no branch + self._repository = self._makeClassAttribute(github.Repository.Repository, attributes["repository"]) + if "score" in attributes: # pragma no branch + self._score = self._makeFloatAttribute(attributes["score"]) + if "sha" in attributes: # pragma no branch + self._sha = self._makeStringAttribute(attributes["sha"]) + if "size" in attributes: # pragma no branch + self._size = self._makeIntAttribute(attributes["size"]) + if "submodule_git_url" in attributes: # pragma no branch + self._submodule_git_url = self._makeStringAttribute(attributes["submodule_git_url"]) + if "target" in attributes: # pragma no branch + self._target = self._makeStringAttribute(attributes["target"]) + if "text_matches" in attributes: # pragma no branch + self._text_matches = self._makeListOfDictsAttribute(attributes["text_matches"]) + if "type" in attributes: # pragma no branch + self._type = self._makeStringAttribute(attributes["type"]) + if "url" in attributes: # pragma no branch + self._url = self._makeStringAttribute(attributes["url"]) diff --git a/venv/lib/python3.10/site-packages/github/Copilot.py b/venv/lib/python3.10/site-packages/github/Copilot.py new file mode 100644 index 0000000000000000000000000000000000000000..ab63752192c85a24d3aa12f6c69bde17a2ed0c89 --- /dev/null +++ b/venv/lib/python3.10/site-packages/github/Copilot.py @@ -0,0 +1,96 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2024 Pasha Fateev # +# Copyright 2025 Enrico Minack # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +import github.CopilotSeat +from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet +from github.PaginatedList import PaginatedList + +if TYPE_CHECKING: + from github.CopilotSeat import CopilotSeat + from github.Requester import Requester + + +class Copilot(NonCompletableGithubObject): + def __init__(self, requester: Requester, org_name: str) -> None: + super().__init__(requester, {}, {"org_name": org_name}) + + def _initAttributes(self) -> None: + self._org_name: Attribute[str] = NotSet + + def _useAttributes(self, attributes: dict[str, Any]) -> None: + if "org_name" in attributes: # pragma no branch + self._org_name = self._makeStringAttribute(attributes["org_name"]) + + def __repr__(self) -> str: + return self.get__repr__({"org_name": self._org_name.value if self._org_name is not NotSet else NotSet}) + + @property + def org_name(self) -> str: + return self._org_name.value + + def get_seats(self) -> PaginatedList[CopilotSeat]: + """ + :calls: `GET /orgs/{org}/copilot/billing/seats `_ + """ + url = f"/orgs/{self._org_name.value}/copilot/billing/seats" + return PaginatedList( + github.CopilotSeat.CopilotSeat, + self._requester, + url, + None, + list_item="seats", + ) + + def add_seats(self, selected_usernames: list[str]) -> int: + """ + :calls: `POST /orgs/{org}/copilot/billing/selected_users `_ + :param selected_usernames: List of usernames to add Copilot seats for + :rtype: int + :return: Number of seats created + """ + url = f"/orgs/{self._org_name.value}/copilot/billing/selected_users" + _, data = self._requester.requestJsonAndCheck( + "POST", + url, + input={"selected_usernames": selected_usernames}, + ) + return data["seats_created"] + + def remove_seats(self, selected_usernames: list[str]) -> int: + """ + :calls: `DELETE /orgs/{org}/copilot/billing/selected_users `_ + :param selected_usernames: List of usernames to remove Copilot seats for + :rtype: int + :return: Number of seats cancelled + """ + url = f"/orgs/{self._org_name.value}/copilot/billing/selected_users" + _, data = self._requester.requestJsonAndCheck( + "DELETE", + url, + input={"selected_usernames": selected_usernames}, + ) + return data["seats_cancelled"] diff --git a/venv/lib/python3.10/site-packages/github/CopilotSeat.py b/venv/lib/python3.10/site-packages/github/CopilotSeat.py new file mode 100644 index 0000000000000000000000000000000000000000..ef2158c6690d267ce2a6a5acd94eff5c3993d350 --- /dev/null +++ b/venv/lib/python3.10/site-packages/github/CopilotSeat.py @@ -0,0 +1,96 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2024 Pasha Fateev # +# Copyright 2025 Enrico Minack # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from __future__ import annotations + +from datetime import datetime +from typing import Any + +import github.NamedUser +import github.Team +from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet, _NotSetType + + +class CopilotSeat(NonCompletableGithubObject): + def _initAttributes(self) -> None: + self._created_at: Attribute[datetime] | _NotSetType = NotSet + self._updated_at: Attribute[datetime] | _NotSetType = NotSet + self._pending_cancellation_date: Attribute[datetime] | _NotSetType = NotSet + self._last_activity_at: Attribute[datetime] | _NotSetType = NotSet + self._last_activity_editor: Attribute[str] | _NotSetType = NotSet + self._plan_type: Attribute[str] | _NotSetType = NotSet + self._assignee: Attribute[github.NamedUser.NamedUser] | _NotSetType = NotSet + self._assigning_team: Attribute[github.Team.Team] | _NotSetType = NotSet + + def _useAttributes(self, attributes: dict[str, Any]) -> None: + if "created_at" in attributes: + self._created_at = self._makeDatetimeAttribute(attributes["created_at"]) + if "updated_at" in attributes: + self._updated_at = self._makeDatetimeAttribute(attributes["updated_at"]) + if "pending_cancellation_date" in attributes: + self._pending_cancellation_date = self._makeDatetimeAttribute(attributes["pending_cancellation_date"]) + if "last_activity_at" in attributes: + self._last_activity_at = self._makeDatetimeAttribute(attributes["last_activity_at"]) + if "last_activity_editor" in attributes: + self._last_activity_editor = self._makeStringAttribute(attributes["last_activity_editor"]) + if "plan_type" in attributes: + self._plan_type = self._makeStringAttribute(attributes["plan_type"]) + if "assignee" in attributes: + self._assignee = self._makeClassAttribute(github.NamedUser.NamedUser, attributes["assignee"]) + if "assigning_team" in attributes: + self._assigning_team = self._makeClassAttribute(github.Team.Team, attributes["assigning_team"]) + + def __repr__(self) -> str: + return self.get__repr__({"assignee": self._assignee.value}) + + @property + def created_at(self) -> datetime: + return self._created_at.value + + @property + def updated_at(self) -> datetime: + return self._updated_at.value + + @property + def pending_cancellation_date(self) -> datetime: + return self._pending_cancellation_date.value + + @property + def last_activity_at(self) -> datetime: + return self._last_activity_at.value + + @property + def last_activity_editor(self) -> str: + return self._last_activity_editor.value + + @property + def plan_type(self) -> str: + return self._plan_type.value + + @property + def assignee(self) -> github.NamedUser.NamedUser: + return self._assignee.value + + @property + def assigning_team(self) -> github.Team.Team: + return self._assigning_team.value diff --git a/venv/lib/python3.10/site-packages/github/DefaultCodeSecurityConfig.py b/venv/lib/python3.10/site-packages/github/DefaultCodeSecurityConfig.py new file mode 100644 index 0000000000000000000000000000000000000000..dabdb71335fef360ebafb21191fece18a9ef4a0b --- /dev/null +++ b/venv/lib/python3.10/site-packages/github/DefaultCodeSecurityConfig.py @@ -0,0 +1,84 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2012 Vincent Jacques # +# Copyright 2012 Zearin # +# Copyright 2013 AKFish # +# Copyright 2013 Vincent Jacques # +# Copyright 2014 Vincent Jacques # +# Copyright 2016 Jannis Gebauer # +# Copyright 2016 Peter Buckley # +# Copyright 2018 Justin Kufro # +# Copyright 2018 Wan Liuyang # +# Copyright 2018 sfdye # +# Copyright 2019 Steve Kowalik # +# Copyright 2019 Wan Liuyang # +# Copyright 2020 Steve Kowalik # +# Copyright 2021 Steve Kowalik # +# Copyright 2023 Enrico Minack # +# Copyright 2023 Trim21 # +# Copyright 2024 Enrico Minack # +# Copyright 2024 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2025 Bill Napier # +# Copyright 2025 Enrico Minack # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from __future__ import annotations + +from typing import Any + +import github.CodeSecurityConfig +from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet + + +class DefaultCodeSecurityConfig(NonCompletableGithubObject): + """ + This class represents a Default Configurations for Code Security. + + The reference can be found here + https://docs.github.com/en/rest/code-security/configurations. + + """ + + def _initAttributes(self) -> None: + self._configuration: Attribute[github.CodeSecurityConfig.CodeSecurityConfig] = NotSet + self._default_for_new_repos: Attribute[str] = NotSet + + def __repr__(self) -> str: + return self.get__repr__( + { + "default_for_new_repos": self.default_for_new_repos, + } + ) + + @property + def configuration(self) -> github.CodeSecurityConfig.CodeSecurityConfig: + return self._configuration.value + + @property + def default_for_new_repos(self) -> str: + return self._default_for_new_repos.value + + def _useAttributes(self, attributes: dict[str, Any]) -> None: + if "configuration" in attributes: # pragma no branch + self._configuration = self._makeClassAttribute( + github.CodeSecurityConfig.CodeSecurityConfig, attributes["configuration"] + ) + if "default_for_new_repos" in attributes: # pragma no branch + self._default_for_new_repos = self._makeStringAttribute(attributes["default_for_new_repos"]) diff --git a/venv/lib/python3.10/site-packages/github/DependabotAlert.py b/venv/lib/python3.10/site-packages/github/DependabotAlert.py new file mode 100644 index 0000000000000000000000000000000000000000..96cfc616c49e5d78cd3328f702a9cf6a1acba51a --- /dev/null +++ b/venv/lib/python3.10/site-packages/github/DependabotAlert.py @@ -0,0 +1,173 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2024 Enrico Minack # +# Copyright 2024 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2024 Thomas Cooper # +# Copyright 2025 Enrico Minack # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from __future__ import annotations + +from datetime import datetime +from typing import TYPE_CHECKING, Any + +import github.AdvisoryVulnerabilityPackage +import github.DependabotAlertAdvisory +import github.DependabotAlertDependency +import github.DependabotAlertVulnerability +import github.NamedUser +from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet + +if TYPE_CHECKING: + from github.DependabotAlertAdvisory import DependabotAlertAdvisory + from github.DependabotAlertDependency import DependabotAlertDependency + from github.DependabotAlertVulnerability import DependabotAlertVulnerability + from github.NamedUser import NamedUser + + +class DependabotAlert(NonCompletableGithubObject): + """ + This class represents a DependabotAlert. + + The reference can be found here + https://docs.github.com/en/rest/dependabot/alerts + + The OpenAPI schema can be found at + - /components/schemas/dependabot-alert + + """ + + def _initAttributes(self) -> None: + self._auto_dismissed_at: Attribute[datetime] = NotSet + self._created_at: Attribute[datetime] = NotSet + self._dependency: Attribute[DependabotAlertDependency] = NotSet + self._dismissed_at: Attribute[datetime | None] = NotSet + self._dismissed_by: Attribute[NamedUser | None] = NotSet + self._dismissed_comment: Attribute[str | None] = NotSet + self._dismissed_reason: Attribute[str | None] = NotSet + self._fixed_at: Attribute[str] = NotSet + self._html_url: Attribute[str] = NotSet + self._number: Attribute[int] = NotSet + self._security_advisory: Attribute[DependabotAlertAdvisory] = NotSet + self._security_vulnerability: Attribute[DependabotAlertVulnerability] = NotSet + self._state: Attribute[str] = NotSet + self._updated_at: Attribute[datetime] = NotSet + self._url: Attribute[str] = NotSet + + def __repr__(self) -> str: + return self.get__repr__({"number": self.number, "ghsa_id": self.security_advisory.ghsa_id}) + + @property + def auto_dismissed_at(self) -> datetime: + return self._auto_dismissed_at.value + + @property + def created_at(self) -> datetime: + return self._created_at.value + + @property + def dependency(self) -> DependabotAlertDependency: + return self._dependency.value + + @property + def dismissed_at(self) -> datetime | None: + return self._dismissed_at.value + + @property + def dismissed_by(self) -> NamedUser | None: + return self._dismissed_by.value + + @property + def dismissed_comment(self) -> str | None: + return self._dismissed_comment.value + + @property + def dismissed_reason(self) -> str | None: + return self._dismissed_reason.value + + @property + def fixed_at(self) -> str | None: + return self._fixed_at.value + + @property + def html_url(self) -> str: + return self._html_url.value + + @property + def number(self) -> int: + return self._number.value + + @property + def security_advisory(self) -> DependabotAlertAdvisory: + return self._security_advisory.value + + @property + def security_vulnerability(self) -> DependabotAlertVulnerability: + return self._security_vulnerability.value + + @property + def state(self) -> str: + return self._state.value + + @property + def updated_at(self) -> datetime: + return self._updated_at.value + + @property + def url(self) -> str: + return self._url.value + + def _useAttributes(self, attributes: dict[str, Any]) -> None: + if "auto_dismissed_at" in attributes: # pragma no branch + self._auto_dismissed_at = self._makeDatetimeAttribute(attributes["auto_dismissed_at"]) + if "created_at" in attributes: + self._created_at = self._makeDatetimeAttribute(attributes["created_at"]) + if "dependency" in attributes: + self._dependency = self._makeClassAttribute( + github.DependabotAlertDependency.DependabotAlertDependency, attributes["dependency"] + ) + if "dismissed_at" in attributes: + self._dismissed_at = self._makeDatetimeAttribute(attributes["dismissed_at"]) + if "dismissed_by" in attributes: + self._dismissed_by = self._makeClassAttribute(github.NamedUser.NamedUser, attributes["dismissed_by"]) + if "dismissed_comment" in attributes: + self._dismissed_comment = self._makeStringAttribute(attributes["dismissed_comment"]) + if "dismissed_reason" in attributes: + self._dismissed_reason = self._makeStringAttribute(attributes["dismissed_reason"]) + if "fixed_at" in attributes: + self._fixed_at = self._makeStringAttribute(attributes["fixed_at"]) + if "html_url" in attributes: + self._html_url = self._makeStringAttribute(attributes["html_url"]) + if "number" in attributes: + self._number = self._makeIntAttribute(attributes["number"]) + if "security_advisory" in attributes: + self._security_advisory = self._makeClassAttribute( + github.DependabotAlertAdvisory.DependabotAlertAdvisory, attributes["security_advisory"] + ) + if "security_vulnerability" in attributes: + self._security_vulnerability = self._makeClassAttribute( + github.DependabotAlertVulnerability.DependabotAlertVulnerability, attributes["security_vulnerability"] + ) + if "state" in attributes: + self._state = self._makeStringAttribute(attributes["state"]) + if "updated_at" in attributes: + self._updated_at = self._makeDatetimeAttribute(attributes["updated_at"]) + if "url" in attributes: + self._url = self._makeStringAttribute(attributes["url"]) diff --git a/venv/lib/python3.10/site-packages/github/DependabotAlertAdvisory.py b/venv/lib/python3.10/site-packages/github/DependabotAlertAdvisory.py new file mode 100644 index 0000000000000000000000000000000000000000..b269bec2e27aa97de24178467a2733cf13ac0838 --- /dev/null +++ b/venv/lib/python3.10/site-packages/github/DependabotAlertAdvisory.py @@ -0,0 +1,73 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2024 Enrico Minack # +# Copyright 2024 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2024 Thomas Cooper # +# Copyright 2025 Enrico Minack # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +import github.DependabotAlertVulnerability +from github.AdvisoryBase import AdvisoryBase +from github.GithubObject import Attribute, NotSet + +if TYPE_CHECKING: + from github.DependabotAlertVulnerability import DependabotAlertVulnerability + + +class DependabotAlertAdvisory(AdvisoryBase): + """ + This class represents a package flagged by a Dependabot alert that is vulnerable to a parent SecurityAdvisory. + + The reference can be found here + https://docs.github.com/en/rest/dependabot/alerts + + The OpenAPI schema can be found at + - /components/schemas/dependabot-alert-security-advisory + + """ + + def _initAttributes(self) -> None: + super()._initAttributes() + self._references: Attribute[list[dict]] = NotSet + self._vulnerabilities: Attribute[list[DependabotAlertVulnerability]] = NotSet + + @property + def references(self) -> list[dict]: + return self._references.value + + @property + def vulnerabilities(self) -> list[DependabotAlertVulnerability]: + return self._vulnerabilities.value + + def _useAttributes(self, attributes: dict[str, Any]) -> None: + if "references" in attributes: + self._references = self._makeListOfDictsAttribute( + attributes["references"], + ) + if "vulnerabilities" in attributes: + self._vulnerabilities = self._makeListOfClassesAttribute( + github.DependabotAlertVulnerability.DependabotAlertVulnerability, + attributes["vulnerabilities"], + ) + super()._useAttributes(attributes) diff --git a/venv/lib/python3.10/site-packages/github/DependabotAlertDependency.py b/venv/lib/python3.10/site-packages/github/DependabotAlertDependency.py new file mode 100644 index 0000000000000000000000000000000000000000..36fa6b75a28fba1c021cec2f3d2d25b14bb99421 --- /dev/null +++ b/venv/lib/python3.10/site-packages/github/DependabotAlertDependency.py @@ -0,0 +1,80 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2024 Enrico Minack # +# Copyright 2024 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2024 Thomas Cooper # +# Copyright 2025 Enrico Minack # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from __future__ import annotations + +from typing import Any + +from github.AdvisoryVulnerabilityPackage import AdvisoryVulnerabilityPackage +from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet + + +class DependabotAlertDependency(NonCompletableGithubObject): + """ + This class represents a DependabotAlertDependency. + + The reference can be found here + https://docs.github.com/en/rest/dependabot/alerts + + The OpenAPI schema can be found at + - /components/schemas/dependabot-alert/properties/dependency + + """ + + def _initAttributes(self) -> None: + self._manifest_path: Attribute[str] = NotSet + self._package: Attribute[AdvisoryVulnerabilityPackage] = NotSet + self._scope: Attribute[str] = NotSet + + def __repr__(self) -> str: + return self.get__repr__( + { + "package": self.package, + "manifest_path": self.manifest_path, + } + ) + + @property + def manifest_path(self) -> str: + return self._manifest_path.value + + @property + def package(self) -> AdvisoryVulnerabilityPackage: + return self._package.value + + @property + def scope(self) -> str: + return self._scope.value + + def _useAttributes(self, attributes: dict[str, Any]) -> None: + if "manifest_path" in attributes: + self._manifest_path = self._makeStringAttribute(attributes["manifest_path"]) + if "package" in attributes: + self._package = self._makeClassAttribute( + AdvisoryVulnerabilityPackage, + attributes["package"], + ) + if "scope" in attributes: + self._scope = self._makeStringAttribute(attributes["scope"]) diff --git a/venv/lib/python3.10/site-packages/github/DependabotAlertVulnerability.py b/venv/lib/python3.10/site-packages/github/DependabotAlertVulnerability.py new file mode 100644 index 0000000000000000000000000000000000000000..ea31b7e3de5fdc624d8435bc37fe6bd7bb50c8ed --- /dev/null +++ b/venv/lib/python3.10/site-packages/github/DependabotAlertVulnerability.py @@ -0,0 +1,83 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2024 Enrico Minack # +# Copyright 2024 Thomas Cooper # +# Copyright 2025 Enrico Minack # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +import github.AdvisoryVulnerabilityPackage +from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet + +if TYPE_CHECKING: + from github.AdvisoryVulnerabilityPackage import AdvisoryVulnerabilityPackage + + +class DependabotAlertVulnerability(NonCompletableGithubObject): + """ + A vulnerability represented in a Dependabot alert. + + The OpenAPI schema can be found at + - /components/schemas/dependabot-alert-security-vulnerability + + """ + + def _initAttributes(self) -> None: + self._first_patched_version: Attribute[dict] = NotSet + self._package: Attribute[AdvisoryVulnerabilityPackage] = NotSet + self._severity: Attribute[str] = NotSet + self._vulnerable_version_range: Attribute[str | None] = NotSet + + def __repr__(self) -> str: + return self.get__repr__({"package": self.package, "severity": self.severity}) + + @property + def first_patched_version(self) -> dict: + return self._first_patched_version.value + + @property + def package(self) -> AdvisoryVulnerabilityPackage: + return self._package.value + + @property + def severity(self) -> str: + return self._severity.value + + @property + def vulnerable_version_range(self) -> str | None: + return self._vulnerable_version_range.value + + def _useAttributes(self, attributes: dict[str, Any]) -> None: + if "first_patched_version" in attributes: + self._first_patched_version = self._makeDictAttribute( + attributes["first_patched_version"], + ) + if "package" in attributes: + self._package = self._makeClassAttribute( + github.AdvisoryVulnerabilityPackage.AdvisoryVulnerabilityPackage, + attributes["package"], + ) + if "severity" in attributes: + self._severity = self._makeStringAttribute(attributes["severity"]) + if "vulnerable_version_range" in attributes: + self._vulnerable_version_range = self._makeStringAttribute(attributes["vulnerable_version_range"]) diff --git a/venv/lib/python3.10/site-packages/github/Deployment.py b/venv/lib/python3.10/site-packages/github/Deployment.py new file mode 100644 index 0000000000000000000000000000000000000000..c7b02fe4dd5cd18c9edda62aff49a2ea37834ead --- /dev/null +++ b/venv/lib/python3.10/site-packages/github/Deployment.py @@ -0,0 +1,298 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2012 Vincent Jacques # +# Copyright 2012 Zearin # +# Copyright 2013 AKFish # +# Copyright 2013 Vincent Jacques # +# Copyright 2014 Vincent Jacques # +# Copyright 2015 Matt Babineau # +# Copyright 2016 Jannis Gebauer # +# Copyright 2016 Martijn Koster # +# Copyright 2016 Peter Buckley # +# Copyright 2018 Wan Liuyang # +# Copyright 2018 sfdye # +# Copyright 2019 Steve Kowalik # +# Copyright 2019 Wan Liuyang # +# Copyright 2020 Colby Gallup # +# Copyright 2020 Pascal Hofmann # +# Copyright 2020 Steve Kowalik # +# Copyright 2021 Steve Kowalik # +# Copyright 2023 Enrico Minack # +# Copyright 2023 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2023 Nevins # +# Copyright 2023 Trim21 # +# Copyright 2024 Enrico Minack # +# Copyright 2024 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2025 Enrico Minack # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from __future__ import annotations + +from datetime import datetime +from typing import TYPE_CHECKING, Any + +import github.Consts +import github.DeploymentStatus +import github.GithubApp +import github.NamedUser +from github.GithubObject import Attribute, CompletableGithubObject, NotSet, Opt +from github.PaginatedList import PaginatedList + +if TYPE_CHECKING: + from github.GithubApp import GithubApp + from github.NamedUser import NamedUser + + +class Deployment(CompletableGithubObject): + """ + This class represents Deployments. + + The reference can be found here + https://docs.github.com/en/rest/reference/repos#deployments + + The OpenAPI schema can be found at + - /components/schemas/deployment + - /components/schemas/deployment-simple + + """ + + def _initAttributes(self) -> None: + self._created_at: Attribute[datetime] = NotSet + self._creator: Attribute[NamedUser] = NotSet + self._description: Attribute[str] = NotSet + self._environment: Attribute[str] = NotSet + self._id: Attribute[int] = NotSet + self._node_id: Attribute[str] = NotSet + self._original_environment: Attribute[str] = NotSet + self._payload: Attribute[dict[str, Any]] = NotSet + self._performed_via_github_app: Attribute[GithubApp] = NotSet + self._production_environment: Attribute[bool] = NotSet + self._ref: Attribute[str] = NotSet + self._repository_url: Attribute[str] = NotSet + self._sha: Attribute[str] = NotSet + self._statuses_url: Attribute[str] = NotSet + self._task: Attribute[str] = NotSet + self._transient_environment: Attribute[bool] = NotSet + self._updated_at: Attribute[datetime | None] = NotSet + self._url: Attribute[str] = NotSet + + def __repr__(self) -> str: + return self.get__repr__({"id": self._id.value, "url": self._url.value}) + + @property + def created_at(self) -> datetime: + self._completeIfNotSet(self._created_at) + return self._created_at.value + + @property + def creator(self) -> NamedUser: + self._completeIfNotSet(self._creator) + return self._creator.value + + @property + def description(self) -> str: + self._completeIfNotSet(self._description) + return self._description.value + + @property + def environment(self) -> str: + self._completeIfNotSet(self._environment) + return self._environment.value + + @property + def id(self) -> int: + self._completeIfNotSet(self._id) + return self._id.value + + @property + def node_id(self) -> str: + self._completeIfNotSet(self._node_id) + return self._node_id.value + + @property + def original_environment(self) -> str: + self._completeIfNotSet(self._original_environment) + return self._original_environment.value + + @property + def payload(self) -> dict[str, Any]: + self._completeIfNotSet(self._payload) + return self._payload.value + + @property + def performed_via_github_app(self) -> GithubApp: + self._completeIfNotSet(self._performed_via_github_app) + return self._performed_via_github_app.value + + @property + def production_environment(self) -> bool: + self._completeIfNotSet(self._production_environment) + return self._production_environment.value + + @property + def ref(self) -> str: + self._completeIfNotSet(self._ref) + return self._ref.value + + @property + def repository_url(self) -> str: + self._completeIfNotSet(self._repository_url) + return self._repository_url.value + + @property + def sha(self) -> str: + self._completeIfNotSet(self._sha) + return self._sha.value + + @property + def statuses_url(self) -> str: + self._completeIfNotSet(self._statuses_url) + return self._statuses_url.value + + @property + def task(self) -> str: + self._completeIfNotSet(self._task) + return self._task.value + + @property + def transient_environment(self) -> bool: + self._completeIfNotSet(self._transient_environment) + return self._transient_environment.value + + @property + def updated_at(self) -> datetime | None: + self._completeIfNotSet(self._updated_at) + return self._updated_at.value + + @property + def url(self) -> str: + self._completeIfNotSet(self._url) + return self._url.value + + def get_statuses(self) -> PaginatedList[github.DeploymentStatus.DeploymentStatus]: + """ + :calls: `GET /repos/{owner}/deployments/{deployment_id}/statuses `_ + """ + return PaginatedList( + github.DeploymentStatus.DeploymentStatus, + self._requester, + f"{self.url}/statuses", + None, + headers={"Accept": self._get_accept_header()}, + ) + + def get_status(self, id_: int) -> github.DeploymentStatus.DeploymentStatus: + """ + :calls: `GET /repos/{owner}/deployments/{deployment_id}/statuses/{status_id} `_ + """ + assert isinstance(id_, int), id_ + headers, data = self._requester.requestJsonAndCheck( + "GET", + f"{self.url}/statuses/{id_}", + headers={"Accept": self._get_accept_header()}, + ) + return github.DeploymentStatus.DeploymentStatus(self._requester, headers, data, completed=True) + + def create_status( + self, + state: str, + target_url: Opt[str] = NotSet, + description: Opt[str] = NotSet, + environment: Opt[str] = NotSet, + environment_url: Opt[str] = NotSet, + auto_inactive: Opt[bool] = NotSet, + ) -> github.DeploymentStatus.DeploymentStatus: + """ + :calls: `POST /repos/{owner}/{repo}/deployments/{deployment_id}/statuses `_ + """ + assert isinstance(state, str), state + assert target_url is NotSet or isinstance(target_url, str), target_url + assert description is NotSet or isinstance(description, str), description + assert environment is NotSet or isinstance(environment, str), environment + assert environment_url is NotSet or isinstance(environment_url, str), environment_url + assert auto_inactive is NotSet or isinstance(auto_inactive, bool), auto_inactive + + post_parameters = NotSet.remove_unset_items( + { + "state": state, + "target_url": target_url, + "description": description, + "environment": environment, + "environment_url": environment_url, + "auto_inactive": auto_inactive, + } + ) + + headers, data = self._requester.requestJsonAndCheck( + "POST", + f"{self.url}/statuses", + input=post_parameters, + headers={"Accept": self._get_accept_header()}, + ) + return github.DeploymentStatus.DeploymentStatus(self._requester, headers, data, completed=True) + + @staticmethod + def _get_accept_header() -> str: + return ", ".join( + [ + github.Consts.deploymentEnhancementsPreview, + github.Consts.deploymentStatusEnhancementsPreview, + ] + ) + + def _useAttributes(self, attributes: dict[str, Any]) -> None: + if "created_at" in attributes: # pragma no branch + self._created_at = self._makeDatetimeAttribute(attributes["created_at"]) + if "creator" in attributes: # pragma no branch + self._creator = self._makeClassAttribute(github.NamedUser.NamedUser, attributes["creator"]) + if "description" in attributes: # pragma no branch + self._description = self._makeStringAttribute(attributes["description"]) + if "environment" in attributes: # pragma no branch + self._environment = self._makeStringAttribute(attributes["environment"]) + if "id" in attributes: # pragma no branch + self._id = self._makeIntAttribute(attributes["id"]) + if "node_id" in attributes: # pragma no branch + self._node_id = self._makeStringAttribute(attributes["node_id"]) + if "original_environment" in attributes: # pragma no branch + self._original_environment = self._makeStringAttribute(attributes["original_environment"]) + if "payload" in attributes: # pragma no branch + self._payload = self._makeDictAttribute(attributes["payload"]) + if "performed_via_github_app" in attributes: # pragma no branch + self._performed_via_github_app = self._makeClassAttribute( + github.GithubApp.GithubApp, attributes["performed_via_github_app"] + ) + if "production_environment" in attributes: # pragma no branch + self._production_environment = self._makeBoolAttribute(attributes["production_environment"]) + if "ref" in attributes: # pragma no branch + self._ref = self._makeStringAttribute(attributes["ref"]) + if "repository_url" in attributes: # pragma no branch + self._repository_url = self._makeStringAttribute(attributes["repository_url"]) + if "sha" in attributes: # pragma no branch + self._sha = self._makeStringAttribute(attributes["sha"]) + if "statuses_url" in attributes: # pragma no branch + self._statuses_url = self._makeStringAttribute(attributes["statuses_url"]) + if "task" in attributes: # pragma no branch + self._task = self._makeStringAttribute(attributes["task"]) + if "transient_environment" in attributes: # pragma no branch + self._transient_environment = self._makeBoolAttribute(attributes["transient_environment"]) + if "updated_at" in attributes: # pragma no branch + self._updated_at = self._makeDatetimeAttribute(attributes["updated_at"]) + if "url" in attributes: # pragma no branch + self._url = self._makeStringAttribute(attributes["url"]) diff --git a/venv/lib/python3.10/site-packages/github/DeploymentStatus.py b/venv/lib/python3.10/site-packages/github/DeploymentStatus.py new file mode 100644 index 0000000000000000000000000000000000000000..19eb16b87efc08c9e79198a5695583e3a37fbb8e --- /dev/null +++ b/venv/lib/python3.10/site-packages/github/DeploymentStatus.py @@ -0,0 +1,198 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2012 Vincent Jacques # +# Copyright 2012 Zearin # +# Copyright 2013 AKFish # +# Copyright 2013 Vincent Jacques # +# Copyright 2014 Vincent Jacques # +# Copyright 2015 Matt Babineau # +# Copyright 2016 Jannis Gebauer # +# Copyright 2016 Martijn Koster # +# Copyright 2016 Peter Buckley # +# Copyright 2018 Wan Liuyang # +# Copyright 2018 sfdye # +# Copyright 2019 Steve Kowalik # +# Copyright 2019 Wan Liuyang # +# Copyright 2020 Colby Gallup # +# Copyright 2020 Pascal Hofmann # +# Copyright 2020 Steve Kowalik # +# Copyright 2021 Steve Kowalik # +# Copyright 2023 Enrico Minack # +# Copyright 2023 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2023 Trim21 # +# Copyright 2024 Enrico Minack # +# Copyright 2024 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2025 Enrico Minack # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from __future__ import annotations + +from datetime import datetime +from typing import TYPE_CHECKING, Any + +import github.GithubApp +import github.NamedUser +from github.GithubObject import Attribute, CompletableGithubObject, NotSet + +if TYPE_CHECKING: + from github.GithubApp import GithubApp + from github.NamedUser import NamedUser + + +class DeploymentStatus(CompletableGithubObject): + """ + This class represents Deployment Statuses. + + The reference can be found here + https://docs.github.com/en/rest/reference/repos#deployments + + The OpenAPI schema can be found at + - /components/schemas/deployment-status + + """ + + def _initAttributes(self) -> None: + self._created_at: Attribute[datetime] = NotSet + self._creator: Attribute[NamedUser] = NotSet + self._deployment_url: Attribute[str] = NotSet + self._description: Attribute[str] = NotSet + self._environment: Attribute[str] = NotSet + self._environment_url: Attribute[str] = NotSet + self._id: Attribute[int] = NotSet + self._log_url: Attribute[str] = NotSet + self._node_id: Attribute[str] = NotSet + self._performed_via_github_app: Attribute[GithubApp] = NotSet + self._repository_url: Attribute[str] = NotSet + self._state: Attribute[str] = NotSet + self._target_url: Attribute[str] = NotSet + self._updated_at: Attribute[datetime] = NotSet + self._url: Attribute[str] = NotSet + + def __repr__(self) -> str: + return self.get__repr__({"id": self._id.value, "url": self._url.value}) + + @property + def created_at(self) -> datetime: + self._completeIfNotSet(self._created_at) + return self._created_at.value + + @property + def creator(self) -> NamedUser: + self._completeIfNotSet(self._creator) + return self._creator.value + + @property + def deployment_url(self) -> str: + self._completeIfNotSet(self._deployment_url) + return self._deployment_url.value + + @property + def description(self) -> str: + self._completeIfNotSet(self._description) + return self._description.value + + @property + def environment(self) -> str: + self._completeIfNotSet(self._environment) + return self._environment.value + + @property + def environment_url(self) -> str: + self._completeIfNotSet(self._environment_url) + return self._environment_url.value + + @property + def id(self) -> int: + self._completeIfNotSet(self._id) + return self._id.value + + @property + def log_url(self) -> str: + self._completeIfNotSet(self._log_url) + return self._log_url.value + + @property + def node_id(self) -> str: + self._completeIfNotSet(self._node_id) + return self._node_id.value + + @property + def performed_via_github_app(self) -> GithubApp: + self._completeIfNotSet(self._performed_via_github_app) + return self._performed_via_github_app.value + + @property + def repository_url(self) -> str: + self._completeIfNotSet(self._repository_url) + return self._repository_url.value + + @property + def state(self) -> str: + self._completeIfNotSet(self._state) + return self._state.value + + @property + def target_url(self) -> str: + self._completeIfNotSet(self._target_url) + return self._target_url.value + + @property + def updated_at(self) -> datetime: + self._completeIfNotSet(self._updated_at) + return self._updated_at.value + + @property + def url(self) -> str: + self._completeIfNotSet(self._url) + return self._url.value + + def _useAttributes(self, attributes: dict[str, Any]) -> None: + if "created_at" in attributes: # pragma no branch + self._created_at = self._makeDatetimeAttribute(attributes["created_at"]) + if "creator" in attributes: # pragma no branch + self._creator = self._makeClassAttribute(github.NamedUser.NamedUser, attributes["creator"]) + if "deployment_url" in attributes: # pragma no branch + self._deployment_url = self._makeStringAttribute(attributes["deployment_url"]) + if "description" in attributes: # pragma no branch + self._description = self._makeStringAttribute(attributes["description"]) + if "environment" in attributes: # pragma no branch + self._environment = self._makeStringAttribute(attributes["environment"]) + if "environment_url" in attributes: # pragma no branch + self._environment_url = self._makeStringAttribute(attributes["environment_url"]) + if "id" in attributes: # pragma no branch + self._id = self._makeIntAttribute(attributes["id"]) + if "log_url" in attributes: # pragma no branch + self._log_url = self._makeStringAttribute(attributes["log_url"]) + if "node_id" in attributes: # pragma no branch + self._node_id = self._makeStringAttribute(attributes["node_id"]) + if "performed_via_github_app" in attributes: # pragma no branch + self._performed_via_github_app = self._makeClassAttribute( + github.GithubApp.GithubApp, attributes["performed_via_github_app"] + ) + if "repository_url" in attributes: # pragma no branch + self._repository_url = self._makeStringAttribute(attributes["repository_url"]) + if "state" in attributes: # pragma no branch + self._state = self._makeStringAttribute(attributes["state"]) + if "target_url" in attributes: # pragma no branch + self._target_url = self._makeStringAttribute(attributes["target_url"]) + if "updated_at" in attributes: # pragma no branch + self._updated_at = self._makeDatetimeAttribute(attributes["updated_at"]) + if "url" in attributes: # pragma no branch + self._url = self._makeStringAttribute(attributes["url"]) diff --git a/venv/lib/python3.10/site-packages/github/DiscussionBase.py b/venv/lib/python3.10/site-packages/github/DiscussionBase.py new file mode 100644 index 0000000000000000000000000000000000000000..43533740639cd879f1ebe71c3a102571726715b6 --- /dev/null +++ b/venv/lib/python3.10/site-packages/github/DiscussionBase.py @@ -0,0 +1,135 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2012 Vincent Jacques # +# Copyright 2012 Zearin # +# Copyright 2013 AKFish # +# Copyright 2013 Vincent Jacques # +# Copyright 2014 Vincent Jacques # +# Copyright 2016 Jannis Gebauer # +# Copyright 2016 Peter Buckley # +# Copyright 2018 Wan Liuyang # +# Copyright 2018 sfdye # +# Copyright 2019 Steve Kowalik # +# Copyright 2019 Wan Liuyang # +# Copyright 2020 Steve Kowalik # +# Copyright 2021 Mark Walker # +# Copyright 2021 Steve Kowalik # +# Copyright 2023 Enrico Minack # +# Copyright 2023 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2023 Trim21 # +# Copyright 2024 Enrico Minack # +# Copyright 2024 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from __future__ import annotations + +from datetime import datetime +from typing import Any + +import github.GithubObject +import github.NamedUser +from github.GithubObject import Attribute, CompletableGithubObject, NotSet + + +class DiscussionBase(CompletableGithubObject): + """ + This class represents a the shared attributes between RepositoryDiscussion and TeamDiscussion + https://docs.github.com/en/graphql/reference/objects#discussion + https://docs.github.com/en/rest/reference/teams#discussions + """ + + def _initAttributes(self) -> None: + self._author: Attribute[github.NamedUser.NamedUser | None] = NotSet + self._body: Attribute[str] = NotSet + self._body_html: Attribute[str] = NotSet + self._created_at: Attribute[datetime] = NotSet + self._last_edited_at: Attribute[datetime] = NotSet + self._number: Attribute[int] = NotSet + self._title: Attribute[str] = NotSet + self._updated_at: Attribute[datetime] = NotSet + self._url: Attribute[str] = NotSet + + def __repr__(self) -> str: + return self.get__repr__({"number": self._number.value, "title": self._title.value}) + + @property + def author(self) -> github.NamedUser.NamedUser | None: + self._completeIfNotSet(self._author) + return self._author.value + + @property + def body(self) -> str: + self._completeIfNotSet(self._body) + return self._body.value + + @property + def body_html(self) -> str: + self._completeIfNotSet(self._body_html) + return self._body_html.value + + @property + def created_at(self) -> datetime: + self._completeIfNotSet(self._created_at) + return self._created_at.value + + @property + def last_edited_at(self) -> datetime: + self._completeIfNotSet(self._last_edited_at) + return self._last_edited_at.value + + @property + def number(self) -> int: + self._completeIfNotSet(self._number) + return self._number.value + + @property + def title(self) -> str: + self._completeIfNotSet(self._title) + return self._title.value + + @property + def updated_at(self) -> datetime: + self._completeIfNotSet(self._updated_at) + return self._updated_at.value + + @property + def url(self) -> str: + self._completeIfNotSet(self._url) + return self._url.value + + def _useAttributes(self, attributes: dict[str, Any]) -> None: + if "author" in attributes: # pragma no branch + self._author = self._makeClassAttribute(github.NamedUser.NamedUser, attributes["author"]) + if "body" in attributes: # pragma no branch + self._body = self._makeStringAttribute(attributes["body"]) + if "body_html" in attributes: # pragma no branch + self._body_html = self._makeStringAttribute(attributes["body_html"]) + if "created_at" in attributes: # pragma no branch + self._created_at = self._makeDatetimeAttribute(attributes["created_at"]) + if "last_edited_at" in attributes: # pragma no branch + self._last_edited_at = self._makeDatetimeAttribute(attributes["last_edited_at"]) + if "number" in attributes: # pragma no branch + self._number = self._makeIntAttribute(attributes["number"]) + if "title" in attributes: + self._title = self._makeStringAttribute(attributes["title"]) + if "updated_at" in attributes: # pragma no branch + self._updated_at = self._makeDatetimeAttribute(attributes["updated_at"]) + if "url" in attributes: # pragma no branch + self._url = self._makeStringAttribute(attributes["url"]) diff --git a/venv/lib/python3.10/site-packages/github/DiscussionCommentBase.py b/venv/lib/python3.10/site-packages/github/DiscussionCommentBase.py new file mode 100644 index 0000000000000000000000000000000000000000..2db3783753856fb73bbbfcc1c276fa4df6077113 --- /dev/null +++ b/venv/lib/python3.10/site-packages/github/DiscussionCommentBase.py @@ -0,0 +1,135 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2012 Vincent Jacques # +# Copyright 2012 Zearin # +# Copyright 2013 AKFish # +# Copyright 2013 Vincent Jacques # +# Copyright 2014 Vincent Jacques # +# Copyright 2016 Jannis Gebauer # +# Copyright 2016 Peter Buckley # +# Copyright 2018 Wan Liuyang # +# Copyright 2018 sfdye # +# Copyright 2019 Steve Kowalik # +# Copyright 2019 Wan Liuyang # +# Copyright 2020 Steve Kowalik # +# Copyright 2021 Mark Walker # +# Copyright 2021 Steve Kowalik # +# Copyright 2023 Enrico Minack # +# Copyright 2023 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2023 Trim21 # +# Copyright 2024 Enrico Minack # +# Copyright 2024 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from __future__ import annotations + +from datetime import datetime +from typing import Any + +import github.GithubObject +import github.NamedUser +from github.GithubObject import Attribute, CompletableGithubObject, NotSet + + +class DiscussionCommentBase(CompletableGithubObject): + """ + This class represents a the shared attributes between RepositoryDiscussionComment and TeamDiscussionComment + https://docs.github.com/en/graphql/reference/objects#discussioncomment + https://docs.github.com/de/rest/teams/discussion-comments + """ + + def _initAttributes(self) -> None: + self._author: Attribute[github.NamedUser.NamedUser | None] = NotSet + self._body: Attribute[str] = NotSet + self._body_html: Attribute[str] = NotSet + self._created_at: Attribute[datetime] = NotSet + self._html_url: Attribute[str] = NotSet + self._last_edited_at: Attribute[datetime] = NotSet + self._node_id: Attribute[str] = NotSet + self._updated_at: Attribute[datetime] = NotSet + self._url: Attribute[str] = NotSet + + def __repr__(self) -> str: + return self.get__repr__({"node_id": self._node_id.value}) + + @property + def author(self) -> github.NamedUser.NamedUser | None: + self._completeIfNotSet(self._author) + return self._author.value + + @property + def body(self) -> str: + self._completeIfNotSet(self._body) + return self._body.value + + @property + def body_html(self) -> str: + self._completeIfNotSet(self._body_html) + return self._body_html.value + + @property + def created_at(self) -> datetime: + self._completeIfNotSet(self._created_at) + return self._created_at.value + + @property + def html_url(self) -> str: + self._completeIfNotSet(self._html_url) + return self._html_url.value + + @property + def last_edited_at(self) -> datetime: + self._completeIfNotSet(self._last_edited_at) + return self._last_edited_at.value + + @property + def node_id(self) -> str: + self._completeIfNotSet(self._node_id) + return self._node_id.value + + @property + def updated_at(self) -> datetime: + self._completeIfNotSet(self._updated_at) + return self._updated_at.value + + @property + def url(self) -> str: + self._completeIfNotSet(self._url) + return self._url.value + + def _useAttributes(self, attributes: dict[str, Any]) -> None: + if "author" in attributes: # pragma no branch + self._author = self._makeClassAttribute(github.NamedUser.NamedUser, attributes["author"]) + if "body" in attributes: # pragma no branch + self._body = self._makeStringAttribute(attributes["body"]) + if "body_html" in attributes: # pragma no branch + self._body_html = self._makeStringAttribute(attributes["body_html"]) + if "created_at" in attributes: # pragma no branch + self._created_at = self._makeDatetimeAttribute(attributes["created_at"]) + if "html_url" in attributes: # pragma no branch + self._html_url = self._makeStringAttribute(attributes["html_url"]) + if "last_edited_at" in attributes: # pragma no branch + self._last_edited_at = self._makeDatetimeAttribute(attributes["last_edited_at"]) + if "node_id" in attributes: # pragma no branch + self._node_id = self._makeStringAttribute(attributes["node_id"]) + if "updated_at" in attributes: # pragma no branch + self._updated_at = self._makeDatetimeAttribute(attributes["updated_at"]) + if "url" in attributes: # pragma no branch + self._url = self._makeStringAttribute(attributes["url"]) diff --git a/venv/lib/python3.10/site-packages/github/Download.py b/venv/lib/python3.10/site-packages/github/Download.py new file mode 100644 index 0000000000000000000000000000000000000000..7249737e49fb92c912b34e223d8cebb3772c087d --- /dev/null +++ b/venv/lib/python3.10/site-packages/github/Download.py @@ -0,0 +1,249 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2012 Vincent Jacques # +# Copyright 2012 Zearin # +# Copyright 2013 AKFish # +# Copyright 2013 Vincent Jacques # +# Copyright 2014 Vincent Jacques # +# Copyright 2016 Jannis Gebauer # +# Copyright 2016 Peter Buckley # +# Copyright 2018 Wan Liuyang # +# Copyright 2018 sfdye # +# Copyright 2019 Steve Kowalik # +# Copyright 2019 Wan Liuyang # +# Copyright 2020 Steve Kowalik # +# Copyright 2021 Steve Kowalik # +# Copyright 2023 Enrico Minack # +# Copyright 2023 Trim21 # +# Copyright 2024 Enrico Minack # +# Copyright 2024 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from __future__ import annotations + +from datetime import datetime +from typing import Any + +from github.GithubObject import Attribute, CompletableGithubObject, NotSet + + +class Download(CompletableGithubObject): + """ + This class represents Downloads. + + The reference can be found here + https://docs.github.com/en/rest/reference/repos + + """ + + def _initAttributes(self) -> None: + self._accesskeyid: Attribute[str] = NotSet + self._acl: Attribute[str] = NotSet + self._bucket: Attribute[str] = NotSet + self._content_type: Attribute[str] = NotSet + self._created_at: Attribute[datetime] = NotSet + self._description: Attribute[str] = NotSet + self._download_count: Attribute[int] = NotSet + self._expirationdate: Attribute[datetime] = NotSet + self._html_url: Attribute[str] = NotSet + self._id: Attribute[int] = NotSet + self._mime_type: Attribute[str] = NotSet + self._name: Attribute[str] = NotSet + self._path: Attribute[str] = NotSet + self._policy: Attribute[str] = NotSet + self._prefix: Attribute[str] = NotSet + self._redirect: Attribute[bool] = NotSet + self._s3_url: Attribute[str] = NotSet + self._signature: Attribute[str] = NotSet + self._size: Attribute[int] = NotSet + self._url: Attribute[str] = NotSet + + def __repr__(self) -> str: + return self.get__repr__({"id": self._id.value}) + + @property + def accesskeyid(self) -> str: + self._completeIfNotSet(self._accesskeyid) + return self._accesskeyid.value + + @property + def acl(self) -> str: + self._completeIfNotSet(self._acl) + return self._acl.value + + @property + def bucket(self) -> str: + self._completeIfNotSet(self._bucket) + return self._bucket.value + + @property + def content_type(self) -> str: + self._completeIfNotSet(self._content_type) + return self._content_type.value + + @property + def created_at(self) -> datetime: + self._completeIfNotSet(self._created_at) + return self._created_at.value + + @property + def description(self) -> str: + self._completeIfNotSet(self._description) + return self._description.value + + @property + def download_count(self) -> int: + self._completeIfNotSet(self._download_count) + return self._download_count.value + + @property + def expirationdate(self) -> datetime: + self._completeIfNotSet(self._expirationdate) + return self._expirationdate.value + + @property + def html_url(self) -> str: + self._completeIfNotSet(self._html_url) + return self._html_url.value + + @property + def id(self) -> int: + self._completeIfNotSet(self._id) + return self._id.value + + @property + def mime_type(self) -> str: + self._completeIfNotSet(self._mime_type) + return self._mime_type.value + + @property + def name(self) -> str: + self._completeIfNotSet(self._name) + return self._name.value + + @property + def path(self) -> str: + self._completeIfNotSet(self._path) + return self._path.value + + @property + def policy(self) -> str: + self._completeIfNotSet(self._policy) + return self._policy.value + + @property + def prefix(self) -> str: + self._completeIfNotSet(self._prefix) + return self._prefix.value + + @property + def redirect(self) -> bool: + self._completeIfNotSet(self._redirect) + return self._redirect.value + + @property + def s3_url(self) -> str: + self._completeIfNotSet(self._s3_url) + return self._s3_url.value + + @property + def signature(self) -> str: + self._completeIfNotSet(self._signature) + return self._signature.value + + @property + def size(self) -> int: + self._completeIfNotSet(self._size) + return self._size.value + + @property + def url(self) -> str: + self._completeIfNotSet(self._url) + return self._url.value + + def delete(self) -> None: + """ + :calls: `DELETE /repos/{owner}/{repo}/downloads/{id} `_ + """ + headers, data = self._requester.requestJsonAndCheck("DELETE", self.url) + + def _useAttributes(self, attributes: dict[str, Any]) -> None: + if "accesskeyid" in attributes: # pragma no branch + self._accesskeyid = self._makeStringAttribute( + attributes["accesskeyid"] + ) # pragma no cover (was covered only by create_download, which has been removed) + if "acl" in attributes: # pragma no branch + self._acl = self._makeStringAttribute( + attributes["acl"] + ) # pragma no cover (was covered only by create_download, which has been removed) + if "bucket" in attributes: # pragma no branch + self._bucket = self._makeStringAttribute( + attributes["bucket"] + ) # pragma no cover (was covered only by create_download, which has been removed) + if "content_type" in attributes: # pragma no branch + self._content_type = self._makeStringAttribute(attributes["content_type"]) + if "created_at" in attributes: # pragma no branch + self._created_at = self._makeDatetimeAttribute(attributes["created_at"]) + if "description" in attributes: # pragma no branch + self._description = self._makeStringAttribute(attributes["description"]) + if "download_count" in attributes: # pragma no branch + self._download_count = self._makeIntAttribute(attributes["download_count"]) + if "expirationdate" in attributes: # pragma no branch + self._expirationdate = self._makeDatetimeAttribute( + attributes["expirationdate"] + ) # pragma no cover (was covered only by create_download, which has been removed) + if "html_url" in attributes: # pragma no branch + self._html_url = self._makeStringAttribute(attributes["html_url"]) + if "id" in attributes: # pragma no branch + self._id = self._makeIntAttribute(attributes["id"]) + if "mime_type" in attributes: # pragma no branch + self._mime_type = self._makeStringAttribute( + attributes["mime_type"] + ) # pragma no cover (was covered only by create_download, which has been removed) + if "name" in attributes: # pragma no branch + self._name = self._makeStringAttribute(attributes["name"]) + if "path" in attributes: # pragma no branch + self._path = self._makeStringAttribute( + attributes["path"] + ) # pragma no cover (was covered only by create_download, which has been removed) + if "policy" in attributes: # pragma no branch + self._policy = self._makeStringAttribute( + attributes["policy"] + ) # pragma no cover (was covered only by create_download, which has been removed) + if "prefix" in attributes: # pragma no branch + self._prefix = self._makeStringAttribute( + attributes["prefix"] + ) # pragma no cover (was covered only by create_download, which has been removed) + if "redirect" in attributes: # pragma no branch + self._redirect = self._makeBoolAttribute( + attributes["redirect"] + ) # pragma no cover (was covered only by create_download, which has been removed) + if "s3_url" in attributes: # pragma no branch + self._s3_url = self._makeStringAttribute( + attributes["s3_url"] + ) # pragma no cover (was covered only by create_download, which has been removed) + if "signature" in attributes: # pragma no branch + self._signature = self._makeStringAttribute( + attributes["signature"] + ) # pragma no cover (was covered only by create_download, which has been removed) + if "size" in attributes: # pragma no branch + self._size = self._makeIntAttribute(attributes["size"]) + if "url" in attributes: # pragma no branch + self._url = self._makeStringAttribute(attributes["url"]) diff --git a/venv/lib/python3.10/site-packages/github/Enterprise.py b/venv/lib/python3.10/site-packages/github/Enterprise.py new file mode 100644 index 0000000000000000000000000000000000000000..46a382dcb364a39dfd51036b0b41c16f62718b74 --- /dev/null +++ b/venv/lib/python3.10/site-packages/github/Enterprise.py @@ -0,0 +1,96 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2012 Vincent Jacques # +# Copyright 2012 Zearin # +# Copyright 2013 AKFish # +# Copyright 2013 Vincent Jacques # +# Copyright 2014 Vincent Jacques # +# Copyright 2016 Jannis Gebauer # +# Copyright 2016 Peter Buckley # +# Copyright 2018 Wan Liuyang # +# Copyright 2018 sfdye # +# Copyright 2019 Steve Kowalik # +# Copyright 2019 Wan Liuyang # +# Copyright 2020 Steve Kowalik # +# Copyright 2023 Enrico Minack # +# Copyright 2023 Mark Amery # +# Copyright 2023 Trim21 # +# Copyright 2023 YugoHino # +# Copyright 2024 Enrico Minack # +# Copyright 2024 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2025 Enrico Minack # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +import urllib.parse +from typing import Any, Dict + +from github.EnterpriseConsumedLicenses import EnterpriseConsumedLicenses +from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet +from github.Requester import Requester + + +class Enterprise(NonCompletableGithubObject): + """ + This class represents Enterprises. + + Such objects do not exist in the Github API, so this class merely collects all endpoints the start with + /enterprises/{enterprise}/. See methods below for specific endpoints and docs. + https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin?apiVersion=2022-11-28 + + """ + + def _initAttributes(self) -> None: + self._enterprise: Attribute[str] = NotSet + self._url: Attribute[str] = NotSet + + def __init__( + self, + requester: Requester, + enterprise: str, + ): + enterprise = urllib.parse.quote(enterprise) + super().__init__(requester, {}, {"enterprise": enterprise, "url": f"/enterprises/{enterprise}"}) + + def __repr__(self) -> str: + return self.get__repr__({"enterprise": self._enterprise.value}) + + @property + def enterprise(self) -> str: + return self._enterprise.value + + @property + def url(self) -> str: + return self._url.value + + def get_consumed_licenses(self) -> EnterpriseConsumedLicenses: + """ + :calls: `GET /enterprises/{enterprise}/consumed-licenses `_ + """ + headers, data = self._requester.requestJsonAndCheck("GET", self.url + "/consumed-licenses") + if "url" not in data: + data["url"] = self.url + "/consumed-licenses" + + return EnterpriseConsumedLicenses(self._requester, headers, data, completed=True) + + def _useAttributes(self, attributes: Dict[str, Any]) -> None: + if "enterprise" in attributes: # pragma no branch + self._enterprise = self._makeStringAttribute(attributes["enterprise"]) + if "url" in attributes: # pragma no branch + self._url = self._makeStringAttribute(attributes["url"]) diff --git a/venv/lib/python3.10/site-packages/github/EnterpriseConsumedLicenses.py b/venv/lib/python3.10/site-packages/github/EnterpriseConsumedLicenses.py new file mode 100644 index 0000000000000000000000000000000000000000..f03e9f10c543d6f85dfcb9289ad195233f0eefaa --- /dev/null +++ b/venv/lib/python3.10/site-packages/github/EnterpriseConsumedLicenses.py @@ -0,0 +1,109 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2012 Vincent Jacques # +# Copyright 2012 Zearin # +# Copyright 2013 AKFish # +# Copyright 2013 Vincent Jacques # +# Copyright 2014 Vincent Jacques # +# Copyright 2016 Jannis Gebauer # +# Copyright 2016 Peter Buckley # +# Copyright 2018 Wan Liuyang # +# Copyright 2018 sfdye # +# Copyright 2019 Steve Kowalik # +# Copyright 2019 Wan Liuyang # +# Copyright 2020 Steve Kowalik # +# Copyright 2021 Steve Kowalik # +# Copyright 2023 Enrico Minack # +# Copyright 2023 Trim21 # +# Copyright 2023 YugoHino # +# Copyright 2024 Enrico Minack # +# Copyright 2024 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2025 Enrico Minack # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from typing import Any, Dict + +from github.GithubObject import Attribute, CompletableGithubObject, NotSet +from github.NamedEnterpriseUser import NamedEnterpriseUser +from github.PaginatedList import PaginatedList + + +class EnterpriseConsumedLicenses(CompletableGithubObject): + """ + This class represents license consumed by enterprises. + + The reference can be found here + https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/license#list-enterprise-consumed-licenses + + """ + + def _initAttributes(self) -> None: + self._enterprise: Attribute[str] = NotSet + self._total_seats_consumed: Attribute[int] = NotSet + self._total_seats_purchased: Attribute[int] = NotSet + self._url: Attribute[str] = NotSet + + def __repr__(self) -> str: + return self.get__repr__({"enterprise": self._enterprise.value}) + + @property + def enterprise(self) -> str: + self._completeIfNotSet(self._enterprise) + return self._enterprise.value + + @property + def total_seats_consumed(self) -> int: + return self._total_seats_consumed.value + + @property + def total_seats_purchased(self) -> int: + return self._total_seats_purchased.value + + @property + def url(self) -> str: + self._completeIfNotSet(self._url) + return self._url.value + + def get_users(self) -> PaginatedList[NamedEnterpriseUser]: + """ + :calls: `GET /enterprises/{enterprise}/consumed-licenses `_ + """ + + url_parameters: Dict[str, Any] = {} + return PaginatedList( + NamedEnterpriseUser, + self._requester, + self.url, + url_parameters, + headers=None, + list_item="users", + firstData=self.raw_data, + firstHeaders=self.raw_headers, + ) + + def _useAttributes(self, attributes: Dict[str, Any]) -> None: + if "enterprise" in attributes: # pragma no branch + self._enterprise = self._makeStringAttribute(attributes["enterprise"]) + if "total_seats_consumed" in attributes: # pragma no branch + self._total_seats_consumed = self._makeIntAttribute(attributes["total_seats_consumed"]) + if "total_seats_purchased" in attributes: # pragma no branch + self._total_seats_purchased = self._makeIntAttribute(attributes["total_seats_purchased"]) + if "url" in attributes: # pragma no branch + self._url = self._makeStringAttribute(attributes["url"]) diff --git a/venv/lib/python3.10/site-packages/github/Environment.py b/venv/lib/python3.10/site-packages/github/Environment.py new file mode 100644 index 0000000000000000000000000000000000000000..bf52029ae0347564c810f1c526498f9aa41e8211 --- /dev/null +++ b/venv/lib/python3.10/site-packages/github/Environment.py @@ -0,0 +1,299 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2012 Vincent Jacques # +# Copyright 2012 Zearin # +# Copyright 2013 AKFish # +# Copyright 2013 Vincent Jacques # +# Copyright 2014 Vincent Jacques # +# Copyright 2016 Jannis Gebauer # +# Copyright 2016 Peter Buckley # +# Copyright 2017 Jannis Gebauer # +# Copyright 2017 Simon # +# Copyright 2018 sfdye # +# Copyright 2019 Steve Kowalik # +# Copyright 2019 Wan Liuyang # +# Copyright 2020 Steve Kowalik # +# Copyright 2021 Steve Kowalik # +# Copyright 2023 Andrew Dawes <53574062+AndrewJDawes@users.noreply.github.com> # +# Copyright 2023 Enrico Minack # +# Copyright 2023 Trim21 # +# Copyright 2023 alson # +# Copyright 2024 Enrico Minack # +# Copyright 2024 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2025 Enrico Minack # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from __future__ import annotations + +from datetime import datetime +from typing import TYPE_CHECKING, Any + +import github.EnvironmentDeploymentBranchPolicy +import github.EnvironmentProtectionRule +from github.GithubObject import Attribute, CompletableGithubObject, NotSet +from github.PaginatedList import PaginatedList +from github.PublicKey import PublicKey +from github.Secret import Secret +from github.Variable import Variable + +if TYPE_CHECKING: + from github.EnvironmentDeploymentBranchPolicy import EnvironmentDeploymentBranchPolicy + from github.EnvironmentProtectionRule import EnvironmentProtectionRule + + +class Environment(CompletableGithubObject): + """ + This class represents Environment. + + The reference can be found here + https://docs.github.com/en/rest/reference/deployments#environments + + """ + + def _initAttributes(self) -> None: + self._created_at: Attribute[datetime] = NotSet + self._deployment_branch_policy: Attribute[EnvironmentDeploymentBranchPolicy] = NotSet + self._environments_url: Attribute[str] = NotSet + self._html_url: Attribute[str] = NotSet + self._id: Attribute[int] = NotSet + self._name: Attribute[str] = NotSet + self._node_id: Attribute[str] = NotSet + self._protection_rules: Attribute[list[EnvironmentProtectionRule]] = NotSet + self._updated_at: Attribute[datetime] = NotSet + self._url: Attribute[str] = NotSet + + def __repr__(self) -> str: + return self.get__repr__({"name": self._name.value}) + + @property + def created_at(self) -> datetime: + self._completeIfNotSet(self._created_at) + return self._created_at.value + + @property + def deployment_branch_policy( + self, + ) -> EnvironmentDeploymentBranchPolicy: + self._completeIfNotSet(self._deployment_branch_policy) + return self._deployment_branch_policy.value + + @property + def environments_url(self) -> str: + """ + :type: string + """ + return self._environments_url.value + + @property + def html_url(self) -> str: + self._completeIfNotSet(self._html_url) + return self._html_url.value + + @property + def id(self) -> int: + self._completeIfNotSet(self._id) + return self._id.value + + @property + def name(self) -> str: + self._completeIfNotSet(self._name) + return self._name.value + + @property + def node_id(self) -> str: + self._completeIfNotSet(self._node_id) + return self._node_id.value + + @property + def protection_rules( + self, + ) -> list[EnvironmentProtectionRule]: + self._completeIfNotSet(self._protection_rules) + return self._protection_rules.value + + @property + def updated_at(self) -> datetime: + self._completeIfNotSet(self._updated_at) + return self._updated_at.value + + @property + def url(self) -> str: + """ + :type: string + """ + # Construct url from environments_url and name, if self._url. is not set + if self._url is NotSet: + self._url = self._makeStringAttribute(self.environments_url + "/" + self.name) + return self._url.value + + def get_public_key(self) -> PublicKey: + """ + :calls: `GET /repositories/{repository_id}/environments/{environment_name}/secrets/public-key `_ + :rtype: :class:`PublicKey` + """ + # https://stackoverflow.com/a/76474814 + # https://docs.github.com/en/rest/secrets?apiVersion=2022-11-28#get-an-environment-public-key + headers, data = self._requester.requestJsonAndCheck("GET", f"{self.url}/secrets/public-key") + return PublicKey(self._requester, headers, data, completed=True) + + def create_secret(self, secret_name: str, unencrypted_value: str) -> Secret: + """ + :calls: `PUT /repositories/{repository_id}/environments/{environment_name}/secrets/{secret_name} `_ + """ + assert isinstance(secret_name, str), secret_name + assert isinstance(unencrypted_value, str), unencrypted_value + public_key = self.get_public_key() + payload = public_key.encrypt(unencrypted_value) + put_parameters = { + "key_id": public_key.key_id, + "encrypted_value": payload, + } + self._requester.requestJsonAndCheck("PUT", f"{self.url}/secrets/{secret_name}", input=put_parameters) + return Secret( + requester=self._requester, + headers={}, + attributes={ + "name": secret_name, + "url": f"{self.url}/secrets/{secret_name}", + }, + completed=False, + ) + + def get_secrets(self) -> PaginatedList[Secret]: + """ + Gets all repository secrets. + """ + return PaginatedList( + Secret, + self._requester, + f"{self.url}/secrets", + None, + attributesTransformer=PaginatedList.override_attributes({"secrets_url": f"{self.url}/secrets"}), + list_item="secrets", + ) + + def get_secret(self, secret_name: str) -> Secret: + """ + :calls: 'GET /repositories/{repository_id}/environments/{environment_name}/secrets/{secret_name} `_ + """ + assert isinstance(secret_name, str), secret_name + return Secret( + requester=self._requester, + headers={}, + attributes={"url": f"{self.url}/secrets/{secret_name}"}, + completed=False, + ) + + def create_variable(self, variable_name: str, value: str) -> Variable: + """ + :calls: `POST /repositories/{repository_id}/environments/{environment_name}/variables/{variable_name} `_ + """ + assert isinstance(variable_name, str), variable_name + assert isinstance(value, str), value + post_parameters = { + "name": variable_name, + "value": value, + } + self._requester.requestJsonAndCheck("POST", f"{self.url}/variables", input=post_parameters) + return Variable( + self._requester, + headers={}, + attributes={ + "name": variable_name, + "value": value, + "url": f"{self.url}/variables/{variable_name}", + }, + completed=False, + ) + + def get_variables(self) -> PaginatedList[Variable]: + """ + Gets all repository variables :rtype: :class:`PaginatedList` of :class:`Variable` + """ + return PaginatedList( + Variable, + self._requester, + f"{self.url}/variables", + None, + attributesTransformer=PaginatedList.override_attributes({"variables_url": f"{self.url}/variables"}), + list_item="variables", + ) + + def get_variable(self, variable_name: str) -> Variable: + """ + :calls: 'GET /orgs/{org}/variables/{variable_name} `_ + :param variable_name: string + :rtype: Variable + """ + assert isinstance(variable_name, str), variable_name + return Variable( + requester=self._requester, + headers={}, + attributes={"url": f"{self.url}/variables/{variable_name}"}, + completed=False, + ) + + def delete_secret(self, secret_name: str) -> bool: + """ + :calls: `DELETE /repositories/{repository_id}/environments/{environment_name}/secrets/{secret_name} `_ + :param secret_name: string + :rtype: bool + """ + assert isinstance(secret_name, str), secret_name + status, headers, data = self._requester.requestJson("DELETE", f"{self.url}/secrets/{secret_name}") + return status == 204 + + def delete_variable(self, variable_name: str) -> bool: + """ + :calls: `DELETE /repositories/{repository_id}/environments/{environment_name}/variables/{variable_name} `_ + :param variable_name: string + :rtype: bool + """ + assert isinstance(variable_name, str), variable_name + status, headers, data = self._requester.requestJson("DELETE", f"{self.url}/variables/{variable_name}") + return status == 204 + + def _useAttributes(self, attributes: dict[str, Any]) -> None: + if "created_at" in attributes: # pragma no branch + self._created_at = self._makeDatetimeAttribute(attributes["created_at"]) + if "deployment_branch_policy" in attributes: # pragma no branch + self._deployment_branch_policy = self._makeClassAttribute( + github.EnvironmentDeploymentBranchPolicy.EnvironmentDeploymentBranchPolicy, + attributes["deployment_branch_policy"], + ) + if "environments_url" in attributes: + self._environments_url = self._makeStringAttribute(attributes["environments_url"]) + if "html_url" in attributes: # pragma no branch + self._html_url = self._makeStringAttribute(attributes["html_url"]) + if "id" in attributes: # pragma no branch + self._id = self._makeIntAttribute(attributes["id"]) + if "name" in attributes: # pragma no branch + self._name = self._makeStringAttribute(attributes["name"]) + if "node_id" in attributes: # pragma no branch + self._node_id = self._makeStringAttribute(attributes["node_id"]) + if "protection_rules" in attributes: # pragma no branch + self._protection_rules = self._makeListOfClassesAttribute( + github.EnvironmentProtectionRule.EnvironmentProtectionRule, + attributes["protection_rules"], + ) + if "updated_at" in attributes: # pragma no branch + self._updated_at = self._makeDatetimeAttribute(attributes["updated_at"]) + if "url" in attributes: # pragma no branch + self._url = self._makeStringAttribute(attributes["url"]) diff --git a/venv/lib/python3.10/site-packages/github/EnvironmentDeploymentBranchPolicy.py b/venv/lib/python3.10/site-packages/github/EnvironmentDeploymentBranchPolicy.py new file mode 100644 index 0000000000000000000000000000000000000000..55bade613274254511202424cb547909a867d937 --- /dev/null +++ b/venv/lib/python3.10/site-packages/github/EnvironmentDeploymentBranchPolicy.py @@ -0,0 +1,80 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2023 Enrico Minack # +# Copyright 2023 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2023 Trim21 # +# Copyright 2023 alson # +# Copyright 2024 Enrico Minack # +# Copyright 2024 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2025 Enrico Minack # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from typing import Any, Dict + +from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet + + +class EnvironmentDeploymentBranchPolicy(NonCompletableGithubObject): + """ + This class represents a deployment branch policy for an environment. + + The reference can be found here + https://docs.github.com/en/rest/reference/deployments#environments + + """ + + def _initAttributes(self) -> None: + self._custom_branch_policies: Attribute[bool] = NotSet + self._protected_branches: Attribute[bool] = NotSet + + def __repr__(self) -> str: + return self.get__repr__({}) + + @property + def custom_branch_policies(self) -> bool: + return self._custom_branch_policies.value + + @property + def protected_branches(self) -> bool: + return self._protected_branches.value + + def _useAttributes(self, attributes: Dict[str, Any]) -> None: + if "custom_branch_policies" in attributes: # pragma no branch + self._custom_branch_policies = self._makeBoolAttribute(attributes["custom_branch_policies"]) + if "protected_branches" in attributes: # pragma no branch + self._protected_branches = self._makeBoolAttribute(attributes["protected_branches"]) + + +class EnvironmentDeploymentBranchPolicyParams: + """ + This class presents the deployment branch policy parameters as can be configured for an Environment. + """ + + def __init__(self, protected_branches: bool = False, custom_branch_policies: bool = False): + assert isinstance(protected_branches, bool) + assert isinstance(custom_branch_policies, bool) + self.protected_branches = protected_branches + self.custom_branch_policies = custom_branch_policies + + def _asdict(self) -> dict: + return { + "protected_branches": self.protected_branches, + "custom_branch_policies": self.custom_branch_policies, + } diff --git a/venv/lib/python3.10/site-packages/github/EnvironmentProtectionRule.py b/venv/lib/python3.10/site-packages/github/EnvironmentProtectionRule.py new file mode 100644 index 0000000000000000000000000000000000000000..d11f1d5400859e9831f81e828ea72d2f1d205dc5 --- /dev/null +++ b/venv/lib/python3.10/site-packages/github/EnvironmentProtectionRule.py @@ -0,0 +1,108 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2012 Vincent Jacques # +# Copyright 2012 Zearin # +# Copyright 2013 Vincent Jacques # +# Copyright 2014 Vincent Jacques # +# Copyright 2016 Peter Buckley # +# Copyright 2018 Wan Liuyang # +# Copyright 2018 sfdye # +# Copyright 2019 Adam Baratz # +# Copyright 2019 Nick Campbell # +# Copyright 2019 Steve Kowalik # +# Copyright 2019 Wan Liuyang # +# Copyright 2020 Steve Kowalik # +# Copyright 2021 Steve Kowalik # +# Copyright 2022 Marco Köpcke # +# Copyright 2023 Enrico Minack # +# Copyright 2023 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2023 Trim21 # +# Copyright 2023 alson # +# Copyright 2024 Enrico Minack # +# Copyright 2024 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2025 Enrico Minack # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +import github.EnvironmentProtectionRuleReviewer +from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet + +if TYPE_CHECKING: + from github.EnvironmentProtectionRuleReviewer import EnvironmentProtectionRuleReviewer + + +class EnvironmentProtectionRule(NonCompletableGithubObject): + """ + This class represents a protection rule for an environment. + + The reference can be found here + https://docs.github.com/en/rest/reference/deployments#environments + + """ + + def _initAttributes(self) -> None: + self._id: Attribute[int] = NotSet + self._node_id: Attribute[str] = NotSet + self._reviewers: Attribute[list[EnvironmentProtectionRuleReviewer]] = NotSet + self._type: Attribute[str] = NotSet + self._wait_timer: Attribute[int] = NotSet + + def __repr__(self) -> str: + return self.get__repr__({"id": self._id.value}) + + @property + def id(self) -> int: + return self._id.value + + @property + def node_id(self) -> str: + return self._node_id.value + + @property + def reviewers( + self, + ) -> list[EnvironmentProtectionRuleReviewer]: + return self._reviewers.value + + @property + def type(self) -> str: + return self._type.value + + @property + def wait_timer(self) -> int: + return self._wait_timer.value + + def _useAttributes(self, attributes: dict[str, Any]) -> None: + if "id" in attributes: # pragma no branch + self._id = self._makeIntAttribute(attributes["id"]) + if "node_id" in attributes: # pragma no branch + self._node_id = self._makeStringAttribute(attributes["node_id"]) + if "reviewers" in attributes: # pragma no branch + self._reviewers = self._makeListOfClassesAttribute( + github.EnvironmentProtectionRuleReviewer.EnvironmentProtectionRuleReviewer, + attributes["reviewers"], + ) + if "type" in attributes: # pragma no branch + self._type = self._makeStringAttribute(attributes["type"]) + if "wait_timer" in attributes: # pragma no branch + self._wait_timer = self._makeIntAttribute(attributes["wait_timer"]) diff --git a/venv/lib/python3.10/site-packages/github/EnvironmentProtectionRuleReviewer.py b/venv/lib/python3.10/site-packages/github/EnvironmentProtectionRuleReviewer.py new file mode 100644 index 0000000000000000000000000000000000000000..6f2fafa73f4c747b34e5c382904c32370e28ae97 --- /dev/null +++ b/venv/lib/python3.10/site-packages/github/EnvironmentProtectionRuleReviewer.py @@ -0,0 +1,101 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2012 Vincent Jacques # +# Copyright 2012 Zearin # +# Copyright 2013 Vincent Jacques # +# Copyright 2014 Vincent Jacques # +# Copyright 2016 Peter Buckley # +# Copyright 2018 Wan Liuyang # +# Copyright 2018 sfdye # +# Copyright 2019 Adam Baratz # +# Copyright 2019 Nick Campbell # +# Copyright 2019 Steve Kowalik # +# Copyright 2019 Wan Liuyang # +# Copyright 2020 Steve Kowalik # +# Copyright 2021 Steve Kowalik # +# Copyright 2023 Enrico Minack # +# Copyright 2023 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2023 Trim21 # +# Copyright 2023 alson # +# Copyright 2024 Enrico Minack # +# Copyright 2024 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2025 Enrico Minack # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from __future__ import annotations + +from typing import Any + +import github.NamedUser +import github.Team +from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet + + +class EnvironmentProtectionRuleReviewer(NonCompletableGithubObject): + """ + This class represents a reviewer for an EnvironmentProtectionRule. + + The reference can be found here + https://docs.github.com/en/rest/reference/deployments#environments + + """ + + def _initAttributes(self) -> None: + self._reviewer: Attribute[github.NamedUser.NamedUser | github.Team.Team] = NotSet + self._type: Attribute[str] = NotSet + + def __repr__(self) -> str: + return self.get__repr__({"type": self._type.value}) + + @property + def reviewer(self) -> github.NamedUser.NamedUser | github.Team.Team: + return self._reviewer.value + + @property + def type(self) -> str: + return self._type.value + + def _useAttributes(self, attributes: dict[str, Any]) -> None: + if "reviewer" in attributes and "type" in attributes: # pragma no branch + assert attributes["type"] in ("User", "Team") + if attributes["type"] == "User": + self._reviewer = self._makeClassAttribute(github.NamedUser.NamedUser, attributes["reviewer"]) + elif attributes["type"] == "Team": + self._reviewer = self._makeClassAttribute(github.Team.Team, attributes["reviewer"]) + if "type" in attributes: # pragma no branch + self._type = self._makeStringAttribute(attributes["type"]) + + +class ReviewerParams: + """ + This class presents reviewers as can be configured for an Environment. + """ + + def __init__(self, type_: str, id_: int): + assert isinstance(type_, str) and type_ in ("User", "Team") + assert isinstance(id_, int) + self.type = type_ + self.id = id_ + + def _asdict(self) -> dict: + return { + "type": self.type, + "id": self.id, + } diff --git a/venv/lib/python3.10/site-packages/github/Event.py b/venv/lib/python3.10/site-packages/github/Event.py new file mode 100644 index 0000000000000000000000000000000000000000..6ff871277e80a55254cfbe87d52540cf39a7b2dc --- /dev/null +++ b/venv/lib/python3.10/site-packages/github/Event.py @@ -0,0 +1,126 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2012 Vincent Jacques # +# Copyright 2012 Zearin # +# Copyright 2013 AKFish # +# Copyright 2013 Vincent Jacques # +# Copyright 2013 martinqt # +# Copyright 2014 Vincent Jacques # +# Copyright 2016 Jannis Gebauer # +# Copyright 2016 Peter Buckley # +# Copyright 2018 sfdye # +# Copyright 2019 Steve Kowalik # +# Copyright 2019 Wan Liuyang # +# Copyright 2020 Steve Kowalik # +# Copyright 2021 Steve Kowalik # +# Copyright 2023 Enrico Minack # +# Copyright 2023 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2023 Trim21 # +# Copyright 2024 Enrico Minack # +# Copyright 2024 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2025 Enrico Minack # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from __future__ import annotations + +from datetime import datetime +from typing import Any + +import github.GithubObject +import github.NamedUser +import github.Organization +import github.Repository +from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet + + +class Event(NonCompletableGithubObject): + """ + This class represents Events. + + The reference can be found here + https://docs.github.com/en/rest/reference/activity#events + + The OpenAPI schema can be found at + - /components/schemas/event + + """ + + def _initAttributes(self) -> None: + self._actor: Attribute[github.NamedUser.NamedUser] = NotSet + self._created_at: Attribute[datetime] = NotSet + self._id: Attribute[str] = NotSet + self._org: Attribute[github.Organization.Organization] = NotSet + self._payload: Attribute[dict[str, Any]] = NotSet + self._public: Attribute[bool] = NotSet + self._repo: Attribute[github.Repository.Repository] = NotSet + self._type: Attribute[str] = NotSet + + def __repr__(self) -> str: + return self.get__repr__({"id": self._id.value, "type": self._type.value}) + + @property + def actor(self) -> github.NamedUser.NamedUser: + return self._actor.value + + @property + def created_at(self) -> datetime: + return self._created_at.value + + @property + def id(self) -> str: + return self._id.value + + @property + def org(self) -> github.Organization.Organization: + return self._org.value + + @property + def payload(self) -> dict[str, Any]: + return self._payload.value + + @property + def public(self) -> bool: + return self._public.value + + @property + def repo(self) -> github.Repository.Repository: + return self._repo.value + + @property + def type(self) -> str: + return self._type.value + + def _useAttributes(self, attributes: dict[str, Any]) -> None: + if "actor" in attributes: # pragma no branch + self._actor = self._makeClassAttribute(github.NamedUser.NamedUser, attributes["actor"]) + if "created_at" in attributes: # pragma no branch + self._created_at = self._makeDatetimeAttribute(attributes["created_at"]) + if "id" in attributes: # pragma no branch + self._id = self._makeStringAttribute(attributes["id"]) + if "org" in attributes: # pragma no branch + self._org = self._makeClassAttribute(github.Organization.Organization, attributes["org"]) + if "payload" in attributes: # pragma no branch + self._payload = self._makeDictAttribute(attributes["payload"]) + if "public" in attributes: # pragma no branch + self._public = self._makeBoolAttribute(attributes["public"]) + if "repo" in attributes: # pragma no branch + self._repo = self._makeClassAttribute(github.Repository.Repository, attributes["repo"]) + if "type" in attributes: # pragma no branch + self._type = self._makeStringAttribute(attributes["type"]) diff --git a/venv/lib/python3.10/site-packages/github/File.py b/venv/lib/python3.10/site-packages/github/File.py new file mode 100644 index 0000000000000000000000000000000000000000..aa4faff220514113035559a22aa08dcaed80b92a --- /dev/null +++ b/venv/lib/python3.10/site-packages/github/File.py @@ -0,0 +1,138 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2012 Vincent Jacques # +# Copyright 2012 Zearin # +# Copyright 2013 AKFish # +# Copyright 2013 Vincent Jacques # +# Copyright 2014 Vincent Jacques # +# Copyright 2016 Jannis Gebauer # +# Copyright 2016 Jeffrey Melvin # +# Copyright 2016 Peter Buckley # +# Copyright 2017 Simon # +# Copyright 2018 Wan Liuyang # +# Copyright 2018 sfdye # +# Copyright 2019 Steve Kowalik # +# Copyright 2019 Wan Liuyang # +# Copyright 2020 Steve Kowalik # +# Copyright 2023 Enrico Minack # +# Copyright 2023 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2023 Trim21 # +# Copyright 2024 Enrico Minack # +# Copyright 2024 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2025 Enrico Minack # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from typing import Any, Dict + +from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet + + +class File(NonCompletableGithubObject): + """ + This class represents Files. + + The OpenAPI schema can be found at + - /components/schemas/diff-entry + + """ + + def _initAttributes(self) -> None: + self._additions: Attribute[int] = NotSet + self._blob_url: Attribute[str] = NotSet + self._changes: Attribute[int] = NotSet + self._contents_url: Attribute[str] = NotSet + self._deletions: Attribute[int] = NotSet + self._filename: Attribute[str] = NotSet + self._patch: Attribute[str] = NotSet + self._previous_filename: Attribute[str] = NotSet + self._raw_url: Attribute[str] = NotSet + self._sha: Attribute[str] = NotSet + self._status: Attribute[str] = NotSet + + def __repr__(self) -> str: + return self.get__repr__({"sha": self._sha.value, "filename": self._filename.value}) + + @property + def additions(self) -> int: + return self._additions.value + + @property + def blob_url(self) -> str: + return self._blob_url.value + + @property + def changes(self) -> int: + return self._changes.value + + @property + def contents_url(self) -> str: + return self._contents_url.value + + @property + def deletions(self) -> int: + return self._deletions.value + + @property + def filename(self) -> str: + return self._filename.value + + @property + def patch(self) -> str: + return self._patch.value + + @property + def previous_filename(self) -> str: + return self._previous_filename.value + + @property + def raw_url(self) -> str: + return self._raw_url.value + + @property + def sha(self) -> str: + return self._sha.value + + @property + def status(self) -> str: + return self._status.value + + def _useAttributes(self, attributes: Dict[str, Any]) -> None: + if "additions" in attributes: # pragma no branch + self._additions = self._makeIntAttribute(attributes["additions"]) + if "blob_url" in attributes: # pragma no branch + self._blob_url = self._makeStringAttribute(attributes["blob_url"]) + if "changes" in attributes: # pragma no branch + self._changes = self._makeIntAttribute(attributes["changes"]) + if "contents_url" in attributes: # pragma no branch + self._contents_url = self._makeStringAttribute(attributes["contents_url"]) + if "deletions" in attributes: # pragma no branch + self._deletions = self._makeIntAttribute(attributes["deletions"]) + if "filename" in attributes: # pragma no branch + self._filename = self._makeStringAttribute(attributes["filename"]) + if "patch" in attributes: # pragma no branch + self._patch = self._makeStringAttribute(attributes["patch"]) + if "previous_filename" in attributes: # pragma no branch + self._previous_filename = self._makeStringAttribute(attributes["previous_filename"]) + if "raw_url" in attributes: # pragma no branch + self._raw_url = self._makeStringAttribute(attributes["raw_url"]) + if "sha" in attributes: # pragma no branch + self._sha = self._makeStringAttribute(attributes["sha"]) + if "status" in attributes: # pragma no branch + self._status = self._makeStringAttribute(attributes["status"]) diff --git a/venv/lib/python3.10/site-packages/github/Gist.py b/venv/lib/python3.10/site-packages/github/Gist.py new file mode 100644 index 0000000000000000000000000000000000000000..417d8e938461e90bb461a073d947648538420dd5 --- /dev/null +++ b/venv/lib/python3.10/site-packages/github/Gist.py @@ -0,0 +1,335 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2012 Steve English # +# Copyright 2012 Vincent Jacques # +# Copyright 2012 Zearin # +# Copyright 2013 AKFish # +# Copyright 2013 Vincent Jacques # +# Copyright 2014 Dale Jung # +# Copyright 2014 Vincent Jacques # +# Copyright 2016 Jannis Gebauer # +# Copyright 2016 Peter Buckley # +# Copyright 2018 Wan Liuyang # +# Copyright 2018 sfdye # +# Copyright 2018 羽 # +# Copyright 2019 Jon Dufresne # +# Copyright 2019 Steve Kowalik # +# Copyright 2019 Wan Liuyang # +# Copyright 2020 Steve Kowalik # +# Copyright 2021 Mark Walker # +# Copyright 2021 Steve Kowalik # +# Copyright 2023 Enrico Minack # +# Copyright 2023 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2023 Trim21 # +# Copyright 2024 Enrico Minack # +# Copyright 2024 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2025 Enrico Minack # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from __future__ import annotations + +from datetime import datetime +from typing import TYPE_CHECKING, Any + +import github.GistComment +import github.GistFile +import github.GistHistoryState +import github.GithubObject +import github.NamedUser +import github.PaginatedList +from github.GithubObject import Attribute, CompletableGithubObject, NotSet, Opt, _NotSetType, is_defined, is_optional +from github.PaginatedList import PaginatedList + +if TYPE_CHECKING: + from github.GistComment import GistComment + from github.GistHistoryState import GistHistoryState + from github.InputFileContent import InputFileContent + + +class Gist(CompletableGithubObject): + """ + This class represents Gists. + + The reference can be found here + https://docs.github.com/en/rest/reference/gists + + The OpenAPI schema can be found at + - /components/schemas/base-gist + - /components/schemas/gist-simple + - /components/schemas/gist-simple/properties/fork_of + - /components/schemas/gist-simple/properties/forks/items + + """ + + def _initAttributes(self) -> None: + self._comments: Attribute[int] = NotSet + self._comments_url: Attribute[str] = NotSet + self._commits_url: Attribute[str] = NotSet + self._created_at: Attribute[datetime] = NotSet + self._description: Attribute[str] = NotSet + self._files: Attribute[dict[str, github.GistFile.GistFile]] = NotSet + self._fork_of: Attribute[Gist] = NotSet + self._forks: Attribute[list[Gist]] = NotSet + self._forks_url: Attribute[str] = NotSet + self._git_pull_url: Attribute[str] = NotSet + self._git_push_url: Attribute[str] = NotSet + self._history: Attribute[list[GistHistoryState]] = NotSet + self._html_url: Attribute[str] = NotSet + self._id: Attribute[str] = NotSet + self._node_id: Attribute[str] = NotSet + self._owner: Attribute[github.NamedUser.NamedUser] = NotSet + self._public: Attribute[bool] = NotSet + self._truncated: Attribute[bool] = NotSet + self._updated_at: Attribute[datetime] = NotSet + self._url: Attribute[str] = NotSet + self._user: Attribute[github.NamedUser.NamedUser] = NotSet + + def __repr__(self) -> str: + return self.get__repr__({"id": self._id.value}) + + @property + def comments(self) -> int: + self._completeIfNotSet(self._comments) + return self._comments.value + + @property + def comments_url(self) -> str: + self._completeIfNotSet(self._comments_url) + return self._comments_url.value + + @property + def commits_url(self) -> str: + self._completeIfNotSet(self._commits_url) + return self._commits_url.value + + @property + def created_at(self) -> datetime: + self._completeIfNotSet(self._created_at) + return self._created_at.value + + @property + def description(self) -> str: + self._completeIfNotSet(self._description) + return self._description.value + + @property + def files(self) -> dict[str, github.GistFile.GistFile]: + self._completeIfNeeded() + return self._files.value + + @property + def fork_of(self) -> github.Gist.Gist: + self._completeIfNotSet(self._fork_of) + return self._fork_of.value + + @property + def forks(self) -> list[Gist]: + self._completeIfNotSet(self._forks) + return self._forks.value + + @property + def forks_url(self) -> str: + self._completeIfNotSet(self._forks_url) + return self._forks_url.value + + @property + def git_pull_url(self) -> str: + self._completeIfNotSet(self._git_pull_url) + return self._git_pull_url.value + + @property + def git_push_url(self) -> str: + self._completeIfNotSet(self._git_push_url) + return self._git_push_url.value + + @property + def history(self) -> list[GistHistoryState]: + self._completeIfNotSet(self._history) + return self._history.value + + @property + def html_url(self) -> str: + self._completeIfNotSet(self._html_url) + return self._html_url.value + + @property + def id(self) -> str: + self._completeIfNotSet(self._id) + return self._id.value + + @property + def node_id(self) -> str: + self._completeIfNotSet(self._node_id) + return self._node_id.value + + @property + def owner(self) -> github.NamedUser.NamedUser: + self._completeIfNotSet(self._owner) + return self._owner.value + + @property + def public(self) -> bool: + self._completeIfNotSet(self._public) + return self._public.value + + @property + def truncated(self) -> bool: + self._completeIfNotSet(self._truncated) + return self._truncated.value + + @property + def updated_at(self) -> datetime: + self._completeIfNotSet(self._updated_at) + return self._updated_at.value + + @property + def url(self) -> str: + self._completeIfNotSet(self._url) + return self._url.value + + @property + def user(self) -> github.NamedUser.NamedUser: + self._completeIfNotSet(self._user) + return self._user.value + + def create_comment(self, body: str) -> GistComment: + """ + :calls: `POST /gists/{gist_id}/comments `_ + """ + assert isinstance(body, str), body + post_parameters = { + "body": body, + } + headers, data = self._requester.requestJsonAndCheck("POST", f"{self.url}/comments", input=post_parameters) + return github.GistComment.GistComment(self._requester, headers, data, completed=True) + + def create_fork(self) -> Gist: + """ + :calls: `POST /gists/{id}/forks `_ + """ + headers, data = self._requester.requestJsonAndCheck("POST", f"{self.url}/forks") + return Gist(self._requester, headers, data, completed=True) + + def delete(self) -> None: + """ + :calls: `DELETE /gists/{id} `_ + """ + headers, data = self._requester.requestJsonAndCheck("DELETE", self.url) + + def edit(self, description: Opt[str] = NotSet, files: Opt[dict[str, InputFileContent | None]] = NotSet) -> None: + """ + :calls: `PATCH /gists/{id} `_ + """ + assert is_optional(description, str), description + # limitation of `TypeGuard` + assert isinstance(files, _NotSetType) or all( + element is None or isinstance(element, github.InputFileContent) for element in files.values() + ), files + post_parameters: dict[str, Any] = {} + if is_defined(description): + post_parameters["description"] = description + if is_defined(files): + post_parameters["files"] = {key: None if value is None else value._identity for key, value in files.items()} + headers, data = self._requester.requestJsonAndCheck("PATCH", self.url, input=post_parameters) + self._useAttributes(data) + + def get_comment(self, id: int) -> GistComment: + """ + :calls: `GET /gists/{gist_id}/comments/{id} `_ + """ + assert isinstance(id, int), id + headers, data = self._requester.requestJsonAndCheck("GET", f"{self.url}/comments/{id}") + return github.GistComment.GistComment(self._requester, headers, data, completed=True) + + def get_comments(self) -> PaginatedList[GistComment]: + """ + :calls: `GET /gists/{gist_id}/comments `_ + """ + return PaginatedList( + github.GistComment.GistComment, + self._requester, + f"{self.url}/comments", + None, + ) + + def is_starred(self) -> bool: + """ + :calls: `GET /gists/{id}/star `_ + """ + status, headers, data = self._requester.requestJson("GET", f"{self.url}/star") + return status == 204 + + def reset_starred(self) -> None: + """ + :calls: `DELETE /gists/{id}/star `_ + """ + headers, data = self._requester.requestJsonAndCheck("DELETE", f"{self.url}/star") + + def set_starred(self) -> None: + """ + :calls: `PUT /gists/{id}/star `_ + """ + headers, data = self._requester.requestJsonAndCheck("PUT", f"{self.url}/star") + + def _useAttributes(self, attributes: dict[str, Any]) -> None: + if "comments" in attributes: # pragma no branch + self._comments = self._makeIntAttribute(attributes["comments"]) + if "comments_url" in attributes: # pragma no branch + self._comments_url = self._makeStringAttribute(attributes["comments_url"]) + if "commits_url" in attributes: # pragma no branch + self._commits_url = self._makeStringAttribute(attributes["commits_url"]) + if "created_at" in attributes: # pragma no branch + self._created_at = self._makeDatetimeAttribute(attributes["created_at"]) + if "description" in attributes: # pragma no branch + self._description = self._makeStringAttribute(attributes["description"]) + if "files" in attributes: # pragma no branch + self._files = self._makeDictOfStringsToClassesAttribute(github.GistFile.GistFile, attributes["files"]) + if "fork_of" in attributes: # pragma no branch + self._fork_of = self._makeClassAttribute(Gist, attributes["fork_of"]) + if "forks" in attributes: # pragma no branch + self._forks = self._makeListOfClassesAttribute(Gist, attributes["forks"]) + if "forks_url" in attributes: # pragma no branch + self._forks_url = self._makeStringAttribute(attributes["forks_url"]) + if "git_pull_url" in attributes: # pragma no branch + self._git_pull_url = self._makeStringAttribute(attributes["git_pull_url"]) + if "git_push_url" in attributes: # pragma no branch + self._git_push_url = self._makeStringAttribute(attributes["git_push_url"]) + if "history" in attributes: # pragma no branch + self._history = self._makeListOfClassesAttribute( + github.GistHistoryState.GistHistoryState, attributes["history"] + ) + if "html_url" in attributes: # pragma no branch + self._html_url = self._makeStringAttribute(attributes["html_url"]) + if "id" in attributes: # pragma no branch + self._id = self._makeStringAttribute(attributes["id"]) + if "node_id" in attributes: # pragma no branch + self._node_id = self._makeStringAttribute(attributes["node_id"]) + if "owner" in attributes: # pragma no branch + self._owner = self._makeClassAttribute(github.NamedUser.NamedUser, attributes["owner"]) + if "public" in attributes: # pragma no branch + self._public = self._makeBoolAttribute(attributes["public"]) + if "truncated" in attributes: # pragma no branch + self._truncated = self._makeBoolAttribute(attributes["truncated"]) + if "updated_at" in attributes: # pragma no branch + self._updated_at = self._makeDatetimeAttribute(attributes["updated_at"]) + if "url" in attributes: # pragma no branch + self._url = self._makeStringAttribute(attributes["url"]) + if "user" in attributes: # pragma no branch + self._user = self._makeClassAttribute(github.NamedUser.NamedUser, attributes["user"]) diff --git a/venv/lib/python3.10/site-packages/github/GistComment.py b/venv/lib/python3.10/site-packages/github/GistComment.py new file mode 100644 index 0000000000000000000000000000000000000000..fcc7a1f18a112d06dd7fc60d89d0e4ce0f96ebc8 --- /dev/null +++ b/venv/lib/python3.10/site-packages/github/GistComment.py @@ -0,0 +1,150 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2012 Vincent Jacques # +# Copyright 2012 Zearin # +# Copyright 2013 AKFish # +# Copyright 2013 Vincent Jacques # +# Copyright 2014 Vincent Jacques # +# Copyright 2016 Jannis Gebauer # +# Copyright 2016 Peter Buckley # +# Copyright 2018 Wan Liuyang # +# Copyright 2018 sfdye # +# Copyright 2019 Steve Kowalik # +# Copyright 2019 Wan Liuyang # +# Copyright 2020 Steve Kowalik # +# Copyright 2021 Mark Walker # +# Copyright 2021 Steve Kowalik # +# Copyright 2023 Enrico Minack # +# Copyright 2023 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2023 Trim21 # +# Copyright 2024 Enrico Minack # +# Copyright 2024 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2025 Enrico Minack # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from __future__ import annotations + +from datetime import datetime +from typing import Any + +import github.GithubObject +import github.NamedUser +from github.GithubObject import Attribute, CompletableGithubObject, NotSet + + +class GistComment(CompletableGithubObject): + """ + This class represents GistComments. + + The reference can be found here + https://docs.github.com/en/rest/reference/gists#comments + + The OpenAPI schema can be found at + - /components/schemas/gist-comment + + """ + + def _initAttributes(self) -> None: + self._author_association: Attribute[str] = NotSet + self._body: Attribute[str] = NotSet + self._created_at: Attribute[datetime] = NotSet + self._id: Attribute[int] = NotSet + self._node_id: Attribute[str] = NotSet + self._updated_at: Attribute[datetime] = NotSet + self._url: Attribute[str] = NotSet + self._user: Attribute[github.NamedUser.NamedUser] = NotSet + + def __repr__(self) -> str: + return self.get__repr__({"id": self._id.value, "user": self._user.value}) + + @property + def author_association(self) -> str: + self._completeIfNotSet(self._author_association) + return self._author_association.value + + @property + def body(self) -> str: + self._completeIfNotSet(self._body) + return self._body.value + + @property + def created_at(self) -> datetime: + self._completeIfNotSet(self._created_at) + return self._created_at.value + + @property + def id(self) -> int: + self._completeIfNotSet(self._id) + return self._id.value + + @property + def node_id(self) -> str: + self._completeIfNotSet(self._node_id) + return self._node_id.value + + @property + def updated_at(self) -> datetime: + self._completeIfNotSet(self._updated_at) + return self._updated_at.value + + @property + def url(self) -> str: + self._completeIfNotSet(self._url) + return self._url.value + + @property + def user(self) -> github.NamedUser.NamedUser: + self._completeIfNotSet(self._user) + return self._user.value + + def delete(self) -> None: + """ + :calls: `DELETE /gists/{gist_id}/comments/{id} `_ + """ + headers, data = self._requester.requestJsonAndCheck("DELETE", self.url) + + def edit(self, body: str) -> None: + """ + :calls: `PATCH /gists/{gist_id}/comments/{id} `_ + """ + assert isinstance(body, str), body + post_parameters = { + "body": body, + } + headers, data = self._requester.requestJsonAndCheck("PATCH", self.url, input=post_parameters) + self._useAttributes(data) + + def _useAttributes(self, attributes: dict[str, Any]) -> None: + if "author_association" in attributes: # pragma no branch + self._author_association = self._makeStringAttribute(attributes["author_association"]) + if "body" in attributes: # pragma no branch + self._body = self._makeStringAttribute(attributes["body"]) + if "created_at" in attributes: # pragma no branch + self._created_at = self._makeDatetimeAttribute(attributes["created_at"]) + if "id" in attributes: # pragma no branch + self._id = self._makeIntAttribute(attributes["id"]) + if "node_id" in attributes: # pragma no branch + self._node_id = self._makeStringAttribute(attributes["node_id"]) + if "updated_at" in attributes: # pragma no branch + self._updated_at = self._makeDatetimeAttribute(attributes["updated_at"]) + if "url" in attributes: # pragma no branch + self._url = self._makeStringAttribute(attributes["url"]) + if "user" in attributes: # pragma no branch + self._user = self._makeClassAttribute(github.NamedUser.NamedUser, attributes["user"]) diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/AccessToken.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/AccessToken.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cfef7aeae0caf970308715ac8acff1bed6c0d8a5 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/AccessToken.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/AdvisoryBase.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/AdvisoryBase.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..48aa45d36bdf31b4f2f80a4b5e98b01b4a7831c3 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/AdvisoryBase.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/AdvisoryCredit.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/AdvisoryCredit.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4cb2d83a659a8c191ff96bed43def84b2a5a2ce0 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/AdvisoryCredit.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/AdvisoryCreditDetailed.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/AdvisoryCreditDetailed.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a6c9e79284bb62656b7c9bd2962b69b4d4f62b39 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/AdvisoryCreditDetailed.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/AdvisoryVulnerability.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/AdvisoryVulnerability.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..47e4810fb840310243177a3fe7b2d7c77ef6a438 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/AdvisoryVulnerability.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/AdvisoryVulnerabilityPackage.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/AdvisoryVulnerabilityPackage.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..be597bc79f95ff8780611c54ccc285265839c405 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/AdvisoryVulnerabilityPackage.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/AppAuthentication.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/AppAuthentication.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..74d0149fd6ee3d9c82ff4d1756e6822f832aa191 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/AppAuthentication.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/ApplicationOAuth.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/ApplicationOAuth.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f3b55d2e5c70c945aed059091a3989c7a813dbe2 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/ApplicationOAuth.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/Artifact.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/Artifact.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..929235584957e1dcd391fdd56b5462396f957783 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/Artifact.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/Auth.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/Auth.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cba9ba312f6c08f2117ffa071b4ea3945e8469b5 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/Auth.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/AuthenticatedUser.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/AuthenticatedUser.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..47386aefc824dabcae56b5e46a7d2e404054f807 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/AuthenticatedUser.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/Authorization.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/Authorization.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..257fc1c68a2bb5e81b28925f78848f2ed9d441db Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/Authorization.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/AuthorizationApplication.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/AuthorizationApplication.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..be50a9eb52644be8e0a56d49bed4b0a9db4debaf Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/AuthorizationApplication.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/Autolink.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/Autolink.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fbb9a79a17b39ddcc35e3e266d3a30a0d4650f1a Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/Autolink.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/Branch.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/Branch.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f38be9a3cde675639780c4f8f63aac69040d647f Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/Branch.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/BranchProtection.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/BranchProtection.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bbb4f7f33b661052a459ff7b86b1115868a0b45c Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/BranchProtection.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/CVSS.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/CVSS.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8c3609bf3eea79e8e705475c595dd7550399729c Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/CVSS.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/CWE.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/CWE.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cc1c41a9855dce152849468053b94fde98300536 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/CWE.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/CheckRun.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/CheckRun.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..502edfb47bc37c5814c22c05e10cb6b01b108bb8 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/CheckRun.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/CheckRunAnnotation.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/CheckRunAnnotation.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..28a4166fc822f5854033a5c0f60c1668ee0cc227 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/CheckRunAnnotation.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/CheckRunOutput.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/CheckRunOutput.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7ef2de15ad612a8bd5324173bff66c02f005a4c5 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/CheckRunOutput.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/CheckSuite.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/CheckSuite.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..72a5187afc4021c1cfdbfacbed84e2763e36cc0d Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/CheckSuite.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/Clones.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/Clones.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..693875278979d3aeb25b6add35e2bae666da584f Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/Clones.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/CodeScanAlert.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/CodeScanAlert.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e5f9e37008f99ca9656573732a90121cb1900087 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/CodeScanAlert.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/CodeScanAlertInstance.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/CodeScanAlertInstance.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..faf007b09baa7defaaefcd7555c4b9795ba1e21d Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/CodeScanAlertInstance.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/CodeScanAlertInstanceLocation.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/CodeScanAlertInstanceLocation.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e8ef072df7a5ead37231399a2657eca73c872b3a Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/CodeScanAlertInstanceLocation.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/CodeScanRule.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/CodeScanRule.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..74e1fd8fd71ab269094734ef72bf70a42c53b8ff Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/CodeScanRule.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/CodeScanTool.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/CodeScanTool.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3df2989a85661000bfbafd9dcaf3ee5c4b8b72ee Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/CodeScanTool.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/CodeSecurityConfig.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/CodeSecurityConfig.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..21bd29f95bf69ffc6bfb62c600f605faa67383a7 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/CodeSecurityConfig.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/CodeSecurityConfigRepository.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/CodeSecurityConfigRepository.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..42f0d5aab0c83a5e38cc3d9e5397b362df791432 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/CodeSecurityConfigRepository.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/Commit.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/Commit.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..76787d7ead98134a0132f3a8bdc59f136106a347 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/Commit.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/CommitCombinedStatus.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/CommitCombinedStatus.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..42ec9ebf9f37d406c02ec91761855baf851b07b9 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/CommitCombinedStatus.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/CommitComment.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/CommitComment.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7583a9feecf2b06ff759f2f09b04cd38f3ee9ceb Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/CommitComment.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/CommitStats.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/CommitStats.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..690445298b5d3aef75fb5bf23ebe0d0a0aebfbf7 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/CommitStats.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/CommitStatus.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/CommitStatus.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cfea79d1d9fbab48402345757827a832feecbe25 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/CommitStatus.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/Comparison.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/Comparison.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5840bffe3a8b21092662930a62045148f10e4029 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/Comparison.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/Consts.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/Consts.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7c4356c7decf25f722ac74b09962de9156c4e54d Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/Consts.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/ContentFile.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/ContentFile.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d360962d2d4dc0969a0e013fdd672c1d488d228a Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/ContentFile.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/Copilot.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/Copilot.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..75e3e5fa5e6515d86c8bb801e85f4b7e4d3a1967 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/Copilot.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/CopilotSeat.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/CopilotSeat.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7db0b9bd98bf0bb7e8fa2f879fcea64e729442d0 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/CopilotSeat.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/DefaultCodeSecurityConfig.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/DefaultCodeSecurityConfig.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f76e06d0702a03b81809fcd75003b359b4f7e625 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/DefaultCodeSecurityConfig.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/DependabotAlert.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/DependabotAlert.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8009607ebe95c6bb5bd45aea954453e7c4f41b2a Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/DependabotAlert.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/DependabotAlertAdvisory.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/DependabotAlertAdvisory.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..32f4d32751d8341a684ff711555b084c710a17a5 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/DependabotAlertAdvisory.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/DependabotAlertDependency.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/DependabotAlertDependency.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..be0b87a80a7af5eda2e0969d77792f4273ff115b Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/DependabotAlertDependency.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/DependabotAlertVulnerability.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/DependabotAlertVulnerability.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5ce8b1a88978fda4a3be16481eb5017061b108da Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/DependabotAlertVulnerability.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/Deployment.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/Deployment.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3553930f67a987e3951c74c28f80db58a2c7faa8 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/Deployment.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/DeploymentStatus.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/DeploymentStatus.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..132c00c78f53ef9765093fcc6703ffbdb7f9441b Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/DeploymentStatus.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/DiscussionBase.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/DiscussionBase.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3e75ac918ec6801872fd430a878df3779ff88a37 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/DiscussionBase.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/DiscussionCommentBase.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/DiscussionCommentBase.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7c41d9ed3f2d996f20c223a28a2bc5303c122623 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/DiscussionCommentBase.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/Download.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/Download.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..367ed55c45a643c3f7ea70c10a10d30d5f409044 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/Download.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/Enterprise.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/Enterprise.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dbe7731c280c161a825e5bb7b0ce25fa0448be21 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/Enterprise.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/EnterpriseConsumedLicenses.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/EnterpriseConsumedLicenses.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0821782c142a1d8758e16a1f538cf37565749cd9 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/EnterpriseConsumedLicenses.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/Environment.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/Environment.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..502202f4f78b92d46f88d9de50fb04ffc454eb78 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/Environment.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/EnvironmentDeploymentBranchPolicy.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/EnvironmentDeploymentBranchPolicy.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9826a95f0874f00df97cf1b74363995148008a62 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/EnvironmentDeploymentBranchPolicy.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/EnvironmentProtectionRule.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/EnvironmentProtectionRule.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9bf7bb57f403a18d0134ecb8704b652def5f8a8b Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/EnvironmentProtectionRule.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/EnvironmentProtectionRuleReviewer.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/EnvironmentProtectionRuleReviewer.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8deedfb571ab37585799f8d8299cce57b7b3bf9f Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/EnvironmentProtectionRuleReviewer.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/Event.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/Event.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7cd9887ecc49523a94452ecef8168a19bf6cc0d1 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/Event.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/File.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/File.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3b2e7508672526ba460f5c3810bae52a9e681de4 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/File.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/Gist.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/Gist.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a7f2b6efac065fef5caca9152dbac04beec440a9 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/Gist.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/GistComment.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/GistComment.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..83d0042eafdf88bda6a676b6d0e84ead9f236f91 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/GistComment.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/GistFile.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/GistFile.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e8ad8ec1a0fd933d5950ff95df469edf2b2b1a97 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/GistFile.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/GistHistoryState.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/GistHistoryState.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..49fdc8fce3b4011244e260e3ac2aa9467b219493 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/GistHistoryState.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/GitAuthor.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/GitAuthor.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b7964c5a291686589153061a76e7f1c3d17ab247 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/GitAuthor.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/GitBlob.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/GitBlob.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ebf02ea6129247c70b99002c4ffb2636135bc0a7 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/GitBlob.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/GitCommit.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/GitCommit.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..018507dea3a21add45d856b418e337b14c88606f Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/GitCommit.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/GitCommitVerification.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/GitCommitVerification.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7f312334ac2def3c49933f3087048fe4aef556b4 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/GitCommitVerification.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/GitObject.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/GitObject.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ab896768ebe7ecc5901d34ae3d6f8a42bbe51fa4 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/GitObject.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/GitRef.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/GitRef.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d04ec807ab1e85e97f202628290df22058a28256 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/GitRef.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/GitRelease.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/GitRelease.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ac35145b2991972c53d574ea62d679aa6d15bc64 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/GitRelease.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/GitReleaseAsset.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/GitReleaseAsset.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2d2743c80c4417bbf96f5fec72e52d646d0ef421 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/GitReleaseAsset.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/GitTag.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/GitTag.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..008d60f0e943e128417182566b76c8a1d5f4b068 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/GitTag.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/GitTree.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/GitTree.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d45b3cb74c807a4e2c86bdf86df400448698e455 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/GitTree.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/GitTreeElement.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/GitTreeElement.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..819ec350f300a0e26a467aa8c09d32c3b64769cb Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/GitTreeElement.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/GithubApp.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/GithubApp.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aecec93db3a9a3a2d87210f38a67fca6d83a8903 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/GithubApp.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/GithubException.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/GithubException.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7b5743461b13c053d83ec72099626135c178a62c Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/GithubException.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/GithubIntegration.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/GithubIntegration.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ae5a553031af668f3414d312c071fe5f341a8387 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/GithubIntegration.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/GithubObject.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/GithubObject.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a5a3f29b63f32d74f0cd926b60d2125a1efc1667 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/GithubObject.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/GithubRetry.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/GithubRetry.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..135e2534b1af706283481974e6d923302d2d509e Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/GithubRetry.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/GitignoreTemplate.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/GitignoreTemplate.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1f0cae673bd9ab84b24e1b74315ad619894ac317 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/GitignoreTemplate.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/GlobalAdvisory.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/GlobalAdvisory.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..34a7fbe98128d825f8e39ef85811a6b2195f92a0 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/GlobalAdvisory.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/Hook.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/Hook.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a57666ce0d2506fa35f1798e84034370faacf4e3 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/Hook.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/HookDelivery.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/HookDelivery.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3cc0ed34b22ae64da27a4931cba7f88f8bf5eb3c Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/HookDelivery.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/HookDescription.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/HookDescription.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ac5de6c12a358f06326164344d0d56e33f615764 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/HookDescription.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/HookResponse.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/HookResponse.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a6d4b92e3792725b733bfe386bef88a2aae37637 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/HookResponse.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/InputFileContent.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/InputFileContent.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e7a10a9c15706178acbda4970b77dde9c41e4a99 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/InputFileContent.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/InputGitAuthor.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/InputGitAuthor.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8ea32700af1007d96240e2446a0666c3a262f892 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/InputGitAuthor.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/Installation.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/Installation.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5894781fc0c1488c214e67747eb71d10d88b496a Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/Installation.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/Invitation.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/Invitation.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0cb7bc050a2f7ece2c69dfd84a29aed888ef3b5b Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/Invitation.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/github/__pycache__/MainClass.cpython-310.pyc b/venv/lib/python3.10/site-packages/github/__pycache__/MainClass.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a69c1f93cfe9b77109b7ab22f84d7f7fc887d894 Binary files /dev/null and b/venv/lib/python3.10/site-packages/github/__pycache__/MainClass.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/matrix_client-0.4.0.dist-info/INSTALLER b/venv/lib/python3.10/site-packages/matrix_client-0.4.0.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/venv/lib/python3.10/site-packages/matrix_client-0.4.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python3.10/site-packages/matrix_client-0.4.0.dist-info/LICENSE b/venv/lib/python3.10/site-packages/matrix_client-0.4.0.dist-info/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..e06d2081865a766a8668acc12878f98b27fc9ea0 --- /dev/null +++ b/venv/lib/python3.10/site-packages/matrix_client-0.4.0.dist-info/LICENSE @@ -0,0 +1,202 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/venv/lib/python3.10/site-packages/matrix_client-0.4.0.dist-info/METADATA b/venv/lib/python3.10/site-packages/matrix_client-0.4.0.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..89b6c7956b394ff46b874e8afd0425a20721f04c --- /dev/null +++ b/venv/lib/python3.10/site-packages/matrix_client-0.4.0.dist-info/METADATA @@ -0,0 +1,157 @@ +Metadata-Version: 2.1 +Name: matrix-client +Version: 0.4.0 +Summary: Client-Server SDK for Matrix +Home-page: https://github.com/matrix-org/matrix-python-sdk +Author: The Matrix.org Team +Author-email: team@matrix.org +License: Apache License, Version 2.0 +Keywords: chat sdk matrix matrix.org +Platform: UNKNOWN +Classifier: Development Status :: 3 - Alpha +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 3 +Classifier: Topic :: Communications :: Chat +Classifier: Topic :: Communications :: Conferencing +Description-Content-Type: text/x-rst +License-File: LICENSE +Requires-Dist: requests (~=2.22) +Requires-Dist: urllib3 (~=1.21) +Provides-Extra: doc +Requires-Dist: Sphinx (==1.*,>=1.7.6) ; extra == 'doc' +Requires-Dist: sphinx-rtd-theme (==0.1.*,>=0.1.9) ; extra == 'doc' +Requires-Dist: sphinxcontrib-napoleon (==0.5.*,>=0.5.3) ; extra == 'doc' +Provides-Extra: e2e +Requires-Dist: python-olm (~=3.1) ; extra == 'e2e' +Requires-Dist: canonicaljson (~=1.1) ; extra == 'e2e' +Provides-Extra: test +Requires-Dist: pytest (<6.0.0,>=4.6) ; extra == 'test' +Requires-Dist: responses (==0.10.*,>=0.10.6) ; extra == 'test' + +Matrix Client SDK for Python +============================ + +.. image:: https://img.shields.io/pypi/v/matrix-client.svg?maxAge=600 + :target: https://pypi.python.org/pypi/matrix-client + :alt: Latest Version +.. image:: https://travis-ci.org/matrix-org/matrix-python-sdk.svg?branch=master + :target: https://travis-ci.org/matrix-org/matrix-python-sdk + :alt: Travis-CI Results +.. image:: https://coveralls.io/repos/github/matrix-org/matrix-python-sdk/badge.svg?branch=master + :target: https://coveralls.io/github/matrix-org/matrix-python-sdk?branch=master + :alt: coveralls.io Results +.. image:: https://img.shields.io/matrix/matrix-python-sdk:matrix.org + :target: https://matrix.to/#/%23matrix-python-sdk:matrix.org + :alt: Matrix chatroom +.. image:: https://img.shields.io/badge/docs-stable-blue + :target: https://matrix-org.github.io/matrix-python-sdk/ + :alt: Documentation + + +Matrix client-server SDK for Python 2.7 and 3.4+ + +Project Status +-------------- + +We strongly recommend using the `matrix-nio`_ library rather than this +sdk. It is both more featureful and more actively maintained. + +This sdk is currently lightly maintained without any person ultimately +responsible for the project. Pull-requests **may** be reviewed, but no +new-features or bug-fixes are being actively developed. For more info +or to volunteer to help, please see +https://github.com/matrix-org/matrix-python-sdk/issues/279 or come +chat in `#matrix-python-sdk:matrix.org`_. + +.. _`matrix-nio`: https://github.com/poljar/matrix-nio +.. _`#matrix-python-sdk:matrix.org`: https://matrix.to/#/%23matrix-python-sdk:matrix.org + +Installation +============ +Stable release +-------------- +Install with pip from pypi. This will install all necessary dependencies as well. + +.. code:: shell + + pip install matrix_client + +Development version +------------------- +Install using ``setup.py`` in root project directory. This will also install all +needed dependencies. + +.. code:: shell + + git clone https://github.com/matrix-org/matrix-python-sdk.git + cd matrix-python-sdk + python setup.py install + +Usage +===== +The SDK provides 2 layers of interaction. The low-level layer just wraps the +raw HTTP API calls. The high-level layer wraps the low-level layer and provides +an object model to perform actions on. + +Client: + +.. code:: python + + from matrix_client.client import MatrixClient + + client = MatrixClient("http://localhost:8008") + + # New user + token = client.register_with_password(username="foobar", password="monkey") + + # Existing user + token = client.login(username="foobar", password="monkey") + + room = client.create_room("my_room_alias") + room.send_text("Hello!") + + +API: + +.. code:: python + + from matrix_client.api import MatrixHttpApi + + matrix = MatrixHttpApi("https://matrix.org", token="some_token") + response = matrix.send_message("!roomid:matrix.org", "Hello!") + + +Structure +========= +The SDK is split into two modules: ``api`` and ``client``. + +API +--- +This contains the raw HTTP API calls and has minimal business logic. You can +set the access token (``token``) to use for requests as well as set a custom +transaction ID (``txn_id``) which will be incremented for each request. + +Client +------ +This encapsulates the API module and provides object models such as ``Room``. + +Samples +======= +A collection of samples are included, written in Python 3. + +You can either install the SDK, or run the sample like this: + +.. code:: shell + + PYTHONPATH=. python samples/samplename.py + +Building the Documentation +========================== + +The documentation can be built by installing ``sphinx`` and ``sphinx_rtd_theme``. + +Simple run ``make`` inside ``docs`` which will list the avaliable output formats. + + diff --git a/venv/lib/python3.10/site-packages/matrix_client-0.4.0.dist-info/RECORD b/venv/lib/python3.10/site-packages/matrix_client-0.4.0.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..ac3e90efb56901ab8e3616f821729d58f89d3f58 --- /dev/null +++ b/venv/lib/python3.10/site-packages/matrix_client-0.4.0.dist-info/RECORD @@ -0,0 +1,37 @@ +matrix_client-0.4.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +matrix_client-0.4.0.dist-info/LICENSE,sha256=y16Ofl9KOYjhBjwULGDcLfdWBfTEZRXnduOspt-XbhQ,11325 +matrix_client-0.4.0.dist-info/METADATA,sha256=u50tqp4kU07AzJgHE12jW-BrFqEnWS049T7dOKxuIdk,4956 +matrix_client-0.4.0.dist-info/RECORD,, +matrix_client-0.4.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +matrix_client-0.4.0.dist-info/WHEEL,sha256=Z-nyYpwrcSqxfdux5Mbn_DQ525iP7J2DG3JgGvOYyTQ,110 +matrix_client-0.4.0.dist-info/top_level.txt,sha256=dmjf3uMSj0bJanvGvcANIgJLTPFC3k4E4Ifg5k-0cyU,19 +matrix_client/__init__.py,sha256=sdVPVNtj0pA7JoOfZEx2FdWzUoH-qVaxNq6YLt1EChc,656 +matrix_client/__pycache__/__init__.cpython-310.pyc,, +matrix_client/__pycache__/api.cpython-310.pyc,, +matrix_client/__pycache__/checks.cpython-310.pyc,, +matrix_client/__pycache__/client.cpython-310.pyc,, +matrix_client/__pycache__/errors.cpython-310.pyc,, +matrix_client/__pycache__/room.cpython-310.pyc,, +matrix_client/__pycache__/user.cpython-310.pyc,, +matrix_client/api.py,sha256=F3hi-A3yPxj-hCo9topL8F2geYxOtw5UadRxajseN1A,39415 +matrix_client/checks.py,sha256=M_BsgQNTg3hb3yX4obEsxvTjcrFJ1DhnEHtfPIau-gM,1054 +matrix_client/client.py,sha256=jlQiDTpjc-PWfq5IxPmURL7a-L4NWol9jbiOl5PvRQM,24926 +matrix_client/crypto/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +matrix_client/crypto/__pycache__/__init__.cpython-310.pyc,, +matrix_client/crypto/__pycache__/olm_device.cpython-310.pyc,, +matrix_client/crypto/__pycache__/one_time_keys.cpython-310.pyc,, +matrix_client/crypto/olm_device.py,sha256=bjo7R0iWiRYHv9WvHUdrTjUWr2GAa1aMxPyvPET-lQ8,8538 +matrix_client/crypto/one_time_keys.py,sha256=0cDvhMX395C8A8xROnorKf6CMX_MCFLaPXEfCLfi5Eo,1531 +matrix_client/errors.py,sha256=ruFMLa0_zV1xBrLKqyK-z7BJv1KwuLaMFJ1IPZ1cN1Y,1742 +matrix_client/room.py,sha256=Gxu85KC-LCGUJvGuV9a00aPp3L_HyDMvgRCzjlPEYSM,24910 +matrix_client/user.py,sha256=kZ0UycJY0l7dCCZkwM5FRPnbwBgcQEupJGIpL_db77o,2527 +test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +test/__pycache__/__init__.cpython-310.pyc,, +test/__pycache__/api_test.cpython-310.pyc,, +test/__pycache__/client_test.cpython-310.pyc,, +test/__pycache__/response_examples.cpython-310.pyc,, +test/__pycache__/user_test.cpython-310.pyc,, +test/api_test.py,sha256=d0LWb7-YNcF2O_q-CFYutJbsvC-PLS1DT8Ii85tNQ4s,18737 +test/client_test.py,sha256=rJ1CneoAzCvfmhT1GlCQjFB4Sd9kF3CayGCOnc3KDR8,19047 +test/response_examples.py,sha256=pUxyy5gEa7qXwf2KpzW3JKmzLkdWaNgr5coMfYKQf_U,5976 +test/user_test.py,sha256=OPeDnDSAaoGfa-42W1DdHewO21AMBY5yv6TcQ1ocppU,1636 diff --git a/venv/lib/python3.10/site-packages/matrix_client-0.4.0.dist-info/REQUESTED b/venv/lib/python3.10/site-packages/matrix_client-0.4.0.dist-info/REQUESTED new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.10/site-packages/matrix_client-0.4.0.dist-info/WHEEL b/venv/lib/python3.10/site-packages/matrix_client-0.4.0.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..01b8fc7d4a10cb8b4f1d21f11d3398d07d6b3478 --- /dev/null +++ b/venv/lib/python3.10/site-packages/matrix_client-0.4.0.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.36.2) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/venv/lib/python3.10/site-packages/matrix_client-0.4.0.dist-info/top_level.txt b/venv/lib/python3.10/site-packages/matrix_client-0.4.0.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..ba4c019187a9d1fa0080bd217769b2a8598ebe58 --- /dev/null +++ b/venv/lib/python3.10/site-packages/matrix_client-0.4.0.dist-info/top_level.txt @@ -0,0 +1,2 @@ +matrix_client +test diff --git a/venv/lib/python3.10/site-packages/matrix_client/__init__.py b/venv/lib/python3.10/site-packages/matrix_client/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..7b18c407159a875ba6a2451c2d34d602789d918d --- /dev/null +++ b/venv/lib/python3.10/site-packages/matrix_client/__init__.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Copyright 2015 OpenMarket Ltd +# Copyright 2018 Adam Beckmeyer +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +__version__ = "0.4.0" diff --git a/venv/lib/python3.10/site-packages/matrix_client/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/matrix_client/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a98cfd49a8e5322443f6c43ef6a3a8baf3a4a3a7 Binary files /dev/null and b/venv/lib/python3.10/site-packages/matrix_client/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/matrix_client/__pycache__/api.cpython-310.pyc b/venv/lib/python3.10/site-packages/matrix_client/__pycache__/api.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..18e0a1148df5f3e1f4db8bc2603b86e7529aaa32 Binary files /dev/null and b/venv/lib/python3.10/site-packages/matrix_client/__pycache__/api.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/matrix_client/__pycache__/checks.cpython-310.pyc b/venv/lib/python3.10/site-packages/matrix_client/__pycache__/checks.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..92b2db4102f87f0835165b3cdb3b6f92bfd976d2 Binary files /dev/null and b/venv/lib/python3.10/site-packages/matrix_client/__pycache__/checks.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/matrix_client/__pycache__/client.cpython-310.pyc b/venv/lib/python3.10/site-packages/matrix_client/__pycache__/client.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5e4f97f16e8d57942e669f79e2690e934915d40a Binary files /dev/null and b/venv/lib/python3.10/site-packages/matrix_client/__pycache__/client.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/matrix_client/__pycache__/errors.cpython-310.pyc b/venv/lib/python3.10/site-packages/matrix_client/__pycache__/errors.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5521f9dbfcfaba789e4905e94bb2d916c744be23 Binary files /dev/null and b/venv/lib/python3.10/site-packages/matrix_client/__pycache__/errors.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/matrix_client/__pycache__/room.cpython-310.pyc b/venv/lib/python3.10/site-packages/matrix_client/__pycache__/room.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cdbcbb609e410a55904c38d4ad51d869e12fb317 Binary files /dev/null and b/venv/lib/python3.10/site-packages/matrix_client/__pycache__/room.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/matrix_client/__pycache__/user.cpython-310.pyc b/venv/lib/python3.10/site-packages/matrix_client/__pycache__/user.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4c4f5ec6a486d3be0f2f748fd34095ea1a87378a Binary files /dev/null and b/venv/lib/python3.10/site-packages/matrix_client/__pycache__/user.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/matrix_client/api.py b/venv/lib/python3.10/site-packages/matrix_client/api.py new file mode 100644 index 0000000000000000000000000000000000000000..aae52f999bef2ed32984d05cf27367671531b457 --- /dev/null +++ b/venv/lib/python3.10/site-packages/matrix_client/api.py @@ -0,0 +1,1098 @@ +# -*- coding: utf-8 -*- +# Copyright 2015 OpenMarket Ltd +# Copyright 2017, 2018 Adam Beckmeyer +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import warnings +from requests import Session, RequestException +from time import time, sleep +from .__init__ import __version__ +from .errors import MatrixError, MatrixRequestError, MatrixHttpLibError +from urllib3.util import parse_url +from urllib3.exceptions import LocationParseError + +try: + from urllib import quote +except ImportError: + from urllib.parse import quote + +MATRIX_V2_API_PATH = "/_matrix/client/r0" + + +class MatrixHttpApi(object): + """Contains all raw Matrix HTTP Client-Server API calls. + + For room and sync handling, consider using MatrixClient. + + Args: + base_url (str): The home server URL e.g. 'http://localhost:8008' + token (str): Optional. The client's access token. + identity (str): Optional. The mxid to act as (For application services only). + default_429_wait_ms (int): Optional. Time in millseconds to wait before retrying + a request when server returns a HTTP 429 + response without a 'retry_after_ms' key. + use_authorization_header (bool): Optional. Use Authorization header instead + ` of access_token query parameter. + + Examples: + Create a client and send a message:: + + matrix = MatrixHttpApi("https://matrix.org", token="foobar") + response = matrix.sync() + response = matrix.send_message("!roomid:matrix.org", "Hello!") + """ + + def __init__( + self, base_url, token=None, identity=None, + default_429_wait_ms=5000, + use_authorization_header=True + ): + try: + scheme, auth, host, port, path, query, fragment = parse_url(base_url) + except LocationParseError: + raise MatrixError("Invalid homeserver url %s" % base_url) + if not scheme: + raise MatrixError("No scheme in homeserver url %s" % base_url) + self._base_url = base_url + + self.token = token + self.identity = identity + self.txn_id = 0 + self.validate_cert = True + self.session = Session() + self.default_429_wait_ms = default_429_wait_ms + self.use_authorization_header = use_authorization_header + + def initial_sync(self, limit=1): + """ + .. warning:: + + Deprecated. Use sync instead. + + Perform /initialSync. + + Args: + limit (int): The limit= param to provide. + """ + warnings.warn("initial_sync is deprecated. Use sync instead.", DeprecationWarning) + return self._send("GET", "/initialSync", query_params={"limit": limit}) + + def sync(self, since=None, timeout_ms=30000, filter=None, + full_state=None, set_presence=None): + """ Perform a sync request. + + Args: + since (str): Optional. A token which specifies where to continue a sync from. + timeout_ms (int): Optional. The time in milliseconds to wait. + filter (int|str): Either a Filter ID or a JSON string. + full_state (bool): Return the full state for every room the user has joined + Defaults to false. + set_presence (str): Should the client be marked as "online" or" offline" + """ + + request = { + # non-integer timeouts appear to cause issues + "timeout": int(timeout_ms) + } + + if since: + request["since"] = since + + if filter: + request["filter"] = filter + + if full_state: + request["full_state"] = json.dumps(full_state) + + if set_presence: + request["set_presence"] = set_presence + + return self._send("GET", "/sync", query_params=request, + api_path=MATRIX_V2_API_PATH) + + def validate_certificate(self, valid): + self.validate_cert = valid + + def register(self, auth_body=None, kind="user", bind_email=None, + username=None, password=None, device_id=None, + initial_device_display_name=None, inhibit_login=None): + """Performs /register. + + Args: + auth_body (dict): Authentication Params. + kind (str): Specify kind of account to register. Can be 'guest' or 'user'. + bind_email (bool): Whether to use email in registration and authentication. + username (str): The localpart of a Matrix ID. + password (str): The desired password of the account. + device_id (str): ID of the client device. + initial_device_display_name (str): Display name to be assigned. + inhibit_login (bool): Whether to login after registration. Defaults to false. + """ + content = {} + content["kind"] = kind + if auth_body: + content["auth"] = auth_body + if username: + content["username"] = username + if password: + content["password"] = password + if device_id: + content["device_id"] = device_id + if initial_device_display_name: + content["initial_device_display_name"] = \ + initial_device_display_name + if bind_email: + content["bind_email"] = bind_email + if inhibit_login: + content["inhibit_login"] = inhibit_login + return self._send( + "POST", + "/register", + content=content, + query_params={'kind': kind} + ) + + def login(self, login_type, **kwargs): + """Perform /login. + + Args: + login_type (str): The value for the 'type' key. + **kwargs: Additional key/values to add to the JSON submitted. + """ + content = { + "type": login_type + } + for key in kwargs: + if kwargs[key]: + content[key] = kwargs[key] + + return self._send("POST", "/login", content) + + def logout(self): + """Perform /logout. + """ + return self._send("POST", "/logout") + + def logout_all(self): + """Perform /logout/all.""" + return self._send("POST", "/logout/all") + + def create_room( + self, + alias=None, + name=None, + is_public=False, + invitees=None, + federate=None + ): + """Perform /createRoom. + + Args: + alias (str): Optional. The room alias name to set for this room. + name (str): Optional. Name for new room. + is_public (bool): Optional. The public/private visibility. + invitees (list): Optional. The list of user IDs to invite. + federate (bool): Optional. Сan a room be federated. + Default to True. + """ + content = { + "visibility": "public" if is_public else "private" + } + if alias: + content["room_alias_name"] = alias + if invitees: + content["invite"] = invitees + if name: + content["name"] = name + if federate is not None: + content["creation_content"] = {'m.federate': federate} + return self._send("POST", "/createRoom", content) + + def join_room(self, room_id_or_alias): + """Performs /join/$room_id + + Args: + room_id_or_alias (str): The room ID or room alias to join. + """ + if not room_id_or_alias: + raise MatrixError("No alias or room ID to join.") + + path = "/join/%s" % quote(room_id_or_alias) + + return self._send("POST", path) + + def event_stream(self, from_token, timeout=30000): + """ Deprecated. Use sync instead. + Performs /events + + Args: + from_token (str): The 'from' query parameter. + timeout (int): Optional. The 'timeout' query parameter. + """ + warnings.warn("event_stream is deprecated. Use sync instead.", + DeprecationWarning) + path = "/events" + return self._send( + "GET", path, query_params={ + "timeout": timeout, + "from": from_token + } + ) + + def send_state_event(self, room_id, event_type, content, state_key="", + timestamp=None): + """Perform PUT /rooms/$room_id/state/$event_type + + Args: + room_id(str): The room ID to send the state event in. + event_type(str): The state event type to send. + content(dict): The JSON content to send. + state_key(str): Optional. The state key for the event. + timestamp (int): Set origin_server_ts (For application services only) + """ + path = "/rooms/%s/state/%s" % ( + quote(room_id), quote(event_type), + ) + if state_key: + path += "/%s" % (quote(state_key)) + params = {} + if timestamp: + params["ts"] = timestamp + return self._send("PUT", path, content, query_params=params) + + def get_state_event(self, room_id, event_type): + """Perform GET /rooms/$room_id/state/$event_type + + Args: + room_id(str): The room ID. + event_type (str): The type of the event. + + Raises: + MatrixRequestError(code=404) if the state event is not found. + """ + return self._send("GET", "/rooms/{}/state/{}".format(quote(room_id), event_type)) + + def send_message_event(self, room_id, event_type, content, txn_id=None, + timestamp=None): + """Perform PUT /rooms/$room_id/send/$event_type + + Args: + room_id (str): The room ID to send the message event in. + event_type (str): The event type to send. + content (dict): The JSON content to send. + txn_id (int): Optional. The transaction ID to use. + timestamp (int): Set origin_server_ts (For application services only) + """ + if not txn_id: + txn_id = self._make_txn_id() + + path = "/rooms/%s/send/%s/%s" % ( + quote(room_id), quote(event_type), quote(str(txn_id)), + ) + params = {} + if timestamp: + params["ts"] = timestamp + return self._send("PUT", path, content, query_params=params) + + def redact_event(self, room_id, event_id, reason=None, txn_id=None, timestamp=None): + """Perform PUT /rooms/$room_id/redact/$event_id/$txn_id/ + + Args: + room_id(str): The room ID to redact the message event in. + event_id(str): The event id to redact. + reason (str): Optional. The reason the message was redacted. + txn_id(int): Optional. The transaction ID to use. + timestamp(int): Optional. Set origin_server_ts (For application services only) + """ + if not txn_id: + txn_id = self._make_txn_id() + + path = '/rooms/%s/redact/%s/%s' % ( + room_id, event_id, txn_id + ) + content = {} + if reason: + content['reason'] = reason + params = {} + if timestamp: + params["ts"] = timestamp + return self._send("PUT", path, content, query_params=params) + + # content_type can be a image,audio or video + # extra information should be supplied, see + # https://matrix.org/docs/spec/r0.0.1/client_server.html + def send_content(self, room_id, item_url, item_name, msg_type, + extra_information=None, timestamp=None): + if extra_information is None: + extra_information = {} + + content_pack = { + "url": item_url, + "msgtype": msg_type, + "body": item_name, + "info": extra_information + } + return self.send_message_event(room_id, "m.room.message", content_pack, + timestamp=timestamp) + + # http://matrix.org/docs/spec/client_server/r0.2.0.html#m-location + def send_location(self, room_id, geo_uri, name, thumb_url=None, thumb_info=None, + timestamp=None): + """Send m.location message event + + Args: + room_id (str): The room ID to send the event in. + geo_uri (str): The geo uri representing the location. + name (str): Description for the location. + thumb_url (str): URL to the thumbnail of the location. + thumb_info (dict): Metadata about the thumbnail, type ImageInfo. + timestamp (int): Set origin_server_ts (For application services only) + """ + content_pack = { + "geo_uri": geo_uri, + "msgtype": "m.location", + "body": name, + } + if thumb_url: + content_pack["thumbnail_url"] = thumb_url + if thumb_info: + content_pack["thumbnail_info"] = thumb_info + + return self.send_message_event(room_id, "m.room.message", content_pack, + timestamp=timestamp) + + def send_message(self, room_id, text_content, msgtype="m.text", timestamp=None): + """Perform PUT /rooms/$room_id/send/m.room.message + + Args: + room_id (str): The room ID to send the event in. + text_content (str): The m.text body to send. + timestamp (int): Set origin_server_ts (For application services only) + """ + return self.send_message_event( + room_id, "m.room.message", + self.get_text_body(text_content, msgtype), + timestamp=timestamp + ) + + def send_emote(self, room_id, text_content, timestamp=None): + """Perform PUT /rooms/$room_id/send/m.room.message with m.emote msgtype + + Args: + room_id (str): The room ID to send the event in. + text_content (str): The m.emote body to send. + timestamp (int): Set origin_server_ts (For application services only) + """ + return self.send_message_event( + room_id, "m.room.message", + self.get_emote_body(text_content), + timestamp=timestamp + ) + + def send_notice(self, room_id, text_content, timestamp=None): + """Perform PUT /rooms/$room_id/send/m.room.message with m.notice msgtype + + Args: + room_id (str): The room ID to send the event in. + text_content (str): The m.notice body to send. + timestamp (int): Set origin_server_ts (For application services only) + """ + body = { + "msgtype": "m.notice", + "body": text_content + } + return self.send_message_event(room_id, "m.room.message", body, + timestamp=timestamp) + + def get_room_messages(self, room_id, token, direction, limit=10, to=None): + """Perform GET /rooms/{roomId}/messages. + + Args: + room_id (str): The room's id. + token (str): The token to start returning events from. + direction (str): The direction to return events from. One of: ["b", "f"]. + limit (int): The maximum number of events to return. + to (str): The token to stop returning events at. + """ + query = { + "roomId": room_id, + "from": token, + "dir": direction, + "limit": limit, + } + + if to: + query["to"] = to + + return self._send("GET", "/rooms/{}/messages".format(quote(room_id)), + query_params=query, api_path="/_matrix/client/r0") + + def get_room_name(self, room_id): + """Perform GET /rooms/$room_id/state/m.room.name + Args: + room_id(str): The room ID + """ + return self.get_state_event(room_id, "m.room.name") + + def set_room_name(self, room_id, name, timestamp=None): + """Perform PUT /rooms/$room_id/state/m.room.name + Args: + room_id (str): The room ID + name (str): The new room name + timestamp (int): Set origin_server_ts (For application services only) + """ + body = { + "name": name + } + return self.send_state_event(room_id, "m.room.name", body, timestamp=timestamp) + + def get_room_topic(self, room_id): + """Perform GET /rooms/$room_id/state/m.room.topic + Args: + room_id (str): The room ID + """ + return self.get_state_event(room_id, "m.room.topic") + + def set_room_topic(self, room_id, topic, timestamp=None): + """Perform PUT /rooms/$room_id/state/m.room.topic + Args: + room_id (str): The room ID + topic (str): The new room topic + timestamp (int): Set origin_server_ts (For application services only) + """ + body = { + "topic": topic + } + return self.send_state_event(room_id, "m.room.topic", body, timestamp=timestamp) + + def get_power_levels(self, room_id): + """Perform GET /rooms/$room_id/state/m.room.power_levels + + Args: + room_id(str): The room ID + """ + return self.get_state_event(room_id, "m.room.power_levels") + + def set_power_levels(self, room_id, content): + """Perform PUT /rooms/$room_id/state/m.room.power_levels + + Note that any power levels which are not explicitly specified + in the content arg are reset to default values. + + Args: + room_id (str): The room ID + content (dict): The JSON content to send. See example content below. + + Example:: + + api = MatrixHttpApi("http://example.com", token="foobar") + api.set_power_levels("!exampleroom:example.com", + { + "ban": 50, # defaults to 50 if unspecified + "events": { + "m.room.name": 100, # must have PL 100 to change room name + "m.room.power_levels": 100 # must have PL 100 to change PLs + }, + "events_default": 0, # defaults to 0 + "invite": 50, # defaults to 50 + "kick": 50, # defaults to 50 + "redact": 50, # defaults to 50 + "state_default": 50, # defaults to 50 if m.room.power_levels exists + "users": { + "@someguy:example.com": 100 # defaults to 0 + }, + "users_default": 0 # defaults to 0 + } + ) + """ + # Synapse returns M_UNKNOWN if body['events'] is omitted, + # as of 2016-10-31 + if "events" not in content: + content["events"] = {} + + return self.send_state_event(room_id, "m.room.power_levels", content) + + def leave_room(self, room_id): + """Perform POST /rooms/$room_id/leave + + Args: + room_id (str): The room ID + """ + return self._send("POST", "/rooms/" + room_id + "/leave", {}) + + def forget_room(self, room_id): + """Perform POST /rooms/$room_id/forget + + Args: + room_id(str): The room ID + """ + return self._send("POST", "/rooms/" + room_id + "/forget", content={}) + + def invite_user(self, room_id, user_id): + """Perform POST /rooms/$room_id/invite + + Args: + room_id (str): The room ID + user_id (str): The user ID of the invitee + """ + body = { + "user_id": user_id + } + return self._send("POST", "/rooms/" + room_id + "/invite", body) + + def kick_user(self, room_id, user_id, reason=""): + """Calls set_membership with membership="leave" for the user_id provided + """ + self.set_membership(room_id, user_id, "leave", reason) + + def get_membership(self, room_id, user_id): + """Perform GET /rooms/$room_id/state/m.room.member/$user_id + + Args: + room_id (str): The room ID + user_id (str): The user ID + """ + return self._send( + "GET", + "/rooms/%s/state/m.room.member/%s" % (room_id, user_id) + ) + + def set_membership(self, room_id, user_id, membership, reason="", profile=None, + timestamp=None): + """Perform PUT /rooms/$room_id/state/m.room.member/$user_id + + Args: + room_id (str): The room ID + user_id (str): The user ID + membership (str): New membership value + reason (str): The reason + timestamp (int): Set origin_server_ts (For application services only) + """ + if profile is None: + profile = {} + body = { + "membership": membership, + "reason": reason + } + if 'displayname' in profile: + body["displayname"] = profile["displayname"] + if 'avatar_url' in profile: + body["avatar_url"] = profile["avatar_url"] + + return self.send_state_event(room_id, "m.room.member", body, state_key=user_id, + timestamp=timestamp) + + def ban_user(self, room_id, user_id, reason=""): + """Perform POST /rooms/$room_id/ban + + Args: + room_id (str): The room ID + user_id (str): The user ID of the banee(sic) + reason (str): The reason for this ban + """ + body = { + "user_id": user_id, + "reason": reason + } + return self._send("POST", "/rooms/" + room_id + "/ban", body) + + def unban_user(self, room_id, user_id): + """Perform POST /rooms/$room_id/unban + + Args: + room_id (str): The room ID + user_id (str): The user ID of the banee(sic) + """ + body = { + "user_id": user_id + } + return self._send("POST", "/rooms/" + room_id + "/unban", body) + + def get_user_tags(self, user_id, room_id): + return self._send( + "GET", + "/user/%s/rooms/%s/tags" % (user_id, room_id), + ) + + def remove_user_tag(self, user_id, room_id, tag): + return self._send( + "DELETE", + "/user/%s/rooms/%s/tags/%s" % (user_id, room_id, tag), + ) + + def add_user_tag(self, user_id, room_id, tag, order=None, body=None): + if body: + pass + elif order: + body = {"order": order} + else: + body = {} + return self._send( + "PUT", + "/user/%s/rooms/%s/tags/%s" % (user_id, room_id, tag), + body, + ) + + def set_account_data(self, user_id, type, account_data): + return self._send( + "PUT", + "/user/%s/account_data/%s" % (user_id, type), + account_data, + ) + + def set_room_account_data(self, user_id, room_id, type, account_data): + return self._send( + "PUT", + "/user/%s/rooms/%s/account_data/%s" % (user_id, room_id, type), + account_data + ) + + def get_room_state(self, room_id): + """Perform GET /rooms/$room_id/state + + Args: + room_id (str): The room ID + """ + return self._send("GET", "/rooms/" + room_id + "/state") + + def get_text_body(self, text, msgtype="m.text"): + return { + "msgtype": msgtype, + "body": text + } + + def get_emote_body(self, text): + return { + "msgtype": "m.emote", + "body": text + } + + def get_filter(self, user_id, filter_id): + return self._send("GET", "/user/{userId}/filter/{filterId}" + .format(userId=user_id, filterId=filter_id)) + + def create_filter(self, user_id, filter_params): + return self._send("POST", + "/user/{userId}/filter".format(userId=user_id), + filter_params) + + def _send(self, method, path, content=None, query_params=None, headers=None, + api_path=MATRIX_V2_API_PATH, return_json=True): + if query_params is None: + query_params = {} + if headers is None: + headers = {} + + if "User-Agent" not in headers: + headers["User-Agent"] = "matrix-python-sdk/%s" % __version__ + + method = method.upper() + if method not in ["GET", "PUT", "DELETE", "POST"]: + raise MatrixError("Unsupported HTTP method: %s" % method) + + if "Content-Type" not in headers: + headers["Content-Type"] = "application/json" + + if self.use_authorization_header: + headers["Authorization"] = 'Bearer %s' % self.token + else: + query_params["access_token"] = self.token + + if self.identity: + query_params["user_id"] = self.identity + + endpoint = self._base_url + api_path + path + + if headers["Content-Type"] == "application/json" and content is not None: + content = json.dumps(content) + + while True: + try: + response = self.session.request( + method, endpoint, + params=query_params, + data=content, + headers=headers, + verify=self.validate_cert + ) + except RequestException as e: + raise MatrixHttpLibError(e, method, endpoint) + + if response.status_code == 429: + waittime = self.default_429_wait_ms / 1000 + try: + waittime = response.json()['retry_after_ms'] / 1000 + except KeyError: + try: + errordata = json.loads(response.json()['error']) + waittime = errordata['retry_after_ms'] / 1000 + except KeyError: + pass + sleep(waittime) + else: + break + + if response.status_code < 200 or response.status_code >= 300: + raise MatrixRequestError( + code=response.status_code, content=response.text + ) + if return_json: + return response.json() + else: + return response + + def media_upload(self, content, content_type, filename=None): + query_params = {} + if filename is not None: + query_params['filename'] = filename + + return self._send( + "POST", "", + content=content, + headers={"Content-Type": content_type}, + api_path="/_matrix/media/r0/upload", + query_params=query_params + ) + + def get_display_name(self, user_id): + content = self._send("GET", "/profile/%s/displayname" % user_id) + return content.get('displayname', None) + + def set_display_name(self, user_id, display_name): + content = {"displayname": display_name} + return self._send("PUT", "/profile/%s/displayname" % user_id, content) + + def get_avatar_url(self, user_id): + content = self._send("GET", "/profile/%s/avatar_url" % user_id) + return content.get('avatar_url', None) + + def set_avatar_url(self, user_id, avatar_url): + content = {"avatar_url": avatar_url} + return self._send("PUT", "/profile/%s/avatar_url" % user_id, content) + + def get_download_url(self, mxcurl): + if mxcurl.startswith('mxc://'): + return self._base_url + "/_matrix/media/r0/download/" + mxcurl[6:] + else: + raise ValueError("MXC URL did not begin with 'mxc://'") + + def media_download(self, mxcurl, allow_remote=True): + """Download raw media from provided mxc URL. + + Args: + mxcurl (str): mxc media URL. + allow_remote (bool): indicates to the server that it should not + attempt to fetch the media if it is deemed remote. Defaults + to true if not provided. + """ + query_params = {} + if not allow_remote: + query_params["allow_remote"] = False + if mxcurl.startswith('mxc://'): + return self._send( + "GET", mxcurl[6:], + api_path="/_matrix/media/r0/download/", + query_params=query_params, + return_json=False + ) + else: + raise ValueError( + "MXC URL '%s' did not begin with 'mxc://'" % mxcurl + ) + + def get_thumbnail(self, mxcurl, width, height, method='scale', allow_remote=True): + """Download raw media thumbnail from provided mxc URL. + + Args: + mxcurl (str): mxc media URL + width (int): desired thumbnail width + height (int): desired thumbnail height + method (str): thumb creation method. Must be + in ['scale', 'crop']. Default 'scale'. + allow_remote (bool): indicates to the server that it should not + attempt to fetch the media if it is deemed remote. Defaults + to true if not provided. + """ + if method not in ['scale', 'crop']: + raise ValueError( + "Unsupported thumb method '%s'" % method + ) + query_params = { + "width": width, + "height": height, + "method": method + } + if not allow_remote: + query_params["allow_remote"] = False + if mxcurl.startswith('mxc://'): + return self._send( + "GET", mxcurl[6:], + query_params=query_params, + api_path="/_matrix/media/r0/thumbnail/", + return_json=False + ) + else: + raise ValueError( + "MXC URL '%s' did not begin with 'mxc://'" % mxcurl + ) + + def get_url_preview(self, url, ts=None): + """Get preview for URL. + + Args: + url (str): URL to get a preview + ts (double): The preferred point in time to return + a preview for. The server may return a newer + version if it does not have the requested + version available. + """ + params = {'url': url} + if ts: + params['ts'] = ts + return self._send( + "GET", "", + query_params=params, + api_path="/_matrix/media/r0/preview_url" + ) + + def get_room_id(self, room_alias): + """Get room id from its alias. + + Args: + room_alias (str): The room alias name. + + Returns: + Wanted room's id. + """ + content = self._send("GET", "/directory/room/{}".format(quote(room_alias))) + return content.get("room_id", None) + + def set_room_alias(self, room_id, room_alias): + """Set alias to room id + + Args: + room_id (str): The room id. + room_alias (str): The room wanted alias name. + """ + data = { + "room_id": room_id + } + + return self._send("PUT", "/directory/room/{}".format(quote(room_alias)), + content=data) + + def remove_room_alias(self, room_alias): + """Remove mapping of an alias + + Args: + room_alias(str): The alias to be removed. + + Raises: + MatrixRequestError + """ + return self._send("DELETE", "/directory/room/{}".format(quote(room_alias))) + + def get_room_members(self, room_id): + """Get the list of members for this room. + + Args: + room_id (str): The room to get the member events for. + """ + return self._send("GET", "/rooms/{}/members".format(quote(room_id))) + + def set_join_rule(self, room_id, join_rule): + """Set the rule for users wishing to join the room. + + Args: + room_id(str): The room to set the rules for. + join_rule(str): The chosen rule. One of: ["public", "knock", + "invite", "private"] + """ + content = { + "join_rule": join_rule + } + return self.send_state_event(room_id, "m.room.join_rules", content) + + def set_guest_access(self, room_id, guest_access): + """Set the guest access policy of the room. + + Args: + room_id(str): The room to set the rules for. + guest_access(str): Wether guests can join. One of: ["can_join", + "forbidden"] + """ + content = { + "guest_access": guest_access + } + return self.send_state_event(room_id, "m.room.guest_access", content) + + def get_devices(self): + """Gets information about all devices for the current user.""" + return self._send("GET", "/devices") + + def get_device(self, device_id): + """Gets information on a single device, by device id.""" + return self._send("GET", "/devices/%s" % device_id) + + def update_device_info(self, device_id, display_name): + """Update the display name of a device. + + Args: + device_id (str): The device ID of the device to update. + display_name (str): New display name for the device. + """ + content = { + "display_name": display_name + } + return self._send("PUT", "/devices/%s" % device_id, content=content) + + def delete_device(self, auth_body, device_id): + """Deletes the given device, and invalidates any access token associated with it. + + NOTE: This endpoint uses the User-Interactive Authentication API. + + Args: + auth_body (dict): Authentication params. + device_id (str): The device ID of the device to delete. + """ + content = { + "auth": auth_body + } + return self._send("DELETE", "/devices/%s" % device_id, content=content) + + def delete_devices(self, auth_body, devices): + """Bulk deletion of devices. + + NOTE: This endpoint uses the User-Interactive Authentication API. + + Args: + auth_body (dict): Authentication params. + devices (list): List of device ID"s to delete. + """ + content = { + "auth": auth_body, + "devices": devices + } + return self._send("POST", "/delete_devices", content=content) + + def upload_keys(self, device_keys=None, one_time_keys=None): + """Publishes end-to-end encryption keys for the device. + + Said device must be the one used when logging in. + + Args: + device_keys (dict): Optional. Identity keys for the device. The required + keys are: + + | user_id (str): The ID of the user the device belongs to. Must match + the user ID used when logging in. + | device_id (str): The ID of the device these keys belong to. Must match + the device ID used when logging in. + | algorithms (list): The encryption algorithms supported by this + device. + | keys (dict): Public identity keys. Should be formatted as + : . + | signatures (dict): Signatures for the device key object. Should be + formatted as : {: } + + one_time_keys (dict): Optional. One-time public keys. Should be + formatted as : , the key format being + determined by the algorithm. + """ + content = {} + if device_keys: + content["device_keys"] = device_keys + if one_time_keys: + content["one_time_keys"] = one_time_keys + return self._send("POST", "/keys/upload", content=content) + + def query_keys(self, user_devices, timeout=None, token=None): + """Query HS for public keys by user and optionally device. + + Args: + user_devices (dict): The devices whose keys to download. Should be + formatted as : []. No device_ids indicates + all devices for the corresponding user. + timeout (int): Optional. The time (in milliseconds) to wait when + downloading keys from remote servers. + token (str): Optional. If the client is fetching keys as a result of + a device update received in a sync request, this should be the + 'since' token of that sync request, or any later sync token. + """ + content = {"device_keys": user_devices} + if timeout: + content["timeout"] = timeout + if token: + content["token"] = token + return self._send("POST", "/keys/query", content=content) + + def claim_keys(self, key_request, timeout=None): + """Claims one-time keys for use in pre-key messages. + + Args: + key_request (dict): The keys to be claimed. Format should be + : { : }. + timeout (int): Optional. The time (in milliseconds) to wait when + downloading keys from remote servers. + """ + content = {"one_time_keys": key_request} + if timeout: + content["timeout"] = timeout + return self._send("POST", "/keys/claim", content=content) + + def key_changes(self, from_token, to_token): + """Gets a list of users who have updated their device identity keys. + + Args: + from_token (str): The desired start point of the list. Should be the + next_batch field from a response to an earlier call to /sync. + to_token (str): The desired end point of the list. Should be the next_batch + field from a recent call to /sync - typically the most recent such call. + """ + params = {"from": from_token, "to": to_token} + return self._send("GET", "/keys/changes", query_params=params) + + def send_to_device(self, event_type, messages, txn_id=None): + """Sends send-to-device events to a set of client devices. + + Args: + event_type (str): The type of event to send. + messages (dict): The messages to send. Format should be + : {: }. + The device ID may also be '*', meaning all known devices for the user. + txn_id (str): Optional. The transaction ID for this event, will be generated + automatically otherwise. + """ + txn_id = txn_id if txn_id else self._make_txn_id() + return self._send( + "PUT", + "/sendToDevice/{}/{}".format(event_type, txn_id), + content={"messages": messages} + ) + + def _make_txn_id(self): + txn_id = str(self.txn_id) + str(int(time() * 1000)) + self.txn_id += 1 + return txn_id + + def whoami(self): + """Determine user_id for authenticated user. + """ + if not self.token: + raise MatrixError("Authentication required.") + return self._send( + "GET", + "/account/whoami" + ) diff --git a/venv/lib/python3.10/site-packages/matrix_client/checks.py b/venv/lib/python3.10/site-packages/matrix_client/checks.py new file mode 100644 index 0000000000000000000000000000000000000000..7e51e7c52c1ac4289148fa4917f7c248151b2fb6 --- /dev/null +++ b/venv/lib/python3.10/site-packages/matrix_client/checks.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Copyright 2015 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +def check_room_id(room_id): + if not room_id.startswith("!"): + raise ValueError("RoomIDs start with !") + + if ":" not in room_id: + raise ValueError("RoomIDs must have a domain component, seperated by a :") + + +def check_user_id(user_id): + if not user_id.startswith("@"): + raise ValueError("UserIDs start with @") + + if ":" not in user_id: + raise ValueError("UserIDs must have a domain component, seperated by a :") diff --git a/venv/lib/python3.10/site-packages/matrix_client/client.py b/venv/lib/python3.10/site-packages/matrix_client/client.py new file mode 100644 index 0000000000000000000000000000000000000000..466cc427c35f54b5f7de6e705ee1b967436f88d4 --- /dev/null +++ b/venv/lib/python3.10/site-packages/matrix_client/client.py @@ -0,0 +1,677 @@ +# -*- coding: utf-8 -*- +# Copyright 2015 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from .api import MatrixHttpApi +from .errors import MatrixRequestError, MatrixUnexpectedResponse +from .room import Room +from .user import User +try: + from .crypto.olm_device import OlmDevice + ENCRYPTION_SUPPORT = True +except ImportError: + ENCRYPTION_SUPPORT = False +from threading import Thread +from time import sleep +from uuid import uuid4 +from warnings import warn +import logging +import sys + +logger = logging.getLogger(__name__) + + +# Cache constants used when instantiating Matrix Client to specify level of caching +class CACHE(int): + pass + + +CACHE.NONE = CACHE(-1) +CACHE.SOME = CACHE(0) +CACHE.ALL = CACHE(1) +# TODO: rather than having CACHE.NONE as kwarg to MatrixClient, there should be a separate +# LightweightMatrixClient that only implements global listeners and doesn't hook into +# User, Room, etc. classes at all. + + +class MatrixClient(object): + """ + The client API for Matrix. For the raw HTTP calls, see MatrixHttpApi. + + Args: + base_url (str): The url of the HS preceding /_matrix. + e.g. (ex: https://localhost:8008 ) + token (Optional[str]): If you have an access token + supply it here. + user_id (Optional[str]): Optional. Obsolete. For backward compatibility. + valid_cert_check (bool): Check the homeservers + certificate on connections? + cache_level (CACHE): One of CACHE.NONE, CACHE.SOME, or + CACHE.ALL (defined in module namespace). + encryption (bool): Optional. Whether or not to enable end-to-end encryption + support. + encryption_conf (dict): Optional. Configuration parameters for encryption. + Refer to :func:`~matrix_client.crypto.olm_device.OlmDevice` for supported + options, since it will be passed to this class. + + Returns: + `MatrixClient` + + Raises: + `MatrixRequestError`, `ValueError` + + Examples: + + Create a new user and send a message:: + + client = MatrixClient("https://matrix.org") + token = client.register_with_password(username="foobar", + password="monkey") + room = client.create_room("myroom") + room.send_image(file_like_object) + + Send a message with an already logged in user:: + + client = MatrixClient("https://matrix.org", token="foobar", + user_id="@foobar:matrix.org") + client.add_listener(func) # NB: event stream callback + client.rooms[0].add_listener(func) # NB: callbacks just for this room. + room = client.join_room("#matrix:matrix.org") + response = room.send_text("Hello!") + response = room.kick("@bob:matrix.org") + + Incoming event callbacks (scopes):: + + def user_callback(user, incoming_event): + pass + + def room_callback(room, incoming_event): + pass + + def global_callback(incoming_event): + pass + + Attributes: + users (dict): A map from user ID to :class:`.User` object. + It is populated automatically while tracking the membership in rooms, and + shouldn't be modified directly. + A :class:`.User` object in this dict is shared between all :class:`.Room` + objects where the corresponding user is joined. + """ + + def __init__(self, base_url, token=None, user_id=None, + valid_cert_check=True, sync_filter_limit=20, + cache_level=CACHE.ALL, encryption=False, encryption_conf=None): + if user_id: + warn( + "user_id is deprecated. " + "Now it is requested from the server.", DeprecationWarning + ) + + if encryption and not ENCRYPTION_SUPPORT: + raise ValueError("Failed to enable encryption. Please make sure the olm " + "library is available.") + + self.api = MatrixHttpApi(base_url, token) + self.api.validate_certificate(valid_cert_check) + self.listeners = [] + self.presence_listeners = {} + self.invite_listeners = [] + self.left_listeners = [] + self.ephemeral_listeners = [] + self.device_id = None + self._encryption = encryption + self.encryption_conf = encryption_conf or {} + self.olm_device = None + if isinstance(cache_level, CACHE): + self._cache_level = cache_level + else: + self._cache_level = CACHE.ALL + raise ValueError( + "cache_level must be one of CACHE.NONE, CACHE.SOME, CACHE.ALL" + ) + + self.sync_token = None + self.sync_filter = '{ "room": { "timeline" : { "limit" : %i } } }' \ + % sync_filter_limit + self.sync_thread = None + self.should_listen = False + + """ Time to wait before attempting a /sync request after failing.""" + self.bad_sync_timeout_limit = 60 * 60 + self.rooms = { + # room_id: Room + } + self.users = { + # user_id: User + } + if token: + response = self.api.whoami() + self.user_id = response["user_id"] + self._sync() + + def get_sync_token(self): + warn("get_sync_token is deprecated. Directly access MatrixClient.sync_token.", + DeprecationWarning) + return self.sync_token + + def set_sync_token(self, token): + warn("set_sync_token is deprecated. Directly access MatrixClient.sync_token.", + DeprecationWarning) + self.sync_token = token + + def set_user_id(self, user_id): + warn("set_user_id is deprecated. Directly access MatrixClient.user_id.", + DeprecationWarning) + self.user_id = user_id + + # TODO: combine register methods into single register method controlled by kwargs + def register_as_guest(self): + """ Register a guest account on this HS. + Note: HS must have guest registration enabled. + Returns: + str: Access Token + Raises: + MatrixRequestError + """ + response = self.api.register(auth_body=None, kind='guest') + return self._post_registration(response) + + def register_with_password(self, username, password): + """ Register for a new account on this HS. + + Args: + username (str): Account username + password (str): Account password + + Returns: + str: Access Token + + Raises: + MatrixRequestError + """ + response = self.api.register( + auth_body={"type": "m.login.dummy"}, + kind='user', + username=username, + password=password, + ) + return self._post_registration(response) + + def _post_registration(self, response): + self.user_id = response["user_id"] + self.token = response["access_token"] + self.hs = response["home_server"] + self.api.token = self.token + self._sync() + return self.token + + def login_with_password_no_sync(self, username, password): + """Deprecated. Use ``login`` with ``sync=False``. + + Login to the homeserver. + + Args: + username (str): Account username + password (str): Account password + + Returns: + str: Access token + + Raises: + MatrixRequestError + """ + warn("login_with_password_no_sync is deprecated. Use login with sync=False.", + DeprecationWarning) + return self.login(username, password, sync=False) + + def login_with_password(self, username, password, limit=10): + """Deprecated. Use ``login`` with ``sync=True``. + + Login to the homeserver. + + Args: + username (str): Account username + password (str): Account password + limit (int): Deprecated. How many messages to return when syncing. + This will be replaced by a filter API in a later release. + + Returns: + str: Access token + + Raises: + MatrixRequestError + """ + warn("login_with_password is deprecated. Use login with sync=True.", + DeprecationWarning) + return self.login(username, password, limit, sync=True) + + def login(self, username, password, limit=10, sync=True, device_id=None): + """Login to the homeserver. + + Args: + username (str): Account username + password (str): Account password + limit (int): Deprecated. How many messages to return when syncing. + This will be replaced by a filter API in a later release. + sync (bool): Optional. Whether to initiate a /sync request after logging in. + device_id (str): Optional. ID of the client device. The server will + auto-generate a device_id if this is not specified. + + Returns: + str: Access token + + Raises: + MatrixRequestError + """ + response = self.api.login( + "m.login.password", user=username, password=password, device_id=device_id + ) + self.user_id = response["user_id"] + self.token = response["access_token"] + self.hs = response["home_server"] + self.api.token = self.token + self.device_id = response["device_id"] + + if self._encryption: + self.olm_device = OlmDevice( + self.api, self.user_id, self.device_id, **self.encryption_conf) + self.olm_device.upload_identity_keys() + self.olm_device.upload_one_time_keys() + + if sync: + """ Limit Filter """ + self.sync_filter = '{ "room": { "timeline" : { "limit" : %i } } }' % limit + self._sync() + return self.token + + def logout(self): + """ Logout from the homeserver. + """ + self.stop_listener_thread() + self.api.logout() + + # TODO: move room creation/joining to User class for future application service usage + # NOTE: we may want to leave thin wrappers here for convenience + def create_room(self, alias=None, is_public=False, invitees=None): + """ Create a new room on the homeserver. + + Args: + alias (str): The canonical_alias of the room. + is_public (bool): The public/private visibility of the room. + invitees (str[]): A set of user ids to invite into the room. + + Returns: + Room + + Raises: + MatrixRequestError + """ + response = self.api.create_room(alias=alias, + is_public=is_public, + invitees=invitees) + return self._mkroom(response["room_id"]) + + def join_room(self, room_id_or_alias): + """ Join a room. + + Args: + room_id_or_alias (str): Room ID or an alias. + + Returns: + Room + + Raises: + MatrixRequestError + """ + response = self.api.join_room(room_id_or_alias) + room_id = ( + response["room_id"] if "room_id" in response else room_id_or_alias + ) + return self._mkroom(room_id) + + def get_rooms(self): + """ Deprecated. Return a dict of {room_id: Room objects} that the user has joined. + + Returns: + Room{}: Rooms the user has joined. + """ + warn("get_rooms is deprecated. Directly access MatrixClient.rooms.", + DeprecationWarning) + return self.rooms + + # TODO: create Listener class and push as much of this logic there as possible + # NOTE: listeners related to things in rooms should be attached to Room objects + def add_listener(self, callback, event_type=None): + """ Add a listener that will send a callback when the client recieves + an event. + + Args: + callback (func(roomchunk)): Callback called when an event arrives. + event_type (str): The event_type to filter for. + + Returns: + uuid.UUID: Unique id of the listener, can be used to identify the listener. + """ + listener_uid = uuid4() + # TODO: listeners should be stored in dict and accessed/deleted directly. Add + # convenience method such that MatrixClient.listeners.new(Listener(...)) performs + # MatrixClient.listeners[uuid4()] = Listener(...) + self.listeners.append( + { + 'uid': listener_uid, + 'callback': callback, + 'event_type': event_type + } + ) + return listener_uid + + def remove_listener(self, uid): + """ Remove listener with given uid. + + Args: + uuid.UUID: Unique id of the listener to remove. + """ + self.listeners[:] = (listener for listener in self.listeners + if listener['uid'] != uid) + + def add_presence_listener(self, callback): + """ Add a presence listener that will send a callback when the client receives + a presence update. + + Args: + callback (func(roomchunk)): Callback called when a presence update arrives. + + Returns: + uuid.UUID: Unique id of the listener, can be used to identify the listener. + """ + listener_uid = uuid4() + self.presence_listeners[listener_uid] = callback + return listener_uid + + def remove_presence_listener(self, uid): + """ Remove presence listener with given uid + + Args: + uuid.UUID: Unique id of the listener to remove + """ + self.presence_listeners.pop(uid) + + def add_ephemeral_listener(self, callback, event_type=None): + """ Add an ephemeral listener that will send a callback when the client recieves + an ephemeral event. + + Args: + callback (func(roomchunk)): Callback called when an ephemeral event arrives. + event_type (str): The event_type to filter for. + + Returns: + uuid.UUID: Unique id of the listener, can be used to identify the listener. + """ + listener_id = uuid4() + self.ephemeral_listeners.append( + { + 'uid': listener_id, + 'callback': callback, + 'event_type': event_type + } + ) + return listener_id + + def remove_ephemeral_listener(self, uid): + """ Remove ephemeral listener with given uid. + + Args: + uuid.UUID: Unique id of the listener to remove. + """ + self.ephemeral_listeners[:] = (listener for listener in self.ephemeral_listeners + if listener['uid'] != uid) + + def add_invite_listener(self, callback): + """ Add a listener that will send a callback when the client receives + an invite. + + Args: + callback (func(room_id, state)): Callback called when an invite arrives. + """ + self.invite_listeners.append(callback) + + def add_leave_listener(self, callback): + """ Add a listener that will send a callback when the client has left a room. + + Args: + callback (func(room_id, room)): Callback called when the client + has left a room. + """ + self.left_listeners.append(callback) + + def listen_for_events(self, timeout_ms=30000): + """ + This function just calls _sync() + + In a future version of this sdk, this function will be deprecated and + _sync method will be renamed sync with the intention of it being called + by downstream code. + + Args: + timeout_ms (int): How long to poll the Home Server for before + retrying. + """ + # TODO: see docstring + self._sync(timeout_ms) + + def listen_forever(self, timeout_ms=30000, exception_handler=None, + bad_sync_timeout=5): + """ Keep listening for events forever. + + Args: + timeout_ms (int): How long to poll the Home Server for before + retrying. + exception_handler (func(exception)): Optional exception handler + function which can be used to handle exceptions in the caller + thread. + bad_sync_timeout (int): Base time to wait after an error before + retrying. Will be increased according to exponential backoff. + """ + _bad_sync_timeout = bad_sync_timeout + self.should_listen = True + while (self.should_listen): + try: + self._sync(timeout_ms) + _bad_sync_timeout = bad_sync_timeout + # TODO: we should also handle MatrixHttpLibError for retry in case no response + except MatrixRequestError as e: + logger.warning("A MatrixRequestError occured during sync.") + if e.code >= 500: + logger.warning("Problem occured serverside. Waiting %i seconds", + bad_sync_timeout) + sleep(bad_sync_timeout) + _bad_sync_timeout = min(_bad_sync_timeout * 2, + self.bad_sync_timeout_limit) + elif exception_handler is not None: + exception_handler(e) + else: + raise + except Exception as e: + logger.exception("Exception thrown during sync") + if exception_handler is not None: + exception_handler(e) + else: + raise + + def start_listener_thread(self, timeout_ms=30000, exception_handler=None): + """ Start a listener thread to listen for events in the background. + + Args: + timeout (int): How long to poll the Home Server for before + retrying. + exception_handler (func(exception)): Optional exception handler + function which can be used to handle exceptions in the caller + thread. + """ + try: + thread = Thread(target=self.listen_forever, + args=(timeout_ms, exception_handler)) + thread.daemon = True + self.sync_thread = thread + self.should_listen = True + thread.start() + except RuntimeError: + e = sys.exc_info()[0] + logger.error("Error: unable to start thread. %s", str(e)) + + def stop_listener_thread(self): + """ Stop listener thread running in the background + """ + if self.sync_thread: + self.should_listen = False + self.sync_thread.join() + self.sync_thread = None + + # TODO: move to User class. Consider creating lightweight Media class. + def upload(self, content, content_type, filename=None): + """ Upload content to the home server and recieve a MXC url. + + Args: + content (bytes): The data of the content. + content_type (str): The mimetype of the content. + filename (str): Optional. Filename of the content. + + Raises: + MatrixUnexpectedResponse: If the homeserver gave a strange response + MatrixRequestError: If the upload failed for some reason. + """ + try: + response = self.api.media_upload(content, content_type, filename) + if "content_uri" in response: + return response["content_uri"] + else: + raise MatrixUnexpectedResponse( + "The upload was successful, but content_uri wasn't found." + ) + except MatrixRequestError as e: + raise MatrixRequestError( + code=e.code, + content="Upload failed: %s" % e + ) + + def _mkroom(self, room_id): + room = Room(self, room_id) + if self._encryption: + try: + event = self.api.get_state_event(room_id, "m.room.encryption") + if event["algorithm"] == "m.megolm.v1.aes-sha2": + room.encrypted = True + except MatrixRequestError as e: + if e.code != 404: + raise + self.rooms[room_id] = room + return self.rooms[room_id] + + # TODO better handling of the blocking I/O caused by update_one_time_key_counts + def _sync(self, timeout_ms=30000): + response = self.api.sync(self.sync_token, timeout_ms, filter=self.sync_filter) + self.sync_token = response["next_batch"] + + if 'presence' in response and 'events' in response['presence']: + for presence_update in response['presence']['events']: + for callback in self.presence_listeners.values(): + callback(presence_update) + + if self._encryption and 'device_one_time_keys_count' in response: + self.olm_device.update_one_time_key_counts( + response['device_one_time_keys_count']) + + rooms = response.get("rooms", {}) + if 'invite' in rooms: + for room_id, invite_room in rooms['invite'].items(): + for listener in self.invite_listeners: + listener(room_id, invite_room['invite_state']) + + if 'leave' in rooms: + for room_id, left_room in rooms['leave'].items(): + for listener in self.left_listeners: + listener(room_id, left_room) + if room_id in self.rooms: + del self.rooms[room_id] + + if 'join' in rooms: + for room_id, sync_room in rooms['join'].items(): + if room_id not in self.rooms: + self._mkroom(room_id) + room = self.rooms[room_id] + # TODO: the rest of this for loop should be in room object method + room.prev_batch = sync_room["timeline"]["prev_batch"] + + if "state" in sync_room and "events" in sync_room["state"]: + for event in sync_room["state"]["events"]: + event['room_id'] = room_id + room._process_state_event(event) + + if "timeline" in sync_room and "events" in sync_room["timeline"]: + for event in sync_room["timeline"]["events"]: + event['room_id'] = room_id + room._put_event(event) + + # TODO: global listeners can still exist but work by each + # room.listeners[uuid] having reference to global listener + + # Dispatch for client (global) listeners + for listener in self.listeners: + if ( + listener['event_type'] is None or + listener['event_type'] == event['type'] + ): + listener['callback'](event) + + if "ephemeral" in sync_room and "events" in sync_room["ephemeral"]: + for event in sync_room['ephemeral']['events']: + event['room_id'] = room_id + room._put_ephemeral_event(event) + + for listener in self.ephemeral_listeners: + if ( + listener['event_type'] is None or + listener['event_type'] == event['type'] + ): + listener['callback'](event) + + def get_user(self, user_id): + """Deprecated. Return a User by their id. + + This method only instantiate a User, which should be done directly. + You can also use :attr:`users` in order to access a User object which + was created automatically. + + Args: + user_id (str): The matrix user id of a user. + """ + warn("get_user is deprecated. Directly instantiate a User instead.", + DeprecationWarning) + return User(self.api, user_id) + + # TODO: move to Room class + def remove_room_alias(self, room_alias): + """Remove mapping of an alias + + Args: + room_alias(str): The alias to be removed. + + Returns: + bool: True if the alias is removed, False otherwise. + """ + try: + self.api.remove_room_alias(room_alias) + return True + except MatrixRequestError: + return False diff --git a/venv/lib/python3.10/site-packages/matrix_client/crypto/__init__.py b/venv/lib/python3.10/site-packages/matrix_client/crypto/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.10/site-packages/matrix_client/crypto/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/matrix_client/crypto/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..96053e90ac81b83ffc93a31646d1763839206a57 Binary files /dev/null and b/venv/lib/python3.10/site-packages/matrix_client/crypto/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/matrix_client/crypto/__pycache__/olm_device.cpython-310.pyc b/venv/lib/python3.10/site-packages/matrix_client/crypto/__pycache__/olm_device.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e1c7360d8592f911bf997bc65e2bf9139c5819c0 Binary files /dev/null and b/venv/lib/python3.10/site-packages/matrix_client/crypto/__pycache__/olm_device.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/matrix_client/crypto/__pycache__/one_time_keys.cpython-310.pyc b/venv/lib/python3.10/site-packages/matrix_client/crypto/__pycache__/one_time_keys.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f6859d35ce7a8ddcbc7765377b4f2747e1096434 Binary files /dev/null and b/venv/lib/python3.10/site-packages/matrix_client/crypto/__pycache__/one_time_keys.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/matrix_client/crypto/olm_device.py b/venv/lib/python3.10/site-packages/matrix_client/crypto/olm_device.py new file mode 100644 index 0000000000000000000000000000000000000000..514965db880eb339887dcd7e564e5611c44c2140 --- /dev/null +++ b/venv/lib/python3.10/site-packages/matrix_client/crypto/olm_device.py @@ -0,0 +1,207 @@ +import logging + +import olm +from canonicaljson import encode_canonical_json + +from matrix_client.checks import check_user_id +from matrix_client.crypto.one_time_keys import OneTimeKeysManager + +logger = logging.getLogger(__name__) + + +class OlmDevice(object): + """Manages the Olm cryptographic functions. + + Has a unique Olm account which holds identity keys. + + Args: + api (MatrixHttpApi): The api object used to make requests. + user_id (str): Matrix user ID. Must match the one used when logging in. + device_id (str): Must match the one used when logging in. + signed_keys_proportion (float): Optional. The proportion of signed one-time keys + we should maintain on the HS compared to unsigned keys. The maximum value of + ``1`` means only signed keys will be uploaded, while the minimum value of + ``0`` means only unsigned keys. The actual amount of keys is determined at + runtime from the given proportion and the maximum number of one-time keys + we can physically hold. + keys_threshold (float): Optional. Threshold below which a one-time key + replenishment is triggered. Must be between ``0`` and ``1``. For example, + ``0.1`` means that new one-time keys will be uploaded when there is less than + 10% of the maximum number of one-time keys on the server. + """ + + _olm_algorithm = 'm.olm.v1.curve25519-aes-sha2' + _megolm_algorithm = 'm.megolm.v1.aes-sha2' + _algorithms = [_olm_algorithm, _megolm_algorithm] + + def __init__(self, + api, + user_id, + device_id, + signed_keys_proportion=1, + keys_threshold=0.1): + if not 0 <= signed_keys_proportion <= 1: + raise ValueError('signed_keys_proportion must be between 0 and 1.') + if not 0 <= keys_threshold <= 1: + raise ValueError('keys_threshold must be between 0 and 1.') + self.api = api + check_user_id(user_id) + self.user_id = user_id + self.device_id = device_id + self.olm_account = olm.Account() + logger.info('Initialised Olm Device.') + self.identity_keys = self.olm_account.identity_keys + # Try to maintain half the number of one-time keys libolm can hold uploaded + # on the HS. This is because some keys will be claimed by peers but not + # used instantly, and we want them to stay in libolm, until the limit is reached + # and it starts discarding keys, starting by the oldest. + target_keys_number = self.olm_account.max_one_time_keys // 2 + self.one_time_keys_manager = OneTimeKeysManager(target_keys_number, + signed_keys_proportion, + keys_threshold) + + def upload_identity_keys(self): + """Uploads this device's identity keys to HS. + + This device must be the one used when logging in. + """ + device_keys = { + 'user_id': self.user_id, + 'device_id': self.device_id, + 'algorithms': self._algorithms, + 'keys': {'{}:{}'.format(alg, self.device_id): key + for alg, key in self.identity_keys.items()} + } + self.sign_json(device_keys) + ret = self.api.upload_keys(device_keys=device_keys) + self.one_time_keys_manager.server_counts = ret['one_time_key_counts'] + logger.info('Uploaded identity keys.') + + def upload_one_time_keys(self, force_update=False): + """Uploads new one-time keys to the HS, if needed. + + Args: + force_update (bool): Fetch the number of one-time keys currently on the HS + before uploading, even if we already know one. In most cases this should + not be necessary, as we get this value from sync responses. + + Returns: + A dict containg the number of new keys that were uploaded for each key type + (signed_curve25519 or curve25519). The format is + ``: ``. If no keys of a given type have been + uploaded, the corresponding key will not be present. Consequently, an + empty dict indicates that no keys were uploaded. + """ + if force_update or not self.one_time_keys_manager.server_counts: + counts = self.api.upload_keys()['one_time_key_counts'] + self.one_time_keys_manager.server_counts = counts + + signed_keys_to_upload = self.one_time_keys_manager.signed_curve25519_to_upload + unsigned_keys_to_upload = self.one_time_keys_manager.curve25519_to_upload + + self.olm_account.generate_one_time_keys(signed_keys_to_upload + + unsigned_keys_to_upload) + + one_time_keys = {} + keys = self.olm_account.one_time_keys['curve25519'] + for i, key_id in enumerate(keys): + if i < signed_keys_to_upload: + key = self.sign_json({'key': keys[key_id]}) + key_type = 'signed_curve25519' + else: + key = keys[key_id] + key_type = 'curve25519' + one_time_keys['{}:{}'.format(key_type, key_id)] = key + + ret = self.api.upload_keys(one_time_keys=one_time_keys) + self.one_time_keys_manager.server_counts = ret['one_time_key_counts'] + self.olm_account.mark_keys_as_published() + + keys_uploaded = {} + if unsigned_keys_to_upload: + keys_uploaded['curve25519'] = unsigned_keys_to_upload + if signed_keys_to_upload: + keys_uploaded['signed_curve25519'] = signed_keys_to_upload + logger.info('Uploaded new one-time keys: %s.', keys_uploaded) + return keys_uploaded + + def update_one_time_key_counts(self, counts): + """Update data on one-time keys count and upload new ones if necessary. + + Args: + counts (dict): Counts of keys currently on the HS for each key type. + """ + self.one_time_keys_manager.server_counts = counts + if self.one_time_keys_manager.should_upload(): + logger.info('Uploading new one-time keys.') + self.upload_one_time_keys() + + def sign_json(self, json): + """Signs a JSON object. + + NOTE: The object is modified in-place and the return value can be ignored. + + As specified, this is done by encoding the JSON object without ``signatures`` or + keys grouped as ``unsigned``, using canonical encoding. + + Args: + json (dict): The JSON object to sign. + + Returns: + The same JSON object, with a ``signatures`` key added. It is formatted as + ``"signatures": ed25519:: ``. + """ + signatures = json.pop('signatures', {}) + unsigned = json.pop('unsigned', None) + + signature_base64 = self.olm_account.sign(encode_canonical_json(json)) + + key_id = 'ed25519:{}'.format(self.device_id) + signatures.setdefault(self.user_id, {})[key_id] = signature_base64 + + json['signatures'] = signatures + if unsigned: + json['unsigned'] = unsigned + + return json + + def verify_json(self, json, user_key, user_id, device_id): + """Verifies a signed key object's signature. + + The object must have a 'signatures' key associated with an object of the form + `user_id: {key_id: signature}`. + + Args: + json (dict): The JSON object to verify. + user_key (str): The public ed25519 key which was used to sign the object. + user_id (str): The user who owns the device. + device_id (str): The device who owns the key. + + Returns: + True if the verification was successful, False if not. + """ + try: + signatures = json.pop('signatures') + except KeyError: + return False + + key_id = 'ed25519:{}'.format(device_id) + try: + signature_base64 = signatures[user_id][key_id] + except KeyError: + json['signatures'] = signatures + return False + + unsigned = json.pop('unsigned', None) + + try: + olm.ed25519_verify(user_key, encode_canonical_json(json), signature_base64) + success = True + except olm.utility.OlmVerifyError: + success = False + + json['signatures'] = signatures + if unsigned: + json['unsigned'] = unsigned + + return success diff --git a/venv/lib/python3.10/site-packages/matrix_client/crypto/one_time_keys.py b/venv/lib/python3.10/site-packages/matrix_client/crypto/one_time_keys.py new file mode 100644 index 0000000000000000000000000000000000000000..131dc023f4d46377c5d2bf29f3af8329f68c4a19 --- /dev/null +++ b/venv/lib/python3.10/site-packages/matrix_client/crypto/one_time_keys.py @@ -0,0 +1,42 @@ +class OneTimeKeysManager(object): + """Handles one-time keys accounting for an OlmDevice.""" + + def __init__(self, target_keys_number, signed_keys_proportion, keys_threshold): + self.target_counts = { + 'signed_curve25519': int(round(signed_keys_proportion * target_keys_number)), + 'curve25519': int(round((1 - signed_keys_proportion) * target_keys_number)), + } + self._server_counts = {} + self.to_upload = {} + self.keys_threshold = keys_threshold + + @property + def server_counts(self): + return self._server_counts + + @server_counts.setter + def server_counts(self, server_counts): + self._server_counts = server_counts + self.update_keys_to_upload() + + def update_keys_to_upload(self): + for key_type, target_number in self.target_counts.items(): + num_keys = self._server_counts.get(key_type, 0) + num_to_create = max(target_number - num_keys, 0) + self.to_upload[key_type] = num_to_create + + def should_upload(self): + if not self._server_counts: + return True + for key_type, target_number in self.target_counts.items(): + if self._server_counts.get(key_type, 0) < target_number * self.keys_threshold: + return True + return False + + @property + def curve25519_to_upload(self): + return self.to_upload.get('curve25519', 0) + + @property + def signed_curve25519_to_upload(self): + return self.to_upload.get('signed_curve25519', 0) diff --git a/venv/lib/python3.10/site-packages/matrix_client/errors.py b/venv/lib/python3.10/site-packages/matrix_client/errors.py new file mode 100644 index 0000000000000000000000000000000000000000..be1356407cbf007ad201827a7ea59993728e0333 --- /dev/null +++ b/venv/lib/python3.10/site-packages/matrix_client/errors.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +# Copyright 2015 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +class MatrixError(Exception): + """A generic Matrix error. Specific errors will subclass this.""" + pass + + +class MatrixUnexpectedResponse(MatrixError): + """The home server gave an unexpected response. """ + + def __init__(self, content=""): + super(MatrixUnexpectedResponse, self).__init__(content) + self.content = content + + +class MatrixRequestError(MatrixError): + """ The home server returned an error response. """ + + def __init__(self, code=0, content=""): + super(MatrixRequestError, self).__init__("%d: %s" % (code, content)) + self.code = code + self.content = content + + +class MatrixHttpLibError(MatrixError): + """The library used for http requests raised an exception.""" + + def __init__(self, original_exception, method, endpoint): + super(MatrixHttpLibError, self).__init__( + "Something went wrong in {} requesting {}: {}".format(method, + endpoint, + original_exception) + ) + self.original_exception = original_exception diff --git a/venv/lib/python3.10/site-packages/matrix_client/room.py b/venv/lib/python3.10/site-packages/matrix_client/room.py new file mode 100644 index 0000000000000000000000000000000000000000..9083ed4a1d24d6f85f4ea53964affd30a0d00af6 --- /dev/null +++ b/venv/lib/python3.10/site-packages/matrix_client/room.py @@ -0,0 +1,689 @@ +# -*- coding: utf-8 -*- +# Copyright 2015 OpenMarket Ltd +# Copyright 2018 Adam Beckmeyer +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import logging +import re +from uuid import uuid4 + +from .checks import check_room_id +from .user import User +from .errors import MatrixRequestError + +logger = logging.getLogger(__name__) + + +class Room(object): + """Call room-specific functions after joining a room from the client. + + NOTE: This should ideally be called from within the Client. + NOTE: This does not verify the room with the Home Server. + """ + + def __init__(self, client, room_id): + check_room_id(room_id) + + self.room_id = room_id + self.client = client + self.listeners = [] + self.state_listeners = [] + self.ephemeral_listeners = [] + self.events = [] + self.event_history_limit = 20 + self.name = None + self.canonical_alias = None + self.aliases = [] + self.topic = None + self.invite_only = None + self.guest_access = None + self._prev_batch = None + self._members = {} + self.members_displaynames = { + # user_id: displayname + } + self.encrypted = False + + def set_user_profile(self, + displayname=None, + avatar_url=None, + reason="Changing room profile information"): + """Set user profile within a room. + + This sets displayname and avatar_url for the logged in user only in a + specific room. It does not change the user's global user profile. + """ + member = self.client.api.get_membership(self.room_id, self.client.user_id) + if member["membership"] != "join": + raise Exception("Can't set profile if you have not joined the room.") + if displayname is None: + displayname = member["displayname"] + if avatar_url is None: + avatar_url = member["avatar_url"] + self.client.api.set_membership( + self.room_id, + self.client.user_id, + 'join', + reason, { + "displayname": displayname, + "avatar_url": avatar_url + } + ) + + @property + def display_name(self): + """Calculates the display name for a room.""" + if self.name: + return self.name + elif self.canonical_alias: + return self.canonical_alias + + # Member display names without me + members = [u.get_display_name(self) for u in self.get_joined_members() if + self.client.user_id != u.user_id] + members.sort() + + if len(members) == 1: + return members[0] + elif len(members) == 2: + return "{0} and {1}".format(members[0], members[1]) + elif len(members) > 2: + return "{0} and {1} others".format(members[0], len(members) - 1) + else: # len(members) <= 0 or not an integer + # TODO i18n + return "Empty room" + + def send_text(self, text): + """Send a plain text message to the room.""" + return self.client.api.send_message(self.room_id, text) + + def get_html_content(self, html, body=None, msgtype="m.text"): + return { + "body": body if body else re.sub('<[^<]+?>', '', html), + "msgtype": msgtype, + "format": "org.matrix.custom.html", + "formatted_body": html + } + + def send_html(self, html, body=None, msgtype="m.text"): + """Send an html formatted message. + + Args: + html (str): The html formatted message to be sent. + body (str): The unformatted body of the message to be sent. + """ + return self.client.api.send_message_event( + self.room_id, "m.room.message", self.get_html_content(html, body, msgtype)) + + def set_account_data(self, type, account_data): + return self.client.api.set_room_account_data( + self.client.user_id, self.room_id, type, account_data) + + def get_tags(self): + return self.client.api.get_user_tags(self.client.user_id, self.room_id) + + def remove_tag(self, tag): + return self.client.api.remove_user_tag( + self.client.user_id, self.room_id, tag + ) + + def add_tag(self, tag, order=None, content=None): + return self.client.api.add_user_tag( + self.client.user_id, self.room_id, + tag, order, content + ) + + def send_emote(self, text): + """Send an emote (/me style) message to the room.""" + return self.client.api.send_emote(self.room_id, text) + + def send_file(self, url, name, **fileinfo): + """Send a pre-uploaded file to the room. + + See http://matrix.org/docs/spec/r0.2.0/client_server.html#m-file for + fileinfo. + + Args: + url (str): The mxc url of the file. + name (str): The filename of the image. + fileinfo (): Extra information about the file + """ + + return self.client.api.send_content( + self.room_id, url, name, "m.file", + extra_information=fileinfo + ) + + def send_notice(self, text): + """Send a notice (from bot) message to the room.""" + return self.client.api.send_notice(self.room_id, text) + + # See http://matrix.org/docs/spec/r0.0.1/client_server.html#m-image for the + # imageinfo args. + def send_image(self, url, name, **imageinfo): + """Send a pre-uploaded image to the room. + + See http://matrix.org/docs/spec/r0.0.1/client_server.html#m-image + for imageinfo + + Args: + url (str): The mxc url of the image. + name (str): The filename of the image. + imageinfo (): Extra information about the image. + """ + return self.client.api.send_content( + self.room_id, url, name, "m.image", + extra_information=imageinfo + ) + + def send_location(self, geo_uri, name, thumb_url=None, **thumb_info): + """Send a location to the room. + + See http://matrix.org/docs/spec/client_server/r0.2.0.html#m-location + for thumb_info + + Args: + geo_uri (str): The geo uri representing the location. + name (str): Description for the location. + thumb_url (str): URL to the thumbnail of the location. + thumb_info (): Metadata about the thumbnail, type ImageInfo. + """ + return self.client.api.send_location(self.room_id, geo_uri, name, + thumb_url, thumb_info) + + def send_video(self, url, name, **videoinfo): + """Send a pre-uploaded video to the room. + + See http://matrix.org/docs/spec/client_server/r0.2.0.html#m-video + for videoinfo + + Args: + url (str): The mxc url of the video. + name (str): The filename of the video. + videoinfo (): Extra information about the video. + """ + return self.client.api.send_content(self.room_id, url, name, "m.video", + extra_information=videoinfo) + + def send_audio(self, url, name, **audioinfo): + """Send a pre-uploaded audio to the room. + + See http://matrix.org/docs/spec/client_server/r0.2.0.html#m-audio + for audioinfo + + Args: + url (str): The mxc url of the audio. + name (str): The filename of the audio. + audioinfo (): Extra information about the audio. + """ + return self.client.api.send_content(self.room_id, url, name, "m.audio", + extra_information=audioinfo) + + def redact_message(self, event_id, reason=None): + """Redacts the message with specified event_id for the given reason. + + See https://matrix.org/docs/spec/r0.0.1/client_server.html#id112 + """ + return self.client.api.redact_event(self.room_id, event_id, reason) + + def add_listener(self, callback, event_type=None): + """Add a callback handler for events going to this room. + + Args: + callback (func(room, event)): Callback called when an event arrives. + event_type (str): The event_type to filter for. + Returns: + uuid.UUID: Unique id of the listener, can be used to identify the listener. + """ + listener_id = uuid4() + self.listeners.append( + { + 'uid': listener_id, + 'callback': callback, + 'event_type': event_type + } + ) + return listener_id + + def remove_listener(self, uid): + """Remove listener with given uid.""" + self.listeners[:] = (listener for listener in self.listeners + if listener['uid'] != uid) + + def add_ephemeral_listener(self, callback, event_type=None): + """Add a callback handler for ephemeral events going to this room. + + Args: + callback (func(room, event)): Callback called when an ephemeral event arrives. + event_type (str): The event_type to filter for. + Returns: + uuid.UUID: Unique id of the listener, can be used to identify the listener. + """ + listener_id = uuid4() + self.ephemeral_listeners.append( + { + 'uid': listener_id, + 'callback': callback, + 'event_type': event_type + } + ) + return listener_id + + def remove_ephemeral_listener(self, uid): + """Remove ephemeral listener with given uid.""" + self.ephemeral_listeners[:] = (listener for listener in self.ephemeral_listeners + if listener['uid'] != uid) + + def add_state_listener(self, callback, event_type=None): + """Add a callback handler for state events going to this room. + + Args: + callback (func(roomchunk)): Callback called when an event arrives. + event_type (str): The event_type to filter for. + """ + self.state_listeners.append( + { + 'callback': callback, + 'event_type': event_type + } + ) + + def _put_event(self, event): + self.events.append(event) + if len(self.events) > self.event_history_limit: + self.events.pop(0) + if 'state_key' in event: + self._process_state_event(event) + + # Dispatch for room-specific listeners + for listener in self.listeners: + if listener['event_type'] is None or listener['event_type'] == event['type']: + listener['callback'](self, event) + + def _put_ephemeral_event(self, event): + # Dispatch for room-specific listeners + for listener in self.ephemeral_listeners: + if listener['event_type'] is None or listener['event_type'] == event['type']: + listener['callback'](self, event) + + def get_events(self): + """Get the most recent events for this room.""" + return self.events + + def invite_user(self, user_id): + """Invite a user to this room. + + Returns: + boolean: Whether invitation was sent. + """ + try: + self.client.api.invite_user(self.room_id, user_id) + return True + except MatrixRequestError: + return False + + def kick_user(self, user_id, reason=""): + """Kick a user from this room. + + + Args: + user_id (str): The matrix user id of a user. + reason (str): A reason for kicking the user. + + Returns: + boolean: Whether user was kicked. + """ + try: + self.client.api.kick_user(self.room_id, user_id) + return True + except MatrixRequestError: + return False + + def ban_user(self, user_id, reason): + """Ban a user from this room + + Args: + user_id (str): The matrix user id of a user. + reason (str): A reason for banning the user. + + Returns: + boolean: The user was banned. + """ + try: + self.client.api.ban_user(self.room_id, user_id, reason) + return True + except MatrixRequestError: + return False + + def unban_user(self, user_id): + """Unban a user from this room + + Returns: + boolean: The user was unbanned. + """ + try: + self.client.api.unban_user(self.room_id, user_id) + return True + except MatrixRequestError: + return False + + def leave(self): + """Leave the room. + + Returns: + boolean: Leaving the room was successful. + """ + try: + self.client.api.leave_room(self.room_id) + del self.client.rooms[self.room_id] + return True + except MatrixRequestError: + return False + + def update_room_name(self): + """Updates self.name and returns True if room name has changed.""" + try: + response = self.client.api.get_room_name(self.room_id) + if "name" in response and response["name"] != self.name: + self.name = response["name"] + return True + else: + return False + except MatrixRequestError: + return False + + def set_room_name(self, name): + """Return True if room name successfully changed.""" + try: + self.client.api.set_room_name(self.room_id, name) + self.name = name + return True + except MatrixRequestError: + return False + + def send_state_event(self, event_type, content, state_key=""): + """Send a state event to the room. + + Args: + event_type (str): The type of event that you are sending. + content (): An object with the content of the message. + state_key (str, optional): A unique key to identify the state. + """ + return self.client.api.send_state_event( + self.room_id, + event_type, + content, + state_key + ) + + def update_room_topic(self): + """Updates self.topic and returns True if room topic has changed.""" + try: + response = self.client.api.get_room_topic(self.room_id) + if "topic" in response and response["topic"] != self.topic: + self.topic = response["topic"] + return True + else: + return False + except MatrixRequestError: + return False + + def set_room_topic(self, topic): + """Set room topic. + + Returns: + boolean: True if the topic changed, False if not + """ + try: + self.client.api.set_room_topic(self.room_id, topic) + self.topic = topic + return True + except MatrixRequestError: + return False + + def update_aliases(self): + """Get aliases information from room state. + + Returns: + boolean: True if the aliases changed, False if not + """ + try: + response = self.client.api.get_room_state(self.room_id) + for chunk in response: + if "content" in chunk and "aliases" in chunk["content"]: + if chunk["content"]["aliases"] != self.aliases: + self.aliases = chunk["content"]["aliases"] + return True + else: + return False + except MatrixRequestError: + return False + + def add_room_alias(self, room_alias): + """Add an alias to the room and return True if successful.""" + try: + self.client.api.set_room_alias(self.room_id, room_alias) + return True + except MatrixRequestError: + return False + + def get_joined_members(self): + """Returns list of joined members (User objects).""" + if self._members: + return list(self._members.values()) + response = self.client.api.get_room_members(self.room_id) + for event in response["chunk"]: + if event["content"]["membership"] == "join": + user_id = event["state_key"] + self._add_member(user_id, event["content"].get("displayname")) + return list(self._members.values()) + + def _add_member(self, user_id, displayname=None): + if displayname: + self.members_displaynames[user_id] = displayname + if user_id in self._members: + return + if user_id in self.client.users: + self._members[user_id] = self.client.users[user_id] + return + self._members[user_id] = User(self.client.api, user_id, displayname) + self.client.users[user_id] = self._members[user_id] + + def backfill_previous_messages(self, reverse=False, limit=10): + """Backfill handling of previous messages. + + Args: + reverse (bool): When false messages will be backfilled in their original + order (old to new), otherwise the order will be reversed (new to old). + limit (int): Number of messages to go back. + """ + res = self.client.api.get_room_messages(self.room_id, self.prev_batch, + direction="b", limit=limit) + events = res["chunk"] + if not reverse: + events = reversed(events) + for event in events: + self._put_event(event) + + def modify_user_power_levels(self, users=None, users_default=None): + """Modify the power level for a subset of users + + Args: + users(dict): Power levels to assign to specific users, in the form + {"@name0:host0": 10, "@name1:host1": 100, "@name3:host3", None} + A level of None causes the user to revert to the default level + as specified by users_default. + users_default(int): Default power level for users in the room + + Returns: + True if successful, False if not + """ + try: + content = self.client.api.get_power_levels(self.room_id) + if users_default: + content["users_default"] = users_default + + if users: + if "users" in content: + content["users"].update(users) + else: + content["users"] = users + + # Remove any keys with value None + for user, power_level in list(content["users"].items()): + if power_level is None: + del content["users"][user] + self.client.api.set_power_levels(self.room_id, content) + return True + except MatrixRequestError: + return False + + def modify_required_power_levels(self, events=None, **kwargs): + """Modifies room power level requirements. + + Args: + events(dict): Power levels required for sending specific event types, + in the form {"m.room.whatever0": 60, "m.room.whatever2": None}. + Overrides events_default and state_default for the specified + events. A level of None causes the target event to revert to the + default level as specified by events_default or state_default. + **kwargs: Key/value pairs specifying the power levels required for + various actions: + + - events_default(int): Default level for sending message events + - state_default(int): Default level for sending state events + - invite(int): Inviting a user + - redact(int): Redacting an event + - ban(int): Banning a user + - kick(int): Kicking a user + + Returns: + True if successful, False if not + """ + try: + content = self.client.api.get_power_levels(self.room_id) + content.update(kwargs) + for key, value in list(content.items()): + if value is None: + del content[key] + + if events: + if "events" in content: + content["events"].update(events) + else: + content["events"] = events + + # Remove any keys with value None + for event, power_level in list(content["events"].items()): + if power_level is None: + del content["events"][event] + + self.client.api.set_power_levels(self.room_id, content) + return True + except MatrixRequestError: + return False + + def set_invite_only(self, invite_only): + """Set how the room can be joined. + + Args: + invite_only(bool): If True, users will have to be invited to join + the room. If False, anyone who knows the room link can join. + + Returns: + True if successful, False if not + """ + join_rule = "invite" if invite_only else "public" + try: + self.client.api.set_join_rule(self.room_id, join_rule) + self.invite_only = invite_only + return True + except MatrixRequestError: + return False + + def set_guest_access(self, allow_guests): + """Set whether guests can join the room and return True if successful.""" + guest_access = "can_join" if allow_guests else "forbidden" + try: + self.client.api.set_guest_access(self.room_id, guest_access) + self.guest_access = allow_guests + return True + except MatrixRequestError: + return False + + def enable_encryption(self): + """Enables encryption in the room. + + NOTE: Once enabled, encryption cannot be disabled. + + Returns: + True if successful, False if not + """ + try: + self.send_state_event("m.room.encryption", + {"algorithm": "m.megolm.v1.aes-sha2"}) + self.encrypted = True + return True + except MatrixRequestError: + return False + + def _process_state_event(self, state_event): + if "type" not in state_event: + return # Ignore event + etype = state_event["type"] + econtent = state_event["content"] + clevel = self.client._cache_level + + # Don't keep track of room state if caching turned off + if clevel >= 0: + try: + if etype == "m.room.name": + self.name = econtent.get("name") + elif etype == "m.room.canonical_alias": + self.canonical_alias = econtent.get("alias") + elif etype == "m.room.topic": + self.topic = econtent.get("topic") + elif etype == "m.room.aliases": + self.aliases = econtent.get("aliases") + elif etype == "m.room.join_rules": + self.invite_only = econtent["join_rule"] == "invite" + elif etype == "m.room.guest_access": + self.guest_access = econtent["guest_access"] == "can_join" + elif etype == "m.room.encryption": + if econtent.get("algorithm") == "m.megolm.v1.aes-sha2": + self.encrypted = True + elif etype == "m.room.member" and clevel == clevel.ALL: + # tracking room members can be large e.g. #matrix:matrix.org + if econtent["membership"] == "join": + user_id = state_event["state_key"] + self._add_member(user_id, econtent.get("displayname")) + elif econtent["membership"] in ("leave", "kick", "invite"): + self._members.pop(state_event["state_key"], None) + except KeyError: + logger.exception("Unable to parse state event %s, passing over.", + state_event['event_id']) + + for listener in self.state_listeners: + if ( + listener['event_type'] is None or + listener['event_type'] == state_event['type'] + ): + listener['callback'](state_event) + + @property + def prev_batch(self): + return self._prev_batch + + @prev_batch.setter + def prev_batch(self, prev_batch): + self._prev_batch = prev_batch diff --git a/venv/lib/python3.10/site-packages/matrix_client/user.py b/venv/lib/python3.10/site-packages/matrix_client/user.py new file mode 100644 index 0000000000000000000000000000000000000000..e56a89ef32569ee093c09f4311a7c1188fb87112 --- /dev/null +++ b/venv/lib/python3.10/site-packages/matrix_client/user.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- +# Copyright 2015 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from warnings import warn + +from .checks import check_user_id + + +class User(object): + """ The User class can be used to call user specific functions. + """ + def __init__(self, api, user_id, displayname=None): + check_user_id(user_id) + + self.user_id = user_id + self.displayname = displayname + self.api = api + + def get_display_name(self, room=None): + """Get this user's display name. + + Args: + room (Room): Optional. When specified, return the display name of the user + in this room. + + Returns: + The display name. Defaults to the user ID if not set. + """ + if room: + try: + return room.members_displaynames[self.user_id] + except KeyError: + return self.user_id + if not self.displayname: + self.displayname = self.api.get_display_name(self.user_id) + return self.displayname or self.user_id + + def get_friendly_name(self): + """Deprecated. Use :meth:`get_display_name` instead.""" + warn("get_friendly_name is deprecated. Use get_display_name instead.", + DeprecationWarning) + return self.get_display_name() + + def set_display_name(self, display_name): + """ Set this users display name. + + Args: + display_name (str): Display Name + """ + self.displayname = display_name + return self.api.set_display_name(self.user_id, display_name) + + def get_avatar_url(self): + mxcurl = self.api.get_avatar_url(self.user_id) + url = None + if mxcurl is not None: + url = self.api.get_download_url(mxcurl) + return url + + def set_avatar_url(self, avatar_url): + """ Set this users avatar. + + Args: + avatar_url (str): mxc url from previously uploaded + """ + return self.api.set_avatar_url(self.user_id, avatar_url) diff --git a/venv/lib/python3.10/site-packages/rich-14.0.0.dist-info/INSTALLER b/venv/lib/python3.10/site-packages/rich-14.0.0.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/venv/lib/python3.10/site-packages/rich-14.0.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python3.10/site-packages/rich-14.0.0.dist-info/LICENSE b/venv/lib/python3.10/site-packages/rich-14.0.0.dist-info/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..4415505566f261c802b671426be529a31f914137 --- /dev/null +++ b/venv/lib/python3.10/site-packages/rich-14.0.0.dist-info/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020 Will McGugan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/venv/lib/python3.10/site-packages/rich-14.0.0.dist-info/METADATA b/venv/lib/python3.10/site-packages/rich-14.0.0.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..7775bd98da2af8bb198f957d64189149892f6855 --- /dev/null +++ b/venv/lib/python3.10/site-packages/rich-14.0.0.dist-info/METADATA @@ -0,0 +1,473 @@ +Metadata-Version: 2.1 +Name: rich +Version: 14.0.0 +Summary: Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal +Home-page: https://github.com/Textualize/rich +License: MIT +Author: Will McGugan +Author-email: willmcgugan@gmail.com +Requires-Python: >=3.8.0 +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Console +Classifier: Framework :: IPython +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: MacOS +Classifier: Operating System :: Microsoft :: Windows +Classifier: Operating System :: POSIX :: Linux +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Typing :: Typed +Provides-Extra: jupyter +Requires-Dist: ipywidgets (>=7.5.1,<9) ; extra == "jupyter" +Requires-Dist: markdown-it-py (>=2.2.0) +Requires-Dist: pygments (>=2.13.0,<3.0.0) +Requires-Dist: typing-extensions (>=4.0.0,<5.0) ; python_version < "3.11" +Project-URL: Documentation, https://rich.readthedocs.io/en/latest/ +Description-Content-Type: text/markdown + +[![Supported Python Versions](https://img.shields.io/pypi/pyversions/rich/13.2.0)](https://pypi.org/project/rich/) [![PyPI version](https://badge.fury.io/py/rich.svg)](https://badge.fury.io/py/rich) + +[![Downloads](https://pepy.tech/badge/rich/month)](https://pepy.tech/project/rich) +[![codecov](https://img.shields.io/codecov/c/github/Textualize/rich?label=codecov&logo=codecov)](https://codecov.io/gh/Textualize/rich) +[![Rich blog](https://img.shields.io/badge/blog-rich%20news-yellowgreen)](https://www.willmcgugan.com/tag/rich/) +[![Twitter Follow](https://img.shields.io/twitter/follow/willmcgugan.svg?style=social)](https://twitter.com/willmcgugan) + +![Logo](https://github.com/textualize/rich/raw/master/imgs/logo.svg) + +[English readme](https://github.com/textualize/rich/blob/master/README.md) + • [简体中文 readme](https://github.com/textualize/rich/blob/master/README.cn.md) + • [正體中文 readme](https://github.com/textualize/rich/blob/master/README.zh-tw.md) + • [Lengua española readme](https://github.com/textualize/rich/blob/master/README.es.md) + • [Deutsche readme](https://github.com/textualize/rich/blob/master/README.de.md) + • [Läs på svenska](https://github.com/textualize/rich/blob/master/README.sv.md) + • [日本語 readme](https://github.com/textualize/rich/blob/master/README.ja.md) + • [한국어 readme](https://github.com/textualize/rich/blob/master/README.kr.md) + • [Français readme](https://github.com/textualize/rich/blob/master/README.fr.md) + • [Schwizerdütsch readme](https://github.com/textualize/rich/blob/master/README.de-ch.md) + • [हिन्दी readme](https://github.com/textualize/rich/blob/master/README.hi.md) + • [Português brasileiro readme](https://github.com/textualize/rich/blob/master/README.pt-br.md) + • [Italian readme](https://github.com/textualize/rich/blob/master/README.it.md) + • [Русский readme](https://github.com/textualize/rich/blob/master/README.ru.md) + • [Indonesian readme](https://github.com/textualize/rich/blob/master/README.id.md) + • [فارسی readme](https://github.com/textualize/rich/blob/master/README.fa.md) + • [Türkçe readme](https://github.com/textualize/rich/blob/master/README.tr.md) + • [Polskie readme](https://github.com/textualize/rich/blob/master/README.pl.md) + + +Rich is a Python library for _rich_ text and beautiful formatting in the terminal. + +The [Rich API](https://rich.readthedocs.io/en/latest/) makes it easy to add color and style to terminal output. Rich can also render pretty tables, progress bars, markdown, syntax highlighted source code, tracebacks, and more — out of the box. + +![Features](https://github.com/textualize/rich/raw/master/imgs/features.png) + +For a video introduction to Rich see [calmcode.io](https://calmcode.io/rich/introduction.html) by [@fishnets88](https://twitter.com/fishnets88). + +See what [people are saying about Rich](https://www.willmcgugan.com/blog/pages/post/rich-tweets/). + +## Compatibility + +Rich works with Linux, macOS and Windows. True color / emoji works with new Windows Terminal, classic terminal is limited to 16 colors. Rich requires Python 3.8 or later. + +Rich works with [Jupyter notebooks](https://jupyter.org/) with no additional configuration required. + +## Installing + +Install with `pip` or your favorite PyPI package manager. + +```sh +python -m pip install rich +``` + +Run the following to test Rich output on your terminal: + +```sh +python -m rich +``` + +## Rich Print + +To effortlessly add rich output to your application, you can import the [rich print](https://rich.readthedocs.io/en/latest/introduction.html#quick-start) method, which has the same signature as the builtin Python function. Try this: + +```python +from rich import print + +print("Hello, [bold magenta]World[/bold magenta]!", ":vampire:", locals()) +``` + +![Hello World](https://github.com/textualize/rich/raw/master/imgs/print.png) + +## Rich REPL + +Rich can be installed in the Python REPL, so that any data structures will be pretty printed and highlighted. + +```python +>>> from rich import pretty +>>> pretty.install() +``` + +![REPL](https://github.com/textualize/rich/raw/master/imgs/repl.png) + +## Using the Console + +For more control over rich terminal content, import and construct a [Console](https://rich.readthedocs.io/en/latest/reference/console.html#rich.console.Console) object. + +```python +from rich.console import Console + +console = Console() +``` + +The Console object has a `print` method which has an intentionally similar interface to the builtin `print` function. Here's an example of use: + +```python +console.print("Hello", "World!") +``` + +As you might expect, this will print `"Hello World!"` to the terminal. Note that unlike the builtin `print` function, Rich will word-wrap your text to fit within the terminal width. + +There are a few ways of adding color and style to your output. You can set a style for the entire output by adding a `style` keyword argument. Here's an example: + +```python +console.print("Hello", "World!", style="bold red") +``` + +The output will be something like the following: + +![Hello World](https://github.com/textualize/rich/raw/master/imgs/hello_world.png) + +That's fine for styling a line of text at a time. For more finely grained styling, Rich renders a special markup which is similar in syntax to [bbcode](https://en.wikipedia.org/wiki/BBCode). Here's an example: + +```python +console.print("Where there is a [bold cyan]Will[/bold cyan] there [u]is[/u] a [i]way[/i].") +``` + +![Console Markup](https://github.com/textualize/rich/raw/master/imgs/where_there_is_a_will.png) + +You can use a Console object to generate sophisticated output with minimal effort. See the [Console API](https://rich.readthedocs.io/en/latest/console.html) docs for details. + +## Rich Inspect + +Rich has an [inspect](https://rich.readthedocs.io/en/latest/reference/init.html?highlight=inspect#rich.inspect) function which can produce a report on any Python object, such as class, instance, or builtin. + +```python +>>> my_list = ["foo", "bar"] +>>> from rich import inspect +>>> inspect(my_list, methods=True) +``` + +![Log](https://github.com/textualize/rich/raw/master/imgs/inspect.png) + +See the [inspect docs](https://rich.readthedocs.io/en/latest/reference/init.html#rich.inspect) for details. + +# Rich Library + +Rich contains a number of builtin _renderables_ you can use to create elegant output in your CLI and help you debug your code. + +Click the following headings for details: + +
+Log + +The Console object has a `log()` method which has a similar interface to `print()`, but also renders a column for the current time and the file and line which made the call. By default Rich will do syntax highlighting for Python structures and for repr strings. If you log a collection (i.e. a dict or a list) Rich will pretty print it so that it fits in the available space. Here's an example of some of these features. + +```python +from rich.console import Console +console = Console() + +test_data = [ + {"jsonrpc": "2.0", "method": "sum", "params": [None, 1, 2, 4, False, True], "id": "1",}, + {"jsonrpc": "2.0", "method": "notify_hello", "params": [7]}, + {"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": "2"}, +] + +def test_log(): + enabled = False + context = { + "foo": "bar", + } + movies = ["Deadpool", "Rise of the Skywalker"] + console.log("Hello from", console, "!") + console.log(test_data, log_locals=True) + + +test_log() +``` + +The above produces the following output: + +![Log](https://github.com/textualize/rich/raw/master/imgs/log.png) + +Note the `log_locals` argument, which outputs a table containing the local variables where the log method was called. + +The log method could be used for logging to the terminal for long running applications such as servers, but is also a very nice debugging aid. + +
+
+Logging Handler + +You can also use the builtin [Handler class](https://rich.readthedocs.io/en/latest/logging.html) to format and colorize output from Python's logging module. Here's an example of the output: + +![Logging](https://github.com/textualize/rich/raw/master/imgs/logging.png) + +
+ +
+Emoji + +To insert an emoji in to console output place the name between two colons. Here's an example: + +```python +>>> console.print(":smiley: :vampire: :pile_of_poo: :thumbs_up: :raccoon:") +😃 🧛 💩 👍 🦝 +``` + +Please use this feature wisely. + +
+ +
+Tables + +Rich can render flexible [tables](https://rich.readthedocs.io/en/latest/tables.html) with unicode box characters. There is a large variety of formatting options for borders, styles, cell alignment etc. + +![table movie](https://github.com/textualize/rich/raw/master/imgs/table_movie.gif) + +The animation above was generated with [table_movie.py](https://github.com/textualize/rich/blob/master/examples/table_movie.py) in the examples directory. + +Here's a simpler table example: + +```python +from rich.console import Console +from rich.table import Table + +console = Console() + +table = Table(show_header=True, header_style="bold magenta") +table.add_column("Date", style="dim", width=12) +table.add_column("Title") +table.add_column("Production Budget", justify="right") +table.add_column("Box Office", justify="right") +table.add_row( + "Dec 20, 2019", "Star Wars: The Rise of Skywalker", "$275,000,000", "$375,126,118" +) +table.add_row( + "May 25, 2018", + "[red]Solo[/red]: A Star Wars Story", + "$275,000,000", + "$393,151,347", +) +table.add_row( + "Dec 15, 2017", + "Star Wars Ep. VIII: The Last Jedi", + "$262,000,000", + "[bold]$1,332,539,889[/bold]", +) + +console.print(table) +``` + +This produces the following output: + +![table](https://github.com/textualize/rich/raw/master/imgs/table.png) + +Note that console markup is rendered in the same way as `print()` and `log()`. In fact, anything that is renderable by Rich may be included in the headers / rows (even other tables). + +The `Table` class is smart enough to resize columns to fit the available width of the terminal, wrapping text as required. Here's the same example, with the terminal made smaller than the table above: + +![table2](https://github.com/textualize/rich/raw/master/imgs/table2.png) + +
+ +
+Progress Bars + +Rich can render multiple flicker-free [progress](https://rich.readthedocs.io/en/latest/progress.html) bars to track long-running tasks. + +For basic usage, wrap any sequence in the `track` function and iterate over the result. Here's an example: + +```python +from rich.progress import track + +for step in track(range(100)): + do_step(step) +``` + +It's not much harder to add multiple progress bars. Here's an example taken from the docs: + +![progress](https://github.com/textualize/rich/raw/master/imgs/progress.gif) + +The columns may be configured to show any details you want. Built-in columns include percentage complete, file size, file speed, and time remaining. Here's another example showing a download in progress: + +![progress](https://github.com/textualize/rich/raw/master/imgs/downloader.gif) + +To try this out yourself, see [examples/downloader.py](https://github.com/textualize/rich/blob/master/examples/downloader.py) which can download multiple URLs simultaneously while displaying progress. + +
+ +
+Status + +For situations where it is hard to calculate progress, you can use the [status](https://rich.readthedocs.io/en/latest/reference/console.html#rich.console.Console.status) method which will display a 'spinner' animation and message. The animation won't prevent you from using the console as normal. Here's an example: + +```python +from time import sleep +from rich.console import Console + +console = Console() +tasks = [f"task {n}" for n in range(1, 11)] + +with console.status("[bold green]Working on tasks...") as status: + while tasks: + task = tasks.pop(0) + sleep(1) + console.log(f"{task} complete") +``` + +This generates the following output in the terminal. + +![status](https://github.com/textualize/rich/raw/master/imgs/status.gif) + +The spinner animations were borrowed from [cli-spinners](https://www.npmjs.com/package/cli-spinners). You can select a spinner by specifying the `spinner` parameter. Run the following command to see the available values: + +``` +python -m rich.spinner +``` + +The above command generates the following output in the terminal: + +![spinners](https://github.com/textualize/rich/raw/master/imgs/spinners.gif) + +
+ +
+Tree + +Rich can render a [tree](https://rich.readthedocs.io/en/latest/tree.html) with guide lines. A tree is ideal for displaying a file structure, or any other hierarchical data. + +The labels of the tree can be simple text or anything else Rich can render. Run the following for a demonstration: + +``` +python -m rich.tree +``` + +This generates the following output: + +![markdown](https://github.com/textualize/rich/raw/master/imgs/tree.png) + +See the [tree.py](https://github.com/textualize/rich/blob/master/examples/tree.py) example for a script that displays a tree view of any directory, similar to the linux `tree` command. + +
+ +
+Columns + +Rich can render content in neat [columns](https://rich.readthedocs.io/en/latest/columns.html) with equal or optimal width. Here's a very basic clone of the (MacOS / Linux) `ls` command which displays a directory listing in columns: + +```python +import os +import sys + +from rich import print +from rich.columns import Columns + +directory = os.listdir(sys.argv[1]) +print(Columns(directory)) +``` + +The following screenshot is the output from the [columns example](https://github.com/textualize/rich/blob/master/examples/columns.py) which displays data pulled from an API in columns: + +![columns](https://github.com/textualize/rich/raw/master/imgs/columns.png) + +
+ +
+Markdown + +Rich can render [markdown](https://rich.readthedocs.io/en/latest/markdown.html) and does a reasonable job of translating the formatting to the terminal. + +To render markdown import the `Markdown` class and construct it with a string containing markdown code. Then print it to the console. Here's an example: + +```python +from rich.console import Console +from rich.markdown import Markdown + +console = Console() +with open("README.md") as readme: + markdown = Markdown(readme.read()) +console.print(markdown) +``` + +This will produce output something like the following: + +![markdown](https://github.com/textualize/rich/raw/master/imgs/markdown.png) + +
+ +
+Syntax Highlighting + +Rich uses the [pygments](https://pygments.org/) library to implement [syntax highlighting](https://rich.readthedocs.io/en/latest/syntax.html). Usage is similar to rendering markdown; construct a `Syntax` object and print it to the console. Here's an example: + +```python +from rich.console import Console +from rich.syntax import Syntax + +my_code = ''' +def iter_first_last(values: Iterable[T]) -> Iterable[Tuple[bool, bool, T]]: + """Iterate and generate a tuple with a flag for first and last value.""" + iter_values = iter(values) + try: + previous_value = next(iter_values) + except StopIteration: + return + first = True + for value in iter_values: + yield first, False, previous_value + first = False + previous_value = value + yield first, True, previous_value +''' +syntax = Syntax(my_code, "python", theme="monokai", line_numbers=True) +console = Console() +console.print(syntax) +``` + +This will produce the following output: + +![syntax](https://github.com/textualize/rich/raw/master/imgs/syntax.png) + +
+ +
+Tracebacks + +Rich can render [beautiful tracebacks](https://rich.readthedocs.io/en/latest/traceback.html) which are easier to read and show more code than standard Python tracebacks. You can set Rich as the default traceback handler so all uncaught exceptions will be rendered by Rich. + +Here's what it looks like on OSX (similar on Linux): + +![traceback](https://github.com/textualize/rich/raw/master/imgs/traceback.png) + +
+ +All Rich renderables make use of the [Console Protocol](https://rich.readthedocs.io/en/latest/protocol.html), which you can also use to implement your own Rich content. + +# Rich CLI + + +See also [Rich CLI](https://github.com/textualize/rich-cli) for a command line application powered by Rich. Syntax highlight code, render markdown, display CSVs in tables, and more, directly from the command prompt. + + +![Rich CLI](https://raw.githubusercontent.com/Textualize/rich-cli/main/imgs/rich-cli-splash.jpg) + +# Textual + +See also Rich's sister project, [Textual](https://github.com/Textualize/textual), which you can use to build sophisticated User Interfaces in the terminal. + +![Textual screenshot](https://raw.githubusercontent.com/Textualize/textual/main/imgs/textual.png) + diff --git a/venv/lib/python3.10/site-packages/rich-14.0.0.dist-info/RECORD b/venv/lib/python3.10/site-packages/rich-14.0.0.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..47be3e249bad678a527ecb9ae36a14efcb6ebc30 --- /dev/null +++ b/venv/lib/python3.10/site-packages/rich-14.0.0.dist-info/RECORD @@ -0,0 +1,163 @@ +rich-14.0.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +rich-14.0.0.dist-info/LICENSE,sha256=3u18F6QxgVgZCj6iOcyHmlpQJxzruYrnAl9I--WNyhU,1056 +rich-14.0.0.dist-info/METADATA,sha256=d9GYl36IYeimndEYRjoelIVrkxiBe-jvhPN0UouMqG0,18274 +rich-14.0.0.dist-info/RECORD,, +rich-14.0.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +rich-14.0.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88 +rich/__init__.py,sha256=lh2WcoIOJp5M5_lbAsSUMGv8oiJeumROazHH_AYMS8I,6066 +rich/__main__.py,sha256=Wvh53rmOMyWeUeyqUHpn1PXsHlBc4TVcQnqrw46nf9Y,8333 +rich/__pycache__/__init__.cpython-310.pyc,, +rich/__pycache__/__main__.cpython-310.pyc,, +rich/__pycache__/_cell_widths.cpython-310.pyc,, +rich/__pycache__/_emoji_codes.cpython-310.pyc,, +rich/__pycache__/_emoji_replace.cpython-310.pyc,, +rich/__pycache__/_export_format.cpython-310.pyc,, +rich/__pycache__/_extension.cpython-310.pyc,, +rich/__pycache__/_fileno.cpython-310.pyc,, +rich/__pycache__/_inspect.cpython-310.pyc,, +rich/__pycache__/_log_render.cpython-310.pyc,, +rich/__pycache__/_loop.cpython-310.pyc,, +rich/__pycache__/_null_file.cpython-310.pyc,, +rich/__pycache__/_palettes.cpython-310.pyc,, +rich/__pycache__/_pick.cpython-310.pyc,, +rich/__pycache__/_ratio.cpython-310.pyc,, +rich/__pycache__/_spinners.cpython-310.pyc,, +rich/__pycache__/_stack.cpython-310.pyc,, +rich/__pycache__/_timer.cpython-310.pyc,, +rich/__pycache__/_win32_console.cpython-310.pyc,, +rich/__pycache__/_windows.cpython-310.pyc,, +rich/__pycache__/_windows_renderer.cpython-310.pyc,, +rich/__pycache__/_wrap.cpython-310.pyc,, +rich/__pycache__/abc.cpython-310.pyc,, +rich/__pycache__/align.cpython-310.pyc,, +rich/__pycache__/ansi.cpython-310.pyc,, +rich/__pycache__/bar.cpython-310.pyc,, +rich/__pycache__/box.cpython-310.pyc,, +rich/__pycache__/cells.cpython-310.pyc,, +rich/__pycache__/color.cpython-310.pyc,, +rich/__pycache__/color_triplet.cpython-310.pyc,, +rich/__pycache__/columns.cpython-310.pyc,, +rich/__pycache__/console.cpython-310.pyc,, +rich/__pycache__/constrain.cpython-310.pyc,, +rich/__pycache__/containers.cpython-310.pyc,, +rich/__pycache__/control.cpython-310.pyc,, +rich/__pycache__/default_styles.cpython-310.pyc,, +rich/__pycache__/diagnose.cpython-310.pyc,, +rich/__pycache__/emoji.cpython-310.pyc,, +rich/__pycache__/errors.cpython-310.pyc,, +rich/__pycache__/file_proxy.cpython-310.pyc,, +rich/__pycache__/filesize.cpython-310.pyc,, +rich/__pycache__/highlighter.cpython-310.pyc,, +rich/__pycache__/json.cpython-310.pyc,, +rich/__pycache__/jupyter.cpython-310.pyc,, +rich/__pycache__/layout.cpython-310.pyc,, +rich/__pycache__/live.cpython-310.pyc,, +rich/__pycache__/live_render.cpython-310.pyc,, +rich/__pycache__/logging.cpython-310.pyc,, +rich/__pycache__/markdown.cpython-310.pyc,, +rich/__pycache__/markup.cpython-310.pyc,, +rich/__pycache__/measure.cpython-310.pyc,, +rich/__pycache__/padding.cpython-310.pyc,, +rich/__pycache__/pager.cpython-310.pyc,, +rich/__pycache__/palette.cpython-310.pyc,, +rich/__pycache__/panel.cpython-310.pyc,, +rich/__pycache__/pretty.cpython-310.pyc,, +rich/__pycache__/progress.cpython-310.pyc,, +rich/__pycache__/progress_bar.cpython-310.pyc,, +rich/__pycache__/prompt.cpython-310.pyc,, +rich/__pycache__/protocol.cpython-310.pyc,, +rich/__pycache__/region.cpython-310.pyc,, +rich/__pycache__/repr.cpython-310.pyc,, +rich/__pycache__/rule.cpython-310.pyc,, +rich/__pycache__/scope.cpython-310.pyc,, +rich/__pycache__/screen.cpython-310.pyc,, +rich/__pycache__/segment.cpython-310.pyc,, +rich/__pycache__/spinner.cpython-310.pyc,, +rich/__pycache__/status.cpython-310.pyc,, +rich/__pycache__/style.cpython-310.pyc,, +rich/__pycache__/styled.cpython-310.pyc,, +rich/__pycache__/syntax.cpython-310.pyc,, +rich/__pycache__/table.cpython-310.pyc,, +rich/__pycache__/terminal_theme.cpython-310.pyc,, +rich/__pycache__/text.cpython-310.pyc,, +rich/__pycache__/theme.cpython-310.pyc,, +rich/__pycache__/themes.cpython-310.pyc,, +rich/__pycache__/traceback.cpython-310.pyc,, +rich/__pycache__/tree.cpython-310.pyc,, +rich/_cell_widths.py,sha256=fbmeyetEdHjzE_Vx2l1uK7tnPOhMs2X1lJfO3vsKDpA,10209 +rich/_emoji_codes.py,sha256=hu1VL9nbVdppJrVoijVshRlcRRe_v3dju3Mmd2sKZdY,140235 +rich/_emoji_replace.py,sha256=n-kcetsEUx2ZUmhQrfeMNc-teeGhpuSQ5F8VPBsyvDo,1064 +rich/_export_format.py,sha256=RI08pSrm5tBSzPMvnbTqbD9WIalaOoN5d4M1RTmLq1Y,2128 +rich/_extension.py,sha256=G66PkbH_QdTJh6jD-J228O76CmAnr2hLQv72CgPPuzE,241 +rich/_fileno.py,sha256=HWZxP5C2ajMbHryvAQZseflVfQoGzsKOHzKGsLD8ynQ,799 +rich/_inspect.py,sha256=QM05lEFnFoTaFqpnbx-zBEI6k8oIKrD3cvjEOQNhKig,9655 +rich/_log_render.py,sha256=xBKCxqiO4FZk8eG56f8crFdrmJxFrJsQE3V3F-fFekc,3213 +rich/_loop.py,sha256=hV_6CLdoPm0va22Wpw4zKqM0RYsz3TZxXj0PoS-9eDQ,1236 +rich/_null_file.py,sha256=ADGKp1yt-k70FMKV6tnqCqecB-rSJzp-WQsD7LPL-kg,1394 +rich/_palettes.py,sha256=cdev1JQKZ0JvlguV9ipHgznTdnvlIzUFDBb0It2PzjI,7063 +rich/_pick.py,sha256=evDt8QN4lF5CiwrUIXlOJCntitBCOsI3ZLPEIAVRLJU,423 +rich/_ratio.py,sha256=d2k38QnkJKhkHAqqSseqMQ-ZuvgbwnocRKhMQq84EdI,5459 +rich/_spinners.py,sha256=U2r1_g_1zSjsjiUdAESc2iAMc3i4ri_S8PYP6kQ5z1I,19919 +rich/_stack.py,sha256=-C8OK7rxn3sIUdVwxZBBpeHhIzX0eI-VM3MemYfaXm0,351 +rich/_timer.py,sha256=zelxbT6oPFZnNrwWPpc1ktUeAT-Vc4fuFcRZLQGLtMI,417 +rich/_win32_console.py,sha256=o2QN_IRx10biGP3Ap1neaqX8FBGlUKSmWM6Kw4OSg-U,22719 +rich/_windows.py,sha256=is3WpbHMj8WaTHYB11hc6lP2t4hlvt4TViTlHSmjsi0,1901 +rich/_windows_renderer.py,sha256=d799xOnxLbCCCzGu9-U7YLmIQkxtxQIBFQQ6iu4veSc,2759 +rich/_wrap.py,sha256=FlSsom5EX0LVkA3KWy34yHnCfLtqX-ZIepXKh-70rpc,3404 +rich/abc.py,sha256=dALMOGfKVNeAbvqq66IpTQxQUerxD7AE4FKwqd0eQKk,878 +rich/align.py,sha256=gxlfgvi4ah8ERmg8RpGFtWY1Z4WBuWm-6qSIUSFx4bQ,10421 +rich/ansi.py,sha256=Avs1LHbSdcyOvDOdpELZUoULcBiYewY76eNBp6uFBhs,6921 +rich/bar.py,sha256=ldbVHOzKJOnflVNuv1xS7g6dLX2E3wMnXkdPbpzJTcs,3263 +rich/box.py,sha256=46rA0eBKLBcqNhCXmEKS4pN1dz36F0Vzi52hyVT-tyc,10783 +rich/cells.py,sha256=KrQkj5-LghCCpJLSNQIyAZjndc4bnEqOEmi5YuZ9UCY,5130 +rich/color.py,sha256=3HSULVDj7qQkXUdFWv78JOiSZzfy5y1nkcYhna296V0,18211 +rich/color_triplet.py,sha256=3lhQkdJbvWPoLDO-AnYImAWmJvV5dlgYNCVZ97ORaN4,1054 +rich/columns.py,sha256=HUX0KcMm9dsKNi11fTbiM_h2iDtl8ySCaVcxlalEzq8,7131 +rich/console.py,sha256=Hi0WEKiqGzDJH1CaUhnDRGz5ThhwEBlNxQlSchhVJT8,100493 +rich/constrain.py,sha256=1VIPuC8AgtKWrcncQrjBdYqA3JVWysu6jZo1rrh7c7Q,1288 +rich/containers.py,sha256=c_56TxcedGYqDepHBMTuZdUIijitAQgnox-Qde0Z1qo,5502 +rich/control.py,sha256=Ix-rO8ZhSB2q1Biazr4l72ZyAw27H9or7ElipWVVo0M,6606 +rich/default_styles.py,sha256=j9eZgSn7bqnymxYzYp8h-0OGTRy2ZOj-PfY9toqp0Rw,8221 +rich/diagnose.py,sha256=5VBWa56B0ahicEgz3F82OyGG78-vyYeYI_EkCFCbylw,950 +rich/emoji.py,sha256=1jTRHFwvQxY1ciul22MdEZcWc7brfjKT8FG6ZjXj5dM,2465 +rich/errors.py,sha256=5pP3Kc5d4QJ_c0KFsxrfyhjiPVe7J1zOqSFbFAzcV-Y,642 +rich/file_proxy.py,sha256=Tl9THMDZ-Pk5Wm8sI1gGg_U5DhusmxD-FZ0fUbcU0W0,1683 +rich/filesize.py,sha256=_iz9lIpRgvW7MNSeCZnLg-HwzbP4GETg543WqD8SFs0,2484 +rich/highlighter.py,sha256=G_sn-8DKjM1sEjLG_oc4ovkWmiUpWvj8bXi0yed2LnY,9586 +rich/json.py,sha256=omC2WHTgURxEosna1ftoSJCne2EX7MDuQtCdswS3qsk,5019 +rich/jupyter.py,sha256=G9pOJmR4ESIFYSd4MKGqmHqCtstx0oRWpyeTgv54-Xc,3228 +rich/layout.py,sha256=WR8PCSroYnteIT3zawxQ3k3ad1sQO5wGG1SZOoeBuBM,13944 +rich/live.py,sha256=DhzAPEnjTxQuq9_0Y2xh2MUwQcP_aGPkenLfKETslwM,14270 +rich/live_render.py,sha256=QaiB8dtGikCdssoXpkEmmiH55fxT-9bzLkBO9pbBvrU,3654 +rich/logging.py,sha256=aqZpsmIEE45-wbnZqWnEaNSdQ89cbGcaL26-ZV0poj0,12446 +rich/markdown.py,sha256=eDi7dMN7RQD5u21tuqCOSpNWGZdKmyGtKmaZNt257rA,25969 +rich/markup.py,sha256=btpr271BLhiCR1jNglRnv2BpIzVcNefYwSMeW9teDbc,8427 +rich/measure.py,sha256=HmrIJX8sWRTHbgh8MxEay_83VkqNW_70s8aKP5ZcYI8,5305 +rich/padding.py,sha256=h8XnIivLrNtlxI3vQPKHXh4hAwjOJqZx0slM0z3g1_M,4896 +rich/pager.py,sha256=SO_ETBFKbg3n_AgOzXm41Sv36YxXAyI3_R-KOY2_uSc,828 +rich/palette.py,sha256=Ar6ZUrYHiFt6-Rr2k-k9F8V7hxgJYHNdqjk2vVXsLgc,3288 +rich/panel.py,sha256=SUDaa3z4MU7vIjzvbi0SXuc6BslDzADwdY1AX4TbTdY,11225 +rich/pretty.py,sha256=eQs437AksYaCB2qO_d-z6e0DF_t5F1KfXfa1Hi-Ya0E,36355 +rich/progress.py,sha256=tLmBGHrAfxIQxfB2kq1IpNXTVFNuvl9bXd_QkLQUN8Q,60333 +rich/progress_bar.py,sha256=mZTPpJUwcfcdgQCTTz3kyY-fc79ddLwtx6Ghhxfo064,8162 +rich/prompt.py,sha256=k0CUIW-3I55jGk8U3O1WiEhdF6yXa2EiWeRqRhuJXWA,12435 +rich/protocol.py,sha256=Wt-2HZd67OYiopUkCTOz7lM38vyo5r3HEQZ9TOPDl5Q,1367 +rich/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +rich/region.py,sha256=rNT9xZrVZTYIXZC0NYn41CJQwYNbR-KecPOxTgQvB8Y,166 +rich/repr.py,sha256=HIsurPLZK9Gray75l3_vQx7S27AzTpAj4ChXSfe1Fes,4419 +rich/rule.py,sha256=umO21Wjw0FcYAeTB3UumNLCsDWhejzxnjlf2VwiXiDI,4590 +rich/scope.py,sha256=lf6Qet_e4JOY34lwhYSAG-NBXYKBcYu6t_igv_JoGog,2831 +rich/screen.py,sha256=rL_j2wX-4SeuIOI2oOlc418QP9EAvD59GInUmEAE6jQ,1579 +rich/segment.py,sha256=7gOdwSPrzu0a2gRmxBDtu3u2S8iG5s9l7wlB58dKMy0,24707 +rich/spinner.py,sha256=PT5qgXPG3ZpqRj7n3EZQ6NW56mx3ldZqZCU7gEMyZk4,4364 +rich/status.py,sha256=kkPph3YeAZBo-X-4wPp8gTqZyU466NLwZBA4PZTTewo,4424 +rich/style.py,sha256=xpj4uMBZMtuNuNomfUiamigl3p1sDvTCZwrG1tcTVeg,27059 +rich/styled.py,sha256=wljVsVTXbABMMZvkzkO43ZEk_-irzEtvUiQ-sNnikQ8,1234 +rich/syntax.py,sha256=NY1DRIqXBkFExudqxm5K3BJXFCttN63AF_3IZAvtLMg,35655 +rich/table.py,sha256=52hmoLoHpeJEomznWvW8Ce2m1w62HuQDSGmaG6fYyqI,40025 +rich/terminal_theme.py,sha256=1j5-ufJfnvlAo5Qsi_ACZiXDmwMXzqgmFByObT9-yJY,3370 +rich/text.py,sha256=v-vCOG8gS_D5QDhOhU19478-yEJGAXKVi8iYCCk7O_M,47540 +rich/theme.py,sha256=oNyhXhGagtDlbDye3tVu3esWOWk0vNkuxFw-_unlaK0,3771 +rich/themes.py,sha256=0xgTLozfabebYtcJtDdC5QkX5IVUEaviqDUJJh4YVFk,102 +rich/traceback.py,sha256=oritjl2IgQ4fh6f7bu_SSKxGY-SLIRNpI0bS0Zx2yiI,35098 +rich/tree.py,sha256=QoOwg424FkdwGfR8K0tZ6Q7qtzWNAUP_m4sFaYuG6nw,9391 diff --git a/venv/lib/python3.10/site-packages/rich-14.0.0.dist-info/REQUESTED b/venv/lib/python3.10/site-packages/rich-14.0.0.dist-info/REQUESTED new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.10/site-packages/rich-14.0.0.dist-info/WHEEL b/venv/lib/python3.10/site-packages/rich-14.0.0.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..d73ccaae8e0eea45949b0957a5af034099b36aa4 --- /dev/null +++ b/venv/lib/python3.10/site-packages/rich-14.0.0.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: poetry-core 1.9.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/venv/lib/python3.10/site-packages/rich/styled.py b/venv/lib/python3.10/site-packages/rich/styled.py new file mode 100644 index 0000000000000000000000000000000000000000..27243beb76bb22c2851b97227249e70068ec4420 --- /dev/null +++ b/venv/lib/python3.10/site-packages/rich/styled.py @@ -0,0 +1,42 @@ +from typing import TYPE_CHECKING + +from .measure import Measurement +from .segment import Segment +from .style import StyleType + +if TYPE_CHECKING: + from .console import Console, ConsoleOptions, RenderResult, RenderableType + + +class Styled: + """Apply a style to a renderable. + + Args: + renderable (RenderableType): Any renderable. + style (StyleType): A style to apply across the entire renderable. + """ + + def __init__(self, renderable: "RenderableType", style: "StyleType") -> None: + self.renderable = renderable + self.style = style + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + style = console.get_style(self.style) + rendered_segments = console.render(self.renderable, options) + segments = Segment.apply_style(rendered_segments, style) + return segments + + def __rich_measure__( + self, console: "Console", options: "ConsoleOptions" + ) -> Measurement: + return Measurement.get(console, options, self.renderable) + + +if __name__ == "__main__": # pragma: no cover + from rich import print + from rich.panel import Panel + + panel = Styled(Panel("hello"), "on blue") + print(panel) diff --git a/venv/lib/python3.10/site-packages/rich/syntax.py b/venv/lib/python3.10/site-packages/rich/syntax.py new file mode 100644 index 0000000000000000000000000000000000000000..cff8fd235dba57f0ae660b5cbf3409d431452d8c --- /dev/null +++ b/venv/lib/python3.10/site-packages/rich/syntax.py @@ -0,0 +1,966 @@ +import os.path +import re +import sys +import textwrap +from abc import ABC, abstractmethod +from pathlib import Path +from typing import ( + Any, + Dict, + Iterable, + List, + NamedTuple, + Optional, + Sequence, + Set, + Tuple, + Type, + Union, +) + +from pygments.lexer import Lexer +from pygments.lexers import get_lexer_by_name, guess_lexer_for_filename +from pygments.style import Style as PygmentsStyle +from pygments.styles import get_style_by_name +from pygments.token import ( + Comment, + Error, + Generic, + Keyword, + Name, + Number, + Operator, + String, + Token, + Whitespace, +) +from pygments.util import ClassNotFound + +from rich.containers import Lines +from rich.padding import Padding, PaddingDimensions + +from ._loop import loop_first +from .cells import cell_len +from .color import Color, blend_rgb +from .console import Console, ConsoleOptions, JustifyMethod, RenderResult +from .jupyter import JupyterMixin +from .measure import Measurement +from .segment import Segment, Segments +from .style import Style, StyleType +from .text import Text + +TokenType = Tuple[str, ...] + +WINDOWS = sys.platform == "win32" +DEFAULT_THEME = "monokai" + +# The following styles are based on https://github.com/pygments/pygments/blob/master/pygments/formatters/terminal.py +# A few modifications were made + +ANSI_LIGHT: Dict[TokenType, Style] = { + Token: Style(), + Whitespace: Style(color="white"), + Comment: Style(dim=True), + Comment.Preproc: Style(color="cyan"), + Keyword: Style(color="blue"), + Keyword.Type: Style(color="cyan"), + Operator.Word: Style(color="magenta"), + Name.Builtin: Style(color="cyan"), + Name.Function: Style(color="green"), + Name.Namespace: Style(color="cyan", underline=True), + Name.Class: Style(color="green", underline=True), + Name.Exception: Style(color="cyan"), + Name.Decorator: Style(color="magenta", bold=True), + Name.Variable: Style(color="red"), + Name.Constant: Style(color="red"), + Name.Attribute: Style(color="cyan"), + Name.Tag: Style(color="bright_blue"), + String: Style(color="yellow"), + Number: Style(color="blue"), + Generic.Deleted: Style(color="bright_red"), + Generic.Inserted: Style(color="green"), + Generic.Heading: Style(bold=True), + Generic.Subheading: Style(color="magenta", bold=True), + Generic.Prompt: Style(bold=True), + Generic.Error: Style(color="bright_red"), + Error: Style(color="red", underline=True), +} + +ANSI_DARK: Dict[TokenType, Style] = { + Token: Style(), + Whitespace: Style(color="bright_black"), + Comment: Style(dim=True), + Comment.Preproc: Style(color="bright_cyan"), + Keyword: Style(color="bright_blue"), + Keyword.Type: Style(color="bright_cyan"), + Operator.Word: Style(color="bright_magenta"), + Name.Builtin: Style(color="bright_cyan"), + Name.Function: Style(color="bright_green"), + Name.Namespace: Style(color="bright_cyan", underline=True), + Name.Class: Style(color="bright_green", underline=True), + Name.Exception: Style(color="bright_cyan"), + Name.Decorator: Style(color="bright_magenta", bold=True), + Name.Variable: Style(color="bright_red"), + Name.Constant: Style(color="bright_red"), + Name.Attribute: Style(color="bright_cyan"), + Name.Tag: Style(color="bright_blue"), + String: Style(color="yellow"), + Number: Style(color="bright_blue"), + Generic.Deleted: Style(color="bright_red"), + Generic.Inserted: Style(color="bright_green"), + Generic.Heading: Style(bold=True), + Generic.Subheading: Style(color="bright_magenta", bold=True), + Generic.Prompt: Style(bold=True), + Generic.Error: Style(color="bright_red"), + Error: Style(color="red", underline=True), +} + +RICH_SYNTAX_THEMES = {"ansi_light": ANSI_LIGHT, "ansi_dark": ANSI_DARK} +NUMBERS_COLUMN_DEFAULT_PADDING = 2 + + +class SyntaxTheme(ABC): + """Base class for a syntax theme.""" + + @abstractmethod + def get_style_for_token(self, token_type: TokenType) -> Style: + """Get a style for a given Pygments token.""" + raise NotImplementedError # pragma: no cover + + @abstractmethod + def get_background_style(self) -> Style: + """Get the background color.""" + raise NotImplementedError # pragma: no cover + + +class PygmentsSyntaxTheme(SyntaxTheme): + """Syntax theme that delegates to Pygments theme.""" + + def __init__(self, theme: Union[str, Type[PygmentsStyle]]) -> None: + self._style_cache: Dict[TokenType, Style] = {} + if isinstance(theme, str): + try: + self._pygments_style_class = get_style_by_name(theme) + except ClassNotFound: + self._pygments_style_class = get_style_by_name("default") + else: + self._pygments_style_class = theme + + self._background_color = self._pygments_style_class.background_color + self._background_style = Style(bgcolor=self._background_color) + + def get_style_for_token(self, token_type: TokenType) -> Style: + """Get a style from a Pygments class.""" + try: + return self._style_cache[token_type] + except KeyError: + try: + pygments_style = self._pygments_style_class.style_for_token(token_type) + except KeyError: + style = Style.null() + else: + color = pygments_style["color"] + bgcolor = pygments_style["bgcolor"] + style = Style( + color="#" + color if color else "#000000", + bgcolor="#" + bgcolor if bgcolor else self._background_color, + bold=pygments_style["bold"], + italic=pygments_style["italic"], + underline=pygments_style["underline"], + ) + self._style_cache[token_type] = style + return style + + def get_background_style(self) -> Style: + return self._background_style + + +class ANSISyntaxTheme(SyntaxTheme): + """Syntax theme to use standard colors.""" + + def __init__(self, style_map: Dict[TokenType, Style]) -> None: + self.style_map = style_map + self._missing_style = Style.null() + self._background_style = Style.null() + self._style_cache: Dict[TokenType, Style] = {} + + def get_style_for_token(self, token_type: TokenType) -> Style: + """Look up style in the style map.""" + try: + return self._style_cache[token_type] + except KeyError: + # Styles form a hierarchy + # We need to go from most to least specific + # e.g. ("foo", "bar", "baz") to ("foo", "bar") to ("foo",) + get_style = self.style_map.get + token = tuple(token_type) + style = self._missing_style + while token: + _style = get_style(token) + if _style is not None: + style = _style + break + token = token[:-1] + self._style_cache[token_type] = style + return style + + def get_background_style(self) -> Style: + return self._background_style + + +SyntaxPosition = Tuple[int, int] + + +class _SyntaxHighlightRange(NamedTuple): + """ + A range to highlight in a Syntax object. + `start` and `end` are 2-integers tuples, where the first integer is the line number + (starting from 1) and the second integer is the column index (starting from 0). + """ + + style: StyleType + start: SyntaxPosition + end: SyntaxPosition + style_before: bool = False + + +class Syntax(JupyterMixin): + """Construct a Syntax object to render syntax highlighted code. + + Args: + code (str): Code to highlight. + lexer (Lexer | str): Lexer to use (see https://pygments.org/docs/lexers/) + theme (str, optional): Color theme, aka Pygments style (see https://pygments.org/docs/styles/#getting-a-list-of-available-styles). Defaults to "monokai". + dedent (bool, optional): Enable stripping of initial whitespace. Defaults to False. + line_numbers (bool, optional): Enable rendering of line numbers. Defaults to False. + start_line (int, optional): Starting number for line numbers. Defaults to 1. + line_range (Tuple[int | None, int | None], optional): If given should be a tuple of the start and end line to render. + A value of None in the tuple indicates the range is open in that direction. + highlight_lines (Set[int]): A set of line numbers to highlight. + code_width: Width of code to render (not including line numbers), or ``None`` to use all available width. + tab_size (int, optional): Size of tabs. Defaults to 4. + word_wrap (bool, optional): Enable word wrapping. + background_color (str, optional): Optional background color, or None to use theme color. Defaults to None. + indent_guides (bool, optional): Show indent guides. Defaults to False. + padding (PaddingDimensions): Padding to apply around the syntax. Defaults to 0 (no padding). + """ + + _pygments_style_class: Type[PygmentsStyle] + _theme: SyntaxTheme + + @classmethod + def get_theme(cls, name: Union[str, SyntaxTheme]) -> SyntaxTheme: + """Get a syntax theme instance.""" + if isinstance(name, SyntaxTheme): + return name + theme: SyntaxTheme + if name in RICH_SYNTAX_THEMES: + theme = ANSISyntaxTheme(RICH_SYNTAX_THEMES[name]) + else: + theme = PygmentsSyntaxTheme(name) + return theme + + def __init__( + self, + code: str, + lexer: Union[Lexer, str], + *, + theme: Union[str, SyntaxTheme] = DEFAULT_THEME, + dedent: bool = False, + line_numbers: bool = False, + start_line: int = 1, + line_range: Optional[Tuple[Optional[int], Optional[int]]] = None, + highlight_lines: Optional[Set[int]] = None, + code_width: Optional[int] = None, + tab_size: int = 4, + word_wrap: bool = False, + background_color: Optional[str] = None, + indent_guides: bool = False, + padding: PaddingDimensions = 0, + ) -> None: + self.code = code + self._lexer = lexer + self.dedent = dedent + self.line_numbers = line_numbers + self.start_line = start_line + self.line_range = line_range + self.highlight_lines = highlight_lines or set() + self.code_width = code_width + self.tab_size = tab_size + self.word_wrap = word_wrap + self.background_color = background_color + self.background_style = ( + Style(bgcolor=background_color) if background_color else Style() + ) + self.indent_guides = indent_guides + self.padding = padding + + self._theme = self.get_theme(theme) + self._stylized_ranges: List[_SyntaxHighlightRange] = [] + + @classmethod + def from_path( + cls, + path: str, + encoding: str = "utf-8", + lexer: Optional[Union[Lexer, str]] = None, + theme: Union[str, SyntaxTheme] = DEFAULT_THEME, + dedent: bool = False, + line_numbers: bool = False, + line_range: Optional[Tuple[int, int]] = None, + start_line: int = 1, + highlight_lines: Optional[Set[int]] = None, + code_width: Optional[int] = None, + tab_size: int = 4, + word_wrap: bool = False, + background_color: Optional[str] = None, + indent_guides: bool = False, + padding: PaddingDimensions = 0, + ) -> "Syntax": + """Construct a Syntax object from a file. + + Args: + path (str): Path to file to highlight. + encoding (str): Encoding of file. + lexer (str | Lexer, optional): Lexer to use. If None, lexer will be auto-detected from path/file content. + theme (str, optional): Color theme, aka Pygments style (see https://pygments.org/docs/styles/#getting-a-list-of-available-styles). Defaults to "emacs". + dedent (bool, optional): Enable stripping of initial whitespace. Defaults to True. + line_numbers (bool, optional): Enable rendering of line numbers. Defaults to False. + start_line (int, optional): Starting number for line numbers. Defaults to 1. + line_range (Tuple[int, int], optional): If given should be a tuple of the start and end line to render. + highlight_lines (Set[int]): A set of line numbers to highlight. + code_width: Width of code to render (not including line numbers), or ``None`` to use all available width. + tab_size (int, optional): Size of tabs. Defaults to 4. + word_wrap (bool, optional): Enable word wrapping of code. + background_color (str, optional): Optional background color, or None to use theme color. Defaults to None. + indent_guides (bool, optional): Show indent guides. Defaults to False. + padding (PaddingDimensions): Padding to apply around the syntax. Defaults to 0 (no padding). + + Returns: + [Syntax]: A Syntax object that may be printed to the console + """ + code = Path(path).read_text(encoding=encoding) + + if not lexer: + lexer = cls.guess_lexer(path, code=code) + + return cls( + code, + lexer, + theme=theme, + dedent=dedent, + line_numbers=line_numbers, + line_range=line_range, + start_line=start_line, + highlight_lines=highlight_lines, + code_width=code_width, + tab_size=tab_size, + word_wrap=word_wrap, + background_color=background_color, + indent_guides=indent_guides, + padding=padding, + ) + + @classmethod + def guess_lexer(cls, path: str, code: Optional[str] = None) -> str: + """Guess the alias of the Pygments lexer to use based on a path and an optional string of code. + If code is supplied, it will use a combination of the code and the filename to determine the + best lexer to use. For example, if the file is ``index.html`` and the file contains Django + templating syntax, then "html+django" will be returned. If the file is ``index.html``, and no + templating language is used, the "html" lexer will be used. If no string of code + is supplied, the lexer will be chosen based on the file extension.. + + Args: + path (AnyStr): The path to the file containing the code you wish to know the lexer for. + code (str, optional): Optional string of code that will be used as a fallback if no lexer + is found for the supplied path. + + Returns: + str: The name of the Pygments lexer that best matches the supplied path/code. + """ + lexer: Optional[Lexer] = None + lexer_name = "default" + if code: + try: + lexer = guess_lexer_for_filename(path, code) + except ClassNotFound: + pass + + if not lexer: + try: + _, ext = os.path.splitext(path) + if ext: + extension = ext.lstrip(".").lower() + lexer = get_lexer_by_name(extension) + except ClassNotFound: + pass + + if lexer: + if lexer.aliases: + lexer_name = lexer.aliases[0] + else: + lexer_name = lexer.name + + return lexer_name + + def _get_base_style(self) -> Style: + """Get the base style.""" + default_style = self._theme.get_background_style() + self.background_style + return default_style + + def _get_token_color(self, token_type: TokenType) -> Optional[Color]: + """Get a color (if any) for the given token. + + Args: + token_type (TokenType): A token type tuple from Pygments. + + Returns: + Optional[Color]: Color from theme, or None for no color. + """ + style = self._theme.get_style_for_token(token_type) + return style.color + + @property + def lexer(self) -> Optional[Lexer]: + """The lexer for this syntax, or None if no lexer was found. + + Tries to find the lexer by name if a string was passed to the constructor. + """ + + if isinstance(self._lexer, Lexer): + return self._lexer + try: + return get_lexer_by_name( + self._lexer, + stripnl=False, + ensurenl=True, + tabsize=self.tab_size, + ) + except ClassNotFound: + return None + + @property + def default_lexer(self) -> Lexer: + """A Pygments Lexer to use if one is not specified or invalid.""" + return get_lexer_by_name( + "text", + stripnl=False, + ensurenl=True, + tabsize=self.tab_size, + ) + + def highlight( + self, + code: str, + line_range: Optional[Tuple[Optional[int], Optional[int]]] = None, + ) -> Text: + """Highlight code and return a Text instance. + + Args: + code (str): Code to highlight. + line_range(Tuple[int, int], optional): Optional line range to highlight. + + Returns: + Text: A text instance containing highlighted syntax. + """ + + base_style = self._get_base_style() + justify: JustifyMethod = ( + "default" if base_style.transparent_background else "left" + ) + + text = Text( + justify=justify, + style=base_style, + tab_size=self.tab_size, + no_wrap=not self.word_wrap, + ) + _get_theme_style = self._theme.get_style_for_token + + lexer = self.lexer or self.default_lexer + + if lexer is None: + text.append(code) + else: + if line_range: + # More complicated path to only stylize a portion of the code + # This speeds up further operations as there are less spans to process + line_start, line_end = line_range + + def line_tokenize() -> Iterable[Tuple[Any, str]]: + """Split tokens to one per line.""" + assert lexer # required to make MyPy happy - we know lexer is not None at this point + + for token_type, token in lexer.get_tokens(code): + while token: + line_token, new_line, token = token.partition("\n") + yield token_type, line_token + new_line + + def tokens_to_spans() -> Iterable[Tuple[str, Optional[Style]]]: + """Convert tokens to spans.""" + tokens = iter(line_tokenize()) + line_no = 0 + _line_start = line_start - 1 if line_start else 0 + + # Skip over tokens until line start + while line_no < _line_start: + try: + _token_type, token = next(tokens) + except StopIteration: + break + yield (token, None) + if token.endswith("\n"): + line_no += 1 + # Generate spans until line end + for token_type, token in tokens: + yield (token, _get_theme_style(token_type)) + if token.endswith("\n"): + line_no += 1 + if line_end and line_no >= line_end: + break + + text.append_tokens(tokens_to_spans()) + + else: + text.append_tokens( + (token, _get_theme_style(token_type)) + for token_type, token in lexer.get_tokens(code) + ) + if self.background_color is not None: + text.stylize(f"on {self.background_color}") + + if self._stylized_ranges: + self._apply_stylized_ranges(text) + + return text + + def stylize_range( + self, + style: StyleType, + start: SyntaxPosition, + end: SyntaxPosition, + style_before: bool = False, + ) -> None: + """ + Adds a custom style on a part of the code, that will be applied to the syntax display when it's rendered. + Line numbers are 1-based, while column indexes are 0-based. + + Args: + style (StyleType): The style to apply. + start (Tuple[int, int]): The start of the range, in the form `[line number, column index]`. + end (Tuple[int, int]): The end of the range, in the form `[line number, column index]`. + style_before (bool): Apply the style before any existing styles. + """ + self._stylized_ranges.append( + _SyntaxHighlightRange(style, start, end, style_before) + ) + + def _get_line_numbers_color(self, blend: float = 0.3) -> Color: + background_style = self._theme.get_background_style() + self.background_style + background_color = background_style.bgcolor + if background_color is None or background_color.is_system_defined: + return Color.default() + foreground_color = self._get_token_color(Token.Text) + if foreground_color is None or foreground_color.is_system_defined: + return foreground_color or Color.default() + new_color = blend_rgb( + background_color.get_truecolor(), + foreground_color.get_truecolor(), + cross_fade=blend, + ) + return Color.from_triplet(new_color) + + @property + def _numbers_column_width(self) -> int: + """Get the number of characters used to render the numbers column.""" + column_width = 0 + if self.line_numbers: + column_width = ( + len(str(self.start_line + self.code.count("\n"))) + + NUMBERS_COLUMN_DEFAULT_PADDING + ) + return column_width + + def _get_number_styles(self, console: Console) -> Tuple[Style, Style, Style]: + """Get background, number, and highlight styles for line numbers.""" + background_style = self._get_base_style() + if background_style.transparent_background: + return Style.null(), Style(dim=True), Style.null() + if console.color_system in ("256", "truecolor"): + number_style = Style.chain( + background_style, + self._theme.get_style_for_token(Token.Text), + Style(color=self._get_line_numbers_color()), + self.background_style, + ) + highlight_number_style = Style.chain( + background_style, + self._theme.get_style_for_token(Token.Text), + Style(bold=True, color=self._get_line_numbers_color(0.9)), + self.background_style, + ) + else: + number_style = background_style + Style(dim=True) + highlight_number_style = background_style + Style(dim=False) + return background_style, number_style, highlight_number_style + + def __rich_measure__( + self, console: "Console", options: "ConsoleOptions" + ) -> "Measurement": + _, right, _, left = Padding.unpack(self.padding) + padding = left + right + if self.code_width is not None: + width = self.code_width + self._numbers_column_width + padding + 1 + return Measurement(self._numbers_column_width, width) + lines = self.code.splitlines() + width = ( + self._numbers_column_width + + padding + + (max(cell_len(line) for line in lines) if lines else 0) + ) + if self.line_numbers: + width += 1 + return Measurement(self._numbers_column_width, width) + + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: + segments = Segments(self._get_syntax(console, options)) + if self.padding: + yield Padding(segments, style=self._get_base_style(), pad=self.padding) + else: + yield segments + + def _get_syntax( + self, + console: Console, + options: ConsoleOptions, + ) -> Iterable[Segment]: + """ + Get the Segments for the Syntax object, excluding any vertical/horizontal padding + """ + transparent_background = self._get_base_style().transparent_background + code_width = ( + ( + (options.max_width - self._numbers_column_width - 1) + if self.line_numbers + else options.max_width + ) + if self.code_width is None + else self.code_width + ) + + ends_on_nl, processed_code = self._process_code(self.code) + text = self.highlight(processed_code, self.line_range) + + if not self.line_numbers and not self.word_wrap and not self.line_range: + if not ends_on_nl: + text.remove_suffix("\n") + # Simple case of just rendering text + style = ( + self._get_base_style() + + self._theme.get_style_for_token(Comment) + + Style(dim=True) + + self.background_style + ) + if self.indent_guides and not options.ascii_only: + text = text.with_indent_guides(self.tab_size, style=style) + text.overflow = "crop" + if style.transparent_background: + yield from console.render( + text, options=options.update(width=code_width) + ) + else: + syntax_lines = console.render_lines( + text, + options.update(width=code_width, height=None, justify="left"), + style=self.background_style, + pad=True, + new_lines=True, + ) + for syntax_line in syntax_lines: + yield from syntax_line + return + + start_line, end_line = self.line_range or (None, None) + line_offset = 0 + if start_line: + line_offset = max(0, start_line - 1) + lines: Union[List[Text], Lines] = text.split("\n", allow_blank=ends_on_nl) + if self.line_range: + if line_offset > len(lines): + return + lines = lines[line_offset:end_line] + + if self.indent_guides and not options.ascii_only: + style = ( + self._get_base_style() + + self._theme.get_style_for_token(Comment) + + Style(dim=True) + + self.background_style + ) + lines = ( + Text("\n") + .join(lines) + .with_indent_guides(self.tab_size, style=style + Style(italic=False)) + .split("\n", allow_blank=True) + ) + + numbers_column_width = self._numbers_column_width + render_options = options.update(width=code_width) + + highlight_line = self.highlight_lines.__contains__ + _Segment = Segment + new_line = _Segment("\n") + + line_pointer = "> " if options.legacy_windows else "❱ " + + ( + background_style, + number_style, + highlight_number_style, + ) = self._get_number_styles(console) + + for line_no, line in enumerate(lines, self.start_line + line_offset): + if self.word_wrap: + wrapped_lines = console.render_lines( + line, + render_options.update(height=None, justify="left"), + style=background_style, + pad=not transparent_background, + ) + else: + segments = list(line.render(console, end="")) + if options.no_wrap: + wrapped_lines = [segments] + else: + wrapped_lines = [ + _Segment.adjust_line_length( + segments, + render_options.max_width, + style=background_style, + pad=not transparent_background, + ) + ] + + if self.line_numbers: + wrapped_line_left_pad = _Segment( + " " * numbers_column_width + " ", background_style + ) + for first, wrapped_line in loop_first(wrapped_lines): + if first: + line_column = str(line_no).rjust(numbers_column_width - 2) + " " + if highlight_line(line_no): + yield _Segment(line_pointer, Style(color="red")) + yield _Segment(line_column, highlight_number_style) + else: + yield _Segment(" ", highlight_number_style) + yield _Segment(line_column, number_style) + else: + yield wrapped_line_left_pad + yield from wrapped_line + yield new_line + else: + for wrapped_line in wrapped_lines: + yield from wrapped_line + yield new_line + + def _apply_stylized_ranges(self, text: Text) -> None: + """ + Apply stylized ranges to a text instance, + using the given code to determine the right portion to apply the style to. + + Args: + text (Text): Text instance to apply the style to. + """ + code = text.plain + newlines_offsets = [ + # Let's add outer boundaries at each side of the list: + 0, + # N.B. using "\n" here is much faster than using metacharacters such as "^" or "\Z": + *[ + match.start() + 1 + for match in re.finditer("\n", code, flags=re.MULTILINE) + ], + len(code) + 1, + ] + + for stylized_range in self._stylized_ranges: + start = _get_code_index_for_syntax_position( + newlines_offsets, stylized_range.start + ) + end = _get_code_index_for_syntax_position( + newlines_offsets, stylized_range.end + ) + if start is not None and end is not None: + if stylized_range.style_before: + text.stylize_before(stylized_range.style, start, end) + else: + text.stylize(stylized_range.style, start, end) + + def _process_code(self, code: str) -> Tuple[bool, str]: + """ + Applies various processing to a raw code string + (normalises it so it always ends with a line return, dedents it if necessary, etc.) + + Args: + code (str): The raw code string to process + + Returns: + Tuple[bool, str]: the boolean indicates whether the raw code ends with a line return, + while the string is the processed code. + """ + ends_on_nl = code.endswith("\n") + processed_code = code if ends_on_nl else code + "\n" + processed_code = ( + textwrap.dedent(processed_code) if self.dedent else processed_code + ) + processed_code = processed_code.expandtabs(self.tab_size) + return ends_on_nl, processed_code + + +def _get_code_index_for_syntax_position( + newlines_offsets: Sequence[int], position: SyntaxPosition +) -> Optional[int]: + """ + Returns the index of the code string for the given positions. + + Args: + newlines_offsets (Sequence[int]): The offset of each newline character found in the code snippet. + position (SyntaxPosition): The position to search for. + + Returns: + Optional[int]: The index of the code string for this position, or `None` + if the given position's line number is out of range (if it's the column that is out of range + we silently clamp its value so that it reaches the end of the line) + """ + lines_count = len(newlines_offsets) + + line_number, column_index = position + if line_number > lines_count or len(newlines_offsets) < (line_number + 1): + return None # `line_number` is out of range + line_index = line_number - 1 + line_length = newlines_offsets[line_index + 1] - newlines_offsets[line_index] - 1 + # If `column_index` is out of range: let's silently clamp it: + column_index = min(line_length, column_index) + return newlines_offsets[line_index] + column_index + + +if __name__ == "__main__": # pragma: no cover + import argparse + import sys + + parser = argparse.ArgumentParser( + description="Render syntax to the console with Rich" + ) + parser.add_argument( + "path", + metavar="PATH", + help="path to file, or - for stdin", + ) + parser.add_argument( + "-c", + "--force-color", + dest="force_color", + action="store_true", + default=None, + help="force color for non-terminals", + ) + parser.add_argument( + "-i", + "--indent-guides", + dest="indent_guides", + action="store_true", + default=False, + help="display indent guides", + ) + parser.add_argument( + "-l", + "--line-numbers", + dest="line_numbers", + action="store_true", + help="render line numbers", + ) + parser.add_argument( + "-w", + "--width", + type=int, + dest="width", + default=None, + help="width of output (default will auto-detect)", + ) + parser.add_argument( + "-r", + "--wrap", + dest="word_wrap", + action="store_true", + default=False, + help="word wrap long lines", + ) + parser.add_argument( + "-s", + "--soft-wrap", + action="store_true", + dest="soft_wrap", + default=False, + help="enable soft wrapping mode", + ) + parser.add_argument( + "-t", "--theme", dest="theme", default="monokai", help="pygments theme" + ) + parser.add_argument( + "-b", + "--background-color", + dest="background_color", + default=None, + help="Override background color", + ) + parser.add_argument( + "-x", + "--lexer", + default=None, + dest="lexer_name", + help="Lexer name", + ) + parser.add_argument( + "-p", "--padding", type=int, default=0, dest="padding", help="Padding" + ) + parser.add_argument( + "--highlight-line", + type=int, + default=None, + dest="highlight_line", + help="The line number (not index!) to highlight", + ) + args = parser.parse_args() + + from rich.console import Console + + console = Console(force_terminal=args.force_color, width=args.width) + + if args.path == "-": + code = sys.stdin.read() + syntax = Syntax( + code=code, + lexer=args.lexer_name, + line_numbers=args.line_numbers, + word_wrap=args.word_wrap, + theme=args.theme, + background_color=args.background_color, + indent_guides=args.indent_guides, + padding=args.padding, + highlight_lines={args.highlight_line}, + ) + else: + syntax = Syntax.from_path( + args.path, + lexer=args.lexer_name, + line_numbers=args.line_numbers, + word_wrap=args.word_wrap, + theme=args.theme, + background_color=args.background_color, + indent_guides=args.indent_guides, + padding=args.padding, + highlight_lines={args.highlight_line}, + ) + console.print(syntax, soft_wrap=args.soft_wrap) diff --git a/venv/lib/python3.10/site-packages/rich/table.py b/venv/lib/python3.10/site-packages/rich/table.py new file mode 100644 index 0000000000000000000000000000000000000000..942175dc3a53ffbfb0df3fc3f2dc921a148243fd --- /dev/null +++ b/venv/lib/python3.10/site-packages/rich/table.py @@ -0,0 +1,1006 @@ +from dataclasses import dataclass, field, replace +from typing import ( + TYPE_CHECKING, + Dict, + Iterable, + List, + NamedTuple, + Optional, + Sequence, + Tuple, + Union, +) + +from . import box, errors +from ._loop import loop_first_last, loop_last +from ._pick import pick_bool +from ._ratio import ratio_distribute, ratio_reduce +from .align import VerticalAlignMethod +from .jupyter import JupyterMixin +from .measure import Measurement +from .padding import Padding, PaddingDimensions +from .protocol import is_renderable +from .segment import Segment +from .style import Style, StyleType +from .text import Text, TextType + +if TYPE_CHECKING: + from .console import ( + Console, + ConsoleOptions, + JustifyMethod, + OverflowMethod, + RenderableType, + RenderResult, + ) + + +@dataclass +class Column: + """Defines a column within a ~Table. + + Args: + title (Union[str, Text], optional): The title of the table rendered at the top. Defaults to None. + caption (Union[str, Text], optional): The table caption rendered below. Defaults to None. + width (int, optional): The width in characters of the table, or ``None`` to automatically fit. Defaults to None. + min_width (Optional[int], optional): The minimum width of the table, or ``None`` for no minimum. Defaults to None. + box (box.Box, optional): One of the constants in box.py used to draw the edges (see :ref:`appendix_box`), or ``None`` for no box lines. Defaults to box.HEAVY_HEAD. + safe_box (Optional[bool], optional): Disable box characters that don't display on windows legacy terminal with *raster* fonts. Defaults to True. + padding (PaddingDimensions, optional): Padding for cells (top, right, bottom, left). Defaults to (0, 1). + collapse_padding (bool, optional): Enable collapsing of padding around cells. Defaults to False. + pad_edge (bool, optional): Enable padding of edge cells. Defaults to True. + expand (bool, optional): Expand the table to fit the available space if ``True``, otherwise the table width will be auto-calculated. Defaults to False. + show_header (bool, optional): Show a header row. Defaults to True. + show_footer (bool, optional): Show a footer row. Defaults to False. + show_edge (bool, optional): Draw a box around the outside of the table. Defaults to True. + show_lines (bool, optional): Draw lines between every row. Defaults to False. + leading (int, optional): Number of blank lines between rows (precludes ``show_lines``). Defaults to 0. + style (Union[str, Style], optional): Default style for the table. Defaults to "none". + row_styles (List[Union, str], optional): Optional list of row styles, if more than one style is given then the styles will alternate. Defaults to None. + header_style (Union[str, Style], optional): Style of the header. Defaults to "table.header". + footer_style (Union[str, Style], optional): Style of the footer. Defaults to "table.footer". + border_style (Union[str, Style], optional): Style of the border. Defaults to None. + title_style (Union[str, Style], optional): Style of the title. Defaults to None. + caption_style (Union[str, Style], optional): Style of the caption. Defaults to None. + title_justify (str, optional): Justify method for title. Defaults to "center". + caption_justify (str, optional): Justify method for caption. Defaults to "center". + highlight (bool, optional): Highlight cell contents (if str). Defaults to False. + """ + + header: "RenderableType" = "" + """RenderableType: Renderable for the header (typically a string)""" + + footer: "RenderableType" = "" + """RenderableType: Renderable for the footer (typically a string)""" + + header_style: StyleType = "" + """StyleType: The style of the header.""" + + footer_style: StyleType = "" + """StyleType: The style of the footer.""" + + style: StyleType = "" + """StyleType: The style of the column.""" + + justify: "JustifyMethod" = "left" + """str: How to justify text within the column ("left", "center", "right", or "full")""" + + vertical: "VerticalAlignMethod" = "top" + """str: How to vertically align content ("top", "middle", or "bottom")""" + + overflow: "OverflowMethod" = "ellipsis" + """str: Overflow method.""" + + width: Optional[int] = None + """Optional[int]: Width of the column, or ``None`` (default) to auto calculate width.""" + + min_width: Optional[int] = None + """Optional[int]: Minimum width of column, or ``None`` for no minimum. Defaults to None.""" + + max_width: Optional[int] = None + """Optional[int]: Maximum width of column, or ``None`` for no maximum. Defaults to None.""" + + ratio: Optional[int] = None + """Optional[int]: Ratio to use when calculating column width, or ``None`` (default) to adapt to column contents.""" + + no_wrap: bool = False + """bool: Prevent wrapping of text within the column. Defaults to ``False``.""" + + highlight: bool = False + """bool: Apply highlighter to column. Defaults to ``False``.""" + + _index: int = 0 + """Index of column.""" + + _cells: List["RenderableType"] = field(default_factory=list) + + def copy(self) -> "Column": + """Return a copy of this Column.""" + return replace(self, _cells=[]) + + @property + def cells(self) -> Iterable["RenderableType"]: + """Get all cells in the column, not including header.""" + yield from self._cells + + @property + def flexible(self) -> bool: + """Check if this column is flexible.""" + return self.ratio is not None + + +@dataclass +class Row: + """Information regarding a row.""" + + style: Optional[StyleType] = None + """Style to apply to row.""" + + end_section: bool = False + """Indicated end of section, which will force a line beneath the row.""" + + +class _Cell(NamedTuple): + """A single cell in a table.""" + + style: StyleType + """Style to apply to cell.""" + renderable: "RenderableType" + """Cell renderable.""" + vertical: VerticalAlignMethod + """Cell vertical alignment.""" + + +class Table(JupyterMixin): + """A console renderable to draw a table. + + Args: + *headers (Union[Column, str]): Column headers, either as a string, or :class:`~rich.table.Column` instance. + title (Union[str, Text], optional): The title of the table rendered at the top. Defaults to None. + caption (Union[str, Text], optional): The table caption rendered below. Defaults to None. + width (int, optional): The width in characters of the table, or ``None`` to automatically fit. Defaults to None. + min_width (Optional[int], optional): The minimum width of the table, or ``None`` for no minimum. Defaults to None. + box (box.Box, optional): One of the constants in box.py used to draw the edges (see :ref:`appendix_box`), or ``None`` for no box lines. Defaults to box.HEAVY_HEAD. + safe_box (Optional[bool], optional): Disable box characters that don't display on windows legacy terminal with *raster* fonts. Defaults to True. + padding (PaddingDimensions, optional): Padding for cells (top, right, bottom, left). Defaults to (0, 1). + collapse_padding (bool, optional): Enable collapsing of padding around cells. Defaults to False. + pad_edge (bool, optional): Enable padding of edge cells. Defaults to True. + expand (bool, optional): Expand the table to fit the available space if ``True``, otherwise the table width will be auto-calculated. Defaults to False. + show_header (bool, optional): Show a header row. Defaults to True. + show_footer (bool, optional): Show a footer row. Defaults to False. + show_edge (bool, optional): Draw a box around the outside of the table. Defaults to True. + show_lines (bool, optional): Draw lines between every row. Defaults to False. + leading (int, optional): Number of blank lines between rows (precludes ``show_lines``). Defaults to 0. + style (Union[str, Style], optional): Default style for the table. Defaults to "none". + row_styles (List[Union, str], optional): Optional list of row styles, if more than one style is given then the styles will alternate. Defaults to None. + header_style (Union[str, Style], optional): Style of the header. Defaults to "table.header". + footer_style (Union[str, Style], optional): Style of the footer. Defaults to "table.footer". + border_style (Union[str, Style], optional): Style of the border. Defaults to None. + title_style (Union[str, Style], optional): Style of the title. Defaults to None. + caption_style (Union[str, Style], optional): Style of the caption. Defaults to None. + title_justify (str, optional): Justify method for title. Defaults to "center". + caption_justify (str, optional): Justify method for caption. Defaults to "center". + highlight (bool, optional): Highlight cell contents (if str). Defaults to False. + """ + + columns: List[Column] + rows: List[Row] + + def __init__( + self, + *headers: Union[Column, str], + title: Optional[TextType] = None, + caption: Optional[TextType] = None, + width: Optional[int] = None, + min_width: Optional[int] = None, + box: Optional[box.Box] = box.HEAVY_HEAD, + safe_box: Optional[bool] = None, + padding: PaddingDimensions = (0, 1), + collapse_padding: bool = False, + pad_edge: bool = True, + expand: bool = False, + show_header: bool = True, + show_footer: bool = False, + show_edge: bool = True, + show_lines: bool = False, + leading: int = 0, + style: StyleType = "none", + row_styles: Optional[Iterable[StyleType]] = None, + header_style: Optional[StyleType] = "table.header", + footer_style: Optional[StyleType] = "table.footer", + border_style: Optional[StyleType] = None, + title_style: Optional[StyleType] = None, + caption_style: Optional[StyleType] = None, + title_justify: "JustifyMethod" = "center", + caption_justify: "JustifyMethod" = "center", + highlight: bool = False, + ) -> None: + self.columns: List[Column] = [] + self.rows: List[Row] = [] + self.title = title + self.caption = caption + self.width = width + self.min_width = min_width + self.box = box + self.safe_box = safe_box + self._padding = Padding.unpack(padding) + self.pad_edge = pad_edge + self._expand = expand + self.show_header = show_header + self.show_footer = show_footer + self.show_edge = show_edge + self.show_lines = show_lines + self.leading = leading + self.collapse_padding = collapse_padding + self.style = style + self.header_style = header_style or "" + self.footer_style = footer_style or "" + self.border_style = border_style + self.title_style = title_style + self.caption_style = caption_style + self.title_justify: "JustifyMethod" = title_justify + self.caption_justify: "JustifyMethod" = caption_justify + self.highlight = highlight + self.row_styles: Sequence[StyleType] = list(row_styles or []) + append_column = self.columns.append + for header in headers: + if isinstance(header, str): + self.add_column(header=header) + else: + header._index = len(self.columns) + append_column(header) + + @classmethod + def grid( + cls, + *headers: Union[Column, str], + padding: PaddingDimensions = 0, + collapse_padding: bool = True, + pad_edge: bool = False, + expand: bool = False, + ) -> "Table": + """Get a table with no lines, headers, or footer. + + Args: + *headers (Union[Column, str]): Column headers, either as a string, or :class:`~rich.table.Column` instance. + padding (PaddingDimensions, optional): Get padding around cells. Defaults to 0. + collapse_padding (bool, optional): Enable collapsing of padding around cells. Defaults to True. + pad_edge (bool, optional): Enable padding around edges of table. Defaults to False. + expand (bool, optional): Expand the table to fit the available space if ``True``, otherwise the table width will be auto-calculated. Defaults to False. + + Returns: + Table: A table instance. + """ + return cls( + *headers, + box=None, + padding=padding, + collapse_padding=collapse_padding, + show_header=False, + show_footer=False, + show_edge=False, + pad_edge=pad_edge, + expand=expand, + ) + + @property + def expand(self) -> bool: + """Setting a non-None self.width implies expand.""" + return self._expand or self.width is not None + + @expand.setter + def expand(self, expand: bool) -> None: + """Set expand.""" + self._expand = expand + + @property + def _extra_width(self) -> int: + """Get extra width to add to cell content.""" + width = 0 + if self.box and self.show_edge: + width += 2 + if self.box: + width += len(self.columns) - 1 + return width + + @property + def row_count(self) -> int: + """Get the current number of rows.""" + return len(self.rows) + + def get_row_style(self, console: "Console", index: int) -> StyleType: + """Get the current row style.""" + style = Style.null() + if self.row_styles: + style += console.get_style(self.row_styles[index % len(self.row_styles)]) + row_style = self.rows[index].style + if row_style is not None: + style += console.get_style(row_style) + return style + + def __rich_measure__( + self, console: "Console", options: "ConsoleOptions" + ) -> Measurement: + max_width = options.max_width + if self.width is not None: + max_width = self.width + if max_width < 0: + return Measurement(0, 0) + + extra_width = self._extra_width + max_width = sum( + self._calculate_column_widths( + console, options.update_width(max_width - extra_width) + ) + ) + _measure_column = self._measure_column + + measurements = [ + _measure_column(console, options.update_width(max_width), column) + for column in self.columns + ] + minimum_width = ( + sum(measurement.minimum for measurement in measurements) + extra_width + ) + maximum_width = ( + sum(measurement.maximum for measurement in measurements) + extra_width + if (self.width is None) + else self.width + ) + measurement = Measurement(minimum_width, maximum_width) + measurement = measurement.clamp(self.min_width) + return measurement + + @property + def padding(self) -> Tuple[int, int, int, int]: + """Get cell padding.""" + return self._padding + + @padding.setter + def padding(self, padding: PaddingDimensions) -> "Table": + """Set cell padding.""" + self._padding = Padding.unpack(padding) + return self + + def add_column( + self, + header: "RenderableType" = "", + footer: "RenderableType" = "", + *, + header_style: Optional[StyleType] = None, + highlight: Optional[bool] = None, + footer_style: Optional[StyleType] = None, + style: Optional[StyleType] = None, + justify: "JustifyMethod" = "left", + vertical: "VerticalAlignMethod" = "top", + overflow: "OverflowMethod" = "ellipsis", + width: Optional[int] = None, + min_width: Optional[int] = None, + max_width: Optional[int] = None, + ratio: Optional[int] = None, + no_wrap: bool = False, + ) -> None: + """Add a column to the table. + + Args: + header (RenderableType, optional): Text or renderable for the header. + Defaults to "". + footer (RenderableType, optional): Text or renderable for the footer. + Defaults to "". + header_style (Union[str, Style], optional): Style for the header, or None for default. Defaults to None. + highlight (bool, optional): Whether to highlight the text. The default of None uses the value of the table (self) object. + footer_style (Union[str, Style], optional): Style for the footer, or None for default. Defaults to None. + style (Union[str, Style], optional): Style for the column cells, or None for default. Defaults to None. + justify (JustifyMethod, optional): Alignment for cells. Defaults to "left". + vertical (VerticalAlignMethod, optional): Vertical alignment, one of "top", "middle", or "bottom". Defaults to "top". + overflow (OverflowMethod): Overflow method: "crop", "fold", "ellipsis". Defaults to "ellipsis". + width (int, optional): Desired width of column in characters, or None to fit to contents. Defaults to None. + min_width (Optional[int], optional): Minimum width of column, or ``None`` for no minimum. Defaults to None. + max_width (Optional[int], optional): Maximum width of column, or ``None`` for no maximum. Defaults to None. + ratio (int, optional): Flexible ratio for the column (requires ``Table.expand`` or ``Table.width``). Defaults to None. + no_wrap (bool, optional): Set to ``True`` to disable wrapping of this column. + """ + + column = Column( + _index=len(self.columns), + header=header, + footer=footer, + header_style=header_style or "", + highlight=highlight if highlight is not None else self.highlight, + footer_style=footer_style or "", + style=style or "", + justify=justify, + vertical=vertical, + overflow=overflow, + width=width, + min_width=min_width, + max_width=max_width, + ratio=ratio, + no_wrap=no_wrap, + ) + self.columns.append(column) + + def add_row( + self, + *renderables: Optional["RenderableType"], + style: Optional[StyleType] = None, + end_section: bool = False, + ) -> None: + """Add a row of renderables. + + Args: + *renderables (None or renderable): Each cell in a row must be a renderable object (including str), + or ``None`` for a blank cell. + style (StyleType, optional): An optional style to apply to the entire row. Defaults to None. + end_section (bool, optional): End a section and draw a line. Defaults to False. + + Raises: + errors.NotRenderableError: If you add something that can't be rendered. + """ + + def add_cell(column: Column, renderable: "RenderableType") -> None: + column._cells.append(renderable) + + cell_renderables: List[Optional["RenderableType"]] = list(renderables) + + columns = self.columns + if len(cell_renderables) < len(columns): + cell_renderables = [ + *cell_renderables, + *[None] * (len(columns) - len(cell_renderables)), + ] + for index, renderable in enumerate(cell_renderables): + if index == len(columns): + column = Column(_index=index, highlight=self.highlight) + for _ in self.rows: + add_cell(column, Text("")) + self.columns.append(column) + else: + column = columns[index] + if renderable is None: + add_cell(column, "") + elif is_renderable(renderable): + add_cell(column, renderable) + else: + raise errors.NotRenderableError( + f"unable to render {type(renderable).__name__}; a string or other renderable object is required" + ) + self.rows.append(Row(style=style, end_section=end_section)) + + def add_section(self) -> None: + """Add a new section (draw a line after current row).""" + + if self.rows: + self.rows[-1].end_section = True + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + if not self.columns: + yield Segment("\n") + return + + max_width = options.max_width + if self.width is not None: + max_width = self.width + + extra_width = self._extra_width + widths = self._calculate_column_widths( + console, options.update_width(max_width - extra_width) + ) + table_width = sum(widths) + extra_width + + render_options = options.update( + width=table_width, highlight=self.highlight, height=None + ) + + def render_annotation( + text: TextType, style: StyleType, justify: "JustifyMethod" = "center" + ) -> "RenderResult": + render_text = ( + console.render_str(text, style=style, highlight=False) + if isinstance(text, str) + else text + ) + return console.render( + render_text, options=render_options.update(justify=justify) + ) + + if self.title: + yield from render_annotation( + self.title, + style=Style.pick_first(self.title_style, "table.title"), + justify=self.title_justify, + ) + yield from self._render(console, render_options, widths) + if self.caption: + yield from render_annotation( + self.caption, + style=Style.pick_first(self.caption_style, "table.caption"), + justify=self.caption_justify, + ) + + def _calculate_column_widths( + self, console: "Console", options: "ConsoleOptions" + ) -> List[int]: + """Calculate the widths of each column, including padding, not including borders.""" + max_width = options.max_width + columns = self.columns + width_ranges = [ + self._measure_column(console, options, column) for column in columns + ] + widths = [_range.maximum or 1 for _range in width_ranges] + get_padding_width = self._get_padding_width + extra_width = self._extra_width + if self.expand: + ratios = [col.ratio or 0 for col in columns if col.flexible] + if any(ratios): + fixed_widths = [ + 0 if column.flexible else _range.maximum + for _range, column in zip(width_ranges, columns) + ] + flex_minimum = [ + (column.width or 1) + get_padding_width(column._index) + for column in columns + if column.flexible + ] + flexible_width = max_width - sum(fixed_widths) + flex_widths = ratio_distribute(flexible_width, ratios, flex_minimum) + iter_flex_widths = iter(flex_widths) + for index, column in enumerate(columns): + if column.flexible: + widths[index] = fixed_widths[index] + next(iter_flex_widths) + table_width = sum(widths) + + if table_width > max_width: + widths = self._collapse_widths( + widths, + [(column.width is None and not column.no_wrap) for column in columns], + max_width, + ) + table_width = sum(widths) + # last resort, reduce columns evenly + if table_width > max_width: + excess_width = table_width - max_width + widths = ratio_reduce(excess_width, [1] * len(widths), widths, widths) + table_width = sum(widths) + + width_ranges = [ + self._measure_column(console, options.update_width(width), column) + for width, column in zip(widths, columns) + ] + widths = [_range.maximum or 0 for _range in width_ranges] + + if (table_width < max_width and self.expand) or ( + self.min_width is not None and table_width < (self.min_width - extra_width) + ): + _max_width = ( + max_width + if self.min_width is None + else min(self.min_width - extra_width, max_width) + ) + pad_widths = ratio_distribute(_max_width - table_width, widths) + widths = [_width + pad for _width, pad in zip(widths, pad_widths)] + + return widths + + @classmethod + def _collapse_widths( + cls, widths: List[int], wrapable: List[bool], max_width: int + ) -> List[int]: + """Reduce widths so that the total is under max_width. + + Args: + widths (List[int]): List of widths. + wrapable (List[bool]): List of booleans that indicate if a column may shrink. + max_width (int): Maximum width to reduce to. + + Returns: + List[int]: A new list of widths. + """ + total_width = sum(widths) + excess_width = total_width - max_width + if any(wrapable): + while total_width and excess_width > 0: + max_column = max( + width for width, allow_wrap in zip(widths, wrapable) if allow_wrap + ) + second_max_column = max( + width if allow_wrap and width != max_column else 0 + for width, allow_wrap in zip(widths, wrapable) + ) + column_difference = max_column - second_max_column + ratios = [ + (1 if (width == max_column and allow_wrap) else 0) + for width, allow_wrap in zip(widths, wrapable) + ] + if not any(ratios) or not column_difference: + break + max_reduce = [min(excess_width, column_difference)] * len(widths) + widths = ratio_reduce(excess_width, ratios, max_reduce, widths) + + total_width = sum(widths) + excess_width = total_width - max_width + return widths + + def _get_cells( + self, console: "Console", column_index: int, column: Column + ) -> Iterable[_Cell]: + """Get all the cells with padding and optional header.""" + + collapse_padding = self.collapse_padding + pad_edge = self.pad_edge + padding = self.padding + any_padding = any(padding) + + first_column = column_index == 0 + last_column = column_index == len(self.columns) - 1 + + _padding_cache: Dict[Tuple[bool, bool], Tuple[int, int, int, int]] = {} + + def get_padding(first_row: bool, last_row: bool) -> Tuple[int, int, int, int]: + cached = _padding_cache.get((first_row, last_row)) + if cached: + return cached + top, right, bottom, left = padding + + if collapse_padding: + if not first_column: + left = max(0, left - right) + if not last_row: + bottom = max(0, top - bottom) + + if not pad_edge: + if first_column: + left = 0 + if last_column: + right = 0 + if first_row: + top = 0 + if last_row: + bottom = 0 + _padding = (top, right, bottom, left) + _padding_cache[(first_row, last_row)] = _padding + return _padding + + raw_cells: List[Tuple[StyleType, "RenderableType"]] = [] + _append = raw_cells.append + get_style = console.get_style + if self.show_header: + header_style = get_style(self.header_style or "") + get_style( + column.header_style + ) + _append((header_style, column.header)) + cell_style = get_style(column.style or "") + for cell in column.cells: + _append((cell_style, cell)) + if self.show_footer: + footer_style = get_style(self.footer_style or "") + get_style( + column.footer_style + ) + _append((footer_style, column.footer)) + + if any_padding: + _Padding = Padding + for first, last, (style, renderable) in loop_first_last(raw_cells): + yield _Cell( + style, + _Padding(renderable, get_padding(first, last)), + getattr(renderable, "vertical", None) or column.vertical, + ) + else: + for style, renderable in raw_cells: + yield _Cell( + style, + renderable, + getattr(renderable, "vertical", None) or column.vertical, + ) + + def _get_padding_width(self, column_index: int) -> int: + """Get extra width from padding.""" + _, pad_right, _, pad_left = self.padding + if self.collapse_padding: + if column_index > 0: + pad_left = max(0, pad_left - pad_right) + return pad_left + pad_right + + def _measure_column( + self, + console: "Console", + options: "ConsoleOptions", + column: Column, + ) -> Measurement: + """Get the minimum and maximum width of the column.""" + + max_width = options.max_width + if max_width < 1: + return Measurement(0, 0) + + padding_width = self._get_padding_width(column._index) + + if column.width is not None: + # Fixed width column + return Measurement( + column.width + padding_width, column.width + padding_width + ).with_maximum(max_width) + # Flexible column, we need to measure contents + min_widths: List[int] = [] + max_widths: List[int] = [] + append_min = min_widths.append + append_max = max_widths.append + get_render_width = Measurement.get + for cell in self._get_cells(console, column._index, column): + _min, _max = get_render_width(console, options, cell.renderable) + append_min(_min) + append_max(_max) + + measurement = Measurement( + max(min_widths) if min_widths else 1, + max(max_widths) if max_widths else max_width, + ).with_maximum(max_width) + measurement = measurement.clamp( + None if column.min_width is None else column.min_width + padding_width, + None if column.max_width is None else column.max_width + padding_width, + ) + return measurement + + def _render( + self, console: "Console", options: "ConsoleOptions", widths: List[int] + ) -> "RenderResult": + table_style = console.get_style(self.style or "") + + border_style = table_style + console.get_style(self.border_style or "") + _column_cells = ( + self._get_cells(console, column_index, column) + for column_index, column in enumerate(self.columns) + ) + row_cells: List[Tuple[_Cell, ...]] = list(zip(*_column_cells)) + _box = ( + self.box.substitute( + options, safe=pick_bool(self.safe_box, console.safe_box) + ) + if self.box + else None + ) + _box = _box.get_plain_headed_box() if _box and not self.show_header else _box + + new_line = Segment.line() + + columns = self.columns + show_header = self.show_header + show_footer = self.show_footer + show_edge = self.show_edge + show_lines = self.show_lines + leading = self.leading + + _Segment = Segment + if _box: + box_segments = [ + ( + _Segment(_box.head_left, border_style), + _Segment(_box.head_right, border_style), + _Segment(_box.head_vertical, border_style), + ), + ( + _Segment(_box.mid_left, border_style), + _Segment(_box.mid_right, border_style), + _Segment(_box.mid_vertical, border_style), + ), + ( + _Segment(_box.foot_left, border_style), + _Segment(_box.foot_right, border_style), + _Segment(_box.foot_vertical, border_style), + ), + ] + if show_edge: + yield _Segment(_box.get_top(widths), border_style) + yield new_line + else: + box_segments = [] + + get_row_style = self.get_row_style + get_style = console.get_style + + for index, (first, last, row_cell) in enumerate(loop_first_last(row_cells)): + header_row = first and show_header + footer_row = last and show_footer + row = ( + self.rows[index - show_header] + if (not header_row and not footer_row) + else None + ) + max_height = 1 + cells: List[List[List[Segment]]] = [] + if header_row or footer_row: + row_style = Style.null() + else: + row_style = get_style( + get_row_style(console, index - 1 if show_header else index) + ) + for width, cell, column in zip(widths, row_cell, columns): + render_options = options.update( + width=width, + justify=column.justify, + no_wrap=column.no_wrap, + overflow=column.overflow, + height=None, + highlight=column.highlight, + ) + lines = console.render_lines( + cell.renderable, + render_options, + style=get_style(cell.style) + row_style, + ) + max_height = max(max_height, len(lines)) + cells.append(lines) + + row_height = max(len(cell) for cell in cells) + + def align_cell( + cell: List[List[Segment]], + vertical: "VerticalAlignMethod", + width: int, + style: Style, + ) -> List[List[Segment]]: + if header_row: + vertical = "bottom" + elif footer_row: + vertical = "top" + + if vertical == "top": + return _Segment.align_top(cell, width, row_height, style) + elif vertical == "middle": + return _Segment.align_middle(cell, width, row_height, style) + return _Segment.align_bottom(cell, width, row_height, style) + + cells[:] = [ + _Segment.set_shape( + align_cell( + cell, + _cell.vertical, + width, + get_style(_cell.style) + row_style, + ), + width, + max_height, + ) + for width, _cell, cell, column in zip(widths, row_cell, cells, columns) + ] + + if _box: + if last and show_footer: + yield _Segment( + _box.get_row(widths, "foot", edge=show_edge), border_style + ) + yield new_line + left, right, _divider = box_segments[0 if first else (2 if last else 1)] + + # If the column divider is whitespace also style it with the row background + divider = ( + _divider + if _divider.text.strip() + else _Segment( + _divider.text, row_style.background_style + _divider.style + ) + ) + for line_no in range(max_height): + if show_edge: + yield left + for last_cell, rendered_cell in loop_last(cells): + yield from rendered_cell[line_no] + if not last_cell: + yield divider + if show_edge: + yield right + yield new_line + else: + for line_no in range(max_height): + for rendered_cell in cells: + yield from rendered_cell[line_no] + yield new_line + if _box and first and show_header: + yield _Segment( + _box.get_row(widths, "head", edge=show_edge), border_style + ) + yield new_line + end_section = row and row.end_section + if _box and (show_lines or leading or end_section): + if ( + not last + and not (show_footer and index >= len(row_cells) - 2) + and not (show_header and header_row) + ): + if leading: + yield _Segment( + _box.get_row(widths, "mid", edge=show_edge) * leading, + border_style, + ) + else: + yield _Segment( + _box.get_row(widths, "row", edge=show_edge), border_style + ) + yield new_line + + if _box and show_edge: + yield _Segment(_box.get_bottom(widths), border_style) + yield new_line + + +if __name__ == "__main__": # pragma: no cover + from rich.console import Console + from rich.highlighter import ReprHighlighter + + from ._timer import timer + + with timer("Table render"): + table = Table( + title="Star Wars Movies", + caption="Rich example table", + caption_justify="right", + ) + + table.add_column( + "Released", header_style="bright_cyan", style="cyan", no_wrap=True + ) + table.add_column("Title", style="magenta") + table.add_column("Box Office", justify="right", style="green") + + table.add_row( + "Dec 20, 2019", + "Star Wars: The Rise of Skywalker", + "$952,110,690", + ) + table.add_row("May 25, 2018", "Solo: A Star Wars Story", "$393,151,347") + table.add_row( + "Dec 15, 2017", + "Star Wars Ep. V111: The Last Jedi", + "$1,332,539,889", + style="on black", + end_section=True, + ) + table.add_row( + "Dec 16, 2016", + "Rogue One: A Star Wars Story", + "$1,332,439,889", + ) + + def header(text: str) -> None: + console.print() + console.rule(highlight(text)) + console.print() + + console = Console() + highlight = ReprHighlighter() + header("Example Table") + console.print(table, justify="center") + + table.expand = True + header("expand=True") + console.print(table) + + table.width = 50 + header("width=50") + + console.print(table, justify="center") + + table.width = None + table.expand = False + table.row_styles = ["dim", "none"] + header("row_styles=['dim', 'none']") + + console.print(table, justify="center") + + table.width = None + table.expand = False + table.row_styles = ["dim", "none"] + table.leading = 1 + header("leading=1, row_styles=['dim', 'none']") + console.print(table, justify="center") + + table.width = None + table.expand = False + table.row_styles = ["dim", "none"] + table.show_lines = True + table.leading = 0 + header("show_lines=True, row_styles=['dim', 'none']") + console.print(table, justify="center") diff --git a/venv/lib/python3.10/site-packages/rich/terminal_theme.py b/venv/lib/python3.10/site-packages/rich/terminal_theme.py new file mode 100644 index 0000000000000000000000000000000000000000..565e9d960f8604c487e063ad9ed3f6f63027f3b4 --- /dev/null +++ b/venv/lib/python3.10/site-packages/rich/terminal_theme.py @@ -0,0 +1,153 @@ +from typing import List, Optional, Tuple + +from .color_triplet import ColorTriplet +from .palette import Palette + +_ColorTuple = Tuple[int, int, int] + + +class TerminalTheme: + """A color theme used when exporting console content. + + Args: + background (Tuple[int, int, int]): The background color. + foreground (Tuple[int, int, int]): The foreground (text) color. + normal (List[Tuple[int, int, int]]): A list of 8 normal intensity colors. + bright (List[Tuple[int, int, int]], optional): A list of 8 bright colors, or None + to repeat normal intensity. Defaults to None. + """ + + def __init__( + self, + background: _ColorTuple, + foreground: _ColorTuple, + normal: List[_ColorTuple], + bright: Optional[List[_ColorTuple]] = None, + ) -> None: + self.background_color = ColorTriplet(*background) + self.foreground_color = ColorTriplet(*foreground) + self.ansi_colors = Palette(normal + (bright or normal)) + + +DEFAULT_TERMINAL_THEME = TerminalTheme( + (255, 255, 255), + (0, 0, 0), + [ + (0, 0, 0), + (128, 0, 0), + (0, 128, 0), + (128, 128, 0), + (0, 0, 128), + (128, 0, 128), + (0, 128, 128), + (192, 192, 192), + ], + [ + (128, 128, 128), + (255, 0, 0), + (0, 255, 0), + (255, 255, 0), + (0, 0, 255), + (255, 0, 255), + (0, 255, 255), + (255, 255, 255), + ], +) + +MONOKAI = TerminalTheme( + (12, 12, 12), + (217, 217, 217), + [ + (26, 26, 26), + (244, 0, 95), + (152, 224, 36), + (253, 151, 31), + (157, 101, 255), + (244, 0, 95), + (88, 209, 235), + (196, 197, 181), + (98, 94, 76), + ], + [ + (244, 0, 95), + (152, 224, 36), + (224, 213, 97), + (157, 101, 255), + (244, 0, 95), + (88, 209, 235), + (246, 246, 239), + ], +) +DIMMED_MONOKAI = TerminalTheme( + (25, 25, 25), + (185, 188, 186), + [ + (58, 61, 67), + (190, 63, 72), + (135, 154, 59), + (197, 166, 53), + (79, 118, 161), + (133, 92, 141), + (87, 143, 164), + (185, 188, 186), + (136, 137, 135), + ], + [ + (251, 0, 31), + (15, 114, 47), + (196, 112, 51), + (24, 109, 227), + (251, 0, 103), + (46, 112, 109), + (253, 255, 185), + ], +) +NIGHT_OWLISH = TerminalTheme( + (255, 255, 255), + (64, 63, 83), + [ + (1, 22, 39), + (211, 66, 62), + (42, 162, 152), + (218, 170, 1), + (72, 118, 214), + (64, 63, 83), + (8, 145, 106), + (122, 129, 129), + (122, 129, 129), + ], + [ + (247, 110, 110), + (73, 208, 197), + (218, 194, 107), + (92, 167, 228), + (105, 112, 152), + (0, 201, 144), + (152, 159, 177), + ], +) + +SVG_EXPORT_THEME = TerminalTheme( + (41, 41, 41), + (197, 200, 198), + [ + (75, 78, 85), + (204, 85, 90), + (152, 168, 75), + (208, 179, 68), + (96, 138, 177), + (152, 114, 159), + (104, 160, 179), + (197, 200, 198), + (154, 155, 153), + ], + [ + (255, 38, 39), + (0, 130, 61), + (208, 132, 66), + (25, 132, 233), + (255, 44, 122), + (57, 130, 128), + (253, 253, 197), + ], +) diff --git a/venv/lib/python3.10/site-packages/rich/text.py b/venv/lib/python3.10/site-packages/rich/text.py new file mode 100644 index 0000000000000000000000000000000000000000..b57d77c276c5e52f11f736ef6ab3c58b31e23045 --- /dev/null +++ b/venv/lib/python3.10/site-packages/rich/text.py @@ -0,0 +1,1361 @@ +import re +from functools import partial, reduce +from math import gcd +from operator import itemgetter +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Dict, + Iterable, + List, + NamedTuple, + Optional, + Pattern, + Tuple, + Union, +) + +from ._loop import loop_last +from ._pick import pick_bool +from ._wrap import divide_line +from .align import AlignMethod +from .cells import cell_len, set_cell_size +from .containers import Lines +from .control import strip_control_codes +from .emoji import EmojiVariant +from .jupyter import JupyterMixin +from .measure import Measurement +from .segment import Segment +from .style import Style, StyleType + +if TYPE_CHECKING: # pragma: no cover + from .console import Console, ConsoleOptions, JustifyMethod, OverflowMethod + +DEFAULT_JUSTIFY: "JustifyMethod" = "default" +DEFAULT_OVERFLOW: "OverflowMethod" = "fold" + + +_re_whitespace = re.compile(r"\s+$") + +TextType = Union[str, "Text"] +"""A plain string or a :class:`Text` instance.""" + +GetStyleCallable = Callable[[str], Optional[StyleType]] + + +class Span(NamedTuple): + """A marked up region in some text.""" + + start: int + """Span start index.""" + end: int + """Span end index.""" + style: Union[str, Style] + """Style associated with the span.""" + + def __repr__(self) -> str: + return f"Span({self.start}, {self.end}, {self.style!r})" + + def __bool__(self) -> bool: + return self.end > self.start + + def split(self, offset: int) -> Tuple["Span", Optional["Span"]]: + """Split a span in to 2 from a given offset.""" + + if offset < self.start: + return self, None + if offset >= self.end: + return self, None + + start, end, style = self + span1 = Span(start, min(end, offset), style) + span2 = Span(span1.end, end, style) + return span1, span2 + + def move(self, offset: int) -> "Span": + """Move start and end by a given offset. + + Args: + offset (int): Number of characters to add to start and end. + + Returns: + TextSpan: A new TextSpan with adjusted position. + """ + start, end, style = self + return Span(start + offset, end + offset, style) + + def right_crop(self, offset: int) -> "Span": + """Crop the span at the given offset. + + Args: + offset (int): A value between start and end. + + Returns: + Span: A new (possibly smaller) span. + """ + start, end, style = self + if offset >= end: + return self + return Span(start, min(offset, end), style) + + def extend(self, cells: int) -> "Span": + """Extend the span by the given number of cells. + + Args: + cells (int): Additional space to add to end of span. + + Returns: + Span: A span. + """ + if cells: + start, end, style = self + return Span(start, end + cells, style) + else: + return self + + +class Text(JupyterMixin): + """Text with color / style. + + Args: + text (str, optional): Default unstyled text. Defaults to "". + style (Union[str, Style], optional): Base style for text. Defaults to "". + justify (str, optional): Justify method: "left", "center", "full", "right". Defaults to None. + overflow (str, optional): Overflow method: "crop", "fold", "ellipsis". Defaults to None. + no_wrap (bool, optional): Disable text wrapping, or None for default. Defaults to None. + end (str, optional): Character to end text with. Defaults to "\\\\n". + tab_size (int): Number of spaces per tab, or ``None`` to use ``console.tab_size``. Defaults to None. + spans (List[Span], optional). A list of predefined style spans. Defaults to None. + """ + + __slots__ = [ + "_text", + "style", + "justify", + "overflow", + "no_wrap", + "end", + "tab_size", + "_spans", + "_length", + ] + + def __init__( + self, + text: str = "", + style: Union[str, Style] = "", + *, + justify: Optional["JustifyMethod"] = None, + overflow: Optional["OverflowMethod"] = None, + no_wrap: Optional[bool] = None, + end: str = "\n", + tab_size: Optional[int] = None, + spans: Optional[List[Span]] = None, + ) -> None: + sanitized_text = strip_control_codes(text) + self._text = [sanitized_text] + self.style = style + self.justify: Optional["JustifyMethod"] = justify + self.overflow: Optional["OverflowMethod"] = overflow + self.no_wrap = no_wrap + self.end = end + self.tab_size = tab_size + self._spans: List[Span] = spans or [] + self._length: int = len(sanitized_text) + + def __len__(self) -> int: + return self._length + + def __bool__(self) -> bool: + return bool(self._length) + + def __str__(self) -> str: + return self.plain + + def __repr__(self) -> str: + return f"" + + def __add__(self, other: Any) -> "Text": + if isinstance(other, (str, Text)): + result = self.copy() + result.append(other) + return result + return NotImplemented + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Text): + return NotImplemented + return self.plain == other.plain and self._spans == other._spans + + def __contains__(self, other: object) -> bool: + if isinstance(other, str): + return other in self.plain + elif isinstance(other, Text): + return other.plain in self.plain + return False + + def __getitem__(self, slice: Union[int, slice]) -> "Text": + def get_text_at(offset: int) -> "Text": + _Span = Span + text = Text( + self.plain[offset], + spans=[ + _Span(0, 1, style) + for start, end, style in self._spans + if end > offset >= start + ], + end="", + ) + return text + + if isinstance(slice, int): + return get_text_at(slice) + else: + start, stop, step = slice.indices(len(self.plain)) + if step == 1: + lines = self.divide([start, stop]) + return lines[1] + else: + # This would be a bit of work to implement efficiently + # For now, its not required + raise TypeError("slices with step!=1 are not supported") + + @property + def cell_len(self) -> int: + """Get the number of cells required to render this text.""" + return cell_len(self.plain) + + @property + def markup(self) -> str: + """Get console markup to render this Text. + + Returns: + str: A string potentially creating markup tags. + """ + from .markup import escape + + output: List[str] = [] + + plain = self.plain + markup_spans = [ + (0, False, self.style), + *((span.start, False, span.style) for span in self._spans), + *((span.end, True, span.style) for span in self._spans), + (len(plain), True, self.style), + ] + markup_spans.sort(key=itemgetter(0, 1)) + position = 0 + append = output.append + for offset, closing, style in markup_spans: + if offset > position: + append(escape(plain[position:offset])) + position = offset + if style: + append(f"[/{style}]" if closing else f"[{style}]") + markup = "".join(output) + return markup + + @classmethod + def from_markup( + cls, + text: str, + *, + style: Union[str, Style] = "", + emoji: bool = True, + emoji_variant: Optional[EmojiVariant] = None, + justify: Optional["JustifyMethod"] = None, + overflow: Optional["OverflowMethod"] = None, + end: str = "\n", + ) -> "Text": + """Create Text instance from markup. + + Args: + text (str): A string containing console markup. + style (Union[str, Style], optional): Base style for text. Defaults to "". + emoji (bool, optional): Also render emoji code. Defaults to True. + emoji_variant (str, optional): Optional emoji variant, either "text" or "emoji". Defaults to None. + justify (str, optional): Justify method: "left", "center", "full", "right". Defaults to None. + overflow (str, optional): Overflow method: "crop", "fold", "ellipsis". Defaults to None. + end (str, optional): Character to end text with. Defaults to "\\\\n". + + Returns: + Text: A Text instance with markup rendered. + """ + from .markup import render + + rendered_text = render(text, style, emoji=emoji, emoji_variant=emoji_variant) + rendered_text.justify = justify + rendered_text.overflow = overflow + rendered_text.end = end + return rendered_text + + @classmethod + def from_ansi( + cls, + text: str, + *, + style: Union[str, Style] = "", + justify: Optional["JustifyMethod"] = None, + overflow: Optional["OverflowMethod"] = None, + no_wrap: Optional[bool] = None, + end: str = "\n", + tab_size: Optional[int] = 8, + ) -> "Text": + """Create a Text object from a string containing ANSI escape codes. + + Args: + text (str): A string containing escape codes. + style (Union[str, Style], optional): Base style for text. Defaults to "". + justify (str, optional): Justify method: "left", "center", "full", "right". Defaults to None. + overflow (str, optional): Overflow method: "crop", "fold", "ellipsis". Defaults to None. + no_wrap (bool, optional): Disable text wrapping, or None for default. Defaults to None. + end (str, optional): Character to end text with. Defaults to "\\\\n". + tab_size (int): Number of spaces per tab, or ``None`` to use ``console.tab_size``. Defaults to None. + """ + from .ansi import AnsiDecoder + + joiner = Text( + "\n", + justify=justify, + overflow=overflow, + no_wrap=no_wrap, + end=end, + tab_size=tab_size, + style=style, + ) + decoder = AnsiDecoder() + result = joiner.join(line for line in decoder.decode(text)) + return result + + @classmethod + def styled( + cls, + text: str, + style: StyleType = "", + *, + justify: Optional["JustifyMethod"] = None, + overflow: Optional["OverflowMethod"] = None, + ) -> "Text": + """Construct a Text instance with a pre-applied styled. A style applied in this way won't be used + to pad the text when it is justified. + + Args: + text (str): A string containing console markup. + style (Union[str, Style]): Style to apply to the text. Defaults to "". + justify (str, optional): Justify method: "left", "center", "full", "right". Defaults to None. + overflow (str, optional): Overflow method: "crop", "fold", "ellipsis". Defaults to None. + + Returns: + Text: A text instance with a style applied to the entire string. + """ + styled_text = cls(text, justify=justify, overflow=overflow) + styled_text.stylize(style) + return styled_text + + @classmethod + def assemble( + cls, + *parts: Union[str, "Text", Tuple[str, StyleType]], + style: Union[str, Style] = "", + justify: Optional["JustifyMethod"] = None, + overflow: Optional["OverflowMethod"] = None, + no_wrap: Optional[bool] = None, + end: str = "\n", + tab_size: int = 8, + meta: Optional[Dict[str, Any]] = None, + ) -> "Text": + """Construct a text instance by combining a sequence of strings with optional styles. + The positional arguments should be either strings, or a tuple of string + style. + + Args: + style (Union[str, Style], optional): Base style for text. Defaults to "". + justify (str, optional): Justify method: "left", "center", "full", "right". Defaults to None. + overflow (str, optional): Overflow method: "crop", "fold", "ellipsis". Defaults to None. + no_wrap (bool, optional): Disable text wrapping, or None for default. Defaults to None. + end (str, optional): Character to end text with. Defaults to "\\\\n". + tab_size (int): Number of spaces per tab, or ``None`` to use ``console.tab_size``. Defaults to None. + meta (Dict[str, Any], optional). Meta data to apply to text, or None for no meta data. Default to None + + Returns: + Text: A new text instance. + """ + text = cls( + style=style, + justify=justify, + overflow=overflow, + no_wrap=no_wrap, + end=end, + tab_size=tab_size, + ) + append = text.append + _Text = Text + for part in parts: + if isinstance(part, (_Text, str)): + append(part) + else: + append(*part) + if meta: + text.apply_meta(meta) + return text + + @property + def plain(self) -> str: + """Get the text as a single string.""" + if len(self._text) != 1: + self._text[:] = ["".join(self._text)] + return self._text[0] + + @plain.setter + def plain(self, new_text: str) -> None: + """Set the text to a new value.""" + if new_text != self.plain: + sanitized_text = strip_control_codes(new_text) + self._text[:] = [sanitized_text] + old_length = self._length + self._length = len(sanitized_text) + if old_length > self._length: + self._trim_spans() + + @property + def spans(self) -> List[Span]: + """Get a reference to the internal list of spans.""" + return self._spans + + @spans.setter + def spans(self, spans: List[Span]) -> None: + """Set spans.""" + self._spans = spans[:] + + def blank_copy(self, plain: str = "") -> "Text": + """Return a new Text instance with copied metadata (but not the string or spans).""" + copy_self = Text( + plain, + style=self.style, + justify=self.justify, + overflow=self.overflow, + no_wrap=self.no_wrap, + end=self.end, + tab_size=self.tab_size, + ) + return copy_self + + def copy(self) -> "Text": + """Return a copy of this instance.""" + copy_self = Text( + self.plain, + style=self.style, + justify=self.justify, + overflow=self.overflow, + no_wrap=self.no_wrap, + end=self.end, + tab_size=self.tab_size, + ) + copy_self._spans[:] = self._spans + return copy_self + + def stylize( + self, + style: Union[str, Style], + start: int = 0, + end: Optional[int] = None, + ) -> None: + """Apply a style to the text, or a portion of the text. + + Args: + style (Union[str, Style]): Style instance or style definition to apply. + start (int): Start offset (negative indexing is supported). Defaults to 0. + end (Optional[int], optional): End offset (negative indexing is supported), or None for end of text. Defaults to None. + """ + if style: + length = len(self) + if start < 0: + start = length + start + if end is None: + end = length + if end < 0: + end = length + end + if start >= length or end <= start: + # Span not in text or not valid + return + self._spans.append(Span(start, min(length, end), style)) + + def stylize_before( + self, + style: Union[str, Style], + start: int = 0, + end: Optional[int] = None, + ) -> None: + """Apply a style to the text, or a portion of the text. Styles will be applied before other styles already present. + + Args: + style (Union[str, Style]): Style instance or style definition to apply. + start (int): Start offset (negative indexing is supported). Defaults to 0. + end (Optional[int], optional): End offset (negative indexing is supported), or None for end of text. Defaults to None. + """ + if style: + length = len(self) + if start < 0: + start = length + start + if end is None: + end = length + if end < 0: + end = length + end + if start >= length or end <= start: + # Span not in text or not valid + return + self._spans.insert(0, Span(start, min(length, end), style)) + + def apply_meta( + self, meta: Dict[str, Any], start: int = 0, end: Optional[int] = None + ) -> None: + """Apply metadata to the text, or a portion of the text. + + Args: + meta (Dict[str, Any]): A dict of meta information. + start (int): Start offset (negative indexing is supported). Defaults to 0. + end (Optional[int], optional): End offset (negative indexing is supported), or None for end of text. Defaults to None. + + """ + style = Style.from_meta(meta) + self.stylize(style, start=start, end=end) + + def on(self, meta: Optional[Dict[str, Any]] = None, **handlers: Any) -> "Text": + """Apply event handlers (used by Textual project). + + Example: + >>> from rich.text import Text + >>> text = Text("hello world") + >>> text.on(click="view.toggle('world')") + + Args: + meta (Dict[str, Any]): Mapping of meta information. + **handlers: Keyword args are prefixed with "@" to defined handlers. + + Returns: + Text: Self is returned to method may be chained. + """ + meta = {} if meta is None else meta + meta.update({f"@{key}": value for key, value in handlers.items()}) + self.stylize(Style.from_meta(meta)) + return self + + def remove_suffix(self, suffix: str) -> None: + """Remove a suffix if it exists. + + Args: + suffix (str): Suffix to remove. + """ + if self.plain.endswith(suffix): + self.right_crop(len(suffix)) + + def get_style_at_offset(self, console: "Console", offset: int) -> Style: + """Get the style of a character at give offset. + + Args: + console (~Console): Console where text will be rendered. + offset (int): Offset in to text (negative indexing supported) + + Returns: + Style: A Style instance. + """ + # TODO: This is a little inefficient, it is only used by full justify + if offset < 0: + offset = len(self) + offset + get_style = console.get_style + style = get_style(self.style).copy() + for start, end, span_style in self._spans: + if end > offset >= start: + style += get_style(span_style, default="") + return style + + def extend_style(self, spaces: int) -> None: + """Extend the Text given number of spaces where the spaces have the same style as the last character. + + Args: + spaces (int): Number of spaces to add to the Text. + """ + if spaces <= 0: + return + spans = self.spans + new_spaces = " " * spaces + if spans: + end_offset = len(self) + self._spans[:] = [ + span.extend(spaces) if span.end >= end_offset else span + for span in spans + ] + self._text.append(new_spaces) + self._length += spaces + else: + self.plain += new_spaces + + def highlight_regex( + self, + re_highlight: Union[Pattern[str], str], + style: Optional[Union[GetStyleCallable, StyleType]] = None, + *, + style_prefix: str = "", + ) -> int: + """Highlight text with a regular expression, where group names are + translated to styles. + + Args: + re_highlight (Union[re.Pattern, str]): A regular expression object or string. + style (Union[GetStyleCallable, StyleType]): Optional style to apply to whole match, or a callable + which accepts the matched text and returns a style. Defaults to None. + style_prefix (str, optional): Optional prefix to add to style group names. + + Returns: + int: Number of regex matches + """ + count = 0 + append_span = self._spans.append + _Span = Span + plain = self.plain + if isinstance(re_highlight, str): + re_highlight = re.compile(re_highlight) + for match in re_highlight.finditer(plain): + get_span = match.span + if style: + start, end = get_span() + match_style = style(plain[start:end]) if callable(style) else style + if match_style is not None and end > start: + append_span(_Span(start, end, match_style)) + + count += 1 + for name in match.groupdict().keys(): + start, end = get_span(name) + if start != -1 and end > start: + append_span(_Span(start, end, f"{style_prefix}{name}")) + return count + + def highlight_words( + self, + words: Iterable[str], + style: Union[str, Style], + *, + case_sensitive: bool = True, + ) -> int: + """Highlight words with a style. + + Args: + words (Iterable[str]): Words to highlight. + style (Union[str, Style]): Style to apply. + case_sensitive (bool, optional): Enable case sensitive matching. Defaults to True. + + Returns: + int: Number of words highlighted. + """ + re_words = "|".join(re.escape(word) for word in words) + add_span = self._spans.append + count = 0 + _Span = Span + for match in re.finditer( + re_words, self.plain, flags=0 if case_sensitive else re.IGNORECASE + ): + start, end = match.span(0) + add_span(_Span(start, end, style)) + count += 1 + return count + + def rstrip(self) -> None: + """Strip whitespace from end of text.""" + self.plain = self.plain.rstrip() + + def rstrip_end(self, size: int) -> None: + """Remove whitespace beyond a certain width at the end of the text. + + Args: + size (int): The desired size of the text. + """ + text_length = len(self) + if text_length > size: + excess = text_length - size + whitespace_match = _re_whitespace.search(self.plain) + if whitespace_match is not None: + whitespace_count = len(whitespace_match.group(0)) + self.right_crop(min(whitespace_count, excess)) + + def set_length(self, new_length: int) -> None: + """Set new length of the text, clipping or padding is required.""" + length = len(self) + if length != new_length: + if length < new_length: + self.pad_right(new_length - length) + else: + self.right_crop(length - new_length) + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> Iterable[Segment]: + tab_size: int = console.tab_size if self.tab_size is None else self.tab_size + justify = self.justify or options.justify or DEFAULT_JUSTIFY + + overflow = self.overflow or options.overflow or DEFAULT_OVERFLOW + + lines = self.wrap( + console, + options.max_width, + justify=justify, + overflow=overflow, + tab_size=tab_size or 8, + no_wrap=pick_bool(self.no_wrap, options.no_wrap, False), + ) + all_lines = Text("\n").join(lines) + yield from all_lines.render(console, end=self.end) + + def __rich_measure__( + self, console: "Console", options: "ConsoleOptions" + ) -> Measurement: + text = self.plain + lines = text.splitlines() + max_text_width = max(cell_len(line) for line in lines) if lines else 0 + words = text.split() + min_text_width = ( + max(cell_len(word) for word in words) if words else max_text_width + ) + return Measurement(min_text_width, max_text_width) + + def render(self, console: "Console", end: str = "") -> Iterable["Segment"]: + """Render the text as Segments. + + Args: + console (Console): Console instance. + end (Optional[str], optional): Optional end character. + + Returns: + Iterable[Segment]: Result of render that may be written to the console. + """ + _Segment = Segment + text = self.plain + if not self._spans: + yield Segment(text) + if end: + yield _Segment(end) + return + get_style = partial(console.get_style, default=Style.null()) + + enumerated_spans = list(enumerate(self._spans, 1)) + style_map = {index: get_style(span.style) for index, span in enumerated_spans} + style_map[0] = get_style(self.style) + + spans = [ + (0, False, 0), + *((span.start, False, index) for index, span in enumerated_spans), + *((span.end, True, index) for index, span in enumerated_spans), + (len(text), True, 0), + ] + spans.sort(key=itemgetter(0, 1)) + + stack: List[int] = [] + stack_append = stack.append + stack_pop = stack.remove + + style_cache: Dict[Tuple[Style, ...], Style] = {} + style_cache_get = style_cache.get + combine = Style.combine + + def get_current_style() -> Style: + """Construct current style from stack.""" + styles = tuple(style_map[_style_id] for _style_id in sorted(stack)) + cached_style = style_cache_get(styles) + if cached_style is not None: + return cached_style + current_style = combine(styles) + style_cache[styles] = current_style + return current_style + + for (offset, leaving, style_id), (next_offset, _, _) in zip(spans, spans[1:]): + if leaving: + stack_pop(style_id) + else: + stack_append(style_id) + if next_offset > offset: + yield _Segment(text[offset:next_offset], get_current_style()) + if end: + yield _Segment(end) + + def join(self, lines: Iterable["Text"]) -> "Text": + """Join text together with this instance as the separator. + + Args: + lines (Iterable[Text]): An iterable of Text instances to join. + + Returns: + Text: A new text instance containing join text. + """ + + new_text = self.blank_copy() + + def iter_text() -> Iterable["Text"]: + if self.plain: + for last, line in loop_last(lines): + yield line + if not last: + yield self + else: + yield from lines + + extend_text = new_text._text.extend + append_span = new_text._spans.append + extend_spans = new_text._spans.extend + offset = 0 + _Span = Span + + for text in iter_text(): + extend_text(text._text) + if text.style: + append_span(_Span(offset, offset + len(text), text.style)) + extend_spans( + _Span(offset + start, offset + end, style) + for start, end, style in text._spans + ) + offset += len(text) + new_text._length = offset + return new_text + + def expand_tabs(self, tab_size: Optional[int] = None) -> None: + """Converts tabs to spaces. + + Args: + tab_size (int, optional): Size of tabs. Defaults to 8. + + """ + if "\t" not in self.plain: + return + if tab_size is None: + tab_size = self.tab_size + if tab_size is None: + tab_size = 8 + + new_text: List[Text] = [] + append = new_text.append + + for line in self.split("\n", include_separator=True): + if "\t" not in line.plain: + append(line) + else: + cell_position = 0 + parts = line.split("\t", include_separator=True) + for part in parts: + if part.plain.endswith("\t"): + part._text[-1] = part._text[-1][:-1] + " " + cell_position += part.cell_len + tab_remainder = cell_position % tab_size + if tab_remainder: + spaces = tab_size - tab_remainder + part.extend_style(spaces) + cell_position += spaces + else: + cell_position += part.cell_len + append(part) + + result = Text("").join(new_text) + + self._text = [result.plain] + self._length = len(self.plain) + self._spans[:] = result._spans + + def truncate( + self, + max_width: int, + *, + overflow: Optional["OverflowMethod"] = None, + pad: bool = False, + ) -> None: + """Truncate text if it is longer that a given width. + + Args: + max_width (int): Maximum number of characters in text. + overflow (str, optional): Overflow method: "crop", "fold", or "ellipsis". Defaults to None, to use self.overflow. + pad (bool, optional): Pad with spaces if the length is less than max_width. Defaults to False. + """ + _overflow = overflow or self.overflow or DEFAULT_OVERFLOW + if _overflow != "ignore": + length = cell_len(self.plain) + if length > max_width: + if _overflow == "ellipsis": + self.plain = set_cell_size(self.plain, max_width - 1) + "…" + else: + self.plain = set_cell_size(self.plain, max_width) + if pad and length < max_width: + spaces = max_width - length + self._text = [f"{self.plain}{' ' * spaces}"] + self._length = len(self.plain) + + def _trim_spans(self) -> None: + """Remove or modify any spans that are over the end of the text.""" + max_offset = len(self.plain) + _Span = Span + self._spans[:] = [ + ( + span + if span.end < max_offset + else _Span(span.start, min(max_offset, span.end), span.style) + ) + for span in self._spans + if span.start < max_offset + ] + + def pad(self, count: int, character: str = " ") -> None: + """Pad left and right with a given number of characters. + + Args: + count (int): Width of padding. + character (str): The character to pad with. Must be a string of length 1. + """ + assert len(character) == 1, "Character must be a string of length 1" + if count: + pad_characters = character * count + self.plain = f"{pad_characters}{self.plain}{pad_characters}" + _Span = Span + self._spans[:] = [ + _Span(start + count, end + count, style) + for start, end, style in self._spans + ] + + def pad_left(self, count: int, character: str = " ") -> None: + """Pad the left with a given character. + + Args: + count (int): Number of characters to pad. + character (str, optional): Character to pad with. Defaults to " ". + """ + assert len(character) == 1, "Character must be a string of length 1" + if count: + self.plain = f"{character * count}{self.plain}" + _Span = Span + self._spans[:] = [ + _Span(start + count, end + count, style) + for start, end, style in self._spans + ] + + def pad_right(self, count: int, character: str = " ") -> None: + """Pad the right with a given character. + + Args: + count (int): Number of characters to pad. + character (str, optional): Character to pad with. Defaults to " ". + """ + assert len(character) == 1, "Character must be a string of length 1" + if count: + self.plain = f"{self.plain}{character * count}" + + def align(self, align: AlignMethod, width: int, character: str = " ") -> None: + """Align text to a given width. + + Args: + align (AlignMethod): One of "left", "center", or "right". + width (int): Desired width. + character (str, optional): Character to pad with. Defaults to " ". + """ + self.truncate(width) + excess_space = width - cell_len(self.plain) + if excess_space: + if align == "left": + self.pad_right(excess_space, character) + elif align == "center": + left = excess_space // 2 + self.pad_left(left, character) + self.pad_right(excess_space - left, character) + else: + self.pad_left(excess_space, character) + + def append( + self, text: Union["Text", str], style: Optional[Union[str, "Style"]] = None + ) -> "Text": + """Add text with an optional style. + + Args: + text (Union[Text, str]): A str or Text to append. + style (str, optional): A style name. Defaults to None. + + Returns: + Text: Returns self for chaining. + """ + + if not isinstance(text, (str, Text)): + raise TypeError("Only str or Text can be appended to Text") + + if len(text): + if isinstance(text, str): + sanitized_text = strip_control_codes(text) + self._text.append(sanitized_text) + offset = len(self) + text_length = len(sanitized_text) + if style: + self._spans.append(Span(offset, offset + text_length, style)) + self._length += text_length + elif isinstance(text, Text): + _Span = Span + if style is not None: + raise ValueError( + "style must not be set when appending Text instance" + ) + text_length = self._length + if text.style: + self._spans.append( + _Span(text_length, text_length + len(text), text.style) + ) + self._text.append(text.plain) + self._spans.extend( + _Span(start + text_length, end + text_length, style) + for start, end, style in text._spans.copy() + ) + self._length += len(text) + return self + + def append_text(self, text: "Text") -> "Text": + """Append another Text instance. This method is more performant that Text.append, but + only works for Text. + + Args: + text (Text): The Text instance to append to this instance. + + Returns: + Text: Returns self for chaining. + """ + _Span = Span + text_length = self._length + if text.style: + self._spans.append(_Span(text_length, text_length + len(text), text.style)) + self._text.append(text.plain) + self._spans.extend( + _Span(start + text_length, end + text_length, style) + for start, end, style in text._spans.copy() + ) + self._length += len(text) + return self + + def append_tokens( + self, tokens: Iterable[Tuple[str, Optional[StyleType]]] + ) -> "Text": + """Append iterable of str and style. Style may be a Style instance or a str style definition. + + Args: + tokens (Iterable[Tuple[str, Optional[StyleType]]]): An iterable of tuples containing str content and style. + + Returns: + Text: Returns self for chaining. + """ + append_text = self._text.append + append_span = self._spans.append + _Span = Span + offset = len(self) + for content, style in tokens: + content = strip_control_codes(content) + append_text(content) + if style: + append_span(_Span(offset, offset + len(content), style)) + offset += len(content) + self._length = offset + return self + + def copy_styles(self, text: "Text") -> None: + """Copy styles from another Text instance. + + Args: + text (Text): A Text instance to copy styles from, must be the same length. + """ + self._spans.extend(text._spans) + + def split( + self, + separator: str = "\n", + *, + include_separator: bool = False, + allow_blank: bool = False, + ) -> Lines: + """Split rich text in to lines, preserving styles. + + Args: + separator (str, optional): String to split on. Defaults to "\\\\n". + include_separator (bool, optional): Include the separator in the lines. Defaults to False. + allow_blank (bool, optional): Return a blank line if the text ends with a separator. Defaults to False. + + Returns: + List[RichText]: A list of rich text, one per line of the original. + """ + assert separator, "separator must not be empty" + + text = self.plain + if separator not in text: + return Lines([self.copy()]) + + if include_separator: + lines = self.divide( + match.end() for match in re.finditer(re.escape(separator), text) + ) + else: + + def flatten_spans() -> Iterable[int]: + for match in re.finditer(re.escape(separator), text): + start, end = match.span() + yield start + yield end + + lines = Lines( + line for line in self.divide(flatten_spans()) if line.plain != separator + ) + + if not allow_blank and text.endswith(separator): + lines.pop() + + return lines + + def divide(self, offsets: Iterable[int]) -> Lines: + """Divide text in to a number of lines at given offsets. + + Args: + offsets (Iterable[int]): Offsets used to divide text. + + Returns: + Lines: New RichText instances between offsets. + """ + _offsets = list(offsets) + + if not _offsets: + return Lines([self.copy()]) + + text = self.plain + text_length = len(text) + divide_offsets = [0, *_offsets, text_length] + line_ranges = list(zip(divide_offsets, divide_offsets[1:])) + + style = self.style + justify = self.justify + overflow = self.overflow + _Text = Text + new_lines = Lines( + _Text( + text[start:end], + style=style, + justify=justify, + overflow=overflow, + ) + for start, end in line_ranges + ) + if not self._spans: + return new_lines + + _line_appends = [line._spans.append for line in new_lines._lines] + line_count = len(line_ranges) + _Span = Span + + for span_start, span_end, style in self._spans: + lower_bound = 0 + upper_bound = line_count + start_line_no = (lower_bound + upper_bound) // 2 + + while True: + line_start, line_end = line_ranges[start_line_no] + if span_start < line_start: + upper_bound = start_line_no - 1 + elif span_start > line_end: + lower_bound = start_line_no + 1 + else: + break + start_line_no = (lower_bound + upper_bound) // 2 + + if span_end < line_end: + end_line_no = start_line_no + else: + end_line_no = lower_bound = start_line_no + upper_bound = line_count + + while True: + line_start, line_end = line_ranges[end_line_no] + if span_end < line_start: + upper_bound = end_line_no - 1 + elif span_end > line_end: + lower_bound = end_line_no + 1 + else: + break + end_line_no = (lower_bound + upper_bound) // 2 + + for line_no in range(start_line_no, end_line_no + 1): + line_start, line_end = line_ranges[line_no] + new_start = max(0, span_start - line_start) + new_end = min(span_end - line_start, line_end - line_start) + if new_end > new_start: + _line_appends[line_no](_Span(new_start, new_end, style)) + + return new_lines + + def right_crop(self, amount: int = 1) -> None: + """Remove a number of characters from the end of the text.""" + max_offset = len(self.plain) - amount + _Span = Span + self._spans[:] = [ + ( + span + if span.end < max_offset + else _Span(span.start, min(max_offset, span.end), span.style) + ) + for span in self._spans + if span.start < max_offset + ] + self._text = [self.plain[:-amount]] + self._length -= amount + + def wrap( + self, + console: "Console", + width: int, + *, + justify: Optional["JustifyMethod"] = None, + overflow: Optional["OverflowMethod"] = None, + tab_size: int = 8, + no_wrap: Optional[bool] = None, + ) -> Lines: + """Word wrap the text. + + Args: + console (Console): Console instance. + width (int): Number of cells available per line. + justify (str, optional): Justify method: "default", "left", "center", "full", "right". Defaults to "default". + overflow (str, optional): Overflow method: "crop", "fold", or "ellipsis". Defaults to None. + tab_size (int, optional): Default tab size. Defaults to 8. + no_wrap (bool, optional): Disable wrapping, Defaults to False. + + Returns: + Lines: Number of lines. + """ + wrap_justify = justify or self.justify or DEFAULT_JUSTIFY + wrap_overflow = overflow or self.overflow or DEFAULT_OVERFLOW + + no_wrap = pick_bool(no_wrap, self.no_wrap, False) or overflow == "ignore" + + lines = Lines() + for line in self.split(allow_blank=True): + if "\t" in line: + line.expand_tabs(tab_size) + if no_wrap: + new_lines = Lines([line]) + else: + offsets = divide_line(str(line), width, fold=wrap_overflow == "fold") + new_lines = line.divide(offsets) + for line in new_lines: + line.rstrip_end(width) + if wrap_justify: + new_lines.justify( + console, width, justify=wrap_justify, overflow=wrap_overflow + ) + for line in new_lines: + line.truncate(width, overflow=wrap_overflow) + lines.extend(new_lines) + return lines + + def fit(self, width: int) -> Lines: + """Fit the text in to given width by chopping in to lines. + + Args: + width (int): Maximum characters in a line. + + Returns: + Lines: Lines container. + """ + lines: Lines = Lines() + append = lines.append + for line in self.split(): + line.set_length(width) + append(line) + return lines + + def detect_indentation(self) -> int: + """Auto-detect indentation of code. + + Returns: + int: Number of spaces used to indent code. + """ + + _indentations = { + len(match.group(1)) + for match in re.finditer(r"^( *)(.*)$", self.plain, flags=re.MULTILINE) + } + + try: + indentation = ( + reduce(gcd, [indent for indent in _indentations if not indent % 2]) or 1 + ) + except TypeError: + indentation = 1 + + return indentation + + def with_indent_guides( + self, + indent_size: Optional[int] = None, + *, + character: str = "│", + style: StyleType = "dim green", + ) -> "Text": + """Adds indent guide lines to text. + + Args: + indent_size (Optional[int]): Size of indentation, or None to auto detect. Defaults to None. + character (str, optional): Character to use for indentation. Defaults to "│". + style (Union[Style, str], optional): Style of indent guides. + + Returns: + Text: New text with indentation guides. + """ + + _indent_size = self.detect_indentation() if indent_size is None else indent_size + + text = self.copy() + text.expand_tabs() + indent_line = f"{character}{' ' * (_indent_size - 1)}" + + re_indent = re.compile(r"^( *)(.*)$") + new_lines: List[Text] = [] + add_line = new_lines.append + blank_lines = 0 + for line in text.split(allow_blank=True): + match = re_indent.match(line.plain) + if not match or not match.group(2): + blank_lines += 1 + continue + indent = match.group(1) + full_indents, remaining_space = divmod(len(indent), _indent_size) + new_indent = f"{indent_line * full_indents}{' ' * remaining_space}" + line.plain = new_indent + line.plain[len(new_indent) :] + line.stylize(style, 0, len(new_indent)) + if blank_lines: + new_lines.extend([Text(new_indent, style=style)] * blank_lines) + blank_lines = 0 + add_line(line) + if blank_lines: + new_lines.extend([Text("", style=style)] * blank_lines) + + new_text = text.blank_copy("\n").join(new_lines) + return new_text + + +if __name__ == "__main__": # pragma: no cover + from rich.console import Console + + text = Text( + """\nLorem ipsum dolor sit amet, consectetur adipiscing 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.\n""" + ) + text.highlight_words(["Lorem"], "bold") + text.highlight_words(["ipsum"], "italic") + + console = Console() + + console.rule("justify='left'") + console.print(text, style="red") + console.print() + + console.rule("justify='center'") + console.print(text, style="green", justify="center") + console.print() + + console.rule("justify='right'") + console.print(text, style="blue", justify="right") + console.print() + + console.rule("justify='full'") + console.print(text, style="magenta", justify="full") + console.print() diff --git a/venv/lib/python3.10/site-packages/rich/theme.py b/venv/lib/python3.10/site-packages/rich/theme.py new file mode 100644 index 0000000000000000000000000000000000000000..227f1d8635f8ba915153b21a6b925643a11d286e --- /dev/null +++ b/venv/lib/python3.10/site-packages/rich/theme.py @@ -0,0 +1,115 @@ +import configparser +from typing import IO, Dict, List, Mapping, Optional + +from .default_styles import DEFAULT_STYLES +from .style import Style, StyleType + + +class Theme: + """A container for style information, used by :class:`~rich.console.Console`. + + Args: + styles (Dict[str, Style], optional): A mapping of style names on to styles. Defaults to None for a theme with no styles. + inherit (bool, optional): Inherit default styles. Defaults to True. + """ + + styles: Dict[str, Style] + + def __init__( + self, styles: Optional[Mapping[str, StyleType]] = None, inherit: bool = True + ): + self.styles = DEFAULT_STYLES.copy() if inherit else {} + if styles is not None: + self.styles.update( + { + name: style if isinstance(style, Style) else Style.parse(style) + for name, style in styles.items() + } + ) + + @property + def config(self) -> str: + """Get contents of a config file for this theme.""" + config = "[styles]\n" + "\n".join( + f"{name} = {style}" for name, style in sorted(self.styles.items()) + ) + return config + + @classmethod + def from_file( + cls, config_file: IO[str], source: Optional[str] = None, inherit: bool = True + ) -> "Theme": + """Load a theme from a text mode file. + + Args: + config_file (IO[str]): An open conf file. + source (str, optional): The filename of the open file. Defaults to None. + inherit (bool, optional): Inherit default styles. Defaults to True. + + Returns: + Theme: A New theme instance. + """ + config = configparser.ConfigParser() + config.read_file(config_file, source=source) + styles = {name: Style.parse(value) for name, value in config.items("styles")} + theme = Theme(styles, inherit=inherit) + return theme + + @classmethod + def read( + cls, path: str, inherit: bool = True, encoding: Optional[str] = None + ) -> "Theme": + """Read a theme from a path. + + Args: + path (str): Path to a config file readable by Python configparser module. + inherit (bool, optional): Inherit default styles. Defaults to True. + encoding (str, optional): Encoding of the config file. Defaults to None. + + Returns: + Theme: A new theme instance. + """ + with open(path, encoding=encoding) as config_file: + return cls.from_file(config_file, source=path, inherit=inherit) + + +class ThemeStackError(Exception): + """Base exception for errors related to the theme stack.""" + + +class ThemeStack: + """A stack of themes. + + Args: + theme (Theme): A theme instance + """ + + def __init__(self, theme: Theme) -> None: + self._entries: List[Dict[str, Style]] = [theme.styles] + self.get = self._entries[-1].get + + def push_theme(self, theme: Theme, inherit: bool = True) -> None: + """Push a theme on the top of the stack. + + Args: + theme (Theme): A Theme instance. + inherit (boolean, optional): Inherit styles from current top of stack. + """ + styles: Dict[str, Style] + styles = ( + {**self._entries[-1], **theme.styles} if inherit else theme.styles.copy() + ) + self._entries.append(styles) + self.get = self._entries[-1].get + + def pop_theme(self) -> None: + """Pop (and discard) the top-most theme.""" + if len(self._entries) == 1: + raise ThemeStackError("Unable to pop base theme") + self._entries.pop() + self.get = self._entries[-1].get + + +if __name__ == "__main__": # pragma: no cover + theme = Theme() + print(theme.config) diff --git a/venv/lib/python3.10/site-packages/rich/themes.py b/venv/lib/python3.10/site-packages/rich/themes.py new file mode 100644 index 0000000000000000000000000000000000000000..bf6db104a2c4fd4f3dc699e85f2b262c3d31e9a0 --- /dev/null +++ b/venv/lib/python3.10/site-packages/rich/themes.py @@ -0,0 +1,5 @@ +from .default_styles import DEFAULT_STYLES +from .theme import Theme + + +DEFAULT = Theme(DEFAULT_STYLES) diff --git a/venv/lib/python3.10/site-packages/rich/traceback.py b/venv/lib/python3.10/site-packages/rich/traceback.py new file mode 100644 index 0000000000000000000000000000000000000000..b2cc6304048b6149d55c05550c597774241ddd0d --- /dev/null +++ b/venv/lib/python3.10/site-packages/rich/traceback.py @@ -0,0 +1,884 @@ +import inspect +import linecache +import os +import sys +from dataclasses import dataclass, field +from itertools import islice +from traceback import walk_tb +from types import ModuleType, TracebackType +from typing import ( + Any, + Callable, + Dict, + Iterable, + List, + Optional, + Sequence, + Tuple, + Type, + Union, +) + +from pygments.lexers import guess_lexer_for_filename +from pygments.token import Comment, Keyword, Name, Number, Operator, String +from pygments.token import Text as TextToken +from pygments.token import Token +from pygments.util import ClassNotFound + +from . import pretty +from ._loop import loop_first_last, loop_last +from .columns import Columns +from .console import ( + Console, + ConsoleOptions, + ConsoleRenderable, + Group, + RenderResult, + group, +) +from .constrain import Constrain +from .highlighter import RegexHighlighter, ReprHighlighter +from .panel import Panel +from .scope import render_scope +from .style import Style +from .syntax import Syntax, SyntaxPosition +from .text import Text +from .theme import Theme + +WINDOWS = sys.platform == "win32" + +LOCALS_MAX_LENGTH = 10 +LOCALS_MAX_STRING = 80 + + +def _iter_syntax_lines( + start: SyntaxPosition, end: SyntaxPosition +) -> Iterable[Tuple[int, int, int]]: + """Yield start and end positions per line. + + Args: + start: Start position. + end: End position. + + Returns: + Iterable of (LINE, COLUMN1, COLUMN2). + """ + + line1, column1 = start + line2, column2 = end + + if line1 == line2: + yield line1, column1, column2 + else: + for first, last, line_no in loop_first_last(range(line1, line2 + 1)): + if first: + yield line_no, column1, -1 + elif last: + yield line_no, 0, column2 + else: + yield line_no, 0, -1 + + +def install( + *, + console: Optional[Console] = None, + width: Optional[int] = 100, + code_width: Optional[int] = 88, + extra_lines: int = 3, + theme: Optional[str] = None, + word_wrap: bool = False, + show_locals: bool = False, + locals_max_length: int = LOCALS_MAX_LENGTH, + locals_max_string: int = LOCALS_MAX_STRING, + locals_hide_dunder: bool = True, + locals_hide_sunder: Optional[bool] = None, + indent_guides: bool = True, + suppress: Iterable[Union[str, ModuleType]] = (), + max_frames: int = 100, +) -> Callable[[Type[BaseException], BaseException, Optional[TracebackType]], Any]: + """Install a rich traceback handler. + + Once installed, any tracebacks will be printed with syntax highlighting and rich formatting. + + + Args: + console (Optional[Console], optional): Console to write exception to. Default uses internal Console instance. + width (Optional[int], optional): Width (in characters) of traceback. Defaults to 100. + code_width (Optional[int], optional): Code width (in characters) of traceback. Defaults to 88. + extra_lines (int, optional): Extra lines of code. Defaults to 3. + theme (Optional[str], optional): Pygments theme to use in traceback. Defaults to ``None`` which will pick + a theme appropriate for the platform. + word_wrap (bool, optional): Enable word wrapping of long lines. Defaults to False. + show_locals (bool, optional): Enable display of local variables. Defaults to False. + locals_max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation. + Defaults to 10. + locals_max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to 80. + locals_hide_dunder (bool, optional): Hide locals prefixed with double underscore. Defaults to True. + locals_hide_sunder (bool, optional): Hide locals prefixed with single underscore. Defaults to False. + indent_guides (bool, optional): Enable indent guides in code and locals. Defaults to True. + suppress (Sequence[Union[str, ModuleType]]): Optional sequence of modules or paths to exclude from traceback. + + Returns: + Callable: The previous exception handler that was replaced. + + """ + traceback_console = Console(stderr=True) if console is None else console + + locals_hide_sunder = ( + True + if (traceback_console.is_jupyter and locals_hide_sunder is None) + else locals_hide_sunder + ) + + def excepthook( + type_: Type[BaseException], + value: BaseException, + traceback: Optional[TracebackType], + ) -> None: + exception_traceback = Traceback.from_exception( + type_, + value, + traceback, + width=width, + code_width=code_width, + extra_lines=extra_lines, + theme=theme, + word_wrap=word_wrap, + show_locals=show_locals, + locals_max_length=locals_max_length, + locals_max_string=locals_max_string, + locals_hide_dunder=locals_hide_dunder, + locals_hide_sunder=bool(locals_hide_sunder), + indent_guides=indent_guides, + suppress=suppress, + max_frames=max_frames, + ) + traceback_console.print(exception_traceback) + + def ipy_excepthook_closure(ip: Any) -> None: # pragma: no cover + tb_data = {} # store information about showtraceback call + default_showtraceback = ip.showtraceback # keep reference of default traceback + + def ipy_show_traceback(*args: Any, **kwargs: Any) -> None: + """wrap the default ip.showtraceback to store info for ip._showtraceback""" + nonlocal tb_data + tb_data = kwargs + default_showtraceback(*args, **kwargs) + + def ipy_display_traceback( + *args: Any, is_syntax: bool = False, **kwargs: Any + ) -> None: + """Internally called traceback from ip._showtraceback""" + nonlocal tb_data + exc_tuple = ip._get_exc_info() + + # do not display trace on syntax error + tb: Optional[TracebackType] = None if is_syntax else exc_tuple[2] + + # determine correct tb_offset + compiled = tb_data.get("running_compiled_code", False) + tb_offset = tb_data.get("tb_offset", 1 if compiled else 0) + # remove ipython internal frames from trace with tb_offset + for _ in range(tb_offset): + if tb is None: + break + tb = tb.tb_next + + excepthook(exc_tuple[0], exc_tuple[1], tb) + tb_data = {} # clear data upon usage + + # replace _showtraceback instead of showtraceback to allow ipython features such as debugging to work + # this is also what the ipython docs recommends to modify when subclassing InteractiveShell + ip._showtraceback = ipy_display_traceback + # add wrapper to capture tb_data + ip.showtraceback = ipy_show_traceback + ip.showsyntaxerror = lambda *args, **kwargs: ipy_display_traceback( + *args, is_syntax=True, **kwargs + ) + + try: # pragma: no cover + # if within ipython, use customized traceback + ip = get_ipython() # type: ignore[name-defined] + ipy_excepthook_closure(ip) + return sys.excepthook + except Exception: + # otherwise use default system hook + old_excepthook = sys.excepthook + sys.excepthook = excepthook + return old_excepthook + + +@dataclass +class Frame: + filename: str + lineno: int + name: str + line: str = "" + locals: Optional[Dict[str, pretty.Node]] = None + last_instruction: Optional[Tuple[Tuple[int, int], Tuple[int, int]]] = None + + +@dataclass +class _SyntaxError: + offset: int + filename: str + line: str + lineno: int + msg: str + notes: List[str] = field(default_factory=list) + + +@dataclass +class Stack: + exc_type: str + exc_value: str + syntax_error: Optional[_SyntaxError] = None + is_cause: bool = False + frames: List[Frame] = field(default_factory=list) + notes: List[str] = field(default_factory=list) + is_group: bool = False + exceptions: List["Trace"] = field(default_factory=list) + + +@dataclass +class Trace: + stacks: List[Stack] + + +class PathHighlighter(RegexHighlighter): + highlights = [r"(?P.*/)(?P.+)"] + + +class Traceback: + """A Console renderable that renders a traceback. + + Args: + trace (Trace, optional): A `Trace` object produced from `extract`. Defaults to None, which uses + the last exception. + width (Optional[int], optional): Number of characters used to traceback. Defaults to 100. + code_width (Optional[int], optional): Number of code characters used to traceback. Defaults to 88. + extra_lines (int, optional): Additional lines of code to render. Defaults to 3. + theme (str, optional): Override pygments theme used in traceback. + word_wrap (bool, optional): Enable word wrapping of long lines. Defaults to False. + show_locals (bool, optional): Enable display of local variables. Defaults to False. + indent_guides (bool, optional): Enable indent guides in code and locals. Defaults to True. + locals_max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation. + Defaults to 10. + locals_max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to 80. + locals_hide_dunder (bool, optional): Hide locals prefixed with double underscore. Defaults to True. + locals_hide_sunder (bool, optional): Hide locals prefixed with single underscore. Defaults to False. + suppress (Sequence[Union[str, ModuleType]]): Optional sequence of modules or paths to exclude from traceback. + max_frames (int): Maximum number of frames to show in a traceback, 0 for no maximum. Defaults to 100. + + """ + + LEXERS = { + "": "text", + ".py": "python", + ".pxd": "cython", + ".pyx": "cython", + ".pxi": "pyrex", + } + + def __init__( + self, + trace: Optional[Trace] = None, + *, + width: Optional[int] = 100, + code_width: Optional[int] = 88, + extra_lines: int = 3, + theme: Optional[str] = None, + word_wrap: bool = False, + show_locals: bool = False, + locals_max_length: int = LOCALS_MAX_LENGTH, + locals_max_string: int = LOCALS_MAX_STRING, + locals_hide_dunder: bool = True, + locals_hide_sunder: bool = False, + indent_guides: bool = True, + suppress: Iterable[Union[str, ModuleType]] = (), + max_frames: int = 100, + ): + if trace is None: + exc_type, exc_value, traceback = sys.exc_info() + if exc_type is None or exc_value is None or traceback is None: + raise ValueError( + "Value for 'trace' required if not called in except: block" + ) + trace = self.extract( + exc_type, exc_value, traceback, show_locals=show_locals + ) + self.trace = trace + self.width = width + self.code_width = code_width + self.extra_lines = extra_lines + self.theme = Syntax.get_theme(theme or "ansi_dark") + self.word_wrap = word_wrap + self.show_locals = show_locals + self.indent_guides = indent_guides + self.locals_max_length = locals_max_length + self.locals_max_string = locals_max_string + self.locals_hide_dunder = locals_hide_dunder + self.locals_hide_sunder = locals_hide_sunder + + self.suppress: Sequence[str] = [] + for suppress_entity in suppress: + if not isinstance(suppress_entity, str): + assert ( + suppress_entity.__file__ is not None + ), f"{suppress_entity!r} must be a module with '__file__' attribute" + path = os.path.dirname(suppress_entity.__file__) + else: + path = suppress_entity + path = os.path.normpath(os.path.abspath(path)) + self.suppress.append(path) + self.max_frames = max(4, max_frames) if max_frames > 0 else 0 + + @classmethod + def from_exception( + cls, + exc_type: Type[Any], + exc_value: BaseException, + traceback: Optional[TracebackType], + *, + width: Optional[int] = 100, + code_width: Optional[int] = 88, + extra_lines: int = 3, + theme: Optional[str] = None, + word_wrap: bool = False, + show_locals: bool = False, + locals_max_length: int = LOCALS_MAX_LENGTH, + locals_max_string: int = LOCALS_MAX_STRING, + locals_hide_dunder: bool = True, + locals_hide_sunder: bool = False, + indent_guides: bool = True, + suppress: Iterable[Union[str, ModuleType]] = (), + max_frames: int = 100, + ) -> "Traceback": + """Create a traceback from exception info + + Args: + exc_type (Type[BaseException]): Exception type. + exc_value (BaseException): Exception value. + traceback (TracebackType): Python Traceback object. + width (Optional[int], optional): Number of characters used to traceback. Defaults to 100. + code_width (Optional[int], optional): Number of code characters used to traceback. Defaults to 88. + extra_lines (int, optional): Additional lines of code to render. Defaults to 3. + theme (str, optional): Override pygments theme used in traceback. + word_wrap (bool, optional): Enable word wrapping of long lines. Defaults to False. + show_locals (bool, optional): Enable display of local variables. Defaults to False. + indent_guides (bool, optional): Enable indent guides in code and locals. Defaults to True. + locals_max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation. + Defaults to 10. + locals_max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to 80. + locals_hide_dunder (bool, optional): Hide locals prefixed with double underscore. Defaults to True. + locals_hide_sunder (bool, optional): Hide locals prefixed with single underscore. Defaults to False. + suppress (Iterable[Union[str, ModuleType]]): Optional sequence of modules or paths to exclude from traceback. + max_frames (int): Maximum number of frames to show in a traceback, 0 for no maximum. Defaults to 100. + + Returns: + Traceback: A Traceback instance that may be printed. + """ + rich_traceback = cls.extract( + exc_type, + exc_value, + traceback, + show_locals=show_locals, + locals_max_length=locals_max_length, + locals_max_string=locals_max_string, + locals_hide_dunder=locals_hide_dunder, + locals_hide_sunder=locals_hide_sunder, + ) + + return cls( + rich_traceback, + width=width, + code_width=code_width, + extra_lines=extra_lines, + theme=theme, + word_wrap=word_wrap, + show_locals=show_locals, + indent_guides=indent_guides, + locals_max_length=locals_max_length, + locals_max_string=locals_max_string, + locals_hide_dunder=locals_hide_dunder, + locals_hide_sunder=locals_hide_sunder, + suppress=suppress, + max_frames=max_frames, + ) + + @classmethod + def extract( + cls, + exc_type: Type[BaseException], + exc_value: BaseException, + traceback: Optional[TracebackType], + *, + show_locals: bool = False, + locals_max_length: int = LOCALS_MAX_LENGTH, + locals_max_string: int = LOCALS_MAX_STRING, + locals_hide_dunder: bool = True, + locals_hide_sunder: bool = False, + ) -> Trace: + """Extract traceback information. + + Args: + exc_type (Type[BaseException]): Exception type. + exc_value (BaseException): Exception value. + traceback (TracebackType): Python Traceback object. + show_locals (bool, optional): Enable display of local variables. Defaults to False. + locals_max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation. + Defaults to 10. + locals_max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to 80. + locals_hide_dunder (bool, optional): Hide locals prefixed with double underscore. Defaults to True. + locals_hide_sunder (bool, optional): Hide locals prefixed with single underscore. Defaults to False. + + Returns: + Trace: A Trace instance which you can use to construct a `Traceback`. + """ + + stacks: List[Stack] = [] + is_cause = False + + from rich import _IMPORT_CWD + + notes: List[str] = getattr(exc_value, "__notes__", None) or [] + + def safe_str(_object: Any) -> str: + """Don't allow exceptions from __str__ to propagate.""" + try: + return str(_object) + except Exception: + return "" + + while True: + stack = Stack( + exc_type=safe_str(exc_type.__name__), + exc_value=safe_str(exc_value), + is_cause=is_cause, + notes=notes, + ) + + if sys.version_info >= (3, 11): + if isinstance(exc_value, (BaseExceptionGroup, ExceptionGroup)): + stack.is_group = True + for exception in exc_value.exceptions: + stack.exceptions.append( + Traceback.extract( + type(exception), + exception, + exception.__traceback__, + show_locals=show_locals, + locals_max_length=locals_max_length, + locals_hide_dunder=locals_hide_dunder, + locals_hide_sunder=locals_hide_sunder, + ) + ) + + if isinstance(exc_value, SyntaxError): + stack.syntax_error = _SyntaxError( + offset=exc_value.offset or 0, + filename=exc_value.filename or "?", + lineno=exc_value.lineno or 0, + line=exc_value.text or "", + msg=exc_value.msg, + notes=notes, + ) + + stacks.append(stack) + append = stack.frames.append + + def get_locals( + iter_locals: Iterable[Tuple[str, object]], + ) -> Iterable[Tuple[str, object]]: + """Extract locals from an iterator of key pairs.""" + if not (locals_hide_dunder or locals_hide_sunder): + yield from iter_locals + return + for key, value in iter_locals: + if locals_hide_dunder and key.startswith("__"): + continue + if locals_hide_sunder and key.startswith("_"): + continue + yield key, value + + for frame_summary, line_no in walk_tb(traceback): + filename = frame_summary.f_code.co_filename + + last_instruction: Optional[Tuple[Tuple[int, int], Tuple[int, int]]] + last_instruction = None + if sys.version_info >= (3, 11): + instruction_index = frame_summary.f_lasti // 2 + instruction_position = next( + islice( + frame_summary.f_code.co_positions(), + instruction_index, + instruction_index + 1, + ) + ) + ( + start_line, + end_line, + start_column, + end_column, + ) = instruction_position + if ( + start_line is not None + and end_line is not None + and start_column is not None + and end_column is not None + ): + last_instruction = ( + (start_line, start_column), + (end_line, end_column), + ) + + if filename and not filename.startswith("<"): + if not os.path.isabs(filename): + filename = os.path.join(_IMPORT_CWD, filename) + if frame_summary.f_locals.get("_rich_traceback_omit", False): + continue + + frame = Frame( + filename=filename or "?", + lineno=line_no, + name=frame_summary.f_code.co_name, + locals=( + { + key: pretty.traverse( + value, + max_length=locals_max_length, + max_string=locals_max_string, + ) + for key, value in get_locals(frame_summary.f_locals.items()) + if not (inspect.isfunction(value) or inspect.isclass(value)) + } + if show_locals + else None + ), + last_instruction=last_instruction, + ) + append(frame) + if frame_summary.f_locals.get("_rich_traceback_guard", False): + del stack.frames[:] + + cause = getattr(exc_value, "__cause__", None) + if cause: + exc_type = cause.__class__ + exc_value = cause + # __traceback__ can be None, e.g. for exceptions raised by the + # 'multiprocessing' module + traceback = cause.__traceback__ + is_cause = True + continue + + cause = exc_value.__context__ + if cause and not getattr(exc_value, "__suppress_context__", False): + exc_type = cause.__class__ + exc_value = cause + traceback = cause.__traceback__ + is_cause = False + continue + # No cover, code is reached but coverage doesn't recognize it. + break # pragma: no cover + + trace = Trace(stacks=stacks) + + return trace + + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: + theme = self.theme + background_style = theme.get_background_style() + token_style = theme.get_style_for_token + + traceback_theme = Theme( + { + "pretty": token_style(TextToken), + "pygments.text": token_style(Token), + "pygments.string": token_style(String), + "pygments.function": token_style(Name.Function), + "pygments.number": token_style(Number), + "repr.indent": token_style(Comment) + Style(dim=True), + "repr.str": token_style(String), + "repr.brace": token_style(TextToken) + Style(bold=True), + "repr.number": token_style(Number), + "repr.bool_true": token_style(Keyword.Constant), + "repr.bool_false": token_style(Keyword.Constant), + "repr.none": token_style(Keyword.Constant), + "scope.border": token_style(String.Delimiter), + "scope.equals": token_style(Operator), + "scope.key": token_style(Name), + "scope.key.special": token_style(Name.Constant) + Style(dim=True), + }, + inherit=False, + ) + + highlighter = ReprHighlighter() + + @group() + def render_stack(stack: Stack, last: bool) -> RenderResult: + if stack.frames: + stack_renderable: ConsoleRenderable = Panel( + self._render_stack(stack), + title="[traceback.title]Traceback [dim](most recent call last)", + style=background_style, + border_style="traceback.border", + expand=True, + padding=(0, 1), + ) + stack_renderable = Constrain(stack_renderable, self.width) + with console.use_theme(traceback_theme): + yield stack_renderable + + if stack.syntax_error is not None: + with console.use_theme(traceback_theme): + yield Constrain( + Panel( + self._render_syntax_error(stack.syntax_error), + style=background_style, + border_style="traceback.border.syntax_error", + expand=True, + padding=(0, 1), + width=self.width, + ), + self.width, + ) + yield Text.assemble( + (f"{stack.exc_type}: ", "traceback.exc_type"), + highlighter(stack.syntax_error.msg), + ) + elif stack.exc_value: + yield Text.assemble( + (f"{stack.exc_type}: ", "traceback.exc_type"), + highlighter(stack.exc_value), + ) + else: + yield Text.assemble((f"{stack.exc_type}", "traceback.exc_type")) + + for note in stack.notes: + yield Text.assemble(("[NOTE] ", "traceback.note"), highlighter(note)) + + if stack.is_group: + for group_no, group_exception in enumerate(stack.exceptions, 1): + grouped_exceptions: List[Group] = [] + for group_last, group_stack in loop_last(group_exception.stacks): + grouped_exceptions.append(render_stack(group_stack, group_last)) + yield "" + yield Constrain( + Panel( + Group(*grouped_exceptions), + title=f"Sub-exception #{group_no}", + border_style="traceback.group.border", + ), + self.width, + ) + + if not last: + if stack.is_cause: + yield Text.from_markup( + "\n[i]The above exception was the direct cause of the following exception:\n", + ) + else: + yield Text.from_markup( + "\n[i]During handling of the above exception, another exception occurred:\n", + ) + + for last, stack in loop_last(reversed(self.trace.stacks)): + yield render_stack(stack, last) + + @group() + def _render_syntax_error(self, syntax_error: _SyntaxError) -> RenderResult: + highlighter = ReprHighlighter() + path_highlighter = PathHighlighter() + if syntax_error.filename != "": + if os.path.exists(syntax_error.filename): + text = Text.assemble( + (f" {syntax_error.filename}", "pygments.string"), + (":", "pygments.text"), + (str(syntax_error.lineno), "pygments.number"), + style="pygments.text", + ) + yield path_highlighter(text) + syntax_error_text = highlighter(syntax_error.line.rstrip()) + syntax_error_text.no_wrap = True + offset = min(syntax_error.offset - 1, len(syntax_error_text)) + syntax_error_text.stylize("bold underline", offset, offset) + syntax_error_text += Text.from_markup( + "\n" + " " * offset + "[traceback.offset]▲[/]", + style="pygments.text", + ) + yield syntax_error_text + + @classmethod + def _guess_lexer(cls, filename: str, code: str) -> str: + ext = os.path.splitext(filename)[-1] + if not ext: + # No extension, look at first line to see if it is a hashbang + # Note, this is an educated guess and not a guarantee + # If it fails, the only downside is that the code is highlighted strangely + new_line_index = code.index("\n") + first_line = code[:new_line_index] if new_line_index != -1 else code + if first_line.startswith("#!") and "python" in first_line.lower(): + return "python" + try: + return cls.LEXERS.get(ext) or guess_lexer_for_filename(filename, code).name + except ClassNotFound: + return "text" + + @group() + def _render_stack(self, stack: Stack) -> RenderResult: + path_highlighter = PathHighlighter() + theme = self.theme + + def render_locals(frame: Frame) -> Iterable[ConsoleRenderable]: + if frame.locals: + yield render_scope( + frame.locals, + title="locals", + indent_guides=self.indent_guides, + max_length=self.locals_max_length, + max_string=self.locals_max_string, + ) + + exclude_frames: Optional[range] = None + if self.max_frames != 0: + exclude_frames = range( + self.max_frames // 2, + len(stack.frames) - self.max_frames // 2, + ) + + excluded = False + for frame_index, frame in enumerate(stack.frames): + if exclude_frames and frame_index in exclude_frames: + excluded = True + continue + + if excluded: + assert exclude_frames is not None + yield Text( + f"\n... {len(exclude_frames)} frames hidden ...", + justify="center", + style="traceback.error", + ) + excluded = False + + first = frame_index == 0 + frame_filename = frame.filename + suppressed = any(frame_filename.startswith(path) for path in self.suppress) + + if os.path.exists(frame.filename): + text = Text.assemble( + path_highlighter(Text(frame.filename, style="pygments.string")), + (":", "pygments.text"), + (str(frame.lineno), "pygments.number"), + " in ", + (frame.name, "pygments.function"), + style="pygments.text", + ) + else: + text = Text.assemble( + "in ", + (frame.name, "pygments.function"), + (":", "pygments.text"), + (str(frame.lineno), "pygments.number"), + style="pygments.text", + ) + if not frame.filename.startswith("<") and not first: + yield "" + yield text + if frame.filename.startswith("<"): + yield from render_locals(frame) + continue + if not suppressed: + try: + code_lines = linecache.getlines(frame.filename) + code = "".join(code_lines) + if not code: + # code may be an empty string if the file doesn't exist, OR + # if the traceback filename is generated dynamically + continue + lexer_name = self._guess_lexer(frame.filename, code) + syntax = Syntax( + code, + lexer_name, + theme=theme, + line_numbers=True, + line_range=( + frame.lineno - self.extra_lines, + frame.lineno + self.extra_lines, + ), + highlight_lines={frame.lineno}, + word_wrap=self.word_wrap, + code_width=self.code_width, + indent_guides=self.indent_guides, + dedent=False, + ) + yield "" + except Exception as error: + yield Text.assemble( + (f"\n{error}", "traceback.error"), + ) + else: + if frame.last_instruction is not None: + start, end = frame.last_instruction + + # Stylize a line at a time + # So that indentation isn't underlined (which looks bad) + for line1, column1, column2 in _iter_syntax_lines(start, end): + try: + if column1 == 0: + line = code_lines[line1 - 1] + column1 = len(line) - len(line.lstrip()) + if column2 == -1: + column2 = len(code_lines[line1 - 1]) + except IndexError: + # Being defensive here + # If last_instruction reports a line out-of-bounds, we don't want to crash + continue + + syntax.stylize_range( + style="traceback.error_range", + start=(line1, column1), + end=(line1, column2), + ) + yield ( + Columns( + [ + syntax, + *render_locals(frame), + ], + padding=1, + ) + if frame.locals + else syntax + ) + + +if __name__ == "__main__": # pragma: no cover + install(show_locals=True) + import sys + + def bar( + a: Any, + ) -> None: # 这是对亚洲语言支持的测试。面对模棱两可的想法,拒绝猜测的诱惑 + one = 1 + print(one / a) + + def foo(a: Any) -> None: + _rich_traceback_guard = True + zed = { + "characters": { + "Paul Atreides", + "Vladimir Harkonnen", + "Thufir Hawat", + "Duncan Idaho", + }, + "atomic_types": (None, False, True), + } + bar(a) + + def error() -> None: + foo(0) + + error() diff --git a/venv/lib/python3.10/site-packages/rich/tree.py b/venv/lib/python3.10/site-packages/rich/tree.py new file mode 100644 index 0000000000000000000000000000000000000000..9a87d60ded6fc3c8a6e678644dbf2931005dc71f --- /dev/null +++ b/venv/lib/python3.10/site-packages/rich/tree.py @@ -0,0 +1,257 @@ +from typing import Iterator, List, Optional, Tuple + +from ._loop import loop_first, loop_last +from .console import Console, ConsoleOptions, RenderableType, RenderResult +from .jupyter import JupyterMixin +from .measure import Measurement +from .segment import Segment +from .style import Style, StyleStack, StyleType +from .styled import Styled + +GuideType = Tuple[str, str, str, str] + + +class Tree(JupyterMixin): + """A renderable for a tree structure. + + Attributes: + ASCII_GUIDES (GuideType): Guide lines used when Console.ascii_only is True. + TREE_GUIDES (List[GuideType, GuideType, GuideType]): Default guide lines. + + Args: + label (RenderableType): The renderable or str for the tree label. + style (StyleType, optional): Style of this tree. Defaults to "tree". + guide_style (StyleType, optional): Style of the guide lines. Defaults to "tree.line". + expanded (bool, optional): Also display children. Defaults to True. + highlight (bool, optional): Highlight renderable (if str). Defaults to False. + hide_root (bool, optional): Hide the root node. Defaults to False. + """ + + ASCII_GUIDES = (" ", "| ", "+-- ", "`-- ") + TREE_GUIDES = [ + (" ", "│ ", "├── ", "└── "), + (" ", "┃ ", "┣━━ ", "┗━━ "), + (" ", "║ ", "╠══ ", "╚══ "), + ] + + def __init__( + self, + label: RenderableType, + *, + style: StyleType = "tree", + guide_style: StyleType = "tree.line", + expanded: bool = True, + highlight: bool = False, + hide_root: bool = False, + ) -> None: + self.label = label + self.style = style + self.guide_style = guide_style + self.children: List[Tree] = [] + self.expanded = expanded + self.highlight = highlight + self.hide_root = hide_root + + def add( + self, + label: RenderableType, + *, + style: Optional[StyleType] = None, + guide_style: Optional[StyleType] = None, + expanded: bool = True, + highlight: Optional[bool] = False, + ) -> "Tree": + """Add a child tree. + + Args: + label (RenderableType): The renderable or str for the tree label. + style (StyleType, optional): Style of this tree. Defaults to "tree". + guide_style (StyleType, optional): Style of the guide lines. Defaults to "tree.line". + expanded (bool, optional): Also display children. Defaults to True. + highlight (Optional[bool], optional): Highlight renderable (if str). Defaults to False. + + Returns: + Tree: A new child Tree, which may be further modified. + """ + node = Tree( + label, + style=self.style if style is None else style, + guide_style=self.guide_style if guide_style is None else guide_style, + expanded=expanded, + highlight=self.highlight if highlight is None else highlight, + ) + self.children.append(node) + return node + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + stack: List[Iterator[Tuple[bool, Tree]]] = [] + pop = stack.pop + push = stack.append + new_line = Segment.line() + + get_style = console.get_style + null_style = Style.null() + guide_style = get_style(self.guide_style, default="") or null_style + SPACE, CONTINUE, FORK, END = range(4) + + _Segment = Segment + + def make_guide(index: int, style: Style) -> Segment: + """Make a Segment for a level of the guide lines.""" + if options.ascii_only: + line = self.ASCII_GUIDES[index] + else: + guide = 1 if style.bold else (2 if style.underline2 else 0) + line = self.TREE_GUIDES[0 if options.legacy_windows else guide][index] + return _Segment(line, style) + + levels: List[Segment] = [make_guide(CONTINUE, guide_style)] + push(iter(loop_last([self]))) + + guide_style_stack = StyleStack(get_style(self.guide_style)) + style_stack = StyleStack(get_style(self.style)) + remove_guide_styles = Style(bold=False, underline2=False) + + depth = 0 + + while stack: + stack_node = pop() + try: + last, node = next(stack_node) + except StopIteration: + levels.pop() + if levels: + guide_style = levels[-1].style or null_style + levels[-1] = make_guide(FORK, guide_style) + guide_style_stack.pop() + style_stack.pop() + continue + push(stack_node) + if last: + levels[-1] = make_guide(END, levels[-1].style or null_style) + + guide_style = guide_style_stack.current + get_style(node.guide_style) + style = style_stack.current + get_style(node.style) + prefix = levels[(2 if self.hide_root else 1) :] + renderable_lines = console.render_lines( + Styled(node.label, style), + options.update( + width=options.max_width + - sum(level.cell_length for level in prefix), + highlight=self.highlight, + height=None, + ), + pad=options.justify is not None, + ) + + if not (depth == 0 and self.hide_root): + for first, line in loop_first(renderable_lines): + if prefix: + yield from _Segment.apply_style( + prefix, + style.background_style, + post_style=remove_guide_styles, + ) + yield from line + yield new_line + if first and prefix: + prefix[-1] = make_guide( + SPACE if last else CONTINUE, prefix[-1].style or null_style + ) + + if node.expanded and node.children: + levels[-1] = make_guide( + SPACE if last else CONTINUE, levels[-1].style or null_style + ) + levels.append( + make_guide(END if len(node.children) == 1 else FORK, guide_style) + ) + style_stack.push(get_style(node.style)) + guide_style_stack.push(get_style(node.guide_style)) + push(iter(loop_last(node.children))) + depth += 1 + + def __rich_measure__( + self, console: "Console", options: "ConsoleOptions" + ) -> "Measurement": + stack: List[Iterator[Tree]] = [iter([self])] + pop = stack.pop + push = stack.append + minimum = 0 + maximum = 0 + measure = Measurement.get + level = 0 + while stack: + iter_tree = pop() + try: + tree = next(iter_tree) + except StopIteration: + level -= 1 + continue + push(iter_tree) + min_measure, max_measure = measure(console, options, tree.label) + indent = level * 4 + minimum = max(min_measure + indent, minimum) + maximum = max(max_measure + indent, maximum) + if tree.expanded and tree.children: + push(iter(tree.children)) + level += 1 + return Measurement(minimum, maximum) + + +if __name__ == "__main__": # pragma: no cover + from rich.console import Group + from rich.markdown import Markdown + from rich.panel import Panel + from rich.syntax import Syntax + from rich.table import Table + + table = Table(row_styles=["", "dim"]) + + table.add_column("Released", style="cyan", no_wrap=True) + table.add_column("Title", style="magenta") + table.add_column("Box Office", justify="right", style="green") + + table.add_row("Dec 20, 2019", "Star Wars: The Rise of Skywalker", "$952,110,690") + table.add_row("May 25, 2018", "Solo: A Star Wars Story", "$393,151,347") + table.add_row("Dec 15, 2017", "Star Wars Ep. V111: The Last Jedi", "$1,332,539,889") + table.add_row("Dec 16, 2016", "Rogue One: A Star Wars Story", "$1,332,439,889") + + code = """\ +class Segment(NamedTuple): + text: str = "" + style: Optional[Style] = None + is_control: bool = False +""" + syntax = Syntax(code, "python", theme="monokai", line_numbers=True) + + markdown = Markdown( + """\ +### example.md +> Hello, World! +> +> Markdown _all_ the things +""" + ) + + root = Tree("🌲 [b green]Rich Tree", highlight=True, hide_root=True) + + node = root.add(":file_folder: Renderables", guide_style="red") + simple_node = node.add(":file_folder: [bold yellow]Atomic", guide_style="uu green") + simple_node.add(Group("📄 Syntax", syntax)) + simple_node.add(Group("📄 Markdown", Panel(markdown, border_style="green"))) + + containers_node = node.add( + ":file_folder: [bold magenta]Containers", guide_style="bold magenta" + ) + containers_node.expanded = True + panel = Panel.fit("Just a panel", border_style="red") + containers_node.add(Group("📄 Panels", panel)) + + containers_node.add(Group("📄 [b magenta]Table", table)) + + console = Console() + + console.print(root) diff --git a/venv/lib/python3.10/site-packages/test/__init__.py b/venv/lib/python3.10/site-packages/test/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.10/site-packages/test/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/test/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0b020216823c4c833083c33336ae3edffd9bc9ab Binary files /dev/null and b/venv/lib/python3.10/site-packages/test/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/test/__pycache__/api_test.cpython-310.pyc b/venv/lib/python3.10/site-packages/test/__pycache__/api_test.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f9a8b50bf10f1255b67f847e89f83407c4db5028 Binary files /dev/null and b/venv/lib/python3.10/site-packages/test/__pycache__/api_test.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/test/__pycache__/client_test.cpython-310.pyc b/venv/lib/python3.10/site-packages/test/__pycache__/client_test.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a797a743b4f7eb610521b510cb613244fc5fed02 Binary files /dev/null and b/venv/lib/python3.10/site-packages/test/__pycache__/client_test.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/test/__pycache__/response_examples.cpython-310.pyc b/venv/lib/python3.10/site-packages/test/__pycache__/response_examples.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cdbc00be4e1f2619d402f2acb4987bc52e921f35 Binary files /dev/null and b/venv/lib/python3.10/site-packages/test/__pycache__/response_examples.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/test/__pycache__/user_test.cpython-310.pyc b/venv/lib/python3.10/site-packages/test/__pycache__/user_test.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8c6878f9c24239c692542d58c0353ee4eb7cdcf3 Binary files /dev/null and b/venv/lib/python3.10/site-packages/test/__pycache__/user_test.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/test/api_test.py b/venv/lib/python3.10/site-packages/test/api_test.py new file mode 100644 index 0000000000000000000000000000000000000000..90175918bdeb39bccd903b1f5f3a0424954aedd5 --- /dev/null +++ b/venv/lib/python3.10/site-packages/test/api_test.py @@ -0,0 +1,507 @@ +import responses +import pytest +import json +from copy import deepcopy +from matrix_client import client, api +from matrix_client.errors import MatrixRequestError, MatrixError, MatrixHttpLibError +from matrix_client import __version__ as lib_version +from . import response_examples +MATRIX_V2_API_PATH = "/_matrix/client/r0" + + +class TestTagsApi: + cli = client.MatrixClient("http://example.com") + user_id = "@user:matrix.org" + room_id = "#foo:matrix.org" + + @responses.activate + def test_get_user_tags(self): + tags_url = "http://example.com" \ + "/_matrix/client/r0/user/@user:matrix.org/rooms/#foo:matrix.org/tags" + responses.add(responses.GET, tags_url, body='{}') + self.cli.api.get_user_tags(self.user_id, self.room_id) + req = responses.calls[0].request + assert req.url == tags_url + assert req.method == 'GET' + + @responses.activate + def test_add_user_tags(self): + tags_url = "http://example.com" \ + "/_matrix/client/r0/user/@user:matrix.org/rooms/#foo:matrix.org/tags/foo" + responses.add(responses.PUT, tags_url, body='{}') + self.cli.api.add_user_tag(self.user_id, self.room_id, "foo", body={"order": "5"}) + req = responses.calls[0].request + assert req.url == tags_url + assert req.method == 'PUT' + + @responses.activate + def test_remove_user_tags(self): + tags_url = "http://example.com" \ + "/_matrix/client/r0/user/@user:matrix.org/rooms/#foo:matrix.org/tags/foo" + responses.add(responses.DELETE, tags_url, body='{}') + self.cli.api.remove_user_tag(self.user_id, self.room_id, "foo") + req = responses.calls[0].request + assert req.url == tags_url + assert req.method == 'DELETE' + + +class TestAccountDataApi: + cli = client.MatrixClient("http://example.com") + user_id = "@user:matrix.org" + room_id = "#foo:matrix.org" + + @responses.activate + def test_set_account_data(self): + account_data_url = "http://example.com" \ + "/_matrix/client/r0/user/@user:matrix.org/account_data/foo" + responses.add(responses.PUT, account_data_url, body='{}') + self.cli.api.set_account_data(self.user_id, 'foo', {'bar': 1}) + req = responses.calls[0].request + assert req.url == account_data_url + assert req.method == 'PUT' + + @responses.activate + def test_set_room_account_data(self): + account_data_url = "http://example.com/_matrix/client/r0/user" \ + "/@user:matrix.org/rooms/#foo:matrix.org/account_data/foo" + responses.add(responses.PUT, account_data_url, body='{}') + self.cli.api.set_room_account_data(self.user_id, self.room_id, 'foo', {'bar': 1}) + req = responses.calls[0].request + assert req.url == account_data_url + assert req.method == 'PUT' + + +class TestUnbanApi: + cli = client.MatrixClient("http://example.com") + user_id = "@user:matrix.org" + room_id = "#foo:matrix.org" + + @responses.activate + def test_unban(self): + unban_url = "http://example.com" \ + "/_matrix/client/r0/rooms/#foo:matrix.org/unban" + responses.add(responses.POST, unban_url, body='{}') + self.cli.api.unban_user(self.room_id, self.user_id) + req = responses.calls[0].request + assert req.url == unban_url + assert req.method == 'POST' + + +class TestDeviceApi: + cli = client.MatrixClient("http://example.com") + device_id = "QBUAZIFURK" + display_name = "test_name" + auth_body = { + "auth": { + "type": "example.type.foo", + "session": "xxxxx", + "example_credential": "verypoorsharedsecret" + } + } + + @responses.activate + def test_get_devices(self): + get_devices_url = "http://example.com/_matrix/client/r0/devices" + responses.add(responses.GET, get_devices_url, body='{}') + self.cli.api.get_devices() + req = responses.calls[0].request + assert req.url == get_devices_url + assert req.method == 'GET' + + @responses.activate + def test_get_device(self): + get_device_url = "http://example.com/_matrix/client/r0/devices/QBUAZIFURK" + responses.add(responses.GET, get_device_url, body='{}') + self.cli.api.get_device(self.device_id) + req = responses.calls[0].request + assert req.url == get_device_url + assert req.method == 'GET' + + @responses.activate + def test_update_device_info(self): + update_url = "http://example.com/_matrix/client/r0/devices/QBUAZIFURK" + responses.add(responses.PUT, update_url, body='{}') + self.cli.api.update_device_info(self.device_id, self.display_name) + req = responses.calls[0].request + assert req.url == update_url + assert req.method == 'PUT' + + @responses.activate + def test_delete_device(self): + delete_device_url = "http://example.com/_matrix/client/r0/devices/QBUAZIFURK" + responses.add(responses.DELETE, delete_device_url, body='{}') + # Test for 401 status code of User-Interactive Auth API + responses.add(responses.DELETE, delete_device_url, body='{}', status=401) + self.cli.api.delete_device(self.auth_body, self.device_id) + req = responses.calls[0].request + assert req.url == delete_device_url + assert req.method == 'DELETE' + + with pytest.raises(MatrixRequestError): + self.cli.api.delete_device(self.auth_body, self.device_id) + + @responses.activate + def test_delete_devices(self): + delete_devices_url = "http://example.com/_matrix/client/r0/delete_devices" + responses.add(responses.POST, delete_devices_url, body='{}') + # Test for 401 status code of User-Interactive Auth API + responses.add(responses.POST, delete_devices_url, body='{}', status=401) + self.cli.api.delete_devices(self.auth_body, [self.device_id]) + req = responses.calls[0].request + assert req.url == delete_devices_url + assert req.method == 'POST' + + with pytest.raises(MatrixRequestError): + self.cli.api.delete_devices(self.auth_body, [self.device_id]) + + +class TestKeysApi: + cli = client.MatrixClient("http://example.com") + user_id = "@alice:matrix.org" + device_id = "JLAFKJWSCS" + one_time_keys = {"curve25519:AAAAAQ": "/qyvZvwjiTxGdGU0RCguDCLeR+nmsb3FfNG3/Ve4vU8"} + device_keys = { + "user_id": "@alice:example.com", + "device_id": "JLAFKJWSCS", + "algorithms": [ + "m.olm.curve25519-aes-sha256", + "m.megolm.v1.aes-sha" + ], + "keys": { + "curve25519:JLAFKJWSCS": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", + "ed25519:JLAFKJWSCS": "lEuiRJBit0IG6nUf5pUzWTUEsRVVe/HJkoKuEww9ULI" + }, + "signatures": { + "@alice:example.com": { + "ed25519:JLAFKJWSCS": ("dSO80A01XiigH3uBiDVx/EjzaoycHcjq9lfQX0uWsqxl2gi" + "MIiSPR8a4d291W1ihKJL/a+myXS367WT6NAIcBA") + } + } + } + + @responses.activate + @pytest.mark.parametrize("args", [ + {}, + {'device_keys': device_keys}, + {'one_time_keys': one_time_keys} + ]) + def test_upload_keys(self, args): + upload_keys_url = "http://example.com/_matrix/client/r0/keys/upload" + responses.add(responses.POST, upload_keys_url, body='{}') + self.cli.api.upload_keys(**args) + req = responses.calls[0].request + assert req.url == upload_keys_url + assert req.method == 'POST' + + @responses.activate + def test_query_keys(self): + query_user_keys_url = "http://example.com/_matrix/client/r0/keys/query" + responses.add(responses.POST, query_user_keys_url, body='{}') + self.cli.api.query_keys({self.user_id: self.device_id}, timeout=10) + req = responses.calls[0].request + assert req.url == query_user_keys_url + assert req.method == 'POST' + + @responses.activate + def test_claim_keys(self): + claim_keys_url = "http://example.com/_matrix/client/r0/keys/claim" + responses.add(responses.POST, claim_keys_url, body='{}') + self.cli.api.claim_keys({self.user_id: {self.device_id: "algo"}}, timeout=1000) + req = responses.calls[0].request + assert req.url == claim_keys_url + assert req.method == 'POST' + + @responses.activate + def test_key_changes(self): + key_changes_url = "http://example.com/_matrix/client/r0/keys/changes" + responses.add(responses.GET, key_changes_url, body='{}') + self.cli.api.key_changes('s72594_4483_1934', 's75689_5632_2435') + req = responses.calls[0].request + assert req.url.split('?')[0] == key_changes_url + assert req.method == 'GET' + + +class TestSendToDeviceApi: + cli = client.MatrixClient("http://example.com") + user_id = "@alice:matrix.org" + device_id = "JLAFKJWSCS" + + @responses.activate + def test_send_to_device(self): + txn_id = self.cli.api._make_txn_id() + send_to_device_url = \ + "http://example.com/_matrix/client/r0/sendToDevice/m.new_device/" + txn_id + responses.add(responses.PUT, send_to_device_url, body='{}') + payload = {self.user_id: {self.device_id: {"test": 1}}} + self.cli.api.send_to_device("m.new_device", payload, txn_id) + req = responses.calls[0].request + assert req.url == send_to_device_url + assert req.method == 'PUT' + + +class TestMainApi: + user_id = "@alice:matrix.org" + token = "Dp0YKRXwx0iWDhFj7lg3DVjwsWzGcUIgARljgyAip2JD8qd5dSaW" \ + "cxowTKEFetPulfLijAhv8eOmUSScyGcWgZyNMRTBmoJ0RFc0HotPvTBZ" \ + "U98yKRLtat7V43aCpFmK" + test_path = "/account/whoami" + + @responses.activate + def test_send_token_header(self): + mapi = api.MatrixHttpApi("http://example.com", token=self.token) + responses.add( + responses.GET, + mapi._base_url+MATRIX_V2_API_PATH+self.test_path, + body='{"application/json": {"user_id": "%s"}}' % self.user_id + ) + mapi._send("GET", self.test_path) + req = responses.calls[0].request + assert req.method == 'GET' + assert req.headers['Authorization'] == 'Bearer %s' % self.token + + @responses.activate + def test_send_user_agent_header(self): + mapi = api.MatrixHttpApi("http://example.com") + responses.add( + responses.GET, + mapi._base_url+MATRIX_V2_API_PATH+self.test_path, + body='{"application/json": {"user_id": "%s"}}' % self.user_id + ) + mapi._send("GET", self.test_path) + req = responses.calls[0].request + assert req.method == 'GET' + assert req.headers['User-Agent'] == 'matrix-python-sdk/%s' % lib_version + + @responses.activate + def test_send_token_query(self): + mapi = api.MatrixHttpApi( + "http://example.com", + token=self.token, + use_authorization_header=False + ) + responses.add( + responses.GET, + mapi._base_url+MATRIX_V2_API_PATH+self.test_path, + body='{"application/json": {"user_id": "%s"}}' % self.user_id + ) + mapi._send("GET", self.test_path) + req = responses.calls[0].request + assert req.method == 'GET' + assert self.token in req.url + + @responses.activate + def test_send_user_id(self): + mapi = api.MatrixHttpApi( + "http://example.com", + token=self.token, + identity=self.user_id + ) + responses.add( + responses.GET, + mapi._base_url+MATRIX_V2_API_PATH+self.test_path, + body='{"application/json": {"user_id": "%s"}}' % self.user_id + ) + mapi._send("GET", self.test_path) + req = responses.calls[0].request + assert "user_id" in req.url + + @responses.activate + def test_send_unsup_method(self): + mapi = api.MatrixHttpApi("http://example.com") + with pytest.raises(MatrixError): + mapi._send("GOT", self.test_path) + + @responses.activate + def test_send_request_error(self): + mapi = api.MatrixHttpApi("http://example.com") + with pytest.raises(MatrixHttpLibError): + mapi._send("GET", self.test_path) + + +class TestMediaApi: + cli = client.MatrixClient("http://example.com") + user_id = "@alice:example.com" + mxcurl = "mxc://example.com/OonjUOmcuVpUnmOWKtzPmAFe" + + @responses.activate + def test_media_download(self): + media_url = \ + "http://example.com/_matrix/media/r0/download/" + self.mxcurl[6:] + with open('test/response_examples.py', 'rb') as fil: + responses.add( + responses.GET, media_url, + content_type='application/python', + body=fil.read(), status=200, stream=True + ) + resp = self.cli.api.media_download(self.mxcurl, allow_remote=False) + resp.raw.decode_content = True + req = responses.calls[0].request + assert req.url.split('?')[0] == media_url + assert req.method == 'GET' + + def test_media_download_wrong_url(self): + with pytest.raises(ValueError): + self.cli.api.media_download(self.mxcurl[6:]) + + @responses.activate + def test_get_thumbnail(self): + media_url = \ + "http://example.com/_matrix/media/r0/thumbnail/" + self.mxcurl[6:] + with open('test/response_examples.py', 'rb') as fil: + responses.add( + responses.GET, media_url, + content_type='application/python', + body=fil.read(), status=200, stream=True + ) + resp = self.cli.api.get_thumbnail( + self.mxcurl, 28, 28, allow_remote=False + ) + resp.raw.decode_content = True + req = responses.calls[0].request + assert req.url.split('?')[0] == media_url + assert req.method == 'GET' + + def test_get_thumbnail_wrong_url(self): + with pytest.raises(ValueError): + self.cli.api.get_thumbnail(self.mxcurl[6:], 28, 28) + + def test_get_thumbnail_wrong_method(self): + with pytest.raises(ValueError): + self.cli.api.get_thumbnail(self.mxcurl, 28, 28, 'cut') + + @responses.activate + def test_get_url_preview(self): + media_url = \ + "http://example.com/_matrix/media/r0/preview_url" + preview_url = deepcopy(response_examples.example_preview_url) + responses.add( + responses.GET, media_url, + body=json.dumps(preview_url) + ) + self.cli.api.get_url_preview("https://google.com/", 1510610716656) + req = responses.calls[0].request + assert req.url.split('?')[0] == media_url + assert req.method == 'GET' + + +class TestRoomApi: + cli = client.MatrixClient("http://example.com") + user_id = "@user:matrix.org" + room_id = "#foo:matrix.org" + + @responses.activate + def test_create_room_visibility_public(self): + create_room_url = "http://example.com" \ + "/_matrix/client/r0/createRoom" + responses.add( + responses.POST, + create_room_url, + json='{"room_id": "!sefiuhWgwghwWgh:example.com"}' + ) + self.cli.api.create_room( + name="test", + alias="#test:example.com", + is_public=True + ) + req = responses.calls[0].request + assert req.url == create_room_url + assert req.method == 'POST' + j = json.loads(req.body) + assert j["room_alias_name"] == "#test:example.com" + assert j["visibility"] == "public" + assert j["name"] == "test" + + @responses.activate + def test_create_room_visibility_private(self): + create_room_url = "http://example.com" \ + "/_matrix/client/r0/createRoom" + responses.add( + responses.POST, + create_room_url, + json='{"room_id": "!sefiuhWgwghwWgh:example.com"}' + ) + self.cli.api.create_room( + name="test", + alias="#test:example.com", + is_public=False + ) + req = responses.calls[0].request + assert req.url == create_room_url + assert req.method == 'POST' + j = json.loads(req.body) + assert j["room_alias_name"] == "#test:example.com" + assert j["visibility"] == "private" + assert j["name"] == "test" + + @responses.activate + def test_create_room_federate_true(self): + create_room_url = "http://example.com" \ + "/_matrix/client/r0/createRoom" + responses.add( + responses.POST, + create_room_url, + json='{"room_id": "!sefiuhWgwghwWgh:example.com"}' + ) + self.cli.api.create_room( + name="test2", + alias="#test2:example.com", + federate=True + ) + req = responses.calls[0].request + assert req.url == create_room_url + assert req.method == 'POST' + j = json.loads(req.body) + assert j["creation_content"]["m.federate"] + + @responses.activate + def test_create_room_federate_false(self): + create_room_url = "http://example.com" \ + "/_matrix/client/r0/createRoom" + responses.add( + responses.POST, + create_room_url, + json='{"room_id": "!sefiuhWgwghwWgh:example.com"}' + ) + self.cli.api.create_room( + name="test", + alias="#test:example.com", + federate=False + ) + req = responses.calls[0].request + assert req.url == create_room_url + assert req.method == 'POST' + j = json.loads(req.body) + assert not j["creation_content"]["m.federate"] + + +class TestWhoamiQuery: + user_id = "@alice:example.com" + token = "Dp0YKRXwx0iWDhFj7lg3DVjwsWzGcUIgARljgyAip2JD8qd5dSaW" \ + "cxowTKEFetPulfLijAhv8eOmUSScyGcWgZyNMRTBmoJ0RFc0HotPvTBZ" \ + "U98yKRLtat7V43aCpFmK" + + @responses.activate + def test_whoami(self): + mapi = api.MatrixHttpApi("http://example.com", token=self.token) + whoami_url = "http://example.com/_matrix/client/r0/account/whoami" + responses.add( + responses.GET, + whoami_url, + body='{"user_id": "%s"}' % self.user_id + ) + mapi.whoami() + req = responses.calls[0].request + assert req.method == 'GET' + assert whoami_url in req.url + + @responses.activate + def test_whoami_unauth(self): + mapi = api.MatrixHttpApi("http://example.com") + whoami_url = "http://example.com/_matrix/client/r0/account/whoami" + responses.add( + responses.GET, + whoami_url, + body='{"user_id": "%s"}' % self.user_id + ) + with pytest.raises(MatrixError): + mapi.whoami() diff --git a/venv/lib/python3.10/site-packages/test/client_test.py b/venv/lib/python3.10/site-packages/test/client_test.py new file mode 100644 index 0000000000000000000000000000000000000000..406082522195352ad34a51cff6180a6ca4314abe --- /dev/null +++ b/venv/lib/python3.10/site-packages/test/client_test.py @@ -0,0 +1,573 @@ +import pytest +import responses +import json +from copy import deepcopy +from matrix_client.client import MatrixClient, Room, User, CACHE +from matrix_client.api import MATRIX_V2_API_PATH +from . import response_examples +try: + from urllib import quote +except ImportError: + from urllib.parse import quote + +HOSTNAME = "http://example.com" + + +def test_create_client(): + MatrixClient("http://example.com") + + +@responses.activate +def test_create_client_with_token(): + user_id = "@alice:example.com" + token = "Dp0YKRXwx0iWDhFj7lg3DVjwsWzGcUIgARljgyAip2JD8qd5dSaW" \ + "cxowTKEFetPulfLijAhv8eOmUSScyGcWgZyNMRTBmoJ0RFc0HotPvTBZ" \ + "U98yKRLtat7V43aCpFmK" + whoami_url = HOSTNAME+MATRIX_V2_API_PATH+"/account/whoami" + responses.add( + responses.GET, + whoami_url, + body='{"user_id": "%s"}' % user_id + ) + sync_response = deepcopy(response_examples.example_sync) + response_body = json.dumps(sync_response) + sync_url = HOSTNAME + MATRIX_V2_API_PATH + "/sync" + responses.add(responses.GET, sync_url, body=response_body) + MatrixClient(HOSTNAME, token=token) + req = responses.calls[0].request + assert req.method == 'GET' + assert whoami_url in req.url + + +def test_sync_token(): + client = MatrixClient("http://example.com") + assert client.get_sync_token() is None + client.set_sync_token("FAKE_TOKEN") + assert client.get_sync_token() == "FAKE_TOKEN" + + +def test__mkroom(): + client = MatrixClient("http://example.com") + + roomId = "!UcYsUzyxTGDxLBEvLz:matrix.org" + goodRoom = client._mkroom(roomId) + + assert isinstance(goodRoom, Room) + assert goodRoom.room_id is roomId + + with pytest.raises(ValueError): + client._mkroom("BAD_ROOM:matrix.org") + client._mkroom("!BAD_ROOMmatrix.org") + client._mkroom("!BAD_ROOM::matrix.org") + + +def test_get_rooms(): + client = MatrixClient("http://example.com") + rooms = client.get_rooms() + assert isinstance(rooms, dict) + assert len(rooms) == 0 + + client = MatrixClient("http://example.com") + + client._mkroom("!abc:matrix.org") + client._mkroom("!def:matrix.org") + client._mkroom("!ghi:matrix.org") + + rooms = client.get_rooms() + assert isinstance(rooms, dict) + assert len(rooms) == 3 + + +def test_bad_state_events(): + client = MatrixClient("http://example.com") + room = client._mkroom("!abc:matrix.org") + + ev = { + "tomato": False + } + + room._process_state_event(ev) + + +def test_state_event(): + client = MatrixClient("http://example.com") + room = client._mkroom("!abc:matrix.org") + + room.name = False + room.topic = False + room.aliases = False + + ev = { + "type": "m.room.name", + "content": {}, + "event_id": "$10000000000000AAAAA:matrix.org" + } + + room._process_state_event(ev) + assert room.name is None + + ev["content"]["name"] = "TestName" + room._process_state_event(ev) + assert room.name == "TestName" + + ev["type"] = "m.room.topic" + room._process_state_event(ev) + assert room.topic is None + + ev["content"]["topic"] = "TestTopic" + room._process_state_event(ev) + assert room.topic == "TestTopic" + + ev["type"] = "m.room.aliases" + room._process_state_event(ev) + assert room.aliases is None + + aliases = ["#foo:matrix.org", "#bar:matrix.org"] + ev["content"]["aliases"] = aliases + room._process_state_event(ev) + assert room.aliases is aliases + + # test member join event + ev["type"] = "m.room.member" + ev["content"] = {'membership': 'join', 'displayname': 'stereo'} + ev["state_key"] = "@stereo:xxx.org" + room._process_state_event(ev) + assert len(room._members) == 1 + assert room._members["@stereo:xxx.org"] + # test member leave event + ev["content"]['membership'] = 'leave' + room._process_state_event(ev) + assert len(room._members) == 0 + + # test join_rules + room.invite_only = False + ev["type"] = "m.room.join_rules" + ev["content"] = {"join_rule": "invite"} + room._process_state_event(ev) + assert room.invite_only + + # test guest_access + room.guest_access = False + ev["type"] = "m.room.guest_access" + ev["content"] = {"guest_access": "can_join"} + room._process_state_event(ev) + assert room.guest_access + + # test malformed event (check does not throw exception) + room.guest_access = False + ev["type"] = "m.room.guest_access" + ev["content"] = {} + room._process_state_event(ev) + assert not room.guest_access + + # test encryption + room.encrypted = False + ev["type"] = "m.room.encryption" + ev["content"] = {"algorithm": "m.megolm.v1.aes-sha2"} + room._process_state_event(ev) + assert room.encrypted + # encrypted flag must not be cleared on configuration change + ev["content"] = {"algorithm": None} + room._process_state_event(ev) + assert room.encrypted + + +def test_get_user(): + client = MatrixClient("http://example.com") + + assert isinstance(client.get_user("@foobar:matrix.org"), User) + + with pytest.raises(ValueError): + client.get_user("badfoobar:matrix.org") + client.get_user("@badfoobarmatrix.org") + client.get_user("@badfoobar:::matrix.org") + + +def test_get_download_url(): + client = MatrixClient("http://example.com") + real_url = "http://example.com/_matrix/media/r0/download/foobar" + assert client.api.get_download_url("mxc://foobar") == real_url + + with pytest.raises(ValueError): + client.api.get_download_url("http://foobar") + + +def test_remove_listener(): + def dummy_listener(): + pass + + client = MatrixClient("http://example.com") + handler = client.add_listener(dummy_listener) + + found_listener = False + for listener in client.listeners: + if listener["uid"] == handler: + found_listener = True + break + + assert found_listener, "listener was not added properly" + + client.remove_listener(handler) + found_listener = False + for listener in client.listeners: + if listener["uid"] == handler: + found_listener = True + break + + assert not found_listener, "listener was not removed properly" + + +class TestClientRegister: + cli = MatrixClient(HOSTNAME) + + @responses.activate + def test_register_as_guest(self): + cli = self.cli + + def _sync(self): + self._sync_called = True + cli.__dict__[_sync.__name__] = _sync.__get__(cli, cli.__class__) + register_guest_url = HOSTNAME + MATRIX_V2_API_PATH + "/register" + response_body = json.dumps({ + 'access_token': 'EXAMPLE_ACCESS_TOKEN', + 'device_id': 'guest_device', + 'home_server': 'example.com', + 'user_id': '@455:example.com' + }) + responses.add(responses.POST, register_guest_url, body=response_body) + cli.register_as_guest() + assert cli.token == cli.api.token == 'EXAMPLE_ACCESS_TOKEN' + assert cli.hs == 'example.com' + assert cli.user_id == '@455:example.com' + assert cli._sync_called + + +def test_get_rooms_display_name(): + + def add_members(api, room, num): + for i in range(num): + room._add_member('@frho%s:matrix.org' % i, 'ho%s' % i) + + client = MatrixClient("http://example.com") + client.user_id = "@frho0:matrix.org" + room1 = client._mkroom("!abc:matrix.org") + add_members(client.api, room1, 1) + room2 = client._mkroom("!def:matrix.org") + add_members(client.api, room2, 2) + room3 = client._mkroom("!ghi:matrix.org") + add_members(client.api, room3, 3) + room4 = client._mkroom("!rfi:matrix.org") + add_members(client.api, room4, 30) + + rooms = client.get_rooms() + assert len(rooms) == 4 + assert room1.display_name == "Empty room" + assert room2.display_name == "ho1" + assert room3.display_name == "ho1 and ho2" + assert room4.display_name == "ho1 and 28 others" + + +@responses.activate +def test_presence_listener(): + client = MatrixClient("http://example.com") + accumulator = [] + + def dummy_callback(event): + accumulator.append(event) + presence_events = [ + { + "content": { + "avatar_url": "mxc://localhost:wefuiwegh8742w", + "currently_active": False, + "last_active_ago": 2478593, + "presence": "online", + "user_id": "@example:localhost" + }, + "event_id": "$WLGTSEFSEF:localhost", + "type": "m.presence" + }, + { + "content": { + "avatar_url": "mxc://localhost:weaugwe742w", + "currently_active": True, + "last_active_ago": 1478593, + "presence": "online", + "user_id": "@example2:localhost" + }, + "event_id": "$CIGTXEFREF:localhost", + "type": "m.presence" + }, + { + "content": { + "avatar_url": "mxc://localhost:wefudweg13742w", + "currently_active": False, + "last_active_ago": 24795, + "presence": "offline", + "user_id": "@example3:localhost" + }, + "event_id": "$ZEGASEDSEF:localhost", + "type": "m.presence" + }, + ] + sync_response = deepcopy(response_examples.example_sync) + sync_response["presence"]["events"] = presence_events + response_body = json.dumps(sync_response) + sync_url = HOSTNAME + MATRIX_V2_API_PATH + "/sync" + + responses.add(responses.GET, sync_url, body=response_body) + callback_uid = client.add_presence_listener(dummy_callback) + client._sync() + assert accumulator == presence_events + + responses.add(responses.GET, sync_url, body=response_body) + client.remove_presence_listener(callback_uid) + accumulator = [] + client._sync() + assert accumulator == [] + + +@responses.activate +def test_changing_user_power_levels(): + client = MatrixClient(HOSTNAME) + room_id = "!UcYsUzyxTGDxLBEvLz:matrix.org" + room = client._mkroom(room_id) + PL_state_path = HOSTNAME + MATRIX_V2_API_PATH + \ + "/rooms/" + quote(room_id) + "/state/m.room.power_levels" + + # Code should first get current power_levels and then modify them + responses.add(responses.GET, PL_state_path, + json=response_examples.example_pl_event["content"]) + responses.add(responses.PUT, PL_state_path, + json=response_examples.example_event_response) + # Removes user from user and adds user to to users list + assert room.modify_user_power_levels(users={"@example:localhost": None, + "@foobar:example.com": 49}) + + expected_request = deepcopy(response_examples.example_pl_event["content"]) + del expected_request["users"]["@example:localhost"] + expected_request["users"]["@foobar:example.com"] = 49 + + assert json.loads(responses.calls[1].request.body) == expected_request + + +@responses.activate +def test_changing_default_power_level(): + client = MatrixClient(HOSTNAME) + room_id = "!UcYsUzyxTGDxLBEvLz:matrix.org" + room = client._mkroom(room_id) + PL_state_path = HOSTNAME + MATRIX_V2_API_PATH + \ + "/rooms/" + quote(room_id) + "/state/m.room.power_levels" + + # Code should first get current power_levels and then modify them + responses.add(responses.GET, PL_state_path, + json=response_examples.example_pl_event["content"]) + responses.add(responses.PUT, PL_state_path, + json=response_examples.example_event_response) + assert room.modify_user_power_levels(users_default=23) + + expected_request = deepcopy(response_examples.example_pl_event["content"]) + expected_request["users_default"] = 23 + + assert json.loads(responses.calls[1].request.body) == expected_request + + +@responses.activate +def test_changing_event_required_power_levels(): + client = MatrixClient(HOSTNAME) + room_id = "!UcYsUzyxTGDxLBEvLz:matrix.org" + room = client._mkroom(room_id) + PL_state_path = HOSTNAME + MATRIX_V2_API_PATH + \ + "/rooms/" + quote(room_id) + "/state/m.room.power_levels" + + # Code should first get current power_levels and then modify them + responses.add(responses.GET, PL_state_path, + json=response_examples.example_pl_event["content"]) + responses.add(responses.PUT, PL_state_path, + json=response_examples.example_event_response) + # Remove event from events and adds new controlled event + assert room.modify_required_power_levels(events={"m.room.name": None, + "example.event": 51}) + + expected_request = deepcopy(response_examples.example_pl_event["content"]) + del expected_request["events"]["m.room.name"] + expected_request["events"]["example.event"] = 51 + + assert json.loads(responses.calls[1].request.body) == expected_request + + +@responses.activate +def test_changing_other_required_power_levels(): + client = MatrixClient(HOSTNAME) + room_id = "!UcYsUzyxTGDxLBEvLz:matrix.org" + room = client._mkroom(room_id) + PL_state_path = HOSTNAME + MATRIX_V2_API_PATH + \ + "/rooms/" + quote(room_id) + "/state/m.room.power_levels" + + # Code should first get current power_levels and then modify them + responses.add(responses.GET, PL_state_path, + json=response_examples.example_pl_event["content"]) + responses.add(responses.PUT, PL_state_path, + json=response_examples.example_event_response) + # Remove event from events and adds new controlled event + assert room.modify_required_power_levels(kick=53, redact=2, + state_default=None) + + expected_request = deepcopy(response_examples.example_pl_event["content"]) + expected_request["kick"] = 53 + expected_request["redact"] = 2 + del expected_request["state_default"] + + assert json.loads(responses.calls[1].request.body) == expected_request + + +@responses.activate +def test_cache(): + m_none = MatrixClient("http://example.com", cache_level=CACHE.NONE) + m_some = MatrixClient("http://example.com", cache_level=CACHE.SOME) + m_all = MatrixClient("http://example.com", cache_level=CACHE.ALL) + sync_url = HOSTNAME + MATRIX_V2_API_PATH + "/sync" + room_id = "!726s6s6q:example.com" + room_name = "The FooBar" + sync_response = deepcopy(response_examples.example_sync) + + with pytest.raises(ValueError): + MatrixClient("http://example.com", cache_level=1) + MatrixClient("http://example.com", cache_level=5) + MatrixClient("http://example.com", cache_level=0.5) + MatrixClient("http://example.com", cache_level=-5) + MatrixClient("http://example.com", cache_level="foo") + MatrixClient("http://example.com", cache_level=0.0) + + sync_response["rooms"]["join"][room_id]["state"]["events"].append( + { + "sender": "@alice:example.com", + "type": "m.room.name", + "state_key": "", + "content": {"name": room_name}, + } + ) + + responses.add(responses.GET, sync_url, json.dumps(sync_response)) + m_none._sync() + responses.add(responses.GET, sync_url, json.dumps(sync_response)) + m_some._sync() + responses.add(responses.GET, sync_url, json.dumps(sync_response)) + m_all._sync() + + assert m_none.rooms[room_id].name is None + assert m_some.rooms[room_id].name == room_name + assert m_all.rooms[room_id].name == room_name + + assert m_none.rooms[room_id]._members == m_some.rooms[room_id]._members == {} + assert len(m_all.rooms[room_id]._members) == 2 + assert m_all.rooms[room_id]._members["@alice:example.com"] + + +@responses.activate +def test_room_join_rules(): + client = MatrixClient(HOSTNAME) + room_id = "!UcYsUzyxTGDxLBEvLz:matrix.org" + room = client._mkroom(room_id) + assert room.invite_only is None + join_rules_state_path = HOSTNAME + MATRIX_V2_API_PATH + \ + "/rooms/" + quote(room_id) + "/state/m.room.join_rules" + + responses.add(responses.PUT, join_rules_state_path, + json=response_examples.example_event_response) + + assert room.set_invite_only(True) + assert room.invite_only + + +@responses.activate +def test_room_guest_access(): + client = MatrixClient(HOSTNAME) + room_id = "!UcYsUzyxTGDxLBEvLz:matrix.org" + room = client._mkroom(room_id) + assert room.guest_access is None + guest_access_state_path = HOSTNAME + MATRIX_V2_API_PATH + \ + "/rooms/" + quote(room_id) + "/state/m.room.guest_access" + + responses.add(responses.PUT, guest_access_state_path, + json=response_examples.example_event_response) + + assert room.set_guest_access(True) + assert room.guest_access + + +@responses.activate +def test_enable_encryption(): + pytest.importorskip('olm') + client = MatrixClient(HOSTNAME, encryption=True) + + login_path = HOSTNAME + MATRIX_V2_API_PATH + "/login" + responses.add(responses.POST, login_path, + json=response_examples.example_success_login_response) + + upload_path = HOSTNAME + MATRIX_V2_API_PATH + '/keys/upload' + responses.add(responses.POST, upload_path, body='{"one_time_key_counts": {}}') + + client.login("@example:localhost", "password", sync=False) + + assert client.olm_device + + +@responses.activate +def test_enable_encryption_in_room(): + pytest.importorskip('olm') + client = MatrixClient(HOSTNAME) + room_id = "!UcYsUzyxTGDxLBEvLz:matrix.org" + room = client._mkroom(room_id) + assert not room.encrypted + encryption_state_path = HOSTNAME + MATRIX_V2_API_PATH + \ + "/rooms/" + quote(room_id) + "/state/m.room.encryption" + + responses.add(responses.PUT, encryption_state_path, + json=response_examples.example_event_response) + + assert room.enable_encryption() + assert room.encrypted + + +@responses.activate +def test_detect_encryption_state(): + pytest.importorskip('olm') + client = MatrixClient(HOSTNAME, encryption=True) + room_id = "!UcYsUzyxTGDxLBEvLz:matrix.org" + + encryption_state_path = HOSTNAME + MATRIX_V2_API_PATH + \ + "/rooms/" + quote(room_id) + "/state/m.room.encryption" + responses.add(responses.GET, encryption_state_path, + json={"algorithm": "m.megolm.v1.aes-sha2"}) + responses.add(responses.GET, encryption_state_path, + json={}, status=404) + + room = client._mkroom(room_id) + assert room.encrypted + + room = client._mkroom(room_id) + assert not room.encrypted + + +@responses.activate +def test_one_time_keys_sync(): + pytest.importorskip('olm') + client = MatrixClient(HOSTNAME, encryption=True) + sync_url = HOSTNAME + MATRIX_V2_API_PATH + "/sync" + sync_response = deepcopy(response_examples.example_sync) + payload = {'dummy': 1} + sync_response["device_one_time_keys_count"] = payload + sync_response['rooms']['join'] = {} + + class DummyDevice: + + def update_one_time_key_counts(self, payload): + self.payload = payload + + device = DummyDevice() + client.olm_device = device + + responses.add(responses.GET, sync_url, json=sync_response) + + client._sync() + assert device.payload == payload diff --git a/venv/lib/python3.10/site-packages/test/response_examples.py b/venv/lib/python3.10/site-packages/test/response_examples.py new file mode 100644 index 0000000000000000000000000000000000000000..2d45aa8612698a0da45fe6f7d8d72760288e8f7d --- /dev/null +++ b/venv/lib/python3.10/site-packages/test/response_examples.py @@ -0,0 +1,186 @@ +example_sync = { + "next_batch": "s72595_4483_1934", + "presence": { + "events": [ + { + "sender": "@alice:example.com", + "type": "m.presence", + "content": { + "presence": "online" + } + } + ] + }, + "account_data": { + "events": [ + { + "type": "org.example.custom.config", + "content": { + "custom_config_key": "custom_config_value" + } + } + ] + }, + "rooms": { + "join": { + "!726s6s6q:example.com": { + "state": { + "events": [ + { + "sender": "@alice:example.com", + "type": "m.room.member", + "state_key": "@alice:example.com", + "content": { + "membership": "join" + }, + "origin_server_ts": 1417731086795, + "event_id": "$66697273743031:example.com" + } + ] + }, + "timeline": { + "events": [ + { + "sender": "@bob:example.com", + "type": "m.room.member", + "state_key": "@bob:example.com", + "content": { + "membership": "join" + }, + "prev_content": { + "membership": "invite" + }, + "origin_server_ts": 1417731086795, + "event_id": "$7365636s6r6432:example.com" + }, + { + "sender": "@alice:example.com", + "type": "m.room.message", + "age": 124524, + "txn_id": "1234", + "content": { + "body": "I am a fish", + "msgtype": "m.text" + }, + "origin_server_ts": 1417731086797, + "event_id": "$74686972643033:example.com" + } + ], + "limited": True, + "prev_batch": "t34-23535_0_0" + }, + "ephemeral": { + "events": [ + { + "type": "m.typing", + "content": { + "user_ids": [ + "@alice:example.com" + ] + } + } + ] + }, + "account_data": { + "events": [ + { + "type": "m.tag", + "content": { + "tags": { + "work": { + "order": 1 + } + } + } + }, + { + "type": "org.example.custom.room.config", + "content": { + "custom_config_key": "custom_config_value" + } + } + ] + } + } + }, + "invite": { + "!696r7674:example.com": { + "invite_state": { + "events": [ + { + "sender": "@alice:example.com", + "type": "m.room.name", + "state_key": "", + "content": { + "name": "My Room Name" + } + }, + { + "sender": "@alice:example.com", + "type": "m.room.member", + "state_key": "@bob:example.com", + "content": { + "membership": "invite" + } + } + ] + } + } + }, + "leave": {} + } +} + +example_pl_event = { + "age": 242352, + "content": { + "ban": 50, + "events": { + "m.room.name": 100, + "m.room.power_levels": 100 + }, + "events_default": 0, + "invite": 50, + "kick": 50, + "redact": 50, + "state_default": 50, + "users": { + "@example:localhost": 100 + }, + "users_default": 0 + }, + "event_id": "$WLGTSEFSEF:localhost", + "origin_server_ts": 1431961217939, + "room_id": "!Cuyf34gef24t:localhost", + "sender": "@example:localhost", + "state_key": "", + "type": "m.room.power_levels" +} + +example_event_response = { + "event_id": "YUwRidLecu" +} + +example_key_upload_response = { + "one_time_key_counts": { + "curve25519": 10, + "signed_curve25519": 20 + } +} + +example_success_login_response = { + "user_id": "@example:localhost", + "access_token": "abc123", + "home_server": "matrix.org", + "device_id": "GHTYAJCE" +} + +example_preview_url = { + "matrix:image:size": 102400, + "og:description": "This is a really cool blog post from matrix.org", + "og:image": "mxc://example.com/ascERGshawAWawugaAcauga", + "og:image:height": 48, + "og:image:type": "image/png", + "og:image:width": 48, + "og:title": "Matrix Blog Post" +} diff --git a/venv/lib/python3.10/site-packages/test/user_test.py b/venv/lib/python3.10/site-packages/test/user_test.py new file mode 100644 index 0000000000000000000000000000000000000000..db5bae820c2331294d3627d80cae678e64478e7d --- /dev/null +++ b/venv/lib/python3.10/site-packages/test/user_test.py @@ -0,0 +1,52 @@ +import pytest +import responses + +from matrix_client.api import MATRIX_V2_API_PATH +from matrix_client.client import MatrixClient +from matrix_client.user import User + +HOSTNAME = "http://localhost" + + +class TestUser: + cli = MatrixClient(HOSTNAME) + user_id = "@test:localhost" + room_id = "!test:localhost" + + @pytest.fixture() + def user(self): + return User(self.cli.api, self.user_id) + + @pytest.fixture() + def room(self): + return self.cli._mkroom(self.room_id) + + @responses.activate + def test_get_display_name(self, user, room): + displayname_url = HOSTNAME + MATRIX_V2_API_PATH + \ + "/profile/{}/displayname".format(user.user_id) + displayname = 'test' + room_displayname = 'room_test' + + # No displayname + assert user.get_display_name(room) == user.user_id + responses.add(responses.GET, displayname_url, json={}) + assert user.get_display_name() == user.user_id + assert len(responses.calls) == 1 + + # Get global displayname + responses.replace(responses.GET, displayname_url, + json={"displayname": displayname}) + assert user.get_display_name() == displayname + assert len(responses.calls) == 2 + + # Global displayname already present + assert user.get_display_name() == displayname + # No new request + assert len(responses.calls) == 2 + + # Per-room displayname + room.members_displaynames[user.user_id] = room_displayname + assert user.get_display_name(room) == room_displayname + # No new request + assert len(responses.calls) == 2 diff --git a/venv/lib/python3.10/site-packages/typer/_typing.py b/venv/lib/python3.10/site-packages/typer/_typing.py new file mode 100644 index 0000000000000000000000000000000000000000..c4b3f7c8dd6d8692f41d9d28dce3fec2627617b6 --- /dev/null +++ b/venv/lib/python3.10/site-packages/typer/_typing.py @@ -0,0 +1,113 @@ +# Copied from pydantic 1.9.2 (the latest version to support python 3.6.) +# https://github.com/pydantic/pydantic/blob/v1.9.2/pydantic/typing.py +# Reduced drastically to only include Typer-specific 3.7+ functionality +# mypy: ignore-errors + +import sys +from typing import ( + Any, + Callable, + Optional, + Tuple, + Type, + Union, +) + +if sys.version_info >= (3, 9): + from typing import Annotated, Literal, get_args, get_origin, get_type_hints +else: + from typing_extensions import ( + Annotated, + Literal, + get_args, + get_origin, + get_type_hints, + ) + +if sys.version_info < (3, 10): + + def is_union(tp: Optional[Type[Any]]) -> bool: + return tp is Union + +else: + import types + + def is_union(tp: Optional[Type[Any]]) -> bool: + return tp is Union or tp is types.UnionType # noqa: E721 + + +__all__ = ( + "NoneType", + "is_none_type", + "is_callable_type", + "is_literal_type", + "all_literal_values", + "is_union", + "Annotated", + "Literal", + "get_args", + "get_origin", + "get_type_hints", +) + + +NoneType = None.__class__ + + +NONE_TYPES: Tuple[Any, Any, Any] = (None, NoneType, Literal[None]) + + +if sys.version_info < (3, 8): + # Even though this implementation is slower, we need it for python 3.7: + # In python 3.7 "Literal" is not a builtin type and uses a different + # mechanism. + # for this reason `Literal[None] is Literal[None]` evaluates to `False`, + # breaking the faster implementation used for the other python versions. + + def is_none_type(type_: Any) -> bool: + return type_ in NONE_TYPES + +elif sys.version_info[:2] == (3, 8): + # We can use the fast implementation for 3.8 but there is a very weird bug + # where it can fail for `Literal[None]`. + # We just need to redefine a useless `Literal[None]` inside the function body to fix this + + def is_none_type(type_: Any) -> bool: + Literal[None] # fix edge case + for none_type in NONE_TYPES: + if type_ is none_type: + return True + return False + +else: + + def is_none_type(type_: Any) -> bool: + for none_type in NONE_TYPES: + if type_ is none_type: + return True + return False + + +def is_callable_type(type_: Type[Any]) -> bool: + return type_ is Callable or get_origin(type_) is Callable + + +def is_literal_type(type_: Type[Any]) -> bool: + return Literal is not None and get_origin(type_) is Literal + + +def literal_values(type_: Type[Any]) -> Tuple[Any, ...]: + return get_args(type_) + + +def all_literal_values(type_: Type[Any]) -> Tuple[Any, ...]: + """ + This method is used to retrieve all Literal values as + Literal can be used recursively (see https://www.python.org/dev/peps/pep-0586) + e.g. `Literal[Literal[Literal[1, 2, 3], "foo"], 5, None]` + """ + if not is_literal_type(type_): + return (type_,) + + values = literal_values(type_) + return tuple(x for value in values for x in all_literal_values(value)) diff --git a/venv/lib/python3.10/site-packages/typer/colors.py b/venv/lib/python3.10/site-packages/typer/colors.py new file mode 100644 index 0000000000000000000000000000000000000000..54e7b166cb1de83321a4965cc4915824b47a7f4f --- /dev/null +++ b/venv/lib/python3.10/site-packages/typer/colors.py @@ -0,0 +1,20 @@ +# Variable names to colors, just for completion +BLACK = "black" +RED = "red" +GREEN = "green" +YELLOW = "yellow" +BLUE = "blue" +MAGENTA = "magenta" +CYAN = "cyan" +WHITE = "white" + +RESET = "reset" + +BRIGHT_BLACK = "bright_black" +BRIGHT_RED = "bright_red" +BRIGHT_GREEN = "bright_green" +BRIGHT_YELLOW = "bright_yellow" +BRIGHT_BLUE = "bright_blue" +BRIGHT_MAGENTA = "bright_magenta" +BRIGHT_CYAN = "bright_cyan" +BRIGHT_WHITE = "bright_white" diff --git a/venv/lib/python3.10/site-packages/typer/completion.py b/venv/lib/python3.10/site-packages/typer/completion.py new file mode 100644 index 0000000000000000000000000000000000000000..c355baa78182a6e7a6ba692db660fca162ef79f9 --- /dev/null +++ b/venv/lib/python3.10/site-packages/typer/completion.py @@ -0,0 +1,149 @@ +import os +import sys +from typing import Any, MutableMapping, Tuple + +import click + +from ._completion_classes import completion_init +from ._completion_shared import Shells, get_completion_script, install +from .models import ParamMeta +from .params import Option +from .utils import get_params_from_function + +try: + import shellingham +except ImportError: # pragma: no cover + shellingham = None + + +_click_patched = False + + +def get_completion_inspect_parameters() -> Tuple[ParamMeta, ParamMeta]: + completion_init() + test_disable_detection = os.getenv("_TYPER_COMPLETE_TEST_DISABLE_SHELL_DETECTION") + if shellingham and not test_disable_detection: + parameters = get_params_from_function(_install_completion_placeholder_function) + else: + parameters = get_params_from_function( + _install_completion_no_auto_placeholder_function + ) + install_param, show_param = parameters.values() + return install_param, show_param + + +def install_callback(ctx: click.Context, param: click.Parameter, value: Any) -> Any: + if not value or ctx.resilient_parsing: + return value # pragma: no cover + if isinstance(value, str): + shell, path = install(shell=value) + else: + shell, path = install() + click.secho(f"{shell} completion installed in {path}", fg="green") + click.echo("Completion will take effect once you restart the terminal") + sys.exit(0) + + +def show_callback(ctx: click.Context, param: click.Parameter, value: Any) -> Any: + if not value or ctx.resilient_parsing: + return value # pragma: no cover + prog_name = ctx.find_root().info_name + assert prog_name + complete_var = "_{}_COMPLETE".format(prog_name.replace("-", "_").upper()) + shell = "" + test_disable_detection = os.getenv("_TYPER_COMPLETE_TEST_DISABLE_SHELL_DETECTION") + if isinstance(value, str): + shell = value + elif shellingham and not test_disable_detection: + shell, _ = shellingham.detect_shell() + script_content = get_completion_script( + prog_name=prog_name, complete_var=complete_var, shell=shell + ) + click.echo(script_content) + sys.exit(0) + + +# Create a fake command function to extract the completion parameters +def _install_completion_placeholder_function( + install_completion: bool = Option( + None, + "--install-completion", + callback=install_callback, + expose_value=False, + help="Install completion for the current shell.", + ), + show_completion: bool = Option( + None, + "--show-completion", + callback=show_callback, + expose_value=False, + help="Show completion for the current shell, to copy it or customize the installation.", + ), +) -> Any: + pass # pragma: no cover + + +def _install_completion_no_auto_placeholder_function( + install_completion: Shells = Option( + None, + callback=install_callback, + expose_value=False, + help="Install completion for the specified shell.", + ), + show_completion: Shells = Option( + None, + callback=show_callback, + expose_value=False, + help="Show completion for the specified shell, to copy it or customize the installation.", + ), +) -> Any: + pass # pragma: no cover + + +# Re-implement Click's shell_complete to add error message with: +# Invalid completion instruction +# To use 7.x instruction style for compatibility +# And to add extra error messages, for compatibility with Typer in previous versions +# This is only called in new Command method, only used by Click 8.x+ +def shell_complete( + cli: click.Command, + ctx_args: MutableMapping[str, Any], + prog_name: str, + complete_var: str, + instruction: str, +) -> int: + import click + import click.shell_completion + + if "_" not in instruction: + click.echo("Invalid completion instruction.", err=True) + return 1 + + # Click 8 changed the order/style of shell instructions from e.g. + # source_bash to bash_source + # Typer override to preserve the old style for compatibility + # Original in Click 8.x commented: + # shell, _, instruction = instruction.partition("_") + instruction, _, shell = instruction.partition("_") + # Typer override end + + comp_cls = click.shell_completion.get_completion_class(shell) + + if comp_cls is None: + click.echo(f"Shell {shell} not supported.", err=True) + return 1 + + comp = comp_cls(cli, ctx_args, prog_name, complete_var) + + if instruction == "source": + click.echo(comp.source()) + return 0 + + # Typer override to print the completion help msg with Rich + if instruction == "complete": + click.echo(comp.complete()) + return 0 + # Typer override end + + click.echo(f'Completion instruction "{instruction}" not supported.', err=True) + return 1 diff --git a/venv/lib/python3.10/site-packages/typer/core.py b/venv/lib/python3.10/site-packages/typer/core.py new file mode 100644 index 0000000000000000000000000000000000000000..f6c4f72e8a5bd2ab8d117f87895b9d1200cb6e9a --- /dev/null +++ b/venv/lib/python3.10/site-packages/typer/core.py @@ -0,0 +1,781 @@ +import errno +import inspect +import os +import sys +from enum import Enum +from gettext import gettext as _ +from typing import ( + Any, + Callable, + Dict, + List, + MutableMapping, + Optional, + Sequence, + TextIO, + Tuple, + Union, + cast, +) + +import click +import click.core +import click.formatting +import click.parser +import click.shell_completion +import click.types +import click.utils + +from ._typing import Literal + +MarkupMode = Literal["markdown", "rich", None] + +try: + import rich + + from . import rich_utils + + DEFAULT_MARKUP_MODE: MarkupMode = "rich" + +except ImportError: # pragma: no cover + rich = None # type: ignore + DEFAULT_MARKUP_MODE = None + + +# Copy from click.parser._split_opt +def _split_opt(opt: str) -> Tuple[str, str]: + first = opt[:1] + if first.isalnum(): + return "", opt + if opt[1:2] == first: + return opt[:2], opt[2:] + return first, opt[1:] + + +def _typer_param_setup_autocompletion_compat( + self: click.Parameter, + *, + autocompletion: Optional[ + Callable[[click.Context, List[str], str], List[Union[Tuple[str, str], str]]] + ] = None, +) -> None: + if self._custom_shell_complete is not None: + import warnings + + warnings.warn( + "In Typer, only the parameter 'autocompletion' is supported. " + "The support for 'shell_complete' is deprecated and will be removed in upcoming versions. ", + DeprecationWarning, + stacklevel=2, + ) + + if autocompletion is not None: + + def compat_autocompletion( + ctx: click.Context, param: click.core.Parameter, incomplete: str + ) -> List["click.shell_completion.CompletionItem"]: + from click.shell_completion import CompletionItem + + out = [] + + for c in autocompletion(ctx, [], incomplete): + if isinstance(c, tuple): + use_completion = CompletionItem(c[0], help=c[1]) + else: + assert isinstance(c, str) + use_completion = CompletionItem(c) + + if use_completion.value.startswith(incomplete): + out.append(use_completion) + + return out + + self._custom_shell_complete = compat_autocompletion + + +def _get_default_string( + obj: Union["TyperArgument", "TyperOption"], + *, + ctx: click.Context, + show_default_is_str: bool, + default_value: Union[List[Any], Tuple[Any, ...], str, Callable[..., Any], Any], +) -> str: + # Extracted from click.core.Option.get_help_record() to be reused by + # rich_utils avoiding RegEx hacks + if show_default_is_str: + default_string = f"({obj.show_default})" + elif isinstance(default_value, (list, tuple)): + default_string = ", ".join( + _get_default_string( + obj, ctx=ctx, show_default_is_str=show_default_is_str, default_value=d + ) + for d in default_value + ) + elif isinstance(default_value, Enum): + default_string = str(default_value.value) + elif inspect.isfunction(default_value): + default_string = _("(dynamic)") + elif isinstance(obj, TyperOption) and obj.is_bool_flag and obj.secondary_opts: + # For boolean flags that have distinct True/False opts, + # use the opt without prefix instead of the value. + # Typer override, original commented + # default_string = click.parser.split_opt( + # (self.opts if self.default else self.secondary_opts)[0] + # )[1] + if obj.default: + if obj.opts: + default_string = _split_opt(obj.opts[0])[1] + else: + default_string = str(default_value) + else: + default_string = _split_opt(obj.secondary_opts[0])[1] + # Typer override end + elif ( + isinstance(obj, TyperOption) + and obj.is_bool_flag + and not obj.secondary_opts + and not default_value + ): + default_string = "" + else: + default_string = str(default_value) + return default_string + + +def _extract_default_help_str( + obj: Union["TyperArgument", "TyperOption"], *, ctx: click.Context +) -> Optional[Union[Any, Callable[[], Any]]]: + # Extracted from click.core.Option.get_help_record() to be reused by + # rich_utils avoiding RegEx hacks + # Temporarily enable resilient parsing to avoid type casting + # failing for the default. Might be possible to extend this to + # help formatting in general. + resilient = ctx.resilient_parsing + ctx.resilient_parsing = True + + try: + default_value = obj.get_default(ctx, call=False) + finally: + ctx.resilient_parsing = resilient + return default_value + + +def _main( + self: click.Command, + *, + args: Optional[Sequence[str]] = None, + prog_name: Optional[str] = None, + complete_var: Optional[str] = None, + standalone_mode: bool = True, + windows_expand_args: bool = True, + rich_markup_mode: MarkupMode = DEFAULT_MARKUP_MODE, + **extra: Any, +) -> Any: + # Typer override, duplicated from click.main() to handle custom rich exceptions + # Verify that the environment is configured correctly, or reject + # further execution to avoid a broken script. + if args is None: + args = sys.argv[1:] + + # Covered in Click tests + if os.name == "nt" and windows_expand_args: # pragma: no cover + args = click.utils._expand_args(args) + else: + args = list(args) + + if prog_name is None: + prog_name = click.utils._detect_program_name() + + # Process shell completion requests and exit early. + self._main_shell_completion(extra, prog_name, complete_var) + + try: + try: + with self.make_context(prog_name, args, **extra) as ctx: + rv = self.invoke(ctx) + if not standalone_mode: + return rv + # it's not safe to `ctx.exit(rv)` here! + # note that `rv` may actually contain data like "1" which + # has obvious effects + # more subtle case: `rv=[None, None]` can come out of + # chained commands which all returned `None` -- so it's not + # even always obvious that `rv` indicates success/failure + # by its truthiness/falsiness + ctx.exit() + except EOFError as e: + click.echo(file=sys.stderr) + raise click.Abort() from e + except KeyboardInterrupt as e: + raise click.exceptions.Exit(130) from e + except click.ClickException as e: + if not standalone_mode: + raise + # Typer override + if rich and rich_markup_mode is not None: + rich_utils.rich_format_error(e) + else: + e.show() + # Typer override end + sys.exit(e.exit_code) + except OSError as e: + if e.errno == errno.EPIPE: + sys.stdout = cast(TextIO, click.utils.PacifyFlushWrapper(sys.stdout)) + sys.stderr = cast(TextIO, click.utils.PacifyFlushWrapper(sys.stderr)) + sys.exit(1) + else: + raise + except click.exceptions.Exit as e: + if standalone_mode: + sys.exit(e.exit_code) + else: + # in non-standalone mode, return the exit code + # note that this is only reached if `self.invoke` above raises + # an Exit explicitly -- thus bypassing the check there which + # would return its result + # the results of non-standalone execution may therefore be + # somewhat ambiguous: if there are codepaths which lead to + # `ctx.exit(1)` and to `return 1`, the caller won't be able to + # tell the difference between the two + return e.exit_code + except click.Abort: + if not standalone_mode: + raise + # Typer override + if rich and rich_markup_mode is not None: + rich_utils.rich_abort_error() + else: + click.echo(_("Aborted!"), file=sys.stderr) + # Typer override end + sys.exit(1) + + +class TyperArgument(click.core.Argument): + def __init__( + self, + *, + # Parameter + param_decls: List[str], + type: Optional[Any] = None, + required: Optional[bool] = None, + default: Optional[Any] = None, + callback: Optional[Callable[..., Any]] = None, + nargs: Optional[int] = None, + metavar: Optional[str] = None, + expose_value: bool = True, + is_eager: bool = False, + envvar: Optional[Union[str, List[str]]] = None, + # Note that shell_complete is not fully supported and will be removed in future versions + # TODO: Remove shell_complete in a future version (after 0.16.0) + shell_complete: Optional[ + Callable[ + [click.Context, click.Parameter, str], + Union[List["click.shell_completion.CompletionItem"], List[str]], + ] + ] = None, + autocompletion: Optional[Callable[..., Any]] = None, + # TyperArgument + show_default: Union[bool, str] = True, + show_choices: bool = True, + show_envvar: bool = True, + help: Optional[str] = None, + hidden: bool = False, + # Rich settings + rich_help_panel: Union[str, None] = None, + ): + self.help = help + self.show_default = show_default + self.show_choices = show_choices + self.show_envvar = show_envvar + self.hidden = hidden + self.rich_help_panel = rich_help_panel + + super().__init__( + param_decls=param_decls, + type=type, + required=required, + default=default, + callback=callback, + nargs=nargs, + metavar=metavar, + expose_value=expose_value, + is_eager=is_eager, + envvar=envvar, + shell_complete=shell_complete, + ) + _typer_param_setup_autocompletion_compat(self, autocompletion=autocompletion) + + def _get_default_string( + self, + *, + ctx: click.Context, + show_default_is_str: bool, + default_value: Union[List[Any], Tuple[Any, ...], str, Callable[..., Any], Any], + ) -> str: + return _get_default_string( + self, + ctx=ctx, + show_default_is_str=show_default_is_str, + default_value=default_value, + ) + + def _extract_default_help_str( + self, *, ctx: click.Context + ) -> Optional[Union[Any, Callable[[], Any]]]: + return _extract_default_help_str(self, ctx=ctx) + + def get_help_record(self, ctx: click.Context) -> Optional[Tuple[str, str]]: + # Modified version of click.core.Option.get_help_record() + # to support Arguments + if self.hidden: + return None + name = self.make_metavar(ctx=ctx) + help = self.help or "" + extra = [] + if self.show_envvar: + envvar = self.envvar + # allow_from_autoenv is currently not supported in Typer for CLI Arguments + if envvar is not None: + var_str = ( + ", ".join(str(d) for d in envvar) + if isinstance(envvar, (list, tuple)) + else envvar + ) + extra.append(f"env var: {var_str}") + + # Typer override: + # Extracted to _extract_default_help_str() to allow re-using it in rich_utils + default_value = self._extract_default_help_str(ctx=ctx) + # Typer override end + + show_default_is_str = isinstance(self.show_default, str) + + if show_default_is_str or ( + default_value is not None and (self.show_default or ctx.show_default) + ): + # Typer override: + # Extracted to _get_default_string() to allow re-using it in rich_utils + default_string = self._get_default_string( + ctx=ctx, + show_default_is_str=show_default_is_str, + default_value=default_value, + ) + # Typer override end + if default_string: + extra.append(_("default: {default}").format(default=default_string)) + if self.required: + extra.append(_("required")) + if extra: + extra_str = "; ".join(extra) + extra_str = f"[{extra_str}]" + if rich is not None: + # This is needed for when we want to export to HTML + extra_str = rich.markup.escape(extra_str).strip() + + help = f"{help} {extra_str}" if help else f"{extra_str}" + return name, help + + def make_metavar(self, ctx: Union[click.Context, None] = None) -> str: + # Modified version of click.core.Argument.make_metavar() + # to include Argument name + if self.metavar is not None: + return self.metavar + var = (self.name or "").upper() + if not self.required: + var = f"[{var}]" + # TODO: When deprecating Click < 8.2, remove this + signature = inspect.signature(self.type.get_metavar) + if "ctx" in signature.parameters: + # Click >= 8.2 + type_var = self.type.get_metavar(self, ctx=ctx) # type: ignore[arg-type] + else: + # Click < 8.2 + type_var = self.type.get_metavar(self) # type: ignore[call-arg] + # TODO: /When deprecating Click < 8.2, remove this, uncomment the line below + # type_var = self.type.get_metavar(self, ctx=ctx) + if type_var: + var += f":{type_var}" + if self.nargs != 1: + var += "..." + return var + + +class TyperOption(click.core.Option): + def __init__( + self, + *, + # Parameter + param_decls: List[str], + type: Optional[Union[click.types.ParamType, Any]] = None, + required: Optional[bool] = None, + default: Optional[Any] = None, + callback: Optional[Callable[..., Any]] = None, + nargs: Optional[int] = None, + metavar: Optional[str] = None, + expose_value: bool = True, + is_eager: bool = False, + envvar: Optional[Union[str, List[str]]] = None, + # Note that shell_complete is not fully supported and will be removed in future versions + # TODO: Remove shell_complete in a future version (after 0.16.0) + shell_complete: Optional[ + Callable[ + [click.Context, click.Parameter, str], + Union[List["click.shell_completion.CompletionItem"], List[str]], + ] + ] = None, + autocompletion: Optional[Callable[..., Any]] = None, + # Option + show_default: Union[bool, str] = False, + prompt: Union[bool, str] = False, + confirmation_prompt: Union[bool, str] = False, + prompt_required: bool = True, + hide_input: bool = False, + is_flag: Optional[bool] = None, + multiple: bool = False, + count: bool = False, + allow_from_autoenv: bool = True, + help: Optional[str] = None, + hidden: bool = False, + show_choices: bool = True, + show_envvar: bool = False, + # Rich settings + rich_help_panel: Union[str, None] = None, + ): + super().__init__( + param_decls=param_decls, + type=type, + required=required, + default=default, + callback=callback, + nargs=nargs, + metavar=metavar, + expose_value=expose_value, + is_eager=is_eager, + envvar=envvar, + show_default=show_default, + prompt=prompt, + confirmation_prompt=confirmation_prompt, + hide_input=hide_input, + is_flag=is_flag, + multiple=multiple, + count=count, + allow_from_autoenv=allow_from_autoenv, + help=help, + hidden=hidden, + show_choices=show_choices, + show_envvar=show_envvar, + prompt_required=prompt_required, + shell_complete=shell_complete, + ) + _typer_param_setup_autocompletion_compat(self, autocompletion=autocompletion) + self.rich_help_panel = rich_help_panel + + def _get_default_string( + self, + *, + ctx: click.Context, + show_default_is_str: bool, + default_value: Union[List[Any], Tuple[Any, ...], str, Callable[..., Any], Any], + ) -> str: + return _get_default_string( + self, + ctx=ctx, + show_default_is_str=show_default_is_str, + default_value=default_value, + ) + + def _extract_default_help_str( + self, *, ctx: click.Context + ) -> Optional[Union[Any, Callable[[], Any]]]: + return _extract_default_help_str(self, ctx=ctx) + + def make_metavar(self, ctx: Union[click.Context, None] = None) -> str: + signature = inspect.signature(super().make_metavar) + if "ctx" in signature.parameters: + # Click >= 8.2 + return super().make_metavar(ctx=ctx) # type: ignore[arg-type] + # Click < 8.2 + return super().make_metavar() # type: ignore[call-arg] + + def get_help_record(self, ctx: click.Context) -> Optional[Tuple[str, str]]: + # Duplicate all of Click's logic only to modify a single line, to allow boolean + # flags with only names for False values as it's currently supported by Typer + # Ref: https://typer.tiangolo.com/tutorial/parameter-types/bool/#only-names-for-false + if self.hidden: + return None + + any_prefix_is_slash = False + + def _write_opts(opts: Sequence[str]) -> str: + nonlocal any_prefix_is_slash + + rv, any_slashes = click.formatting.join_options(opts) + + if any_slashes: + any_prefix_is_slash = True + + if not self.is_flag and not self.count: + rv += f" {self.make_metavar(ctx=ctx)}" + + return rv + + rv = [_write_opts(self.opts)] + + if self.secondary_opts: + rv.append(_write_opts(self.secondary_opts)) + + help = self.help or "" + extra = [] + + if self.show_envvar: + envvar = self.envvar + + if envvar is None: + if ( + self.allow_from_autoenv + and ctx.auto_envvar_prefix is not None + and self.name is not None + ): + envvar = f"{ctx.auto_envvar_prefix}_{self.name.upper()}" + + if envvar is not None: + var_str = ( + envvar + if isinstance(envvar, str) + else ", ".join(str(d) for d in envvar) + ) + extra.append(_("env var: {var}").format(var=var_str)) + + # Typer override: + # Extracted to _extract_default() to allow re-using it in rich_utils + default_value = self._extract_default_help_str(ctx=ctx) + # Typer override end + + show_default_is_str = isinstance(self.show_default, str) + + if show_default_is_str or ( + default_value is not None and (self.show_default or ctx.show_default) + ): + # Typer override: + # Extracted to _get_default_string() to allow re-using it in rich_utils + default_string = self._get_default_string( + ctx=ctx, + show_default_is_str=show_default_is_str, + default_value=default_value, + ) + # Typer override end + if default_string: + extra.append(_("default: {default}").format(default=default_string)) + + if isinstance(self.type, click.types._NumberRangeBase): + range_str = self.type._describe_range() + + if range_str: + extra.append(range_str) + + if self.required: + extra.append(_("required")) + + if extra: + extra_str = "; ".join(extra) + extra_str = f"[{extra_str}]" + if rich is not None: + # This is needed for when we want to export to HTML + extra_str = rich.markup.escape(extra_str).strip() + + help = f"{help} {extra_str}" if help else f"{extra_str}" + + return ("; " if any_prefix_is_slash else " / ").join(rv), help + + +def _typer_format_options( + self: click.core.Command, *, ctx: click.Context, formatter: click.HelpFormatter +) -> None: + args = [] + opts = [] + for param in self.get_params(ctx): + rv = param.get_help_record(ctx) + if rv is not None: + if param.param_type_name == "argument": + args.append(rv) + elif param.param_type_name == "option": + opts.append(rv) + + if args: + with formatter.section(_("Arguments")): + formatter.write_dl(args) + if opts: + with formatter.section(_("Options")): + formatter.write_dl(opts) + + +def _typer_main_shell_completion( + self: click.core.Command, + *, + ctx_args: MutableMapping[str, Any], + prog_name: str, + complete_var: Optional[str] = None, +) -> None: + if complete_var is None: + complete_var = f"_{prog_name}_COMPLETE".replace("-", "_").upper() + + instruction = os.environ.get(complete_var) + + if not instruction: + return + + from .completion import shell_complete + + rv = shell_complete(self, ctx_args, prog_name, complete_var, instruction) + sys.exit(rv) + + +class TyperCommand(click.core.Command): + def __init__( + self, + name: Optional[str], + *, + context_settings: Optional[Dict[str, Any]] = None, + callback: Optional[Callable[..., Any]] = None, + params: Optional[List[click.Parameter]] = None, + help: Optional[str] = None, + epilog: Optional[str] = None, + short_help: Optional[str] = None, + options_metavar: Optional[str] = "[OPTIONS]", + add_help_option: bool = True, + no_args_is_help: bool = False, + hidden: bool = False, + deprecated: bool = False, + # Rich settings + rich_markup_mode: MarkupMode = DEFAULT_MARKUP_MODE, + rich_help_panel: Union[str, None] = None, + ) -> None: + super().__init__( + name=name, + context_settings=context_settings, + callback=callback, + params=params, + help=help, + epilog=epilog, + short_help=short_help, + options_metavar=options_metavar, + add_help_option=add_help_option, + no_args_is_help=no_args_is_help, + hidden=hidden, + deprecated=deprecated, + ) + self.rich_markup_mode: MarkupMode = rich_markup_mode + self.rich_help_panel = rich_help_panel + + def format_options( + self, ctx: click.Context, formatter: click.HelpFormatter + ) -> None: + _typer_format_options(self, ctx=ctx, formatter=formatter) + + def _main_shell_completion( + self, + ctx_args: MutableMapping[str, Any], + prog_name: str, + complete_var: Optional[str] = None, + ) -> None: + _typer_main_shell_completion( + self, ctx_args=ctx_args, prog_name=prog_name, complete_var=complete_var + ) + + def main( + self, + args: Optional[Sequence[str]] = None, + prog_name: Optional[str] = None, + complete_var: Optional[str] = None, + standalone_mode: bool = True, + windows_expand_args: bool = True, + **extra: Any, + ) -> Any: + return _main( + self, + args=args, + prog_name=prog_name, + complete_var=complete_var, + standalone_mode=standalone_mode, + windows_expand_args=windows_expand_args, + rich_markup_mode=self.rich_markup_mode, + **extra, + ) + + def format_help(self, ctx: click.Context, formatter: click.HelpFormatter) -> None: + if not rich or self.rich_markup_mode is None: + return super().format_help(ctx, formatter) + return rich_utils.rich_format_help( + obj=self, + ctx=ctx, + markup_mode=self.rich_markup_mode, + ) + + +class TyperGroup(click.core.Group): + def __init__( + self, + *, + name: Optional[str] = None, + commands: Optional[ + Union[Dict[str, click.Command], Sequence[click.Command]] + ] = None, + # Rich settings + rich_markup_mode: MarkupMode = DEFAULT_MARKUP_MODE, + rich_help_panel: Union[str, None] = None, + **attrs: Any, + ) -> None: + super().__init__(name=name, commands=commands, **attrs) + self.rich_markup_mode: MarkupMode = rich_markup_mode + self.rich_help_panel = rich_help_panel + + def format_options( + self, ctx: click.Context, formatter: click.HelpFormatter + ) -> None: + _typer_format_options(self, ctx=ctx, formatter=formatter) + self.format_commands(ctx, formatter) + + def _main_shell_completion( + self, + ctx_args: MutableMapping[str, Any], + prog_name: str, + complete_var: Optional[str] = None, + ) -> None: + _typer_main_shell_completion( + self, ctx_args=ctx_args, prog_name=prog_name, complete_var=complete_var + ) + + def main( + self, + args: Optional[Sequence[str]] = None, + prog_name: Optional[str] = None, + complete_var: Optional[str] = None, + standalone_mode: bool = True, + windows_expand_args: bool = True, + **extra: Any, + ) -> Any: + return _main( + self, + args=args, + prog_name=prog_name, + complete_var=complete_var, + standalone_mode=standalone_mode, + windows_expand_args=windows_expand_args, + rich_markup_mode=self.rich_markup_mode, + **extra, + ) + + def format_help(self, ctx: click.Context, formatter: click.HelpFormatter) -> None: + if not rich or self.rich_markup_mode is None: + return super().format_help(ctx, formatter) + return rich_utils.rich_format_help( + obj=self, + ctx=ctx, + markup_mode=self.rich_markup_mode, + ) + + def list_commands(self, ctx: click.Context) -> List[str]: + """Returns a list of subcommand names. + Note that in Click's Group class, these are sorted. + In Typer, we wish to maintain the original order of creation (cf Issue #933)""" + return [n for n, c in self.commands.items()] diff --git a/venv/lib/python3.10/site-packages/typer/main.py b/venv/lib/python3.10/site-packages/typer/main.py new file mode 100644 index 0000000000000000000000000000000000000000..59e22c77aaac18f3f4194201e2a118dfa136b8a8 --- /dev/null +++ b/venv/lib/python3.10/site-packages/typer/main.py @@ -0,0 +1,1145 @@ +import inspect +import os +import platform +import shutil +import subprocess +import sys +import traceback +from datetime import datetime +from enum import Enum +from functools import update_wrapper +from pathlib import Path +from traceback import FrameSummary, StackSummary +from types import TracebackType +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Type, Union +from uuid import UUID + +import click +from typer._types import TyperChoice + +from ._typing import get_args, get_origin, is_union +from .completion import get_completion_inspect_parameters +from .core import ( + DEFAULT_MARKUP_MODE, + MarkupMode, + TyperArgument, + TyperCommand, + TyperGroup, + TyperOption, +) +from .models import ( + AnyType, + ArgumentInfo, + CommandFunctionType, + CommandInfo, + Default, + DefaultPlaceholder, + DeveloperExceptionConfig, + FileBinaryRead, + FileBinaryWrite, + FileText, + FileTextWrite, + NoneType, + OptionInfo, + ParameterInfo, + ParamMeta, + Required, + TyperInfo, + TyperPath, +) +from .utils import get_params_from_function + +try: + import rich + from rich.traceback import Traceback + + from . import rich_utils + + console_stderr = rich_utils._get_rich_console(stderr=True) + +except ImportError: # pragma: no cover + rich = None # type: ignore + +_original_except_hook = sys.excepthook +_typer_developer_exception_attr_name = "__typer_developer_exception__" + + +def except_hook( + exc_type: Type[BaseException], exc_value: BaseException, tb: Optional[TracebackType] +) -> None: + exception_config: Union[DeveloperExceptionConfig, None] = getattr( + exc_value, _typer_developer_exception_attr_name, None + ) + standard_traceback = os.getenv("_TYPER_STANDARD_TRACEBACK") + if ( + standard_traceback + or not exception_config + or not exception_config.pretty_exceptions_enable + ): + _original_except_hook(exc_type, exc_value, tb) + return + typer_path = os.path.dirname(__file__) + click_path = os.path.dirname(click.__file__) + supress_internal_dir_names = [typer_path, click_path] + exc = exc_value + if rich: + from .rich_utils import MAX_WIDTH + + rich_tb = Traceback.from_exception( + type(exc), + exc, + exc.__traceback__, + show_locals=exception_config.pretty_exceptions_show_locals, + suppress=supress_internal_dir_names, + width=MAX_WIDTH, + ) + console_stderr.print(rich_tb) + return + tb_exc = traceback.TracebackException.from_exception(exc) + stack: List[FrameSummary] = [] + for frame in tb_exc.stack: + if any(frame.filename.startswith(path) for path in supress_internal_dir_names): + if not exception_config.pretty_exceptions_short: + # Hide the line for internal libraries, Typer and Click + stack.append( + traceback.FrameSummary( + filename=frame.filename, + lineno=frame.lineno, + name=frame.name, + line="", + ) + ) + else: + stack.append(frame) + # Type ignore ref: https://github.com/python/typeshed/pull/8244 + final_stack_summary = StackSummary.from_list(stack) + tb_exc.stack = final_stack_summary + for line in tb_exc.format(): + print(line, file=sys.stderr) + return + + +def get_install_completion_arguments() -> Tuple[click.Parameter, click.Parameter]: + install_param, show_param = get_completion_inspect_parameters() + click_install_param, _ = get_click_param(install_param) + click_show_param, _ = get_click_param(show_param) + return click_install_param, click_show_param + + +class Typer: + def __init__( + self, + *, + name: Optional[str] = Default(None), + cls: Optional[Type[TyperGroup]] = Default(None), + invoke_without_command: bool = Default(False), + no_args_is_help: bool = Default(False), + subcommand_metavar: Optional[str] = Default(None), + chain: bool = Default(False), + result_callback: Optional[Callable[..., Any]] = Default(None), + # Command + context_settings: Optional[Dict[Any, Any]] = Default(None), + callback: Optional[Callable[..., Any]] = Default(None), + help: Optional[str] = Default(None), + epilog: Optional[str] = Default(None), + short_help: Optional[str] = Default(None), + options_metavar: str = Default("[OPTIONS]"), + add_help_option: bool = Default(True), + hidden: bool = Default(False), + deprecated: bool = Default(False), + add_completion: bool = True, + # Rich settings + rich_markup_mode: MarkupMode = Default(DEFAULT_MARKUP_MODE), + rich_help_panel: Union[str, None] = Default(None), + pretty_exceptions_enable: bool = True, + pretty_exceptions_show_locals: bool = True, + pretty_exceptions_short: bool = True, + ): + self._add_completion = add_completion + self.rich_markup_mode: MarkupMode = rich_markup_mode + self.rich_help_panel = rich_help_panel + self.pretty_exceptions_enable = pretty_exceptions_enable + self.pretty_exceptions_show_locals = pretty_exceptions_show_locals + self.pretty_exceptions_short = pretty_exceptions_short + self.info = TyperInfo( + name=name, + cls=cls, + invoke_without_command=invoke_without_command, + no_args_is_help=no_args_is_help, + subcommand_metavar=subcommand_metavar, + chain=chain, + result_callback=result_callback, + context_settings=context_settings, + callback=callback, + help=help, + epilog=epilog, + short_help=short_help, + options_metavar=options_metavar, + add_help_option=add_help_option, + hidden=hidden, + deprecated=deprecated, + ) + self.registered_groups: List[TyperInfo] = [] + self.registered_commands: List[CommandInfo] = [] + self.registered_callback: Optional[TyperInfo] = None + + def callback( + self, + *, + cls: Optional[Type[TyperGroup]] = Default(None), + invoke_without_command: bool = Default(False), + no_args_is_help: bool = Default(False), + subcommand_metavar: Optional[str] = Default(None), + chain: bool = Default(False), + result_callback: Optional[Callable[..., Any]] = Default(None), + # Command + context_settings: Optional[Dict[Any, Any]] = Default(None), + help: Optional[str] = Default(None), + epilog: Optional[str] = Default(None), + short_help: Optional[str] = Default(None), + options_metavar: str = Default("[OPTIONS]"), + add_help_option: bool = Default(True), + hidden: bool = Default(False), + deprecated: bool = Default(False), + # Rich settings + rich_help_panel: Union[str, None] = Default(None), + ) -> Callable[[CommandFunctionType], CommandFunctionType]: + def decorator(f: CommandFunctionType) -> CommandFunctionType: + self.registered_callback = TyperInfo( + cls=cls, + invoke_without_command=invoke_without_command, + no_args_is_help=no_args_is_help, + subcommand_metavar=subcommand_metavar, + chain=chain, + result_callback=result_callback, + context_settings=context_settings, + callback=f, + help=help, + epilog=epilog, + short_help=short_help, + options_metavar=options_metavar, + add_help_option=add_help_option, + hidden=hidden, + deprecated=deprecated, + rich_help_panel=rich_help_panel, + ) + return f + + return decorator + + def command( + self, + name: Optional[str] = None, + *, + cls: Optional[Type[TyperCommand]] = None, + context_settings: Optional[Dict[Any, Any]] = None, + help: Optional[str] = None, + epilog: Optional[str] = None, + short_help: Optional[str] = None, + options_metavar: str = "[OPTIONS]", + add_help_option: bool = True, + no_args_is_help: bool = False, + hidden: bool = False, + deprecated: bool = False, + # Rich settings + rich_help_panel: Union[str, None] = Default(None), + ) -> Callable[[CommandFunctionType], CommandFunctionType]: + if cls is None: + cls = TyperCommand + + def decorator(f: CommandFunctionType) -> CommandFunctionType: + self.registered_commands.append( + CommandInfo( + name=name, + cls=cls, + context_settings=context_settings, + callback=f, + help=help, + epilog=epilog, + short_help=short_help, + options_metavar=options_metavar, + add_help_option=add_help_option, + no_args_is_help=no_args_is_help, + hidden=hidden, + deprecated=deprecated, + # Rich settings + rich_help_panel=rich_help_panel, + ) + ) + return f + + return decorator + + def add_typer( + self, + typer_instance: "Typer", + *, + name: Optional[str] = Default(None), + cls: Optional[Type[TyperGroup]] = Default(None), + invoke_without_command: bool = Default(False), + no_args_is_help: bool = Default(False), + subcommand_metavar: Optional[str] = Default(None), + chain: bool = Default(False), + result_callback: Optional[Callable[..., Any]] = Default(None), + # Command + context_settings: Optional[Dict[Any, Any]] = Default(None), + callback: Optional[Callable[..., Any]] = Default(None), + help: Optional[str] = Default(None), + epilog: Optional[str] = Default(None), + short_help: Optional[str] = Default(None), + options_metavar: str = Default("[OPTIONS]"), + add_help_option: bool = Default(True), + hidden: bool = Default(False), + deprecated: bool = Default(False), + # Rich settings + rich_help_panel: Union[str, None] = Default(None), + ) -> None: + self.registered_groups.append( + TyperInfo( + typer_instance, + name=name, + cls=cls, + invoke_without_command=invoke_without_command, + no_args_is_help=no_args_is_help, + subcommand_metavar=subcommand_metavar, + chain=chain, + result_callback=result_callback, + context_settings=context_settings, + callback=callback, + help=help, + epilog=epilog, + short_help=short_help, + options_metavar=options_metavar, + add_help_option=add_help_option, + hidden=hidden, + deprecated=deprecated, + rich_help_panel=rich_help_panel, + ) + ) + + def __call__(self, *args: Any, **kwargs: Any) -> Any: + if sys.excepthook != except_hook: + sys.excepthook = except_hook + try: + return get_command(self)(*args, **kwargs) + except Exception as e: + # Set a custom attribute to tell the hook to show nice exceptions for user + # code. An alternative/first implementation was a custom exception with + # raise custom_exc from e + # but that means the last error shown is the custom exception, not the + # actual error. This trick improves developer experience by showing the + # actual error last. + setattr( + e, + _typer_developer_exception_attr_name, + DeveloperExceptionConfig( + pretty_exceptions_enable=self.pretty_exceptions_enable, + pretty_exceptions_show_locals=self.pretty_exceptions_show_locals, + pretty_exceptions_short=self.pretty_exceptions_short, + ), + ) + raise e + + +def get_group(typer_instance: Typer) -> TyperGroup: + group = get_group_from_info( + TyperInfo(typer_instance), + pretty_exceptions_short=typer_instance.pretty_exceptions_short, + rich_markup_mode=typer_instance.rich_markup_mode, + ) + return group + + +def get_command(typer_instance: Typer) -> click.Command: + if typer_instance._add_completion: + click_install_param, click_show_param = get_install_completion_arguments() + if ( + typer_instance.registered_callback + or typer_instance.info.callback + or typer_instance.registered_groups + or len(typer_instance.registered_commands) > 1 + ): + # Create a Group + click_command: click.Command = get_group(typer_instance) + if typer_instance._add_completion: + click_command.params.append(click_install_param) + click_command.params.append(click_show_param) + return click_command + elif len(typer_instance.registered_commands) == 1: + # Create a single Command + single_command = typer_instance.registered_commands[0] + + if not single_command.context_settings and not isinstance( + typer_instance.info.context_settings, DefaultPlaceholder + ): + single_command.context_settings = typer_instance.info.context_settings + + click_command = get_command_from_info( + single_command, + pretty_exceptions_short=typer_instance.pretty_exceptions_short, + rich_markup_mode=typer_instance.rich_markup_mode, + ) + if typer_instance._add_completion: + click_command.params.append(click_install_param) + click_command.params.append(click_show_param) + return click_command + raise RuntimeError( + "Could not get a command for this Typer instance" + ) # pragma: no cover + + +def solve_typer_info_help(typer_info: TyperInfo) -> str: + # Priority 1: Explicit value was set in app.add_typer() + if not isinstance(typer_info.help, DefaultPlaceholder): + return inspect.cleandoc(typer_info.help or "") + # Priority 2: Explicit value was set in sub_app.callback() + try: + callback_help = typer_info.typer_instance.registered_callback.help + if not isinstance(callback_help, DefaultPlaceholder): + return inspect.cleandoc(callback_help or "") + except AttributeError: + pass + # Priority 3: Explicit value was set in sub_app = typer.Typer() + try: + instance_help = typer_info.typer_instance.info.help + if not isinstance(instance_help, DefaultPlaceholder): + return inspect.cleandoc(instance_help or "") + except AttributeError: + pass + # Priority 4: Implicit inference from callback docstring in app.add_typer() + if typer_info.callback: + doc = inspect.getdoc(typer_info.callback) + if doc: + return doc + # Priority 5: Implicit inference from callback docstring in @app.callback() + try: + callback = typer_info.typer_instance.registered_callback.callback + if not isinstance(callback, DefaultPlaceholder): + doc = inspect.getdoc(callback or "") + if doc: + return doc + except AttributeError: + pass + # Priority 6: Implicit inference from callback docstring in typer.Typer() + try: + instance_callback = typer_info.typer_instance.info.callback + if not isinstance(instance_callback, DefaultPlaceholder): + doc = inspect.getdoc(instance_callback) + if doc: + return doc + except AttributeError: + pass + # Value not set, use the default + return typer_info.help.value + + +def solve_typer_info_defaults(typer_info: TyperInfo) -> TyperInfo: + values: Dict[str, Any] = {} + for name, value in typer_info.__dict__.items(): + # Priority 1: Value was set in app.add_typer() + if not isinstance(value, DefaultPlaceholder): + values[name] = value + continue + # Priority 2: Value was set in @subapp.callback() + try: + callback_value = getattr( + typer_info.typer_instance.registered_callback, # type: ignore + name, + ) + if not isinstance(callback_value, DefaultPlaceholder): + values[name] = callback_value + continue + except AttributeError: + pass + # Priority 3: Value set in subapp = typer.Typer() + try: + instance_value = getattr( + typer_info.typer_instance.info, # type: ignore + name, + ) + if not isinstance(instance_value, DefaultPlaceholder): + values[name] = instance_value + continue + except AttributeError: + pass + # Value not set, use the default + values[name] = value.value + values["help"] = solve_typer_info_help(typer_info) + return TyperInfo(**values) + + +def get_group_from_info( + group_info: TyperInfo, + *, + pretty_exceptions_short: bool, + rich_markup_mode: MarkupMode, +) -> TyperGroup: + assert group_info.typer_instance, ( + "A Typer instance is needed to generate a Click Group" + ) + commands: Dict[str, click.Command] = {} + for command_info in group_info.typer_instance.registered_commands: + command = get_command_from_info( + command_info=command_info, + pretty_exceptions_short=pretty_exceptions_short, + rich_markup_mode=rich_markup_mode, + ) + if command.name: + commands[command.name] = command + for sub_group_info in group_info.typer_instance.registered_groups: + sub_group = get_group_from_info( + sub_group_info, + pretty_exceptions_short=pretty_exceptions_short, + rich_markup_mode=rich_markup_mode, + ) + if sub_group.name: + commands[sub_group.name] = sub_group + else: + if sub_group.callback: + import warnings + + warnings.warn( + "The 'callback' parameter is not supported by Typer when using `add_typer` without a name", + stacklevel=5, + ) + for sub_command_name, sub_command in sub_group.commands.items(): + commands[sub_command_name] = sub_command + solved_info = solve_typer_info_defaults(group_info) + ( + params, + convertors, + context_param_name, + ) = get_params_convertors_ctx_param_name_from_function(solved_info.callback) + cls = solved_info.cls or TyperGroup + assert issubclass(cls, TyperGroup), f"{cls} should be a subclass of {TyperGroup}" + group = cls( + name=solved_info.name or "", + commands=commands, + invoke_without_command=solved_info.invoke_without_command, + no_args_is_help=solved_info.no_args_is_help, + subcommand_metavar=solved_info.subcommand_metavar, + chain=solved_info.chain, + result_callback=solved_info.result_callback, + context_settings=solved_info.context_settings, + callback=get_callback( + callback=solved_info.callback, + params=params, + convertors=convertors, + context_param_name=context_param_name, + pretty_exceptions_short=pretty_exceptions_short, + ), + params=params, + help=solved_info.help, + epilog=solved_info.epilog, + short_help=solved_info.short_help, + options_metavar=solved_info.options_metavar, + add_help_option=solved_info.add_help_option, + hidden=solved_info.hidden, + deprecated=solved_info.deprecated, + rich_markup_mode=rich_markup_mode, + # Rich settings + rich_help_panel=solved_info.rich_help_panel, + ) + return group + + +def get_command_name(name: str) -> str: + return name.lower().replace("_", "-") + + +def get_params_convertors_ctx_param_name_from_function( + callback: Optional[Callable[..., Any]], +) -> Tuple[List[Union[click.Argument, click.Option]], Dict[str, Any], Optional[str]]: + params = [] + convertors = {} + context_param_name = None + if callback: + parameters = get_params_from_function(callback) + for param_name, param in parameters.items(): + if lenient_issubclass(param.annotation, click.Context): + context_param_name = param_name + continue + click_param, convertor = get_click_param(param) + if convertor: + convertors[param_name] = convertor + params.append(click_param) + return params, convertors, context_param_name + + +def get_command_from_info( + command_info: CommandInfo, + *, + pretty_exceptions_short: bool, + rich_markup_mode: MarkupMode, +) -> click.Command: + assert command_info.callback, "A command must have a callback function" + name = command_info.name or get_command_name(command_info.callback.__name__) + use_help = command_info.help + if use_help is None: + use_help = inspect.getdoc(command_info.callback) + else: + use_help = inspect.cleandoc(use_help) + ( + params, + convertors, + context_param_name, + ) = get_params_convertors_ctx_param_name_from_function(command_info.callback) + cls = command_info.cls or TyperCommand + command = cls( + name=name, + context_settings=command_info.context_settings, + callback=get_callback( + callback=command_info.callback, + params=params, + convertors=convertors, + context_param_name=context_param_name, + pretty_exceptions_short=pretty_exceptions_short, + ), + params=params, # type: ignore + help=use_help, + epilog=command_info.epilog, + short_help=command_info.short_help, + options_metavar=command_info.options_metavar, + add_help_option=command_info.add_help_option, + no_args_is_help=command_info.no_args_is_help, + hidden=command_info.hidden, + deprecated=command_info.deprecated, + rich_markup_mode=rich_markup_mode, + # Rich settings + rich_help_panel=command_info.rich_help_panel, + ) + return command + + +def determine_type_convertor(type_: Any) -> Optional[Callable[[Any], Any]]: + convertor: Optional[Callable[[Any], Any]] = None + if lenient_issubclass(type_, Path): + convertor = param_path_convertor + if lenient_issubclass(type_, Enum): + convertor = generate_enum_convertor(type_) + return convertor + + +def param_path_convertor(value: Optional[str] = None) -> Optional[Path]: + if value is not None: + return Path(value) + return None + + +def generate_enum_convertor(enum: Type[Enum]) -> Callable[[Any], Any]: + val_map = {str(val.value): val for val in enum} + + def convertor(value: Any) -> Any: + if value is not None: + val = str(value) + if val in val_map: + key = val_map[val] + return enum(key) + + return convertor + + +def generate_list_convertor( + convertor: Optional[Callable[[Any], Any]], default_value: Optional[Any] +) -> Callable[[Sequence[Any]], Optional[List[Any]]]: + def internal_convertor(value: Sequence[Any]) -> Optional[List[Any]]: + if default_value is None and len(value) == 0: + return None + return [convertor(v) if convertor else v for v in value] + + return internal_convertor + + +def generate_tuple_convertor( + types: Sequence[Any], +) -> Callable[[Optional[Tuple[Any, ...]]], Optional[Tuple[Any, ...]]]: + convertors = [determine_type_convertor(type_) for type_ in types] + + def internal_convertor( + param_args: Optional[Tuple[Any, ...]], + ) -> Optional[Tuple[Any, ...]]: + if param_args is None: + return None + return tuple( + convertor(arg) if convertor else arg + for (convertor, arg) in zip(convertors, param_args) + ) + + return internal_convertor + + +def get_callback( + *, + callback: Optional[Callable[..., Any]] = None, + params: Sequence[click.Parameter] = [], + convertors: Optional[Dict[str, Callable[[str], Any]]] = None, + context_param_name: Optional[str] = None, + pretty_exceptions_short: bool, +) -> Optional[Callable[..., Any]]: + use_convertors = convertors or {} + if not callback: + return None + parameters = get_params_from_function(callback) + use_params: Dict[str, Any] = {} + for param_name in parameters: + use_params[param_name] = None + for param in params: + if param.name: + use_params[param.name] = param.default + + def wrapper(**kwargs: Any) -> Any: + _rich_traceback_guard = pretty_exceptions_short # noqa: F841 + for k, v in kwargs.items(): + if k in use_convertors: + use_params[k] = use_convertors[k](v) + else: + use_params[k] = v + if context_param_name: + use_params[context_param_name] = click.get_current_context() + return callback(**use_params) + + update_wrapper(wrapper, callback) + return wrapper + + +def get_click_type( + *, annotation: Any, parameter_info: ParameterInfo +) -> click.ParamType: + if parameter_info.click_type is not None: + return parameter_info.click_type + + elif parameter_info.parser is not None: + return click.types.FuncParamType(parameter_info.parser) + + elif annotation is str: + return click.STRING + elif annotation is int: + if parameter_info.min is not None or parameter_info.max is not None: + min_ = None + max_ = None + if parameter_info.min is not None: + min_ = int(parameter_info.min) + if parameter_info.max is not None: + max_ = int(parameter_info.max) + return click.IntRange(min=min_, max=max_, clamp=parameter_info.clamp) + else: + return click.INT + elif annotation is float: + if parameter_info.min is not None or parameter_info.max is not None: + return click.FloatRange( + min=parameter_info.min, + max=parameter_info.max, + clamp=parameter_info.clamp, + ) + else: + return click.FLOAT + elif annotation is bool: + return click.BOOL + elif annotation == UUID: + return click.UUID + elif annotation == datetime: + return click.DateTime(formats=parameter_info.formats) + elif ( + annotation == Path + or parameter_info.allow_dash + or parameter_info.path_type + or parameter_info.resolve_path + ): + return TyperPath( + exists=parameter_info.exists, + file_okay=parameter_info.file_okay, + dir_okay=parameter_info.dir_okay, + writable=parameter_info.writable, + readable=parameter_info.readable, + resolve_path=parameter_info.resolve_path, + allow_dash=parameter_info.allow_dash, + path_type=parameter_info.path_type, + ) + elif lenient_issubclass(annotation, FileTextWrite): + return click.File( + mode=parameter_info.mode or "w", + encoding=parameter_info.encoding, + errors=parameter_info.errors, + lazy=parameter_info.lazy, + atomic=parameter_info.atomic, + ) + elif lenient_issubclass(annotation, FileText): + return click.File( + mode=parameter_info.mode or "r", + encoding=parameter_info.encoding, + errors=parameter_info.errors, + lazy=parameter_info.lazy, + atomic=parameter_info.atomic, + ) + elif lenient_issubclass(annotation, FileBinaryRead): + return click.File( + mode=parameter_info.mode or "rb", + encoding=parameter_info.encoding, + errors=parameter_info.errors, + lazy=parameter_info.lazy, + atomic=parameter_info.atomic, + ) + elif lenient_issubclass(annotation, FileBinaryWrite): + return click.File( + mode=parameter_info.mode or "wb", + encoding=parameter_info.encoding, + errors=parameter_info.errors, + lazy=parameter_info.lazy, + atomic=parameter_info.atomic, + ) + elif lenient_issubclass(annotation, Enum): + # The custom TyperChoice is only needed for Click < 8.2.0, to parse the + # command line values matching them to the enum values. Click 8.2.0 added + # support for enum values but reading enum names. + # Passing here the list of enum values (instead of just the enum) accounts for + # Click < 8.2.0. + return TyperChoice( + [item.value for item in annotation], + case_sensitive=parameter_info.case_sensitive, + ) + raise RuntimeError(f"Type not yet supported: {annotation}") # pragma: no cover + + +def lenient_issubclass( + cls: Any, class_or_tuple: Union[AnyType, Tuple[AnyType, ...]] +) -> bool: + return isinstance(cls, type) and issubclass(cls, class_or_tuple) + + +def get_click_param( + param: ParamMeta, +) -> Tuple[Union[click.Argument, click.Option], Any]: + # First, find out what will be: + # * ParamInfo (ArgumentInfo or OptionInfo) + # * default_value + # * required + default_value = None + required = False + if isinstance(param.default, ParameterInfo): + parameter_info = param.default + if parameter_info.default == Required: + required = True + else: + default_value = parameter_info.default + elif param.default == Required or param.default is param.empty: + required = True + parameter_info = ArgumentInfo() + else: + default_value = param.default + parameter_info = OptionInfo() + annotation: Any + if param.annotation is not param.empty: + annotation = param.annotation + else: + annotation = str + main_type = annotation + is_list = False + is_tuple = False + parameter_type: Any = None + is_flag = None + origin = get_origin(main_type) + + if origin is not None: + # Handle SomeType | None and Optional[SomeType] + if is_union(origin): + types = [] + for type_ in get_args(main_type): + if type_ is NoneType: + continue + types.append(type_) + assert len(types) == 1, "Typer Currently doesn't support Union types" + main_type = types[0] + origin = get_origin(main_type) + # Handle Tuples and Lists + if lenient_issubclass(origin, List): + main_type = get_args(main_type)[0] + assert not get_origin(main_type), ( + "List types with complex sub-types are not currently supported" + ) + is_list = True + elif lenient_issubclass(origin, Tuple): # type: ignore + types = [] + for type_ in get_args(main_type): + assert not get_origin(type_), ( + "Tuple types with complex sub-types are not currently supported" + ) + types.append( + get_click_type(annotation=type_, parameter_info=parameter_info) + ) + parameter_type = tuple(types) + is_tuple = True + if parameter_type is None: + parameter_type = get_click_type( + annotation=main_type, parameter_info=parameter_info + ) + convertor = determine_type_convertor(main_type) + if is_list: + convertor = generate_list_convertor( + convertor=convertor, default_value=default_value + ) + if is_tuple: + convertor = generate_tuple_convertor(get_args(main_type)) + if isinstance(parameter_info, OptionInfo): + if main_type is bool: + is_flag = True + # Click doesn't accept a flag of type bool, only None, and then it sets it + # to bool internally + parameter_type = None + default_option_name = get_command_name(param.name) + if is_flag: + default_option_declaration = ( + f"--{default_option_name}/--no-{default_option_name}" + ) + else: + default_option_declaration = f"--{default_option_name}" + param_decls = [param.name] + if parameter_info.param_decls: + param_decls.extend(parameter_info.param_decls) + else: + param_decls.append(default_option_declaration) + return ( + TyperOption( + # Option + param_decls=param_decls, + show_default=parameter_info.show_default, + prompt=parameter_info.prompt, + confirmation_prompt=parameter_info.confirmation_prompt, + prompt_required=parameter_info.prompt_required, + hide_input=parameter_info.hide_input, + is_flag=is_flag, + multiple=is_list, + count=parameter_info.count, + allow_from_autoenv=parameter_info.allow_from_autoenv, + type=parameter_type, + help=parameter_info.help, + hidden=parameter_info.hidden, + show_choices=parameter_info.show_choices, + show_envvar=parameter_info.show_envvar, + # Parameter + required=required, + default=default_value, + callback=get_param_callback( + callback=parameter_info.callback, convertor=convertor + ), + metavar=parameter_info.metavar, + expose_value=parameter_info.expose_value, + is_eager=parameter_info.is_eager, + envvar=parameter_info.envvar, + shell_complete=parameter_info.shell_complete, + autocompletion=get_param_completion(parameter_info.autocompletion), + # Rich settings + rich_help_panel=parameter_info.rich_help_panel, + ), + convertor, + ) + elif isinstance(parameter_info, ArgumentInfo): + param_decls = [param.name] + nargs = None + if is_list: + nargs = -1 + return ( + TyperArgument( + # Argument + param_decls=param_decls, + type=parameter_type, + required=required, + nargs=nargs, + # TyperArgument + show_default=parameter_info.show_default, + show_choices=parameter_info.show_choices, + show_envvar=parameter_info.show_envvar, + help=parameter_info.help, + hidden=parameter_info.hidden, + # Parameter + default=default_value, + callback=get_param_callback( + callback=parameter_info.callback, convertor=convertor + ), + metavar=parameter_info.metavar, + expose_value=parameter_info.expose_value, + is_eager=parameter_info.is_eager, + envvar=parameter_info.envvar, + shell_complete=parameter_info.shell_complete, + autocompletion=get_param_completion(parameter_info.autocompletion), + # Rich settings + rich_help_panel=parameter_info.rich_help_panel, + ), + convertor, + ) + raise AssertionError("A click.Parameter should be returned") # pragma: no cover + + +def get_param_callback( + *, + callback: Optional[Callable[..., Any]] = None, + convertor: Optional[Callable[..., Any]] = None, +) -> Optional[Callable[..., Any]]: + if not callback: + return None + parameters = get_params_from_function(callback) + ctx_name = None + click_param_name = None + value_name = None + untyped_names: List[str] = [] + for param_name, param_sig in parameters.items(): + if lenient_issubclass(param_sig.annotation, click.Context): + ctx_name = param_name + elif lenient_issubclass(param_sig.annotation, click.Parameter): + click_param_name = param_name + else: + untyped_names.append(param_name) + # Extract value param name first + if untyped_names: + value_name = untyped_names.pop() + # If context and Click param were not typed (old/Click callback style) extract them + if untyped_names: + if ctx_name is None: + ctx_name = untyped_names.pop(0) + if click_param_name is None: + if untyped_names: + click_param_name = untyped_names.pop(0) + if untyped_names: + raise click.ClickException( + "Too many CLI parameter callback function parameters" + ) + + def wrapper(ctx: click.Context, param: click.Parameter, value: Any) -> Any: + use_params: Dict[str, Any] = {} + if ctx_name: + use_params[ctx_name] = ctx + if click_param_name: + use_params[click_param_name] = param + if value_name: + if convertor: + use_value = convertor(value) + else: + use_value = value + use_params[value_name] = use_value + return callback(**use_params) + + update_wrapper(wrapper, callback) + return wrapper + + +def get_param_completion( + callback: Optional[Callable[..., Any]] = None, +) -> Optional[Callable[..., Any]]: + if not callback: + return None + parameters = get_params_from_function(callback) + ctx_name = None + args_name = None + incomplete_name = None + unassigned_params = list(parameters.values()) + for param_sig in unassigned_params[:]: + origin = get_origin(param_sig.annotation) + if lenient_issubclass(param_sig.annotation, click.Context): + ctx_name = param_sig.name + unassigned_params.remove(param_sig) + elif lenient_issubclass(origin, List): + args_name = param_sig.name + unassigned_params.remove(param_sig) + elif lenient_issubclass(param_sig.annotation, str): + incomplete_name = param_sig.name + unassigned_params.remove(param_sig) + # If there are still unassigned parameters (not typed), extract by name + for param_sig in unassigned_params[:]: + if ctx_name is None and param_sig.name == "ctx": + ctx_name = param_sig.name + unassigned_params.remove(param_sig) + elif args_name is None and param_sig.name == "args": + args_name = param_sig.name + unassigned_params.remove(param_sig) + elif incomplete_name is None and param_sig.name == "incomplete": + incomplete_name = param_sig.name + unassigned_params.remove(param_sig) + # Extract value param name first + if unassigned_params: + show_params = " ".join([param.name for param in unassigned_params]) + raise click.ClickException( + f"Invalid autocompletion callback parameters: {show_params}" + ) + + def wrapper(ctx: click.Context, args: List[str], incomplete: Optional[str]) -> Any: + use_params: Dict[str, Any] = {} + if ctx_name: + use_params[ctx_name] = ctx + if args_name: + use_params[args_name] = args + if incomplete_name: + use_params[incomplete_name] = incomplete + return callback(**use_params) + + update_wrapper(wrapper, callback) + return wrapper + + +def run(function: Callable[..., Any]) -> None: + app = Typer(add_completion=False) + app.command()(function) + app() + + +def _is_macos() -> bool: + return platform.system() == "Darwin" + + +def _is_linux_or_bsd() -> bool: + if platform.system() == "Linux": + return True + + return "BSD" in platform.system() + + +def launch(url: str, wait: bool = False, locate: bool = False) -> int: + """This function launches the given URL (or filename) in the default + viewer application for this file type. If this is an executable, it + might launch the executable in a new session. The return value is + the exit code of the launched application. Usually, ``0`` indicates + success. + + This function handles url in different operating systems separately: + - On macOS (Darwin), it uses the 'open' command. + - On Linux and BSD, it uses 'xdg-open' if available. + - On Windows (and other OSes), it uses the standard webbrowser module. + + The function avoids, when possible, using the webbrowser module on Linux and macOS + to prevent spammy terminal messages from some browsers (e.g., Chrome). + + Examples:: + + typer.launch("https://typer.tiangolo.com/") + typer.launch("/my/downloaded/file", locate=True) + + :param url: URL or filename of the thing to launch. + :param wait: Wait for the program to exit before returning. This + only works if the launched program blocks. In particular, + ``xdg-open`` on Linux does not block. + :param locate: if this is set to `True` then instead of launching the + application associated with the URL it will attempt to + launch a file manager with the file located. This + might have weird effects if the URL does not point to + the filesystem. + """ + + if url.startswith("http://") or url.startswith("https://"): + if _is_macos(): + return subprocess.Popen( + ["open", url], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT + ).wait() + + has_xdg_open = _is_linux_or_bsd() and shutil.which("xdg-open") is not None + + if has_xdg_open: + return subprocess.Popen( + ["xdg-open", url], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT + ).wait() + + import webbrowser + + webbrowser.open(url) + + return 0 + + else: + return click.launch(url) diff --git a/venv/lib/python3.10/site-packages/typer/models.py b/venv/lib/python3.10/site-packages/typer/models.py new file mode 100644 index 0000000000000000000000000000000000000000..e0bddb965be67ec2e8fd7c045a9e53baa887a915 --- /dev/null +++ b/venv/lib/python3.10/site-packages/typer/models.py @@ -0,0 +1,544 @@ +import inspect +import io +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Dict, + List, + Optional, + Sequence, + Type, + TypeVar, + Union, +) + +import click +import click.shell_completion + +if TYPE_CHECKING: # pragma: no cover + from .core import TyperCommand, TyperGroup + from .main import Typer + + +NoneType = type(None) + +AnyType = Type[Any] + +Required = ... + + +class Context(click.Context): + pass + + +class FileText(io.TextIOWrapper): + pass + + +class FileTextWrite(FileText): + pass + + +class FileBinaryRead(io.BufferedReader): + pass + + +class FileBinaryWrite(io.BufferedWriter): + pass + + +class CallbackParam(click.Parameter): + pass + + +class DefaultPlaceholder: + """ + You shouldn't use this class directly. + + It's used internally to recognize when a default value has been overwritten, even + if the new value is `None`. + """ + + def __init__(self, value: Any): + self.value = value + + def __bool__(self) -> bool: + return bool(self.value) + + +DefaultType = TypeVar("DefaultType") + +CommandFunctionType = TypeVar("CommandFunctionType", bound=Callable[..., Any]) + + +def Default(value: DefaultType) -> DefaultType: + """ + You shouldn't use this function directly. + + It's used internally to recognize when a default value has been overwritten, even + if the new value is `None`. + """ + return DefaultPlaceholder(value) # type: ignore + + +class CommandInfo: + def __init__( + self, + name: Optional[str] = None, + *, + cls: Optional[Type["TyperCommand"]] = None, + context_settings: Optional[Dict[Any, Any]] = None, + callback: Optional[Callable[..., Any]] = None, + help: Optional[str] = None, + epilog: Optional[str] = None, + short_help: Optional[str] = None, + options_metavar: str = "[OPTIONS]", + add_help_option: bool = True, + no_args_is_help: bool = False, + hidden: bool = False, + deprecated: bool = False, + # Rich settings + rich_help_panel: Union[str, None] = None, + ): + self.name = name + self.cls = cls + self.context_settings = context_settings + self.callback = callback + self.help = help + self.epilog = epilog + self.short_help = short_help + self.options_metavar = options_metavar + self.add_help_option = add_help_option + self.no_args_is_help = no_args_is_help + self.hidden = hidden + self.deprecated = deprecated + # Rich settings + self.rich_help_panel = rich_help_panel + + +class TyperInfo: + def __init__( + self, + typer_instance: Optional["Typer"] = Default(None), + *, + name: Optional[str] = Default(None), + cls: Optional[Type["TyperGroup"]] = Default(None), + invoke_without_command: bool = Default(False), + no_args_is_help: bool = Default(False), + subcommand_metavar: Optional[str] = Default(None), + chain: bool = Default(False), + result_callback: Optional[Callable[..., Any]] = Default(None), + # Command + context_settings: Optional[Dict[Any, Any]] = Default(None), + callback: Optional[Callable[..., Any]] = Default(None), + help: Optional[str] = Default(None), + epilog: Optional[str] = Default(None), + short_help: Optional[str] = Default(None), + options_metavar: str = Default("[OPTIONS]"), + add_help_option: bool = Default(True), + hidden: bool = Default(False), + deprecated: bool = Default(False), + # Rich settings + rich_help_panel: Union[str, None] = Default(None), + ): + self.typer_instance = typer_instance + self.name = name + self.cls = cls + self.invoke_without_command = invoke_without_command + self.no_args_is_help = no_args_is_help + self.subcommand_metavar = subcommand_metavar + self.chain = chain + self.result_callback = result_callback + self.context_settings = context_settings + self.callback = callback + self.help = help + self.epilog = epilog + self.short_help = short_help + self.options_metavar = options_metavar + self.add_help_option = add_help_option + self.hidden = hidden + self.deprecated = deprecated + self.rich_help_panel = rich_help_panel + + +class ParameterInfo: + def __init__( + self, + *, + default: Optional[Any] = None, + param_decls: Optional[Sequence[str]] = None, + callback: Optional[Callable[..., Any]] = None, + metavar: Optional[str] = None, + expose_value: bool = True, + is_eager: bool = False, + envvar: Optional[Union[str, List[str]]] = None, + # Note that shell_complete is not fully supported and will be removed in future versions + # TODO: Remove shell_complete in a future version (after 0.16.0) + shell_complete: Optional[ + Callable[ + [click.Context, click.Parameter, str], + Union[List["click.shell_completion.CompletionItem"], List[str]], + ] + ] = None, + autocompletion: Optional[Callable[..., Any]] = None, + default_factory: Optional[Callable[[], Any]] = None, + # Custom type + parser: Optional[Callable[[str], Any]] = None, + click_type: Optional[click.ParamType] = None, + # TyperArgument + show_default: Union[bool, str] = True, + show_choices: bool = True, + show_envvar: bool = True, + help: Optional[str] = None, + hidden: bool = False, + # Choice + case_sensitive: bool = True, + # Numbers + min: Optional[Union[int, float]] = None, + max: Optional[Union[int, float]] = None, + clamp: bool = False, + # DateTime + formats: Optional[List[str]] = None, + # File + mode: Optional[str] = None, + encoding: Optional[str] = None, + errors: Optional[str] = "strict", + lazy: Optional[bool] = None, + atomic: bool = False, + # Path + exists: bool = False, + file_okay: bool = True, + dir_okay: bool = True, + writable: bool = False, + readable: bool = True, + resolve_path: bool = False, + allow_dash: bool = False, + path_type: Union[None, Type[str], Type[bytes]] = None, + # Rich settings + rich_help_panel: Union[str, None] = None, + ): + # Check if user has provided multiple custom parsers + if parser and click_type: + raise ValueError( + "Multiple custom type parsers provided. " + "`parser` and `click_type` may not both be provided." + ) + + self.default = default + self.param_decls = param_decls + self.callback = callback + self.metavar = metavar + self.expose_value = expose_value + self.is_eager = is_eager + self.envvar = envvar + self.shell_complete = shell_complete + self.autocompletion = autocompletion + self.default_factory = default_factory + # Custom type + self.parser = parser + self.click_type = click_type + # TyperArgument + self.show_default = show_default + self.show_choices = show_choices + self.show_envvar = show_envvar + self.help = help + self.hidden = hidden + # Choice + self.case_sensitive = case_sensitive + # Numbers + self.min = min + self.max = max + self.clamp = clamp + # DateTime + self.formats = formats + # File + self.mode = mode + self.encoding = encoding + self.errors = errors + self.lazy = lazy + self.atomic = atomic + # Path + self.exists = exists + self.file_okay = file_okay + self.dir_okay = dir_okay + self.writable = writable + self.readable = readable + self.resolve_path = resolve_path + self.allow_dash = allow_dash + self.path_type = path_type + # Rich settings + self.rich_help_panel = rich_help_panel + + +class OptionInfo(ParameterInfo): + def __init__( + self, + *, + # ParameterInfo + default: Optional[Any] = None, + param_decls: Optional[Sequence[str]] = None, + callback: Optional[Callable[..., Any]] = None, + metavar: Optional[str] = None, + expose_value: bool = True, + is_eager: bool = False, + envvar: Optional[Union[str, List[str]]] = None, + # Note that shell_complete is not fully supported and will be removed in future versions + # TODO: Remove shell_complete in a future version (after 0.16.0) + shell_complete: Optional[ + Callable[ + [click.Context, click.Parameter, str], + Union[List["click.shell_completion.CompletionItem"], List[str]], + ] + ] = None, + autocompletion: Optional[Callable[..., Any]] = None, + default_factory: Optional[Callable[[], Any]] = None, + # Custom type + parser: Optional[Callable[[str], Any]] = None, + click_type: Optional[click.ParamType] = None, + # Option + show_default: Union[bool, str] = True, + prompt: Union[bool, str] = False, + confirmation_prompt: bool = False, + prompt_required: bool = True, + hide_input: bool = False, + # TODO: remove is_flag and flag_value in a future release + is_flag: Optional[bool] = None, + flag_value: Optional[Any] = None, + count: bool = False, + allow_from_autoenv: bool = True, + help: Optional[str] = None, + hidden: bool = False, + show_choices: bool = True, + show_envvar: bool = True, + # Choice + case_sensitive: bool = True, + # Numbers + min: Optional[Union[int, float]] = None, + max: Optional[Union[int, float]] = None, + clamp: bool = False, + # DateTime + formats: Optional[List[str]] = None, + # File + mode: Optional[str] = None, + encoding: Optional[str] = None, + errors: Optional[str] = "strict", + lazy: Optional[bool] = None, + atomic: bool = False, + # Path + exists: bool = False, + file_okay: bool = True, + dir_okay: bool = True, + writable: bool = False, + readable: bool = True, + resolve_path: bool = False, + allow_dash: bool = False, + path_type: Union[None, Type[str], Type[bytes]] = None, + # Rich settings + rich_help_panel: Union[str, None] = None, + ): + super().__init__( + default=default, + param_decls=param_decls, + callback=callback, + metavar=metavar, + expose_value=expose_value, + is_eager=is_eager, + envvar=envvar, + shell_complete=shell_complete, + autocompletion=autocompletion, + default_factory=default_factory, + # Custom type + parser=parser, + click_type=click_type, + # TyperArgument + show_default=show_default, + show_choices=show_choices, + show_envvar=show_envvar, + help=help, + hidden=hidden, + # Choice + case_sensitive=case_sensitive, + # Numbers + min=min, + max=max, + clamp=clamp, + # DateTime + formats=formats, + # File + mode=mode, + encoding=encoding, + errors=errors, + lazy=lazy, + atomic=atomic, + # Path + exists=exists, + file_okay=file_okay, + dir_okay=dir_okay, + writable=writable, + readable=readable, + resolve_path=resolve_path, + allow_dash=allow_dash, + path_type=path_type, + # Rich settings + rich_help_panel=rich_help_panel, + ) + if is_flag is not None or flag_value is not None: + import warnings + + warnings.warn( + "The 'is_flag' and 'flag_value' parameters are not supported by Typer " + "and will be removed entirely in a future release.", + DeprecationWarning, + stacklevel=2, + ) + self.prompt = prompt + self.confirmation_prompt = confirmation_prompt + self.prompt_required = prompt_required + self.hide_input = hide_input + self.count = count + self.allow_from_autoenv = allow_from_autoenv + + +class ArgumentInfo(ParameterInfo): + def __init__( + self, + *, + # ParameterInfo + default: Optional[Any] = None, + param_decls: Optional[Sequence[str]] = None, + callback: Optional[Callable[..., Any]] = None, + metavar: Optional[str] = None, + expose_value: bool = True, + is_eager: bool = False, + envvar: Optional[Union[str, List[str]]] = None, + # Note that shell_complete is not fully supported and will be removed in future versions + # TODO: Remove shell_complete in a future version (after 0.16.0) + shell_complete: Optional[ + Callable[ + [click.Context, click.Parameter, str], + Union[List["click.shell_completion.CompletionItem"], List[str]], + ] + ] = None, + autocompletion: Optional[Callable[..., Any]] = None, + default_factory: Optional[Callable[[], Any]] = None, + # Custom type + parser: Optional[Callable[[str], Any]] = None, + click_type: Optional[click.ParamType] = None, + # TyperArgument + show_default: Union[bool, str] = True, + show_choices: bool = True, + show_envvar: bool = True, + help: Optional[str] = None, + hidden: bool = False, + # Choice + case_sensitive: bool = True, + # Numbers + min: Optional[Union[int, float]] = None, + max: Optional[Union[int, float]] = None, + clamp: bool = False, + # DateTime + formats: Optional[List[str]] = None, + # File + mode: Optional[str] = None, + encoding: Optional[str] = None, + errors: Optional[str] = "strict", + lazy: Optional[bool] = None, + atomic: bool = False, + # Path + exists: bool = False, + file_okay: bool = True, + dir_okay: bool = True, + writable: bool = False, + readable: bool = True, + resolve_path: bool = False, + allow_dash: bool = False, + path_type: Union[None, Type[str], Type[bytes]] = None, + # Rich settings + rich_help_panel: Union[str, None] = None, + ): + super().__init__( + default=default, + param_decls=param_decls, + callback=callback, + metavar=metavar, + expose_value=expose_value, + is_eager=is_eager, + envvar=envvar, + shell_complete=shell_complete, + autocompletion=autocompletion, + default_factory=default_factory, + # Custom type + parser=parser, + click_type=click_type, + # TyperArgument + show_default=show_default, + show_choices=show_choices, + show_envvar=show_envvar, + help=help, + hidden=hidden, + # Choice + case_sensitive=case_sensitive, + # Numbers + min=min, + max=max, + clamp=clamp, + # DateTime + formats=formats, + # File + mode=mode, + encoding=encoding, + errors=errors, + lazy=lazy, + atomic=atomic, + # Path + exists=exists, + file_okay=file_okay, + dir_okay=dir_okay, + writable=writable, + readable=readable, + resolve_path=resolve_path, + allow_dash=allow_dash, + path_type=path_type, + # Rich settings + rich_help_panel=rich_help_panel, + ) + + +class ParamMeta: + empty = inspect.Parameter.empty + + def __init__( + self, + *, + name: str, + default: Any = inspect.Parameter.empty, + annotation: Any = inspect.Parameter.empty, + ) -> None: + self.name = name + self.default = default + self.annotation = annotation + + +class DeveloperExceptionConfig: + def __init__( + self, + *, + pretty_exceptions_enable: bool = True, + pretty_exceptions_show_locals: bool = True, + pretty_exceptions_short: bool = True, + ) -> None: + self.pretty_exceptions_enable = pretty_exceptions_enable + self.pretty_exceptions_show_locals = pretty_exceptions_show_locals + self.pretty_exceptions_short = pretty_exceptions_short + + +class TyperPath(click.Path): + # Overwrite Click's behaviour to be compatible with Typer's autocompletion system + def shell_complete( + self, ctx: click.Context, param: click.Parameter, incomplete: str + ) -> List[click.shell_completion.CompletionItem]: + """Return an empty list so that the autocompletion functionality + will work properly from the commandline. + """ + return [] diff --git a/venv/lib/python3.10/site-packages/typer/params.py b/venv/lib/python3.10/site-packages/typer/params.py new file mode 100644 index 0000000000000000000000000000000000000000..66c2b32d3e35e313454ed3dcbaac8ac9bf71d14d --- /dev/null +++ b/venv/lib/python3.10/site-packages/typer/params.py @@ -0,0 +1,479 @@ +from typing import TYPE_CHECKING, Any, Callable, List, Optional, Type, Union, overload + +import click + +from .models import ArgumentInfo, OptionInfo + +if TYPE_CHECKING: # pragma: no cover + import click.shell_completion + + +# Overload for Option created with custom type 'parser' +@overload +def Option( + # Parameter + default: Optional[Any] = ..., + *param_decls: str, + callback: Optional[Callable[..., Any]] = None, + metavar: Optional[str] = None, + expose_value: bool = True, + is_eager: bool = False, + envvar: Optional[Union[str, List[str]]] = None, + # Note that shell_complete is not fully supported and will be removed in future versions + # TODO: Remove shell_complete in a future version (after 0.16.0) + shell_complete: Optional[ + Callable[ + [click.Context, click.Parameter, str], + Union[List["click.shell_completion.CompletionItem"], List[str]], + ] + ] = None, + autocompletion: Optional[Callable[..., Any]] = None, + default_factory: Optional[Callable[[], Any]] = None, + # Custom type + parser: Optional[Callable[[str], Any]] = None, + # Option + show_default: Union[bool, str] = True, + prompt: Union[bool, str] = False, + confirmation_prompt: bool = False, + prompt_required: bool = True, + hide_input: bool = False, + # TODO: remove is_flag and flag_value in a future release + is_flag: Optional[bool] = None, + flag_value: Optional[Any] = None, + count: bool = False, + allow_from_autoenv: bool = True, + help: Optional[str] = None, + hidden: bool = False, + show_choices: bool = True, + show_envvar: bool = True, + # Choice + case_sensitive: bool = True, + # Numbers + min: Optional[Union[int, float]] = None, + max: Optional[Union[int, float]] = None, + clamp: bool = False, + # DateTime + formats: Optional[List[str]] = None, + # File + mode: Optional[str] = None, + encoding: Optional[str] = None, + errors: Optional[str] = "strict", + lazy: Optional[bool] = None, + atomic: bool = False, + # Path + exists: bool = False, + file_okay: bool = True, + dir_okay: bool = True, + writable: bool = False, + readable: bool = True, + resolve_path: bool = False, + allow_dash: bool = False, + path_type: Union[None, Type[str], Type[bytes]] = None, + # Rich settings + rich_help_panel: Union[str, None] = None, +) -> Any: ... + + +# Overload for Option created with custom type 'click_type' +@overload +def Option( + # Parameter + default: Optional[Any] = ..., + *param_decls: str, + callback: Optional[Callable[..., Any]] = None, + metavar: Optional[str] = None, + expose_value: bool = True, + is_eager: bool = False, + envvar: Optional[Union[str, List[str]]] = None, + # Note that shell_complete is not fully supported and will be removed in future versions + # TODO: Remove shell_complete in a future version (after 0.16.0) + shell_complete: Optional[ + Callable[ + [click.Context, click.Parameter, str], + Union[List["click.shell_completion.CompletionItem"], List[str]], + ] + ] = None, + autocompletion: Optional[Callable[..., Any]] = None, + default_factory: Optional[Callable[[], Any]] = None, + # Custom type + click_type: Optional[click.ParamType] = None, + # Option + show_default: Union[bool, str] = True, + prompt: Union[bool, str] = False, + confirmation_prompt: bool = False, + prompt_required: bool = True, + hide_input: bool = False, + # TODO: remove is_flag and flag_value in a future release + is_flag: Optional[bool] = None, + flag_value: Optional[Any] = None, + count: bool = False, + allow_from_autoenv: bool = True, + help: Optional[str] = None, + hidden: bool = False, + show_choices: bool = True, + show_envvar: bool = True, + # Choice + case_sensitive: bool = True, + # Numbers + min: Optional[Union[int, float]] = None, + max: Optional[Union[int, float]] = None, + clamp: bool = False, + # DateTime + formats: Optional[List[str]] = None, + # File + mode: Optional[str] = None, + encoding: Optional[str] = None, + errors: Optional[str] = "strict", + lazy: Optional[bool] = None, + atomic: bool = False, + # Path + exists: bool = False, + file_okay: bool = True, + dir_okay: bool = True, + writable: bool = False, + readable: bool = True, + resolve_path: bool = False, + allow_dash: bool = False, + path_type: Union[None, Type[str], Type[bytes]] = None, + # Rich settings + rich_help_panel: Union[str, None] = None, +) -> Any: ... + + +def Option( + # Parameter + default: Optional[Any] = ..., + *param_decls: str, + callback: Optional[Callable[..., Any]] = None, + metavar: Optional[str] = None, + expose_value: bool = True, + is_eager: bool = False, + envvar: Optional[Union[str, List[str]]] = None, + # Note that shell_complete is not fully supported and will be removed in future versions + # TODO: Remove shell_complete in a future version (after 0.16.0) + shell_complete: Optional[ + Callable[ + [click.Context, click.Parameter, str], + Union[List["click.shell_completion.CompletionItem"], List[str]], + ] + ] = None, + autocompletion: Optional[Callable[..., Any]] = None, + default_factory: Optional[Callable[[], Any]] = None, + # Custom type + parser: Optional[Callable[[str], Any]] = None, + click_type: Optional[click.ParamType] = None, + # Option + show_default: Union[bool, str] = True, + prompt: Union[bool, str] = False, + confirmation_prompt: bool = False, + prompt_required: bool = True, + hide_input: bool = False, + # TODO: remove is_flag and flag_value in a future release + is_flag: Optional[bool] = None, + flag_value: Optional[Any] = None, + count: bool = False, + allow_from_autoenv: bool = True, + help: Optional[str] = None, + hidden: bool = False, + show_choices: bool = True, + show_envvar: bool = True, + # Choice + case_sensitive: bool = True, + # Numbers + min: Optional[Union[int, float]] = None, + max: Optional[Union[int, float]] = None, + clamp: bool = False, + # DateTime + formats: Optional[List[str]] = None, + # File + mode: Optional[str] = None, + encoding: Optional[str] = None, + errors: Optional[str] = "strict", + lazy: Optional[bool] = None, + atomic: bool = False, + # Path + exists: bool = False, + file_okay: bool = True, + dir_okay: bool = True, + writable: bool = False, + readable: bool = True, + resolve_path: bool = False, + allow_dash: bool = False, + path_type: Union[None, Type[str], Type[bytes]] = None, + # Rich settings + rich_help_panel: Union[str, None] = None, +) -> Any: + return OptionInfo( + # Parameter + default=default, + param_decls=param_decls, + callback=callback, + metavar=metavar, + expose_value=expose_value, + is_eager=is_eager, + envvar=envvar, + shell_complete=shell_complete, + autocompletion=autocompletion, + default_factory=default_factory, + # Custom type + parser=parser, + click_type=click_type, + # Option + show_default=show_default, + prompt=prompt, + confirmation_prompt=confirmation_prompt, + prompt_required=prompt_required, + hide_input=hide_input, + is_flag=is_flag, + flag_value=flag_value, + count=count, + allow_from_autoenv=allow_from_autoenv, + help=help, + hidden=hidden, + show_choices=show_choices, + show_envvar=show_envvar, + # Choice + case_sensitive=case_sensitive, + # Numbers + min=min, + max=max, + clamp=clamp, + # DateTime + formats=formats, + # File + mode=mode, + encoding=encoding, + errors=errors, + lazy=lazy, + atomic=atomic, + # Path + exists=exists, + file_okay=file_okay, + dir_okay=dir_okay, + writable=writable, + readable=readable, + resolve_path=resolve_path, + allow_dash=allow_dash, + path_type=path_type, + # Rich settings + rich_help_panel=rich_help_panel, + ) + + +# Overload for Argument created with custom type 'parser' +@overload +def Argument( + # Parameter + default: Optional[Any] = ..., + *, + callback: Optional[Callable[..., Any]] = None, + metavar: Optional[str] = None, + expose_value: bool = True, + is_eager: bool = False, + envvar: Optional[Union[str, List[str]]] = None, + # Note that shell_complete is not fully supported and will be removed in future versions + # TODO: Remove shell_complete in a future version (after 0.16.0) + shell_complete: Optional[ + Callable[ + [click.Context, click.Parameter, str], + Union[List["click.shell_completion.CompletionItem"], List[str]], + ] + ] = None, + autocompletion: Optional[Callable[..., Any]] = None, + default_factory: Optional[Callable[[], Any]] = None, + # Custom type + parser: Optional[Callable[[str], Any]] = None, + # TyperArgument + show_default: Union[bool, str] = True, + show_choices: bool = True, + show_envvar: bool = True, + help: Optional[str] = None, + hidden: bool = False, + # Choice + case_sensitive: bool = True, + # Numbers + min: Optional[Union[int, float]] = None, + max: Optional[Union[int, float]] = None, + clamp: bool = False, + # DateTime + formats: Optional[List[str]] = None, + # File + mode: Optional[str] = None, + encoding: Optional[str] = None, + errors: Optional[str] = "strict", + lazy: Optional[bool] = None, + atomic: bool = False, + # Path + exists: bool = False, + file_okay: bool = True, + dir_okay: bool = True, + writable: bool = False, + readable: bool = True, + resolve_path: bool = False, + allow_dash: bool = False, + path_type: Union[None, Type[str], Type[bytes]] = None, + # Rich settings + rich_help_panel: Union[str, None] = None, +) -> Any: ... + + +# Overload for Argument created with custom type 'click_type' +@overload +def Argument( + # Parameter + default: Optional[Any] = ..., + *, + callback: Optional[Callable[..., Any]] = None, + metavar: Optional[str] = None, + expose_value: bool = True, + is_eager: bool = False, + envvar: Optional[Union[str, List[str]]] = None, + # Note that shell_complete is not fully supported and will be removed in future versions + # TODO: Remove shell_complete in a future version (after 0.16.0) + shell_complete: Optional[ + Callable[ + [click.Context, click.Parameter, str], + Union[List["click.shell_completion.CompletionItem"], List[str]], + ] + ] = None, + autocompletion: Optional[Callable[..., Any]] = None, + default_factory: Optional[Callable[[], Any]] = None, + # Custom type + click_type: Optional[click.ParamType] = None, + # TyperArgument + show_default: Union[bool, str] = True, + show_choices: bool = True, + show_envvar: bool = True, + help: Optional[str] = None, + hidden: bool = False, + # Choice + case_sensitive: bool = True, + # Numbers + min: Optional[Union[int, float]] = None, + max: Optional[Union[int, float]] = None, + clamp: bool = False, + # DateTime + formats: Optional[List[str]] = None, + # File + mode: Optional[str] = None, + encoding: Optional[str] = None, + errors: Optional[str] = "strict", + lazy: Optional[bool] = None, + atomic: bool = False, + # Path + exists: bool = False, + file_okay: bool = True, + dir_okay: bool = True, + writable: bool = False, + readable: bool = True, + resolve_path: bool = False, + allow_dash: bool = False, + path_type: Union[None, Type[str], Type[bytes]] = None, + # Rich settings + rich_help_panel: Union[str, None] = None, +) -> Any: ... + + +def Argument( + # Parameter + default: Optional[Any] = ..., + *, + callback: Optional[Callable[..., Any]] = None, + metavar: Optional[str] = None, + expose_value: bool = True, + is_eager: bool = False, + envvar: Optional[Union[str, List[str]]] = None, + # Note that shell_complete is not fully supported and will be removed in future versions + # TODO: Remove shell_complete in a future version (after 0.16.0) + shell_complete: Optional[ + Callable[ + [click.Context, click.Parameter, str], + Union[List["click.shell_completion.CompletionItem"], List[str]], + ] + ] = None, + autocompletion: Optional[Callable[..., Any]] = None, + default_factory: Optional[Callable[[], Any]] = None, + # Custom type + parser: Optional[Callable[[str], Any]] = None, + click_type: Optional[click.ParamType] = None, + # TyperArgument + show_default: Union[bool, str] = True, + show_choices: bool = True, + show_envvar: bool = True, + help: Optional[str] = None, + hidden: bool = False, + # Choice + case_sensitive: bool = True, + # Numbers + min: Optional[Union[int, float]] = None, + max: Optional[Union[int, float]] = None, + clamp: bool = False, + # DateTime + formats: Optional[List[str]] = None, + # File + mode: Optional[str] = None, + encoding: Optional[str] = None, + errors: Optional[str] = "strict", + lazy: Optional[bool] = None, + atomic: bool = False, + # Path + exists: bool = False, + file_okay: bool = True, + dir_okay: bool = True, + writable: bool = False, + readable: bool = True, + resolve_path: bool = False, + allow_dash: bool = False, + path_type: Union[None, Type[str], Type[bytes]] = None, + # Rich settings + rich_help_panel: Union[str, None] = None, +) -> Any: + return ArgumentInfo( + # Parameter + default=default, + # Arguments can only have one param declaration + # it will be generated from the param name + param_decls=None, + callback=callback, + metavar=metavar, + expose_value=expose_value, + is_eager=is_eager, + envvar=envvar, + shell_complete=shell_complete, + autocompletion=autocompletion, + default_factory=default_factory, + # Custom type + parser=parser, + click_type=click_type, + # TyperArgument + show_default=show_default, + show_choices=show_choices, + show_envvar=show_envvar, + help=help, + hidden=hidden, + # Choice + case_sensitive=case_sensitive, + # Numbers + min=min, + max=max, + clamp=clamp, + # DateTime + formats=formats, + # File + mode=mode, + encoding=encoding, + errors=errors, + lazy=lazy, + atomic=atomic, + # Path + exists=exists, + file_okay=file_okay, + dir_okay=dir_okay, + writable=writable, + readable=readable, + resolve_path=resolve_path, + allow_dash=allow_dash, + path_type=path_type, + # Rich settings + rich_help_panel=rich_help_panel, + ) diff --git a/venv/lib/python3.10/site-packages/typer/py.typed b/venv/lib/python3.10/site-packages/typer/py.typed new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.10/site-packages/typer/rich_utils.py b/venv/lib/python3.10/site-packages/typer/rich_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..4b6c5a840f2aa9b52c788ea998d906e196c4224a --- /dev/null +++ b/venv/lib/python3.10/site-packages/typer/rich_utils.py @@ -0,0 +1,741 @@ +# Extracted and modified from https://github.com/ewels/rich-click + +import inspect +import io +import sys +from collections import defaultdict +from gettext import gettext as _ +from os import getenv +from typing import Any, DefaultDict, Dict, Iterable, List, Optional, Union + +import click +from rich import box +from rich.align import Align +from rich.columns import Columns +from rich.console import Console, RenderableType, group +from rich.emoji import Emoji +from rich.highlighter import RegexHighlighter +from rich.markdown import Markdown +from rich.padding import Padding +from rich.panel import Panel +from rich.table import Table +from rich.text import Text +from rich.theme import Theme + +if sys.version_info >= (3, 9): + from typing import Literal +else: + from typing_extensions import Literal + +# Default styles +STYLE_OPTION = "bold cyan" +STYLE_SWITCH = "bold green" +STYLE_NEGATIVE_OPTION = "bold magenta" +STYLE_NEGATIVE_SWITCH = "bold red" +STYLE_METAVAR = "bold yellow" +STYLE_METAVAR_SEPARATOR = "dim" +STYLE_USAGE = "yellow" +STYLE_USAGE_COMMAND = "bold" +STYLE_DEPRECATED = "red" +STYLE_DEPRECATED_COMMAND = "dim" +STYLE_HELPTEXT_FIRST_LINE = "" +STYLE_HELPTEXT = "dim" +STYLE_OPTION_HELP = "" +STYLE_OPTION_DEFAULT = "dim" +STYLE_OPTION_ENVVAR = "dim yellow" +STYLE_REQUIRED_SHORT = "red" +STYLE_REQUIRED_LONG = "dim red" +STYLE_OPTIONS_PANEL_BORDER = "dim" +ALIGN_OPTIONS_PANEL: Literal["left", "center", "right"] = "left" +STYLE_OPTIONS_TABLE_SHOW_LINES = False +STYLE_OPTIONS_TABLE_LEADING = 0 +STYLE_OPTIONS_TABLE_PAD_EDGE = False +STYLE_OPTIONS_TABLE_PADDING = (0, 1) +STYLE_OPTIONS_TABLE_BOX = "" +STYLE_OPTIONS_TABLE_ROW_STYLES = None +STYLE_OPTIONS_TABLE_BORDER_STYLE = None +STYLE_COMMANDS_PANEL_BORDER = "dim" +ALIGN_COMMANDS_PANEL: Literal["left", "center", "right"] = "left" +STYLE_COMMANDS_TABLE_SHOW_LINES = False +STYLE_COMMANDS_TABLE_LEADING = 0 +STYLE_COMMANDS_TABLE_PAD_EDGE = False +STYLE_COMMANDS_TABLE_PADDING = (0, 1) +STYLE_COMMANDS_TABLE_BOX = "" +STYLE_COMMANDS_TABLE_ROW_STYLES = None +STYLE_COMMANDS_TABLE_BORDER_STYLE = None +STYLE_COMMANDS_TABLE_FIRST_COLUMN = "bold cyan" +STYLE_ERRORS_PANEL_BORDER = "red" +ALIGN_ERRORS_PANEL: Literal["left", "center", "right"] = "left" +STYLE_ERRORS_SUGGESTION = "dim" +STYLE_ABORTED = "red" +_TERMINAL_WIDTH = getenv("TERMINAL_WIDTH") +MAX_WIDTH = int(_TERMINAL_WIDTH) if _TERMINAL_WIDTH else None +COLOR_SYSTEM: Optional[Literal["auto", "standard", "256", "truecolor", "windows"]] = ( + "auto" # Set to None to disable colors +) +_TYPER_FORCE_DISABLE_TERMINAL = getenv("_TYPER_FORCE_DISABLE_TERMINAL") +FORCE_TERMINAL = ( + True + if getenv("GITHUB_ACTIONS") or getenv("FORCE_COLOR") or getenv("PY_COLORS") + else None +) +if _TYPER_FORCE_DISABLE_TERMINAL: + FORCE_TERMINAL = False + +# Fixed strings +DEPRECATED_STRING = _("(deprecated) ") +DEFAULT_STRING = _("[default: {}]") +ENVVAR_STRING = _("[env var: {}]") +REQUIRED_SHORT_STRING = "*" +REQUIRED_LONG_STRING = _("[required]") +RANGE_STRING = " [{}]" +ARGUMENTS_PANEL_TITLE = _("Arguments") +OPTIONS_PANEL_TITLE = _("Options") +COMMANDS_PANEL_TITLE = _("Commands") +ERRORS_PANEL_TITLE = _("Error") +ABORTED_TEXT = _("Aborted.") +RICH_HELP = _("Try [blue]'{command_path} {help_option}'[/] for help.") + +MARKUP_MODE_MARKDOWN = "markdown" +MARKUP_MODE_RICH = "rich" +_RICH_HELP_PANEL_NAME = "rich_help_panel" + +MarkupMode = Literal["markdown", "rich", None] + + +# Rich regex highlighter +class OptionHighlighter(RegexHighlighter): + """Highlights our special options.""" + + highlights = [ + r"(^|\W)(?P\-\w+)(?![a-zA-Z0-9])", + r"(^|\W)(?P